Source/WebCore:
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Nov 2014 22:33:17 +0000 (22:33 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Nov 2014 22:33:17 +0000 (22:33 +0000)
Add support to -webkit-line-break property for CSS3 Text line-break property values and semantics.
https://bugs.webkit.org/show_bug.cgi?id=89235

Reviewed by Eric Seidel and Dave Hyatt.

This patch adds semantic support for the CSS3 line-break property (qua -webkit-line-break),
and enables testing on (apple) mac ports. Follow on patches will enable these tests on
other ports as they are incrementally verified.

See also wiki documentation at:
[1] http://trac.webkit.org/wiki/LineBreaking
[2] http://trac.webkit.org/wiki/LineBreakingCSS3Mapping

Tests: css3/line-break/line-break-auto-centered.html
       css3/line-break/line-break-auto-half-kana.html
       css3/line-break/line-break-auto-hyphens.html
       css3/line-break/line-break-auto-inseparables.html
       css3/line-break/line-break-auto-iteration-marks.html
       css3/line-break/line-break-auto-postfixes.html
       css3/line-break/line-break-auto-prefixes.html
       css3/line-break/line-break-auto-sound-marks.html
       css3/line-break/line-break-loose-centered.html
       css3/line-break/line-break-loose-half-kana.html
       css3/line-break/line-break-loose-hyphens.html
       css3/line-break/line-break-loose-inseparables.html
       css3/line-break/line-break-loose-iteration-marks.html
       css3/line-break/line-break-loose-postfixes.html
       css3/line-break/line-break-loose-prefixes.html
       css3/line-break/line-break-loose-sound-marks.html
       css3/line-break/line-break-normal-centered.html
       css3/line-break/line-break-normal-half-kana.html
       css3/line-break/line-break-normal-hyphens.html
       css3/line-break/line-break-normal-inseparables.html
       css3/line-break/line-break-normal-iteration-marks.html
       css3/line-break/line-break-normal-postfixes.html
       css3/line-break/line-break-normal-prefixes.html
       css3/line-break/line-break-normal-sound-marks.html
       css3/line-break/line-break-strict-centered.html
       css3/line-break/line-break-strict-half-kana.html
       css3/line-break/line-break-strict-hyphens.html
       css3/line-break/line-break-strict-inseparables.html
       css3/line-break/line-break-strict-iteration-marks.html
       css3/line-break/line-break-strict-postfixes.html
       css3/line-break/line-break-strict-prefixes.html
       css3/line-break/line-break-strict-sound-marks.html

These tests were previously added in http://trac.webkit.org/changeset/143378, but skipped
in generic TestExpectations. In this patch, they are marked as Pass for the (apple) mac ports.

* platform/text/LineBreakIteratorPoolICU.h:
(WebCore::LineBreakIteratorPool::makeLocaleWithBreakKeyword):
Add static function to construct ICU locale argument (also used as pool key) with additional
break keyword.
(LineBreakIteratorPool):
(WebCore::LineBreakIteratorPool::take):
(WebCore::LineBreakIteratorPool::put):
Remove direct dependency from ICU library (and types), moving that dependency into
new {open,close}LineBreakIterator() functions (defined in TextBreakIteratorICU.cpp).
Update to take line break mode into account.
Create (and cache) different break iterators depending on line break mode (in addition to locale),
which entails expanding pool entry key format to optionally append "@break=" +
"loose"|"normal"|"strict" keyword to locale string.

* platform/text/TextBreakIterator.h:
(WebCore::LazyLineBreakIterator::LazyLineBreakIterator):
(WebCore::LazyLineBreakIterator::isLooseCJKMode):
(WebCore::LazyLineBreakIterator::get):
(WebCore::LazyLineBreakIterator::reset):
(LazyLineBreakIterator):
Define LineBreakIteratorMode enumeration for use in TextBreakIterator et al.
Add state member to indicate line break mode.

* platform/text/TextBreakIteratorICU.cpp:
(WebCore::acquireLineBreakIterator):
Use new line break mode when making iterator from pool.
Handle change of return type of LineBreakIteratorPool::take() to non-ICU type,
i.e., TextBreakIterator* instead of ICU's UBreakIterator*.
(WebCore::releaseLineBreakIterator):
Handle change of parameter type of LineBreakIteratorPool::put() to non-ICU type,
i.e., TextBreakIterator* instead of ICU's UBreakIterator*.
(WebCore):
(WebCore::isCJKLocale):
New functions for determining if CJK rules apply.
(WebCore::openLineBreakIterator):
New function for abstracting opening of ICU style line break iterator. This is now
used in LineBreakIteratorPoolICU.h rather than having direct ICU API dependency there.
This function also takes into account the line break mode.

Note that this function only calls ubrk_openRules() when the author has opted-in via
using the -webkit-line-break CSS property. Eventually, we would like to be able to
customize the rules that ICU's line breaking algorithm uses (especially for CJK text);
however, ubrk_openRules() currently parses its input string to create a DFA and is
therefore very slow. In fact, it's so slow that increasing our cache size in
LineBreakIteratorPool doesn't actually help enough. Also note that the default value
for the line-break CSS property is 'auto'.
(WebCore::closeLineBreakIterator):
(WebCore::mapLineIteratorModeToRules):
New function for abstracting closing of ICU style line break iterator. This is now
used in LineBreakIteratorPoolICU.h rather than having direct ICU API dependency there.

* rendering/RenderBlockLineLayout.cpp:
(WebCore::RenderBlock::LineBreaker::nextSegmentBreak):
Pass line break iterator mode flag when reseting LazyLineBreakIterator.
Add looseMode local variable to prevent need for computing under isBreakable().

* rendering/RenderText.cpp:
(WebCore::mapLineBreakToIteratorMode):
Add implementation for mapLineBreakToIteratorMode(), used by both RenderText::computePreferredLogicalWidths
and RenderBlock::LineBreaker::nextLineBreak.
(WebCore):
(WebCore::RenderText::computePreferredLogicalWidths):
Ensure (lazy line) breakIterator is initialized for line break mode.
Ensure isBreakable() is passed loose mode flag to match behavior in RenderBlock::LineBreaker::nextLineBreak.

* rendering/RenderText.h:
(WebCore):
Add declaration for mapLineBreakToIteratorMode(), used by both RenderText::computePreferredLogicalWidths
and RenderBlock::LineBreaker::nextLineBreak.

* rendering/break_lines.cpp:
(WebCore):
Introduce (local) enum NBSPBehavior for expanding template on nextBreakablePosition.
(WebCore::isBreakableSpace):
Add externally specified loose mode parameter to prevent need to invoke line break iterator
accessor method on each invocation. Use new loose mode flavors off NBP functions.
(WebCore::needsLineBreakIterator):
Use enum NBSP behavior template parameter rather than boolean.
(WebCore::nextBreakablePositionNonLoosely):
Extend name to distinguish from loose flavor of this function.
(WebCore::nextBreakablePositionLoosely):
Add loose flavor of NBP invoked only when loose mode applies, in which case ASCII shortcut
table cannot be used.
(WebCore::nextBreakablePosition):
(WebCore::nextBreakablePositionIgnoringNBSP):
Use (renamed) non-loose flavor of NBP.
(WebCore::nextBreakablePositionLoose):
(WebCore::nextBreakablePositionIgnoringNBSPLoose):
Introduce loose flavor of NBP template expansions.

* rendering/break_lines.h:
(WebCore):
(WebCore::isBreakable):
Add externally specified loose mode parameter to prevent need to invoke line break iterator
accessor method on each invocation.

LayoutTests:
CSS3: line-break property support
https://bugs.webkit.org/show_bug.cgi?id=89235

Reviewed by Eric Seidel and Dave Hyatt.

* platform/mac/TestExpectations: Mark css3/line-break tests as passing.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@176473 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
LayoutTests/ChangeLog
LayoutTests/platform/mac/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/WebCore.exp.in
Source/WebCore/platform/text/LineBreakIteratorPoolICU.h
Source/WebCore/platform/text/TextBreakIterator.cpp
Source/WebCore/platform/text/TextBreakIterator.h
Source/WebCore/rendering/RenderText.cpp
Source/WebCore/rendering/RenderText.h
Source/WebCore/rendering/SimpleLineLayout.cpp
Source/WebCore/rendering/SimpleLineLayoutFlowContents.cpp
Source/WebCore/rendering/break_lines.h
Source/WebCore/rendering/line/BreakingContextInlineHeaders.h

index 610f0e4..f4f1275 100644 (file)
@@ -1,3 +1,12 @@
+2014-11-21  Glenn Adams  <glenn@skynav.com> and Myles C. Maxfield  <mmaxfield@apple.com>
+
+        CSS3: line-break property support
+        https://bugs.webkit.org/show_bug.cgi?id=89235
+
+        Reviewed by Eric Seidel and Dave Hyatt.
+
+        * platform/mac/TestExpectations: Mark css3/line-break tests as passing.
+
 2014-11-21  Zalan Bujtas  <zalan@apple.com>
 
         REGRESSION(r175259) Simple line layout text measuring behavior changed.
index 1fb1d39..5c105d3 100644 (file)
@@ -1429,3 +1429,8 @@ webkit.org/b/138075 [ Yosemite ] transforms/2d/hindi-rotated.html [ Pass Failure
 webkit.org/b/138075 [ Yosemite ] fast/text/international/hindi-spacing.html [ Pass Failure ]
 
 webkit.org/b/82980 http/tests/navigation/back-twice-without-commit.html [ Timeout ]
+
+# Verified passing, so override generic skip
+webkit.org/b/89235 css3/line-break [ Pass ]
+webkit.org/b/138115 css3/line-break/line-break-auto-hyphens.html [ ImageOnlyFailure ]
+webkit.org/b/138115 css3/line-break/line-break-auto-sound-marks.html [ ImageOnlyFailure ]
index ab5babb..d6b5d78 100644 (file)
@@ -1,3 +1,150 @@
+2014-11-21  Glenn Adams  <glenn@skynav.com> and Myles C. Maxfield  <mmaxfield@apple.com>
+
+        Add support to -webkit-line-break property for CSS3 Text line-break property values and semantics.
+        https://bugs.webkit.org/show_bug.cgi?id=89235
+
+        Reviewed by Eric Seidel and Dave Hyatt.
+
+        This patch adds semantic support for the CSS3 line-break property (qua -webkit-line-break),
+        and enables testing on (apple) mac ports. Follow on patches will enable these tests on
+        other ports as they are incrementally verified.
+
+        See also wiki documentation at:
+        [1] http://trac.webkit.org/wiki/LineBreaking
+        [2] http://trac.webkit.org/wiki/LineBreakingCSS3Mapping
+
+        Tests: css3/line-break/line-break-auto-centered.html
+               css3/line-break/line-break-auto-half-kana.html
+               css3/line-break/line-break-auto-hyphens.html
+               css3/line-break/line-break-auto-inseparables.html
+               css3/line-break/line-break-auto-iteration-marks.html
+               css3/line-break/line-break-auto-postfixes.html
+               css3/line-break/line-break-auto-prefixes.html
+               css3/line-break/line-break-auto-sound-marks.html
+               css3/line-break/line-break-loose-centered.html
+               css3/line-break/line-break-loose-half-kana.html
+               css3/line-break/line-break-loose-hyphens.html
+               css3/line-break/line-break-loose-inseparables.html
+               css3/line-break/line-break-loose-iteration-marks.html
+               css3/line-break/line-break-loose-postfixes.html
+               css3/line-break/line-break-loose-prefixes.html
+               css3/line-break/line-break-loose-sound-marks.html
+               css3/line-break/line-break-normal-centered.html
+               css3/line-break/line-break-normal-half-kana.html
+               css3/line-break/line-break-normal-hyphens.html
+               css3/line-break/line-break-normal-inseparables.html
+               css3/line-break/line-break-normal-iteration-marks.html
+               css3/line-break/line-break-normal-postfixes.html
+               css3/line-break/line-break-normal-prefixes.html
+               css3/line-break/line-break-normal-sound-marks.html
+               css3/line-break/line-break-strict-centered.html
+               css3/line-break/line-break-strict-half-kana.html
+               css3/line-break/line-break-strict-hyphens.html
+               css3/line-break/line-break-strict-inseparables.html
+               css3/line-break/line-break-strict-iteration-marks.html
+               css3/line-break/line-break-strict-postfixes.html
+               css3/line-break/line-break-strict-prefixes.html
+               css3/line-break/line-break-strict-sound-marks.html
+
+        These tests were previously added in http://trac.webkit.org/changeset/143378, but skipped
+        in generic TestExpectations. In this patch, they are marked as Pass for the (apple) mac ports.
+
+        * platform/text/LineBreakIteratorPoolICU.h:
+        (WebCore::LineBreakIteratorPool::makeLocaleWithBreakKeyword):
+        Add static function to construct ICU locale argument (also used as pool key) with additional
+        break keyword.
+        (LineBreakIteratorPool):
+        (WebCore::LineBreakIteratorPool::take):
+        (WebCore::LineBreakIteratorPool::put):
+        Remove direct dependency from ICU library (and types), moving that dependency into
+        new {open,close}LineBreakIterator() functions (defined in TextBreakIteratorICU.cpp).
+        Update to take line break mode into account.
+        Create (and cache) different break iterators depending on line break mode (in addition to locale),
+        which entails expanding pool entry key format to optionally append "@break=" +
+        "loose"|"normal"|"strict" keyword to locale string.
+
+        * platform/text/TextBreakIterator.h:
+        (WebCore::LazyLineBreakIterator::LazyLineBreakIterator):
+        (WebCore::LazyLineBreakIterator::isLooseCJKMode):
+        (WebCore::LazyLineBreakIterator::get):
+        (WebCore::LazyLineBreakIterator::reset):
+        (LazyLineBreakIterator):
+        Define LineBreakIteratorMode enumeration for use in TextBreakIterator et al.
+        Add state member to indicate line break mode.
+
+        * platform/text/TextBreakIteratorICU.cpp:
+        (WebCore::acquireLineBreakIterator):
+        Use new line break mode when making iterator from pool.
+        Handle change of return type of LineBreakIteratorPool::take() to non-ICU type,
+        i.e., TextBreakIterator* instead of ICU's UBreakIterator*.
+        (WebCore::releaseLineBreakIterator):
+        Handle change of parameter type of LineBreakIteratorPool::put() to non-ICU type,
+        i.e., TextBreakIterator* instead of ICU's UBreakIterator*.
+        (WebCore):
+        (WebCore::isCJKLocale):
+        New functions for determining if CJK rules apply.
+        (WebCore::openLineBreakIterator):
+        New function for abstracting opening of ICU style line break iterator. This is now
+        used in LineBreakIteratorPoolICU.h rather than having direct ICU API dependency there.
+        This function also takes into account the line break mode.
+
+        Note that this function only calls ubrk_openRules() when the author has opted-in via
+        using the -webkit-line-break CSS property. Eventually, we would like to be able to
+        customize the rules that ICU's line breaking algorithm uses (especially for CJK text);
+        however, ubrk_openRules() currently parses its input string to create a DFA and is
+        therefore very slow. In fact, it's so slow that increasing our cache size in
+        LineBreakIteratorPool doesn't actually help enough. Also note that the default value
+        for the line-break CSS property is 'auto'.
+        (WebCore::closeLineBreakIterator):
+        (WebCore::mapLineIteratorModeToRules):
+        New function for abstracting closing of ICU style line break iterator. This is now
+        used in LineBreakIteratorPoolICU.h rather than having direct ICU API dependency there.
+
+        * rendering/RenderBlockLineLayout.cpp:
+        (WebCore::RenderBlock::LineBreaker::nextSegmentBreak):
+        Pass line break iterator mode flag when reseting LazyLineBreakIterator.
+        Add looseMode local variable to prevent need for computing under isBreakable().
+
+        * rendering/RenderText.cpp:
+        (WebCore::mapLineBreakToIteratorMode):
+        Add implementation for mapLineBreakToIteratorMode(), used by both RenderText::computePreferredLogicalWidths
+        and RenderBlock::LineBreaker::nextLineBreak.
+        (WebCore):
+        (WebCore::RenderText::computePreferredLogicalWidths):
+        Ensure (lazy line) breakIterator is initialized for line break mode.
+        Ensure isBreakable() is passed loose mode flag to match behavior in RenderBlock::LineBreaker::nextLineBreak.
+
+        * rendering/RenderText.h:
+        (WebCore):
+        Add declaration for mapLineBreakToIteratorMode(), used by both RenderText::computePreferredLogicalWidths
+        and RenderBlock::LineBreaker::nextLineBreak.
+
+        * rendering/break_lines.cpp:
+        (WebCore):
+        Introduce (local) enum NBSPBehavior for expanding template on nextBreakablePosition.
+        (WebCore::isBreakableSpace):
+        Add externally specified loose mode parameter to prevent need to invoke line break iterator
+        accessor method on each invocation. Use new loose mode flavors off NBP functions.
+        (WebCore::needsLineBreakIterator):
+        Use enum NBSP behavior template parameter rather than boolean.
+        (WebCore::nextBreakablePositionNonLoosely):
+        Extend name to distinguish from loose flavor of this function.
+        (WebCore::nextBreakablePositionLoosely):
+        Add loose flavor of NBP invoked only when loose mode applies, in which case ASCII shortcut
+        table cannot be used.
+        (WebCore::nextBreakablePosition):
+        (WebCore::nextBreakablePositionIgnoringNBSP):
+        Use (renamed) non-loose flavor of NBP.
+        (WebCore::nextBreakablePositionLoose):
+        (WebCore::nextBreakablePositionIgnoringNBSPLoose):
+        Introduce loose flavor of NBP template expansions.
+
+        * rendering/break_lines.h:
+        (WebCore):
+        (WebCore::isBreakable):
+        Add externally specified loose mode parameter to prevent need to invoke line break iterator
+        accessor method on each invocation.
+
 2014-11-21  Anders Carlsson  <andersca@apple.com>
 
         More build fixes.
index 4ef9637..dc13068 100644 (file)
@@ -2593,6 +2593,7 @@ __ZN7WebCore11Geolocation29resetAllGeolocationPermissionEv
 __ZN7WebCore11MathMLNames4initEv
 __ZN7WebCore11MemoryCache18pruneDeadResourcesEv
 __ZN7WebCore11MemoryCache18pruneLiveResourcesEb
+__ZN7WebCore11isCJKLocaleERKN3WTF12AtomicStringE
 __ZN7WebCore11isEndOfLineERKNS_15VisiblePositionE
 __ZN7WebCore11prefetchDNSERKN3WTF6StringE
 __ZN7WebCore12AudioSession11setCategoryENS0_12CategoryTypeE
@@ -2705,7 +2706,7 @@ __ZN7WebCore24DocumentMarkerController14markersInRangeEPNS_5RangeENS_14DocumentM
 __ZN7WebCore24FloatingPointEnvironment21enableDenormalSupportEv
 __ZN7WebCore24FloatingPointEnvironment25saveMainThreadEnvironmentEv
 __ZN7WebCore24FloatingPointEnvironment6sharedEv
-__ZN7WebCore24acquireLineBreakIteratorEN3WTF10StringViewERKNS0_12AtomicStringEPKtj
+__ZN7WebCore24acquireLineBreakIteratorEN3WTF10StringViewERKNS0_12AtomicStringEPKtjNS_21LineBreakIteratorModeEb
 __ZN7WebCore24charactersAroundPositionERKNS_15VisiblePositionERiS3_S3_
 __ZN7WebCore24createTemporaryDirectoryEP8NSString
 __ZN7WebCore24distanceBetweenPositionsERKNS_15VisiblePositionES2_
index d2eb26d..674a06d 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef LineBreakIteratorPoolICU_h
 #define LineBreakIteratorPoolICU_h
 
+#include "TextBreakIterator.h"
 #include "TextBreakIteratorInternalICU.h"
 #include <unicode/ubrk.h>
 #include <wtf/Assertions.h>
@@ -34,6 +35,7 @@
 #include <wtf/ThreadSpecific.h>
 #include <wtf/text/AtomicString.h>
 #include <wtf/text/CString.h>
+#include <wtf/text/StringBuilder.h>
 
 namespace WebCore {
 
@@ -48,11 +50,39 @@ public:
 
     static PassOwnPtr<LineBreakIteratorPool> create() { return adoptPtr(new LineBreakIteratorPool); }
 
-    UBreakIterator* take(const AtomicString& locale)
+    static String makeLocaleWithBreakKeyword(const AtomicString& locale, LineBreakIteratorMode mode)
     {
-        UBreakIterator* iterator = 0;
+        StringBuilder localeWithKeyword;
+        localeWithKeyword.append(locale);
+        localeWithKeyword.appendLiteral("@break=");
+        switch (mode) {
+        case LineBreakIteratorModeUAX14:
+            ASSERT_NOT_REACHED();
+            break;
+        case LineBreakIteratorModeUAX14Loose:
+            localeWithKeyword.appendLiteral("loose");
+            break;
+        case LineBreakIteratorModeUAX14Normal:
+            localeWithKeyword.appendLiteral("normal");
+            break;
+        case LineBreakIteratorModeUAX14Strict:
+            localeWithKeyword.appendLiteral("strict");
+            break;
+        }
+        return localeWithKeyword.toString();
+    }
+
+    TextBreakIterator* take(const AtomicString& locale, LineBreakIteratorMode mode, bool isCJK)
+    {
+        AtomicString localeWithOptionalBreakKeyword;
+        if (mode == LineBreakIteratorModeUAX14)
+            localeWithOptionalBreakKeyword = locale;
+        else
+            localeWithOptionalBreakKeyword = makeLocaleWithBreakKeyword(locale, mode);
+
+        TextBreakIterator* iterator = 0;
         for (size_t i = 0; i < m_pool.size(); ++i) {
-            if (m_pool[i].first == locale) {
+            if (m_pool[i].first == localeWithOptionalBreakKeyword) {
                 iterator = m_pool[i].second;
                 m_pool.remove(i);
                 break;
@@ -60,33 +90,22 @@ public:
         }
 
         if (!iterator) {
-            UErrorCode openStatus = U_ZERO_ERROR;
-            bool localeIsEmpty = locale.isEmpty();
-            iterator = ubrk_open(UBRK_LINE, localeIsEmpty ? currentTextBreakLocaleID() : locale.string().utf8().data(), 0, 0, &openStatus);
-            // locale comes from a web page and it can be invalid, leading ICU
-            // to fail, in which case we fall back to the default locale.
-            if (!localeIsEmpty && U_FAILURE(openStatus)) {
-                openStatus = U_ZERO_ERROR;
-                iterator = ubrk_open(UBRK_LINE, currentTextBreakLocaleID(), 0, 0, &openStatus);
-            }
-                
-            if (U_FAILURE(openStatus)) {
-                LOG_ERROR("ubrk_open failed with status %d", openStatus);
+            iterator = openLineBreakIterator(localeWithOptionalBreakKeyword, mode, isCJK);
+            if (!iterator)
                 return 0;
-            }
         }
 
         ASSERT(!m_vendedIterators.contains(iterator));
-        m_vendedIterators.set(iterator, locale);
+        m_vendedIterators.set(iterator, localeWithOptionalBreakKeyword);
         return iterator;
     }
 
-    void put(UBreakIterator* iterator)
+    void put(TextBreakIterator* iterator)
     {
         ASSERT_ARG(iterator, m_vendedIterators.contains(iterator));
 
         if (m_pool.size() == capacity) {
-            ubrk_close(m_pool[0].second);
+            closeLineBreakIterator(m_pool[0].second);
             m_pool.remove(0);
         }
 
@@ -98,10 +117,10 @@ private:
 
     static const size_t capacity = 4;
 
-    typedef std::pair<AtomicString, UBreakIterator*> Entry;
+    typedef std::pair<AtomicString, TextBreakIterator*> Entry;
     typedef Vector<Entry, capacity> Pool;
     Pool m_pool;
-    HashMap<UBreakIterator*, AtomicString> m_vendedIterators;
+    HashMap<TextBreakIterator*, AtomicString> m_vendedIterators;
 
     friend WTF::ThreadSpecific<LineBreakIteratorPool>::operator LineBreakIteratorPool*();
 };
index 37af0d8..f1480e3 100644 (file)
@@ -256,9 +256,9 @@ TextBreakIterator* cursorMovementIterator(StringView string)
     return setTextForIterator(*staticCursorMovementIterator, string);
 }
 
-TextBreakIterator* acquireLineBreakIterator(StringView string, const AtomicString& locale, const UChar* priorContext, unsigned priorContextLength)
+TextBreakIterator* acquireLineBreakIterator(StringView string, const AtomicString& locale, const UChar* priorContext, unsigned priorContextLength, LineBreakIteratorMode mode, bool isCJK)
 {
-    TextBreakIterator* iterator = reinterpret_cast<TextBreakIterator*>(LineBreakIteratorPool::sharedPool().take(locale));
+    TextBreakIterator* iterator = LineBreakIteratorPool::sharedPool().take(locale, mode, isCJK);
     if (!iterator)
         return nullptr;
 
@@ -269,7 +269,473 @@ void releaseLineBreakIterator(TextBreakIterator* iterator)
 {
     ASSERT_ARG(iterator, iterator);
 
-    LineBreakIteratorPool::sharedPool().put(reinterpret_cast<UBreakIterator*>(iterator));
+    LineBreakIteratorPool::sharedPool().put(iterator);
+}
+
+static const char* uax14Prologue =
+    "!!chain;"
+    "!!LBCMNoChain;"
+    "!!lookAheadHardBreak;";
+
+static const char* uax14AssignmentsBefore =
+    // explicitly enumerate $CJ since ICU versions prior to 49 don't support :LineBreak=Conditional_Japanese_Starter:
+    "$CJ = ["
+#if (U_ICU_VERSION_MAJOR_NUM >= 4) && (U_ICU_VERSION_MINOR_NUM >= 9)
+    ":LineBreak=Conditional_Japanese_Starter:"
+#else
+    "\\u3041\\u3043\\u3045\\u3047\\u3049\\u3063\\u3083\\u3085\\u3087\\u308E\\u3095\\u3096\\u30A1\\u30A3\\u30A5\\u30A7"
+    "\\u30A9\\u30C3\\u30E3\\u30E5\\u30E7\\u30EE\\u30F5\\u30F6\\u30FC"
+    "\\u31F0\\u31F1\\u31F2\\u31F3\\u31F4\\u31F5\\u31F6\\u31F7\\u31F8\\u31F9\\u31FA\\u31FB\\u31FC\\u31FD\\u31FE\\u31FF"
+    "\\uFF67\\uFF68\\uFF69\\uFF6A\\uFF6B\\uFF6C\\uFF6D\\uFF6E\\uFF6F\\uFF70"
+#endif
+    "];";
+
+static const char* uax14AssignmentsCustomLooseCJK =
+    "$BA_SUB = [\\u2010\\u2013];"
+    "$EX_SUB = [\\u0021\\u003F\\uFF01\\uFF1F];"
+    "$ID_SUB = '';"
+    "$IN_SUB = [\\u2025\\u2026];"
+    "$IS_SUB = [\\u003A\\u003B];"
+    "$NS_SUB = [\\u203C\\u2047\\u2048\\u2049\\u3005\\u301C\\u303B\\u309D\\u309E\\u30A0\\u30FB\\u30FD\\u30FE\\uFF1A\\uFF1B\\uFF65];"
+    "$PO_SUB = [\\u0025\\u00A2\\u00B0\\u2030\\u2032\\u2033\\u2103\\uFF05\\uFFE0];"
+    "$PR_SUB = [\\u0024\\u00A3\\u00A5\\u20AC\\u2116\\uFF04\\uFFE1\\uFFE5];"
+    "$ID_ADD = [$CJ $BA_SUB $EX_SUB $IN_SUB $IS_SUB $NS_SUB $PO_SUB $PR_SUB];"
+    "$NS_ADD = '';";
+
+static const char* uax14AssignmentsCustomLooseNonCJK =
+    "$BA_SUB = '';"
+    "$EX_SUB = '';"
+    "$ID_SUB = '';"
+    "$IN_SUB = [\\u2025\\u2026];"
+    "$IS_SUB = '';"
+    "$NS_SUB = [\\u3005\\u303B\\u309D\\u309E\\u30FD\\u30FE];"
+    "$PO_SUB = '';"
+    "$PR_SUB = '';"
+    "$ID_ADD = [$CJ $IN_SUB $NS_SUB];"
+    "$NS_ADD = '';";
+
+static const char* uax14AssignmentsCustomNormalCJK =
+    "$BA_SUB = [\\u2010\\u2013];"
+    "$EX_SUB = '';"
+    "$IN_SUB = '';"
+    "$ID_SUB = '';"
+    "$IS_SUB = '';"
+    "$NS_SUB = [\\u301C\\u30A0];"
+    "$PO_SUB = '';"
+    "$PR_SUB = '';"
+    "$ID_ADD = [$CJ $BA_SUB $NS_SUB];"
+    "$NS_ADD = '';";
+
+static const char* uax14AssignmentsCustomNormalNonCJK =
+    "$BA_SUB = '';"
+    "$EX_SUB = '';"
+    "$ID_SUB = '';"
+    "$IN_SUB = '';"
+    "$IS_SUB = '';"
+    "$NS_SUB = '';"
+    "$PO_SUB = '';"
+    "$PR_SUB = '';"
+    "$ID_ADD = [$CJ];"
+    "$NS_ADD = '';";
+
+static const char* uax14AssignmentsCustomStrictCJK =
+    "$BA_SUB = '';"
+    "$EX_SUB = '';"
+    "$ID_SUB = '';"
+    "$IN_SUB = '';"
+    "$IS_SUB = '';"
+    "$NS_SUB = '';"
+    "$PO_SUB = '';"
+    "$PR_SUB = '';"
+    "$ID_ADD = '';"
+    "$NS_ADD = [$CJ];";
+
+#define uax14AssignmentsCustomStrictNonCJK      uax14AssignmentsCustomStrictCJK
+#define uax14AssignmentsCustomDefaultCJK        uax14AssignmentsCustomNormalCJK
+#define uax14AssignmentsCustomDefaultNonCJK     uax14AssignmentsCustomStrictNonCJK
+
+static const char* uax14AssignmentsAfter =
+    "$AI = [:LineBreak = Ambiguous:];"
+    "$AL = [:LineBreak = Alphabetic:];"
+    "$BA = [[:LineBreak = Break_After:] - $BA_SUB];"
+    "$BB = [:LineBreak = Break_Before:];"
+    "$BK = [:LineBreak = Mandatory_Break:];"
+    "$B2 = [:LineBreak = Break_Both:];"
+    "$CB = [:LineBreak = Contingent_Break:];"
+    "$CL = [:LineBreak = Close_Punctuation:];"
+    "$CM = [:LineBreak = Combining_Mark:];"
+    "$CP = [:LineBreak = Close_Parenthesis:];"
+    "$CR = [:LineBreak = Carriage_Return:];"
+    "$EX = [[:LineBreak = Exclamation:] - $EX_SUB];"
+    "$GL = [:LineBreak = Glue:];"
+#if (U_ICU_VERSION_MAJOR_NUM >= 4) && (U_ICU_VERSION_MINOR_NUM >= 9)
+    "$HL = [:LineBreak = Hebrew_Letter:];"
+#else
+    "$HL = [[:Hebrew:] & [:Letter:]];"
+#endif
+    "$HY = [:LineBreak = Hyphen:];"
+    "$H2 = [:LineBreak = H2:];"
+    "$H3 = [:LineBreak = H3:];"
+    "$ID = [[[[:LineBreak = Ideographic:] - $CJ] $ID_ADD] - $ID_SUB];"
+    "$IN = [[:LineBreak = Inseparable:] - $IN_SUB];"
+    "$IS = [[:LineBreak = Infix_Numeric:] - $IS_SUB];"
+    "$JL = [:LineBreak = JL:];"
+    "$JV = [:LineBreak = JV:];"
+    "$JT = [:LineBreak = JT:];"
+    "$LF = [:LineBreak = Line_Feed:];"
+    "$NL = [:LineBreak = Next_Line:];"
+    "$NS = [[[[:LineBreak = Nonstarter:] - $CJ] $NS_ADD] - $NS_SUB];"
+    "$NU = [:LineBreak = Numeric:];"
+    "$OP = [:LineBreak = Open_Punctuation:];"
+    "$PO = [[:LineBreak = Postfix_Numeric:] - $PO_SUB];"
+    "$PR = [[:LineBreak = Prefix_Numeric:] - $PR_SUB];"
+    "$QU = [:LineBreak = Quotation:];"
+    "$SA = [:LineBreak = Complex_Context:];"
+    "$SG = [:LineBreak = Surrogate:];"
+    "$SP = [:LineBreak = Space:];"
+    "$SY = [:LineBreak = Break_Symbols:];"
+    "$WJ = [:LineBreak = Word_Joiner:];"
+    "$XX = [:LineBreak = Unknown:];"
+    "$ZW = [:LineBreak = ZWSpace:];"
+    "$dictionary = [:LineBreak = Complex_Context:];"
+    "$ALPlus = [$AL $AI $SA $SG $XX];"
+    "$ALcm = $ALPlus $CM*;"
+    "$BAcm = $BA $CM*;"
+    "$BBcm = $BB $CM*;"
+    "$B2cm = $B2 $CM*;"
+    "$CLcm = $CL $CM*;"
+    "$CPcm = $CP $CM*;"
+    "$EXcm = $EX $CM*;"
+    "$GLcm = $GL $CM*;"
+    "$HLcm = $HL $CM*;"
+    "$HYcm = $HY $CM*;"
+    "$H2cm = $H2 $CM*;"
+    "$H3cm = $H3 $CM*;"
+    "$IDcm = $ID $CM*;"
+    "$INcm = $IN $CM*;"
+    "$IScm = $IS $CM*;"
+    "$JLcm = $JL $CM*;"
+    "$JVcm = $JV $CM*;"
+    "$JTcm = $JT $CM*;"
+    "$NScm = $NS $CM*;"
+    "$NUcm = $NU $CM*;"
+    "$OPcm = $OP $CM*;"
+    "$POcm = $PO $CM*;"
+    "$PRcm = $PR $CM*;"
+    "$QUcm = $QU $CM*;"
+    "$SYcm = $SY $CM*;"
+    "$WJcm = $WJ $CM*;";
+
+static const char* uax14Forward =
+    "!!forward;"
+    "$CAN_CM = [^$SP $BK $CR $LF $NL $ZW $CM];"
+    "$CANT_CM = [$SP $BK $CR $LF $NL $ZW $CM];"
+    "$AL_FOLLOW_NOCM = [$BK $CR $LF $NL $ZW $SP];"
+    "$AL_FOLLOW_CM = [$CL $CP $EX $HL $IS $SY $WJ $GL $OP $QU $BA $HY $NS $IN $NU $ALPlus];"
+    "$AL_FOLLOW = [$AL_FOLLOW_NOCM $AL_FOLLOW_CM];"
+    "$LB4Breaks = [$BK $CR $LF $NL];"
+    "$LB4NonBreaks = [^$BK $CR $LF $NL];"
+    "$LB8Breaks = [$LB4Breaks $ZW];"
+    "$LB8NonBreaks = [[$LB4NonBreaks] - [$ZW]];"
+    "$LB18NonBreaks = [$LB8NonBreaks - [$SP]];"
+    "$LB18Breaks = [$LB8Breaks $SP];"
+    "$LB20NonBreaks = [$LB18NonBreaks - $CB];"
+    "$ALPlus $CM+;"
+    "$BA $CM+;"
+    "$BB $CM+;"
+    "$B2 $CM+;"
+    "$CL $CM+;"
+    "$CP $CM+;"
+    "$EX $CM+;"
+    "$GL $CM+;"
+    "$HL $CM+;"
+    "$HY $CM+;"
+    "$H2 $CM+;"
+    "$H3 $CM+;"
+    "$ID $CM+;"
+    "$IN $CM+;"
+    "$IS $CM+;"
+    "$JL $CM+;"
+    "$JV $CM+;"
+    "$JT $CM+;"
+    "$NS $CM+;"
+    "$NU $CM+;"
+    "$OP $CM+;"
+    "$PO $CM+;"
+    "$PR $CM+;"
+    "$QU $CM+;"
+    "$SY $CM+;"
+    "$WJ $CM+;"
+    "$CR $LF {100};"
+    "$LB4NonBreaks? $LB4Breaks {100};"
+    "$CAN_CM $CM* $LB4Breaks {100};"
+    "$CM+ $LB4Breaks {100};"
+    "$LB4NonBreaks [$SP $ZW];"
+    "$CAN_CM $CM* [$SP $ZW];"
+    "$CM+ [$SP $ZW];"
+    "$CAN_CM $CM+;"
+    "$CM+;"
+    "$CAN_CM $CM* $WJcm;"
+    "$LB8NonBreaks $WJcm;"
+    "$CM+ $WJcm;"
+    "$WJcm $CANT_CM;"
+    "$WJcm $CAN_CM $CM*;"
+    "$GLcm $CAN_CM $CM*;"
+    "$GLcm $CANT_CM;"
+    "[[$LB8NonBreaks] - [$SP $BA $HY]] $CM* $GLcm;"
+    "$CM+ GLcm;"
+    "$LB8NonBreaks $CL;"
+    "$CAN_CM $CM* $CL;"
+    "$CM+ $CL;"
+    "$LB8NonBreaks $CP;"
+    "$CAN_CM $CM* $CP;"
+    "$CM+ $CP;"
+    "$LB8NonBreaks $EX;"
+    "$CAN_CM $CM* $EX;"
+    "$CM+ $EX;"
+    "$LB8NonBreaks $IS;"
+    "$CAN_CM $CM* $IS;"
+    "$CM+ $IS;"
+    "$LB8NonBreaks $SY;"
+    "$CAN_CM $CM* $SY;"
+    "$CM+ $SY;"
+    "$OPcm $SP* $CAN_CM $CM*;"
+    "$OPcm $SP* $CANT_CM;"
+    "$OPcm $SP+ $CM+ $AL_FOLLOW?;"
+    "$QUcm $SP* $OPcm;"
+    "($CLcm | $CPcm) $SP* $NScm;"
+    "$B2cm $SP* $B2cm;"
+    "$LB18NonBreaks $CM* $QUcm;"
+    "$CM+ $QUcm;"
+    "$QUcm .?;"
+    "$QUcm $LB18NonBreaks $CM*;"
+    "$LB20NonBreaks $CM* ($BAcm | $HYcm | $NScm); "
+    "$BBcm [^$CB];"
+    "$BBcm $LB20NonBreaks $CM*;"
+    "$HLcm ($HYcm | $BAcm) [^$CB]?;"
+    "($ALcm | $HLcm) $INcm;"
+    "$CM+ $INcm;"
+    "$IDcm $INcm;"
+    "$INcm $INcm;"
+    "$NUcm $INcm;"
+    "$IDcm $POcm;"
+    "$ALcm $NUcm;"
+    "$HLcm $NUcm;"
+    "$CM+ $NUcm;"
+    "$NUcm $ALcm;"
+    "$NUcm $HLcm;"
+    "$PRcm $IDcm;"
+    "$PRcm ($ALcm | $HLcm);"
+    "$POcm ($ALcm | $HLcm);"
+    "($PRcm | $POcm)? ($OPcm | $HYcm)? $NUcm ($NUcm | $SYcm | $IScm)* ($CLcm | $CPcm)? ($PRcm | $POcm)?;"
+    "$JLcm ($JLcm | $JVcm | $H2cm | $H3cm);"
+    "($JVcm | $H2cm) ($JVcm | $JTcm);"
+    "($JTcm | $H3cm) $JTcm;"
+    "($JLcm | $JVcm | $JTcm | $H2cm | $H3cm) $INcm;"
+    "($JLcm | $JVcm | $JTcm | $H2cm | $H3cm) $POcm;"
+    "$PRcm ($JLcm | $JVcm | $JTcm | $H2cm | $H3cm);"
+    "($ALcm | $HLcm) ($ALcm | $HLcm);"
+    "$CM+ ($ALcm | $HLcm);"
+    "$IScm ($ALcm | $HLcm);"
+    "($ALcm | $HLcm | $NUcm) $OPcm;"
+    "$CM+ $OPcm;"
+    "$CPcm ($ALcm | $HLcm | $NUcm);";
+
+static const char* uax14Reverse =
+    "!!reverse;"
+    "$CM+ $ALPlus;"
+    "$CM+ $BA;"
+    "$CM+ $BB;"
+    "$CM+ $B2;"
+    "$CM+ $CL;"
+    "$CM+ $CP;"
+    "$CM+ $EX;"
+    "$CM+ $GL;"
+    "$CM+ $HL;"
+    "$CM+ $HY;"
+    "$CM+ $H2;"
+    "$CM+ $H3;"
+    "$CM+ $ID;"
+    "$CM+ $IN;"
+    "$CM+ $IS;"
+    "$CM+ $JL;"
+    "$CM+ $JV;"
+    "$CM+ $JT;"
+    "$CM+ $NS;"
+    "$CM+ $NU;"
+    "$CM+ $OP;"
+    "$CM+ $PO;"
+    "$CM+ $PR;"
+    "$CM+ $QU;"
+    "$CM+ $SY;"
+    "$CM+ $WJ;"
+    "$CM+;"
+    "$AL_FOLLOW $CM+ / ([$BK $CR $LF $NL $ZW {eof}] | $SP+ $CM+ $SP | $SP+ $CM* ([^$OP $CM $SP] | [$AL {eof}]));"
+    "[$PR] / $CM+ [$BK $CR $LF $NL $ZW $SP {eof}];"
+    "$LB4Breaks [$LB4NonBreaks-$CM];"
+    "$LB4Breaks $CM+ $CAN_CM;"
+    "$LF $CR;"
+    "[$SP $ZW] [$LB4NonBreaks-$CM];"
+    "[$SP $ZW] $CM+ $CAN_CM;"
+    "$CM+ $CAN_CM;"
+    "$CM* $WJ $CM* $CAN_CM;"
+    "$CM* $WJ [$LB8NonBreaks-$CM];"
+    "$CANT_CM $CM* $WJ;"
+    "$CM* $CAN_CM $CM* $WJ;"
+    "$CM* $GL $CM* [$LB8NonBreaks-[$CM $SP $BA $HY]];"
+    "$CANT_CM $CM* $GL;"
+    "$CM* $CAN_CM $CM* $GL;"
+    "$CL $CM+ $CAN_CM;"
+    "$CP $CM+ $CAN_CM;"
+    "$EX $CM+ $CAN_CM;"
+    "$IS $CM+ $CAN_CM;"
+    "$SY $CM+ $CAN_CM;"
+    "$CL [$LB8NonBreaks-$CM];"
+    "$CP [$LB8NonBreaks-$CM];"
+    "$EX [$LB8NonBreaks-$CM];"
+    "$IS [$LB8NonBreaks-$CM];"
+    "$SY [$LB8NonBreaks-$CM];"
+    "[$CL $CP $EX $IS $SY] $CM+ $SP+ $CM* $OP; "
+    "$CM* $CAN_CM $SP* $CM* $OP;"
+    "$CANT_CM $SP* $CM* $OP;"
+    "$AL_FOLLOW? $CM+ $SP $SP* $CM* $OP;"
+    "$AL_FOLLOW_NOCM $CM+ $SP+ $CM* $OP;"
+    "$CM* $AL_FOLLOW_CM $CM+ $SP+ $CM* $OP;"
+    "$SY $CM $SP+ $OP;"
+    "$CM* $OP $SP* $CM* $QU;"
+    "$CM* $NS $SP* $CM* ($CL | $CP);"
+    "$CM* $B2 $SP* $CM* $B2;"
+    "$CM* $QU $CM* $CAN_CM;"
+    "$CM* $QU $LB18NonBreaks;"
+    "$CM* $CAN_CM $CM* $QU;"
+    "$CANT_CM $CM* $QU;"
+    "$CM* ($BA | $HY | $NS) $CM* [$LB20NonBreaks-$CM];"
+    "$CM* [$LB20NonBreaks-$CM] $CM* $BB;"
+    "[^$CB] $CM* $BB;"
+    "[^$CB] $CM* ($HY | $BA) $CM* $HL;"
+    "$CM* $IN $CM* ($ALPlus | $HL);"
+    "$CM* $IN $CM* $ID;"
+    "$CM* $IN $CM* $IN;"
+    "$CM* $IN $CM* $NU;"
+    "$CM* $PO $CM* $ID;"
+    "$CM* $NU $CM* ($ALPlus | $HL);"
+    "$CM* ($ALPlus | $HL) $CM* $NU;"
+    "$CM* $ID $CM* $PR;"
+    "$CM* ($ALPlus | $HL) $CM* $PR;"
+    "$CM* ($ALPlus | $HL) $CM* $PO;"
+    "($CM* ($PR | $PO))? ($CM* ($CL | $CP))? ($CM* ($NU | $IS | $SY))* $CM* $NU ($CM* ($OP | $HY))? ($CM* ($PR | $PO))?;"
+    "$CM* ($H3 | $H2 | $JV | $JL) $CM* $JL;"
+    "$CM* ($JT | $JV) $CM* ($H2 | $JV);"
+    "$CM* $JT $CM* ($H3 | $JT);"
+    "$CM* $IN $CM* ($H3 | $H2 | $JT | $JV | $JL);"
+    "$CM* $PO $CM* ($H3 | $H2 | $JT | $JV | $JL);"
+    "$CM* ($H3 | $H2 | $JT | $JV | $JL) $CM* $PR;"
+    "$CM* ($ALPlus | $HL) $CM* ($ALPlus | $HL);"
+    "$CM* ($ALPlus | $HL) $CM* $IS;"
+    "$CM* $OP $CM* ($ALPlus | $HL | $NU);"
+    "$CM* ($ALPlus | $HL | $NU) $CM* $CP;";
+
+static const char* uax14SafeForward =
+    "!!safe_forward;"
+    "[$CM $OP $QU $CL $CP $B2 $PR $HY $BA $SP $dictionary]+ [^$CM $OP $QU $CL $CP $B2 $PR $HY $BA $dictionary];"
+    "$dictionary $dictionary;";
+
+static const char* uax14SafeReverse =
+    "!!safe_reverse;"
+    "$CM+ [^$CM $BK $CR $LF $NL $ZW $SP];"
+    "$CM+ $SP / .;"
+    "$SP+ $CM* $OP;"
+    "$SP+ $CM* $QU;"
+    "$SP+ $CM* ($CL | $CP);"
+    "$SP+ $CM* $B2;"
+    "$CM* ($HY | $BA) $CM* $HL;"
+    "($CM* ($IS | $SY))+ $CM* $NU;"
+    "($CL | $CP) $CM* ($NU | $IS | $SY);"
+    "$dictionary $dictionary;";
+
+static String mapLineIteratorModeToRules(LineBreakIteratorMode mode, bool isCJK)
+{
+    StringBuilder rulesBuilder;
+    rulesBuilder.append(uax14Prologue);
+    rulesBuilder.append(uax14AssignmentsBefore);
+    switch (mode) {
+    case LineBreakIteratorModeUAX14:
+        rulesBuilder.append(isCJK ? uax14AssignmentsCustomDefaultCJK : uax14AssignmentsCustomDefaultNonCJK);
+        break;
+    case LineBreakIteratorModeUAX14Loose:
+        rulesBuilder.append(isCJK ? uax14AssignmentsCustomLooseCJK : uax14AssignmentsCustomLooseNonCJK);
+        break;
+    case LineBreakIteratorModeUAX14Normal:
+        rulesBuilder.append(isCJK ? uax14AssignmentsCustomNormalCJK : uax14AssignmentsCustomNormalNonCJK);
+        break;
+    case LineBreakIteratorModeUAX14Strict:
+        rulesBuilder.append(isCJK ? uax14AssignmentsCustomStrictCJK : uax14AssignmentsCustomStrictNonCJK);
+        break;
+    }
+    rulesBuilder.append(uax14AssignmentsAfter);
+    rulesBuilder.append(uax14Forward);
+    rulesBuilder.append(uax14Reverse);
+    rulesBuilder.append(uax14SafeForward);
+    rulesBuilder.append(uax14SafeReverse);
+    return rulesBuilder.toString();
+}
+
+// Recognize BCP47 compliant primary language values of 'zh', 'ja', 'ko'
+// (in any combination of case), optionally followed by subtags. Don't
+// recognize 3-letter variants 'chi'/'zho', 'jpn', or 'kor' since BCP47
+// requires use of shortest language tag.
+bool isCJKLocale(const AtomicString& locale)
+{
+    size_t length = locale.length();
+    if (length < 2)
+        return false;
+    auto c1 = locale[0];
+    auto c2 = locale[1];
+    auto c3 = length == 2 ? 0 : locale[2];
+    if (!c3 || c3 == '-' || c3 == '_' || c3 == '@') {
+        if (c1 == 'z' || c1 == 'Z')
+            return c2 == 'h' || c2 == 'H';
+        if (c1 == 'j' || c1 == 'J')
+            return c2 == 'a' || c2 == 'A';
+        if (c1 == 'k' || c1 == 'K')
+            return c2 == 'o' || c2 == 'O';
+    }
+    return false;
+}
+
+TextBreakIterator* openLineBreakIterator(const AtomicString& locale, LineBreakIteratorMode mode, bool isCJK)
+{
+    UBreakIterator* ubrkIter;
+    UErrorCode openStatus = U_ZERO_ERROR;
+    bool localeIsEmpty = locale.isEmpty();
+    if (mode == LineBreakIteratorModeUAX14)
+        ubrkIter = ubrk_open(UBRK_LINE, localeIsEmpty ? currentTextBreakLocaleID() : locale.string().utf8().data(), 0, 0, &openStatus);
+    else {
+        UParseError parseStatus;
+        auto rules = mapLineIteratorModeToRules(mode, isCJK);
+        ubrkIter = ubrk_openRules(StringView(rules).upconvertedCharacters(), rules.length(), 0, 0, &parseStatus, &openStatus);
+    }
+    // locale comes from a web page and it can be invalid, leading ICU
+    // to fail, in which case we fall back to the default locale.
+    if (!localeIsEmpty && U_FAILURE(openStatus)) {
+        openStatus = U_ZERO_ERROR;
+        ubrkIter = ubrk_open(UBRK_LINE, currentTextBreakLocaleID(), 0, 0, &openStatus);
+    }
+
+    if (U_FAILURE(openStatus)) {
+        LOG_ERROR("ubrk_open failed with status %d", openStatus);
+        return nullptr;
+    }
+
+    return reinterpret_cast<TextBreakIterator*>(ubrkIter);
+}
+
+void closeLineBreakIterator(TextBreakIterator*& iterator)
+{
+    UBreakIterator* ubrkIter = reinterpret_cast<UBreakIterator*>(iterator);
+    ASSERT(ubrkIter);
+    ubrk_close(ubrkIter);
+    iterator = nullptr;
 }
 
 static TextBreakIterator* nonSharedCharacterBreakIterator;
index 10c16c7..6ba28f9 100644 (file)
@@ -31,6 +31,13 @@ class TextBreakIterator;
 
 // Note: The returned iterator is good only until you get another iterator, with the exception of acquireLineBreakIterator.
 
+enum LineBreakIteratorMode {
+    LineBreakIteratorModeUAX14,
+    LineBreakIteratorModeUAX14Loose,
+    LineBreakIteratorModeUAX14Normal,
+    LineBreakIteratorModeUAX14Strict,
+};
+
 // This is similar to character break iterator in most cases, but is subject to
 // platform UI conventions. One notable example where this can be different
 // from character break iterator is Thai prepend characters, see bug 24342.
@@ -40,8 +47,10 @@ TextBreakIterator* cursorMovementIterator(StringView);
 TextBreakIterator* wordBreakIterator(StringView);
 TextBreakIterator* sentenceBreakIterator(StringView);
 
-WEBCORE_EXPORT TextBreakIterator* acquireLineBreakIterator(StringView, const AtomicString& locale, const UChar* priorContext, unsigned priorContextLength);
+WEBCORE_EXPORT TextBreakIterator* acquireLineBreakIterator(StringView, const AtomicString& locale, const UChar* priorContext, unsigned priorContextLength, LineBreakIteratorMode, bool isCJK);
 WEBCORE_EXPORT void releaseLineBreakIterator(TextBreakIterator*);
+TextBreakIterator* openLineBreakIterator(const AtomicString& locale, LineBreakIteratorMode, bool isCJK);
+void closeLineBreakIterator(TextBreakIterator*&);
 
 int textBreakFirst(TextBreakIterator*);
 int textBreakLast(TextBreakIterator*);
@@ -55,24 +64,30 @@ bool isWordTextBreak(TextBreakIterator*);
 
 const int TextBreakDone = -1;
 
+bool isCJKLocale(const AtomicString&);
+
 class LazyLineBreakIterator {
 public:
     LazyLineBreakIterator()
-        : m_iterator(0)
-        , m_cachedPriorContext(0)
+        : m_iterator(nullptr)
+        , m_cachedPriorContext(nullptr)
+        , m_mode(LineBreakIteratorModeUAX14)
         , m_cachedPriorContextLength(0)
+        , m_isCJK(false)
     {
         resetPriorContext();
     }
 
-    LazyLineBreakIterator(String string, const AtomicString& locale = AtomicString())
+    LazyLineBreakIterator(String string, const AtomicString& locale = AtomicString(), LineBreakIteratorMode mode = LineBreakIteratorModeUAX14)
         : m_string(string)
         , m_locale(locale)
-        , m_iterator(0)
-        , m_cachedPriorContext(0)
+        , m_iterator(nullptr)
+        , m_cachedPriorContext(nullptr)
+        , m_mode(mode)
         , m_cachedPriorContextLength(0)
     {
         resetPriorContext();
+        m_isCJK = isCJKLocale(locale);
     }
 
     ~LazyLineBreakIterator()
@@ -82,35 +97,41 @@ public:
     }
 
     String string() const { return m_string; }
+    bool isLooseCJKMode() const { return m_isCJK && m_mode == LineBreakIteratorModeUAX14Loose; }
 
     UChar lastCharacter() const
     {
         COMPILE_ASSERT(WTF_ARRAY_LENGTH(m_priorContext) == 2, TextBreakIterator_unexpected_prior_context_length);
         return m_priorContext[1];
     }
+
     UChar secondToLastCharacter() const
     {
         COMPILE_ASSERT(WTF_ARRAY_LENGTH(m_priorContext) == 2, TextBreakIterator_unexpected_prior_context_length);
         return m_priorContext[0];
     }
+
     void setPriorContext(UChar last, UChar secondToLast)
     {
         COMPILE_ASSERT(WTF_ARRAY_LENGTH(m_priorContext) == 2, TextBreakIterator_unexpected_prior_context_length);
         m_priorContext[0] = secondToLast;
         m_priorContext[1] = last;
     }
+
     void updatePriorContext(UChar last)
     {
         COMPILE_ASSERT(WTF_ARRAY_LENGTH(m_priorContext) == 2, TextBreakIterator_unexpected_prior_context_length);
         m_priorContext[0] = m_priorContext[1];
         m_priorContext[1] = last;
     }
+
     void resetPriorContext()
     {
         COMPILE_ASSERT(WTF_ARRAY_LENGTH(m_priorContext) == 2, TextBreakIterator_unexpected_prior_context_length);
         m_priorContext[0] = 0;
         m_priorContext[1] = 0;
     }
+
     unsigned priorContextLength() const
     {
         unsigned priorContextLength = 0;
@@ -122,6 +143,7 @@ public:
         }
         return priorContextLength;
     }
+
     // Obtain text break iterator, possibly previously cached, where this iterator is (or has been)
     // initialized to use the previously stored string as the primary breaking context and using
     // previously stored prior context if non-empty.
@@ -130,23 +152,26 @@ public:
         ASSERT(priorContextLength <= priorContextCapacity);
         const UChar* priorContext = priorContextLength ? &m_priorContext[priorContextCapacity - priorContextLength] : 0;
         if (!m_iterator) {
-            m_iterator = acquireLineBreakIterator(m_string, m_locale, priorContext, priorContextLength);
+            m_iterator = acquireLineBreakIterator(m_string, m_locale, priorContext, priorContextLength, m_mode, m_isCJK);
             m_cachedPriorContext = priorContext;
             m_cachedPriorContextLength = priorContextLength;
         } else if (priorContext != m_cachedPriorContext || priorContextLength != m_cachedPriorContextLength) {
-            this->resetStringAndReleaseIterator(m_string, m_locale);
+            resetStringAndReleaseIterator(m_string, m_locale, m_mode);
             return this->get(priorContextLength);
         }
         return m_iterator;
     }
-    void resetStringAndReleaseIterator(String string, const AtomicString& locale)
+
+    void resetStringAndReleaseIterator(String string, const AtomicString& locale, LineBreakIteratorMode mode)
     {
         if (m_iterator)
             releaseLineBreakIterator(m_iterator);
         m_string = string;
         m_locale = locale;
-        m_iterator = 0;
-        m_cachedPriorContext = 0;
+        m_iterator = nullptr;
+        m_cachedPriorContext = nullptr;
+        m_mode = mode;
+        m_isCJK = isCJKLocale(locale);
         m_cachedPriorContextLength = 0;
     }
 
@@ -155,9 +180,11 @@ private:
     String m_string;
     AtomicString m_locale;
     TextBreakIterator* m_iterator;
-    UChar m_priorContext[priorContextCapacity];
     const UChar* m_cachedPriorContext;
+    LineBreakIteratorMode m_mode;
     unsigned m_cachedPriorContextLength;
+    UChar m_priorContext[priorContextCapacity];
+    bool m_isCJK;
 };
 
 // Iterates over "extended grapheme clusters", as defined in UAX #29.
index 857de67..1267d96 100644 (file)
@@ -597,6 +597,22 @@ float RenderText::maxLogicalWidth() const
     return m_maxWidth;
 }
 
+LineBreakIteratorMode mapLineBreakToIteratorMode(LineBreak lineBreak)
+{
+    switch (lineBreak) {
+    case LineBreakAuto:
+    case LineBreakAfterWhiteSpace:
+        return LineBreakIteratorModeUAX14;
+    case LineBreakLoose:
+        return LineBreakIteratorModeUAX14Loose;
+    case LineBreakNormal:
+        return LineBreakIteratorModeUAX14Normal;
+    case LineBreakStrict:
+        return LineBreakIteratorModeUAX14Strict;
+    }
+    return LineBreakIteratorModeUAX14;
+}
+
 void RenderText::computePreferredLogicalWidths(float leadWidth)
 {
     HashSet<const SimpleFontData*> fallbackFonts;
@@ -673,7 +689,7 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
     const Font& font = style.font(); // FIXME: This ignores first-line.
     float wordSpacing = font.wordSpacing();
     int len = textLength();
-    LazyLineBreakIterator breakIterator(m_text, style.locale());
+    LazyLineBreakIterator breakIterator(m_text, style.locale(), mapLineBreakToIteratorMode(style.lineBreak()));
     bool needsWordSpacing = false;
     bool ignoringSpaces = false;
     bool isSpace = false;
@@ -708,6 +724,7 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
 
     bool breakNBSP = style.autoWrap() && style.nbspMode() == SPACE;
     bool breakAll = (style.wordBreak() == BreakAllWordBreak || style.wordBreak() == BreakWordBreak) && style.autoWrap();
+    bool isLooseCJKMode = breakIterator.isLooseCJKMode();
 
     for (int i = 0; i < len; i++) {
         UChar c = uncheckedCharacterAt(i);
@@ -755,7 +772,7 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
             continue;
         }
 
-        bool hasBreak = breakAll || isBreakable(breakIterator, i, nextBreakable, breakNBSP);
+        bool hasBreak = breakAll || isBreakable(breakIterator, i, nextBreakable, breakNBSP, isLooseCJKMode);
         bool betweenWords = true;
         int j = i;
         while (c != '\n' && !isSpaceAccordingToStyle(c, style) && c != '\t' && (c != softHyphen || style.hyphens() == HyphensNone)) {
@@ -763,7 +780,7 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
             if (j == len)
                 break;
             c = uncheckedCharacterAt(j);
-            if (isBreakable(breakIterator, j, nextBreakable, breakNBSP) && characterAt(j - 1) != softHyphen)
+            if (isBreakable(breakIterator, j, nextBreakable, breakNBSP, isLooseCJKMode) && characterAt(j - 1) != softHyphen)
                 break;
             if (breakAll) {
                 betweenWords = false;
index 6c718d3..4a5075b 100644 (file)
@@ -27,6 +27,7 @@
 #include "RenderTextLineBoxes.h"
 #include "SimpleLineLayout.h"
 #include "Text.h"
+#include "TextBreakIterator.h"
 #include <wtf/Forward.h>
 
 namespace WebCore {
@@ -274,6 +275,7 @@ inline Color RenderText::selectionEmphasisMarkColor() const
 
 void applyTextTransform(const RenderStyle&, String&, UChar);
 void makeCapitalized(String*, UChar previous);
+LineBreakIteratorMode mapLineBreakToIteratorMode(LineBreak);
     
 inline RenderText* Text::renderer() const
 {
index f2c3345..3253475 100644 (file)
@@ -161,6 +161,8 @@ bool canUseFor(const RenderBlockFlow& flow)
         return false;
     if (style.borderFit() == BorderFitLines)
         return false;
+    if (style.lineBreak() != LineBreakAuto)
+        return false;
     const RenderText& textRenderer = downcast<RenderText>(*flow.firstChild());
     if (flow.containsFloats()) {
         // We can't use the code path if any lines would need to be shifted below floats. This is because we don't keep per-line y coordinates.
index 34407ac..c1e8b50 100644 (file)
@@ -53,7 +53,7 @@ FlowContents::FlowContents(const RenderBlockFlow& flow)
 unsigned FlowContents::findNextBreakablePosition(unsigned position) const
 {
     String string = m_lineBreakIterator.string();
-    unsigned breakablePosition = nextBreakablePosition<LChar, false>(m_lineBreakIterator, string.characters8(), string.length(), position);
+    unsigned breakablePosition = nextBreakablePositionNonLoosely<LChar, NBSPBehavior::IgnoreNBSP>(m_lineBreakIterator, string.characters8(), string.length(), position);
     if (appendNextRendererContentIfNeeded(breakablePosition))
         return findNextBreakablePosition(position);
     ASSERT(breakablePosition >= position);
@@ -157,7 +157,7 @@ bool FlowContents::appendNextRendererContentIfNeeded(unsigned position) const
         return false;
 
     ++m_lastRendererIndex;
-    m_lineBreakIterator.resetStringAndReleaseIterator(string + String(nextRenderer->text()), m_flow.style().locale());
+    m_lineBreakIterator.resetStringAndReleaseIterator(string + String(nextRenderer->text()), m_flow.style().locale(), LineBreakIteratorModeUAX14);
     return true;
 }
 
index 7683573..a2d8c83 100644 (file)
@@ -35,10 +35,12 @@ static const unsigned asciiLineBreakTableColumnCount = (asciiLineBreakTableLastC
 
 extern const unsigned char asciiLineBreakTable[][asciiLineBreakTableColumnCount];
 
-int nextBreakablePositionIgnoringNBSP(LazyLineBreakIterator&, int pos);
-int nextBreakablePosition(LazyLineBreakIterator&, int pos);
+enum class NBSPBehavior {
+    IgnoreNBSP,
+    TreatNBSPAsBreak,
+};
 
-template<bool treatNoBreakSpaceAsBreak>
+template<NBSPBehavior nbspBehavior>
 static inline bool isBreakableSpace(UChar ch)
 {
     switch (ch) {
@@ -47,7 +49,7 @@ static inline bool isBreakableSpace(UChar ch)
     case '\t':
         return true;
     case noBreakSpace:
-        return treatNoBreakSpaceAsBreak;
+        return nbspBehavior == NBSPBehavior::TreatNBSPAsBreak;
     default:
         return false;
     }
@@ -71,16 +73,17 @@ inline bool shouldBreakAfter(UChar lastCh, UChar ch, UChar nextCh)
     return false;
 }
 
-template<bool treatNoBreakSpaceAsBreak>
+template<NBSPBehavior nbspBehavior>
 inline bool needsLineBreakIterator(UChar ch)
 {
-    if (treatNoBreakSpaceAsBreak)
+    if (nbspBehavior == NBSPBehavior::TreatNBSPAsBreak)
         return ch > asciiLineBreakTableLastChar;
     return ch > asciiLineBreakTableLastChar && ch != noBreakSpace;
 }
 
-template<typename CharacterType, bool treatNoBreakSpaceAsBreak>
-inline int nextBreakablePosition(LazyLineBreakIterator& lazyBreakIterator, const CharacterType* str, unsigned length, int pos)
+// When in non-loose mode, we can use the ASCII shortcut table.
+template<typename CharacterType, NBSPBehavior nbspBehavior>
+inline int nextBreakablePositionNonLoosely(LazyLineBreakIterator& lazyBreakIterator, const CharacterType* str, unsigned length, int pos)
 {
     int len = static_cast<int>(length);
     int nextBreak = -1;
@@ -91,10 +94,12 @@ inline int nextBreakablePosition(LazyLineBreakIterator& lazyBreakIterator, const
     for (int i = pos; i < len; i++) {
         CharacterType ch = str[i];
 
-        if (isBreakableSpace<treatNoBreakSpaceAsBreak>(ch) || shouldBreakAfter(lastLastCh, lastCh, ch))
+        // Non-loose mode, so use ASCII shortcut (shouldBreakAfter) if not breakable space.
+        if (isBreakableSpace<nbspBehavior>(ch) || shouldBreakAfter(lastLastCh, lastCh, ch))
             return i;
 
-        if (needsLineBreakIterator<treatNoBreakSpaceAsBreak>(ch) || needsLineBreakIterator<treatNoBreakSpaceAsBreak>(lastCh)) {
+        // Non-loose mode, so conditionally use break iterator.
+        if (needsLineBreakIterator<nbspBehavior>(ch) || needsLineBreakIterator<nbspBehavior>(lastCh)) {
             if (nextBreak < i) {
                 // Don't break if positioned at start of primary context and there is no prior context.
                 if (i || priorContextLength) {
@@ -106,7 +111,7 @@ inline int nextBreakablePosition(LazyLineBreakIterator& lazyBreakIterator, const
                     }
                 }
             }
-            if (i == nextBreak && !isBreakableSpace<treatNoBreakSpaceAsBreak>(lastCh))
+            if (i == nextBreak && !isBreakableSpace<nbspBehavior>(lastCh))
                 return i;
         }
 
@@ -117,25 +122,88 @@ inline int nextBreakablePosition(LazyLineBreakIterator& lazyBreakIterator, const
     return len;
 }
 
+// When in loose mode, we can't use the ASCII shortcut table since loose mode allows "$100" to break after '$' in content marked as CJK.
+// N.B. It should be possible to combine the following with the non-loose version above by adding a LooseBehavior template parameter;
+// however, when doing this, a 10% performance regression appeared on chromium-win (https://bugs.webkit.org/show_bug.cgi?id=89235#c112).
+template<typename CharacterType, NBSPBehavior nbspBehavior>
+static inline int nextBreakablePositionLoosely(LazyLineBreakIterator& lazyBreakIterator, const CharacterType* str, unsigned length, int pos)
+{
+    int len = static_cast<int>(length);
+    int nextBreak = -1;
+
+    CharacterType lastCh = pos > 0 ? str[pos - 1] : static_cast<CharacterType>(lazyBreakIterator.lastCharacter());
+    unsigned priorContextLength = lazyBreakIterator.priorContextLength();
+    for (int i = pos; i < len; i++) {
+        CharacterType ch = str[i];
+
+        // Always loose mode, so don't use ASCII shortcut (shouldBreakAfter).
+        if (isBreakableSpace<nbspBehavior>(ch))
+            return i;
+
+        // Always use line break iterator in loose mode.
+        if (nextBreak < i) {
+            // Don't break if positioned at start of primary context and there is no prior context.
+            if (i || priorContextLength) {
+                TextBreakIterator* breakIterator = lazyBreakIterator.get(priorContextLength);
+                if (breakIterator) {
+                    nextBreak = textBreakFollowing(breakIterator, i - 1 + priorContextLength);
+                    if (nextBreak >= 0)
+                        nextBreak -= priorContextLength;
+                }
+            }
+        }
+        if (i == nextBreak && !isBreakableSpace<nbspBehavior>(lastCh))
+            return i;
+
+        lastCh = ch;
+    }
+
+    return len;
+}
+
+inline int nextBreakablePosition(LazyLineBreakIterator& lazyBreakIterator, int pos)
+{
+    String string = lazyBreakIterator.string();
+    if (string.is8Bit())
+        return nextBreakablePositionNonLoosely<LChar, NBSPBehavior::TreatNBSPAsBreak>(lazyBreakIterator, string.characters8(), string.length(), pos);
+    return nextBreakablePositionNonLoosely<UChar, NBSPBehavior::TreatNBSPAsBreak>(lazyBreakIterator, string.characters16(), string.length(), pos);
+}
+
 inline int nextBreakablePositionIgnoringNBSP(LazyLineBreakIterator& lazyBreakIterator, int pos)
 {
     String string = lazyBreakIterator.string();
     if (string.is8Bit())
-        return nextBreakablePosition<LChar, false>(lazyBreakIterator, string.characters8(), string.length(), pos);
-    return nextBreakablePosition<UChar, false>(lazyBreakIterator, string.characters16(), string.length(), pos);
+        return nextBreakablePositionNonLoosely<LChar, NBSPBehavior::IgnoreNBSP>(lazyBreakIterator, string.characters8(), string.length(), pos);
+    return nextBreakablePositionNonLoosely<UChar, NBSPBehavior::IgnoreNBSP>(lazyBreakIterator, string.characters16(), string.length(), pos);
 }
 
-inline int nextBreakablePosition(LazyLineBreakIterator& lazyBreakIterator, int pos)
+inline int nextBreakablePositionLoose(LazyLineBreakIterator& lazyBreakIterator, int pos)
 {
     String string = lazyBreakIterator.string();
     if (string.is8Bit())
-        return nextBreakablePosition<LChar, true>(lazyBreakIterator, string.characters8(), string.length(), pos);
-    return nextBreakablePosition<UChar, true>(lazyBreakIterator, string.characters16(), string.length(), pos);
+        return nextBreakablePositionLoosely<LChar, NBSPBehavior::TreatNBSPAsBreak>(lazyBreakIterator, string.characters8(), string.length(), pos);
+    return nextBreakablePositionLoosely<UChar, NBSPBehavior::TreatNBSPAsBreak>(lazyBreakIterator, string.characters16(), string.length(), pos);
 }
 
-inline bool isBreakable(LazyLineBreakIterator& lazyBreakIterator, int pos, int& nextBreakable, bool breakNBSP)
+inline int nextBreakablePositionIgnoringNBSPLoose(LazyLineBreakIterator& lazyBreakIterator, int pos)
 {
-    if (pos > nextBreakable) {
+    String string = lazyBreakIterator.string();
+    if (string.is8Bit())
+        return nextBreakablePositionLoosely<LChar, NBSPBehavior::IgnoreNBSP>(lazyBreakIterator, string.characters8(), string.length(), pos);
+    return nextBreakablePositionLoosely<UChar, NBSPBehavior::IgnoreNBSP>(lazyBreakIterator, string.characters16(), string.length(), pos);
+}
+
+inline bool isBreakable(LazyLineBreakIterator& lazyBreakIterator, int pos, int& nextBreakable, bool breakNBSP, bool isLooseMode)
+{
+    if (pos <= nextBreakable)
+        return pos == nextBreakable;
+
+    if (isLooseMode) {
+        if (breakNBSP)
+            nextBreakable = nextBreakablePositionLoose(lazyBreakIterator, pos);
+        else
+            nextBreakable = nextBreakablePositionIgnoringNBSPLoose(lazyBreakIterator, pos);
+    } else {
         if (breakNBSP)
             nextBreakable = nextBreakablePosition(lazyBreakIterator, pos);
         else
index 71bdaf5..6e2ef90 100644 (file)
@@ -624,6 +624,7 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
     bool midWordBreak = false;
     bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap;
     float hyphenWidth = 0;
+    bool isLooseCJKMode = false;
 
     if (isSVGText) {
         breakWords = false;
@@ -635,7 +636,8 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
         m_renderTextInfo.m_text = &renderText;
         m_renderTextInfo.m_font = &font;
         m_renderTextInfo.m_layout = font.createLayout(&renderText, m_width.currentWidth(), m_collapseWhiteSpace);
-        m_renderTextInfo.m_lineBreakIterator.resetStringAndReleaseIterator(renderText.text(), style.locale());
+        m_renderTextInfo.m_lineBreakIterator.resetStringAndReleaseIterator(renderText.text(), style.locale(), mapLineBreakToIteratorMode(m_blockStyle.lineBreak()));
+        isLooseCJKMode = m_renderTextInfo.m_lineBreakIterator.isLooseCJKMode();
     } else if (m_renderTextInfo.m_layout && m_renderTextInfo.m_font != &font) {
         m_renderTextInfo.m_font = &font;
         m_renderTextInfo.m_layout = font.createLayout(&renderText, m_width.currentWidth(), m_collapseWhiteSpace);
@@ -676,7 +678,7 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
         }
 
         int nextBreakablePosition = m_current.nextBreakablePosition();
-        bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.m_lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP)
+        bool betweenWords = c == '\n' || (m_currWS != PRE && !m_atStart && isBreakable(m_renderTextInfo.m_lineBreakIterator, m_current.offset(), nextBreakablePosition, breakNBSP, isLooseCJKMode)
             && (style.hyphens() != HyphensNone || (m_current.previousInSameNode() != softHyphen)));
         m_current.setNextBreakablePosition(nextBreakablePosition);