Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsTextRunTransformations.cpp
blob47155879b053f23a97ecb1c49f8d274ab8ab098a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsTextRunTransformations.h"
8 #include "mozilla/MemoryReporting.h"
10 #include "nsGkAtoms.h"
11 #include "nsStyleConsts.h"
12 #include "nsStyleContext.h"
13 #include "nsUnicharUtils.h"
14 #include "nsUnicodeProperties.h"
15 #include "nsSpecialCasingData.h"
16 #include "mozilla/gfx/2D.h"
17 #include "nsTextFrameUtils.h"
18 #include "nsIPersistentProperties2.h"
19 #include "nsNetUtil.h"
20 #include "GreekCasing.h"
21 #include "IrishCasing.h"
23 // Unicode characters needing special casing treatment in tr/az languages
24 #define LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE 0x0130
25 #define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
27 // Greek sigma needs custom handling for the lowercase transform; for details
28 // see comments under "case NS_STYLE_TEXT_TRANSFORM_LOWERCASE" within
29 // nsCaseTransformTextRunFactory::RebuildTextRun(), and bug 740120.
30 #define GREEK_CAPITAL_LETTER_SIGMA 0x03A3
31 #define GREEK_SMALL_LETTER_FINAL_SIGMA 0x03C2
32 #define GREEK_SMALL_LETTER_SIGMA 0x03C3
34 nsTransformedTextRun *
35 nsTransformedTextRun::Create(const gfxTextRunFactory::Parameters* aParams,
36 nsTransformingTextRunFactory* aFactory,
37 gfxFontGroup* aFontGroup,
38 const char16_t* aString, uint32_t aLength,
39 const uint32_t aFlags, nsStyleContext** aStyles,
40 bool aOwnsFactory)
42 NS_ASSERTION(!(aFlags & gfxTextRunFactory::TEXT_IS_8BIT),
43 "didn't expect text to be marked as 8-bit here");
45 void *storage = AllocateStorageForTextRun(sizeof(nsTransformedTextRun), aLength);
46 if (!storage) {
47 return nullptr;
50 return new (storage) nsTransformedTextRun(aParams, aFactory, aFontGroup,
51 aString, aLength,
52 aFlags, aStyles, aOwnsFactory);
55 void
56 nsTransformedTextRun::SetCapitalization(uint32_t aStart, uint32_t aLength,
57 bool* aCapitalization,
58 gfxContext* aRefContext)
60 if (mCapitalize.IsEmpty()) {
61 if (!mCapitalize.AppendElements(GetLength()))
62 return;
63 memset(mCapitalize.Elements(), 0, GetLength()*sizeof(bool));
65 memcpy(mCapitalize.Elements() + aStart, aCapitalization, aLength*sizeof(bool));
66 mNeedsRebuild = true;
69 bool
70 nsTransformedTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength,
71 uint8_t* aBreakBefore,
72 gfxContext* aRefContext)
74 bool changed = gfxTextRun::SetPotentialLineBreaks(aStart, aLength,
75 aBreakBefore, aRefContext);
76 if (changed) {
77 mNeedsRebuild = true;
79 return changed;
82 size_t
83 nsTransformedTextRun::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
85 size_t total = gfxTextRun::SizeOfExcludingThis(aMallocSizeOf);
86 total += mStyles.SizeOfExcludingThis(aMallocSizeOf);
87 total += mCapitalize.SizeOfExcludingThis(aMallocSizeOf);
88 if (mOwnsFactory) {
89 total += aMallocSizeOf(mFactory);
91 return total;
94 size_t
95 nsTransformedTextRun::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
97 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
100 nsTransformedTextRun*
101 nsTransformingTextRunFactory::MakeTextRun(const char16_t* aString, uint32_t aLength,
102 const gfxTextRunFactory::Parameters* aParams,
103 gfxFontGroup* aFontGroup, uint32_t aFlags,
104 nsStyleContext** aStyles, bool aOwnsFactory)
106 return nsTransformedTextRun::Create(aParams, this, aFontGroup,
107 aString, aLength, aFlags, aStyles, aOwnsFactory);
110 nsTransformedTextRun*
111 nsTransformingTextRunFactory::MakeTextRun(const uint8_t* aString, uint32_t aLength,
112 const gfxTextRunFactory::Parameters* aParams,
113 gfxFontGroup* aFontGroup, uint32_t aFlags,
114 nsStyleContext** aStyles, bool aOwnsFactory)
116 // We'll only have a Unicode code path to minimize the amount of code needed
117 // for these rarely used features
118 NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), aLength);
119 return MakeTextRun(unicodeString.get(), aLength, aParams, aFontGroup,
120 aFlags & ~(gfxFontGroup::TEXT_IS_PERSISTENT | gfxFontGroup::TEXT_IS_8BIT),
121 aStyles, aOwnsFactory);
124 void
125 MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
126 const bool* aCharsToMerge, const bool* aDeletedChars)
128 aDest->ResetGlyphRuns();
130 gfxTextRun::GlyphRunIterator iter(aSrc, 0, aSrc->GetLength());
131 uint32_t offset = 0;
132 nsAutoTArray<gfxTextRun::DetailedGlyph,2> glyphs;
133 while (iter.NextRun()) {
134 gfxTextRun::GlyphRun* run = iter.GetGlyphRun();
135 nsresult rv = aDest->AddGlyphRun(run->mFont, run->mMatchType,
136 offset, false, run->mOrientation);
137 if (NS_FAILED(rv))
138 return;
140 bool anyMissing = false;
141 uint32_t mergeRunStart = iter.GetStringStart();
142 const gfxTextRun::CompressedGlyph *srcGlyphs = aSrc->GetCharacterGlyphs();
143 gfxTextRun::CompressedGlyph mergedGlyph = srcGlyphs[mergeRunStart];
144 uint32_t stringEnd = iter.GetStringEnd();
145 for (uint32_t k = iter.GetStringStart(); k < stringEnd; ++k) {
146 const gfxTextRun::CompressedGlyph g = srcGlyphs[k];
147 if (g.IsSimpleGlyph()) {
148 if (!anyMissing) {
149 gfxTextRun::DetailedGlyph details;
150 details.mGlyphID = g.GetSimpleGlyph();
151 details.mAdvance = g.GetSimpleAdvance();
152 details.mXOffset = 0;
153 details.mYOffset = 0;
154 glyphs.AppendElement(details);
156 } else {
157 if (g.IsMissing()) {
158 anyMissing = true;
159 glyphs.Clear();
161 if (g.GetGlyphCount() > 0) {
162 glyphs.AppendElements(aSrc->GetDetailedGlyphs(k), g.GetGlyphCount());
166 if (k + 1 < iter.GetStringEnd() && aCharsToMerge[k + 1]) {
167 // next char is supposed to merge with current, so loop without
168 // writing current merged glyph to the destination
169 continue;
172 // If the start of the merge run is actually a character that should
173 // have been merged with the previous character (this can happen
174 // if there's a font change in the middle of a case-mapped character,
175 // that decomposed into a sequence of base+diacritics, for example),
176 // just discard the entire merge run. See comment at start of this
177 // function.
178 NS_WARN_IF_FALSE(!aCharsToMerge[mergeRunStart],
179 "unable to merge across a glyph run boundary, "
180 "glyph(s) discarded");
181 if (!aCharsToMerge[mergeRunStart]) {
182 if (anyMissing) {
183 mergedGlyph.SetMissing(glyphs.Length());
184 } else {
185 mergedGlyph.SetComplex(mergedGlyph.IsClusterStart(),
186 mergedGlyph.IsLigatureGroupStart(),
187 glyphs.Length());
189 aDest->SetGlyphs(offset, mergedGlyph, glyphs.Elements());
190 ++offset;
192 while (offset < aDest->GetLength() && aDeletedChars[offset]) {
193 aDest->SetGlyphs(offset++, gfxTextRun::CompressedGlyph(), nullptr);
197 glyphs.Clear();
198 anyMissing = false;
199 mergeRunStart = k + 1;
200 if (mergeRunStart < stringEnd) {
201 mergedGlyph = srcGlyphs[mergeRunStart];
204 NS_ASSERTION(glyphs.Length() == 0,
205 "Leftover glyphs, don't request merging of the last character with its next!");
207 NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
210 gfxTextRunFactory::Parameters
211 GetParametersForInner(nsTransformedTextRun* aTextRun, uint32_t* aFlags,
212 gfxContext* aRefContext)
214 gfxTextRunFactory::Parameters params =
215 { aRefContext, nullptr, nullptr,
216 nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()
218 *aFlags = aTextRun->GetFlags() & ~gfxFontGroup::TEXT_IS_PERSISTENT;
219 return params;
222 // Some languages have special casing conventions that differ from the
223 // default Unicode mappings.
224 // The enum values here are named for well-known exemplar languages that
225 // exhibit the behavior in question; multiple lang tags may map to the
226 // same setting here, if the behavior is shared by other languages.
227 enum LanguageSpecificCasingBehavior {
228 eLSCB_None, // default non-lang-specific behavior
229 eLSCB_Dutch, // treat "ij" digraph as a unit for capitalization
230 eLSCB_Greek, // strip accent when uppercasing Greek vowels
231 eLSCB_Irish, // keep prefix letters as lowercase when uppercasing Irish
232 eLSCB_Turkish // preserve dotted/dotless-i distinction in uppercase
235 static LanguageSpecificCasingBehavior
236 GetCasingFor(const nsIAtom* aLang)
238 if (!aLang) {
239 return eLSCB_None;
241 if (aLang == nsGkAtoms::tr ||
242 aLang == nsGkAtoms::az ||
243 aLang == nsGkAtoms::ba ||
244 aLang == nsGkAtoms::crh ||
245 aLang == nsGkAtoms::tt) {
246 return eLSCB_Turkish;
248 if (aLang == nsGkAtoms::nl) {
249 return eLSCB_Dutch;
251 if (aLang == nsGkAtoms::el) {
252 return eLSCB_Greek;
254 if (aLang == nsGkAtoms::ga) {
255 return eLSCB_Irish;
258 // Is there a region subtag we should ignore?
259 nsAtomString langStr(const_cast<nsIAtom*>(aLang));
260 int index = langStr.FindChar('-');
261 if (index > 0) {
262 langStr.Truncate(index);
263 nsCOMPtr<nsIAtom> truncatedLang = do_GetAtom(langStr);
264 return GetCasingFor(truncatedLang);
267 return eLSCB_None;
270 bool
271 nsCaseTransformTextRunFactory::TransformString(
272 const nsAString& aString,
273 nsString& aConvertedString,
274 bool aAllUppercase,
275 const nsIAtom* aLanguage,
276 nsTArray<bool>& aCharsToMergeArray,
277 nsTArray<bool>& aDeletedCharsArray,
278 nsTransformedTextRun* aTextRun,
279 nsTArray<uint8_t>* aCanBreakBeforeArray,
280 nsTArray<nsStyleContext*>* aStyleArray)
282 NS_PRECONDITION(!aTextRun || (aCanBreakBeforeArray && aStyleArray),
283 "either none or all three optional parameters required");
285 uint32_t length = aString.Length();
286 const char16_t* str = aString.BeginReading();
288 bool mergeNeeded = false;
290 bool capitalizeDutchIJ = false;
291 bool prevIsLetter = false;
292 bool ntPrefix = false; // true immediately after a word-initial 'n' or 't'
293 // when doing Irish lowercasing
294 uint32_t sigmaIndex = uint32_t(-1);
295 nsIUGenCategory::nsUGenCategory cat;
297 uint8_t style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE : 0;
298 const nsIAtom* lang = aLanguage;
300 LanguageSpecificCasingBehavior languageSpecificCasing = GetCasingFor(lang);
301 mozilla::GreekCasing::State greekState;
302 mozilla::IrishCasing::State irishState;
303 uint32_t irishMark = uint32_t(-1); // location of possible prefix letter(s)
305 for (uint32_t i = 0; i < length; ++i) {
306 uint32_t ch = str[i];
308 nsStyleContext* styleContext;
309 if (aTextRun) {
310 styleContext = aTextRun->mStyles[i];
311 style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE :
312 styleContext->StyleText()->mTextTransform;
314 const nsStyleFont* styleFont = styleContext->StyleFont();
315 nsIAtom* newLang = styleFont->mExplicitLanguage
316 ? styleFont->mLanguage : nullptr;
317 if (lang != newLang) {
318 lang = newLang;
319 languageSpecificCasing = GetCasingFor(lang);
320 greekState.Reset();
321 irishState.Reset();
322 irishMark = uint32_t(-1);
326 int extraChars = 0;
327 const mozilla::unicode::MultiCharMapping *mcm;
328 bool inhibitBreakBefore = false; // have we just deleted preceding hyphen?
330 if (NS_IS_HIGH_SURROGATE(ch) && i < length - 1 &&
331 NS_IS_LOW_SURROGATE(str[i + 1])) {
332 ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
335 switch (style) {
336 case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
337 if (languageSpecificCasing == eLSCB_Turkish) {
338 if (ch == 'I') {
339 ch = LATIN_SMALL_LETTER_DOTLESS_I;
340 prevIsLetter = true;
341 sigmaIndex = uint32_t(-1);
342 break;
344 if (ch == LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) {
345 ch = 'i';
346 prevIsLetter = true;
347 sigmaIndex = uint32_t(-1);
348 break;
352 cat = mozilla::unicode::GetGenCategory(ch);
354 if (languageSpecificCasing == eLSCB_Irish &&
355 cat == nsIUGenCategory::kLetter) {
356 // See bug 1018805 for Irish lowercasing requirements
357 if (!prevIsLetter && (ch == 'n' || ch == 't')) {
358 ntPrefix = true;
359 } else {
360 if (ntPrefix && mozilla::IrishCasing::IsUpperVowel(ch)) {
361 aConvertedString.Append('-');
362 ++extraChars;
364 ntPrefix = false;
366 } else {
367 ntPrefix = false;
370 // Special lowercasing behavior for Greek Sigma: note that this is listed
371 // as context-sensitive in Unicode's SpecialCasing.txt, but is *not* a
372 // language-specific mapping; it applies regardless of the language of
373 // the element.
375 // The lowercase mapping for CAPITAL SIGMA should be to SMALL SIGMA (i.e.
376 // the non-final form) whenever there is a following letter, or when the
377 // CAPITAL SIGMA occurs in isolation (neither preceded nor followed by a
378 // LETTER); and to FINAL SIGMA when it is preceded by another letter but
379 // not followed by one.
381 // To implement the context-sensitive nature of this mapping, we keep
382 // track of whether the previous character was a letter. If not, CAPITAL
383 // SIGMA will map directly to SMALL SIGMA. If the previous character
384 // was a letter, CAPITAL SIGMA maps to FINAL SIGMA and we record the
385 // position in the converted string; if we then encounter another letter,
386 // that FINAL SIGMA is replaced with a standard SMALL SIGMA.
388 // If sigmaIndex is not -1, it marks where we have provisionally mapped
389 // a CAPITAL SIGMA to FINAL SIGMA; if we now find another letter, we
390 // need to change it to SMALL SIGMA.
391 if (sigmaIndex != uint32_t(-1)) {
392 if (cat == nsIUGenCategory::kLetter) {
393 aConvertedString.SetCharAt(GREEK_SMALL_LETTER_SIGMA, sigmaIndex);
397 if (ch == GREEK_CAPITAL_LETTER_SIGMA) {
398 // If preceding char was a letter, map to FINAL instead of SMALL,
399 // and note where it occurred by setting sigmaIndex; we'll change it
400 // to standard SMALL SIGMA later if another letter follows
401 if (prevIsLetter) {
402 ch = GREEK_SMALL_LETTER_FINAL_SIGMA;
403 sigmaIndex = aConvertedString.Length();
404 } else {
405 // CAPITAL SIGMA not preceded by a letter is unconditionally mapped
406 // to SMALL SIGMA
407 ch = GREEK_SMALL_LETTER_SIGMA;
408 sigmaIndex = uint32_t(-1);
410 prevIsLetter = true;
411 break;
414 // ignore diacritics for the purpose of contextual sigma mapping;
415 // otherwise, reset prevIsLetter appropriately and clear the
416 // sigmaIndex marker
417 if (cat != nsIUGenCategory::kMark) {
418 prevIsLetter = (cat == nsIUGenCategory::kLetter);
419 sigmaIndex = uint32_t(-1);
422 mcm = mozilla::unicode::SpecialLower(ch);
423 if (mcm) {
424 int j = 0;
425 while (j < 2 && mcm->mMappedChars[j + 1]) {
426 aConvertedString.Append(mcm->mMappedChars[j]);
427 ++extraChars;
428 ++j;
430 ch = mcm->mMappedChars[j];
431 break;
434 ch = ToLowerCase(ch);
435 break;
437 case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
438 if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
439 ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
440 break;
443 if (languageSpecificCasing == eLSCB_Greek) {
444 ch = mozilla::GreekCasing::UpperCase(ch, greekState);
445 break;
448 if (languageSpecificCasing == eLSCB_Irish) {
449 bool mark;
450 uint8_t action;
451 ch = mozilla::IrishCasing::UpperCase(ch, irishState, mark, action);
452 if (mark) {
453 irishMark = aConvertedString.Length();
454 break;
455 } else if (action) {
456 nsString& str = aConvertedString; // shorthand
457 switch (action) {
458 case 1:
459 // lowercase a single prefix letter
460 NS_ASSERTION(str.Length() > 0 && irishMark < str.Length(),
461 "bad irishMark!");
462 str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
463 irishMark = uint32_t(-1);
464 break;
465 case 2:
466 // lowercase two prefix letters (immediately before current pos)
467 NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2,
468 "bad irishMark!");
469 str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
470 str.SetCharAt(ToLowerCase(str[irishMark + 1]), irishMark + 1);
471 irishMark = uint32_t(-1);
472 break;
473 case 3:
474 // lowercase one prefix letter, and delete following hyphen
475 // (which must be the immediately-preceding char)
476 NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2,
477 "bad irishMark!");
478 str.Replace(irishMark, 2, ToLowerCase(str[irishMark]));
479 aDeletedCharsArray[irishMark + 1] = true;
480 // Remove the trailing entries (corresponding to the deleted hyphen)
481 // from the auxiliary arrays.
482 aCharsToMergeArray.SetLength(aCharsToMergeArray.Length() - 1);
483 if (aTextRun) {
484 aStyleArray->SetLength(aStyleArray->Length() - 1);
485 aCanBreakBeforeArray->SetLength(aCanBreakBeforeArray->Length() - 1);
486 inhibitBreakBefore = true;
488 mergeNeeded = true;
489 irishMark = uint32_t(-1);
490 break;
492 // ch has been set to the uppercase for current char;
493 // No need to check for SpecialUpper here as none of the characters
494 // that could trigger an Irish casing action have special mappings.
495 break;
497 // If we didn't have any special action to perform, fall through
498 // to check for special uppercase (ß)
501 mcm = mozilla::unicode::SpecialUpper(ch);
502 if (mcm) {
503 int j = 0;
504 while (j < 2 && mcm->mMappedChars[j + 1]) {
505 aConvertedString.Append(mcm->mMappedChars[j]);
506 ++extraChars;
507 ++j;
509 ch = mcm->mMappedChars[j];
510 break;
513 ch = ToUpperCase(ch);
514 break;
516 case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
517 if (aTextRun) {
518 if (capitalizeDutchIJ && ch == 'j') {
519 ch = 'J';
520 capitalizeDutchIJ = false;
521 break;
523 capitalizeDutchIJ = false;
524 if (i < aTextRun->mCapitalize.Length() && aTextRun->mCapitalize[i]) {
525 if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
526 ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
527 break;
529 if (languageSpecificCasing == eLSCB_Dutch && ch == 'i') {
530 ch = 'I';
531 capitalizeDutchIJ = true;
532 break;
535 mcm = mozilla::unicode::SpecialTitle(ch);
536 if (mcm) {
537 int j = 0;
538 while (j < 2 && mcm->mMappedChars[j + 1]) {
539 aConvertedString.Append(mcm->mMappedChars[j]);
540 ++extraChars;
541 ++j;
543 ch = mcm->mMappedChars[j];
544 break;
547 ch = ToTitleCase(ch);
550 break;
552 case NS_STYLE_TEXT_TRANSFORM_FULLWIDTH:
553 ch = mozilla::unicode::GetFullWidth(ch);
554 break;
556 default:
557 break;
560 if (ch == uint32_t(-1)) {
561 aDeletedCharsArray.AppendElement(true);
562 mergeNeeded = true;
563 } else {
564 aDeletedCharsArray.AppendElement(false);
565 aCharsToMergeArray.AppendElement(false);
566 if (aTextRun) {
567 aStyleArray->AppendElement(styleContext);
568 aCanBreakBeforeArray->AppendElement(inhibitBreakBefore ? false :
569 aTextRun->CanBreakLineBefore(i));
572 if (IS_IN_BMP(ch)) {
573 aConvertedString.Append(ch);
574 } else {
575 aConvertedString.Append(H_SURROGATE(ch));
576 aConvertedString.Append(L_SURROGATE(ch));
577 ++i;
578 aDeletedCharsArray.AppendElement(true); // not exactly deleted, but the
579 // trailing surrogate is skipped
580 ++extraChars;
583 while (extraChars-- > 0) {
584 mergeNeeded = true;
585 aCharsToMergeArray.AppendElement(true);
586 if (aTextRun) {
587 aStyleArray->AppendElement(styleContext);
588 aCanBreakBeforeArray->AppendElement(false);
594 return mergeNeeded;
597 void
598 nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
599 gfxContext* aRefContext, gfxMissingFontRecorder *aMFR)
601 nsAutoString convertedString;
602 nsAutoTArray<bool,50> charsToMergeArray;
603 nsAutoTArray<bool,50> deletedCharsArray;
604 nsAutoTArray<uint8_t,50> canBreakBeforeArray;
605 nsAutoTArray<nsStyleContext*,50> styleArray;
607 bool mergeNeeded = TransformString(aTextRun->mString,
608 convertedString,
609 mAllUppercase,
610 nullptr,
611 charsToMergeArray,
612 deletedCharsArray,
613 aTextRun,
614 &canBreakBeforeArray,
615 &styleArray);
617 uint32_t flags;
618 gfxTextRunFactory::Parameters innerParams =
619 GetParametersForInner(aTextRun, &flags, aRefContext);
620 gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
622 nsAutoPtr<nsTransformedTextRun> transformedChild;
623 nsAutoPtr<gfxTextRun> cachedChild;
624 gfxTextRun* child;
626 if (mInnerTransformingTextRunFactory) {
627 transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
628 convertedString.BeginReading(), convertedString.Length(),
629 &innerParams, fontGroup, flags, styleArray.Elements(), false);
630 child = transformedChild.get();
631 } else {
632 cachedChild = fontGroup->MakeTextRun(
633 convertedString.BeginReading(), convertedString.Length(),
634 &innerParams, flags, aMFR);
635 child = cachedChild.get();
637 if (!child)
638 return;
639 // Copy potential linebreaks into child so they're preserved
640 // (and also child will be shaped appropriately)
641 NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
642 "Dropped characters or break-before values somewhere!");
643 child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
644 canBreakBeforeArray.Elements(), aRefContext);
645 if (transformedChild) {
646 transformedChild->FinishSettingProperties(aRefContext, aMFR);
649 if (mergeNeeded) {
650 // Now merge multiple characters into one multi-glyph character as required
651 // and deal with skipping deleted accent chars
652 NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
653 "source length mismatch");
654 NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
655 "destination length mismatch");
656 MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
657 deletedCharsArray.Elements());
658 } else {
659 // No merging to do, so just copy; this produces a more optimized textrun.
660 // We can't steal the data because the child may be cached and stealing
661 // the data would break the cache.
662 aTextRun->ResetGlyphRuns();
663 aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), 0);