Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / base / nsLineBreaker.cpp
blobffc224fc278c361167a93d5ff4c78a77ec1a57b1
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsLineBreaker.h"
8 #include "nsContentUtils.h"
9 #include "gfxTextRun.h" // for the gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_* values
10 #include "nsHyphenationManager.h"
11 #include "nsHyphenator.h"
12 #include "mozilla/AutoRestore.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/gfx/2D.h"
15 #include "mozilla/intl/LineBreaker.h" // for LineBreaker::ComputeBreakPositions
16 #include "mozilla/intl/Locale.h"
17 #include "mozilla/intl/UnicodeProperties.h"
18 #include "mozilla/StaticPrefs_intl.h"
20 using mozilla::AutoRestore;
21 using mozilla::intl::LineBreaker;
22 using mozilla::intl::LineBreakRule;
23 using mozilla::intl::Locale;
24 using mozilla::intl::LocaleParser;
25 using mozilla::intl::UnicodeProperties;
26 using mozilla::intl::WordBreakRule;
28 // There is no break opportunity between any pair of characters that has line
29 // break class of either AL (Alphabetic), IS (Infix Numeric Separator), NU
30 // (Numeric), or QU (Quotation). See
31 // https://www.unicode.org/Public/UCD/latest/ucd/LineBreak.txt for Unicode code
32 // point and line break class mapping.
33 static constexpr uint8_t kNonBreakableASCII[] = {
34 // clang-format off
35 // 0x20-0x2f
36 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0,
37 // 0x30-0x3f
38 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
39 // 0x40-0x4f
40 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
41 // 0x50-0x5f
42 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
43 // 0x60-0x6f
44 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
45 // 0x70-0x7f
46 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
47 // clang-format on
50 template <typename T>
51 static constexpr bool IsNonBreakableChar(T aChar, bool aLegacyBehavior) {
52 if (aLegacyBehavior) {
53 // If not using ICU4X, line break rules aren't compatible with UAX#14. Use
54 // old way.
55 return (0x0030 <= aChar && aChar <= 0x0039) ||
56 (0x0041 <= aChar && aChar <= 0x005A) ||
57 (0x0061 <= aChar && aChar <= 0x007A) || (0x000a == aChar);
59 if (aChar < 0x20 || aChar > 0x7f) {
60 return false;
62 return !!kNonBreakableASCII[aChar - 0x20];
65 nsLineBreaker::nsLineBreaker()
66 : mCurrentWordLanguage(nullptr),
67 mCurrentWordContainsMixedLang(false),
68 mScriptIsChineseOrJapanese(false),
69 mAfterBreakableSpace(false),
70 mBreakHere(false),
71 mWordBreak(WordBreakRule::Normal),
72 mLineBreak(LineBreakRule::Auto),
73 mWordContinuation(false),
74 mLegacyBehavior(!mozilla::StaticPrefs::intl_icu4x_segmenter_enabled()) {}
76 nsLineBreaker::~nsLineBreaker() {
77 NS_ASSERTION(mCurrentWord.Length() == 0,
78 "Should have Reset() before destruction!");
81 static void SetupCapitalization(const char16_t* aWord, uint32_t aLength,
82 bool* aCapitalization) {
83 // Capitalize the first alphanumeric character after a space or punctuation.
84 using mozilla::intl::GeneralCategory;
85 bool capitalizeNextChar = true;
86 for (uint32_t i = 0; i < aLength; ++i) {
87 uint32_t ch = aWord[i];
88 if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aWord[i + 1])) {
89 ch = SURROGATE_TO_UCS4(ch, aWord[i + 1]);
91 auto category = UnicodeProperties::CharType(ch);
92 switch (category) {
93 case GeneralCategory::Uppercase_Letter:
94 case GeneralCategory::Lowercase_Letter:
95 case GeneralCategory::Titlecase_Letter:
96 case GeneralCategory::Modifier_Letter:
97 case GeneralCategory::Other_Letter:
98 case GeneralCategory::Decimal_Number:
99 case GeneralCategory::Letter_Number:
100 case GeneralCategory::Other_Number:
101 if (capitalizeNextChar) {
102 aCapitalization[i] = true;
103 capitalizeNextChar = false;
105 break;
106 case GeneralCategory::Space_Separator:
107 case GeneralCategory::Line_Separator:
108 case GeneralCategory::Paragraph_Separator:
109 case GeneralCategory::Dash_Punctuation:
110 case GeneralCategory::Initial_Punctuation:
111 /* These punctuation categories are excluded, for examples like
112 * "what colo[u]r" -> "What Colo[u]r?" (rather than "What Colo[U]R?")
113 * and
114 * "snake_case" -> "Snake_case" (to match word selection behavior)
115 case GeneralCategory::Open_Punctuation:
116 case GeneralCategory::Close_Punctuation:
117 case GeneralCategory::Connector_Punctuation:
119 capitalizeNextChar = true;
120 break;
121 case GeneralCategory::Final_Punctuation:
122 /* Special-case: exclude Unicode single-close-quote/apostrophe,
123 for examples like "Lowe’s" etc. */
124 if (ch != 0x2019) {
125 capitalizeNextChar = true;
127 break;
128 case GeneralCategory::Other_Punctuation:
129 /* Special-case: exclude ASCII apostrophe, for "Lowe's" etc.,
130 and MIDDLE DOT, for Catalan "l·l". */
131 if (ch != '\'' && ch != 0x00B7) {
132 capitalizeNextChar = true;
134 break;
135 default:
136 break;
138 if (!IS_IN_BMP(ch)) {
139 ++i;
144 nsresult nsLineBreaker::FlushCurrentWord() {
145 uint32_t length = mCurrentWord.Length();
146 AutoTArray<uint8_t, 4000> breakState;
147 if (!breakState.AppendElements(length, mozilla::fallible)) {
148 return NS_ERROR_OUT_OF_MEMORY;
151 if (mLineBreak == LineBreakRule::Anywhere) {
152 memset(breakState.Elements(),
153 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
154 length * sizeof(uint8_t));
155 } else if (!mCurrentWordMightBeBreakable &&
156 mWordBreak != WordBreakRule::BreakAll) {
157 // word-break: normal or keep-all has no break opportunity if the word
158 // is non-breakable. (See the comment of kNonBreakableASCII).
159 memset(breakState.Elements(),
160 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE,
161 length * sizeof(uint8_t));
162 } else {
163 LineBreaker::ComputeBreakPositions(
164 mCurrentWord.Elements(), length, mWordBreak, mLineBreak,
165 mScriptIsChineseOrJapanese, breakState.Elements());
168 bool autoHyphenate = mCurrentWordLanguage && !mCurrentWordContainsMixedLang;
169 uint32_t i;
170 for (i = 0; autoHyphenate && i < mTextItems.Length(); ++i) {
171 TextItem* ti = &mTextItems[i];
172 if (!(ti->mFlags & BREAK_USE_AUTO_HYPHENATION)) {
173 autoHyphenate = false;
176 if (autoHyphenate) {
177 RefPtr<nsHyphenator> hyphenator =
178 nsHyphenationManager::Instance()->GetHyphenator(mCurrentWordLanguage);
179 if (hyphenator) {
180 FindHyphenationPoints(hyphenator, mCurrentWord.Elements(),
181 mCurrentWord.Elements() + length,
182 breakState.Elements());
186 nsTArray<bool> capitalizationState;
187 uint32_t offset = 0;
188 for (i = 0; i < mTextItems.Length(); ++i) {
189 TextItem* ti = &mTextItems[i];
190 NS_ASSERTION(ti->mLength > 0, "Zero length word contribution?");
192 if ((ti->mFlags & BREAK_SUPPRESS_INITIAL) && ti->mSinkOffset == 0) {
193 breakState[offset] = gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
195 if (ti->mFlags & BREAK_SUPPRESS_INSIDE) {
196 uint32_t exclude = ti->mSinkOffset == 0 ? 1 : 0;
197 memset(breakState.Elements() + offset + exclude,
198 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE,
199 (ti->mLength - exclude) * sizeof(uint8_t));
202 // Don't set the break state for the first character of the word, because
203 // it was already set correctly earlier and we don't know what the true
204 // value should be.
205 uint32_t skipSet = i == 0 ? 1 : 0;
206 if (ti->mSink) {
207 ti->mSink->SetBreaks(ti->mSinkOffset + skipSet, ti->mLength - skipSet,
208 breakState.Elements() + offset + skipSet);
210 if (!mWordContinuation && (ti->mFlags & BREAK_NEED_CAPITALIZATION)) {
211 if (capitalizationState.Length() == 0) {
212 if (!capitalizationState.AppendElements(length, mozilla::fallible)) {
213 return NS_ERROR_OUT_OF_MEMORY;
215 memset(capitalizationState.Elements(), false, length * sizeof(bool));
216 SetupCapitalization(mCurrentWord.Elements(), length,
217 capitalizationState.Elements());
219 ti->mSink->SetCapitalization(ti->mSinkOffset, ti->mLength,
220 capitalizationState.Elements() + offset);
224 offset += ti->mLength;
227 mCurrentWord.Clear();
228 mTextItems.Clear();
229 mCurrentWordMightBeBreakable = false;
230 mCurrentWordContainsMixedLang = false;
231 mCurrentWordLanguage = nullptr;
232 mWordContinuation = false;
233 return NS_OK;
236 // If the aFlags parameter to AppendText has all these bits set,
237 // then we don't need to worry about finding break opportunities
238 // in the appended text.
239 #define NO_BREAKS_NEEDED_FLAGS \
240 (BREAK_SUPPRESS_INITIAL | BREAK_SUPPRESS_INSIDE | \
241 BREAK_SKIP_SETTING_NO_BREAKS)
243 nsresult nsLineBreaker::AppendText(nsAtom* aHyphenationLanguage,
244 const char16_t* aText, uint32_t aLength,
245 uint32_t aFlags, nsILineBreakSink* aSink) {
246 NS_ASSERTION(aLength > 0, "Appending empty text...");
248 uint32_t offset = 0;
250 // Continue the current word
251 if (mCurrentWord.Length() > 0) {
252 NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere,
253 "These should not be set");
255 while (offset < aLength && !IsSegmentSpace(aText[offset])) {
256 mCurrentWord.AppendElement(aText[offset]);
257 if (!mCurrentWordMightBeBreakable &&
258 !IsNonBreakableChar<char16_t>(aText[offset], mLegacyBehavior)) {
259 mCurrentWordMightBeBreakable = true;
261 UpdateCurrentWordLanguage(aHyphenationLanguage);
262 ++offset;
265 if (offset > 0) {
266 mTextItems.AppendElement(TextItem(aSink, 0, offset, aFlags));
269 if (offset == aLength) {
270 return NS_OK;
273 // We encountered whitespace, so we're done with this word
274 nsresult rv = FlushCurrentWord();
275 if (NS_FAILED(rv)) {
276 return rv;
280 AutoTArray<uint8_t, 4000> breakState;
281 if (aSink) {
282 if (!breakState.AppendElements(aLength, mozilla::fallible)) {
283 return NS_ERROR_OUT_OF_MEMORY;
287 bool noCapitalizationNeeded = true;
288 nsTArray<bool> capitalizationState;
289 if (aSink && (aFlags & BREAK_NEED_CAPITALIZATION)) {
290 if (!capitalizationState.AppendElements(aLength, mozilla::fallible)) {
291 return NS_ERROR_OUT_OF_MEMORY;
293 memset(capitalizationState.Elements(), false, aLength * sizeof(bool));
294 noCapitalizationNeeded = false;
297 uint32_t start = offset;
298 bool noBreaksNeeded =
299 !aSink || ((aFlags & NO_BREAKS_NEEDED_FLAGS) == NO_BREAKS_NEEDED_FLAGS &&
300 !mBreakHere && !mAfterBreakableSpace);
301 if (noBreaksNeeded && noCapitalizationNeeded) {
302 // Skip to the space before the last word, since either the break data
303 // here is not needed, or no breaks are set in the sink and there cannot
304 // be any breaks in this chunk; and we don't need to do word-initial
305 // capitalization. All we need is the context for the next chunk (if any).
306 offset = aLength;
307 while (offset > start) {
308 --offset;
309 if (IsSegmentSpace(aText[offset])) {
310 break;
314 uint32_t wordStart = offset;
315 bool wordMightBeBreakable = false;
317 RefPtr<nsHyphenator> hyphenator;
318 if ((aFlags & BREAK_USE_AUTO_HYPHENATION) &&
319 !(aFlags & BREAK_SUPPRESS_INSIDE) && aHyphenationLanguage) {
320 hyphenator =
321 nsHyphenationManager::Instance()->GetHyphenator(aHyphenationLanguage);
324 for (;;) {
325 char16_t ch = aText[offset];
326 bool isSpace = IsSegmentSpace(ch);
327 bool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
329 if (aSink && !noBreaksNeeded) {
330 breakState[offset] =
331 mBreakHere || (mAfterBreakableSpace && !isBreakableSpace) ||
332 mWordBreak == WordBreakRule::BreakAll ||
333 mLineBreak == LineBreakRule::Anywhere
334 ? gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL
335 : gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
337 mBreakHere = false;
338 mAfterBreakableSpace = isBreakableSpace;
340 if (isSpace || ch == '\n') {
341 if (offset > wordStart && aSink) {
342 if (!(aFlags & BREAK_SUPPRESS_INSIDE)) {
343 if (mLineBreak == LineBreakRule::Anywhere) {
344 memset(breakState.Elements() + wordStart,
345 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
346 offset - wordStart);
347 } else if (wordMightBeBreakable) {
348 // Save current start-of-word state because ComputeBreakPositions()
349 // will set it to false.
350 AutoRestore<uint8_t> saveWordStartBreakState(breakState[wordStart]);
351 LineBreaker::ComputeBreakPositions(
352 aText + wordStart, offset - wordStart, mWordBreak, mLineBreak,
353 mScriptIsChineseOrJapanese, breakState.Elements() + wordStart);
355 if (hyphenator) {
356 FindHyphenationPoints(hyphenator, aText + wordStart, aText + offset,
357 breakState.Elements() + wordStart);
360 if (!mWordContinuation && !noCapitalizationNeeded) {
361 SetupCapitalization(aText + wordStart, offset - wordStart,
362 capitalizationState.Elements() + wordStart);
365 wordMightBeBreakable = false;
366 mWordContinuation = false;
367 ++offset;
368 if (offset >= aLength) {
369 break;
371 wordStart = offset;
372 continue;
375 if (!wordMightBeBreakable &&
376 !IsNonBreakableChar<char16_t>(ch, mLegacyBehavior)) {
377 wordMightBeBreakable = true;
379 ++offset;
380 if (offset >= aLength) {
381 // Save this word
382 mCurrentWordMightBeBreakable = wordMightBeBreakable;
383 uint32_t len = offset - wordStart;
384 char16_t* elems = mCurrentWord.AppendElements(len, mozilla::fallible);
385 if (!elems) {
386 return NS_ERROR_OUT_OF_MEMORY;
388 memcpy(elems, aText + wordStart, sizeof(char16_t) * len);
389 mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
390 // Ensure that the break-before for this word is written out
391 offset = wordStart + 1;
392 UpdateCurrentWordLanguage(aHyphenationLanguage);
393 break;
397 if (aSink) {
398 if (!noBreaksNeeded) {
399 aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
401 if (!noCapitalizationNeeded) {
402 aSink->SetCapitalization(start, offset - start,
403 capitalizationState.Elements() + start);
406 return NS_OK;
409 void nsLineBreaker::FindHyphenationPoints(nsHyphenator* aHyphenator,
410 const char16_t* aTextStart,
411 const char16_t* aTextLimit,
412 uint8_t* aBreakState) {
413 nsDependentSubstring string(aTextStart, aTextLimit);
414 AutoTArray<bool, 200> hyphens;
415 if (NS_SUCCEEDED(aHyphenator->Hyphenate(string, hyphens))) {
416 for (uint32_t i = 0; i + 1 < string.Length(); ++i) {
417 if (hyphens[i]) {
418 aBreakState[i + 1] =
419 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN;
425 nsresult nsLineBreaker::AppendText(nsAtom* aHyphenationLanguage,
426 const uint8_t* aText, uint32_t aLength,
427 uint32_t aFlags, nsILineBreakSink* aSink) {
428 NS_ASSERTION(aLength > 0, "Appending empty text...");
430 if (aFlags & (BREAK_NEED_CAPITALIZATION | BREAK_USE_AUTO_HYPHENATION)) {
431 // Defer to the Unicode path if capitalization or hyphenation is required
432 nsAutoString str;
433 const char* cp = reinterpret_cast<const char*>(aText);
434 CopyASCIItoUTF16(nsDependentCSubstring(cp, cp + aLength), str);
435 return AppendText(aHyphenationLanguage, str.get(), aLength, aFlags, aSink);
438 uint32_t offset = 0;
440 // Continue the current word
441 if (mCurrentWord.Length() > 0) {
442 NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere,
443 "These should not be set");
445 while (offset < aLength && !IsSegmentSpace(aText[offset])) {
446 mCurrentWord.AppendElement(aText[offset]);
447 if (!mCurrentWordMightBeBreakable &&
448 !IsNonBreakableChar<uint8_t>(aText[offset], mLegacyBehavior)) {
449 mCurrentWordMightBeBreakable = true;
451 ++offset;
454 if (offset > 0) {
455 mTextItems.AppendElement(TextItem(aSink, 0, offset, aFlags));
458 if (offset == aLength) {
459 // We did not encounter whitespace so the word hasn't finished yet.
460 return NS_OK;
463 // We encountered whitespace, so we're done with this word
464 nsresult rv = FlushCurrentWord();
465 if (NS_FAILED(rv)) {
466 return rv;
470 AutoTArray<uint8_t, 4000> breakState;
471 if (aSink) {
472 if (!breakState.AppendElements(aLength, mozilla::fallible)) {
473 return NS_ERROR_OUT_OF_MEMORY;
477 uint32_t start = offset;
478 bool noBreaksNeeded =
479 !aSink || ((aFlags & NO_BREAKS_NEEDED_FLAGS) == NO_BREAKS_NEEDED_FLAGS &&
480 !mBreakHere && !mAfterBreakableSpace);
481 if (noBreaksNeeded) {
482 // Skip to the space before the last word, since either the break data
483 // here is not needed, or no breaks are set in the sink and there cannot
484 // be any breaks in this chunk; all we need is the context for the next
485 // chunk (if any)
486 offset = aLength;
487 while (offset > start) {
488 --offset;
489 if (IsSegmentSpace(aText[offset])) {
490 break;
494 uint32_t wordStart = offset;
495 bool wordMightBeBreakable = false;
497 for (;;) {
498 uint8_t ch = aText[offset];
499 bool isSpace = IsSegmentSpace(ch);
500 bool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
502 if (aSink) {
503 // Consider word-break style. Since the break position of CJK scripts
504 // will be set by nsILineBreaker, we don't consider CJK at this point.
505 breakState[offset] =
506 mBreakHere || (mAfterBreakableSpace && !isBreakableSpace) ||
507 mWordBreak == WordBreakRule::BreakAll ||
508 mLineBreak == LineBreakRule::Anywhere
509 ? gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL
510 : gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
512 mBreakHere = false;
513 mAfterBreakableSpace = isBreakableSpace;
515 if (isSpace) {
516 if (offset > wordStart && aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
517 if (mLineBreak == LineBreakRule::Anywhere) {
518 memset(breakState.Elements() + wordStart,
519 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
520 offset - wordStart);
521 } else if (wordMightBeBreakable) {
522 // Save current start-of-word state because ComputeBreakPositions()
523 // will set it to false.
524 AutoRestore<uint8_t> saveWordStartBreakState(breakState[wordStart]);
525 LineBreaker::ComputeBreakPositions(
526 aText + wordStart, offset - wordStart, mWordBreak, mLineBreak,
527 mScriptIsChineseOrJapanese, breakState.Elements() + wordStart);
531 wordMightBeBreakable = false;
532 mWordContinuation = false;
533 ++offset;
534 if (offset >= aLength) {
535 break;
537 wordStart = offset;
538 continue;
541 if (!wordMightBeBreakable &&
542 !IsNonBreakableChar<uint8_t>(ch, mLegacyBehavior)) {
543 wordMightBeBreakable = true;
545 ++offset;
546 if (offset >= aLength) {
547 // Save this word
548 mCurrentWordMightBeBreakable = wordMightBeBreakable;
549 uint32_t len = offset - wordStart;
550 char16_t* elems = mCurrentWord.AppendElements(len, mozilla::fallible);
551 if (!elems) {
552 return NS_ERROR_OUT_OF_MEMORY;
554 uint32_t i;
555 for (i = wordStart; i < offset; ++i) {
556 elems[i - wordStart] = aText[i];
558 mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
559 // Ensure that the break-before for this word is written out
560 offset = wordStart + 1;
561 break;
565 if (!noBreaksNeeded) {
566 aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
568 return NS_OK;
571 void nsLineBreaker::UpdateCurrentWordLanguage(nsAtom* aHyphenationLanguage) {
572 if (mCurrentWordLanguage && mCurrentWordLanguage != aHyphenationLanguage) {
573 mCurrentWordContainsMixedLang = true;
574 mScriptIsChineseOrJapanese = false;
575 return;
578 if (aHyphenationLanguage && !mCurrentWordLanguage) {
579 static mozilla::StaticRefPtr<nsAtom> sLastHyphenationLanguage;
580 static bool sLastScriptIsChineseOrJapanese = false;
581 static bool sInit = false;
583 if (!sInit) {
584 mozilla::ClearOnShutdown(&sLastHyphenationLanguage);
585 sInit = true;
588 if (sLastHyphenationLanguage == aHyphenationLanguage) {
589 MOZ_ASSERT(nsAtomString(sLastHyphenationLanguage)
590 .Equals(nsAtomString(aHyphenationLanguage)));
591 mScriptIsChineseOrJapanese = sLastScriptIsChineseOrJapanese;
592 } else {
593 Locale loc;
594 auto result =
595 LocaleParser::TryParse(nsAtomCString(aHyphenationLanguage), loc);
597 if (result.isErr()) {
598 return;
600 if (loc.Script().Missing() && loc.AddLikelySubtags().isErr()) {
601 return;
603 mScriptIsChineseOrJapanese =
604 loc.Script().EqualTo("Hans") || loc.Script().EqualTo("Hant") ||
605 loc.Script().EqualTo("Jpan") || loc.Script().EqualTo("Hrkt");
607 sLastHyphenationLanguage = aHyphenationLanguage;
608 sLastScriptIsChineseOrJapanese = mScriptIsChineseOrJapanese;
611 mCurrentWordLanguage = aHyphenationLanguage;
614 nsresult nsLineBreaker::AppendInvisibleWhitespace(uint32_t aFlags) {
615 nsresult rv = FlushCurrentWord();
616 if (NS_FAILED(rv)) {
617 return rv;
620 bool isBreakableSpace = !(aFlags & BREAK_SUPPRESS_INSIDE);
621 if (mAfterBreakableSpace && !isBreakableSpace) {
622 mBreakHere = true;
624 mAfterBreakableSpace = isBreakableSpace;
625 mWordContinuation = false;
626 return NS_OK;
629 nsresult nsLineBreaker::Reset(bool* aTrailingBreak) {
630 nsresult rv = FlushCurrentWord();
631 if (NS_FAILED(rv)) {
632 return rv;
635 *aTrailingBreak = mBreakHere || mAfterBreakableSpace;
636 mBreakHere = false;
637 mAfterBreakableSpace = false;
638 return NS_OK;