Skip to content

A float without a width takes the whole line, and every ancestor of a float grows to contain it #590

Open

Description

@jakejackson1

Part of #552 (step 28, values read wrongly).

Floats with a width work. Two things differ from CSS:

  • A float with no width takes the whole remaining line instead of shrinking to fit its content, so nothing flows beside it.
  • Every ancestor of a float grows to contain it. In CSS, a float sticks out of a parent in normal flow, and text after the parent flows beside it. Only a block that starts a new block formatting context contains its floats, such as one with overflow: hidden, display: flow-root or its own float.

Measured on gravitypdf at 60b82630, PHP 8.5.4, from the filled rectangles and the positions text is drawn at.

Document Browser mPDF
<div style="float:left;background:#0f0">aa</div><p>bb</p> a float as wide as aa, with bb beside it a 180 mm float; bb below it at y = 25.5 mm
<div style="background:#0f0"><div style="float:left;width:40mm;height:30mm">aa</div></div><p>zz</p> the green parent is 0 mm tall; zz beside the float the parent is 30 mm tall; zz below the float at y = 50.4 mm
the same with overflow: hidden on the parent the parent is 30 mm tall; zz below the same

Cause.

  • Width. With no width, BlockTag::open() sets css_set_width to the container's remaining width. The comment there reads "For now - just set to maximum width left" (src/Tag/BlockTag.php:636-644 for right, :680-690 for left). Shrinking to fit needs the content's widest line, which is not known until the content has been laid out.
  • Containment. When a float closes, BlockTag::close() records where it ends on every open ancestor (src/Tag/BlockTag.php:1310-1318). Each ancestor then moves its bottom down to that position when it closes (:1203-1222).

Fix.

  • Width. Shrink-to-fit is min(max(min-content, available), max-content). Table cells already work out the widths they need before anything is drawn. They track the longest word and the longest line as content is added (miw, s and maxs, src/Tag/Td.php:411-460), and _tableColumnWidth() (src/Mpdf.php:20547) sizes the columns from those. A float with no width can be sized the same way:

    • buffer its content
    • measure it
    • set css_set_width
    • lay it out

    The buffering has to hold nested blocks, which cells do. A measuring pass must leave no state behind, or StateSnapshot must put it back.

  • Containment. Record a float's end only up to the nearest ancestor that starts a block formatting context: the body, a float, a positioned block, a table cell, overflow other than visible, display: flow-root. Blocks after a parent that does not contain the float then flow beside it, which GetFloatDivInfo() has to allow across levels.

The containment change alters the output of existing documents. Say so in the PR, and list the snapshot fixtures that change.

Tests. A left and a right float with no width, holding one short word and holding a long paragraph. A float with no width beside a float with one. A parent with and without overflow: hidden, and with display: flow-root. A float crossing a page break.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions