textrendering/textformatting/tagma/TMGLYPH.CPP
changeset 0 1fb32624e06b
child 9 26914f8d1faf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/textrendering/textformatting/tagma/TMGLYPH.CPP	Tue Feb 02 02:02:46 2010 +0200
@@ -0,0 +1,549 @@
+/*
+* Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+* Text caching for TAGMA: cacheing of text and formats extracted from a text source,
+*
+*/
+
+
+#include "TMSTD.H"
+#include <bidi.h>
+
+
+inline TBool IsSurrogate(TText a) { return 0xD800 == (a & 0xF800); }
+inline TBool IsHighSurrogate(TText a) { return 0xD800 == (a & 0xFC00); }
+inline TBool IsLowSurrogate(TText a) { return 0xDC00 == (a & 0xFC00); }
+inline TChar JoinSurrogates(TText aHigh, TText aLow)
+	{
+	return ((aHigh - 0xd7f7) << 10) + aLow;
+	}
+
+inline TText16 GetHighSurrogate(TUint aChar)
+	{
+	return STATIC_CAST(TText16, 0xD7C0 + (aChar >> 10));
+	}
+
+inline TText16 GetLowSurrogate(TUint aChar)
+	{
+	return STATIC_CAST(TText16, 0xDC00 | (aChar & 0x3FF));
+	}
+
+TUint RTmTextCache::Char(TInt aPos)
+	{
+	if (aPos < 0 || aPos > iDocumentLength)
+		return 0xFFFF;
+	if (aPos == iDocumentLength)
+		return CEditableText::EParagraphDelimiter;
+	if (aPos < iTextStart || aPos >= iTextStart + iTextLength)
+		{
+		TPtrC p;
+		GetText(aPos,KMaxTInt,p);
+		}
+	if ( IsHighSurrogate( iText[aPos-iTextStart] ) ) 
+		{
+		if (iTextStart+iTextLength-aPos > 1)
+			{
+			TText high = iText[aPos-iTextStart];
+			TText low = iText[aPos-iTextStart+1];
+			if ( IsLowSurrogate( low ) )
+			    return JoinSurrogates( high, low );
+			else
+			    // If the corrupt surrogate, it is treated as what it is mapped
+			    return iSource.Map( high );
+			}
+		else
+			{
+			// If the corrupt surrogate, it is treated as what it is mapped
+			return iSource.Map( iText[aPos-iTextStart] );
+			}
+		}
+	else
+		{
+		return iText[aPos - iTextStart];
+		}
+	}
+
+/**
+Measure the width of some text, substituting glyphs where necessary.
+
+@param aStart Start position of text in document
+@param aEnd (Exclusive) end position of text in document
+@param aRightToLeft True if the text is right-to-left
+@param aMaxAdvance Measurement will stop if this advance is exceeded
+@param aOutput Output form text measurement function
+@param aExtraChar
+	Fetch this much context beyond aEnd. This helps when the text is to be
+	truncated, and aEnd is not certain to be at a cluster boundary.
+@return Advance width. Side-bearings are not included. */
+TInt RTmTextCache::AdvanceWidthL(TInt aStart, TInt aEnd, TBool aRightToLeft,
+						  TInt aMaxAdvance, CFbsFont::TMeasureTextOutput* aOutput, TInt aExtraChar)
+	{
+	// Get the displayed text, up to the copy fit width if necessary.
+	TInt length = aEnd - aStart;
+	TText buffer[KMaxTextChunkSize + 2 + 1];
+	if (length > KMaxTextChunkSize)
+		TmPanic(ETextWidthBufferOverflow);
+	
+	CTmTextFontCache *font = NULL;
+
+	// Get the text in logical order. If a custom interface to draw (in later stages)
+	// the text in context exists, then measure the text with context.
+	TInt error;
+	if (Source().GetExtendedInterface(KTmCustomExtensionUid))
+		{
+		error = GetDisplayedText(aStart, aEnd + aExtraChar,
+								  aRightToLeft ? ELogicalRightToLeft : ELeftToRight,
+								  buffer,iContextCharPerChunk, 0, &font);
+		}
+		else
+			{
+			error = GetDisplayedText(aStart, aEnd + aExtraChar,
+								  aRightToLeft ? ELogicalRightToLeft : ELeftToRight,
+								  buffer,0, 0, &font);
+			}
+	
+	// Don't push font onto cleanup stack because this method only leaves below.
+	// If there was an error in GetDisplayedText, font will have been closed
+	if (error)
+		User::Leave(error);
+
+	CFont::TMeasureTextInput input;
+ 	input.iFlags = aRightToLeft?
+ 		CFont::TMeasureTextInput::EFVisualOrderRightToLeft
+ 		: CFont::TMeasureTextInput::EFVisualOrder;
+   
+   	// Create a new buffer which will have the context  as well
+   	TPtrC inputText(buffer, length + 2 + aExtraChar);
+   	input.iStartInputChar = 1;
+   	input.iEndInputChar = input.iStartInputChar + length;
+   	input.iMaxBounds = aMaxAdvance;
+   	input.iFlags |= CFont::TMeasureTextInput::EFIncludePenPositionInBoundsCheck;
+ 	CFbsFont::TMeasureTextOutput output;
+ 	CFbsFont::TMeasureTextOutput* pOutput = aOutput? aOutput : &output;
+ 	// Measure this text in the context supplied. Any punctuations in this chunk
+ 	// will now be measured based on this context.
+ 	TInt width = font->Font().MeasureText(inputText, &input, pOutput);
+ 	// Do not report initial Zero-Width Joiner or 0xFFFF
+ 	if (0 != pOutput->iChars)
+ 		--pOutput->iChars;
+	
+	// Find the new context at the end of this chunk. This will be used to pass on to the next chunk.
+	// If the context hasn't changed, pass on the previous context.
+	TUint last;
+	TInt textLength = input.iStartInputChar + pOutput->iChars;
+	TChar::TBdCategory cat = TChar::ELeftToRight;
+	while (textLength != 0)
+		{
+		TUint charSize = 1;
+		last = *(inputText.Ptr() + textLength);
+		if ( IsSurrogate( last ) && textLength > 1 ) 
+			{
+			TText low = *(inputText.Ptr() + textLength);
+			TText high = *(inputText.Ptr() + textLength - 1);
+			if ( IsLowSurrogate( low ) && IsHighSurrogate( high ) )
+				{
+				charSize = 2;
+				last = JoinSurrogates( high, low );
+				}
+			else
+			    {
+			    // If the corrupt surrogate, it is treated as what it is mapped
+			    last = iSource.Map( last );
+			    }
+			}
+		
+		cat = TChar(last).GetBdCategory();
+		if (last != 65535 && textLength !=0 &&
+			(cat == TChar::ELeftToRight ||
+			 cat == TChar::ERightToLeft ||
+			 cat == TChar::ERightToLeftArabic))
+			{
+			iContextCharPerChunk = last;
+			break;	
+			}
+		textLength -= charSize;
+		}
+	
+	// Check if this chunk is a punctuation chunk. If it is not, the context in the byte code for this chunk
+	// will be NULL. Otherwise it will just be the previous context.
+	if (cat == TChar::ELeftToRight ||
+		 cat == TChar::ERightToLeft ||
+		 cat == TChar::ERightToLeftArabic ||
+		 cat == TChar::EWhitespace)
+		{
+		iContextCharInByteCode = NULL;
+		}
+	else
+		iContextCharInByteCode = iContextCharPerChunk;
+
+	font->Close();
+
+	return width;
+	}
+
+/** Measure the width of some text, substituting glyphs where necessary.
+@param aStart Start position of text in document
+@param aEnd (Exclusive) end position of text in document
+@param aRightToLeft True if the text is right-to-left
+@return width including side-bearings */
+TInt RTmTextCache::TotalWidthL(TInt aStart, TInt aEnd,
+	TBool aRightToLeft)
+	{
+ 	CFbsFont::TMeasureTextOutput output;
+ 	TInt width = AdvanceWidthL(aStart, aEnd, aRightToLeft, KMaxTInt, &output);
+ 	// Add on any protruding side-bearings
+ 	if (width < output.iBounds.iBr.iX)
+ 		width = output.iBounds.iBr.iX;
+ 	if (output.iBounds.iTl.iX < 0)
+ 		width -= output.iBounds.iTl.iX;
+ 	return width;
+	}
+
+
+/*
+Get source text starting at aPos and not more than (aMaxEndChar - aPos) characters.
+If aFormat is non-null get the format.
+@param aFont If aFont is not null, on return contains a pointer to an opened CTmTextFontCache*
+		The caller must call Close on aFont when finished with the font.
+@return Failure condition from getting the font, if any.
+*/
+TInt RTmTextCache::GetText(TInt aPos,TInt aMaxEndChar,TPtrC& aText,TTmCharFormat* aFormat,CTmTextFontCache** aFont)
+	{
+	TInt error = KErrNone;
+	if (aPos < iTextStart || aPos >= iTextStart + iTextLength)
+		{
+		iTextStart = aPos;
+		iTextBuffer.Zero();
+		TPtrC p;
+		TTmCharFormat new_format;
+		iSource.GetText(iTextStart,p,new_format);
+		if (!(new_format.iFontSpec == iFormat.iFontSpec))
+			{
+			ReleaseFont();
+			}
+		iFormat = new_format;
+		iText = p.Ptr();
+		iTextLength = p.Length();
+		if (iTextLength == 0)
+			TmPanic(EZeroLengthTextSupplied);
+		}
+	int offset = aPos - iTextStart;
+	aText.Set(iText + offset,iTextLength - offset);
+	if (aText.Length() > aMaxEndChar - aPos)
+		{
+		const TText* p = aText.Ptr();
+		aText.Set(p,aMaxEndChar - aPos);
+		}
+	if (aFormat)
+		*aFormat = iFormat;
+	if (aFont)
+		{
+		if (iFont == NULL)
+			{
+			TFontSpec fs;
+			iFormat.iFontSpec.GetTFontSpec(fs);
+			CFont *font = NULL;
+			error = iDevice.GetNearestFontInTwips(font,fs);
+			if (font != NULL)
+				{
+				iFont = CTmTextFontCache::New(iDevice, *font);
+				if (iFont == NULL)
+					{
+					iDevice.ReleaseFont(font);
+					error = KErrNoMemory;
+					}
+				}
+			}
+		if (iFont && error == KErrNone)
+			{
+			*aFont = iFont;
+			iFont->Open();
+			}
+		}
+	return error;
+	}
+
+/** Finds the extent of the run of characters that have the same format as
+aFormat following aPos, up to aMaxEndChar.
+@internalComponent */
+TInt FollowOnTextEnd(TInt aPos, TInt aMaxEndChar,
+	const TTmCharFormat& aFormat, MTmSource& aSource, TBool forcedExtend = EFalse)
+	{
+	TTmCharFormat format;
+	TPtrC text;
+	while (aPos < aMaxEndChar)
+		{
+		aSource.GetText(aPos, text, format);
+		// The forcedExtend flag is used for only one case that to prevent single ZWJ being 
+		// returned by RTmTextCache::GetTextL. Please see RTmTextCache::GetTextL.
+		if (format != aFormat && !forcedExtend)
+			return aPos;
+		TInt length = text.Length();
+		if (length == 0)
+			return aPos;
+		if (text[0] == CEditableText::EPictureCharacter)
+			{
+			return aPos;
+			}
+		aPos += length;
+		}
+	return aMaxEndChar;
+	}
+
+/** Copys the specified text from the source into the buffer.
+@internalComponent */
+void AppendTextToBuffer(TDes& aBuffer,
+	TInt aStart, TInt aEnd, MTmSource& aSource)
+	{
+	TTmCharFormat dummy;
+	while (aStart != aEnd)
+		{
+		TPtrC text;
+		aSource.GetText(aStart, text, dummy);
+		TInt textEnd = aStart + text.Length();
+		__ASSERT_ALWAYS(aStart < textEnd, TmPanic(EZeroLengthTextSupplied));
+		if (aEnd < textEnd)
+			{
+			// This run is overlong, so just append all that will fit
+			aBuffer.Append(&text[0], aEnd - aStart);
+			return;
+			}
+		aBuffer.Append(text);
+		aStart = textEnd;
+		}
+	}
+
+/** Same as GetText but will join text together if it has the same format.
+@param
+	aText the text returned. Is valid until the next call of GetText, GetTextL
+	or Close.
+@param aFont If aFont is not null, on return contains a pointer to an opened CTmTextFontCache*
+		The caller must call Close on aFont when finished with the font.
+@internalComponent */
+TInt RTmTextCache::GetTextL(TInt aPos, TInt aMaxEndChar, TPtrC& aText,
+	TTmCharFormat* aFormat, CTmTextFontCache** aFont)
+	{
+	TInt bufferEnd = iTextBufferStart + iTextBuffer.Length();
+	if ( iTextBufferStart <= aPos && aPos < bufferEnd
+		&& (aMaxEndChar < bufferEnd || iTextBufferEndsInFormatChange) )
+		{
+		TInt textEnd = Min(bufferEnd, aMaxEndChar);
+		aText.Set(&iTextBuffer[aPos - iTextBufferStart], textEnd - aPos);
+		if (aFormat)
+			*aFormat = iFormat;
+		if (aFont)
+			{
+			*aFont = iFont;
+			iFont->Open();
+			}
+			
+		return KErrNone;
+		}
+
+	TInt error =  GetText(aPos, aMaxEndChar, aText, aFormat, aFont);
+	TBool forcedExtend = EFalse;
+	const TText* ch = aText.Ptr();
+	// When the text is just a ZWJ, the following text should be forced extended, because the ZWJ should
+	// never be returned as a single text chunk.
+	if (aText.Length() == 1 && *ch == KZeroWidthJoiner)
+		forcedExtend = ETrue;
+	if (error == KErrNone)
+		{
+		CleanupClosePushL(**aFont);
+		TInt endOfAText = aPos + aText.Length();
+
+		TInt end = FollowOnTextEnd(endOfAText, aMaxEndChar, iFormat, iSource, forcedExtend);
+		if (end != endOfAText)
+			{
+			iTextBuffer.Zero();
+			if (iTextBuffer.MaxLength() < end - aPos)
+				iTextBuffer.ReAllocL(end - aPos);
+
+			iTextBuffer.Append(aText);
+			AppendTextToBuffer(iTextBuffer, endOfAText, end, iSource);
+			iTextBufferStart = aPos;
+			iTextBufferEndsInFormatChange = end < aMaxEndChar;
+			aText.Set(iTextBuffer);
+			}
+		CleanupStack::Pop(*aFont);
+		}
+	return error;
+	}
+
+TBool RTmTextCache::IsArabicPoint(TInt aChar)
+	{
+	return 0x64B <= aChar && aChar < 0x671
+		&& !(0x656 <= aChar && aChar < 0x670);
+	}
+
+TUint SupplementaryCharMap(MTmSource *aSource, TText aHigh, TText aLow)
+    {
+    TChar c(aHigh);
+    if ( IsHighSurrogate( aHigh ) && IsLowSurrogate( aLow ))
+        c = JoinSurrogates( aHigh, aLow );
+    return aSource->Map(c);
+    }
+
+/**
+Gets all the displayed text in the range aStart...aEnd and puts it into a
+buffer that must be at least aEnd - aStart + 2 characters in length. If
+aFormat is non-null gets the format of the first section of text. If aFont
+is non-null gets the font for the first section of text. If aDirectionality
+is EVisualRightToLeft reverses the text and mirrors appropriate characters.
+Adds a zero-width joiner to the start and/or end of the text returned if
+these are necessary for the correct contextual glyph choice. Adds a
+0xFFFF to each end if this is not required.
+@param aFont If aFont is not null, on return contains a pointer to an opened CTmTextFontCache*
+		The caller must call Close on aFont when finished with the font.
+@return Failure condition on getting font, if any.
+@internalComponent
+*/
+TInt RTmTextCache::GetDisplayedText(TInt aStart, TInt aEnd,
+									TDisplayedTextDirectionality aDirectionality, TText* aBuffer,TUint aContextChar,
+									TTmCharFormat* aFormat, CTmTextFontCache** aFont)
+	{
+	__ASSERT_DEBUG(aStart <= aEnd, TmPanic(EBadArg));
+	if (aContextChar == 0)
+		aBuffer[0] = 0xFFFF;
+	else
+		aBuffer[0] = aContextChar;
+	if (aEnd == aStart)
+		return KErrGeneral;
+	TPtrC text;
+	TText* output = aBuffer;
+	TTmCharFormat format;
+
+	// might the (logically) first character join with the previous one?
+	GetText(aStart, aStart + 1, text, &format);	
+    if (aFormat)
+        *aFormat = format;
+	TInt c = iSource.Map(text[0]);
+	if (IsHighSurrogate(text[0]))
+		{
+		GetText(aStart, aStart + 2, text, &format);
+		if (aFormat)
+			*aFormat = format;
+		if (text.Length() >= 2)
+			{
+			if (IsLowSurrogate(text[1]))
+				{
+				c = SupplementaryCharMap(&iSource, text[0], text[1]);
+				}
+			else
+				{
+				c = iSource.Map(text[0]);	
+				}
+			}
+		}
+	
+	if (0 < aStart && CFont::CharactersJoin(
+		aDirectionality == ELeftToRight? KZeroWidthJoiner : c,
+		aDirectionality == ELeftToRight? c : KZeroWidthJoiner))
+		{
+		TInt prev = aStart - 1;
+		TTmCharFormat prevFormat;
+		GetText(prev, aStart, text, &prevFormat);
+		TInt prevChar = iSource.Map(text[0]);
+		while (0 < prev && IsArabicPoint(prevChar))
+			{
+			GetText(--prev, aStart, text, &prevFormat);
+			prevChar = iSource.Map(text[0]);
+			}
+		if (CFont::CharactersJoin(
+			aDirectionality == ELeftToRight? prevChar : c,
+			aDirectionality == ELeftToRight? c : prevChar)
+			&& format.iFontSpec == prevFormat.iFontSpec)
+			// Characters join at the beginning.
+			*output = KZeroWidthJoiner;
+		}
+
+	output = aBuffer + 1;
+	TInt error = KErrNone;
+	while (aStart < aEnd)
+		{
+		TInt currentError = GetText(aStart, aEnd, text, &format, aFont);
+		// do not have to push aFont onto clean-up stack because this is a non-leaving method
+		if (aFont)
+			error = currentError;
+		aFont = NULL;
+		TInt length = text.Length();
+		aStart += length;
+		const TText* input = text.Ptr();
+		const TText* end = input + length;
+		while (input < end)
+			{
+			if (IsHighSurrogate(*input))
+				{
+				if (input+1 < end && IsLowSurrogate(input[1]))
+					{
+					c = SupplementaryCharMap(&iSource, input[0], input[1]);
+					input++;
+					input++;
+					*output++ = GetHighSurrogate( c );
+					*output++ = GetLowSurrogate( c );
+					}
+				else
+					{
+					c = iSource.Map(input[0]);
+					input++;
+					*output++ = static_cast<TText>(c);
+					}
+				}
+			else if (IsLowSurrogate(*input))
+				{
+				c = iSource.Map(input[0]);
+				input++;
+				*output++ = static_cast<TText>(c);
+				}
+			else
+				{
+				c = iSource.Map(*input++);
+				*output++ = static_cast<TText>(c);
+				}
+			}
+		}
+
+	*output = 0xFFFF;
+	// might the (logically) last character join with the next one?
+	if (aStart < iSource.DocumentLength())
+		{
+		const TText* prev = output;
+		while (--prev != aBuffer && IsArabicPoint(*prev)) {}
+		if (CFont::CharactersJoin(
+			aDirectionality == ELeftToRight? *prev : KZeroWidthJoiner,
+			aDirectionality == ELeftToRight? KZeroWidthJoiner : *prev))
+			{
+			TTmCharFormat nextFormat;
+			GetText(aStart, aStart + 1, text, &nextFormat);
+			TInt nextChar = iSource.Map(text[0]);
+			if (CFont::CharactersJoin(
+				aDirectionality == ELeftToRight? *prev : nextChar,
+				aDirectionality == ELeftToRight? nextChar : *prev)
+				&& format.iFontSpec == nextFormat.iFontSpec)
+				// Characters join at the end.
+				*output = KZeroWidthJoiner;
+			}
+		}
+
+	if (aDirectionality == EVisualRightToLeft)
+		TBidirectionalState::ReverseGroups(aBuffer, output + 1 - aBuffer);
+	
+	return error;
+	}
+
+void RTmTextCache::SetContextChar(TUint aContextChar)
+	{
+	iContextCharPerChunk = aContextChar;
+	}
+