| Differences between
and this patch
- a/Source/WebCore/ChangeLog +46 lines
Lines 1-3 a/Source/WebCore/ChangeLog_sec1
1
2020-08-25  Zalan Bujtas  <zalan@apple.com>
2
3
        [LFC][IFC] Move inline run alignment logic to a dedicated class
4
        https://bugs.webkit.org/show_bug.cgi?id=214527
5
        <rdar://problem/66115796>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        This is in preparation for moving line alignment logic to LineBox (LineBuilder will not going to be responsible for aligning runs on the line).
10
11
        * layout/FormattingContext.h:
12
        * layout/inlineformatting/InlineFormattingContext.h:
13
        * layout/inlineformatting/InlineLineBuilder.cpp:
14
        (WebCore::Layout::LineContentAligner::formattingContext const):
15
        (WebCore::Layout::LineContentAligner::layoutState const):
16
        (WebCore::Layout::LineContentAligner::LineContentAligner):
17
        (WebCore::Layout::LineContentAligner::alignHorizontally):
18
        (WebCore::Layout::LineContentAligner::alignVertically):
19
        (WebCore::Layout::LineContentAligner::justifyRuns):
20
        (WebCore::Layout::LineContentAligner::adjustBaselineAndLineHeight):
21
        (WebCore::Layout::LineContentAligner::collectHangingContent const):
22
        (WebCore::Layout::LineContentAligner::runContentHeight const):
23
        (WebCore::Layout::LineBuilder::LineBuilder):
24
        (WebCore::Layout::LineBuilder::~LineBuilder):
25
        (WebCore::Layout::LineBuilder::initialize):
26
        (WebCore::Layout::LineBuilder::clear):
27
        (WebCore::Layout::LineBuilder::close):
28
        (WebCore::Layout::LineBuilder::alignContentVertically): Deleted.
29
        (WebCore::Layout::LineBuilder::justifyRuns): Deleted.
30
        (WebCore::Layout::LineBuilder::alignHorizontally): Deleted.
31
        (WebCore::Layout::LineBuilder::collectHangingContent): Deleted.
32
        (WebCore::Layout::LineBuilder::adjustBaselineAndLineHeight): Deleted.
33
        (WebCore::Layout::LineBuilder::runContentHeight const): Deleted.
34
        (WebCore::Layout::LineBuilder::layoutState const): Deleted.
35
        (WebCore::Layout::LineBuilder::root const): Deleted.
36
        * layout/inlineformatting/InlineLineBuilder.h:
37
        (WebCore::Layout::LineBuilder::availableWidth const):
38
        (WebCore::Layout::LineBuilder::contentLogicalRight const):
39
        (WebCore::Layout::LineBuilder::logicalTop const): Deleted.
40
        (WebCore::Layout::LineBuilder::logicalBottom const): Deleted.
41
        (WebCore::Layout::LineBuilder::logicalLeft const): Deleted.
42
        (WebCore::Layout::LineBuilder::logicalRight const): Deleted.
43
        (WebCore::Layout::LineBuilder::logicalWidth const): Deleted.
44
        (WebCore::Layout::LineBuilder::logicalHeight const): Deleted.
45
        (WebCore::Layout::LineBuilder::baseline const): Deleted.
46
1
2020-08-25  Zalan Bujtas  <zalan@apple.com>
47
2020-08-25  Zalan Bujtas  <zalan@apple.com>
2
48
3
        [LFC][IFC] Do not move the runs out of the LineBuilder while closing the line
49
        [LFC][IFC] Do not move the runs out of the LineBuilder while closing the line
- a/Source/WebCore/layout/FormattingContext.h -1 / +1 lines
Lines 109-118 public: a/Source/WebCore/layout/FormattingContext.h_sec1
109
    const Display::Box& geometryForBox(const Box&, Optional<EscapeReason> = WTF::nullopt) const;
109
    const Display::Box& geometryForBox(const Box&, Optional<EscapeReason> = WTF::nullopt) const;
110
    const ContainerBox& root() const { return *m_root; }
110
    const ContainerBox& root() const { return *m_root; }
111
111
112
    LayoutState& layoutState() const;
112
protected:
113
protected:
113
    using LayoutQueue = Vector<const Box*>;
114
    using LayoutQueue = Vector<const Box*>;
114
115
115
    LayoutState& layoutState() const;
116
    const FormattingState& formattingState() const { return m_formattingState; }
116
    const FormattingState& formattingState() const { return m_formattingState; }
117
    FormattingState& formattingState() { return m_formattingState; }
117
    FormattingState& formattingState() { return m_formattingState; }
118
118
- a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp -266 / +281 lines
Lines 64-142 void HangingContent::reset() a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec1
64
    m_width =  0;
64
    m_width =  0;
65
}
65
}
66
66
67
LineBuilder::LineBuilder(const InlineFormattingContext& inlineFormattingContext, Optional<TextAlignMode> horizontalAlignment, IntrinsicSizing intrinsicSizing)
67
class LineContentAligner {
68
    : m_inlineFormattingContext(inlineFormattingContext)
68
public:
69
    , m_trimmableTrailingContent(m_runs)
69
    LineContentAligner(const InlineFormattingContext&, LineBox&, LineBuilder::RunList&, InlineLayoutUnit availableWidth);
70
    , m_horizontalAlignment(horizontalAlignment)
71
    , m_isIntrinsicSizing(intrinsicSizing == IntrinsicSizing::Yes)
72
    , m_shouldIgnoreTrailingLetterSpacing(RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled())
73
{
74
}
75
70
76
LineBuilder::~LineBuilder()
71
    void alignHorizontally(TextAlignMode, LineBuilder::IsLastLineWithInlineContent);
77
{
72
    void alignVertically();
78
}
73
    void adjustBaselineAndLineHeight();
79
74
80
void LineBuilder::initialize(const Constraints& constraints)
75
private:
81
{
76
    void justifyRuns(InlineLayoutUnit availableWidth);
82
    m_lineLogicalWidth = constraints.availableLogicalWidth;
77
    HangingContent collectHangingContent(LineBuilder::IsLastLineWithInlineContent) const;
83
    m_hasIntrusiveFloat = constraints.lineIsConstrainedByFloat;
78
    InlineLayoutUnit runContentHeight(const LineBuilder::Run&) const;
84
    auto initialLineHeight = constraints.lineHeight;
85
    auto lineRect = Display::InlineRect { constraints.logicalTopLeft, 0_lu, initialLineHeight };
86
    auto ascentAndDescent = LineBuilder::halfLeadingMetrics(root().style().fontMetrics(), initialLineHeight);
87
    m_lineBox = LineBox { lineRect, ascentAndDescent };
88
    if (!layoutState().inNoQuirksMode())
89
        m_initialStrut = AscentAndDescent { ascentAndDescent.ascent, initialLineHeight - ascentAndDescent.ascent };
90
    else
91
        m_initialStrut = { };
92
    clear();
93
#if ASSERT_ENABLED
94
    m_isClosed = false;
95
#endif
96
}
97
79
98
void LineBuilder::clear()
80
    const InlineFormattingContext& formattingContext() const { return m_inlineFormattingContext; }
81
    LayoutState& layoutState() const { return formattingContext().layoutState(); }
82
83
    const InlineFormattingContext& m_inlineFormattingContext;
84
    LineBox& m_lineBox;
85
    LineBuilder::RunList& m_runs;
86
    InlineLayoutUnit m_availableWidth;
87
};
88
89
LineContentAligner::LineContentAligner(const InlineFormattingContext& inlineFormattingContext, LineBox& lineBox, LineBuilder::RunList& runs, InlineLayoutUnit availableWidth)
90
    : m_inlineFormattingContext(inlineFormattingContext)
91
    , m_lineBox(lineBox)
92
    , m_runs(runs)
93
    , m_availableWidth(availableWidth)
99
{
94
{
100
    m_lineBox.setLogicalWidth({ });
101
    m_lineBox.setIsConsideredEmpty();
102
    m_runs.clear();
103
    m_trimmableTrailingContent.reset();
104
    m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent = { };
105
}
95
}
106
96
107
void LineBuilder::close(IsLastLineWithInlineContent isLastLineWithInlineContent)
97
void LineContentAligner::alignHorizontally(TextAlignMode horizontalAlignment, LineBuilder::IsLastLineWithInlineContent isLastLine)
108
{
98
{
109
#if ASSERT_ENABLED
99
    auto hangingContent = collectHangingContent(isLastLine);
110
    m_isClosed = true;
100
    auto availableWidth = m_availableWidth + hangingContent.width();
111
#endif
101
    if (m_runs.isEmpty() || availableWidth <= 0)
112
    // 1. Remove trimmable trailing content.
113
    // 2. Align merged runs both vertically and horizontally.
114
    removeTrailingTrimmableContent();
115
    visuallyCollapsePreWrapOverflowContent();
116
    if (m_isIntrinsicSizing)
117
        return;
102
        return;
118
103
119
    auto hangingContent = collectHangingContent(isLastLineWithInlineContent);
104
    if (horizontalAlignment == TextAlignMode::Justify) {
120
    adjustBaselineAndLineHeight();
105
        justifyRuns(availableWidth);
121
    if (isVisuallyEmpty()) {
106
        return;
122
        m_lineBox.resetAlignmentBaseline();
123
        m_lineBox.setLogicalHeight({ });
124
    }
125
    // Remove descent when all content is baseline aligned but none of them have descent.
126
    if (formattingContext().quirks().lineDescentNeedsCollapsing(m_runs)) {
127
        m_lineBox.shrinkVertically(m_lineBox.ascentAndDescent().descent);
128
        m_lineBox.resetDescent();
129
    }
107
    }
130
    alignContentVertically();
108
131
    alignHorizontally(hangingContent, isLastLineWithInlineContent);
109
    auto adjustmentForAlignment = [&] (auto availableWidth) -> Optional<InlineLayoutUnit> {
110
        switch (horizontalAlignment) {
111
        case TextAlignMode::Left:
112
        case TextAlignMode::WebKitLeft:
113
        case TextAlignMode::Start:
114
            return { };
115
        case TextAlignMode::Right:
116
        case TextAlignMode::WebKitRight:
117
        case TextAlignMode::End:
118
            return std::max<InlineLayoutUnit>(availableWidth, 0);
119
        case TextAlignMode::Center:
120
        case TextAlignMode::WebKitCenter:
121
            return std::max<InlineLayoutUnit>(availableWidth / 2, 0);
122
        case TextAlignMode::Justify:
123
            ASSERT_NOT_REACHED();
124
            break;
125
        }
126
        ASSERT_NOT_REACHED();
127
        return { };
128
    };
129
130
    auto adjustment = adjustmentForAlignment(availableWidth);
131
    if (!adjustment)
132
        return;
133
    // Horizontal alignment means that we not only adjust the runs but also make sure
134
    // that the line box is aligned as well
135
    // e.g. <div style="text-align: center; width: 100px;">centered text</div> : the line box will also be centered
136
    // as opposed to start at 0px all the way to [centered text] run's right edge.
137
    m_lineBox.moveHorizontally(*adjustment);
138
    for (auto& run : m_runs)
139
        run.moveHorizontally(*adjustment);
132
}
140
}
133
141
134
void LineBuilder::alignContentVertically()
142
void LineContentAligner::alignVertically()
135
{
143
{
136
    ASSERT(!m_isIntrinsicSizing);
137
    auto scrollableOverflowRect = m_lineBox.logicalRect();
144
    auto scrollableOverflowRect = m_lineBox.logicalRect();
138
    for (auto& run : m_runs) {
145
    for (auto& run : m_runs) {
139
        InlineLayoutUnit logicalTop = 0;
146
        auto logicalTop = InlineLayoutUnit { };
140
        auto& layoutBox = run.layoutBox();
147
        auto& layoutBox = run.layoutBox();
141
        auto verticalAlign = layoutBox.style().verticalAlign();
148
        auto verticalAlign = layoutBox.style().verticalAlign();
142
        auto ascent = layoutBox.style().fontMetrics().ascent();
149
        auto ascent = layoutBox.style().fontMetrics().ascent();
Lines 144-153 void LineBuilder::alignContentVertically() a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec2
144
        switch (verticalAlign) {
151
        switch (verticalAlign) {
145
        case VerticalAlign::Baseline:
152
        case VerticalAlign::Baseline:
146
            if (run.isLineBreak() || run.isText())
153
            if (run.isLineBreak() || run.isText())
147
                logicalTop = baseline() - ascent;
154
                logicalTop = m_lineBox.alignmentBaseline() - ascent;
148
            else if (run.isContainerStart()) {
155
            else if (run.isContainerStart()) {
149
                auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
156
                auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
150
                logicalTop = baseline() - ascent - boxGeometry.borderTop() - boxGeometry.paddingTop().valueOr(0);
157
                logicalTop = m_lineBox.alignmentBaseline() - ascent - boxGeometry.borderTop() - boxGeometry.paddingTop().valueOr(0);
151
            } else if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
158
            } else if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
152
                auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
159
                auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
153
                // Spec makes us generate at least one line -even if it is empty.
160
                // Spec makes us generate at least one line -even if it is empty.
Lines 166-182 void LineBuilder::alignContentVertically() a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec3
166
                //
173
                //
167
                auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
174
                auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
168
                auto baselineFromMarginBox = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0) + inlineBlockBaseline;
175
                auto baselineFromMarginBox = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0) + inlineBlockBaseline;
169
                logicalTop = baseline() - baselineFromMarginBox;
176
                logicalTop = m_lineBox.alignmentBaseline() - baselineFromMarginBox;
170
            } else {
177
            } else {
171
                auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
178
                auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
172
                logicalTop = baseline() - (boxGeometry.verticalBorder() + boxGeometry.verticalPadding().valueOr(0_lu) + run.logicalRect().height() + boxGeometry.marginAfter());
179
                logicalTop = m_lineBox.alignmentBaseline() - (boxGeometry.verticalBorder() + boxGeometry.verticalPadding().valueOr(0_lu) + run.logicalRect().height() + boxGeometry.marginAfter());
173
            }
180
            }
174
            break;
181
            break;
175
        case VerticalAlign::Top:
182
        case VerticalAlign::Top:
176
            logicalTop = 0_lu;
183
            logicalTop = 0_lu;
177
            break;
184
            break;
178
        case VerticalAlign::Bottom:
185
        case VerticalAlign::Bottom:
179
            logicalTop = logicalBottom() - run.logicalRect().height();
186
            logicalTop = m_lineBox.logicalBottom() - run.logicalRect().height();
180
            break;
187
            break;
181
        default:
188
        default:
182
            ASSERT_NOT_IMPLEMENTED_YET();
189
            ASSERT_NOT_IMPLEMENTED_YET();
Lines 186-203 void LineBuilder::alignContentVertically() a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec4
186
        // Adjust scrollable overflow if the run overflows the line.
193
        // Adjust scrollable overflow if the run overflows the line.
187
        scrollableOverflowRect.expandVerticallyToContain(run.logicalRect());
194
        scrollableOverflowRect.expandVerticallyToContain(run.logicalRect());
188
        // Convert runs from relative to the line top/left to the formatting root's border box top/left.
195
        // Convert runs from relative to the line top/left to the formatting root's border box top/left.
189
        run.moveVertically(this->logicalTop());
196
        run.moveVertically(m_lineBox.logicalTop());
190
        run.moveHorizontally(this->logicalLeft());
197
        run.moveHorizontally(m_lineBox.logicalLeft());
191
    }
198
    }
192
    m_lineBox.setScrollableOverflow(scrollableOverflowRect);
199
    m_lineBox.setScrollableOverflow(scrollableOverflowRect);
193
}
200
}
194
201
195
void LineBuilder::justifyRuns(InlineLayoutUnit availableWidth)
202
void LineContentAligner::justifyRuns(InlineLayoutUnit availableWidth)
196
{
203
{
197
    ASSERT(availableWidth > 0);
204
    ASSERT(availableWidth > 0);
198
    // Collect the expansion opportunity numbers and find the last run with content.
205
    // Collect the expansion opportunity numbers and find the last run with content.
199
    auto expansionOpportunityCount = 0;
206
    auto expansionOpportunityCount = 0;
200
    Run* lastRunWithContent = nullptr;
207
    LineBuilder::Run* lastRunWithContent = nullptr;
201
    for (auto& run : m_runs) {
208
    for (auto& run : m_runs) {
202
        expansionOpportunityCount += run.expansionOpportunityCount();
209
        expansionOpportunityCount += run.expansionOpportunityCount();
203
        if (run.isText() || run.isBox())
210
        if (run.isText() || run.isBox())
Lines 229-242 void LineBuilder::justifyRuns(InlineLayoutUnit availableWidth) a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec5
229
    }
236
    }
230
}
237
}
231
238
232
void LineBuilder::alignHorizontally(const HangingContent& hangingContent, IsLastLineWithInlineContent isLastLine)
239
void LineContentAligner::adjustBaselineAndLineHeight()
233
{
240
{
234
    ASSERT(!m_isIntrinsicSizing);
241
    unsigned inlineContainerNestingLevel = 0;
242
    auto hasSeenDirectTextOrLineBreak = false;
243
    for (auto& run : m_runs) {
244
        auto& layoutBox = run.layoutBox();
245
        auto& style = layoutBox.style();
235
246
236
    auto availableWidth = this->availableWidth() + hangingContent.width();
247
        run.setLogicalHeight(runContentHeight(run));
237
    if (m_runs.isEmpty() || availableWidth <= 0)
248
249
        if (run.isText() || run.isLineBreak()) {
250
            // For text content we set the baseline either through the initial strut (set by the formatting context root) or
251
            // through the inline container (start). Normally the text content itself does not stretch the line.
252
            // if (!m_initialStrut) {
253
            //     // We are in standards mode where the baseline and line height are explict.
254
            //     continue;
255
            // }
256
            if (inlineContainerNestingLevel) {
257
                // We've already adjusted the line height/baseline through the parent inline container. 
258
                continue;
259
            }
260
            if (hasSeenDirectTextOrLineBreak) {
261
                // e.g div>first text</div> or <div><span>nested<span>first direct text</div>.
262
                continue;
263
            }
264
            hasSeenDirectTextOrLineBreak = true;
265
            // We are in quirks mode where the font-metrics might change the line line height/baseline and this is the first text content on the line
266
            // outside of an inline container.
267
            // m_lineBox.setAscentIfGreater(m_initialStrut->ascent);
268
            // m_lineBox.setDescentIfGreater(m_initialStrut->descent);
269
            // m_lineBox.setLogicalHeightIfGreater(m_initialStrut->height());
270
            continue;
271
        }
272
273
        if (run.isContainerStart()) {
274
            ++inlineContainerNestingLevel;
275
            // Inline containers stretch the line by their font size.
276
            // Vertical margins, paddings and borders don't contribute to the line height.
277
            auto& fontMetrics = style.fontMetrics();
278
            if (style.verticalAlign() == VerticalAlign::Baseline) {
279
                auto halfLeading = LineBuilder::halfLeadingMetrics(fontMetrics, style.computedLineHeight());
280
                // Both halfleading ascent and descent could be negative (tall font vs. small line-height value)
281
                if (halfLeading.descent > 0)
282
                    m_lineBox.setDescentIfGreater(halfLeading.descent);
283
                if (halfLeading.ascent > 0)
284
                    m_lineBox.setAscentIfGreater(halfLeading.ascent);
285
                m_lineBox.setLogicalHeightIfGreater(m_lineBox.ascentAndDescent().height());
286
            } else
287
                m_lineBox.setLogicalHeightIfGreater(fontMetrics.height());
288
            continue;
289
        }
290
291
        if (run.isContainerEnd()) {
292
            // The line's baseline and height have already been adjusted at ContainerStart.
293
            ASSERT(inlineContainerNestingLevel);
294
            --inlineContainerNestingLevel;
295
            continue;
296
        }
297
298
        if (run.isBox()) {
299
            auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
300
            auto marginBoxHeight = boxGeometry.marginBoxHeight();
301
302
            switch (style.verticalAlign()) {
303
            case VerticalAlign::Baseline: {
304
                if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
305
                    // Inline-blocks with inline content always have baselines.
306
                    auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
307
                    // There has to be at least one line -even if it is empty.
308
                    auto& lastLineBox = formattingState.displayInlineContent()->lineBoxes.last();
309
                    auto beforeHeight = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0);
310
                    m_lineBox.setAlignmentBaselineIfGreater(beforeHeight + lastLineBox.baseline());
311
                    m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
312
                } else {
313
                    // Non inline-block boxes sit on the baseline (including their bottom margin).
314
                    m_lineBox.setAscentIfGreater(marginBoxHeight);
315
                    // Ignore negative descent (yes, negative descent is a thing).
316
                    m_lineBox.setLogicalHeightIfGreater(marginBoxHeight + std::max<InlineLayoutUnit>(0, m_lineBox.ascentAndDescent().descent));
317
                }
318
                break;
319
            }
320
            case VerticalAlign::Top:
321
                // Top align content never changes the baseline, it only pushes the bottom of the line further down.
322
                m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
323
                break;
324
            case VerticalAlign::Bottom: {
325
                // Bottom aligned, tall content pushes the baseline further down from the line top.
326
                auto lineLogicalHeight = m_lineBox.logicalHeight();
327
                if (marginBoxHeight > lineLogicalHeight) {
328
                    m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
329
                    m_lineBox.setAlignmentBaselineIfGreater(m_lineBox.alignmentBaseline() + (marginBoxHeight - lineLogicalHeight));
330
                }
331
                break;
332
            }
333
            default:
334
                ASSERT_NOT_IMPLEMENTED_YET();
335
                break;
336
            }
337
            continue;
338
        }
339
    }
340
}
341
342
HangingContent LineContentAligner::collectHangingContent(LineBuilder::IsLastLineWithInlineContent isLastLineWithInlineContent) const
343
{
344
    auto hangingContent = HangingContent { };
345
    if (isLastLineWithInlineContent == LineBuilder::IsLastLineWithInlineContent::Yes)
346
        hangingContent.setIsConditional();
347
    for (auto& run : WTF::makeReversedRange(m_runs)) {
348
        if (run.isContainerStart() || run.isContainerEnd())
349
            continue;
350
        if (run.isLineBreak()) {
351
            hangingContent.setIsConditional();
352
            continue;
353
        }
354
        if (!run.hasTrailingWhitespace())
355
            break;
356
        // Check if we have a preserved or hung whitespace.
357
        if (run.style().whiteSpace() != WhiteSpace::PreWrap)
358
            break;
359
        // This is either a normal or conditionally hanging trailing whitespace.
360
        hangingContent.expand(run.trailingWhitespaceWidth());
361
    }
362
    return hangingContent;
363
}
364
365
InlineLayoutUnit LineContentAligner::runContentHeight(const LineBuilder::Run& run) const
366
{
367
    auto& fontMetrics = run.style().fontMetrics();
368
    if (run.isText() || run.isLineBreak())
369
        return fontMetrics.height();
370
371
    if (run.isContainerStart() || run.isContainerEnd())
372
        return fontMetrics.height();
373
374
    auto& layoutBox = run.layoutBox();
375
    auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
376
    if (layoutBox.isReplacedBox() || layoutBox.isFloatingPositioned())
377
        return boxGeometry.contentBoxHeight();
378
379
    // Non-replaced inline box (e.g. inline-block). It looks a bit misleading but their margin box is considered the content height here.
380
    return boxGeometry.marginBoxHeight();
381
}
382
383
LineBuilder::LineBuilder(const InlineFormattingContext& inlineFormattingContext, Optional<TextAlignMode> horizontalAlignment, IntrinsicSizing intrinsicSizing)
384
    : m_inlineFormattingContext(inlineFormattingContext)
385
    , m_trimmableTrailingContent(m_runs)
386
    , m_horizontalAlignment(horizontalAlignment)
387
    , m_isIntrinsicSizing(intrinsicSizing == IntrinsicSizing::Yes)
388
    , m_shouldIgnoreTrailingLetterSpacing(RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled())
389
{
390
}
391
392
LineBuilder::~LineBuilder()
393
{
394
}
395
396
void LineBuilder::initialize(const Constraints& constraints)
397
{
398
    m_lineLogicalWidth = constraints.availableLogicalWidth;
399
    m_hasIntrusiveFloat = constraints.lineIsConstrainedByFloat;
400
    auto initialLineHeight = constraints.lineHeight;
401
    auto lineRect = Display::InlineRect { constraints.logicalTopLeft, 0_lu, initialLineHeight };
402
    auto ascentAndDescent = LineBuilder::halfLeadingMetrics(formattingContext().root().style().fontMetrics(), initialLineHeight);
403
    m_lineBox = LineBox { lineRect, ascentAndDescent };
404
    if (!formattingContext().layoutState().inNoQuirksMode())
405
        m_initialStrut = AscentAndDescent { ascentAndDescent.ascent, initialLineHeight - ascentAndDescent.ascent };
406
    else
407
        m_initialStrut = { };
408
    clear();
409
#if ASSERT_ENABLED
410
    m_isClosed = false;
411
#endif
412
}
413
414
void LineBuilder::clear()
415
{
416
    m_lineBox.setLogicalWidth({ });
417
    m_lineBox.setIsConsideredEmpty();
418
    m_runs.clear();
419
    m_trimmableTrailingContent.reset();
420
    m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent = { };
421
}
422
423
void LineBuilder::close(IsLastLineWithInlineContent isLastLine)
424
{
425
#if ASSERT_ENABLED
426
    m_isClosed = true;
427
#endif
428
    // 1. Remove trimmable trailing content.
429
    // 2. Align merged runs both vertically and horizontally.
430
    removeTrailingTrimmableContent();
431
    visuallyCollapsePreWrapOverflowContent();
432
    if (m_isIntrinsicSizing)
238
        return;
433
        return;
239
434
435
    auto contentAligner = LineContentAligner { formattingContext(), m_lineBox, m_runs, availableWidth() };
436
    contentAligner.adjustBaselineAndLineHeight();
437
    if (isVisuallyEmpty()) {
438
        m_lineBox.resetAlignmentBaseline();
439
        m_lineBox.setLogicalHeight({ });
440
    }
441
    // Remove descent when all content is baseline aligned but none of them have descent.
442
    if (formattingContext().quirks().lineDescentNeedsCollapsing(m_runs)) {
443
        m_lineBox.shrinkVertically(m_lineBox.ascentAndDescent().descent);
444
        m_lineBox.resetDescent();
445
    }
446
    contentAligner.alignVertically();
447
240
    auto computedHorizontalAlignment = [&] {
448
    auto computedHorizontalAlignment = [&] {
241
        ASSERT(m_horizontalAlignment);
449
        ASSERT(m_horizontalAlignment);
242
        if (m_horizontalAlignment != TextAlignMode::Justify)
450
        if (m_horizontalAlignment != TextAlignMode::Justify)
Lines 244-290 void LineBuilder::alignHorizontally(const HangingContent& hangingContent, IsLast a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec6
244
        // Text is justified according to the method specified by the text-justify property,
452
        // Text is justified according to the method specified by the text-justify property,
245
        // in order to exactly fill the line box. Unless otherwise specified by text-align-last,
453
        // in order to exactly fill the line box. Unless otherwise specified by text-align-last,
246
        // the last line before a forced break or the end of the block is start-aligned.
454
        // the last line before a forced break or the end of the block is start-aligned.
247
        if (m_runs.last().isLineBreak() || isLastLine == IsLastLineWithInlineContent::Yes)
455
        if (m_runs.last().isLineBreak() || isLastLine == LineBuilder::IsLastLineWithInlineContent::Yes)
248
            return TextAlignMode::Start;
456
            return TextAlignMode::Start;
249
        return TextAlignMode::Justify;
457
        return TextAlignMode::Justify;
250
    }();
251
252
    if (computedHorizontalAlignment == TextAlignMode::Justify) {
253
        justifyRuns(availableWidth);
254
        return;
255
    }
256
257
    auto adjustmentForAlignment = [] (auto horizontalAlignment, auto availableWidth) -> Optional<InlineLayoutUnit> {
258
        switch (horizontalAlignment) {
259
        case TextAlignMode::Left:
260
        case TextAlignMode::WebKitLeft:
261
        case TextAlignMode::Start:
262
            return { };
263
        case TextAlignMode::Right:
264
        case TextAlignMode::WebKitRight:
265
        case TextAlignMode::End:
266
            return std::max<InlineLayoutUnit>(availableWidth, 0);
267
        case TextAlignMode::Center:
268
        case TextAlignMode::WebKitCenter:
269
            return std::max<InlineLayoutUnit>(availableWidth / 2, 0);
270
        case TextAlignMode::Justify:
271
            ASSERT_NOT_REACHED();
272
            break;
273
        }
274
        ASSERT_NOT_REACHED();
275
        return { };
276
    };
458
    };
277
459
    contentAligner.alignHorizontally(computedHorizontalAlignment(), isLastLine);
278
    auto adjustment = adjustmentForAlignment(computedHorizontalAlignment, availableWidth);
279
    if (!adjustment)
280
        return;
281
    // Horizontal alignment means that we not only adjust the runs but also make sure
282
    // that the line box is aligned as well
283
    // e.g. <div style="text-align: center; width: 100px;">centered text</div> : the line box will also be centered
284
    // as opposed to start at 0px all the way to [centered text] run's right edge.
285
    m_lineBox.moveHorizontally(*adjustment);
286
    for (auto& run : m_runs)
287
        run.moveHorizontally(*adjustment);
288
}
460
}
289
461
290
void LineBuilder::removeTrailingTrimmableContent()
462
void LineBuilder::removeTrailingTrimmableContent()
Lines 365-395 void LineBuilder::visuallyCollapsePreWrapOverflowContent() a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec7
365
    m_lineBox.shrinkHorizontally(trimmedContentWidth);
537
    m_lineBox.shrinkHorizontally(trimmedContentWidth);
366
}
538
}
367
539
368
HangingContent LineBuilder::collectHangingContent(IsLastLineWithInlineContent isLastLineWithInlineContent)
369
{
370
    auto hangingContent = HangingContent { };
371
    // Can't setup hanging content with removable trailing whitespace.
372
    ASSERT(m_trimmableTrailingContent.isEmpty());
373
    if (isLastLineWithInlineContent == IsLastLineWithInlineContent::Yes)
374
        hangingContent.setIsConditional();
375
    for (auto& run : WTF::makeReversedRange(m_runs)) {
376
        if (run.isContainerStart() || run.isContainerEnd())
377
            continue;
378
        if (run.isLineBreak()) {
379
            hangingContent.setIsConditional();
380
            continue;
381
        }
382
        if (!run.hasTrailingWhitespace())
383
            break;
384
        // Check if we have a preserved or hung whitespace.
385
        if (run.style().whiteSpace() != WhiteSpace::PreWrap)
386
            break;
387
        // This is either a normal or conditionally hanging trailing whitespace.
388
        hangingContent.expand(run.trailingWhitespaceWidth());
389
    }
390
    return hangingContent;
391
}
392
393
void LineBuilder::moveLogicalLeft(InlineLayoutUnit delta)
540
void LineBuilder::moveLogicalLeft(InlineLayoutUnit delta)
394
{
541
{
395
    if (!delta)
542
    if (!delta)
Lines 548-675 void LineBuilder::appendLineBreak(const InlineItem& inlineItem) a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec8
548
    m_runs.append({ downcast<InlineSoftLineBreakItem>(inlineItem), contentLogicalWidth() });
695
    m_runs.append({ downcast<InlineSoftLineBreakItem>(inlineItem), contentLogicalWidth() });
549
}
696
}
550
697
551
void LineBuilder::adjustBaselineAndLineHeight()
552
{
553
    unsigned inlineContainerNestingLevel = 0;
554
    auto hasSeenDirectTextOrLineBreak = false;
555
    for (auto& run : m_runs) {
556
        auto& layoutBox = run.layoutBox();
557
        auto& style = layoutBox.style();
558
559
        run.setLogicalHeight(runContentHeight(run));
560
561
        if (run.isText() || run.isLineBreak()) {
562
            // For text content we set the baseline either through the initial strut (set by the formatting context root) or
563
            // through the inline container (start). Normally the text content itself does not stretch the line.
564
            if (!m_initialStrut) {
565
                // We are in standards mode where the baseline and line height are explict.
566
                continue;
567
            }
568
            if (inlineContainerNestingLevel) {
569
                // We've already adjusted the line height/baseline through the parent inline container. 
570
                continue;
571
            }
572
            if (hasSeenDirectTextOrLineBreak) {
573
                // e.g div>first text</div> or <div><span>nested<span>first direct text</div>.
574
                continue;
575
            }
576
            hasSeenDirectTextOrLineBreak = true;
577
            // We are in quirks mode where the font-metrics might change the line line height/baseline and this is the first text content on the line
578
            // outside of an inline container.
579
            m_lineBox.setAscentIfGreater(m_initialStrut->ascent);
580
            m_lineBox.setDescentIfGreater(m_initialStrut->descent);
581
            m_lineBox.setLogicalHeightIfGreater(m_initialStrut->height());
582
            continue;
583
        }
584
585
        if (run.isContainerStart()) {
586
            ++inlineContainerNestingLevel;
587
            // Inline containers stretch the line by their font size.
588
            // Vertical margins, padding and borders don't contribute to the line height.
589
            auto& fontMetrics = style.fontMetrics();
590
            if (style.verticalAlign() == VerticalAlign::Baseline) {
591
                auto halfLeading = halfLeadingMetrics(fontMetrics, style.computedLineHeight());
592
                // Both halfleading ascent and descent could be negative (tall font vs. small line-height value)
593
                if (halfLeading.descent > 0)
594
                    m_lineBox.setDescentIfGreater(halfLeading.descent);
595
                if (halfLeading.ascent > 0)
596
                    m_lineBox.setAscentIfGreater(halfLeading.ascent);
597
                m_lineBox.setLogicalHeightIfGreater(m_lineBox.ascentAndDescent().height());
598
            } else
599
                m_lineBox.setLogicalHeightIfGreater(fontMetrics.height());
600
            continue;
601
        }
602
603
        if (run.isContainerEnd()) {
604
            // The line's baseline and height have already been adjusted at ContainerStart.
605
            ASSERT(inlineContainerNestingLevel);
606
            --inlineContainerNestingLevel;
607
            continue;
608
        }
609
610
        if (run.isBox()) {
611
            auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
612
            auto marginBoxHeight = boxGeometry.marginBoxHeight();
613
614
            switch (style.verticalAlign()) {
615
            case VerticalAlign::Baseline: {
616
                if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
617
                    // Inline-blocks with inline content always have baselines.
618
                    auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
619
                    // There has to be at least one line -even if it is empty.
620
                    auto& lastLineBox = formattingState.displayInlineContent()->lineBoxes.last();
621
                    auto beforeHeight = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0);
622
                    m_lineBox.setAlignmentBaselineIfGreater(beforeHeight + lastLineBox.baseline());
623
                    m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
624
                } else {
625
                    // Non inline-block boxes sit on the baseline (including their bottom margin).
626
                    m_lineBox.setAscentIfGreater(marginBoxHeight);
627
                    // Ignore negative descent (yes, negative descent is a thing).
628
                    m_lineBox.setLogicalHeightIfGreater(marginBoxHeight + std::max<InlineLayoutUnit>(0, m_lineBox.ascentAndDescent().descent));
629
                }
630
                break;
631
            }
632
            case VerticalAlign::Top:
633
                // Top align content never changes the baseline, it only pushes the bottom of the line further down.
634
                m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
635
                break;
636
            case VerticalAlign::Bottom: {
637
                // Bottom aligned, tall content pushes the baseline further down from the line top.
638
                auto lineLogicalHeight = m_lineBox.logicalHeight();
639
                if (marginBoxHeight > lineLogicalHeight) {
640
                    m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
641
                    m_lineBox.setAlignmentBaselineIfGreater(m_lineBox.alignmentBaseline() + (marginBoxHeight - lineLogicalHeight));
642
                }
643
                break;
644
            }
645
            default:
646
                ASSERT_NOT_IMPLEMENTED_YET();
647
                break;
648
            }
649
            continue;
650
        }
651
    }
652
}
653
654
InlineLayoutUnit LineBuilder::runContentHeight(const Run& run) const
655
{
656
    ASSERT(!m_isIntrinsicSizing);
657
    auto& fontMetrics = run.style().fontMetrics();
658
    if (run.isText() || run.isLineBreak())
659
        return fontMetrics.height();
660
661
    if (run.isContainerStart() || run.isContainerEnd())
662
        return fontMetrics.height();
663
664
    auto& layoutBox = run.layoutBox();
665
    auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
666
    if (layoutBox.isReplacedBox() || layoutBox.isFloatingPositioned())
667
        return boxGeometry.contentBoxHeight();
668
669
    // Non-replaced inline box (e.g. inline-block). It looks a bit misleading but their margin box is considered the content height here.
670
    return boxGeometry.marginBoxHeight();
671
}
672
673
bool LineBuilder::isVisuallyNonEmpty(const Run& run) const
698
bool LineBuilder::isVisuallyNonEmpty(const Run& run) const
674
{
699
{
675
    if (run.isText())
700
    if (run.isText())
Lines 716-736 AscentAndDescent LineBuilder::halfLeadingMetrics(const FontMetrics& fontMetrics, a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp_sec9
716
    return { adjustedAscent, adjustedDescent };
741
    return { adjustedAscent, adjustedDescent };
717
}
742
}
718
743
719
LayoutState& LineBuilder::layoutState() const
720
{ 
721
    return formattingContext().layoutState();
722
}
723
724
const InlineFormattingContext& LineBuilder::formattingContext() const
744
const InlineFormattingContext& LineBuilder::formattingContext() const
725
{
745
{
726
    return m_inlineFormattingContext;
746
    return m_inlineFormattingContext;
727
}
747
}
728
748
729
const ContainerBox& LineBuilder::root() const
730
{
731
    return formattingContext().root();
732
}
733
734
LineBuilder::TrimmableTrailingContent::TrimmableTrailingContent(RunList& runs)
749
LineBuilder::TrimmableTrailingContent::TrimmableTrailingContent(RunList& runs)
735
    : m_runs(runs)
750
    : m_runs(runs)
736
{
751
{
- a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h -24 / +3 lines
Lines 35-47 a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h_sec1
35
namespace WebCore {
35
namespace WebCore {
36
namespace Layout {
36
namespace Layout {
37
37
38
struct HangingContent;
39
class InlineFormattingContext;
38
class InlineFormattingContext;
40
class InlineSoftLineBreakItem;
39
class InlineSoftLineBreakItem;
40
class LineContentAligner;
41
41
42
class LineBuilder {
42
class LineBuilder {
43
    struct ContinuousContent;
44
45
public:
43
public:
46
    struct Constraints {
44
    struct Constraints {
47
        InlineLayoutPoint logicalTopLeft;
45
        InlineLayoutPoint logicalTopLeft;
Lines 64-70 public: a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h_sec2
64
    void clear();
62
    void clear();
65
    bool isVisuallyEmpty() const { return m_lineBox.isConsideredEmpty(); }
63
    bool isVisuallyEmpty() const { return m_lineBox.isConsideredEmpty(); }
66
    bool hasIntrusiveFloat() const { return m_hasIntrusiveFloat; }
64
    bool hasIntrusiveFloat() const { return m_hasIntrusiveFloat; }
67
    InlineLayoutUnit availableWidth() const { return logicalWidth() - contentLogicalWidth(); }
65
    InlineLayoutUnit availableWidth() const { return m_lineLogicalWidth - contentLogicalWidth(); }
68
66
69
    InlineLayoutUnit trimmableTrailingWidth() const { return m_trimmableTrailingContent.width(); }
67
    InlineLayoutUnit trimmableTrailingWidth() const { return m_trimmableTrailingContent.width(); }
70
    bool isTrailingRunFullyTrimmable() const { return m_trimmableTrailingContent.isTrailingRunFullyTrimmable(); }
68
    bool isTrailingRunFullyTrimmable() const { return m_trimmableTrailingContent.isTrailingRunFullyTrimmable(); }
Lines 92-97 public: a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h_sec3
92
90
93
    private:
91
    private:
94
        friend class LineBuilder;
92
        friend class LineBuilder;
93
        friend class LineContentAligner;
95
94
96
        Run(const InlineTextItem&, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth, bool needsHypen);
95
        Run(const InlineTextItem&, InlineLayoutUnit logicalLeft, InlineLayoutUnit logicalWidth, bool needsHypen);
97
        Run(const InlineSoftLineBreakItem&, InlineLayoutUnit logicalLeft);
96
        Run(const InlineSoftLineBreakItem&, InlineLayoutUnit logicalLeft);
Lines 149-166 public: a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h_sec4
149
    static AscentAndDescent halfLeadingMetrics(const FontMetrics&, InlineLayoutUnit lineLogicalHeight);
148
    static AscentAndDescent halfLeadingMetrics(const FontMetrics&, InlineLayoutUnit lineLogicalHeight);
150
149
151
private:
150
private:
152
    InlineLayoutUnit logicalTop() const { return m_lineBox.logicalTop(); }
153
    InlineLayoutUnit logicalBottom() const { return m_lineBox.logicalBottom(); }
154
155
    InlineLayoutUnit logicalLeft() const { return m_lineBox.logicalLeft(); }
156
    InlineLayoutUnit logicalRight() const { return logicalLeft() + logicalWidth(); }
157
158
    InlineLayoutUnit logicalWidth() const { return m_lineLogicalWidth; }
159
    InlineLayoutUnit logicalHeight() const { return m_lineBox.logicalHeight(); }
160
161
    InlineLayoutUnit contentLogicalWidth() const { return m_lineBox.logicalWidth(); }
151
    InlineLayoutUnit contentLogicalWidth() const { return m_lineBox.logicalWidth(); }
162
    InlineLayoutUnit contentLogicalRight() const { return m_lineBox.logicalRight(); }
152
    InlineLayoutUnit contentLogicalRight() const { return m_lineBox.logicalRight(); }
163
    InlineLayoutUnit baseline() const { return m_lineBox.alignmentBaseline(); }
164
153
165
    struct InlineRunDetails {
154
    struct InlineRunDetails {
166
        InlineLayoutUnit logicalWidth { 0 };
155
        InlineLayoutUnit logicalWidth { 0 };
Lines 177-196 private: a/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h_sec5
177
166
178
    void removeTrailingTrimmableContent();
167
    void removeTrailingTrimmableContent();
179
    void visuallyCollapsePreWrapOverflowContent();
168
    void visuallyCollapsePreWrapOverflowContent();
180
    HangingContent collectHangingContent(IsLastLineWithInlineContent);
181
    void alignHorizontally(const HangingContent&, IsLastLineWithInlineContent);
182
    void alignContentVertically();
183
184
    void adjustBaselineAndLineHeight();
185
    InlineLayoutUnit runContentHeight(const Run&) const;
186
187
    void justifyRuns(InlineLayoutUnit availableWidth);
188
169
189
    bool isVisuallyNonEmpty(const Run&) const;
170
    bool isVisuallyNonEmpty(const Run&) const;
190
171
191
    LayoutState& layoutState() const;
192
    const InlineFormattingContext& formattingContext() const;
172
    const InlineFormattingContext& formattingContext() const;
193
    const ContainerBox& root() const;
194
173
195
    struct TrimmableTrailingContent {
174
    struct TrimmableTrailingContent {
196
        TrimmableTrailingContent(RunList&);
175
        TrimmableTrailingContent(RunList&);

Return to Bug 214527