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
[] = {
36 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0,
38 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
40 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
42 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
44 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
46 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
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
55 return (0x0030 <= aChar
&& aChar
<= 0x0039) ||
56 (0x0041 <= aChar
&& aChar
<= 0x005A) ||
57 (0x0061 <= aChar
&& aChar
<= 0x007A) || (0x000a == aChar
);
59 if (aChar
< 0x20 || aChar
> 0x7f) {
62 return !!kNonBreakableASCII
[aChar
- 0x20];
65 nsLineBreaker::nsLineBreaker()
66 : mCurrentWordLanguage(nullptr),
67 mCurrentWordContainsMixedLang(false),
68 mScriptIsChineseOrJapanese(false),
69 mAfterBreakableSpace(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!");
82 bool nsLineBreaker::ShouldCapitalize(uint32_t aChar
, bool& aCapitalizeNext
) {
83 using mozilla::intl::GeneralCategory
;
84 auto category
= UnicodeProperties::CharType(aChar
);
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;
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?")
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;
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;
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;
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]);
144 nsLineBreaker::ShouldCapitalize(ch
, capitalizeNextChar
);
146 if (!IS_IN_BMP(ch
)) {
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));
171 LineBreaker::ComputeBreakPositions(
172 mCurrentWord
.Elements(), length
, mWordBreak
, mLineBreak
,
173 mScriptIsChineseOrJapanese
, breakState
.Elements());
176 bool autoHyphenate
= mCurrentWordLanguage
&& !mCurrentWordContainsMixedLang
;
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;
185 RefPtr
<nsHyphenator
> hyphenator
=
186 nsHyphenationManager::Instance()->GetHyphenator(mCurrentWordLanguage
);
188 FindHyphenationPoints(hyphenator
, mCurrentWord
.Elements(),
189 mCurrentWord
.Elements() + length
,
190 breakState
.Elements());
194 nsTArray
<bool> capitalizationState
;
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
213 uint32_t skipSet
= i
== 0 ? 1 : 0;
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();
237 mCurrentWordMightBeBreakable
= false;
238 mCurrentWordContainsMixedLang
= false;
239 mCurrentWordLanguage
= nullptr;
240 mWordContinuation
= false;
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...");
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
);
274 mTextItems
.AppendElement(TextItem(aSink
, 0, offset
, aFlags
));
277 if (offset
== aLength
) {
281 // We encountered whitespace, so we're done with this word
282 nsresult rv
= FlushCurrentWord();
288 AutoTArray
<uint8_t, 4000> breakState
;
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).
315 while (offset
> start
) {
317 if (IsSegmentSpace(aText
[offset
])) {
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
) {
329 nsHyphenationManager::Instance()->GetHyphenator(aHyphenationLanguage
);
333 char16_t ch
= aText
[offset
];
334 bool isSpace
= IsSegmentSpace(ch
);
335 bool isBreakableSpace
= isSpace
&& !(aFlags
& BREAK_SUPPRESS_INSIDE
);
337 if (aSink
&& !noBreaksNeeded
) {
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
;
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
,
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
);
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;
376 if (offset
>= aLength
) {
383 if (!wordMightBeBreakable
&&
384 !IsNonBreakableChar
<char16_t
>(ch
, mLegacyBehavior
)) {
385 wordMightBeBreakable
= true;
388 if (offset
>= aLength
) {
390 mCurrentWordMightBeBreakable
= wordMightBeBreakable
;
391 uint32_t len
= offset
- wordStart
;
392 char16_t
* elems
= mCurrentWord
.AppendElements(len
, mozilla::fallible
);
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
);
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
);
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
) {
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
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
);
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;
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.
471 // We encountered whitespace, so we're done with this word
472 nsresult rv
= FlushCurrentWord();
478 AutoTArray
<uint8_t, 4000> breakState
;
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
495 while (offset
> start
) {
497 if (IsSegmentSpace(aText
[offset
])) {
502 uint32_t wordStart
= offset
;
503 bool wordMightBeBreakable
= false;
506 uint8_t ch
= aText
[offset
];
507 bool isSpace
= IsSegmentSpace(ch
);
508 bool isBreakableSpace
= isSpace
&& !(aFlags
& BREAK_SUPPRESS_INSIDE
);
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.
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
;
521 mAfterBreakableSpace
= isBreakableSpace
;
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
,
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;
542 if (offset
>= aLength
) {
549 if (!wordMightBeBreakable
&&
550 !IsNonBreakableChar
<uint8_t>(ch
, mLegacyBehavior
)) {
551 wordMightBeBreakable
= true;
554 if (offset
>= aLength
) {
556 mCurrentWordMightBeBreakable
= wordMightBeBreakable
;
557 uint32_t len
= offset
- wordStart
;
558 char16_t
* elems
= mCurrentWord
.AppendElements(len
, mozilla::fallible
);
560 return NS_ERROR_OUT_OF_MEMORY
;
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;
573 if (!noBreaksNeeded
) {
574 aSink
->SetBreaks(start
, offset
- start
, breakState
.Elements() + start
);
579 void nsLineBreaker::UpdateCurrentWordLanguage(nsAtom
* aHyphenationLanguage
) {
580 if (mCurrentWordLanguage
&& mCurrentWordLanguage
!= aHyphenationLanguage
) {
581 mCurrentWordContainsMixedLang
= true;
582 mScriptIsChineseOrJapanese
= false;
586 if (aHyphenationLanguage
&& !mCurrentWordLanguage
) {
587 static mozilla::StaticRefPtr
<nsAtom
> sLastHyphenationLanguage
;
588 static bool sLastScriptIsChineseOrJapanese
= false;
589 static bool sInit
= false;
592 mozilla::ClearOnShutdown(&sLastHyphenationLanguage
);
596 if (sLastHyphenationLanguage
== aHyphenationLanguage
) {
597 MOZ_ASSERT(nsAtomString(sLastHyphenationLanguage
)
598 .Equals(nsAtomString(aHyphenationLanguage
)));
599 mScriptIsChineseOrJapanese
= sLastScriptIsChineseOrJapanese
;
603 LocaleParser::TryParse(nsAtomCString(aHyphenationLanguage
), loc
);
605 if (result
.isErr()) {
608 if (loc
.Script().Missing() && loc
.AddLikelySubtags().isErr()) {
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();
628 bool isBreakableSpace
= !(aFlags
& BREAK_SUPPRESS_INSIDE
);
629 if (mAfterBreakableSpace
&& !isBreakableSpace
) {
632 mAfterBreakableSpace
= isBreakableSpace
;
633 mWordContinuation
= false;
637 nsresult
nsLineBreaker::Reset(bool* aTrailingBreak
) {
638 nsresult rv
= FlushCurrentWord();
643 *aTrailingBreak
= mBreakHere
|| mAfterBreakableSpace
;
645 mAfterBreakableSpace
= false;