|
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 |