Changeset 252972 in webkit


Ignore:
Timestamp:
Dec 1, 2019 3:14:38 PM (4 years ago)
Author:
Alan Bujtas
Message:

[LFC][IFC] Trim trailing letter spacing.
https://bugs.webkit.org/show_bug.cgi?id=204731
<rdar://problem/57545763>

Reviewed by Antti Koivisto.

Refactor trailing trimmable content to support partial, non-whitespace trimmable content e.g. letter spacing.
https://drafts.csswg.org/css-text-3/#letter-spacing-property -> UAs therefore must not append letter spacing to the right or trailing edge of a line.

  • layout/inlineformatting/InlineLineBreaker.cpp:

(WebCore::Layout::LineBreaker::breakingContextForInlineContent):
(WebCore::Layout::LineBreaker::Content::append):
(WebCore::Layout::LineBreaker::Content::reset):
(WebCore::Layout::LineBreaker::Content::TrailingTrimmableContent::reset):
(WebCore::Layout::LineBreaker::Content::hasNonWhitespaceOrInlineBox const): Deleted.
(WebCore::Layout::LineBreaker::Content::trailingTrimmableWidth const): Deleted.

  • layout/inlineformatting/InlineLineBreaker.h:

(WebCore::Layout::LineBreaker::Content::isEmpty const):
(WebCore::Layout::LineBreaker::Content::nonTrimmableWidth const):
(WebCore::Layout::LineBreaker::Content::hasTrailingTrimmableContent const):
(WebCore::Layout::LineBreaker::Content::isTrailingContentFullyTrimmable const):

  • layout/inlineformatting/InlineLineBuilder.cpp:

(WebCore::Layout::LineBuilder::removeTrailingTrimmableContent):
(WebCore::Layout::LineBuilder::appendTextContent):
(WebCore::Layout::LineBuilder::TrimmableContent::append):
(WebCore::Layout::LineBuilder::InlineItemRun::removeTrailingLetterSpacing):

  • layout/inlineformatting/InlineLineBuilder.h:

(WebCore::Layout::LineBuilder::isTrailingContentFullyTrimmable const):
(WebCore::Layout::LineBuilder::TrimmableContent::isTrailingContentFullyTrimmable const):
(WebCore::Layout::LineBuilder::TrimmableContent::clear):

  • layout/inlineformatting/LineLayoutContext.cpp:

(WebCore::Layout::LineLayoutContext::processUncommittedContent):

Location:
trunk/Source/WebCore
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r252971 r252972  
     12019-12-01  Zalan Bujtas  <zalan@apple.com>
     2
     3        [LFC][IFC] Trim trailing letter spacing.
     4        https://bugs.webkit.org/show_bug.cgi?id=204731
     5        <rdar://problem/57545763>
     6
     7        Reviewed by Antti Koivisto.
     8
     9        Refactor trailing trimmable content to support partial, non-whitespace trimmable content e.g. letter spacing.
     10        https://drafts.csswg.org/css-text-3/#letter-spacing-property -> UAs therefore must not append letter spacing to the right or trailing edge of a line.
     11
     12        * layout/inlineformatting/InlineLineBreaker.cpp:
     13        (WebCore::Layout::LineBreaker::breakingContextForInlineContent):
     14        (WebCore::Layout::LineBreaker::Content::append):
     15        (WebCore::Layout::LineBreaker::Content::reset):
     16        (WebCore::Layout::LineBreaker::Content::TrailingTrimmableContent::reset):
     17        (WebCore::Layout::LineBreaker::Content::hasNonWhitespaceOrInlineBox const): Deleted.
     18        (WebCore::Layout::LineBreaker::Content::trailingTrimmableWidth const): Deleted.
     19        * layout/inlineformatting/InlineLineBreaker.h:
     20        (WebCore::Layout::LineBreaker::Content::isEmpty const):
     21        (WebCore::Layout::LineBreaker::Content::nonTrimmableWidth const):
     22        (WebCore::Layout::LineBreaker::Content::hasTrailingTrimmableContent const):
     23        (WebCore::Layout::LineBreaker::Content::isTrailingContentFullyTrimmable const):
     24        * layout/inlineformatting/InlineLineBuilder.cpp:
     25        (WebCore::Layout::LineBuilder::removeTrailingTrimmableContent):
     26        (WebCore::Layout::LineBuilder::appendTextContent):
     27        (WebCore::Layout::LineBuilder::TrimmableContent::append):
     28        (WebCore::Layout::LineBuilder::InlineItemRun::removeTrailingLetterSpacing):
     29        * layout/inlineformatting/InlineLineBuilder.h:
     30        (WebCore::Layout::LineBuilder::isTrailingContentFullyTrimmable const):
     31        (WebCore::Layout::LineBuilder::TrimmableContent::isTrailingContentFullyTrimmable const):
     32        (WebCore::Layout::LineBuilder::TrimmableContent::clear):
     33        * layout/inlineformatting/LineLayoutContext.cpp:
     34        (WebCore::Layout::LineLayoutContext::processUncommittedContent):
     35
    1362019-12-01  Simon Fraser  <simon.fraser@apple.com>
    237
  • trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp

    r252958 r252972  
    5050}
    5151
    52 LineBreaker::BreakingContext LineBreaker::breakingContextForInlineContent(const Content& content, LayoutUnit availableWidth, LayoutUnit trailingTrimmableWidth,  bool lineIsEmpty)
    53 {
    54     ASSERT(!content.isEmpty());
    55     if (content.width() <= availableWidth)
     52LineBreaker::BreakingContext LineBreaker::breakingContextForInlineContent(const Content& newContent, LayoutUnit availableWidth, bool lineHasFullyTrimmableTrailingContent,  bool lineIsEmpty)
     53{
     54    ASSERT(!newContent.isEmpty());
     55    if (newContent.width() <= availableWidth)
    5656        return { BreakingContext::ContentBreak::Keep, { } };
    57     // Check if it fits without the trailing trimmable content.
    58     if (!TextUtil::shouldPreserveTrailingWhitespace(content.runs()[0].inlineItem.style())) {
    59         auto adjustedAvailableWidth = availableWidth;
    60         auto adjustedContentWidth = content.width() - content.trailingTrimmableWidth();
    61         // When the uncommitted content has trailing trimmable width, it also means that it does not have any other non-trimmable _content_ ([text][ ] <- [text] is on commit boundary)
    62         // so placing it on the line with trimmable trailing content means that we'v got adjacent trimmable content.
    63         // [text][ ] <- existing content on the line with trailing trimmable
    64         // [container start][ ][container end] <- new content
    65         // <- adjacent trimmable _content_ [text][][container start][][container end]
    66         if (trailingTrimmableWidth && !content.hasNonWhitespaceOrInlineBox())
    67             adjustedAvailableWidth += trailingTrimmableWidth;
    68 
    69         if (adjustedContentWidth <= adjustedAvailableWidth)
     57    if (newContent.hasTrailingTrimmableContent()) {
     58        // First check if the content fits without the trailing trimmable part.
     59        if (newContent.nonTrimmableWidth() <= availableWidth)
    7060            return { BreakingContext::ContentBreak::Keep, { } };
    71     }
    72 
    73     if (content.hasTextContentOnly()) {
    74         auto& runs = content.runs();
     61        // Now check if we can trim the line too.
     62        if (lineHasFullyTrimmableTrailingContent && newContent.isTrailingContentFullyTrimmable()) {
     63            // If this new content is fully trimmable, it shoud surely fit.
     64            return { BreakingContext::ContentBreak::Keep, { } };
     65        }
     66    }
     67
     68    if (newContent.hasTextContentOnly()) {
     69        auto& runs = newContent.runs();
    7570        if (auto trailingPartialContent = wordBreakingBehavior(runs, availableWidth))
    7671            return { BreakingContext::ContentBreak::Split, trailingPartialContent };
     
    261256void LineBreaker::Content::append(const InlineItem& inlineItem, LayoutUnit logicalWidth)
    262257{
     258    ASSERT(!inlineItem.isFloat());
    263259    ASSERT(inlineItem.isForcedLineBreak() || !isAtContentBoundary(inlineItem, *this));
    264260    m_continousRuns.append({ inlineItem, logicalWidth });
    265261    m_width += logicalWidth;
     262    // Figure out the trailing trimmable state.
     263    if (inlineItem.isBox() || inlineItem.isForcedLineBreak())
     264        m_trailingTrimmableContent.reset();
     265    else if (inlineItem.isText()) {
     266        auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
     267        auto isFullyTrimmable = [&] {
     268            return inlineTextItem.isWhitespace() && !TextUtil::shouldPreserveTrailingWhitespace(inlineTextItem.style());
     269        };
     270        if (isFullyTrimmable()) {
     271            m_trailingTrimmableContent.width += logicalWidth;
     272            m_trailingTrimmableContent.isFullyTrimmable = true;
     273        } else if (auto trimmableWidth = inlineTextItem.style().letterSpacing()) {
     274            m_trailingTrimmableContent.width = trimmableWidth;
     275            m_trailingTrimmableContent.isFullyTrimmable = false;
     276        } else
     277            m_trailingTrimmableContent.reset();
     278    }
    266279}
    267280
     
    269282{
    270283    m_continousRuns.clear();
     284    m_trailingTrimmableContent.reset();
    271285    m_width = 0;
    272286}
     
    277291        m_width -= m_continousRuns[i].logicalWidth;
    278292    m_continousRuns.shrink(newSize);
    279 }
    280 
    281 bool LineBreaker::Content::hasNonWhitespaceOrInlineBox() const
    282 {
    283     // <span><img></span> is considered a inline box run even with the [container start][container end] inline items.
    284     for (auto& run : m_continousRuns) {
    285         auto& inlineItem = run.inlineItem;
    286         if (inlineItem.isContainerStart() || inlineItem.isContainerEnd() || inlineItem.isForcedLineBreak())
    287             continue;
    288         return !is<InlineTextItem>(inlineItem) || !downcast<InlineTextItem>(inlineItem).isWhitespace();
    289     }
    290     return false;
    291293}
    292294
     
    304306}
    305307
    306 LayoutUnit LineBreaker::Content::trailingTrimmableWidth() const
    307 {
    308     LayoutUnit trailingWhitespaceWidth;
    309     for (auto i = m_continousRuns.size(); i--;) {
    310         auto& inlineItem = m_continousRuns[i].inlineItem;
    311         if (inlineItem.isContainerStart() || inlineItem.isContainerEnd())
    312             continue;
    313         if (inlineItem.isText() && downcast<InlineTextItem>(inlineItem).isWhitespace()) {
    314             trailingWhitespaceWidth += m_continousRuns[i].logicalWidth;
    315             continue;
    316         }
    317         // Can't have [text content][whitespace] as [whitespace] is always a commit boundary when it is adjacent to some other inline content. See UncommittedContent::add.
    318         ASSERT(!trailingWhitespaceWidth);
    319         return trailingWhitespaceWidth;
    320     }
    321     return trailingWhitespaceWidth;
    322 }
     308void LineBreaker::Content::TrailingTrimmableContent::reset()
     309{
     310    isFullyTrimmable = false;
     311    width = { };
     312}
     313
    323314
    324315}
  • trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h

    r252958 r252972  
    7575        const RunList& runs() const { return m_continousRuns; }
    7676        bool isEmpty() const { return m_continousRuns.isEmpty(); }
    77         bool hasNonWhitespaceOrInlineBox() const;
    7877        bool hasTextContentOnly() const;
    7978        unsigned size() const { return m_continousRuns.size(); }
    8079        LayoutUnit width() const { return m_width; }
    81         LayoutUnit trailingTrimmableWidth() const;
     80        LayoutUnit nonTrimmableWidth() const { return m_width - m_trailingTrimmableContent.width; }
     81
     82        bool hasTrailingTrimmableContent() const { return !!m_trailingTrimmableContent.width; }
     83        bool isTrailingContentFullyTrimmable() const { return m_trailingTrimmableContent.isFullyTrimmable; }
    8284
    8385    private:
    8486        RunList m_continousRuns;
     87        struct TrailingTrimmableContent {
     88            void reset();
     89
     90            bool isFullyTrimmable { false };
     91            LayoutUnit width;
     92        };
     93        TrailingTrimmableContent m_trailingTrimmableContent;
    8594        LayoutUnit m_width;
    8695    };
    8796
    88     BreakingContext breakingContextForInlineContent(const Content&, LayoutUnit availableWidth, LayoutUnit trailingTrimmableWidth, bool lineIsEmpty);
     97    BreakingContext breakingContextForInlineContent(const Content&, LayoutUnit availableWidth, bool lineHasFullyTrimmableTrailingContent, bool lineIsEmpty);
    8998    bool shouldWrapFloatBox(LayoutUnit floatLogicalWidth, LayoutUnit availableWidth, bool lineIsEmpty);
    9099
  • trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp

    r252967 r252972  
    384384        auto& trimmableRun = m_inlineItemRuns[trimmableRunIndex];
    385385        ASSERT(trimmableRun.isText());
    386         // FIXME: We might need to be able to differentiate between trimmed and collapsed runs.
    387         trimmableRun.setCollapsesToZeroAdvanceWidth();
     386        if (trimmableRun.isWhitespace())
     387            trimmableRun.setCollapsesToZeroAdvanceWidth();
     388        else
     389            trimmableRun.removeTrailingLetterSpacing();
    388390    }
    389391    m_lineBox.shrinkHorizontally(m_trimmableContent.width());
    390392    m_trimmableContent.clear();
    391393    // If we trimmed the first visible run on the line, we need to re-check the visibility status.
    392     ASSERT(m_lineIsVisuallyEmptyBeforeTrimmableContent.hasValue());
    393     if (*m_lineIsVisuallyEmptyBeforeTrimmableContent) {
     394    if (m_lineIsVisuallyEmptyBeforeTrimmableContent) {
    394395        // Just because the line was visually empty before the trimmed content, it does not necessarily mean it is still visually empty.
    395396        // <span>  </span><span style="padding-left: 10px"></span>  <- non-empty
     
    404405        if (lineIsVisuallyEmpty())
    405406            m_lineBox.setIsConsideredEmpty();
    406         m_lineIsVisuallyEmptyBeforeTrimmableContent = { };
    407     }
     407    }
     408    m_lineIsVisuallyEmptyBeforeTrimmableContent = { };
    408409}
    409410
     
    466467void LineBuilder::appendTextContent(const InlineTextItem& inlineItem, LayoutUnit logicalWidth)
    467468{
    468     auto isTrimmable = !shouldPreserveTrailingContent(inlineItem);
    469     if (!isTrimmable)
    470         m_trimmableContent.clear();
    471 
    472469    auto willCollapseCompletely = [&] {
    473470        // Empty run.
     
    513510
    514511    auto lineRunWidth = lineRun.logicalRect().width();
    515     if (isTrimmable) {
     512    // Trailing whitespace content is fully trimmable so as the trailing letter space.
     513    auto isFullyTrimmable = !shouldPreserveTrailingContent(inlineItem);
     514    auto isPartiallyTrimmable = !inlineItem.isWhitespace() && inlineItem.style().letterSpacing();
     515    auto isTrimmable = isFullyTrimmable || isPartiallyTrimmable;
     516    // Reset the trimmable content if needed.
     517    if (!isTrimmable || isPartiallyTrimmable || (isFullyTrimmable && !m_trimmableContent.isTrailingContentFullyTrimmable()))
     518        m_trimmableContent.clear();
     519    if (isFullyTrimmable) {
    516520        // If we ever trim this content, we need to know if the line visibility state needs to be recomputed.
    517521        if (m_trimmableContent.isEmpty())
    518522            m_lineIsVisuallyEmptyBeforeTrimmableContent = isVisuallyEmpty();
    519         m_trimmableContent.append(lineRunWidth, m_inlineItemRuns.size());
    520     }
     523        m_trimmableContent.append(lineRunWidth, m_inlineItemRuns.size(), TrimmableContent::IsFullyTrimmable::Yes);
     524    } else if (isPartiallyTrimmable)
     525        m_trimmableContent.append(LayoutUnit { inlineItem.style().letterSpacing() }, m_inlineItemRuns.size(), TrimmableContent::IsFullyTrimmable::No);
    521526
    522527    m_lineBox.expandHorizontally(lineRunWidth);
     
    709714}
    710715
    711 void LineBuilder::TrimmableContent::append(LayoutUnit itemRunWidth, size_t runIndex)
     716void LineBuilder::TrimmableContent::append(LayoutUnit itemRunWidth, size_t runIndex, IsFullyTrimmable isFullyTrimmable)
    712717{
    713718    // word-spacing could very well be negative, but it does not mean that the line gains that much extra space when the content is trimmed.
    714719    itemRunWidth = std::max(0_lu, itemRunWidth);
    715720    m_width += itemRunWidth;
     721    m_lastRunIsFullyTrimmable = isFullyTrimmable == IsFullyTrimmable::Yes;
    716722    m_runIndexes.append(runIndex);
    717723}
     
    730736}
    731737
     738void LineBuilder::InlineItemRun::removeTrailingLetterSpacing()
     739{
     740    ASSERT(m_inlineItem.style().letterSpacing());
     741    m_logicalRect.expandHorizontally(LayoutUnit { -m_inlineItem.style().letterSpacing() });
     742}
     743
    732744}
    733745}
  • trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h

    r252928 r252972  
    6666
    6767    LayoutUnit trailingTrimmableWidth() const { return m_trimmableContent.width(); }
     68    bool isTrailingContentFullyTrimmable() const { return m_trimmableContent.isTrailingContentFullyTrimmable(); }
    6869
    6970    const LineBox& lineBox() const { return m_lineBox; }
     
    171172        bool isCollapsed() const { return m_isCollapsed; }
    172173
     174        void removeTrailingLetterSpacing();
    173175        void setCollapsesToZeroAdvanceWidth();
    174176        bool isCollapsedToZeroAdvanceWidth() const { return m_collapsedToZeroAdvanceWidth; }
     
    188190
    189191    struct TrimmableContent {
    190         void append(LayoutUnit itemRunWidth, size_t runIndex);
     192        enum class IsFullyTrimmable { No, Yes };
     193        void append(LayoutUnit itemRunWidth, size_t runIndex, IsFullyTrimmable);
    191194        void clear();
    192195
     
    194197        Vector<size_t, 5>& runIndexes() { return m_runIndexes; }
    195198        bool isEmpty() const { return m_runIndexes.isEmpty(); }
     199        bool isTrailingContentFullyTrimmable() const { return m_lastRunIsFullyTrimmable; }
    196200
    197201    private:
    198202        Vector<size_t, 5> m_runIndexes;
    199203        LayoutUnit m_width;
     204        bool m_lastRunIsFullyTrimmable { false };
    200205    };
    201206
     
    216221    m_runIndexes.clear();
    217222    m_width = { };
     223    m_lastRunIsFullyTrimmable = false;
    218224}
    219225
  • trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp

    r252967 r252972  
    210210    if (shouldDisableHyphenation())
    211211        lineBreaker.setHyphenationDisabled();
    212     auto breakingContext = lineBreaker.breakingContextForInlineContent(m_uncommittedContent, line.availableWidth(), line.trailingTrimmableWidth(), lineIsConsideredEmpty);
     212    auto breakingContext = lineBreaker.breakingContextForInlineContent(m_uncommittedContent, line.availableWidth(), line.isTrailingContentFullyTrimmable(), lineIsConsideredEmpty);
    213213    // The uncommitted content can fully, partially fit the current line (commit/partial commit) or not at all (reset).
    214214    if (breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Keep)
Note: See TracChangeset for help on using the changeset viewer.