@@ -136,19 +136,21 @@ void InlineFormattingContext::layoutInFlowContent(InvalidationState& invalidatio
136136 layoutBox = nextInlineLevelBoxToLayout (*layoutBox, root ());
137137 }
138138
139- collectInlineContentIfNeeded ();
139+ collectContentIfNeeded ();
140140
141141 auto & inlineItems = formattingState ().inlineItems ();
142142 lineLayout (inlineItems, { 0 , inlineItems.size () }, constraints);
143+ computeStaticPositionForOutOfFlowContent (formattingState ().outOfFlowBoxes ());
143144 LOG_WITH_STREAM (FormattingContextLayout, stream << " [End] -> inline formatting context -> formatting root(" << &root () << " )" );
144145}
145146
146147void InlineFormattingContext::lineLayoutForIntergration (InvalidationState& invalidationState, const ConstraintsForInFlowContent& constraints)
147148{
148149 invalidateFormattingState (invalidationState);
149- collectInlineContentIfNeeded ();
150+ collectContentIfNeeded ();
150151 auto & inlineItems = formattingState ().inlineItems ();
151152 lineLayout (inlineItems, { 0 , inlineItems.size () }, constraints);
153+ computeStaticPositionForOutOfFlowContent (formattingState ().outOfFlowBoxes ());
152154}
153155
154156LayoutUnit InlineFormattingContext::usedContentHeight () const
@@ -253,6 +255,103 @@ void InlineFormattingContext::lineLayout(InlineItems& inlineItems, LineBuilder::
253255 }
254256}
255257
258+ void InlineFormattingContext::computeStaticPositionForOutOfFlowContent (const FormattingState::OutOfFlowBoxList& outOfFlowBoxes)
259+ {
260+ // This function computes the static position for out-of-flow content inside the inline formatting context.
261+ // As per spec, the static position of an out-of-flow box is computed as if the position was set to static.
262+ // However it does not mean that the out-of-flow box should be involved in the inline layout process.
263+ // Instead we figure out this static position after the inline layout by looking at the previous/next sibling (or parent) box's geometry and
264+ // place the out-of-flow box at the logical right position.
265+ auto & formattingState = this ->formattingState ();
266+ auto & lines = formattingState.lines ();
267+ auto & lineRuns = formattingState.lineRuns ();
268+
269+ for (auto & outOfFlowBox : outOfFlowBoxes) {
270+ auto & outOfFlowGeometry = formattingState.boxGeometry (*outOfFlowBox);
271+ // Both previous float and out-of-flow boxes are skipped here. A series of adjoining out-of-flow boxes should all be placed
272+ // at the same static position (they don't affect next-sibling positions) and while floats do participate in the inline layout
273+ // their positions have already been taken into account during the inline layout.
274+ auto previousContentSkippingFloats = [&]() -> const Layout::Box* {
275+ auto * previousSibling = outOfFlowBox->previousSibling ();
276+ for (; previousSibling && previousSibling->isFloatingPositioned (); previousSibling = previousSibling->previousSibling ()) { }
277+ if (previousSibling)
278+ return previousSibling;
279+ // Parent is either the root here or another inline box (e.g. <span><img style="position: absolute"></span>)
280+ auto & parent = outOfFlowBox->parent ();
281+ return &parent == &root () ? nullptr : &parent;
282+ }();
283+
284+ if (!previousContentSkippingFloats) {
285+ // This is the first (non-float)child. Let's place it to the left of the first run.
286+ // <div><img style="position: absolute">text content</div>
287+ ASSERT (lineRuns.size ());
288+ outOfFlowGeometry.setLogicalTopLeft ({ lineRuns[0 ].logicalLeft (), lines[0 ].lineBoxLogicalRect ().top () });
289+ continue ;
290+ }
291+
292+ if (previousContentSkippingFloats->isOutOfFlowPositioned ()) {
293+ // Subsequent out-of-flow positioned boxes share the same static position.
294+ // <div>text content<img style="position: absolute"><img style="position: absolute"></div>
295+ outOfFlowGeometry.setLogicalTopLeft (BoxGeometry::borderBoxTopLeft (geometryForBox (*previousContentSkippingFloats)));
296+ continue ;
297+ }
298+
299+ ASSERT (previousContentSkippingFloats->isInFlow ());
300+ auto placeOutOfFlowBoxAfterPreviousInFlowBox = [&] {
301+ // The out-of-flow box should be placed after this inflow box.
302+ // Skip to the last run of this layout box. The last run's geometry is used to compute the out-of-flow box's static position.
303+ size_t lastRunIndexOnPreviousLayoutBox = 0 ;
304+ for (; lastRunIndexOnPreviousLayoutBox < lineRuns.size () && &lineRuns[lastRunIndexOnPreviousLayoutBox].layoutBox () != previousContentSkippingFloats; ++lastRunIndexOnPreviousLayoutBox) { }
305+ if (lastRunIndexOnPreviousLayoutBox == lineRuns.size ()) {
306+ // FIXME: In very rare cases, the previous box's content might have been completely collapsed and left us with no run.
307+ ASSERT_NOT_IMPLEMENTED_YET ();
308+ return ;
309+ }
310+ for (; lastRunIndexOnPreviousLayoutBox < lineRuns.size () && &lineRuns[lastRunIndexOnPreviousLayoutBox].layoutBox () == previousContentSkippingFloats; ++lastRunIndexOnPreviousLayoutBox) { }
311+ --lastRunIndexOnPreviousLayoutBox;
312+ // Let's check if the previous run is the last run on the current line and use the next run's left instead.
313+ auto & previousRun = lineRuns[lastRunIndexOnPreviousLayoutBox];
314+ auto * nextRun = lastRunIndexOnPreviousLayoutBox + 1 < lineRuns.size () ? &lineRuns[lastRunIndexOnPreviousLayoutBox + 1 ] : nullptr ;
315+
316+ if (nextRun && nextRun->lineIndex () == previousRun.lineIndex ()) {
317+ // Previous and next runs are on the same line. The out-of-flow box is right at the previous run's logical right.
318+ // <div>text<img style="position: absolute">content</div>
319+ auto logicalLeft = previousRun.logicalRight ();
320+ if (previousContentSkippingFloats->isInlineBox () && !previousContentSkippingFloats->isAnonymous ()) {
321+ // <div>text<span><img style="position: absolute">content</span></div>
322+ // or
323+ // <div>text<span>content</span><img style="position: absolute"></div>
324+ auto & inlineBoxBoxGeometry = geometryForBox (*previousContentSkippingFloats);
325+ logicalLeft = previousContentSkippingFloats == &outOfFlowBox->parent ()
326+ ? BoxGeometry::borderBoxLeft (inlineBoxBoxGeometry) + inlineBoxBoxGeometry.contentBoxLeft ()
327+ : BoxGeometry::borderBoxRect (inlineBoxBoxGeometry).right ();
328+ }
329+ outOfFlowGeometry.setLogicalTopLeft ({ logicalLeft, lines[previousRun.lineIndex ()].lineBoxLogicalRect ().top () });
330+ return ;
331+ }
332+
333+ if (nextRun) {
334+ // The out of flow box is placed at the beginning of the next line (where the first run on the line is).
335+ // <div>text<br><img style="position: absolute"><img style="position: absolute">content</div>
336+ outOfFlowGeometry.setLogicalTopLeft ({ nextRun->logicalLeft (), lines[nextRun->lineIndex ()].lineBoxLogicalRect ().top () });
337+ return ;
338+ }
339+
340+ auto & lastLineLogicalRect = lines[previousRun.lineIndex ()].lineBoxLogicalRect ();
341+ // This out-of-flow box is the last box.
342+ // FIXME: Use isLineBreak instead to cover preserved new lines too.
343+ if (previousRun.layoutBox ().isLineBreakBox ()) {
344+ // <div>text<br><img style="position: absolute"><img style="position: absolute"></div>
345+ outOfFlowGeometry.setLogicalTopLeft ({ lastLineLogicalRect.left (), lastLineLogicalRect.bottom () });
346+ return ;
347+ }
348+ // FIXME: We may need to check if this box actually fits the last line and move it over to the "next" line.
349+ outOfFlowGeometry.setLogicalTopLeft ({ previousRun.logicalRight (), lastLineLogicalRect.top () });
350+ };
351+ placeOutOfFlowBoxAfterPreviousInFlowBox ();
352+ }
353+ }
354+
256355IntrinsicWidthConstraints InlineFormattingContext::computedIntrinsicWidthConstraints ()
257356{
258357 auto & layoutState = this ->layoutState ();
@@ -291,7 +390,7 @@ IntrinsicWidthConstraints InlineFormattingContext::computedIntrinsicWidthConstra
291390 layoutBox = nextInlineLevelBoxToLayout (*layoutBox, root ());
292391 }
293392
294- collectInlineContentIfNeeded ();
393+ collectContentIfNeeded ();
295394
296395 auto maximumLineWidth = [&](auto availableWidth) {
297396 // Switch to the min/max formatting root width values before formatting the lines.
@@ -415,7 +514,7 @@ void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox, const
415514 boxGeometry.setVerticalMargin ({ contentHeightAndMargin.nonCollapsedMargin .before , contentHeightAndMargin.nonCollapsedMargin .after });
416515}
417516
418- void InlineFormattingContext::collectInlineContentIfNeeded ()
517+ void InlineFormattingContext::collectContentIfNeeded ()
419518{
420519 auto & formattingState = this ->formattingState ();
421520 if (!formattingState.inlineItems ().isEmpty ())
@@ -424,24 +523,28 @@ void InlineFormattingContext::collectInlineContentIfNeeded()
424523 // <span>text<span></span><img></span> -> [InlineBoxStart][InlineLevelBox][InlineBoxStart][InlineBoxEnd][InlineLevelBox][InlineBoxEnd]
425524 ASSERT (root ().hasInFlowOrFloatingChild ());
426525 LayoutQueue layoutQueue;
427- layoutQueue.append (root ().firstInFlowOrFloatingChild ());
526+ layoutQueue.append (root ().firstChild ());
428527 while (!layoutQueue.isEmpty ()) {
429528 while (true ) {
430529 auto & layoutBox = *layoutQueue.last ();
431- auto isBoxWithInlineContent = layoutBox.isInlineBox () && !layoutBox.isInlineTextBox () && !layoutBox.isLineBreakBox ();
432- if (!isBoxWithInlineContent )
530+ auto isInlineBoxWithInlineContent = layoutBox.isInlineBox () && !layoutBox.isInlineTextBox () && !layoutBox.isLineBreakBox () && !layoutBox. isOutOfFlowPositioned ();
531+ if (!isInlineBoxWithInlineContent )
433532 break ;
434533 // This is the start of an inline box (e.g. <span>).
435534 formattingState.addInlineItem ({ layoutBox, InlineItem::Type::InlineBoxStart });
436535 auto & inlineBoxWithInlineContent = downcast<ContainerBox>(layoutBox);
437- if (!inlineBoxWithInlineContent.hasInFlowOrFloatingChild ())
536+ if (!inlineBoxWithInlineContent.hasChild ())
438537 break ;
439- layoutQueue.append (inlineBoxWithInlineContent.firstInFlowOrFloatingChild ());
538+ layoutQueue.append (inlineBoxWithInlineContent.firstChild ());
440539 }
441540
442541 while (!layoutQueue.isEmpty ()) {
443542 auto & layoutBox = *layoutQueue.takeLast ();
444- if (is<LineBreakBox>(layoutBox)) {
543+ if (layoutBox.isOutOfFlowPositioned ()) {
544+ // Let's not construct InlineItems for out-of-flow content as they don't participate in the inline layout.
545+ // However to be able to static positioning them, we need to compute their approximate positions.
546+ formattingState.addOutOfFlowBox (layoutBox);
547+ } else if (is<LineBreakBox>(layoutBox)) {
445548 auto & lineBreakBox = downcast<LineBreakBox>(layoutBox);
446549 formattingState.addInlineItem ({ layoutBox, lineBreakBox.isOptional () ? InlineItem::Type::WordBreakOpportunity : InlineItem::Type::HardLineBreak });
447550 } else if (layoutBox.isFloatingPositioned ())
@@ -455,7 +558,7 @@ void InlineFormattingContext::collectInlineContentIfNeeded()
455558 else
456559 ASSERT_NOT_REACHED ();
457560
458- if (auto * nextSibling = layoutBox.nextInFlowOrFloatingSibling ()) {
561+ if (auto * nextSibling = layoutBox.nextSibling ()) {
459562 layoutQueue.append (nextSibling);
460563 break ;
461564 }
0 commit comments