const TChar* TTextLayout::GetLineText(uint32 line, STextOffset& outLength, bool ignoreWrappedLines) const { ASSERT(line >= 0 && line < fLineCount); LineRec& rec = GetLineRec(line, ignoreWrappedLines); const TChar* result = fText + rec.textOffset; const TChar* endText = FindLineBreak(result, fText + fTextLength); outLength = (endText ? endText - result : fText + fTextLength - result); if (outLength > 0) { if (result[outLength - 1] == kLineEnd10) { if (outLength > 1 && result[outLength - 2] == kLineEnd13) outLength -= 2; else outLength -= 1; } else if (result[outLength - 1] == kLineEnd13) outLength -= 1; } return result; }
//applies word wrapping to the string so that all characters should be in the range of 0..nWidth in //the X axis and in the range [0...+inf] in the Y axis. This undoes any previous word wrapping or //formatting bool CTextureString::WordWrap(uint32 nWidth) { //fail if we have no associated glyphs if(GetTextureImage() == NULL) { return (m_nNumCharacters == 0); } //keep track of our current position uint32 nCurrX = 0; uint32 nCurrY = 0; //determine the height of a single row of text uint32 nRowHeight = GetTextureImage()->GetRowHeight(); //the index into the character where this current row started uint32 nRowStart = 0; //this flag indicates that the character needs to be reset. This is set when a line break //is encountered so that the next character can begin on a new line bool bResetRow = false; //all characters should default to being visible. Then as they break rows or //other rules are applied, they can be made invisible uint32 nCurrChar; for(nCurrChar = 0; nCurrChar < m_nNumCharacters; nCurrChar++) { m_pCharacters[nCurrChar].m_bVisible = true; } //we now need to lay out each character for(nCurrChar = 0; nCurrChar < m_nNumCharacters; nCurrChar++) { //see if we are following a line break and need to reset the row if(bResetRow) { nCurrX = 0; nCurrY += nRowHeight; //our next row will start on the nRowStart = nCurrChar; //clear the flag so it won't do it again bResetRow = false; } //see if this character will fit onto this row CTextureStringChar* pChar = &m_pCharacters[nCurrChar]; uint32 nCurrCharWidth = pChar->m_pGlyph->m_nTotalWidth; //see if this is a hard line break and we have to move onto the next line if(IsHardLineBreak(m_pszString, nCurrChar)) { //we need to reset the row bResetRow = true; //we also want to make this line break character invisible m_pCharacters[nCurrChar].m_bVisible = false; } //see if we are too wide (note the >= is because 0 is counted, so the limit is not included) //but we must also ensure that at least one character is placed per line else if((nCurrChar != nRowStart) && (nCurrX + nCurrCharWidth >= nWidth)) { //we are too wide, lets move down. We need to determine where we can do a break by scanning //backwards on this row and determining where to split, but only if it isn't a hard line break uint32 nSplitChar = FindLineBreak(m_pszString, nRowStart + 1, nCurrChar); //update our cursor position to begin on the next row nCurrX = 0; nCurrY += nRowHeight; //and update our row beginning character nRowStart = nSplitChar + 1; //and we now need to move all the characters at the break onto the next line for(uint32 nMoveChar = nSplitChar + 1; nMoveChar < nCurrChar; nMoveChar++) { CTextureStringChar* pMoveChar = &m_pCharacters[nMoveChar]; pMoveChar->m_nXPos = nCurrX; pMoveChar->m_nYPos = nCurrY; //move the cursor past this character nCurrX += pMoveChar->m_pGlyph->m_nTotalWidth; } //also make the split character invisible m_pCharacters[nSplitChar].m_bVisible = false; } //now that we know there is room, place the character pChar->m_nXPos = nCurrX; pChar->m_nYPos = nCurrY; //update the cursor position nCurrX += nCurrCharWidth; } //update our new extents UpdateExtents(); //success return true; }
/*---------------------------------------------------------------------------------------------- Make a segment by finding a suitable break point in the specified range of text. Note that it is appropriate for line layout to use this routine even if putting text on a single line, because an old writing system may take advantage of line layout to handle direction changes and style changes and generate multiple segments even on one line. For such layouts, pass a large dxMaxWidth, but still expect possibly multiple segments. Arguments: pgjus NULL if no justification will ever be needed for the resulting segment ichMinNew index of the first char in the text that is of interest ichLimText index of the last char in the text that is of interest (+ 1) ichLimBacktrack index of last char that may be included in the segment. Generally the same as ichLimText unless backtracking. fNeedFinalBreak fStartLine seg is logically first on line? dxMaxWidth whatever coords pvg is using lbPref try for longest seg of this weight lbMax max if no preferred break possible twsh how we are handling trailing white-space fParaRtoL overall paragraph direction ppsegRet segment produced, or null if nothing fits pdichLimSeg offset to last char of segment, first of next if any pdxWidth of new segment, if any pest what caused the segment to end? cbPrev (not used) pbPrevSegDat (not used) cbNextMax (not used) pbNextSegDat (not used) pcbNextSegDat (*pcbNextSegDat always set to zero) pdichContext (*pdichContext always set to zero) TODO 1441 (SharonC): handle fParaRtoL; specifically, if the paragraph direction is right-to-left, trailing white-space characters should be reversed. ----------------------------------------------------------------------------------------------*/ STDMETHODIMP RomRenderEngine::FindBreakPoint( IVwGraphics * pvg, IVwTextSource * pts, IVwJustifier * pvjus, int ichMinNew, int ichLimText, int ichLimBacktrack, ComBool fNeedFinalBreak, ComBool fStartLine, int dxMaxWidth, LgLineBreak lbPref, LgLineBreak lbMax, LgTrailingWsHandling twsh, ComBool fParaRtoL, ILgSegment ** ppsegRet, int * pdichLimSeg, int * pdxWidth, LgEndSegmentType * pest, ILgSegment * psegPrev) { BEGIN_COM_METHOD ChkComArgPtr(pvg); ChkComArgPtr(pts); ChkComOutPtr(ppsegRet); ChkComOutPtr(pdichLimSeg); ChkComArgPtr(pdxWidth); ChkComArgPtr(pest); ChkComArgPtrN(psegPrev); #define INIT_BUF_SIZE 1000 OLECHAR rgchBuf[INIT_BUF_SIZE]; // Unlikely segments are longer than this... memset(rgchBuf, 0, sizeof(rgchBuf)); Vector<OLECHAR> vch; // Use as buffer if 1000 is not enough OLECHAR * prgch = rgchBuf; // Use on-stack variable if big enough int cchBuf = INIT_BUF_SIZE; // chars available in prgch; INIT_BUF_SIZE or vch.Size(). int ichForceBreak; byte rglbsBuf[INIT_BUF_SIZE]; // line break status Vector<byte> vlbs; byte * prglbsForString = rglbsBuf; // switch to resized vector if needed. RomRenderSegmentPtr qrrs; LgCharRenderProps chrp; int ichMinRun, ichLimRun; CheckHr(pts->GetCharProps(ichMinNew, &chrp, &ichMinRun, &ichLimRun)); // Ws and old writing system for the segment; don't use chars with different ones. int ws = chrp.ws; int nDirDepth = chrp.nDirDepth; if (fParaRtoL) nDirDepth += 2; //Assert((nDirDepth % 2) == 0); // left-to-right // Get a char props engine AssertPtr(m_qwsf); ILgCharacterPropertyEnginePtr qcpe; CheckHr(m_qwsf->get_CharPropEngine(ws, &qcpe)); // The maximum number of characters we will fetch and measure, beyond what we // know we need. This should be less than the length of rgchBuf. #define MAX_MEASURE 100 EndAvailType eat; int ichLimSegCur; // Current proposal for where seg might end // We aren't allowed to include characters at or beyond ichLimBacktrack in the // segment. But it's worth getting one more, if more are available, so we can // check whether an line break at the very end is allowed. // Note that this time we're getting at most MAX_MEASURE chars, so we don't // have to check for buffer overflow. GetAvailChars(pts, ws, ichMinNew, ichLimBacktrack, MAX_MEASURE, twsh, qcpe, prgch, &ichLimSegCur, &eat); // Measure all the characters we got, or all we are allowed to use, whichever // is less. int cchMeasure = ichLimSegCur - ichMinNew; // Make a segment to use in measuring things; if all goes well use it for the // final result. qrrs.Attach(NewObj RomRenderSegment(pts, this, cchMeasure, klbNoBreak, klbNoBreak, true)); qrrs->SetDirectionInfo(nDirDepth, (twsh == ktwshOnlyWs)); // If a forced empty segment, stop here if (ichLimBacktrack <= ichMinNew) { *pest = kestNoMore; *pdxWidth = 0; *ppsegRet = qrrs.Detach(); *pdichLimSeg = 0; return S_OK; } int dxWidthMeasure = 0; // width of part of run initially measured. CheckHr(qrrs->get_Width(ichMinNew, pvg, &dxWidthMeasure)); int cchLineEst; // Calculate the estimated number of characters to fill line. int cchMaxText = ichLimBacktrack - ichMinNew; #ifdef ICU_LINEBREAKING int ichTempBreak; LgLineBreak lbWeight; LgGeneralCharCategory cc; #endif /*ICU_LINEBREAKING*/ // Now, if it is a short text or already contains all candidate characters, // we have the actual width; otherwise, we have // a measurement of MAX_MEASURE characters that we can use in guessing how many we // need. If we have less than we need, increase the limit until we have more. // Note that we need the <= here because, if we have exactly filled the width, we // will consider putting a break after the MAX_MEASURE'th character. We need to // fetch at least one more character in order to have valid line-break info // about the last one that might be put on the line. while (eat == keatMax && ichLimSegCur < ichLimBacktrack && dxWidthMeasure <= dxMaxWidth) { // Compute the number we estimate will fill the line (min the number we have). // Don't let this estimate be zero unless we actually have no characters available. if (!dxWidthMeasure) cchLineEst = min(max(1, dxMaxWidth * cchMeasure), cchMaxText); else // Use MulDiv to avoid overflow, likely when dxMaxWidth is INT_MAX cchLineEst = min(max(1, MulDiv(dxMaxWidth, cchMeasure, dxWidthMeasure)), cchMaxText); // Make sure the buffer contains MAX_MEASURE more than that. First make sure // there is room for them. // We have already loaded out to ichLimSegCur, so use that as a start position. // Already in the buffer are pts[ichMinNew...ichLimSegCur] starting at prgch. // We need room for cchLineEst + MAX_MEASURE, but add a few more to buffer size // so we are always safe adding one or two for end of line testing. if (cchLineEst + MAX_MEASURE + 5 >= cchBuf) { // Allocate memory for the characters in the vector, or resize it. // Allocate extra in case we keep adding later. cchBuf = cchLineEst + MAX_MEASURE + 500; vch.Resize(cchBuf); if (prgch == rgchBuf) MoveItems(rgchBuf, vch.Begin(), ichLimSegCur - ichMinNew); prgch = vch.Begin(); vlbs.Clear(); // no need to copy, nothing in it yet. vlbs.Resize(cchBuf); prglbsForString = vlbs.Begin(); } GetAvailChars(pts, ws, ichLimSegCur, ichLimBacktrack, ichMinNew + cchLineEst + MAX_MEASURE - ichLimSegCur, twsh, qcpe, prgch + ichLimSegCur - ichMinNew, &ichLimSegCur, &eat); cchMeasure = ichLimSegCur - ichMinNew; qrrs->SetLim(cchMeasure); qrrs->SetDirectionInfo(nDirDepth, (twsh == ktwshOnlyWs)); CheckHr(qrrs->get_Width(ichMinNew, pvg, &dxWidthMeasure)); } #ifdef ICU_LINEBREAKING //updating the BreakIterator text for calculating line breaks later CheckHr(qcpe->put_LineBreakText(prgch, ichLimSegCur-ichMinNew)); #endif /*ICU_LINEBREAKING*/ // Update this with the results of the latest measurement. if (!dxWidthMeasure) cchLineEst = min(max(1, dxMaxWidth * cchMeasure), cchMaxText); else // Use MulDiv to avoid overflow, likely when dxMaxWidth is INT_MAX cchLineEst = min(max(1, MulDiv(dxMaxWidth, cchMeasure, dxWidthMeasure)), cchMaxText); // Now we have measured either all the characters we are allowed, or enough to // fill the line. if (dxWidthMeasure <= dxMaxWidth) { // we will most likely answer the segment we just found *pdxWidth = dxWidthMeasure; *pdichLimSeg = min(ichLimSegCur - ichMinNew, ichLimBacktrack - ichMinNew); if (ichLimSegCur == ichLimText) { // the whole text we were asked to use fit *pest = kestNoMore; *ppsegRet = qrrs.Detach(); return S_OK; } if (ichLimSegCur == ichLimBacktrack) { // Everything allowed fits, but, since this is not the real end of the text, // we have to consider whether this is a valid line break. We will need one // more character in the buffer. We made sure above there is room for it. CheckHr(pts->Fetch(ichLimSegCur, ichLimSegCur + 1, prgch + ichLimSegCur - ichMinNew)); // We want to get line break info for the character at ichLimSegCur - 1, relative to // the string as a whole. Offset relative to buffer is less by ichMinNew. int ichBuf = ichLimSegCur - ichMinNew - 1; #ifndef ICU_LINEBREAKING CheckHr(qcpe->GetLineBreakInfo(prgch, ichLimSegCur - ichMinNew + 1, ichBuf, ichBuf + 1, prglbsForString, &ichForceBreak)); #else //updating the BreakIterator text for calculating line breaks CheckHr(qcpe->put_LineBreakText(prgch, ichLimSegCur-ichMinNew+1)); CheckHr(qcpe->LineBreakAfter(ichBuf, &ichTempBreak, &lbWeight)); // Previous code was wrong twice: // 1. did not offset by ichMinNew // 2. did not allow GetLineBreakInfo to use info about characters earlier in run. // Also it got one more result than we need. // CheckHr(qcpe->GetLineBreakInfo(prgch + ichLimSegCur - 1, 2, 0, 2, prglbsForString, // &ichForceBreak)); CheckHr(qcpe->get_GeneralCategory(prgch[ichBuf], &cc)); if ((ichTempBreak == ichBuf+1) && (cc == kccZs)) #endif /*ICU_LINEBREAKING*/ #ifndef ICU_LINEBREAKING if (prglbsForString[0] & kflbsBrk) #endif /*ICU_LINEBREAKING*/ { // Backtrack posn is also a line break *pest = kestOkayBreak; *ppsegRet = qrrs.Detach(); return S_OK; } else if (!fNeedFinalBreak) { // Backtrack position is not a break, but we may return it anyway // This probably never happens but include it for consistency. *pest = kestBadBreak; *ppsegRet = qrrs.Detach(); return S_OK; } // If we get here we must search for an earlier break. goto LFindEarlierBreak; } if (eat == keatBreak || eat == keatOnlyWs) { *pest = (eat == keatOnlyWs) ? kestOkayBreak : kestHardBreak; // a segment up to a hard return or similar fit // Return the segment we got (which may be an empty segment) *ppsegRet = qrrs.Detach(); return S_OK; } // Only one reason remains for us to have stopped while not filling the width Assert(eat == keatNewWs); // See whether the WS break is also a line break. // We want to get line break info for the character at ichLimSegCur - 1, relative to // the string as a whole. Offset relative to buffer is less by ichMinNew. int ichBuf = ichLimSegCur - ichMinNew - 1; #ifndef ICU_LINEBREAKING CheckHr(qcpe->GetLineBreakInfo(prgch, ichLimSegCur - ichMinNew, ichBuf, ichBuf + 1, prglbsForString, &ichForceBreak)); #else CheckHr(qcpe->LineBreakAfter(ichBuf, &ichTempBreak, &lbWeight)); CheckHr(qcpe->get_GeneralCategory(prgch[ichBuf], &cc)); if ((ichTempBreak == ichBuf+1) && (cc == kccZs)) #endif /*ICU_LINEBREAKING*/ #ifndef ICU_LINEBREAKING if (prglbsForString[0] & kflbsBrk) #endif /*ICU_LINEBREAKING*/ { // WS break is also a line break *pest = kestOkayBreak; *ppsegRet = qrrs.Detach(); return S_OK; } else { if (!fNeedFinalBreak) { // Though not a line break, return it and see if we can fit some // of next WS on to make things OK. *pest = kestWsBreak; *ppsegRet = qrrs.Detach(); return S_OK; } // otherwise, fall through to find a valid line break earlier. } } LFindEarlierBreak: // ENHANCE JohnT: arguably we ought to look further back to be absolutely sure // of line break props. It makes a difference only if old writing system changes // in middle of a run of spaces or combining marks, and even then, the extra spaces // will fit so we shouldn't make a spurious break, and the run of CMs should only // get broken if the column is too narrow for a whole word. I think we can live // wit this. // Note: we can ignore ichForceBreak here because we made sure in GetAvailChars // that there aren't any break characters before ichLimSegCur. CheckHr(qcpe->GetLineBreakInfo(prgch, ichLimSegCur - ichMinNew, 0, ichLimSegCur - ichMinNew, prglbsForString, &ichForceBreak)); // If we got here, we will have to make a line break within the run, somewhere // before ichLimSegCur, since stopping there either makes it too wide, or violates // fNeedFinalBreak. *pest = kestMoreLines; // broken segment will certainly end line CheckHr(qrrs->put_EndLine(ichMinNew, pvg, true)); // Get info about line break possibilities // Figure the best kind of line break we can do at all, // starting at lbPref LgLineBreak lbTry; lbTry = lbPref; // sep from decl because of goto int ichBreak; // index in prglbsForString of character after which break is allowed at // given level int ichDim; // index in prglbsForString of last character before ichBreak which is // not a space, ichBreak = -1; // don't init in decl because of goto int dxBreakWidth; // loop over possible levels of badness of break, from lbPref to lbMax. for (;;) { if (!(lbTry == klbHyphenBreak && lbTry != lbPref)) { // Look for a break at this level. The condition above means // don't try hyphen break if we already tried word break. // This engine can't (yet) find a hyphen break that is not // a word break. #ifndef ICU_LINEBREAKING FindLineBreak(prglbsForString, 0, ichLimSegCur - ichMinNew, lbTry, false, ichBreak, ichDim); #else FindLineBreak(prgch, qcpe, 0, ichLimSegCur - ichMinNew, lbTry, false, ichBreak, ichDim); #endif /*ICU_LINEBREAKING*/ if (ichBreak >= 0) { if (ichDim < 0) break; // all characters up to break are spaces qrrs->SetLim(ichDim + 1); qrrs->SetDirectionInfo(nDirDepth, (twsh == ktwshOnlyWs)); CheckHr(qrrs->get_Width(ichMinNew, pvg, &dxBreakWidth)); if (dxBreakWidth > dxMaxWidth && lbTry < klbClipBreak) { // can't do this kind of break ichBreak = -1; } } if (ichBreak >= 0) break; // found a useable break at this level } lbTry = (LgLineBreak) (lbTry + 1); // otherwise try next level. if (lbTry > lbMax) { // can't get any valid break. Give up. *ppsegRet = NULL; *pdxWidth = 0; return S_OK; // this is a perfectly valid result. } Assert(lbTry <= klbClipBreak); // that level should always succeed } // OK, we are going to put something on the line. The break type will be lbTry. // See if we can find any later breaks of the same type that fit. int ichNewBreak; // in prglbsForString int ichNewDim = -1; // in prglbsForString int dxNewBreakWidth; // 1. look backward for first break prior to cchLineEst // 2. while break doesn't fit, look backward for next prior break // 3. once a break fits, quit and use it. // 4. while break fits, remember it and look forward for next following break // 5. once a break doesn't fit, quit and use the previous break. bool fLookAhead = true; if (cchLineEst > ichBreak) #ifndef ICU_LINEBREAKING FindLineBreak(prglbsForString, ichBreak + 1, cchLineEst, lbTry, true, ichNewBreak, ichNewDim); #else FindLineBreak(prgch, qcpe, ichBreak + 1, cchLineEst, lbTry, true, ichNewBreak, ichNewDim); #endif /*ICU_LINEBREAKING*/ else