textrendering/textformatting/tagma/TMGLYPH.CPP
changeset 0 1fb32624e06b
child 9 26914f8d1faf
equal deleted inserted replaced
-1:000000000000 0:1fb32624e06b
       
     1 /*
       
     2 * Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description: 
       
    15 * Text caching for TAGMA: cacheing of text and formats extracted from a text source,
       
    16 *
       
    17 */
       
    18 
       
    19 
       
    20 #include "TMSTD.H"
       
    21 #include <bidi.h>
       
    22 
       
    23 
       
    24 inline TBool IsSurrogate(TText a) { return 0xD800 == (a & 0xF800); }
       
    25 inline TBool IsHighSurrogate(TText a) { return 0xD800 == (a & 0xFC00); }
       
    26 inline TBool IsLowSurrogate(TText a) { return 0xDC00 == (a & 0xFC00); }
       
    27 inline TChar JoinSurrogates(TText aHigh, TText aLow)
       
    28 	{
       
    29 	return ((aHigh - 0xd7f7) << 10) + aLow;
       
    30 	}
       
    31 
       
    32 inline TText16 GetHighSurrogate(TUint aChar)
       
    33 	{
       
    34 	return STATIC_CAST(TText16, 0xD7C0 + (aChar >> 10));
       
    35 	}
       
    36 
       
    37 inline TText16 GetLowSurrogate(TUint aChar)
       
    38 	{
       
    39 	return STATIC_CAST(TText16, 0xDC00 | (aChar & 0x3FF));
       
    40 	}
       
    41 
       
    42 TUint RTmTextCache::Char(TInt aPos)
       
    43 	{
       
    44 	if (aPos < 0 || aPos > iDocumentLength)
       
    45 		return 0xFFFF;
       
    46 	if (aPos == iDocumentLength)
       
    47 		return CEditableText::EParagraphDelimiter;
       
    48 	if (aPos < iTextStart || aPos >= iTextStart + iTextLength)
       
    49 		{
       
    50 		TPtrC p;
       
    51 		GetText(aPos,KMaxTInt,p);
       
    52 		}
       
    53 	if ( IsHighSurrogate( iText[aPos-iTextStart] ) ) 
       
    54 		{
       
    55 		if (iTextStart+iTextLength-aPos > 1)
       
    56 			{
       
    57 			TText high = iText[aPos-iTextStart];
       
    58 			TText low = iText[aPos-iTextStart+1];
       
    59 			if ( IsLowSurrogate( low ) )
       
    60 			    return JoinSurrogates( high, low );
       
    61 			else
       
    62 			    // If the corrupt surrogate, it is treated as what it is mapped
       
    63 			    return iSource.Map( high );
       
    64 			}
       
    65 		else
       
    66 			{
       
    67 			// If the corrupt surrogate, it is treated as what it is mapped
       
    68 			return iSource.Map( iText[aPos-iTextStart] );
       
    69 			}
       
    70 		}
       
    71 	else
       
    72 		{
       
    73 		return iText[aPos - iTextStart];
       
    74 		}
       
    75 	}
       
    76 
       
    77 /**
       
    78 Measure the width of some text, substituting glyphs where necessary.
       
    79 
       
    80 @param aStart Start position of text in document
       
    81 @param aEnd (Exclusive) end position of text in document
       
    82 @param aRightToLeft True if the text is right-to-left
       
    83 @param aMaxAdvance Measurement will stop if this advance is exceeded
       
    84 @param aOutput Output form text measurement function
       
    85 @param aExtraChar
       
    86 	Fetch this much context beyond aEnd. This helps when the text is to be
       
    87 	truncated, and aEnd is not certain to be at a cluster boundary.
       
    88 @return Advance width. Side-bearings are not included. */
       
    89 TInt RTmTextCache::AdvanceWidthL(TInt aStart, TInt aEnd, TBool aRightToLeft,
       
    90 						  TInt aMaxAdvance, CFbsFont::TMeasureTextOutput* aOutput, TInt aExtraChar)
       
    91 	{
       
    92 	// Get the displayed text, up to the copy fit width if necessary.
       
    93 	TInt length = aEnd - aStart;
       
    94 	TText buffer[KMaxTextChunkSize + 2 + 1];
       
    95 	if (length > KMaxTextChunkSize)
       
    96 		TmPanic(ETextWidthBufferOverflow);
       
    97 	
       
    98 	CTmTextFontCache *font = NULL;
       
    99 
       
   100 	// Get the text in logical order. If a custom interface to draw (in later stages)
       
   101 	// the text in context exists, then measure the text with context.
       
   102 	TInt error;
       
   103 	if (Source().GetExtendedInterface(KTmCustomExtensionUid))
       
   104 		{
       
   105 		error = GetDisplayedText(aStart, aEnd + aExtraChar,
       
   106 								  aRightToLeft ? ELogicalRightToLeft : ELeftToRight,
       
   107 								  buffer,iContextCharPerChunk, 0, &font);
       
   108 		}
       
   109 		else
       
   110 			{
       
   111 			error = GetDisplayedText(aStart, aEnd + aExtraChar,
       
   112 								  aRightToLeft ? ELogicalRightToLeft : ELeftToRight,
       
   113 								  buffer,0, 0, &font);
       
   114 			}
       
   115 	
       
   116 	// Don't push font onto cleanup stack because this method only leaves below.
       
   117 	// If there was an error in GetDisplayedText, font will have been closed
       
   118 	if (error)
       
   119 		User::Leave(error);
       
   120 
       
   121 	CFont::TMeasureTextInput input;
       
   122  	input.iFlags = aRightToLeft?
       
   123  		CFont::TMeasureTextInput::EFVisualOrderRightToLeft
       
   124  		: CFont::TMeasureTextInput::EFVisualOrder;
       
   125    
       
   126    	// Create a new buffer which will have the context  as well
       
   127    	TPtrC inputText(buffer, length + 2 + aExtraChar);
       
   128    	input.iStartInputChar = 1;
       
   129    	input.iEndInputChar = input.iStartInputChar + length;
       
   130    	input.iMaxBounds = aMaxAdvance;
       
   131    	input.iFlags |= CFont::TMeasureTextInput::EFIncludePenPositionInBoundsCheck;
       
   132  	CFbsFont::TMeasureTextOutput output;
       
   133  	CFbsFont::TMeasureTextOutput* pOutput = aOutput? aOutput : &output;
       
   134  	// Measure this text in the context supplied. Any punctuations in this chunk
       
   135  	// will now be measured based on this context.
       
   136  	TInt width = font->Font().MeasureText(inputText, &input, pOutput);
       
   137  	// Do not report initial Zero-Width Joiner or 0xFFFF
       
   138  	if (0 != pOutput->iChars)
       
   139  		--pOutput->iChars;
       
   140 	
       
   141 	// Find the new context at the end of this chunk. This will be used to pass on to the next chunk.
       
   142 	// If the context hasn't changed, pass on the previous context.
       
   143 	TUint last;
       
   144 	TInt textLength = input.iStartInputChar + pOutput->iChars;
       
   145 	TChar::TBdCategory cat = TChar::ELeftToRight;
       
   146 	while (textLength != 0)
       
   147 		{
       
   148 		TUint charSize = 1;
       
   149 		last = *(inputText.Ptr() + textLength);
       
   150 		if ( IsSurrogate( last ) && textLength > 1 ) 
       
   151 			{
       
   152 			TText low = *(inputText.Ptr() + textLength);
       
   153 			TText high = *(inputText.Ptr() + textLength - 1);
       
   154 			if ( IsLowSurrogate( low ) && IsHighSurrogate( high ) )
       
   155 				{
       
   156 				charSize = 2;
       
   157 				last = JoinSurrogates( high, low );
       
   158 				}
       
   159 			else
       
   160 			    {
       
   161 			    // If the corrupt surrogate, it is treated as what it is mapped
       
   162 			    last = iSource.Map( last );
       
   163 			    }
       
   164 			}
       
   165 		
       
   166 		cat = TChar(last).GetBdCategory();
       
   167 		if (last != 65535 && textLength !=0 &&
       
   168 			(cat == TChar::ELeftToRight ||
       
   169 			 cat == TChar::ERightToLeft ||
       
   170 			 cat == TChar::ERightToLeftArabic))
       
   171 			{
       
   172 			iContextCharPerChunk = last;
       
   173 			break;	
       
   174 			}
       
   175 		textLength -= charSize;
       
   176 		}
       
   177 	
       
   178 	// Check if this chunk is a punctuation chunk. If it is not, the context in the byte code for this chunk
       
   179 	// will be NULL. Otherwise it will just be the previous context.
       
   180 	if (cat == TChar::ELeftToRight ||
       
   181 		 cat == TChar::ERightToLeft ||
       
   182 		 cat == TChar::ERightToLeftArabic ||
       
   183 		 cat == TChar::EWhitespace)
       
   184 		{
       
   185 		iContextCharInByteCode = NULL;
       
   186 		}
       
   187 	else
       
   188 		iContextCharInByteCode = iContextCharPerChunk;
       
   189 
       
   190 	font->Close();
       
   191 
       
   192 	return width;
       
   193 	}
       
   194 
       
   195 /** Measure the width of some text, substituting glyphs where necessary.
       
   196 @param aStart Start position of text in document
       
   197 @param aEnd (Exclusive) end position of text in document
       
   198 @param aRightToLeft True if the text is right-to-left
       
   199 @return width including side-bearings */
       
   200 TInt RTmTextCache::TotalWidthL(TInt aStart, TInt aEnd,
       
   201 	TBool aRightToLeft)
       
   202 	{
       
   203  	CFbsFont::TMeasureTextOutput output;
       
   204  	TInt width = AdvanceWidthL(aStart, aEnd, aRightToLeft, KMaxTInt, &output);
       
   205  	// Add on any protruding side-bearings
       
   206  	if (width < output.iBounds.iBr.iX)
       
   207  		width = output.iBounds.iBr.iX;
       
   208  	if (output.iBounds.iTl.iX < 0)
       
   209  		width -= output.iBounds.iTl.iX;
       
   210  	return width;
       
   211 	}
       
   212 
       
   213 
       
   214 /*
       
   215 Get source text starting at aPos and not more than (aMaxEndChar - aPos) characters.
       
   216 If aFormat is non-null get the format.
       
   217 @param aFont If aFont is not null, on return contains a pointer to an opened CTmTextFontCache*
       
   218 		The caller must call Close on aFont when finished with the font.
       
   219 @return Failure condition from getting the font, if any.
       
   220 */
       
   221 TInt RTmTextCache::GetText(TInt aPos,TInt aMaxEndChar,TPtrC& aText,TTmCharFormat* aFormat,CTmTextFontCache** aFont)
       
   222 	{
       
   223 	TInt error = KErrNone;
       
   224 	if (aPos < iTextStart || aPos >= iTextStart + iTextLength)
       
   225 		{
       
   226 		iTextStart = aPos;
       
   227 		iTextBuffer.Zero();
       
   228 		TPtrC p;
       
   229 		TTmCharFormat new_format;
       
   230 		iSource.GetText(iTextStart,p,new_format);
       
   231 		if (!(new_format.iFontSpec == iFormat.iFontSpec))
       
   232 			{
       
   233 			ReleaseFont();
       
   234 			}
       
   235 		iFormat = new_format;
       
   236 		iText = p.Ptr();
       
   237 		iTextLength = p.Length();
       
   238 		if (iTextLength == 0)
       
   239 			TmPanic(EZeroLengthTextSupplied);
       
   240 		}
       
   241 	int offset = aPos - iTextStart;
       
   242 	aText.Set(iText + offset,iTextLength - offset);
       
   243 	if (aText.Length() > aMaxEndChar - aPos)
       
   244 		{
       
   245 		const TText* p = aText.Ptr();
       
   246 		aText.Set(p,aMaxEndChar - aPos);
       
   247 		}
       
   248 	if (aFormat)
       
   249 		*aFormat = iFormat;
       
   250 	if (aFont)
       
   251 		{
       
   252 		if (iFont == NULL)
       
   253 			{
       
   254 			TFontSpec fs;
       
   255 			iFormat.iFontSpec.GetTFontSpec(fs);
       
   256 			CFont *font = NULL;
       
   257 			error = iDevice.GetNearestFontInTwips(font,fs);
       
   258 			if (font != NULL)
       
   259 				{
       
   260 				iFont = CTmTextFontCache::New(iDevice, *font);
       
   261 				if (iFont == NULL)
       
   262 					{
       
   263 					iDevice.ReleaseFont(font);
       
   264 					error = KErrNoMemory;
       
   265 					}
       
   266 				}
       
   267 			}
       
   268 		if (iFont && error == KErrNone)
       
   269 			{
       
   270 			*aFont = iFont;
       
   271 			iFont->Open();
       
   272 			}
       
   273 		}
       
   274 	return error;
       
   275 	}
       
   276 
       
   277 /** Finds the extent of the run of characters that have the same format as
       
   278 aFormat following aPos, up to aMaxEndChar.
       
   279 @internalComponent */
       
   280 TInt FollowOnTextEnd(TInt aPos, TInt aMaxEndChar,
       
   281 	const TTmCharFormat& aFormat, MTmSource& aSource, TBool forcedExtend = EFalse)
       
   282 	{
       
   283 	TTmCharFormat format;
       
   284 	TPtrC text;
       
   285 	while (aPos < aMaxEndChar)
       
   286 		{
       
   287 		aSource.GetText(aPos, text, format);
       
   288 		// The forcedExtend flag is used for only one case that to prevent single ZWJ being 
       
   289 		// returned by RTmTextCache::GetTextL. Please see RTmTextCache::GetTextL.
       
   290 		if (format != aFormat && !forcedExtend)
       
   291 			return aPos;
       
   292 		TInt length = text.Length();
       
   293 		if (length == 0)
       
   294 			return aPos;
       
   295 		if (text[0] == CEditableText::EPictureCharacter)
       
   296 			{
       
   297 			return aPos;
       
   298 			}
       
   299 		aPos += length;
       
   300 		}
       
   301 	return aMaxEndChar;
       
   302 	}
       
   303 
       
   304 /** Copys the specified text from the source into the buffer.
       
   305 @internalComponent */
       
   306 void AppendTextToBuffer(TDes& aBuffer,
       
   307 	TInt aStart, TInt aEnd, MTmSource& aSource)
       
   308 	{
       
   309 	TTmCharFormat dummy;
       
   310 	while (aStart != aEnd)
       
   311 		{
       
   312 		TPtrC text;
       
   313 		aSource.GetText(aStart, text, dummy);
       
   314 		TInt textEnd = aStart + text.Length();
       
   315 		__ASSERT_ALWAYS(aStart < textEnd, TmPanic(EZeroLengthTextSupplied));
       
   316 		if (aEnd < textEnd)
       
   317 			{
       
   318 			// This run is overlong, so just append all that will fit
       
   319 			aBuffer.Append(&text[0], aEnd - aStart);
       
   320 			return;
       
   321 			}
       
   322 		aBuffer.Append(text);
       
   323 		aStart = textEnd;
       
   324 		}
       
   325 	}
       
   326 
       
   327 /** Same as GetText but will join text together if it has the same format.
       
   328 @param
       
   329 	aText the text returned. Is valid until the next call of GetText, GetTextL
       
   330 	or Close.
       
   331 @param aFont If aFont is not null, on return contains a pointer to an opened CTmTextFontCache*
       
   332 		The caller must call Close on aFont when finished with the font.
       
   333 @internalComponent */
       
   334 TInt RTmTextCache::GetTextL(TInt aPos, TInt aMaxEndChar, TPtrC& aText,
       
   335 	TTmCharFormat* aFormat, CTmTextFontCache** aFont)
       
   336 	{
       
   337 	TInt bufferEnd = iTextBufferStart + iTextBuffer.Length();
       
   338 	if ( iTextBufferStart <= aPos && aPos < bufferEnd
       
   339 		&& (aMaxEndChar < bufferEnd || iTextBufferEndsInFormatChange) )
       
   340 		{
       
   341 		TInt textEnd = Min(bufferEnd, aMaxEndChar);
       
   342 		aText.Set(&iTextBuffer[aPos - iTextBufferStart], textEnd - aPos);
       
   343 		if (aFormat)
       
   344 			*aFormat = iFormat;
       
   345 		if (aFont)
       
   346 			{
       
   347 			*aFont = iFont;
       
   348 			iFont->Open();
       
   349 			}
       
   350 			
       
   351 		return KErrNone;
       
   352 		}
       
   353 
       
   354 	TInt error =  GetText(aPos, aMaxEndChar, aText, aFormat, aFont);
       
   355 	TBool forcedExtend = EFalse;
       
   356 	const TText* ch = aText.Ptr();
       
   357 	// When the text is just a ZWJ, the following text should be forced extended, because the ZWJ should
       
   358 	// never be returned as a single text chunk.
       
   359 	if (aText.Length() == 1 && *ch == KZeroWidthJoiner)
       
   360 		forcedExtend = ETrue;
       
   361 	if (error == KErrNone)
       
   362 		{
       
   363 		CleanupClosePushL(**aFont);
       
   364 		TInt endOfAText = aPos + aText.Length();
       
   365 
       
   366 		TInt end = FollowOnTextEnd(endOfAText, aMaxEndChar, iFormat, iSource, forcedExtend);
       
   367 		if (end != endOfAText)
       
   368 			{
       
   369 			iTextBuffer.Zero();
       
   370 			if (iTextBuffer.MaxLength() < end - aPos)
       
   371 				iTextBuffer.ReAllocL(end - aPos);
       
   372 
       
   373 			iTextBuffer.Append(aText);
       
   374 			AppendTextToBuffer(iTextBuffer, endOfAText, end, iSource);
       
   375 			iTextBufferStart = aPos;
       
   376 			iTextBufferEndsInFormatChange = end < aMaxEndChar;
       
   377 			aText.Set(iTextBuffer);
       
   378 			}
       
   379 		CleanupStack::Pop(*aFont);
       
   380 		}
       
   381 	return error;
       
   382 	}
       
   383 
       
   384 TBool RTmTextCache::IsArabicPoint(TInt aChar)
       
   385 	{
       
   386 	return 0x64B <= aChar && aChar < 0x671
       
   387 		&& !(0x656 <= aChar && aChar < 0x670);
       
   388 	}
       
   389 
       
   390 TUint SupplementaryCharMap(MTmSource *aSource, TText aHigh, TText aLow)
       
   391     {
       
   392     TChar c(aHigh);
       
   393     if ( IsHighSurrogate( aHigh ) && IsLowSurrogate( aLow ))
       
   394         c = JoinSurrogates( aHigh, aLow );
       
   395     return aSource->Map(c);
       
   396     }
       
   397 
       
   398 /**
       
   399 Gets all the displayed text in the range aStart...aEnd and puts it into a
       
   400 buffer that must be at least aEnd - aStart + 2 characters in length. If
       
   401 aFormat is non-null gets the format of the first section of text. If aFont
       
   402 is non-null gets the font for the first section of text. If aDirectionality
       
   403 is EVisualRightToLeft reverses the text and mirrors appropriate characters.
       
   404 Adds a zero-width joiner to the start and/or end of the text returned if
       
   405 these are necessary for the correct contextual glyph choice. Adds a
       
   406 0xFFFF to each end if this is not required.
       
   407 @param aFont If aFont is not null, on return contains a pointer to an opened CTmTextFontCache*
       
   408 		The caller must call Close on aFont when finished with the font.
       
   409 @return Failure condition on getting font, if any.
       
   410 @internalComponent
       
   411 */
       
   412 TInt RTmTextCache::GetDisplayedText(TInt aStart, TInt aEnd,
       
   413 									TDisplayedTextDirectionality aDirectionality, TText* aBuffer,TUint aContextChar,
       
   414 									TTmCharFormat* aFormat, CTmTextFontCache** aFont)
       
   415 	{
       
   416 	__ASSERT_DEBUG(aStart <= aEnd, TmPanic(EBadArg));
       
   417 	if (aContextChar == 0)
       
   418 		aBuffer[0] = 0xFFFF;
       
   419 	else
       
   420 		aBuffer[0] = aContextChar;
       
   421 	if (aEnd == aStart)
       
   422 		return KErrGeneral;
       
   423 	TPtrC text;
       
   424 	TText* output = aBuffer;
       
   425 	TTmCharFormat format;
       
   426 
       
   427 	// might the (logically) first character join with the previous one?
       
   428 	GetText(aStart, aStart + 1, text, &format);	
       
   429     if (aFormat)
       
   430         *aFormat = format;
       
   431 	TInt c = iSource.Map(text[0]);
       
   432 	if (IsHighSurrogate(text[0]))
       
   433 		{
       
   434 		GetText(aStart, aStart + 2, text, &format);
       
   435 		if (aFormat)
       
   436 			*aFormat = format;
       
   437 		if (text.Length() >= 2)
       
   438 			{
       
   439 			if (IsLowSurrogate(text[1]))
       
   440 				{
       
   441 				c = SupplementaryCharMap(&iSource, text[0], text[1]);
       
   442 				}
       
   443 			else
       
   444 				{
       
   445 				c = iSource.Map(text[0]);	
       
   446 				}
       
   447 			}
       
   448 		}
       
   449 	
       
   450 	if (0 < aStart && CFont::CharactersJoin(
       
   451 		aDirectionality == ELeftToRight? KZeroWidthJoiner : c,
       
   452 		aDirectionality == ELeftToRight? c : KZeroWidthJoiner))
       
   453 		{
       
   454 		TInt prev = aStart - 1;
       
   455 		TTmCharFormat prevFormat;
       
   456 		GetText(prev, aStart, text, &prevFormat);
       
   457 		TInt prevChar = iSource.Map(text[0]);
       
   458 		while (0 < prev && IsArabicPoint(prevChar))
       
   459 			{
       
   460 			GetText(--prev, aStart, text, &prevFormat);
       
   461 			prevChar = iSource.Map(text[0]);
       
   462 			}
       
   463 		if (CFont::CharactersJoin(
       
   464 			aDirectionality == ELeftToRight? prevChar : c,
       
   465 			aDirectionality == ELeftToRight? c : prevChar)
       
   466 			&& format.iFontSpec == prevFormat.iFontSpec)
       
   467 			// Characters join at the beginning.
       
   468 			*output = KZeroWidthJoiner;
       
   469 		}
       
   470 
       
   471 	output = aBuffer + 1;
       
   472 	TInt error = KErrNone;
       
   473 	while (aStart < aEnd)
       
   474 		{
       
   475 		TInt currentError = GetText(aStart, aEnd, text, &format, aFont);
       
   476 		// do not have to push aFont onto clean-up stack because this is a non-leaving method
       
   477 		if (aFont)
       
   478 			error = currentError;
       
   479 		aFont = NULL;
       
   480 		TInt length = text.Length();
       
   481 		aStart += length;
       
   482 		const TText* input = text.Ptr();
       
   483 		const TText* end = input + length;
       
   484 		while (input < end)
       
   485 			{
       
   486 			if (IsHighSurrogate(*input))
       
   487 				{
       
   488 				if (input+1 < end && IsLowSurrogate(input[1]))
       
   489 					{
       
   490 					c = SupplementaryCharMap(&iSource, input[0], input[1]);
       
   491 					input++;
       
   492 					input++;
       
   493 					*output++ = GetHighSurrogate( c );
       
   494 					*output++ = GetLowSurrogate( c );
       
   495 					}
       
   496 				else
       
   497 					{
       
   498 					c = iSource.Map(input[0]);
       
   499 					input++;
       
   500 					*output++ = static_cast<TText>(c);
       
   501 					}
       
   502 				}
       
   503 			else if (IsLowSurrogate(*input))
       
   504 				{
       
   505 				c = iSource.Map(input[0]);
       
   506 				input++;
       
   507 				*output++ = static_cast<TText>(c);
       
   508 				}
       
   509 			else
       
   510 				{
       
   511 				c = iSource.Map(*input++);
       
   512 				*output++ = static_cast<TText>(c);
       
   513 				}
       
   514 			}
       
   515 		}
       
   516 
       
   517 	*output = 0xFFFF;
       
   518 	// might the (logically) last character join with the next one?
       
   519 	if (aStart < iSource.DocumentLength())
       
   520 		{
       
   521 		const TText* prev = output;
       
   522 		while (--prev != aBuffer && IsArabicPoint(*prev)) {}
       
   523 		if (CFont::CharactersJoin(
       
   524 			aDirectionality == ELeftToRight? *prev : KZeroWidthJoiner,
       
   525 			aDirectionality == ELeftToRight? KZeroWidthJoiner : *prev))
       
   526 			{
       
   527 			TTmCharFormat nextFormat;
       
   528 			GetText(aStart, aStart + 1, text, &nextFormat);
       
   529 			TInt nextChar = iSource.Map(text[0]);
       
   530 			if (CFont::CharactersJoin(
       
   531 				aDirectionality == ELeftToRight? *prev : nextChar,
       
   532 				aDirectionality == ELeftToRight? nextChar : *prev)
       
   533 				&& format.iFontSpec == nextFormat.iFontSpec)
       
   534 				// Characters join at the end.
       
   535 				*output = KZeroWidthJoiner;
       
   536 			}
       
   537 		}
       
   538 
       
   539 	if (aDirectionality == EVisualRightToLeft)
       
   540 		TBidirectionalState::ReverseGroups(aBuffer, output + 1 - aBuffer);
       
   541 	
       
   542 	return error;
       
   543 	}
       
   544 
       
   545 void RTmTextCache::SetContextChar(TUint aContextChar)
       
   546 	{
       
   547 	iContextCharPerChunk = aContextChar;
       
   548 	}
       
   549