Changeset 274008 in webkit


Ignore:
Timestamp:
Mar 5, 2021 2:31:07 PM (17 months ago)
Author:
Alan Bujtas
Message:

word-wrap/overflow-wrap "overwrite" hyphens
https://bugs.webkit.org/show_bug.cgi?id=222548
<rdar://problem/75061741>

Reviewed by Antti Koivisto.

Source/WebCore:

According to https://drafts.csswg.org/css-text-3/#overflow-wrap-property

"overflow-wrap/word-wrap property specifies whether the UA may break at otherwise disallowed points within a line to prevent overflow,
when an otherwise-unbreakable string is too long to fit within the line box."

which means that in case of "hyphen: auto", we should only try to break the content at arbitrary position when there's no prior hyphenation opportunity.
This patch turns WordBreakRule into and OptionSet so that we can put both the hyphenation and arbitrary position break runles in there and prioritize them
in tryBreakingTextRun (check for the hyphenation value first/break the content at hyphenation position and do arbitrary position only
if no hyphenation opportunities are found).

Test: fast/inline/hyphenation-when-overflow-wrap-is-break-word.html

  • layout/inlineformatting/InlineContentBreaker.cpp:

(WebCore::Layout::InlineContentBreaker::wordBreakBehavior const):
(WebCore::Layout::InlineContentBreaker::tryBreakingTextRun const):

  • layout/inlineformatting/InlineContentBreaker.h:

LayoutTests:

  • fast/inline/hyphenation-when-overflow-wrap-is-break-word-expected.html: Added.
  • fast/inline/hyphenation-when-overflow-wrap-is-break-word.html: Added.
Location:
trunk
Files:
2 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r274007 r274008  
     12021-03-05  Zalan Bujtas  <zalan@apple.com>
     2
     3        word-wrap/overflow-wrap "overwrite" hyphens
     4        https://bugs.webkit.org/show_bug.cgi?id=222548
     5        <rdar://problem/75061741>
     6
     7        Reviewed by Antti Koivisto.
     8
     9        * fast/inline/hyphenation-when-overflow-wrap-is-break-word-expected.html: Added.
     10        * fast/inline/hyphenation-when-overflow-wrap-is-break-word.html: Added.
     11
    1122021-03-05  Ryan Haddad  <ryanhaddad@apple.com>
    213
  • trunk/Source/WebCore/ChangeLog

    r273986 r274008  
     12021-03-05  Zalan Bujtas  <zalan@apple.com>
     2
     3        word-wrap/overflow-wrap "overwrite" hyphens
     4        https://bugs.webkit.org/show_bug.cgi?id=222548
     5        <rdar://problem/75061741>
     6
     7        Reviewed by Antti Koivisto.
     8
     9        According to https://drafts.csswg.org/css-text-3/#overflow-wrap-property
     10
     11        "overflow-wrap/word-wrap property specifies whether the UA may break at otherwise disallowed points within a line to prevent overflow,
     12        when an otherwise-unbreakable string is too long to fit within the line box."
     13
     14        which means that in case of "hyphen: auto", we should only try to break the content at arbitrary position when there's no prior hyphenation opportunity.
     15        This patch turns WordBreakRule into and OptionSet so that we can put both the hyphenation and arbitrary position break runles in there and prioritize them
     16        in tryBreakingTextRun (check for the hyphenation value first/break the content at hyphenation position and do arbitrary position only
     17        if no hyphenation opportunities are found).
     18
     19        Test: fast/inline/hyphenation-when-overflow-wrap-is-break-word.html
     20
     21        * layout/inlineformatting/InlineContentBreaker.cpp:
     22        (WebCore::Layout::InlineContentBreaker::wordBreakBehavior const):
     23        (WebCore::Layout::InlineContentBreaker::tryBreakingTextRun const):
     24        * layout/inlineformatting/InlineContentBreaker.h:
     25
    1262021-03-05  Lauro Moura  <lmoura@igalia.com>
    227
  • trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.cpp

    r273918 r274008  
    393393}
    394394
    395 InlineContentBreaker::WordBreakRule InlineContentBreaker::wordBreakBehavior(const RenderStyle& style, bool hasWrapOpportunityAtPreviousPosition) const
     395OptionSet<InlineContentBreaker::WordBreakRule> InlineContentBreaker::wordBreakBehavior(const RenderStyle& style, bool hasWrapOpportunityAtPreviousPosition) const
    396396{
    397397    // Disregard any prohibition against line breaks mandated by the word-break property.
    398398    // The different wrapping opportunities must not be prioritized. Hyphenation is not applied.
    399399    if (style.lineBreak() == LineBreak::Anywhere)
    400         return WordBreakRule::AtArbitraryPosition;
     400        return { WordBreakRule::AtArbitraryPosition };
    401401    // Breaking is allowed within “words”.
    402402    if (style.wordBreak() == WordBreak::BreakAll)
    403         return WordBreakRule::AtArbitraryPosition;
     403        return { WordBreakRule::AtArbitraryPosition };
    404404    // Breaking is forbidden within “words”.
    405405    if (style.wordBreak() == WordBreak::KeepAll)
    406         return WordBreakRule::NoBreak;
     406        return { };
     407
     408    auto breakRules = OptionSet<WordBreakRule> { };
     409    auto hyphenationIsAllowed = !n_hyphenationIsDisabled && style.hyphens() == Hyphens::Auto && canHyphenate(style.computedLocale());
     410    if (hyphenationIsAllowed)
     411        breakRules.add({ WordBreakRule::AtHyphenationOpportunities });
    407412    // For compatibility with legacy content, the word-break property also supports a deprecated break-word keyword.
    408413    // When specified, this has the same effect as word-break: normal and overflow-wrap: anywhere, regardless of the actual value of the overflow-wrap property.
    409     if (style.wordBreak() == WordBreak::BreakWord && !hasWrapOpportunityAtPreviousPosition)
    410         return WordBreakRule::AtArbitraryPosition;
     414    if (style.wordBreak() == WordBreak::BreakWord && !hasWrapOpportunityAtPreviousPosition) {
     415        breakRules.add({ WordBreakRule::AtArbitraryPosition });
     416        return breakRules;
     417    }
    411418    // OverflowWrap::Break: An otherwise unbreakable sequence of characters may be broken at an arbitrary point if there are no otherwise-acceptable break points in the line.
    412     if (style.overflowWrap() == OverflowWrap::Break && !hasWrapOpportunityAtPreviousPosition)
    413         return WordBreakRule::AtArbitraryPosition;
    414 
    415     if (!n_hyphenationIsDisabled && style.hyphens() == Hyphens::Auto && canHyphenate(style.computedLocale()))
    416         return WordBreakRule::OnlyHyphenationAllowed;
    417 
    418     return WordBreakRule::NoBreak;
     419    if (style.overflowWrap() == OverflowWrap::Break && !hasWrapOpportunityAtPreviousPosition) {
     420        breakRules.add({ WordBreakRule::AtArbitraryPosition });
     421        return breakRules;
     422    }
     423    return breakRules;
    419424}
    420425
     
    426431    auto availableSpaceIsInfinite = !availableWidth.hasValue();
    427432
    428     auto breakRule = wordBreakBehavior(style, hasWrapOpportunityAtPreviousPosition);
    429     if (breakRule == WordBreakRule::AtArbitraryPosition) {
    430         if (!inlineTextItem.length()) {
    431             // Empty text runs may be breakable based on style, but in practice we can't really split them any further.
    432             return PartialRun { };
    433         }
    434         if (availableSpaceIsInfinite) {
    435             // When the run can be split at arbitrary position let's just return the entire run when it is intended to fit on the line.
    436             ASSERT(inlineTextItem.length());
    437             auto trailingPartialRunWidth = TextUtil::width(inlineTextItem, logicalLeft);
    438             return PartialRun { inlineTextItem.length(), trailingPartialRunWidth };
    439         }
    440         if (!*availableWidth) {
    441             // Fast path for cases when there's no room at all. The content is breakable but we don't have space for it.
    442             return PartialRun { };
    443         }
    444         auto splitData = TextUtil::split(inlineTextItem, overflowingRun.logicalWidth, *availableWidth, logicalLeft);
    445         return PartialRun { splitData.length, splitData.logicalWidth };
    446     }
    447 
    448     if (breakRule == WordBreakRule::OnlyHyphenationAllowed) {
    449         // Find the hyphen position as follows:
    450         // 1. Split the text by taking the hyphen width into account
    451         // 2. Find the last hyphen position before the split position
    452         if (!availableSpaceIsInfinite && !*availableWidth) {
    453             // We won't be able to find hyphen location when there's no available space.
    454             return { };
    455         }
    456         auto runLength = inlineTextItem.length();
    457         unsigned limitBefore = style.hyphenationLimitBefore() == RenderStyle::initialHyphenationLimitBefore() ? 0 : style.hyphenationLimitBefore();
    458         unsigned limitAfter = style.hyphenationLimitAfter() == RenderStyle::initialHyphenationLimitAfter() ? 0 : style.hyphenationLimitAfter();
    459         // Check if this run can accommodate the before/after limits at all before start measuring text.
    460         if (limitBefore >= runLength || limitAfter >= runLength || limitBefore + limitAfter > runLength)
    461             return { };
    462 
    463         unsigned leftSideLength = runLength;
    464         auto& fontCascade = style.fontCascade();
    465         auto hyphenWidth = InlineLayoutUnit { fontCascade.width(TextRun { StringView { style.hyphenString() } }) };
    466         if (!availableSpaceIsInfinite) {
    467             auto availableWidthExcludingHyphen = *availableWidth - hyphenWidth;
    468             if (availableWidthExcludingHyphen <= 0 || !enoughWidthForHyphenation(availableWidthExcludingHyphen, fontCascade.pixelSize()))
    469                 return { };
    470             leftSideLength = TextUtil::split(inlineTextItem, overflowingRun.logicalWidth, availableWidthExcludingHyphen, logicalLeft).length;
    471         }
    472         if (leftSideLength < limitBefore)
    473             return { };
    474         // Adjust before index to accommodate the limit-after value (it's the last potential hyphen location in this run).
    475         auto hyphenBefore = std::min(leftSideLength, runLength - limitAfter) + 1;
    476         unsigned hyphenLocation = lastHyphenLocation(StringView(inlineTextItem.inlineTextBox().content()).substring(inlineTextItem.start(), inlineTextItem.length()), hyphenBefore, style.computedLocale());
    477         if (!hyphenLocation || hyphenLocation < limitBefore)
    478             return { };
    479         // hyphenLocation is relative to the start of this InlineItemText.
    480         ASSERT(inlineTextItem.start() + hyphenLocation < inlineTextItem.end());
    481         auto trailingPartialRunWidthWithHyphen = TextUtil::width(inlineTextItem, inlineTextItem.start(), inlineTextItem.start() + hyphenLocation, logicalLeft);
    482         return PartialRun { hyphenLocation, trailingPartialRunWidthWithHyphen, hyphenWidth };
    483     }
    484 
    485     ASSERT(breakRule == WordBreakRule::NoBreak);
     433    auto breakRules = wordBreakBehavior(style, hasWrapOpportunityAtPreviousPosition);
     434    if (breakRules.isEmpty())
     435        return { };
     436
     437    if (breakRules.contains(WordBreakRule::AtHyphenationOpportunities)) {
     438        auto tryBreakingAtHyphenationOpportunity = [&]() -> Optional<PartialRun> {
     439            // Find the hyphen position as follows:
     440            // 1. Split the text by taking the hyphen width into account
     441            // 2. Find the last hyphen position before the split position
     442            if (!availableSpaceIsInfinite && !*availableWidth) {
     443                // We won't be able to find hyphen location when there's no available space.
     444                return { };
     445            }
     446            auto runLength = inlineTextItem.length();
     447            unsigned limitBefore = style.hyphenationLimitBefore() == RenderStyle::initialHyphenationLimitBefore() ? 0 : style.hyphenationLimitBefore();
     448            unsigned limitAfter = style.hyphenationLimitAfter() == RenderStyle::initialHyphenationLimitAfter() ? 0 : style.hyphenationLimitAfter();
     449            // Check if this run can accommodate the before/after limits at all before start measuring text.
     450            if (limitBefore >= runLength || limitAfter >= runLength || limitBefore + limitAfter > runLength)
     451                return { };
     452
     453            unsigned leftSideLength = runLength;
     454            auto& fontCascade = style.fontCascade();
     455            auto hyphenWidth = InlineLayoutUnit { fontCascade.width(TextRun { StringView { style.hyphenString() } }) };
     456            if (!availableSpaceIsInfinite) {
     457                auto availableWidthExcludingHyphen = *availableWidth - hyphenWidth;
     458                if (availableWidthExcludingHyphen <= 0 || !enoughWidthForHyphenation(availableWidthExcludingHyphen, fontCascade.pixelSize()))
     459                    return { };
     460                leftSideLength = TextUtil::split(inlineTextItem, overflowingRun.logicalWidth, availableWidthExcludingHyphen, logicalLeft).length;
     461            }
     462            if (leftSideLength < limitBefore)
     463                return { };
     464            // Adjust before index to accommodate the limit-after value (it's the last potential hyphen location in this run).
     465            auto hyphenBefore = std::min(leftSideLength, runLength - limitAfter) + 1;
     466            unsigned hyphenLocation = lastHyphenLocation(StringView(inlineTextItem.inlineTextBox().content()).substring(inlineTextItem.start(), inlineTextItem.length()), hyphenBefore, style.computedLocale());
     467            if (!hyphenLocation || hyphenLocation < limitBefore)
     468                return { };
     469            // hyphenLocation is relative to the start of this InlineItemText.
     470            ASSERT(inlineTextItem.start() + hyphenLocation < inlineTextItem.end());
     471            auto trailingPartialRunWidthWithHyphen = TextUtil::width(inlineTextItem, inlineTextItem.start(), inlineTextItem.start() + hyphenLocation, logicalLeft);
     472            return PartialRun { hyphenLocation, trailingPartialRunWidthWithHyphen, hyphenWidth };
     473        };
     474        if (auto partialRun = tryBreakingAtHyphenationOpportunity())
     475            return partialRun;
     476    }
     477
     478    if (breakRules.contains(WordBreakRule::AtArbitraryPosition)) {
     479        auto tryBreakingAtArbitraryPosition = [&]() -> PartialRun {
     480            if (!inlineTextItem.length()) {
     481                // Empty text runs may be breakable based on style, but in practice we can't really split them any further.
     482                return { };
     483            }
     484            if (availableSpaceIsInfinite) {
     485                // When the run can be split at arbitrary position let's just return the entire run when it is intended to fit on the line.
     486                ASSERT(inlineTextItem.length());
     487                auto trailingPartialRunWidth = TextUtil::width(inlineTextItem, logicalLeft);
     488                return { inlineTextItem.length(), trailingPartialRunWidth };
     489            }
     490            if (!*availableWidth) {
     491                // Fast path for cases when there's no room at all. The content is breakable but we don't have space for it.
     492                return { };
     493            }
     494            auto splitData = TextUtil::split(inlineTextItem, overflowingRun.logicalWidth, *availableWidth, logicalLeft);
     495            return { splitData.length, splitData.logicalWidth };
     496        };
     497        // With arbitrary breaking there's always a valid breaking position (even if it is before the first position).
     498        return tryBreakingAtArbitraryPosition();
     499    }
    486500    return { };
    487501}
  • trunk/Source/WebCore/layout/inlineformatting/InlineContentBreaker.h

    r273556 r274008  
    125125
    126126    enum class WordBreakRule {
    127         NoBreak,
    128         AtArbitraryPosition,
    129         OnlyHyphenationAllowed
     127        AtArbitraryPosition        = 1 << 0,
     128        AtHyphenationOpportunities = 1 << 1
    130129    };
    131     WordBreakRule wordBreakBehavior(const RenderStyle&, bool hasWrapOpportunityAtPreviousPosition) const;
     130    OptionSet<WordBreakRule> wordBreakBehavior(const RenderStyle&, bool hasWrapOpportunityAtPreviousPosition) const;
    132131    bool shouldKeepEndOfLineWhitespace(const ContinuousContent&) const;
    133132
Note: See TracChangeset for help on using the changeset viewer.