Line data Source code
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/. */
5 :
6 : #include "nsTextRunTransformations.h"
7 :
8 : #include "mozilla/MemoryReporting.h"
9 : #include "mozilla/Move.h"
10 :
11 : #include "nsGkAtoms.h"
12 : #include "nsStyleConsts.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 "GreekCasing.h"
20 : #include "IrishCasing.h"
21 :
22 : using namespace mozilla;
23 : using namespace mozilla::gfx;
24 :
25 : // Unicode characters needing special casing treatment in tr/az languages
26 : #define LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE 0x0130
27 : #define LATIN_SMALL_LETTER_DOTLESS_I 0x0131
28 :
29 : // Greek sigma needs custom handling for the lowercase transform; for details
30 : // see comments under "case NS_STYLE_TEXT_TRANSFORM_LOWERCASE" within
31 : // nsCaseTransformTextRunFactory::RebuildTextRun(), and bug 740120.
32 : #define GREEK_CAPITAL_LETTER_SIGMA 0x03A3
33 : #define GREEK_SMALL_LETTER_FINAL_SIGMA 0x03C2
34 : #define GREEK_SMALL_LETTER_SIGMA 0x03C3
35 :
36 : already_AddRefed<nsTransformedTextRun>
37 0 : nsTransformedTextRun::Create(const gfxTextRunFactory::Parameters* aParams,
38 : nsTransformingTextRunFactory* aFactory,
39 : gfxFontGroup* aFontGroup,
40 : const char16_t* aString, uint32_t aLength,
41 : const gfx::ShapedTextFlags aFlags,
42 : const nsTextFrameUtils::Flags aFlags2,
43 : nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles,
44 : bool aOwnsFactory)
45 : {
46 0 : NS_ASSERTION(!(aFlags & gfx::ShapedTextFlags::TEXT_IS_8BIT),
47 : "didn't expect text to be marked as 8-bit here");
48 :
49 0 : void *storage = AllocateStorageForTextRun(sizeof(nsTransformedTextRun), aLength);
50 0 : if (!storage) {
51 0 : return nullptr;
52 : }
53 :
54 : RefPtr<nsTransformedTextRun> result =
55 : new (storage) nsTransformedTextRun(aParams, aFactory, aFontGroup,
56 : aString, aLength, aFlags, aFlags2,
57 0 : Move(aStyles), aOwnsFactory);
58 0 : return result.forget();
59 : }
60 :
61 : void
62 0 : nsTransformedTextRun::SetCapitalization(uint32_t aStart, uint32_t aLength,
63 : bool* aCapitalization)
64 : {
65 0 : if (mCapitalize.IsEmpty()) {
66 0 : if (!mCapitalize.AppendElements(GetLength()))
67 0 : return;
68 0 : memset(mCapitalize.Elements(), 0, GetLength()*sizeof(bool));
69 : }
70 0 : memcpy(mCapitalize.Elements() + aStart, aCapitalization, aLength*sizeof(bool));
71 0 : mNeedsRebuild = true;
72 : }
73 :
74 : bool
75 0 : nsTransformedTextRun::SetPotentialLineBreaks(Range aRange,
76 : const uint8_t* aBreakBefore)
77 : {
78 0 : bool changed = gfxTextRun::SetPotentialLineBreaks(aRange, aBreakBefore);
79 0 : if (changed) {
80 0 : mNeedsRebuild = true;
81 : }
82 0 : return changed;
83 : }
84 :
85 : size_t
86 0 : nsTransformedTextRun::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
87 : {
88 0 : size_t total = gfxTextRun::SizeOfExcludingThis(aMallocSizeOf);
89 0 : total += mStyles.ShallowSizeOfExcludingThis(aMallocSizeOf);
90 0 : total += mCapitalize.ShallowSizeOfExcludingThis(aMallocSizeOf);
91 0 : if (mOwnsFactory) {
92 0 : total += aMallocSizeOf(mFactory);
93 : }
94 0 : return total;
95 : }
96 :
97 : size_t
98 0 : nsTransformedTextRun::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
99 : {
100 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
101 : }
102 :
103 : already_AddRefed<nsTransformedTextRun>
104 0 : nsTransformingTextRunFactory::MakeTextRun(const char16_t* aString, uint32_t aLength,
105 : const gfxTextRunFactory::Parameters* aParams,
106 : gfxFontGroup* aFontGroup,
107 : gfx::ShapedTextFlags aFlags,
108 : nsTextFrameUtils::Flags aFlags2,
109 : nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles,
110 : bool aOwnsFactory)
111 : {
112 : return nsTransformedTextRun::Create(aParams, this, aFontGroup,
113 0 : aString, aLength, aFlags, aFlags2, Move(aStyles),
114 0 : aOwnsFactory);
115 : }
116 :
117 : already_AddRefed<nsTransformedTextRun>
118 0 : nsTransformingTextRunFactory::MakeTextRun(const uint8_t* aString, uint32_t aLength,
119 : const gfxTextRunFactory::Parameters* aParams,
120 : gfxFontGroup* aFontGroup,
121 : gfx::ShapedTextFlags aFlags,
122 : nsTextFrameUtils::Flags aFlags2,
123 : nsTArray<RefPtr<nsTransformedCharStyle>>&& aStyles,
124 : bool aOwnsFactory)
125 : {
126 : // We'll only have a Unicode code path to minimize the amount of code needed
127 : // for these rarely used features
128 0 : NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString), aLength);
129 : return MakeTextRun(unicodeString.get(), aLength, aParams, aFontGroup,
130 0 : aFlags & ~gfx::ShapedTextFlags::TEXT_IS_8BIT,
131 : aFlags2,
132 0 : Move(aStyles), aOwnsFactory);
133 : }
134 :
135 : void
136 0 : MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
137 : const bool* aCharsToMerge, const bool* aDeletedChars)
138 : {
139 0 : aDest->ResetGlyphRuns();
140 :
141 0 : gfxTextRun::GlyphRunIterator iter(aSrc, gfxTextRun::Range(aSrc));
142 0 : uint32_t offset = 0;
143 0 : AutoTArray<gfxTextRun::DetailedGlyph,2> glyphs;
144 0 : while (iter.NextRun()) {
145 0 : const gfxTextRun::GlyphRun* run = iter.GetGlyphRun();
146 0 : nsresult rv = aDest->AddGlyphRun(run->mFont, run->mMatchType,
147 0 : offset, false, run->mOrientation);
148 0 : if (NS_FAILED(rv))
149 0 : return;
150 :
151 0 : bool anyMissing = false;
152 0 : uint32_t mergeRunStart = iter.GetStringStart();
153 0 : const gfxTextRun::CompressedGlyph *srcGlyphs = aSrc->GetCharacterGlyphs();
154 0 : gfxTextRun::CompressedGlyph mergedGlyph = srcGlyphs[mergeRunStart];
155 0 : uint32_t stringEnd = iter.GetStringEnd();
156 0 : for (uint32_t k = iter.GetStringStart(); k < stringEnd; ++k) {
157 0 : const gfxTextRun::CompressedGlyph g = srcGlyphs[k];
158 0 : if (g.IsSimpleGlyph()) {
159 0 : if (!anyMissing) {
160 : gfxTextRun::DetailedGlyph details;
161 0 : details.mGlyphID = g.GetSimpleGlyph();
162 0 : details.mAdvance = g.GetSimpleAdvance();
163 0 : details.mXOffset = 0;
164 0 : details.mYOffset = 0;
165 0 : glyphs.AppendElement(details);
166 : }
167 : } else {
168 0 : if (g.IsMissing()) {
169 0 : anyMissing = true;
170 0 : glyphs.Clear();
171 : }
172 0 : if (g.GetGlyphCount() > 0) {
173 0 : glyphs.AppendElements(aSrc->GetDetailedGlyphs(k), g.GetGlyphCount());
174 : }
175 : }
176 :
177 0 : if (k + 1 < iter.GetStringEnd() && aCharsToMerge[k + 1]) {
178 : // next char is supposed to merge with current, so loop without
179 : // writing current merged glyph to the destination
180 0 : continue;
181 : }
182 :
183 : // If the start of the merge run is actually a character that should
184 : // have been merged with the previous character (this can happen
185 : // if there's a font change in the middle of a case-mapped character,
186 : // that decomposed into a sequence of base+diacritics, for example),
187 : // just discard the entire merge run. See comment at start of this
188 : // function.
189 0 : NS_WARNING_ASSERTION(
190 : !aCharsToMerge[mergeRunStart],
191 : "unable to merge across a glyph run boundary, glyph(s) discarded");
192 0 : if (!aCharsToMerge[mergeRunStart]) {
193 0 : if (anyMissing) {
194 0 : mergedGlyph.SetMissing(glyphs.Length());
195 : } else {
196 0 : mergedGlyph.SetComplex(mergedGlyph.IsClusterStart(),
197 0 : mergedGlyph.IsLigatureGroupStart(),
198 0 : glyphs.Length());
199 : }
200 0 : aDest->SetGlyphs(offset, mergedGlyph, glyphs.Elements());
201 0 : ++offset;
202 :
203 0 : while (offset < aDest->GetLength() && aDeletedChars[offset]) {
204 0 : aDest->SetGlyphs(offset++, gfxTextRun::CompressedGlyph(), nullptr);
205 : }
206 : }
207 :
208 0 : glyphs.Clear();
209 0 : anyMissing = false;
210 0 : mergeRunStart = k + 1;
211 0 : if (mergeRunStart < stringEnd) {
212 0 : mergedGlyph = srcGlyphs[mergeRunStart];
213 : }
214 : }
215 0 : NS_ASSERTION(glyphs.Length() == 0,
216 : "Leftover glyphs, don't request merging of the last character with its next!");
217 : }
218 0 : NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
219 : }
220 :
221 : gfxTextRunFactory::Parameters
222 0 : GetParametersForInner(nsTransformedTextRun* aTextRun,
223 : gfx::ShapedTextFlags* aFlags,
224 : DrawTarget* aRefDrawTarget)
225 : {
226 : gfxTextRunFactory::Parameters params =
227 : { aRefDrawTarget, nullptr, nullptr,
228 0 : nullptr, 0, aTextRun->GetAppUnitsPerDevUnit()
229 0 : };
230 0 : *aFlags = aTextRun->GetFlags();
231 0 : return params;
232 : }
233 :
234 : // Some languages have special casing conventions that differ from the
235 : // default Unicode mappings.
236 : // The enum values here are named for well-known exemplar languages that
237 : // exhibit the behavior in question; multiple lang tags may map to the
238 : // same setting here, if the behavior is shared by other languages.
239 : enum LanguageSpecificCasingBehavior {
240 : eLSCB_None, // default non-lang-specific behavior
241 : eLSCB_Dutch, // treat "ij" digraph as a unit for capitalization
242 : eLSCB_Greek, // strip accent when uppercasing Greek vowels
243 : eLSCB_Irish, // keep prefix letters as lowercase when uppercasing Irish
244 : eLSCB_Turkish // preserve dotted/dotless-i distinction in uppercase
245 : };
246 :
247 : static LanguageSpecificCasingBehavior
248 0 : GetCasingFor(const nsIAtom* aLang)
249 : {
250 0 : if (!aLang) {
251 0 : return eLSCB_None;
252 : }
253 0 : if (aLang == nsGkAtoms::tr ||
254 0 : aLang == nsGkAtoms::az ||
255 0 : aLang == nsGkAtoms::ba ||
256 0 : aLang == nsGkAtoms::crh ||
257 0 : aLang == nsGkAtoms::tt) {
258 0 : return eLSCB_Turkish;
259 : }
260 0 : if (aLang == nsGkAtoms::nl) {
261 0 : return eLSCB_Dutch;
262 : }
263 0 : if (aLang == nsGkAtoms::el) {
264 0 : return eLSCB_Greek;
265 : }
266 0 : if (aLang == nsGkAtoms::ga) {
267 0 : return eLSCB_Irish;
268 : }
269 :
270 : // Is there a region subtag we should ignore?
271 0 : nsAtomString langStr(const_cast<nsIAtom*>(aLang));
272 0 : int index = langStr.FindChar('-');
273 0 : if (index > 0) {
274 0 : langStr.Truncate(index);
275 0 : nsCOMPtr<nsIAtom> truncatedLang = NS_Atomize(langStr);
276 0 : return GetCasingFor(truncatedLang);
277 : }
278 :
279 0 : return eLSCB_None;
280 : }
281 :
282 : bool
283 0 : nsCaseTransformTextRunFactory::TransformString(
284 : const nsAString& aString,
285 : nsString& aConvertedString,
286 : bool aAllUppercase,
287 : const nsIAtom* aLanguage,
288 : nsTArray<bool>& aCharsToMergeArray,
289 : nsTArray<bool>& aDeletedCharsArray,
290 : const nsTransformedTextRun* aTextRun,
291 : uint32_t aOffsetInTextRun,
292 : nsTArray<uint8_t>* aCanBreakBeforeArray,
293 : nsTArray<RefPtr<nsTransformedCharStyle>>* aStyleArray)
294 : {
295 0 : bool auxiliaryOutputArrays = aCanBreakBeforeArray && aStyleArray;
296 0 : MOZ_ASSERT(!auxiliaryOutputArrays || aTextRun,
297 : "text run must be provided to use aux output arrays");
298 :
299 0 : uint32_t length = aString.Length();
300 0 : const char16_t* str = aString.BeginReading();
301 :
302 0 : bool mergeNeeded = false;
303 :
304 0 : bool capitalizeDutchIJ = false;
305 0 : bool prevIsLetter = false;
306 0 : bool ntPrefix = false; // true immediately after a word-initial 'n' or 't'
307 : // when doing Irish lowercasing
308 0 : uint32_t sigmaIndex = uint32_t(-1);
309 : nsUGenCategory cat;
310 :
311 0 : uint8_t style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE : 0;
312 0 : bool forceNonFullWidth = false;
313 0 : const nsIAtom* lang = aLanguage;
314 :
315 0 : LanguageSpecificCasingBehavior languageSpecificCasing = GetCasingFor(lang);
316 0 : mozilla::GreekCasing::State greekState;
317 0 : mozilla::IrishCasing::State irishState;
318 0 : uint32_t irishMark = uint32_t(-1); // location of possible prefix letter(s)
319 : // in the output string
320 0 : uint32_t irishMarkSrc = uint32_t(-1); // corresponding location in source
321 : // string (may differ from output due to
322 : // expansions like eszet -> 'SS')
323 0 : uint32_t greekMark = uint32_t(-1); // location of uppercase ETA that may need
324 : // tonos added (if it is disjunctive eta)
325 0 : const char16_t kGreekUpperEta = 0x0397;
326 :
327 0 : for (uint32_t i = 0; i < length; ++i, ++aOffsetInTextRun) {
328 0 : uint32_t ch = str[i];
329 :
330 0 : RefPtr<nsTransformedCharStyle> charStyle;
331 0 : if (aTextRun) {
332 0 : charStyle = aTextRun->mStyles[aOffsetInTextRun];
333 0 : style = aAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE :
334 0 : charStyle->mTextTransform;
335 0 : forceNonFullWidth = charStyle->mForceNonFullWidth;
336 :
337 0 : nsIAtom* newLang = charStyle->mExplicitLanguage
338 0 : ? charStyle->mLanguage.get() : nullptr;
339 0 : if (lang != newLang) {
340 0 : lang = newLang;
341 0 : languageSpecificCasing = GetCasingFor(lang);
342 0 : greekState.Reset();
343 0 : irishState.Reset();
344 0 : irishMark = uint32_t(-1);
345 0 : irishMarkSrc = uint32_t(-1);
346 0 : greekMark = uint32_t(-1);
347 : }
348 : }
349 :
350 0 : int extraChars = 0;
351 : const mozilla::unicode::MultiCharMapping *mcm;
352 0 : bool inhibitBreakBefore = false; // have we just deleted preceding hyphen?
353 :
354 0 : if (NS_IS_HIGH_SURROGATE(ch) && i < length - 1 &&
355 0 : NS_IS_LOW_SURROGATE(str[i + 1])) {
356 0 : ch = SURROGATE_TO_UCS4(ch, str[i + 1]);
357 : }
358 :
359 0 : switch (style) {
360 : case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
361 0 : if (languageSpecificCasing == eLSCB_Turkish) {
362 0 : if (ch == 'I') {
363 0 : ch = LATIN_SMALL_LETTER_DOTLESS_I;
364 0 : prevIsLetter = true;
365 0 : sigmaIndex = uint32_t(-1);
366 0 : break;
367 : }
368 0 : if (ch == LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) {
369 0 : ch = 'i';
370 0 : prevIsLetter = true;
371 0 : sigmaIndex = uint32_t(-1);
372 0 : break;
373 : }
374 : }
375 :
376 0 : cat = mozilla::unicode::GetGenCategory(ch);
377 :
378 0 : if (languageSpecificCasing == eLSCB_Irish &&
379 : cat == nsUGenCategory::kLetter) {
380 : // See bug 1018805 for Irish lowercasing requirements
381 0 : if (!prevIsLetter && (ch == 'n' || ch == 't')) {
382 0 : ntPrefix = true;
383 : } else {
384 0 : if (ntPrefix && mozilla::IrishCasing::IsUpperVowel(ch)) {
385 0 : aConvertedString.Append('-');
386 0 : ++extraChars;
387 : }
388 0 : ntPrefix = false;
389 : }
390 : } else {
391 0 : ntPrefix = false;
392 : }
393 :
394 : // Special lowercasing behavior for Greek Sigma: note that this is listed
395 : // as context-sensitive in Unicode's SpecialCasing.txt, but is *not* a
396 : // language-specific mapping; it applies regardless of the language of
397 : // the element.
398 : //
399 : // The lowercase mapping for CAPITAL SIGMA should be to SMALL SIGMA (i.e.
400 : // the non-final form) whenever there is a following letter, or when the
401 : // CAPITAL SIGMA occurs in isolation (neither preceded nor followed by a
402 : // LETTER); and to FINAL SIGMA when it is preceded by another letter but
403 : // not followed by one.
404 : //
405 : // To implement the context-sensitive nature of this mapping, we keep
406 : // track of whether the previous character was a letter. If not, CAPITAL
407 : // SIGMA will map directly to SMALL SIGMA. If the previous character
408 : // was a letter, CAPITAL SIGMA maps to FINAL SIGMA and we record the
409 : // position in the converted string; if we then encounter another letter,
410 : // that FINAL SIGMA is replaced with a standard SMALL SIGMA.
411 :
412 : // If sigmaIndex is not -1, it marks where we have provisionally mapped
413 : // a CAPITAL SIGMA to FINAL SIGMA; if we now find another letter, we
414 : // need to change it to SMALL SIGMA.
415 0 : if (sigmaIndex != uint32_t(-1)) {
416 0 : if (cat == nsUGenCategory::kLetter) {
417 0 : aConvertedString.SetCharAt(GREEK_SMALL_LETTER_SIGMA, sigmaIndex);
418 : }
419 : }
420 :
421 0 : if (ch == GREEK_CAPITAL_LETTER_SIGMA) {
422 : // If preceding char was a letter, map to FINAL instead of SMALL,
423 : // and note where it occurred by setting sigmaIndex; we'll change it
424 : // to standard SMALL SIGMA later if another letter follows
425 0 : if (prevIsLetter) {
426 0 : ch = GREEK_SMALL_LETTER_FINAL_SIGMA;
427 0 : sigmaIndex = aConvertedString.Length();
428 : } else {
429 : // CAPITAL SIGMA not preceded by a letter is unconditionally mapped
430 : // to SMALL SIGMA
431 0 : ch = GREEK_SMALL_LETTER_SIGMA;
432 0 : sigmaIndex = uint32_t(-1);
433 : }
434 0 : prevIsLetter = true;
435 0 : break;
436 : }
437 :
438 : // ignore diacritics for the purpose of contextual sigma mapping;
439 : // otherwise, reset prevIsLetter appropriately and clear the
440 : // sigmaIndex marker
441 0 : if (cat != nsUGenCategory::kMark) {
442 0 : prevIsLetter = (cat == nsUGenCategory::kLetter);
443 0 : sigmaIndex = uint32_t(-1);
444 : }
445 :
446 0 : mcm = mozilla::unicode::SpecialLower(ch);
447 0 : if (mcm) {
448 0 : int j = 0;
449 0 : while (j < 2 && mcm->mMappedChars[j + 1]) {
450 0 : aConvertedString.Append(mcm->mMappedChars[j]);
451 0 : ++extraChars;
452 0 : ++j;
453 : }
454 0 : ch = mcm->mMappedChars[j];
455 0 : break;
456 : }
457 :
458 0 : ch = ToLowerCase(ch);
459 0 : break;
460 :
461 : case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
462 0 : if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
463 0 : ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
464 0 : break;
465 : }
466 :
467 0 : if (languageSpecificCasing == eLSCB_Greek) {
468 : bool markEta;
469 : bool updateEta;
470 : ch = mozilla::GreekCasing::UpperCase(ch, greekState,
471 0 : markEta, updateEta);
472 0 : if (markEta) {
473 0 : greekMark = aConvertedString.Length();
474 0 : } else if (updateEta) {
475 : // Remove the TONOS from an uppercase ETA-TONOS that turned out
476 : // not to be disjunctive-eta.
477 0 : MOZ_ASSERT(aConvertedString.Length() > 0 &&
478 : greekMark < aConvertedString.Length(),
479 : "bad greekMark!");
480 0 : aConvertedString.SetCharAt(kGreekUpperEta, greekMark);
481 0 : greekMark = uint32_t(-1);
482 : }
483 0 : break;
484 : }
485 :
486 0 : if (languageSpecificCasing == eLSCB_Irish) {
487 : bool mark;
488 : uint8_t action;
489 0 : ch = mozilla::IrishCasing::UpperCase(ch, irishState, mark, action);
490 0 : if (mark) {
491 0 : irishMark = aConvertedString.Length();
492 0 : irishMarkSrc = i;
493 0 : break;
494 0 : } else if (action) {
495 0 : nsString& str = aConvertedString; // shorthand
496 0 : switch (action) {
497 : case 1:
498 : // lowercase a single prefix letter
499 0 : NS_ASSERTION(str.Length() > 0 && irishMark < str.Length(),
500 : "bad irishMark!");
501 0 : str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
502 0 : irishMark = uint32_t(-1);
503 0 : irishMarkSrc = uint32_t(-1);
504 0 : break;
505 : case 2:
506 : // lowercase two prefix letters (immediately before current pos)
507 0 : NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2,
508 : "bad irishMark!");
509 0 : str.SetCharAt(ToLowerCase(str[irishMark]), irishMark);
510 0 : str.SetCharAt(ToLowerCase(str[irishMark + 1]), irishMark + 1);
511 0 : irishMark = uint32_t(-1);
512 0 : irishMarkSrc = uint32_t(-1);
513 0 : break;
514 : case 3:
515 : // lowercase one prefix letter, and delete following hyphen
516 : // (which must be the immediately-preceding char)
517 0 : NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2,
518 : "bad irishMark!");
519 0 : MOZ_ASSERT(irishMark != uint32_t(-1) && irishMarkSrc != uint32_t(-1),
520 : "failed to set irishMarks");
521 0 : str.Replace(irishMark, 2, ToLowerCase(str[irishMark]));
522 0 : aDeletedCharsArray[irishMarkSrc + 1] = true;
523 : // Remove the trailing entries (corresponding to the deleted hyphen)
524 : // from the auxiliary arrays.
525 0 : aCharsToMergeArray.SetLength(aCharsToMergeArray.Length() - 1);
526 0 : if (auxiliaryOutputArrays) {
527 0 : aStyleArray->SetLength(aStyleArray->Length() - 1);
528 0 : aCanBreakBeforeArray->SetLength(aCanBreakBeforeArray->Length() - 1);
529 0 : inhibitBreakBefore = true;
530 : }
531 0 : mergeNeeded = true;
532 0 : irishMark = uint32_t(-1);
533 0 : irishMarkSrc = uint32_t(-1);
534 0 : break;
535 : }
536 : // ch has been set to the uppercase for current char;
537 : // No need to check for SpecialUpper here as none of the characters
538 : // that could trigger an Irish casing action have special mappings.
539 0 : break;
540 : }
541 : // If we didn't have any special action to perform, fall through
542 : // to check for special uppercase (ß)
543 : }
544 :
545 0 : mcm = mozilla::unicode::SpecialUpper(ch);
546 0 : if (mcm) {
547 0 : int j = 0;
548 0 : while (j < 2 && mcm->mMappedChars[j + 1]) {
549 0 : aConvertedString.Append(mcm->mMappedChars[j]);
550 0 : ++extraChars;
551 0 : ++j;
552 : }
553 0 : ch = mcm->mMappedChars[j];
554 0 : break;
555 : }
556 :
557 0 : ch = ToUpperCase(ch);
558 0 : break;
559 :
560 : case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
561 0 : if (aTextRun) {
562 0 : if (capitalizeDutchIJ && ch == 'j') {
563 0 : ch = 'J';
564 0 : capitalizeDutchIJ = false;
565 0 : break;
566 : }
567 0 : capitalizeDutchIJ = false;
568 0 : if (aOffsetInTextRun < aTextRun->mCapitalize.Length() &&
569 0 : aTextRun->mCapitalize[aOffsetInTextRun]) {
570 0 : if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') {
571 0 : ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE;
572 0 : break;
573 : }
574 0 : if (languageSpecificCasing == eLSCB_Dutch && ch == 'i') {
575 0 : ch = 'I';
576 0 : capitalizeDutchIJ = true;
577 0 : break;
578 : }
579 :
580 0 : mcm = mozilla::unicode::SpecialTitle(ch);
581 0 : if (mcm) {
582 0 : int j = 0;
583 0 : while (j < 2 && mcm->mMappedChars[j + 1]) {
584 0 : aConvertedString.Append(mcm->mMappedChars[j]);
585 0 : ++extraChars;
586 0 : ++j;
587 : }
588 0 : ch = mcm->mMappedChars[j];
589 0 : break;
590 : }
591 :
592 0 : ch = ToTitleCase(ch);
593 : }
594 : }
595 0 : break;
596 :
597 : case NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH:
598 0 : ch = mozilla::unicode::GetFullWidth(ch);
599 0 : break;
600 :
601 : default:
602 0 : break;
603 : }
604 :
605 0 : if (forceNonFullWidth) {
606 0 : ch = mozilla::unicode::GetFullWidthInverse(ch);
607 : }
608 :
609 0 : if (ch == uint32_t(-1)) {
610 0 : aDeletedCharsArray.AppendElement(true);
611 0 : mergeNeeded = true;
612 : } else {
613 0 : aDeletedCharsArray.AppendElement(false);
614 0 : aCharsToMergeArray.AppendElement(false);
615 0 : if (auxiliaryOutputArrays) {
616 0 : aStyleArray->AppendElement(charStyle);
617 0 : aCanBreakBeforeArray->AppendElement(
618 0 : inhibitBreakBefore ? gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_NONE
619 0 : : aTextRun->CanBreakBefore(aOffsetInTextRun));
620 : }
621 :
622 0 : if (IS_IN_BMP(ch)) {
623 0 : aConvertedString.Append(ch);
624 : } else {
625 0 : aConvertedString.Append(H_SURROGATE(ch));
626 0 : aConvertedString.Append(L_SURROGATE(ch));
627 0 : i++;
628 0 : aOffsetInTextRun++;
629 0 : aDeletedCharsArray.AppendElement(true); // not exactly deleted, but the
630 : // trailing surrogate is skipped
631 0 : ++extraChars;
632 : }
633 :
634 0 : while (extraChars-- > 0) {
635 0 : mergeNeeded = true;
636 0 : aCharsToMergeArray.AppendElement(true);
637 0 : if (auxiliaryOutputArrays) {
638 0 : aStyleArray->AppendElement(charStyle);
639 0 : aCanBreakBeforeArray->AppendElement(
640 0 : gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_NONE);
641 : }
642 : }
643 : }
644 : }
645 :
646 0 : return mergeNeeded;
647 : }
648 :
649 : void
650 0 : nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
651 : DrawTarget* aRefDrawTarget,
652 : gfxMissingFontRecorder* aMFR)
653 : {
654 0 : nsAutoString convertedString;
655 0 : AutoTArray<bool,50> charsToMergeArray;
656 0 : AutoTArray<bool,50> deletedCharsArray;
657 0 : AutoTArray<uint8_t,50> canBreakBeforeArray;
658 0 : AutoTArray<RefPtr<nsTransformedCharStyle>,50> styleArray;
659 :
660 0 : bool mergeNeeded = TransformString(aTextRun->mString,
661 : convertedString,
662 0 : mAllUppercase,
663 : nullptr,
664 : charsToMergeArray,
665 : deletedCharsArray,
666 : aTextRun, 0,
667 : &canBreakBeforeArray,
668 0 : &styleArray);
669 :
670 : gfx::ShapedTextFlags flags;
671 : gfxTextRunFactory::Parameters innerParams =
672 0 : GetParametersForInner(aTextRun, &flags, aRefDrawTarget);
673 0 : gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
674 :
675 0 : RefPtr<nsTransformedTextRun> transformedChild;
676 0 : RefPtr<gfxTextRun> cachedChild;
677 : gfxTextRun* child;
678 :
679 0 : if (mInnerTransformingTextRunFactory) {
680 0 : transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
681 : convertedString.BeginReading(), convertedString.Length(),
682 : &innerParams, fontGroup, flags, nsTextFrameUtils::Flags(),
683 0 : Move(styleArray), false);
684 0 : child = transformedChild.get();
685 : } else {
686 0 : cachedChild = fontGroup->MakeTextRun(
687 : convertedString.BeginReading(), convertedString.Length(),
688 0 : &innerParams, flags, nsTextFrameUtils::Flags(), aMFR);
689 0 : child = cachedChild.get();
690 : }
691 0 : if (!child)
692 0 : return;
693 : // Copy potential linebreaks into child so they're preserved
694 : // (and also child will be shaped appropriately)
695 0 : NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
696 : "Dropped characters or break-before values somewhere!");
697 0 : gfxTextRun::Range range(0, uint32_t(canBreakBeforeArray.Length()));
698 0 : child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
699 0 : if (transformedChild) {
700 0 : transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
701 : }
702 :
703 0 : if (mergeNeeded) {
704 : // Now merge multiple characters into one multi-glyph character as required
705 : // and deal with skipping deleted accent chars
706 0 : NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
707 : "source length mismatch");
708 0 : NS_ASSERTION(deletedCharsArray.Length() == aTextRun->GetLength(),
709 : "destination length mismatch");
710 0 : MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
711 0 : deletedCharsArray.Elements());
712 : } else {
713 : // No merging to do, so just copy; this produces a more optimized textrun.
714 : // We can't steal the data because the child may be cached and stealing
715 : // the data would break the cache.
716 0 : aTextRun->ResetGlyphRuns();
717 0 : aTextRun->CopyGlyphDataFrom(child, gfxTextRun::Range(child), 0);
718 : }
719 : }
|