[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / InlineFormattingContextLineLayout.cpp
1 /*
2  * Copyright (C) 2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "InlineFormattingContext.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "FloatingContext.h"
32 #include "FloatingState.h"
33 #include "InlineFormattingState.h"
34 #include "InlineLine.h"
35 #include "InlineLineBreaker.h"
36 #include "LayoutBox.h"
37 #include "LayoutContainer.h"
38 #include "LayoutState.h"
39 #include "TextUtil.h"
40
41 namespace WebCore {
42 namespace Layout {
43
44 static LayoutUnit inlineItemWidth(const LayoutState& layoutState, const InlineItem& inlineItem, LayoutUnit contentLogicalLeft)
45 {
46     if (inlineItem.isLineBreak())
47         return 0;
48
49     if (is<InlineTextItem>(inlineItem)) {
50         auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
51         auto end = inlineTextItem.isCollapsed() ? inlineTextItem.start() + 1 : inlineTextItem.end();
52         return TextUtil::width(inlineTextItem.layoutBox(), inlineTextItem.start(), end, contentLogicalLeft);
53     }
54
55     auto& layoutBox = inlineItem.layoutBox();
56     ASSERT(layoutState.hasDisplayBox(layoutBox));
57     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
58
59     if (layoutBox.isFloatingPositioned())
60         return displayBox.marginBoxWidth();
61
62     if (layoutBox.replaced())
63         return displayBox.width();
64
65     if (inlineItem.isContainerStart())
66         return displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0);
67
68     if (inlineItem.isContainerEnd())
69         return displayBox.marginEnd() + displayBox.borderRight() + displayBox.paddingRight().valueOr(0);
70
71     // Non-replaced inline box (e.g. inline-block)
72     return displayBox.width();
73 }
74
75 struct IndexAndRange {
76     unsigned index { 0 };
77     struct Range {
78         unsigned start { 0 };
79         unsigned length { 0 };
80     };
81     Optional<Range> partialContext;
82 };
83
84 struct LineInput {
85     LineInput(const Line::InitialConstraints& initialLineConstraints, Line::SkipVerticalAligment, IndexAndRange firstToProcess, const InlineItems&);
86
87     Line::InitialConstraints initialConstraints;
88     // FIXME Alternatively we could just have a second pass with vertical positioning (preferred width computation opts out) 
89     Line::SkipVerticalAligment skipVerticalAligment;
90     IndexAndRange firstInlineItem;
91     const InlineItems& inlineItems;
92     Optional<LayoutUnit> floatMinimumLogicalBottom;
93 };
94
95 struct LineContent {
96     Optional<IndexAndRange> lastCommitted;
97     Vector<WeakPtr<InlineItem>> floats;
98     std::unique_ptr<Line::Content> runs;
99 };
100
101 class LineLayout {
102 public:
103     LineLayout(const LayoutState&, const LineInput&);
104
105     LineContent layout();
106
107 private:
108     const LayoutState& layoutState() const { return m_layoutState; }
109     enum class IsEndOfLine { No, Yes };
110     IsEndOfLine placeInlineItem(const InlineItem&);
111     void commitPendingContent();
112     LineContent close();
113     
114     struct UncommittedContent {
115         struct Run {
116             const InlineItem& inlineItem;
117             LayoutUnit logicalWidth;
118         };
119         void add(const InlineItem&, LayoutUnit logicalWidth);
120         void reset();
121
122         Vector<Run> runs() { return m_uncommittedRuns; }
123         bool isEmpty() const { return m_uncommittedRuns.isEmpty(); }
124         unsigned size() const { return m_uncommittedRuns.size(); }
125         LayoutUnit width() const { return m_width; }
126
127     private:
128         Vector<Run> m_uncommittedRuns;
129         LayoutUnit m_width;
130     };
131
132     const LayoutState& m_layoutState;
133     const LineInput& m_lineInput;
134     Line m_line;
135     LineBreaker m_lineBreaker;
136     bool m_lineHasFloatBox { false };
137     UncommittedContent m_uncommittedContent;
138     unsigned m_committedInlineItemCount { 0 };
139     Vector<WeakPtr<InlineItem>> m_floats;
140     std::unique_ptr<InlineTextItem> m_leadingPartialInlineTextItem;
141     std::unique_ptr<InlineTextItem> m_trailingPartialInlineTextItem;
142 };
143
144 void LineLayout::UncommittedContent::add(const InlineItem& inlineItem, LayoutUnit logicalWidth)
145 {
146     m_uncommittedRuns.append({ inlineItem, logicalWidth });
147     m_width += logicalWidth;
148 }
149
150 void LineLayout::UncommittedContent::reset()
151 {
152     m_uncommittedRuns.clear();
153     m_width = 0;
154 }
155
156 LineLayout::LineLayout(const LayoutState& layoutState, const LineInput& lineInput)
157     : m_layoutState(layoutState)
158     , m_lineInput(lineInput)
159     , m_line(layoutState, lineInput.initialConstraints, lineInput.skipVerticalAligment)
160     , m_lineHasFloatBox(lineInput.floatMinimumLogicalBottom.hasValue())
161 {
162 }
163
164 void LineLayout::commitPendingContent()
165 {
166     if (m_uncommittedContent.isEmpty())
167         return;
168     m_committedInlineItemCount += m_uncommittedContent.size();
169     for (auto& uncommittedRun : m_uncommittedContent.runs())
170         m_line.append(uncommittedRun.inlineItem, uncommittedRun.logicalWidth);
171     m_uncommittedContent.reset();
172 }
173
174 LineContent LineLayout::close()
175 {
176     ASSERT(m_committedInlineItemCount || m_lineHasFloatBox);
177     if (!m_committedInlineItemCount)
178         return LineContent { WTF::nullopt, WTFMove(m_floats), m_line.close() };
179
180     auto lastInlineItemIndex = m_lineInput.firstInlineItem.index + m_committedInlineItemCount - 1;
181     Optional<IndexAndRange::Range> partialContext;
182     if (m_trailingPartialInlineTextItem)
183         partialContext = IndexAndRange::Range { m_trailingPartialInlineTextItem->start(), m_trailingPartialInlineTextItem->length() };
184
185     auto lastCommitedItem = IndexAndRange { lastInlineItemIndex, partialContext };
186     return LineContent { lastCommitedItem, WTFMove(m_floats), m_line.close() };
187 }
188
189 LineLayout::IsEndOfLine LineLayout::placeInlineItem(const InlineItem& inlineItem)
190 {
191     auto availableWidth = m_line.availableWidth() - m_uncommittedContent.width();
192     auto currentLogicalRight = m_line.contentLogicalRight() + m_uncommittedContent.width();
193     auto itemLogicalWidth = inlineItemWidth(layoutState(), inlineItem, currentLogicalRight);
194
195     // FIXME: Ensure LineContext::trimmableWidth includes uncommitted content if needed.
196     auto lineIsConsideredEmpty = !m_line.hasContent() && !m_lineHasFloatBox;
197     auto breakingContext = m_lineBreaker.breakingContext(inlineItem, itemLogicalWidth, { availableWidth, currentLogicalRight, m_line.trailingTrimmableWidth(), lineIsConsideredEmpty });
198     if (breakingContext.isAtBreakingOpportunity)
199         commitPendingContent();
200
201     // Content does not fit the current line.
202     if (breakingContext.breakingBehavior == LineBreaker::BreakingBehavior::Wrap)
203         return IsEndOfLine::Yes;
204
205     // Partial content stays on the current line.
206     if (breakingContext.breakingBehavior == LineBreaker::BreakingBehavior::Split) {
207         ASSERT(inlineItem.isText());
208         auto& inlineTextItem = downcast<InlineTextItem>(inlineItem);
209         auto splitData = TextUtil::split(inlineTextItem.layoutBox(), inlineTextItem.start(), inlineTextItem.length(), itemLogicalWidth, availableWidth, currentLogicalRight);
210         // Construct a partial trailing inline item.
211         ASSERT(!m_trailingPartialInlineTextItem);
212         m_trailingPartialInlineTextItem = inlineTextItem.split(splitData.start, splitData.length);
213         m_uncommittedContent.add(*m_trailingPartialInlineTextItem, splitData.logicalWidth);
214         commitPendingContent();
215         return IsEndOfLine::Yes;
216     }
217
218     ASSERT(breakingContext.breakingBehavior == LineBreaker::BreakingBehavior::Keep);
219     if (inlineItem.isFloat()) {
220         auto& floatBox = inlineItem.layoutBox();
221         ASSERT(layoutState().hasDisplayBox(floatBox));
222         // Shrink available space for current line and move existing inline runs.
223         auto floatBoxWidth = layoutState().displayBoxForLayoutBox(floatBox).marginBoxWidth();
224         floatBox.isLeftFloatingPositioned() ? m_line.moveLogicalLeft(floatBoxWidth) : m_line.moveLogicalRight(floatBoxWidth);
225         m_floats.append(makeWeakPtr(inlineItem));
226         ++m_committedInlineItemCount;
227         m_lineHasFloatBox = true;
228         return IsEndOfLine::No;
229     }
230
231     m_uncommittedContent.add(inlineItem, itemLogicalWidth);
232     if (breakingContext.isAtBreakingOpportunity)
233         commitPendingContent();
234
235     return inlineItem.isHardLineBreak() ? IsEndOfLine::Yes : IsEndOfLine::No;
236 }
237
238 LineContent LineLayout::layout()
239 {
240     // Iterate through the inline content and place the inline boxes on the current line.
241     // Start with the partial text from the previous line.
242     auto firstInlineItem = m_lineInput.firstInlineItem;
243     unsigned firstNonPartialIndex = firstInlineItem.index;
244     if (firstInlineItem.partialContext) {
245         // Handle partial inline item (split text from the previous line).
246         auto& originalTextItem = m_lineInput.inlineItems[firstInlineItem.index];
247         RELEASE_ASSERT(originalTextItem->isText());
248
249         auto textRange = *firstInlineItem.partialContext;
250         // Construct a partial leading inline item.
251         ASSERT(!m_leadingPartialInlineTextItem);
252         m_leadingPartialInlineTextItem = downcast<InlineTextItem>(*originalTextItem).split(textRange.start, textRange.length);
253         if (placeInlineItem(*m_leadingPartialInlineTextItem) == IsEndOfLine::Yes)
254             return close();
255         ++firstNonPartialIndex;
256     }
257
258     for (auto inlineItemIndex = firstNonPartialIndex; inlineItemIndex < m_lineInput.inlineItems.size(); ++inlineItemIndex) {
259         if (placeInlineItem(*m_lineInput.inlineItems[inlineItemIndex]) == IsEndOfLine::Yes)
260             return close();
261     }
262     commitPendingContent();
263     return close();
264 }
265
266 LineInput::LineInput(const Line::InitialConstraints& initialLineConstraints, Line::SkipVerticalAligment skipVerticalAligment, IndexAndRange firstToProcess, const InlineItems& inlineItems)
267     : initialConstraints(initialLineConstraints)
268     , skipVerticalAligment(skipVerticalAligment)
269     , firstInlineItem(firstToProcess)
270     , inlineItems(inlineItems)
271 {
272 }
273
274 InlineFormattingContext::InlineLayout::InlineLayout(const InlineFormattingContext& inlineFormattingContext)
275     : m_layoutState(inlineFormattingContext.layoutState())
276     , m_formattingRoot(downcast<Container>(inlineFormattingContext.root()))
277 {
278 }
279
280 void InlineFormattingContext::InlineLayout::layout(const InlineItems& inlineItems, LayoutUnit widthConstraint) const
281 {
282     auto& formattingRootDisplayBox = layoutState().displayBoxForLayoutBox(m_formattingRoot);
283     auto& floatingState = layoutState().establishedFormattingState(m_formattingRoot).floatingState();
284
285     auto lineLogicalTop = formattingRootDisplayBox.contentBoxTop();
286     auto lineLogicalLeft = formattingRootDisplayBox.contentBoxLeft();
287
288     auto applyFloatConstraint = [&](auto& lineInput) {
289         // Check for intruding floats and adjust logical left/available width for this line accordingly.
290         if (floatingState.isEmpty())
291             return;
292         auto availableWidth = lineInput.initialConstraints.availableLogicalWidth;
293         auto lineLogicalLeft = lineInput.initialConstraints.logicalTopLeft.x();
294         auto floatConstraints = floatingState.constraints({ lineLogicalTop }, m_formattingRoot);
295         // Check if these constraints actually put limitation on the line.
296         if (floatConstraints.left && floatConstraints.left->x <= formattingRootDisplayBox.contentBoxLeft())
297             floatConstraints.left = { };
298
299         if (floatConstraints.right && floatConstraints.right->x >= formattingRootDisplayBox.contentBoxRight())
300             floatConstraints.right = { };
301
302         // Set the minimum float bottom value as a hint for the next line if needed.
303         static auto inifitePoint = PointInContextRoot::max();
304         auto floatMinimumLogicalBottom = std::min(floatConstraints.left.valueOr(inifitePoint).y, floatConstraints.right.valueOr(inifitePoint).y);
305         if (floatMinimumLogicalBottom != inifitePoint.y)
306             lineInput.floatMinimumLogicalBottom = floatMinimumLogicalBottom;
307
308         if (floatConstraints.left && floatConstraints.right) {
309             ASSERT(floatConstraints.left->x <= floatConstraints.right->x);
310             availableWidth = floatConstraints.right->x - floatConstraints.left->x;
311             lineLogicalLeft = floatConstraints.left->x;
312         } else if (floatConstraints.left) {
313             ASSERT(floatConstraints.left->x >= lineLogicalLeft);
314             availableWidth -= (floatConstraints.left->x - lineLogicalLeft);
315             lineLogicalLeft = floatConstraints.left->x;
316         } else if (floatConstraints.right) {
317             ASSERT(floatConstraints.right->x >= lineLogicalLeft);
318             availableWidth = floatConstraints.right->x - lineLogicalLeft;
319         }
320         lineInput.initialConstraints.availableLogicalWidth = availableWidth;
321         lineInput.initialConstraints.logicalTopLeft.setX(lineLogicalLeft);
322     };
323
324     IndexAndRange currentInlineItem;
325     while (currentInlineItem.index < inlineItems.size()) {
326         auto lineInput = LineInput { { { lineLogicalLeft, lineLogicalTop }, widthConstraint, Quirks::lineHeightConstraints(layoutState(), m_formattingRoot) }, Line::SkipVerticalAligment::No, currentInlineItem, inlineItems };
327         applyFloatConstraint(lineInput);
328         auto lineContent = LineLayout(layoutState(), lineInput).layout();
329         createDisplayRuns(*lineContent.runs, lineContent.floats, widthConstraint);
330         if (!lineContent.lastCommitted) {
331             // Floats prevented us putting any content on the line.
332             ASSERT(lineInput.floatMinimumLogicalBottom);
333             ASSERT(lineContent.runs->isEmpty());
334             lineLogicalTop = *lineInput.floatMinimumLogicalBottom;
335         } else {
336             currentInlineItem = { lineContent.lastCommitted->index + 1, WTF::nullopt };
337             lineLogicalTop = lineContent.runs->logicalBottom();
338         }
339     }
340 }
341
342 LayoutUnit InlineFormattingContext::InlineLayout::computedIntrinsicWidth(const InlineItems& inlineItems, LayoutUnit widthConstraint) const
343 {
344     LayoutUnit maximumLineWidth;
345     IndexAndRange currentInlineItem;
346     while (currentInlineItem.index < inlineItems.size()) {
347         auto lineContent = LineLayout(layoutState(), { { { }, widthConstraint, Quirks::lineHeightConstraints(layoutState(), m_formattingRoot) }, Line::SkipVerticalAligment::Yes, currentInlineItem, inlineItems }).layout();
348         currentInlineItem = { lineContent.lastCommitted->index + 1, WTF::nullopt };
349         LayoutUnit floatsWidth;
350         for (auto& floatItem : lineContent.floats)
351             floatsWidth += layoutState().displayBoxForLayoutBox(floatItem->layoutBox()).marginBoxWidth();
352         maximumLineWidth = std::max(maximumLineWidth, floatsWidth + lineContent.runs->logicalWidth());
353     }
354     return maximumLineWidth;
355 }
356
357 void InlineFormattingContext::InlineLayout::createDisplayRuns(const Line::Content& lineContent, const Vector<WeakPtr<InlineItem>>& floats, LayoutUnit widthConstraint) const
358 {
359     auto& formattingState = downcast<InlineFormattingState>(layoutState().establishedFormattingState(m_formattingRoot));
360     auto& floatingState = formattingState.floatingState();
361     auto floatingContext = FloatingContext { floatingState };
362
363     // Move floats to their final position.
364     for (auto floatItem : floats) {
365         auto& floatBox = floatItem->layoutBox();
366         ASSERT(layoutState().hasDisplayBox(floatBox));
367         auto& displayBox = layoutState().displayBoxForLayoutBox(floatBox);
368         // Set static position first.
369         displayBox.setTopLeft({ lineContent.logicalLeft(), lineContent.logicalTop() });
370         // Float it.
371         displayBox.setTopLeft(floatingContext.positionForFloat(floatBox));
372         floatingState.append(floatBox);
373     }
374
375     if (lineContent.isEmpty()) {
376         // Spec tells us to create a zero height, empty line box.
377         auto lineBoxRect = Display::Rect { lineContent.logicalTop(), lineContent.logicalLeft(), 0 , 0 };
378         formattingState.addLineBox({ lineBoxRect, lineContent.baseline(), lineContent.baselineOffset() });
379         return;
380     }
381
382     auto& inlineDisplayRuns = formattingState.inlineRuns();
383     Optional<unsigned> previousLineLastRunIndex = inlineDisplayRuns.isEmpty() ? Optional<unsigned>() : inlineDisplayRuns.size() - 1;
384     // 9.4.2 Inline formatting contexts
385     // A line box is always tall enough for all of the boxes it contains.
386     auto lineBoxRect = Display::Rect { lineContent.logicalTop(), lineContent.logicalLeft(), 0, lineContent.logicalHeight()};
387     // Create final display runs.
388     auto& lineRuns = lineContent.runs();
389     for (unsigned index = 0; index < lineRuns.size(); ++index) {
390         auto& lineRun = lineRuns.at(index);
391         auto& logicalRect = lineRun->logicalRect();
392         auto& layoutBox = lineRun->layoutBox();
393         auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
394
395         if (lineRun->isLineBreak()) {
396             displayBox.setTopLeft(logicalRect.topLeft());
397             displayBox.setContentBoxWidth(logicalRect.width());
398             displayBox.setContentBoxHeight(logicalRect.height());
399             formattingState.addInlineRun(makeUnique<Display::Run>(logicalRect));
400             continue;
401         }
402
403         // Inline level box (replaced or inline-block)
404         if (lineRun->isBox()) {
405             auto topLeft = logicalRect.topLeft();
406             if (layoutBox.isInFlowPositioned())
407                 topLeft += Geometry::inFlowPositionedPositionOffset(layoutState(), layoutBox);
408             displayBox.setTopLeft(topLeft);
409             lineBoxRect.expandHorizontally(logicalRect.width());
410             formattingState.addInlineRun(makeUnique<Display::Run>(logicalRect));
411             continue;
412         }
413
414         // Inline level container start (<span>)
415         if (lineRun->isContainerStart()) {
416             displayBox.setTopLeft(logicalRect.topLeft());
417             lineBoxRect.expandHorizontally(logicalRect.width());
418             continue;
419         }
420
421         // Inline level container end (</span>)
422         if (lineRun->isContainerEnd()) {
423             if (layoutBox.isInFlowPositioned()) {
424                 auto inflowOffset = Geometry::inFlowPositionedPositionOffset(layoutState(), layoutBox);
425                 displayBox.moveHorizontally(inflowOffset.width());
426                 displayBox.moveVertically(inflowOffset.height());
427             }
428             auto marginBoxWidth = logicalRect.left() - displayBox.left();
429             auto contentBoxWidth = marginBoxWidth - (displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0));
430             // FIXME fix it for multiline.
431             displayBox.setContentBoxWidth(contentBoxWidth);
432             displayBox.setContentBoxHeight(logicalRect.height());
433             lineBoxRect.expandHorizontally(logicalRect.width());
434             continue;
435         }
436
437         // Text content. Try to join multiple text runs when possible.
438         ASSERT(lineRun->isText());
439         auto textContext = lineRun->textContext();   
440         const Line::Content::Run* previousLineRun = !index ? nullptr : lineRuns[index - 1].get();
441         if (!textContext->isCollapsed) {
442             auto previousRunCanBeExtended = previousLineRun && previousLineRun->textContext() ? previousLineRun->textContext()->canBeExtended : false;
443             auto requiresNewRun = !index || !previousRunCanBeExtended || &layoutBox != &previousLineRun->layoutBox();
444             if (requiresNewRun)
445                 formattingState.addInlineRun(makeUnique<Display::Run>(logicalRect, Display::Run::TextContext { textContext->start, textContext->length }));
446             else {
447                 auto& lastDisplayRun = formattingState.inlineRuns().last();
448                 lastDisplayRun->expandHorizontally(logicalRect.width());
449                 lastDisplayRun->textContext()->expand(textContext->length);
450             }
451             lineBoxRect.expandHorizontally(logicalRect.width());
452         }
453         // FIXME take content breaking into account when part of the layout box is on the previous line.
454         auto firstInlineRunForLayoutBox = !previousLineRun || &previousLineRun->layoutBox() != &layoutBox;
455         if (firstInlineRunForLayoutBox) {
456             // Setup display box for the associated layout box.
457             displayBox.setTopLeft(logicalRect.topLeft());
458             displayBox.setContentBoxWidth(textContext->isCollapsed ? LayoutUnit() : logicalRect.width());
459             displayBox.setContentBoxHeight(logicalRect.height());
460         } else if (!textContext->isCollapsed) {
461             // FIXME fix it for multirun/multiline.
462             displayBox.setContentBoxWidth(displayBox.contentBoxWidth() + logicalRect.width());
463         }
464     }
465     // FIXME linebox needs to be ajusted after content alignment.
466     formattingState.addLineBox({ lineBoxRect, lineContent.baseline(), lineContent.baselineOffset() });
467     alignRuns(m_formattingRoot.style().textAlign(), inlineDisplayRuns, previousLineLastRunIndex.valueOr(-1) + 1, widthConstraint - lineContent.logicalWidth());
468 }
469
470 static Optional<LayoutUnit> horizontalAdjustmentForAlignment(TextAlignMode align, LayoutUnit remainingWidth)
471 {
472     switch (align) {
473     case TextAlignMode::Left:
474     case TextAlignMode::WebKitLeft:
475     case TextAlignMode::Start:
476         return { };
477     case TextAlignMode::Right:
478     case TextAlignMode::WebKitRight:
479     case TextAlignMode::End:
480         return std::max(remainingWidth, 0_lu);
481     case TextAlignMode::Center:
482     case TextAlignMode::WebKitCenter:
483         return std::max(remainingWidth / 2, 0_lu);
484     case TextAlignMode::Justify:
485         ASSERT_NOT_REACHED();
486         break;
487     }
488     ASSERT_NOT_REACHED();
489     return { };
490 }
491
492 void InlineFormattingContext::InlineLayout::alignRuns(TextAlignMode textAlign, InlineRuns& inlineDisplayRuns, unsigned firstRunIndex, LayoutUnit availableWidth) const
493 {
494     auto adjustment = horizontalAdjustmentForAlignment(textAlign, availableWidth);
495     if (!adjustment)
496         return;
497
498     for (unsigned index = firstRunIndex; index < inlineDisplayRuns.size(); ++index)
499         inlineDisplayRuns[index]->moveHorizontally(*adjustment);
500 }
501
502 }
503 }
504
505 #endif