Exemple #1
0
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;
}
Exemple #2
0
//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