|
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 |
{ |