Diff
Modified: trunk/Source/WebCore/ChangeLog (266221 => 266222)
--- trunk/Source/WebCore/ChangeLog 2020-08-27 04:28:30 UTC (rev 266221)
+++ trunk/Source/WebCore/ChangeLog 2020-08-27 04:36:37 UTC (rev 266222)
@@ -1,3 +1,59 @@
+2020-08-26 Zalan Bujtas <[email protected]>
+
+ [LFC][IFC] LineBuilder should not align the runs
+ https://bugs.webkit.org/show_bug.cgi?id=215016
+
+ Reviewed by Antti Koivisto.
+
+ This is in preparation for moving alignment logic over to the LineBox.
+ LineBuilder is responsible for adding content to the line, but the final alignment will be done by the LineBox.
+ This works well with preferred width computation, where we use the LineBuilder to compute line width but we
+ don't really need to construct a LineBox (and no alignment is needed).
+
+ * layout/inlineformatting/InlineFormattingContext.cpp:
+ (WebCore::Layout::HangingContent::width const):
+ (WebCore::Layout::HangingContent::isConditional const):
+ (WebCore::Layout::HangingContent::setIsConditional):
+ (WebCore::Layout::HangingContent::expand):
+ (WebCore::Layout::HangingContent::reset):
+ (WebCore::Layout::LineContentAligner::formattingContext const):
+ (WebCore::Layout::LineContentAligner::layoutState const):
+ (WebCore::Layout::LineContentAligner::LineContentAligner):
+ (WebCore::Layout::LineContentAligner::alignHorizontally):
+ (WebCore::Layout::LineContentAligner::alignVertically):
+ (WebCore::Layout::LineContentAligner::justifyRuns):
+ (WebCore::Layout::LineContentAligner::adjustBaselineAndLineHeight):
+ (WebCore::Layout::LineContentAligner::collectHangingContent const):
+ (WebCore::Layout::LineContentAligner::runContentHeight const):
+ (WebCore::Layout::InlineFormattingContext::lineLayout):
+ (WebCore::Layout::InlineFormattingContext::computedIntrinsicWidthForConstraint const):
+ * layout/inlineformatting/InlineLineBuilder.cpp:
+ (WebCore::Layout::LineBuilder::LineBuilder):
+ (WebCore::Layout::LineBuilder::close):
+ (WebCore::Layout::LineBuilder::removeTrailingTrimmableContent):
+ (WebCore::Layout::LineBuilder::isVisuallyNonEmpty const):
+ (WebCore::Layout::HangingContent::width const): Deleted.
+ (WebCore::Layout::HangingContent::isConditional const): Deleted.
+ (WebCore::Layout::HangingContent::setIsConditional): Deleted.
+ (WebCore::Layout::HangingContent::expand): Deleted.
+ (): Deleted.
+ (WebCore::Layout::HangingContent::reset): Deleted.
+ (WebCore::Layout::LineContentAligner::formattingContext const): Deleted.
+ (WebCore::Layout::LineContentAligner::layoutState const): Deleted.
+ (WebCore::Layout::LineContentAligner::LineContentAligner): Deleted.
+ (WebCore::Layout::LineContentAligner::alignHorizontally): Deleted.
+ (WebCore::Layout::LineContentAligner::alignVertically): Deleted.
+ (WebCore::Layout::LineContentAligner::justifyRuns): Deleted.
+ (WebCore::Layout::LineContentAligner::adjustBaselineAndLineHeight): Deleted.
+ (WebCore::Layout::LineContentAligner::collectHangingContent const): Deleted.
+ (WebCore::Layout::LineContentAligner::runContentHeight const): Deleted.
+ * layout/inlineformatting/InlineLineBuilder.h:
+ (WebCore::Layout::LineBuilder::lineBox):
+ (WebCore::Layout::LineBuilder::runs):
+ * layout/inlineformatting/LineLayoutContext.cpp:
+ (WebCore::Layout::LineLayoutContext::close):
+ * layout/inlineformatting/LineLayoutContext.h:
+
2020-08-26 Chris Dumez <[email protected]>
Add support for sub-sample accurate start for AudioBufferSourceNode
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp (266221 => 266222)
--- trunk/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp 2020-08-27 04:28:30 UTC (rev 266221)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineFormattingContext.cpp 2020-08-27 04:36:37 UTC (rev 266222)
@@ -76,6 +76,333 @@
return nullptr;
}
+struct HangingContent {
+public:
+ void reset();
+
+ InlineLayoutUnit width() const { return m_width; }
+ bool isConditional() const { return m_isConditional; }
+
+ void setIsConditional() { m_isConditional = true; }
+ void expand(InlineLayoutUnit width) { m_width += width; }
+
+private:
+ bool m_isConditional { false };
+ InlineLayoutUnit m_width { 0 };
+};
+
+void HangingContent::reset()
+{
+ m_isConditional = false;
+ m_width = 0;
+}
+
+class LineContentAligner {
+public:
+ LineContentAligner(const InlineFormattingContext&, LineBox&, LineBuilder::RunList&, InlineLayoutUnit availableWidth);
+
+ enum class IsLastLineWithInlineContent { No, Yes };
+ void alignHorizontally(TextAlignMode, IsLastLineWithInlineContent);
+ void alignVertically();
+ void adjustBaselineAndLineHeight();
+
+private:
+ void justifyRuns(InlineLayoutUnit availableWidth);
+ HangingContent collectHangingContent(IsLastLineWithInlineContent) const;
+ InlineLayoutUnit runContentHeight(const LineBuilder::Run&) const;
+
+ const InlineFormattingContext& formattingContext() const { return m_inlineFormattingContext; }
+ LayoutState& layoutState() const { return formattingContext().layoutState(); }
+
+ const InlineFormattingContext& m_inlineFormattingContext;
+ LineBox& m_lineBox;
+ LineBuilder::RunList& m_runs;
+ InlineLayoutUnit m_availableWidth;
+};
+
+LineContentAligner::LineContentAligner(const InlineFormattingContext& inlineFormattingContext, LineBox& lineBox, LineBuilder::RunList& runs, InlineLayoutUnit availableWidth)
+ : m_inlineFormattingContext(inlineFormattingContext)
+ , m_lineBox(lineBox)
+ , m_runs(runs)
+ , m_availableWidth(availableWidth)
+{
+}
+
+void LineContentAligner::alignHorizontally(TextAlignMode horizontalAlignment, IsLastLineWithInlineContent isLastLine)
+{
+ auto hangingContent = collectHangingContent(isLastLine);
+ auto availableWidth = m_availableWidth + hangingContent.width();
+ if (m_runs.isEmpty() || availableWidth <= 0)
+ return;
+
+ if (horizontalAlignment == TextAlignMode::Justify) {
+ justifyRuns(availableWidth);
+ return;
+ }
+
+ auto adjustmentForAlignment = [&] (auto availableWidth) -> Optional<InlineLayoutUnit> {
+ switch (horizontalAlignment) {
+ case TextAlignMode::Left:
+ case TextAlignMode::WebKitLeft:
+ case TextAlignMode::Start:
+ return { };
+ case TextAlignMode::Right:
+ case TextAlignMode::WebKitRight:
+ case TextAlignMode::End:
+ return std::max<InlineLayoutUnit>(availableWidth, 0);
+ case TextAlignMode::Center:
+ case TextAlignMode::WebKitCenter:
+ return std::max<InlineLayoutUnit>(availableWidth / 2, 0);
+ case TextAlignMode::Justify:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ ASSERT_NOT_REACHED();
+ return { };
+ };
+
+ auto adjustment = adjustmentForAlignment(availableWidth);
+ if (!adjustment)
+ return;
+ // Horizontal alignment means that we not only adjust the runs but also make sure
+ // that the line box is aligned as well
+ // e.g. <div style="text-align: center; width: 100px;">centered text</div> : the line box will also be centered
+ // as opposed to start at 0px all the way to [centered text] run's right edge.
+ m_lineBox.moveHorizontally(*adjustment);
+ for (auto& run : m_runs)
+ run.moveHorizontally(*adjustment);
+}
+
+void LineContentAligner::alignVertically()
+{
+ auto scrollableOverflowRect = m_lineBox.logicalRect();
+ for (auto& run : m_runs) {
+ auto logicalTop = InlineLayoutUnit { };
+ auto& layoutBox = run.layoutBox();
+ auto verticalAlign = layoutBox.style().verticalAlign();
+ auto ascent = layoutBox.style().fontMetrics().ascent();
+
+ switch (verticalAlign) {
+ case VerticalAlign::Baseline:
+ if (run.isLineBreak() || run.isText())
+ logicalTop = m_lineBox.alignmentBaseline() - ascent;
+ else if (run.isContainerStart()) {
+ auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
+ logicalTop = m_lineBox.alignmentBaseline() - ascent - boxGeometry.borderTop() - boxGeometry.paddingTop().valueOr(0);
+ } else if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
+ auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
+ // Spec makes us generate at least one line -even if it is empty.
+ auto inlineBlockBaseline = formattingState.displayInlineContent()->lineBoxes.last().baseline();
+ // The inline-block's baseline offset is relative to its content box. Let's convert it relative to the margin box.
+ // _______________ <- margin box
+ // |
+ // | ____________ <- border box
+ // | |
+ // | | _________ <- content box
+ // | | | ^
+ // | | | | <- baseline offset
+ // | | | |
+ // text | | | v text
+ // -----|-|-|---------- <- baseline
+ //
+ auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
+ auto baselineFromMarginBox = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0) + inlineBlockBaseline;
+ logicalTop = m_lineBox.alignmentBaseline() - baselineFromMarginBox;
+ } else {
+ auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
+ logicalTop = m_lineBox.alignmentBaseline() - (boxGeometry.verticalBorder() + boxGeometry.verticalPadding().valueOr(0_lu) + run.logicalRect().height() + boxGeometry.marginAfter());
+ }
+ break;
+ case VerticalAlign::Top:
+ logicalTop = 0_lu;
+ break;
+ case VerticalAlign::Bottom:
+ logicalTop = m_lineBox.logicalBottom() - run.logicalRect().height();
+ break;
+ default:
+ ASSERT_NOT_IMPLEMENTED_YET();
+ break;
+ }
+ run.adjustLogicalTop(logicalTop);
+ // Adjust scrollable overflow if the run overflows the line.
+ scrollableOverflowRect.expandVerticallyToContain(run.logicalRect());
+ // Convert runs from relative to the line top/left to the formatting root's border box top/left.
+ run.moveVertically(m_lineBox.logicalTop());
+ run.moveHorizontally(m_lineBox.logicalLeft());
+ }
+ m_lineBox.setScrollableOverflow(scrollableOverflowRect);
+}
+
+void LineContentAligner::justifyRuns(InlineLayoutUnit availableWidth)
+{
+ ASSERT(availableWidth > 0);
+ // Collect the expansion opportunity numbers and find the last run with content.
+ auto expansionOpportunityCount = 0;
+ LineBuilder::Run* lastRunWithContent = nullptr;
+ for (auto& run : m_runs) {
+ expansionOpportunityCount += run.expansionOpportunityCount();
+ if (run.isText() || run.isBox())
+ lastRunWithContent = &run;
+ }
+ // Need to fix up the last run's trailing expansion.
+ if (lastRunWithContent && lastRunWithContent->hasExpansionOpportunity()) {
+ // Turn off the trailing bits first and add the forbid trailing expansion.
+ auto leftExpansion = lastRunWithContent->expansionBehavior() & LeftExpansionMask;
+ lastRunWithContent->setExpansionBehavior(leftExpansion | ForbidRightExpansion);
+ }
+ // Nothing to distribute?
+ if (!expansionOpportunityCount)
+ return;
+ // Distribute the extra space.
+ auto expansionToDistribute = availableWidth / expansionOpportunityCount;
+ InlineLayoutUnit accumulatedExpansion = 0;
+ for (auto& run : m_runs) {
+ // Expand and moves runs by the accumulated expansion.
+ if (!run.hasExpansionOpportunity()) {
+ run.moveHorizontally(accumulatedExpansion);
+ continue;
+ }
+ ASSERT(run.expansionOpportunityCount());
+ auto computedExpansion = expansionToDistribute * run.expansionOpportunityCount();
+ run.setComputedHorizontalExpansion(computedExpansion);
+ run.moveHorizontally(accumulatedExpansion);
+ accumulatedExpansion += computedExpansion;
+ }
+}
+
+void LineContentAligner::adjustBaselineAndLineHeight()
+{
+ unsigned inlineContainerNestingLevel = 0;
+ auto hasSeenDirectTextOrLineBreak = false;
+ for (auto& run : m_runs) {
+ auto& layoutBox = run.layoutBox();
+ auto& style = layoutBox.style();
+
+ run.setLogicalHeight(runContentHeight(run));
+
+ if (run.isText() || run.isLineBreak()) {
+ if (inlineContainerNestingLevel) {
+ // We've already adjusted the line height/baseline through the parent inline container.
+ continue;
+ }
+ if (hasSeenDirectTextOrLineBreak) {
+ // e.g div>first text</div> or <div><span>nested<span>first direct text</div>.
+ continue;
+ }
+ hasSeenDirectTextOrLineBreak = true;
+ continue;
+ }
+
+ if (run.isContainerStart()) {
+ ++inlineContainerNestingLevel;
+ // Inline containers stretch the line by their font size.
+ // Vertical margins, paddings and borders don't contribute to the line height.
+ auto& fontMetrics = style.fontMetrics();
+ if (style.verticalAlign() == VerticalAlign::Baseline) {
+ auto halfLeading = LineBuilder::halfLeadingMetrics(fontMetrics, style.computedLineHeight());
+ // Both halfleading ascent and descent could be negative (tall font vs. small line-height value)
+ if (halfLeading.descent > 0)
+ m_lineBox.setDescentIfGreater(halfLeading.descent);
+ if (halfLeading.ascent > 0)
+ m_lineBox.setAscentIfGreater(halfLeading.ascent);
+ m_lineBox.setLogicalHeightIfGreater(m_lineBox.ascentAndDescent().height());
+ } else
+ m_lineBox.setLogicalHeightIfGreater(fontMetrics.height());
+ continue;
+ }
+
+ if (run.isContainerEnd()) {
+ // The line's baseline and height have already been adjusted at ContainerStart.
+ ASSERT(inlineContainerNestingLevel);
+ --inlineContainerNestingLevel;
+ continue;
+ }
+
+ if (run.isBox()) {
+ auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
+ auto marginBoxHeight = boxGeometry.marginBoxHeight();
+
+ switch (style.verticalAlign()) {
+ case VerticalAlign::Baseline: {
+ if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
+ // Inline-blocks with inline content always have baselines.
+ auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
+ // There has to be at least one line -even if it is empty.
+ auto& lastLineBox = formattingState.displayInlineContent()->lineBoxes.last();
+ auto beforeHeight = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0);
+ m_lineBox.setAlignmentBaselineIfGreater(beforeHeight + lastLineBox.baseline());
+ m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
+ } else {
+ // Non inline-block boxes sit on the baseline (including their bottom margin).
+ m_lineBox.setAscentIfGreater(marginBoxHeight);
+ // Ignore negative descent (yes, negative descent is a thing).
+ m_lineBox.setLogicalHeightIfGreater(marginBoxHeight + std::max<InlineLayoutUnit>(0, m_lineBox.ascentAndDescent().descent));
+ }
+ break;
+ }
+ case VerticalAlign::Top:
+ // Top align content never changes the baseline, it only pushes the bottom of the line further down.
+ m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
+ break;
+ case VerticalAlign::Bottom: {
+ // Bottom aligned, tall content pushes the baseline further down from the line top.
+ auto lineLogicalHeight = m_lineBox.logicalHeight();
+ if (marginBoxHeight > lineLogicalHeight) {
+ m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
+ m_lineBox.setAlignmentBaselineIfGreater(m_lineBox.alignmentBaseline() + (marginBoxHeight - lineLogicalHeight));
+ }
+ break;
+ }
+ default:
+ ASSERT_NOT_IMPLEMENTED_YET();
+ break;
+ }
+ continue;
+ }
+ }
+}
+
+HangingContent LineContentAligner::collectHangingContent(IsLastLineWithInlineContent isLastLineWithInlineContent) const
+{
+ auto hangingContent = HangingContent { };
+ if (isLastLineWithInlineContent == IsLastLineWithInlineContent::Yes)
+ hangingContent.setIsConditional();
+ for (auto& run : WTF::makeReversedRange(m_runs)) {
+ if (run.isContainerStart() || run.isContainerEnd())
+ continue;
+ if (run.isLineBreak()) {
+ hangingContent.setIsConditional();
+ continue;
+ }
+ if (!run.hasTrailingWhitespace())
+ break;
+ // Check if we have a preserved or hung whitespace.
+ if (run.style().whiteSpace() != WhiteSpace::PreWrap)
+ break;
+ // This is either a normal or conditionally hanging trailing whitespace.
+ hangingContent.expand(run.trailingWhitespaceWidth());
+ }
+ return hangingContent;
+}
+
+InlineLayoutUnit LineContentAligner::runContentHeight(const LineBuilder::Run& run) const
+{
+ auto& fontMetrics = run.style().fontMetrics();
+ if (run.isText() || run.isLineBreak())
+ return fontMetrics.height();
+
+ if (run.isContainerStart() || run.isContainerEnd())
+ return fontMetrics.height();
+
+ auto& layoutBox = run.layoutBox();
+ auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
+ if (layoutBox.isReplacedBox() || layoutBox.isFloatingPositioned())
+ return boxGeometry.contentBoxHeight();
+
+ // Non-replaced inline box (e.g. inline-block). It looks a bit misleading but their margin box is considered the content height here.
+ return boxGeometry.marginBoxHeight();
+}
+
void InlineFormattingContext::layoutInFlowContent(InvalidationState& invalidationState, const ConstraintsForInFlowContent& constraints)
{
LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")");
@@ -139,7 +466,7 @@
Optional<unsigned> overflowContentLength;
};
Optional<PreviousLine> previousLine;
- auto line = LineBuilder { *this, root().style().textAlign(), LineBuilder::IntrinsicSizing::No };
+ auto line = LineBuilder { *this };
auto lineLayoutContext = LineLayoutContext { *this, root(), inlineItems };
while (!needsLayoutRange.isEmpty()) {
@@ -148,9 +475,55 @@
// "sp[<-line break->]lit_content" -> overflow length: 11 -> leading partial content length: 11.
auto partialLeadingContentLength = previousLine ? previousLine->overflowContentLength : WTF::nullopt;
auto lineContent = lineLayoutContext.layoutInlineContent(line, needsLayoutRange, partialLeadingContentLength);
+ auto& lineBox = line.lineBox();
+ auto lineContentRange = lineContent.inlineItemRange;
+
+ // FIXME: This is temporary and will eventually get merged to LineBox.
+ auto alignLineContent = [&] {
+ auto contentAligner = LineContentAligner { *this, lineBox, line.runs(), line.availableWidth() };
+ contentAligner.adjustBaselineAndLineHeight();
+ if (line.isVisuallyEmpty()) {
+ lineBox.resetAlignmentBaseline();
+ lineBox.setLogicalHeight({ });
+ }
+ // Remove descent when all content is baseline aligned but none of them have descent.
+ if (quirks().lineDescentNeedsCollapsing(line.runs())) {
+ lineBox.shrinkVertically(lineBox.ascentAndDescent().descent);
+ lineBox.resetDescent();
+ }
+ contentAligner.alignVertically();
+
+ auto isLastLineWithInlineContent = [&] {
+ if (lineContentRange.end == needsLayoutRange.end)
+ return LineContentAligner::IsLastLineWithInlineContent::Yes;
+ if (lineContent.partialContent)
+ return LineContentAligner::IsLastLineWithInlineContent::No;
+ // Omit floats to see if this is the last line with inline content.
+ for (auto i = needsLayoutRange.end; i--;) {
+ if (!inlineItems[i].isFloat())
+ return i == lineContentRange.end - 1 ? LineContentAligner::IsLastLineWithInlineContent::Yes : LineContentAligner::IsLastLineWithInlineContent::No;
+ }
+ // There has to be at least one non-float item.
+ ASSERT_NOT_REACHED();
+ return LineContentAligner::IsLastLineWithInlineContent::No;
+ }();
+
+ auto computedHorizontalAlignment = [&] {
+ if (root().style().textAlign() != TextAlignMode::Justify)
+ return root().style().textAlign();
+ // Text is justified according to the method specified by the text-justify property,
+ // in order to exactly fill the line box. Unless otherwise specified by text-align-last,
+ // the last line before a forced break or the end of the block is start-aligned.
+ if (line.runs().last().isLineBreak() || isLastLineWithInlineContent == LineContentAligner::IsLastLineWithInlineContent::Yes)
+ return TextAlignMode::Start;
+ return TextAlignMode::Justify;
+ };
+ contentAligner.alignHorizontally(computedHorizontalAlignment(), isLastLineWithInlineContent);
+ };
+
+ alignLineContent();
setDisplayBoxesForLine(line, lineContent, constraints.horizontal);
- auto lineContentRange = lineContent.inlineItems;
if (!lineContentRange.isEmpty()) {
ASSERT(needsLayoutRange.start < lineContentRange.end);
lineLogicalTop = line.lineBox().logicalBottom();
@@ -248,7 +621,7 @@
{
auto& inlineItems = formattingState().inlineItems();
auto maximumLineWidth = InlineLayoutUnit { };
- auto line = LineBuilder { *this, root().style().textAlign(), LineBuilder::IntrinsicSizing::Yes };
+ auto line = LineBuilder { *this };
auto lineLayoutContext = LineLayoutContext { *this, root(), inlineItems };
auto layoutRange = LineLayoutContext::InlineItemRange { 0 , inlineItems.size() };
while (!layoutRange.isEmpty()) {
@@ -255,7 +628,7 @@
// Only the horiztonal available width is constrained when computing intrinsic width.
line.initialize(LineBuilder::Constraints { { }, availableWidth, { }, false });
auto lineContent = lineLayoutContext.layoutInlineContent(line, layoutRange, { });
- layoutRange.start = lineContent.inlineItems.end;
+ layoutRange.start = lineContent.inlineItemRange.end;
// FIXME: Use line logical left and right to take floats into account.
maximumLineWidth = std::max(maximumLineWidth, line.lineBox().logicalWidth());
}
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineFormattingContext.h (266221 => 266222)
--- trunk/Source/WebCore/layout/inlineformatting/InlineFormattingContext.h 2020-08-27 04:28:30 UTC (rev 266221)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineFormattingContext.h 2020-08-27 04:36:37 UTC (rev 266222)
@@ -93,8 +93,6 @@
const InlineFormattingState& formattingState() const { return downcast<InlineFormattingState>(FormattingContext::formattingState()); }
InlineFormattingState& formattingState() { return downcast<InlineFormattingState>(FormattingContext::formattingState()); }
- // FIXME: Come up with a structure that requires no friending.
- friend class LineBuilder;
};
inline InlineFormattingContext::Geometry::Geometry(const InlineFormattingContext& inlineFormattingContext)
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp (266221 => 266222)
--- trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp 2020-08-27 04:28:30 UTC (rev 266221)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp 2020-08-27 04:36:37 UTC (rev 266222)
@@ -43,337 +43,9 @@
return whitespace == WhiteSpace::Pre || whitespace == WhiteSpace::PreWrap || whitespace == WhiteSpace::BreakSpaces;
}
-struct HangingContent {
-public:
- void reset();
-
- InlineLayoutUnit width() const { return m_width; }
- bool isConditional() const { return m_isConditional; }
-
- void setIsConditional() { m_isConditional = true; }
- void expand(InlineLayoutUnit width) { m_width += width; }
-
-private:
- bool m_isConditional { false };
- InlineLayoutUnit m_width { 0 };
-};
-
-void HangingContent::reset()
-{
- m_isConditional = false;
- m_width = 0;
-}
-
-class LineContentAligner {
-public:
- LineContentAligner(const InlineFormattingContext&, LineBox&, LineBuilder::RunList&, InlineLayoutUnit availableWidth);
-
- void alignHorizontally(TextAlignMode, LineBuilder::IsLastLineWithInlineContent);
- void alignVertically();
- void adjustBaselineAndLineHeight();
-
-private:
- void justifyRuns(InlineLayoutUnit availableWidth);
- HangingContent collectHangingContent(LineBuilder::IsLastLineWithInlineContent) const;
- InlineLayoutUnit runContentHeight(const LineBuilder::Run&) const;
-
- const InlineFormattingContext& formattingContext() const { return m_inlineFormattingContext; }
- LayoutState& layoutState() const { return formattingContext().layoutState(); }
-
- const InlineFormattingContext& m_inlineFormattingContext;
- LineBox& m_lineBox;
- LineBuilder::RunList& m_runs;
- InlineLayoutUnit m_availableWidth;
-};
-
-LineContentAligner::LineContentAligner(const InlineFormattingContext& inlineFormattingContext, LineBox& lineBox, LineBuilder::RunList& runs, InlineLayoutUnit availableWidth)
+LineBuilder::LineBuilder(const InlineFormattingContext& inlineFormattingContext)
: m_inlineFormattingContext(inlineFormattingContext)
- , m_lineBox(lineBox)
- , m_runs(runs)
- , m_availableWidth(availableWidth)
-{
-}
-
-void LineContentAligner::alignHorizontally(TextAlignMode horizontalAlignment, LineBuilder::IsLastLineWithInlineContent isLastLine)
-{
- auto hangingContent = collectHangingContent(isLastLine);
- auto availableWidth = m_availableWidth + hangingContent.width();
- if (m_runs.isEmpty() || availableWidth <= 0)
- return;
-
- if (horizontalAlignment == TextAlignMode::Justify) {
- justifyRuns(availableWidth);
- return;
- }
-
- auto adjustmentForAlignment = [&] (auto availableWidth) -> Optional<InlineLayoutUnit> {
- switch (horizontalAlignment) {
- case TextAlignMode::Left:
- case TextAlignMode::WebKitLeft:
- case TextAlignMode::Start:
- return { };
- case TextAlignMode::Right:
- case TextAlignMode::WebKitRight:
- case TextAlignMode::End:
- return std::max<InlineLayoutUnit>(availableWidth, 0);
- case TextAlignMode::Center:
- case TextAlignMode::WebKitCenter:
- return std::max<InlineLayoutUnit>(availableWidth / 2, 0);
- case TextAlignMode::Justify:
- ASSERT_NOT_REACHED();
- break;
- }
- ASSERT_NOT_REACHED();
- return { };
- };
-
- auto adjustment = adjustmentForAlignment(availableWidth);
- if (!adjustment)
- return;
- // Horizontal alignment means that we not only adjust the runs but also make sure
- // that the line box is aligned as well
- // e.g. <div style="text-align: center; width: 100px;">centered text</div> : the line box will also be centered
- // as opposed to start at 0px all the way to [centered text] run's right edge.
- m_lineBox.moveHorizontally(*adjustment);
- for (auto& run : m_runs)
- run.moveHorizontally(*adjustment);
-}
-
-void LineContentAligner::alignVertically()
-{
- auto scrollableOverflowRect = m_lineBox.logicalRect();
- for (auto& run : m_runs) {
- auto logicalTop = InlineLayoutUnit { };
- auto& layoutBox = run.layoutBox();
- auto verticalAlign = layoutBox.style().verticalAlign();
- auto ascent = layoutBox.style().fontMetrics().ascent();
-
- switch (verticalAlign) {
- case VerticalAlign::Baseline:
- if (run.isLineBreak() || run.isText())
- logicalTop = m_lineBox.alignmentBaseline() - ascent;
- else if (run.isContainerStart()) {
- auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
- logicalTop = m_lineBox.alignmentBaseline() - ascent - boxGeometry.borderTop() - boxGeometry.paddingTop().valueOr(0);
- } else if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
- auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
- // Spec makes us generate at least one line -even if it is empty.
- auto inlineBlockBaseline = formattingState.displayInlineContent()->lineBoxes.last().baseline();
- // The inline-block's baseline offset is relative to its content box. Let's convert it relative to the margin box.
- // _______________ <- margin box
- // |
- // | ____________ <- border box
- // | |
- // | | _________ <- content box
- // | | | ^
- // | | | | <- baseline offset
- // | | | |
- // text | | | v text
- // -----|-|-|---------- <- baseline
- //
- auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
- auto baselineFromMarginBox = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0) + inlineBlockBaseline;
- logicalTop = m_lineBox.alignmentBaseline() - baselineFromMarginBox;
- } else {
- auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
- logicalTop = m_lineBox.alignmentBaseline() - (boxGeometry.verticalBorder() + boxGeometry.verticalPadding().valueOr(0_lu) + run.logicalRect().height() + boxGeometry.marginAfter());
- }
- break;
- case VerticalAlign::Top:
- logicalTop = 0_lu;
- break;
- case VerticalAlign::Bottom:
- logicalTop = m_lineBox.logicalBottom() - run.logicalRect().height();
- break;
- default:
- ASSERT_NOT_IMPLEMENTED_YET();
- break;
- }
- run.adjustLogicalTop(logicalTop);
- // Adjust scrollable overflow if the run overflows the line.
- scrollableOverflowRect.expandVerticallyToContain(run.logicalRect());
- // Convert runs from relative to the line top/left to the formatting root's border box top/left.
- run.moveVertically(m_lineBox.logicalTop());
- run.moveHorizontally(m_lineBox.logicalLeft());
- }
- m_lineBox.setScrollableOverflow(scrollableOverflowRect);
-}
-
-void LineContentAligner::justifyRuns(InlineLayoutUnit availableWidth)
-{
- ASSERT(availableWidth > 0);
- // Collect the expansion opportunity numbers and find the last run with content.
- auto expansionOpportunityCount = 0;
- LineBuilder::Run* lastRunWithContent = nullptr;
- for (auto& run : m_runs) {
- expansionOpportunityCount += run.expansionOpportunityCount();
- if (run.isText() || run.isBox())
- lastRunWithContent = &run;
- }
- // Need to fix up the last run's trailing expansion.
- if (lastRunWithContent && lastRunWithContent->hasExpansionOpportunity()) {
- // Turn off the trailing bits first and add the forbid trailing expansion.
- auto leftExpansion = lastRunWithContent->expansionBehavior() & LeftExpansionMask;
- lastRunWithContent->setExpansionBehavior(leftExpansion | ForbidRightExpansion);
- }
- // Nothing to distribute?
- if (!expansionOpportunityCount)
- return;
- // Distribute the extra space.
- auto expansionToDistribute = availableWidth / expansionOpportunityCount;
- InlineLayoutUnit accumulatedExpansion = 0;
- for (auto& run : m_runs) {
- // Expand and moves runs by the accumulated expansion.
- if (!run.hasExpansionOpportunity()) {
- run.moveHorizontally(accumulatedExpansion);
- continue;
- }
- ASSERT(run.expansionOpportunityCount());
- auto computedExpansion = expansionToDistribute * run.expansionOpportunityCount();
- run.setComputedHorizontalExpansion(computedExpansion);
- run.moveHorizontally(accumulatedExpansion);
- accumulatedExpansion += computedExpansion;
- }
-}
-
-void LineContentAligner::adjustBaselineAndLineHeight()
-{
- unsigned inlineContainerNestingLevel = 0;
- auto hasSeenDirectTextOrLineBreak = false;
- for (auto& run : m_runs) {
- auto& layoutBox = run.layoutBox();
- auto& style = layoutBox.style();
-
- run.setLogicalHeight(runContentHeight(run));
-
- if (run.isText() || run.isLineBreak()) {
- if (inlineContainerNestingLevel) {
- // We've already adjusted the line height/baseline through the parent inline container.
- continue;
- }
- if (hasSeenDirectTextOrLineBreak) {
- // e.g div>first text</div> or <div><span>nested<span>first direct text</div>.
- continue;
- }
- hasSeenDirectTextOrLineBreak = true;
- continue;
- }
-
- if (run.isContainerStart()) {
- ++inlineContainerNestingLevel;
- // Inline containers stretch the line by their font size.
- // Vertical margins, paddings and borders don't contribute to the line height.
- auto& fontMetrics = style.fontMetrics();
- if (style.verticalAlign() == VerticalAlign::Baseline) {
- auto halfLeading = LineBuilder::halfLeadingMetrics(fontMetrics, style.computedLineHeight());
- // Both halfleading ascent and descent could be negative (tall font vs. small line-height value)
- if (halfLeading.descent > 0)
- m_lineBox.setDescentIfGreater(halfLeading.descent);
- if (halfLeading.ascent > 0)
- m_lineBox.setAscentIfGreater(halfLeading.ascent);
- m_lineBox.setLogicalHeightIfGreater(m_lineBox.ascentAndDescent().height());
- } else
- m_lineBox.setLogicalHeightIfGreater(fontMetrics.height());
- continue;
- }
-
- if (run.isContainerEnd()) {
- // The line's baseline and height have already been adjusted at ContainerStart.
- ASSERT(inlineContainerNestingLevel);
- --inlineContainerNestingLevel;
- continue;
- }
-
- if (run.isBox()) {
- auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
- auto marginBoxHeight = boxGeometry.marginBoxHeight();
-
- switch (style.verticalAlign()) {
- case VerticalAlign::Baseline: {
- if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
- // Inline-blocks with inline content always have baselines.
- auto& formattingState = layoutState().establishedInlineFormattingState(downcast<ContainerBox>(layoutBox));
- // There has to be at least one line -even if it is empty.
- auto& lastLineBox = formattingState.displayInlineContent()->lineBoxes.last();
- auto beforeHeight = boxGeometry.marginBefore() + boxGeometry.borderTop() + boxGeometry.paddingTop().valueOr(0);
- m_lineBox.setAlignmentBaselineIfGreater(beforeHeight + lastLineBox.baseline());
- m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
- } else {
- // Non inline-block boxes sit on the baseline (including their bottom margin).
- m_lineBox.setAscentIfGreater(marginBoxHeight);
- // Ignore negative descent (yes, negative descent is a thing).
- m_lineBox.setLogicalHeightIfGreater(marginBoxHeight + std::max<InlineLayoutUnit>(0, m_lineBox.ascentAndDescent().descent));
- }
- break;
- }
- case VerticalAlign::Top:
- // Top align content never changes the baseline, it only pushes the bottom of the line further down.
- m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
- break;
- case VerticalAlign::Bottom: {
- // Bottom aligned, tall content pushes the baseline further down from the line top.
- auto lineLogicalHeight = m_lineBox.logicalHeight();
- if (marginBoxHeight > lineLogicalHeight) {
- m_lineBox.setLogicalHeightIfGreater(marginBoxHeight);
- m_lineBox.setAlignmentBaselineIfGreater(m_lineBox.alignmentBaseline() + (marginBoxHeight - lineLogicalHeight));
- }
- break;
- }
- default:
- ASSERT_NOT_IMPLEMENTED_YET();
- break;
- }
- continue;
- }
- }
-}
-
-HangingContent LineContentAligner::collectHangingContent(LineBuilder::IsLastLineWithInlineContent isLastLineWithInlineContent) const
-{
- auto hangingContent = HangingContent { };
- if (isLastLineWithInlineContent == LineBuilder::IsLastLineWithInlineContent::Yes)
- hangingContent.setIsConditional();
- for (auto& run : WTF::makeReversedRange(m_runs)) {
- if (run.isContainerStart() || run.isContainerEnd())
- continue;
- if (run.isLineBreak()) {
- hangingContent.setIsConditional();
- continue;
- }
- if (!run.hasTrailingWhitespace())
- break;
- // Check if we have a preserved or hung whitespace.
- if (run.style().whiteSpace() != WhiteSpace::PreWrap)
- break;
- // This is either a normal or conditionally hanging trailing whitespace.
- hangingContent.expand(run.trailingWhitespaceWidth());
- }
- return hangingContent;
-}
-
-InlineLayoutUnit LineContentAligner::runContentHeight(const LineBuilder::Run& run) const
-{
- auto& fontMetrics = run.style().fontMetrics();
- if (run.isText() || run.isLineBreak())
- return fontMetrics.height();
-
- if (run.isContainerStart() || run.isContainerEnd())
- return fontMetrics.height();
-
- auto& layoutBox = run.layoutBox();
- auto& boxGeometry = formattingContext().geometryForBox(layoutBox);
- if (layoutBox.isReplacedBox() || layoutBox.isFloatingPositioned())
- return boxGeometry.contentBoxHeight();
-
- // Non-replaced inline box (e.g. inline-block). It looks a bit misleading but their margin box is considered the content height here.
- return boxGeometry.marginBoxHeight();
-}
-
-LineBuilder::LineBuilder(const InlineFormattingContext& inlineFormattingContext, Optional<TextAlignMode> horizontalAlignment, IntrinsicSizing intrinsicSizing)
- : m_inlineFormattingContext(inlineFormattingContext)
, m_trimmableTrailingContent(m_runs)
- , m_horizontalAlignment(horizontalAlignment)
- , m_isIntrinsicSizing(intrinsicSizing == IntrinsicSizing::Yes)
, m_shouldIgnoreTrailingLetterSpacing(RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled())
{
}
@@ -405,7 +77,7 @@
m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent = { };
}
-void LineBuilder::close(IsLastLineWithInlineContent isLastLine)
+void LineBuilder::close()
{
#if ASSERT_ENABLED
m_isClosed = true;
@@ -414,34 +86,6 @@
// 2. Align merged runs both vertically and horizontally.
removeTrailingTrimmableContent();
visuallyCollapsePreWrapOverflowContent();
- if (m_isIntrinsicSizing)
- return;
-
- auto contentAligner = LineContentAligner { formattingContext(), m_lineBox, m_runs, availableWidth() };
- contentAligner.adjustBaselineAndLineHeight();
- if (isVisuallyEmpty()) {
- m_lineBox.resetAlignmentBaseline();
- m_lineBox.setLogicalHeight({ });
- }
- // Remove descent when all content is baseline aligned but none of them have descent.
- if (formattingContext().quirks().lineDescentNeedsCollapsing(m_runs)) {
- m_lineBox.shrinkVertically(m_lineBox.ascentAndDescent().descent);
- m_lineBox.resetDescent();
- }
- contentAligner.alignVertically();
-
- auto computedHorizontalAlignment = [&] {
- ASSERT(m_horizontalAlignment);
- if (m_horizontalAlignment != TextAlignMode::Justify)
- return *m_horizontalAlignment;
- // Text is justified according to the method specified by the text-justify property,
- // in order to exactly fill the line box. Unless otherwise specified by text-align-last,
- // the last line before a forced break or the end of the block is start-aligned.
- if (m_runs.last().isLineBreak() || isLastLine == LineBuilder::IsLastLineWithInlineContent::Yes)
- return TextAlignMode::Start;
- return TextAlignMode::Justify;
- };
- contentAligner.alignHorizontally(computedHorizontalAlignment(), isLastLine);
}
void LineBuilder::removeTrailingTrimmableContent()
@@ -452,10 +96,10 @@
// Complex line layout quirk: keep the trailing whitespace around when it is followed by a line break, unless the content overflows the line.
if (RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled()) {
auto isTextAlignRight = [&] {
- ASSERT(m_horizontalAlignment);
- return m_horizontalAlignment == TextAlignMode::Right
- || m_horizontalAlignment == TextAlignMode::WebKitRight
- || m_horizontalAlignment == TextAlignMode::End;
+ auto textAlign = formattingContext().root().style().textAlign();
+ return textAlign == TextAlignMode::Right
+ || textAlign == TextAlignMode::WebKitRight
+ || textAlign == TextAlignMode::End;
}();
if (m_runs.last().isLineBreak() && availableWidth() >= 0 && !isTextAlignRight) {
@@ -703,11 +347,7 @@
if (run.layoutBox().isReplacedBox())
return true;
ASSERT(run.layoutBox().isInlineBlockBox() || run.layoutBox().isInlineTableBox());
- if (!run.logicalWidth())
- return false;
- if (m_isIntrinsicSizing || formattingContext().geometryForBox(run.layoutBox()).height())
- return true;
- return false;
+ return run.logicalWidth();
}
ASSERT_NOT_REACHED();
@@ -894,7 +534,7 @@
m_expansion.behavior = expansionBehavior;
}
-inline ExpansionBehavior LineBuilder::Run::expansionBehavior() const
+ExpansionBehavior LineBuilder::Run::expansionBehavior() const
{
ASSERT(isText());
return m_expansion.behavior;
Modified: trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h (266221 => 266222)
--- trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h 2020-08-27 04:28:30 UTC (rev 266221)
+++ trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h 2020-08-27 04:36:37 UTC (rev 266222)
@@ -48,15 +48,12 @@
bool lineIsConstrainedByFloat { false };
};
- enum class IntrinsicSizing { No, Yes };
- LineBuilder(const InlineFormattingContext&, Optional<TextAlignMode>, IntrinsicSizing);
+ LineBuilder(const InlineFormattingContext&);
~LineBuilder();
void initialize(const Constraints&);
+ void close();
- enum class IsLastLineWithInlineContent { No, Yes };
- void close(IsLastLineWithInlineContent);
-
void append(const InlineItem&, InlineLayoutUnit logicalWidth);
void appendPartialTrailingTextItem(const InlineTextItem&, InlineLayoutUnit logicalWidth, bool needsHypen);
void clear();
@@ -68,6 +65,7 @@
bool isTrailingRunFullyTrimmable() const { return m_trimmableTrailingContent.isTrailingRunFullyTrimmable(); }
const LineBox& lineBox() const { return m_lineBox; }
+ LineBox& lineBox() { return m_lineBox; }
void moveLogicalLeft(InlineLayoutUnit);
void moveLogicalRight(InlineLayoutUnit);
void setHasIntrusiveFloat() { m_hasIntrusiveFloat = true; }
@@ -144,6 +142,7 @@
};
using RunList = Vector<Run, 10>;
const RunList& runs() const { return m_runs; }
+ RunList& runs() { return m_runs; }
static AscentAndDescent halfLeadingMetrics(const FontMetrics&, InlineLayoutUnit lineLogicalHeight);
@@ -198,8 +197,6 @@
RunList m_runs;
TrimmableTrailingContent m_trimmableTrailingContent;
InlineLayoutUnit m_lineLogicalWidth { 0 };
- Optional<TextAlignMode> m_horizontalAlignment;
- bool m_isIntrinsicSizing { false };
bool m_hasIntrusiveFloat { false };
LineBox m_lineBox;
Optional<bool> m_lineIsVisuallyEmptyBeforeTrimmableTrailingContent;
Modified: trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp (266221 => 266222)
--- trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp 2020-08-27 04:28:30 UTC (rev 266221)
+++ trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp 2020-08-27 04:36:37 UTC (rev 266222)
@@ -332,22 +332,7 @@
auto trailingInlineItemIndex = layoutRange.start + numberOfCommittedItems - 1;
auto lineRange = InlineItemRange { layoutRange.start, trailingInlineItemIndex + 1 };
ASSERT(lineRange.end <= layoutRange.end);
-
- auto isLastLineWithInlineContent = [&] {
- if (lineRange.end == layoutRange.end)
- return LineBuilder::IsLastLineWithInlineContent::Yes;
- if (partialContent)
- return LineBuilder::IsLastLineWithInlineContent::No;
- // Omit floats to see if this is the last line with inline content.
- for (auto i = layoutRange.end; i--;) {
- if (!m_inlineItems[i].isFloat())
- return i == trailingInlineItemIndex ? LineBuilder::IsLastLineWithInlineContent::Yes : LineBuilder::IsLastLineWithInlineContent::No;
- }
- // There has to be at least one non-float item.
- ASSERT_NOT_REACHED();
- return LineBuilder::IsLastLineWithInlineContent::No;
- }();
- line.close(isLastLineWithInlineContent);
+ line.close();
// Adjust hyphenated line count.
m_successiveHyphenatedLineCount = partialContent && partialContent->trailingContentHasHyphen ? m_successiveHyphenatedLineCount + 1 : 0;
return LineContent { partialContent, lineRange, WTFMove(m_floats) };
Modified: trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.h (266221 => 266222)
--- trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.h 2020-08-27 04:28:30 UTC (rev 266221)
+++ trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.h 2020-08-27 04:36:37 UTC (rev 266222)
@@ -51,7 +51,7 @@
unsigned overflowContentLength { 0 };
};
Optional<PartialContent> partialContent;
- InlineItemRange inlineItems;
+ InlineItemRange inlineItemRange;
struct Float {
bool isIntrusive { true };
const InlineItem* item { nullptr };