no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / dom / base / nsLineBreaker.cpp
blob7175e9ab1eb524cb19e6c70efb2dc145e2b8a076
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 */
82 bool nsLineBreaker::ShouldCapitalize(uint32_t aChar, bool& aCapitalizeNext) {
83 using mozilla::intl::GeneralCategory;
84 auto category = UnicodeProperties::CharType(aChar);
85 switch (category) {
86 case GeneralCategory::Uppercase_Letter:
87 case GeneralCategory::Lowercase_Letter:
88 case GeneralCategory::Titlecase_Letter:
89 case GeneralCategory::Modifier_Letter:
90 case GeneralCategory::Other_Letter:
91 case GeneralCategory::Decimal_Number:
92 case GeneralCategory::Letter_Number:
93 case GeneralCategory::Other_Number:
94 if (aCapitalizeNext) {
95 aCapitalizeNext = false;
96 return true;
98 break;
99 case GeneralCategory::Space_Separator:
100 case GeneralCategory::Line_Separator:
101 case GeneralCategory::Paragraph_Separator:
102 case GeneralCategory::Dash_Punctuation:
103 case GeneralCategory::Initial_Punctuation:
104 /* These punctuation categories are excluded, for examples like
105 * "what colo[u]r" -> "What Colo[u]r?" (rather than "What Colo[U]R?")
106 * and
107 * "snake_case" -> "Snake_case" (to match word selection behavior)
108 case GeneralCategory::Open_Punctuation:
109 case GeneralCategory::Close_Punctuation:
110 case GeneralCategory::Connector_Punctuation:
112 aCapitalizeNext = true;
113 break;
114 case GeneralCategory::Final_Punctuation:
115 /* Special-case: exclude Unicode single-close-quote/apostrophe,
116 for examples like "Lowe’s" etc. */
117 if (aChar != 0x2019) {
118 aCapitalizeNext = true;
120 break;
121 case GeneralCategory::Other_Punctuation:
122 /* Special-case: exclude ASCII apostrophe, for "Lowe's" etc.,
123 and MIDDLE DOT, for Catalan "l·l". */
124 if (aChar != '\'' && aChar != 0x00B7) {
125 aCapitalizeNext = true;
127 break;
128 default:
129 break;
131 return false;
134 static void SetupCapitalization(const char16_t* aWord, uint32_t aLength,
135 bool* aCapitalization) {
136 // Capitalize the first alphanumeric character after a space or punctuation.
137 bool capitalizeNextChar = true;
138 for (uint32_t i = 0; i < aLength; ++i) {
139 uint32_t ch = aWord[i];
140 if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aWord[i + 1])) {
141 ch = SURROGATE_TO_UCS4(ch, aWord[i + 1]);
143 aCapitalization[i] =
144 nsLineBreaker::ShouldCapitalize(ch, capitalizeNextChar);
146 if (!IS_IN_BMP(ch)) {
147 ++i;
152 nsresult nsLineBreaker::FlushCurrentWord() {
153 uint32_t length = mCurrentWord.Length();
154 AutoTArray<uint8_t, 4000> breakState;
155 if (!breakState.AppendElements(length, mozilla::fallible)) {
156 return NS_ERROR_OUT_OF_MEMORY;
159 if (mLineBreak == LineBreakRule::Anywhere) {
160 memset(breakState.Elements(),
161 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
162 length * sizeof(uint8_t));
163 } else if (!mCurrentWordMightBeBreakable &&
164 mWordBreak != WordBreakRule::BreakAll) {
165 // word-break: normal or keep-all has no break opportunity if the word
166 // is non-breakable. (See the comment of kNonBreakableASCII).
167 memset(breakState.Elements(),
168 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE,
169 length * sizeof(uint8_t));
170 } else {
171 LineBreaker::ComputeBreakPositions(
172 mCurrentWord.Elements(), length, mWordBreak, mLineBreak,
173 mScriptIsChineseOrJapanese, breakState.Elements());
176 bool autoHyphenate = mCurrentWordLanguage && !mCurrentWordContainsMixedLang;
177 uint32_t i;
178 for (i = 0; autoHyphenate && i < mTextItems.Length(); ++i) {
179 TextItem* ti = &mTextItems[i];
180 if (!(ti->mFlags & BREAK_USE_AUTO_HYPHENATION)) {
181 autoHyphenate = false;
184 if (autoHyphenate) {
185 RefPtr<nsHyphenator> hyphenator =
186 nsHyphenationManager::Instance()->GetHyphenator(mCurrentWordLanguage);
187 if (hyphenator) {
188 FindHyphenationPoints(hyphenator, mCurrentWord.Elements(),
189 mCurrentWord.Elements() + length,
190 breakState.Elements());
194 nsTArray<bool> capitalizationState;
195 uint32_t offset = 0;
196 for (i = 0; i < mTextItems.Length(); ++i) {
197 TextItem* ti = &mTextItems[i];
198 NS_ASSERTION(ti->mLength > 0, "Zero length word contribution?");
200 if ((ti->mFlags & BREAK_SUPPRESS_INITIAL) && ti->mSinkOffset == 0) {
201 breakState[offset] = gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
203 if (ti->mFlags & BREAK_SUPPRESS_INSIDE) {
204 uint32_t exclude = ti->mSinkOffset == 0 ? 1 : 0;
205 memset(breakState.Elements() + offset + exclude,
206 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE,
207 (ti->mLength - exclude) * sizeof(uint8_t));
210 // Don't set the break state for the first character of the word, because
211 // it was already set correctly earlier and we don't know what the true
212 // value should be.
213 uint32_t skipSet = i == 0 ? 1 : 0;
214 if (ti->mSink) {
215 ti->mSink->SetBreaks(ti->mSinkOffset + skipSet, ti->mLength - skipSet,
216 breakState.Elements() + offset + skipSet);
218 if (!mWordContinuation && (ti->mFlags & BREAK_NEED_CAPITALIZATION)) {
219 if (capitalizationState.Length() == 0) {
220 if (!capitalizationState.AppendElements(length, mozilla::fallible)) {
221 return NS_ERROR_OUT_OF_MEMORY;
223 memset(capitalizationState.Elements(), false, length * sizeof(bool));
224 SetupCapitalization(mCurrentWord.Elements(), length,
225 capitalizationState.Elements());
227 ti->mSink->SetCapitalization(ti->mSinkOffset, ti->mLength,
228 capitalizationState.Elements() + offset);
232 offset += ti->mLength;
235 mCurrentWord.Clear();
236 mTextItems.Clear();
237 mCurrentWordMightBeBreakable = false;
238 mCurrentWordContainsMixedLang = false;
239 mCurrentWordLanguage = nullptr;
240 mWordContinuation = false;
241 return NS_OK;
244 // If the aFlags parameter to AppendText has all these bits set,
245 // then we don't need to worry about finding break opportunities
246 // in the appended text.
247 #define NO_BREAKS_NEEDED_FLAGS \
248 (BREAK_SUPPRESS_INITIAL | BREAK_SUPPRESS_INSIDE | \
249 BREAK_SKIP_SETTING_NO_BREAKS)
251 nsresult nsLineBreaker::AppendText(nsAtom* aHyphenationLanguage,
252 const char16_t* aText, uint32_t aLength,
253 uint32_t aFlags, nsILineBreakSink* aSink) {
254 NS_ASSERTION(aLength > 0, "Appending empty text...");
256 uint32_t offset = 0;
258 // Continue the current word
259 if (mCurrentWord.Length() > 0) {
260 NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere,
261 "These should not be set");
263 while (offset < aLength && !IsSegmentSpace(aText[offset])) {
264 mCurrentWord.AppendElement(aText[offset]);
265 if (!mCurrentWordMightBeBreakable &&
266 !IsNonBreakableChar<char16_t>(aText[offset], mLegacyBehavior)) {
267 mCurrentWordMightBeBreakable = true;
269 UpdateCurrentWordLanguage(aHyphenationLanguage);
270 ++offset;
273 if (offset > 0) {
274 mTextItems.AppendElement(TextItem(aSink, 0, offset, aFlags));
277 if (offset == aLength) {
278 return NS_OK;
281 // We encountered whitespace, so we're done with this word
282 nsresult rv = FlushCurrentWord();
283 if (NS_FAILED(rv)) {
284 return rv;
288 AutoTArray<uint8_t, 4000> breakState;
289 if (aSink) {
290 if (!breakState.AppendElements(aLength, mozilla::fallible)) {
291 return NS_ERROR_OUT_OF_MEMORY;
295 bool noCapitalizationNeeded = true;
296 nsTArray<bool> capitalizationState;
297 if (aSink && (aFlags & BREAK_NEED_CAPITALIZATION)) {
298 if (!capitalizationState.AppendElements(aLength, mozilla::fallible)) {
299 return NS_ERROR_OUT_OF_MEMORY;
301 memset(capitalizationState.Elements(), false, aLength * sizeof(bool));
302 noCapitalizationNeeded = false;
305 uint32_t start = offset;
306 bool noBreaksNeeded =
307 !aSink || ((aFlags & NO_BREAKS_NEEDED_FLAGS) == NO_BREAKS_NEEDED_FLAGS &&
308 !mBreakHere && !mAfterBreakableSpace);
309 if (noBreaksNeeded && noCapitalizationNeeded) {
310 // Skip to the space before the last word, since either the break data
311 // here is not needed, or no breaks are set in the sink and there cannot
312 // be any breaks in this chunk; and we don't need to do word-initial
313 // capitalization. All we need is the context for the next chunk (if any).
314 offset = aLength;
315 while (offset > start) {
316 --offset;
317 if (IsSegmentSpace(aText[offset])) {
318 break;
322 uint32_t wordStart = offset;
323 bool wordMightBeBreakable = false;
325 RefPtr<nsHyphenator> hyphenator;
326 if ((aFlags & BREAK_USE_AUTO_HYPHENATION) &&
327 !(aFlags & BREAK_SUPPRESS_INSIDE) && aHyphenationLanguage) {
328 hyphenator =
329 nsHyphenationManager::Instance()->GetHyphenator(aHyphenationLanguage);
332 for (;;) {
333 char16_t ch = aText[offset];
334 bool isSpace = IsSegmentSpace(ch);
335 bool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
337 if (aSink && !noBreaksNeeded) {
338 breakState[offset] =
339 mBreakHere || (mAfterBreakableSpace && !isBreakableSpace) ||
340 mWordBreak == WordBreakRule::BreakAll ||
341 mLineBreak == LineBreakRule::Anywhere
342 ? gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL
343 : gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
345 mBreakHere = false;
346 mAfterBreakableSpace = isBreakableSpace;
348 if (isSpace || ch == '\n') {
349 if (offset > wordStart && aSink) {
350 if (!(aFlags & BREAK_SUPPRESS_INSIDE)) {
351 if (mLineBreak == LineBreakRule::Anywhere) {
352 memset(breakState.Elements() + wordStart,
353 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
354 offset - wordStart);
355 } else if (wordMightBeBreakable) {
356 // Save current start-of-word state because ComputeBreakPositions()
357 // will set it to false.
358 AutoRestore<uint8_t> saveWordStartBreakState(breakState[wordStart]);
359 LineBreaker::ComputeBreakPositions(
360 aText + wordStart, offset - wordStart, mWordBreak, mLineBreak,
361 mScriptIsChineseOrJapanese, breakState.Elements() + wordStart);
363 if (hyphenator) {
364 FindHyphenationPoints(hyphenator, aText + wordStart, aText + offset,
365 breakState.Elements() + wordStart);
368 if (!mWordContinuation && !noCapitalizationNeeded) {
369 SetupCapitalization(aText + wordStart, offset - wordStart,
370 capitalizationState.Elements() + wordStart);
373 wordMightBeBreakable = false;
374 mWordContinuation = false;
375 ++offset;
376 if (offset >= aLength) {
377 break;
379 wordStart = offset;
380 continue;
383 if (!wordMightBeBreakable &&
384 !IsNonBreakableChar<char16_t>(ch, mLegacyBehavior)) {
385 wordMightBeBreakable = true;
387 ++offset;
388 if (offset >= aLength) {
389 // Save this word
390 mCurrentWordMightBeBreakable = wordMightBeBreakable;
391 uint32_t len = offset - wordStart;
392 char16_t* elems = mCurrentWord.AppendElements(len, mozilla::fallible);
393 if (!elems) {
394 return NS_ERROR_OUT_OF_MEMORY;
396 memcpy(elems, aText + wordStart, sizeof(char16_t) * len);
397 mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
398 // Ensure that the break-before for this word is written out
399 offset = wordStart + 1;
400 UpdateCurrentWordLanguage(aHyphenationLanguage);
401 break;
405 if (aSink) {
406 if (!noBreaksNeeded) {
407 aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
409 if (!noCapitalizationNeeded) {
410 aSink->SetCapitalization(start, offset - start,
411 capitalizationState.Elements() + start);
414 return NS_OK;
417 void nsLineBreaker::FindHyphenationPoints(nsHyphenator* aHyphenator,
418 const char16_t* aTextStart,
419 const char16_t* aTextLimit,
420 uint8_t* aBreakState) {
421 nsDependentSubstring string(aTextStart, aTextLimit);
422 AutoTArray<bool, 200> hyphens;
423 if (NS_SUCCEEDED(aHyphenator->Hyphenate(string, hyphens))) {
424 for (uint32_t i = 0; i + 1 < string.Length(); ++i) {
425 if (hyphens[i]) {
426 aBreakState[i + 1] =
427 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN;
433 nsresult nsLineBreaker::AppendText(nsAtom* aHyphenationLanguage,
434 const uint8_t* aText, uint32_t aLength,
435 uint32_t aFlags, nsILineBreakSink* aSink) {
436 NS_ASSERTION(aLength > 0, "Appending empty text...");
438 if (aFlags & (BREAK_NEED_CAPITALIZATION | BREAK_USE_AUTO_HYPHENATION)) {
439 // Defer to the Unicode path if capitalization or hyphenation is required
440 nsAutoString str;
441 const char* cp = reinterpret_cast<const char*>(aText);
442 CopyASCIItoUTF16(nsDependentCSubstring(cp, cp + aLength), str);
443 return AppendText(aHyphenationLanguage, str.get(), aLength, aFlags, aSink);
446 uint32_t offset = 0;
448 // Continue the current word
449 if (mCurrentWord.Length() > 0) {
450 NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere,
451 "These should not be set");
453 while (offset < aLength && !IsSegmentSpace(aText[offset])) {
454 mCurrentWord.AppendElement(aText[offset]);
455 if (!mCurrentWordMightBeBreakable &&
456 !IsNonBreakableChar<uint8_t>(aText[offset], mLegacyBehavior)) {
457 mCurrentWordMightBeBreakable = true;
459 ++offset;
462 if (offset > 0) {
463 mTextItems.AppendElement(TextItem(aSink, 0, offset, aFlags));
466 if (offset == aLength) {
467 // We did not encounter whitespace so the word hasn't finished yet.
468 return NS_OK;
471 // We encountered whitespace, so we're done with this word
472 nsresult rv = FlushCurrentWord();
473 if (NS_FAILED(rv)) {
474 return rv;
478 AutoTArray<uint8_t, 4000> breakState;
479 if (aSink) {
480 if (!breakState.AppendElements(aLength, mozilla::fallible)) {
481 return NS_ERROR_OUT_OF_MEMORY;
485 uint32_t start = offset;
486 bool noBreaksNeeded =
487 !aSink || ((aFlags & NO_BREAKS_NEEDED_FLAGS) == NO_BREAKS_NEEDED_FLAGS &&
488 !mBreakHere && !mAfterBreakableSpace);
489 if (noBreaksNeeded) {
490 // Skip to the space before the last word, since either the break data
491 // here is not needed, or no breaks are set in the sink and there cannot
492 // be any breaks in this chunk; all we need is the context for the next
493 // chunk (if any)
494 offset = aLength;
495 while (offset > start) {
496 --offset;
497 if (IsSegmentSpace(aText[offset])) {
498 break;
502 uint32_t wordStart = offset;
503 bool wordMightBeBreakable = false;
505 for (;;) {
506 uint8_t ch = aText[offset];
507 bool isSpace = IsSegmentSpace(ch);
508 bool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
510 if (aSink) {
511 // Consider word-break style. Since the break position of CJK scripts
512 // will be set by nsILineBreaker, we don't consider CJK at this point.
513 breakState[offset] =
514 mBreakHere || (mAfterBreakableSpace && !isBreakableSpace) ||
515 mWordBreak == WordBreakRule::BreakAll ||
516 mLineBreak == LineBreakRule::Anywhere
517 ? gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL
518 : gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NONE;
520 mBreakHere = false;
521 mAfterBreakableSpace = isBreakableSpace;
523 if (isSpace) {
524 if (offset > wordStart && aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
525 if (mLineBreak == LineBreakRule::Anywhere) {
526 memset(breakState.Elements() + wordStart,
527 gfxTextRun::CompressedGlyph::FLAG_BREAK_TYPE_NORMAL,
528 offset - wordStart);
529 } else if (wordMightBeBreakable) {
530 // Save current start-of-word state because ComputeBreakPositions()
531 // will set it to false.
532 AutoRestore<uint8_t> saveWordStartBreakState(breakState[wordStart]);
533 LineBreaker::ComputeBreakPositions(
534 aText + wordStart, offset - wordStart, mWordBreak, mLineBreak,
535 mScriptIsChineseOrJapanese, breakState.Elements() + wordStart);
539 wordMightBeBreakable = false;
540 mWordContinuation = false;
541 ++offset;
542 if (offset >= aLength) {
543 break;
545 wordStart = offset;
546 continue;
549 if (!wordMightBeBreakable &&
550 !IsNonBreakableChar<uint8_t>(ch, mLegacyBehavior)) {
551 wordMightBeBreakable = true;
553 ++offset;
554 if (offset >= aLength) {
555 // Save this word
556 mCurrentWordMightBeBreakable = wordMightBeBreakable;
557 uint32_t len = offset - wordStart;
558 char16_t* elems = mCurrentWord.AppendElements(len, mozilla::fallible);
559 if (!elems) {
560 return NS_ERROR_OUT_OF_MEMORY;
562 uint32_t i;
563 for (i = wordStart; i < offset; ++i) {
564 elems[i - wordStart] = aText[i];
566 mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
567 // Ensure that the break-before for this word is written out
568 offset = wordStart + 1;
569 break;
573 if (!noBreaksNeeded) {
574 aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
576 return NS_OK;
579 void nsLineBreaker::UpdateCurrentWordLanguage(nsAtom* aHyphenationLanguage) {
580 if (mCurrentWordLanguage && mCurrentWordLanguage != aHyphenationLanguage) {
581 mCurrentWordContainsMixedLang = true;
582 mScriptIsChineseOrJapanese = false;
583 return;
586 if (aHyphenationLanguage && !mCurrentWordLanguage) {
587 static mozilla::StaticRefPtr<nsAtom> sLastHyphenationLanguage;
588 static bool sLastScriptIsChineseOrJapanese = false;
589 static bool sInit = false;
591 if (!sInit) {
592 mozilla::ClearOnShutdown(&sLastHyphenationLanguage);
593 sInit = true;
596 if (sLastHyphenationLanguage == aHyphenationLanguage) {
597 MOZ_ASSERT(nsAtomString(sLastHyphenationLanguage)
598 .Equals(nsAtomString(aHyphenationLanguage)));
599 mScriptIsChineseOrJapanese = sLastScriptIsChineseOrJapanese;
600 } else {
601 Locale loc;
602 auto result =
603 LocaleParser::TryParse(nsAtomCString(aHyphenationLanguage), loc);
605 if (result.isErr()) {
606 return;
608 if (loc.Script().Missing() && loc.AddLikelySubtags().isErr()) {
609 return;
611 mScriptIsChineseOrJapanese =
612 loc.Script().EqualTo("Hans") || loc.Script().EqualTo("Hant") ||
613 loc.Script().EqualTo("Jpan") || loc.Script().EqualTo("Hrkt");
615 sLastHyphenationLanguage = aHyphenationLanguage;
616 sLastScriptIsChineseOrJapanese = mScriptIsChineseOrJapanese;
619 mCurrentWordLanguage = aHyphenationLanguage;
622 nsresult nsLineBreaker::AppendInvisibleWhitespace(uint32_t aFlags) {
623 nsresult rv = FlushCurrentWord();
624 if (NS_FAILED(rv)) {
625 return rv;
628 bool isBreakableSpace = !(aFlags & BREAK_SUPPRESS_INSIDE);
629 if (mAfterBreakableSpace && !isBreakableSpace) {
630 mBreakHere = true;
632 mAfterBreakableSpace = isBreakableSpace;
633 mWordContinuation = false;
634 return NS_OK;
637 nsresult nsLineBreaker::Reset(bool* aTrailingBreak) {
638 nsresult rv = FlushCurrentWord();
639 if (NS_FAILED(rv)) {
640 return rv;
643 *aTrailingBreak = mBreakHere || mAfterBreakableSpace;
644 mBreakHere = false;
645 mAfterBreakableSpace = false;
646 return NS_OK;