Skip to content

Enable floats and other advanced layouts#421

Merged
nicoburns merged 14 commits into
linebender:mainfrom
nicoburns:floats
Apr 17, 2026
Merged

Enable floats and other advanced layouts#421
nicoburns merged 14 commits into
linebender:mainfrom
nicoburns:floats

Conversation

@nicoburns

@nicoburns nicoburns commented Sep 26, 2025

Copy link
Copy Markdown
Collaborator

Related PRs

Introduction

This PR is aimed at enabling Blitz to implement CSS floats on top of Parley. However, the changes made are not float-specific, and will also enable other advanced layouts such as those that have "exluded regions" or want to lay out text into a complex shape.

Parley's existing architecture

  • While most users call the simple Layout::break_all_lines(max_advance) to perform line breaking, Parley does already have a lower-level Layout::break_lines() function which returns a BreakLines struct, on which you can call BreakLines::next(). Each call to BreakLines::next() lays out a single line and then returns. This PR extends the functionality of this method so that in addition returning when a line-break occurs, it will also return when:
    • It encounters a floated box
    • The configured max_line_height is exceeded
  • Parley's Line struct has min_coord and max_coord fields.

Changes Made

Per-line bounding box max_advance

When using the BreakLines struct directly, the max_advance (max width) can now be configured on a per-line basis. This makes it possible to create layouts with lines broken to different lengths. Additionally, it is valid to adjust the max_advance midway through line-breaking a line (it is advised to ensure that the existing content of the line still fits, although nothing terrible will happen if you don't (the contents will just overflow)).

Automatically determined (per-line) alignment width

Related to the per-line max-advances, the width used for alignment is now per-line and determined automatically and is equal to the max_advance for that line (at the time that the line is "committed"). This means that each line is aligned within it's own width bound, separately from the other liens. This is really the only way to sensibly align lines of differing widths, but making alignment width automatic had previously been discussed as being desirable anyway.

Added max_line_height

Each line now has a max_line_height. If at any point the line exceeds this height, control flow will be yielded from BreakLines::next(), allowing the user to find a new, larger space to lay out into. By default, max_line_height is set to f32::MAX and thus has no effect.

Customisable per-line x- and y-offset

Parley previously assumed that every line started at x=0 and that the start of the next line would be immediately below the previous line. Parley now allows an x and y offset to be manually configured for each line. This does not affect line breaking, but is used when reporting the positions of glyphs and for selection.

The x offset is in addition to and stored separately from the offset generated by alignment.

Added InlineBoxKind enum

A new kind: InlineBoxKind field has been added the InlineBox struct, where InlineBoxKind is defined as:

enum InlineBoxKind {
     InFlow,
     OutOfFlow,
     CustomOutOfFlow,
}
  • InlineBoxKind::InFlow represents the existing kind of inline box that is is laid out in-flow with text like a display: inline-block box in CSS.
  • InlineBoxKind::OutOfFlow is assigned a position during layout in exactly the same way as an InFlow box. However it does not take up space or affect the position of other items (glyphs or boxes) in the layout. This corresponds to a position: absolute box in CSS. Blitz was previously representing this as a zero-sized box, but I have taken the opportunity here to represent it explicitly.
  • InlineBoxKind::CustomOutOfFlow is the box kind for a floated box. Parley does not attempt to lay these out at all. When it encountered a box of this kind it yields control flow back to the caller who then responsible for positioning the box, adjusting the line's position/size, and then resuming layout (details below).

Tasks

  • Allow boxes to marked as in-flow or out-of-flow
  • Allow line-breaking to yield control flow when encountering a box
  • Allow users of parley to the x/y position and the max-width of each line
  • Update selection to account for line offsets
  • Update alignment to be per-line

@nicoburns nicoburns force-pushed the floats branch 6 times, most recently from dd3a1d6 to 62d4418 Compare September 30, 2025 14:25
@nicoburns nicoburns force-pushed the floats branch 2 times, most recently from e7a038b to 257754b Compare October 7, 2025 13:05
@nicoburns nicoburns force-pushed the floats branch 11 times, most recently from f6e5cef to e55c5cb Compare November 3, 2025 14:12
@nicoburns nicoburns force-pushed the floats branch 5 times, most recently from 486eb53 to d8566d3 Compare November 13, 2025 12:52
@nicoburns nicoburns changed the title WIP: Enable floats and other advanced layouts Enable floats and other advanced layouts Nov 13, 2025
@nicoburns nicoburns marked this pull request as ready for review November 13, 2025 16:03
@nicoburns nicoburns requested a review from taj-p November 13, 2025 17:29

@taj-p taj-p left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay on the review - this PR is actually way more complex than I expected. Kudos on being able to wrangle the complexity of line breaking to support this custom behaviour.

Comment thread parley/src/layout/layout.rs Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please update this documentation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this still needs a little bit of updating.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added suggestion below

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment thread parley/src/layout/line_break.rs Outdated
if self.state.layout_max_advance >= f32::MAX {
self.layout.data.alignment_width = full_width;
for line in &mut self.lines.lines {
line.metrics.inline_max_coord = line.metrics.inline_min_coord + width;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my information, why do we want to overwrite the inline_max_coord for a line with the maximum width + inline_min_coord? What utility does it provide?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I don't think this is correct anymore actually. The basic logic of "if a width constraint is infinite, then set it to a computed value" (+ we treat f32::MAX as infinite) makes sense. But I think it should be done on a line-by-line basis rather than using the layout_max_advance now.

The key thing this is trying to avoid is Parley performing alignment against an infinite width. That not only positions text completely incorrectly, but also causes performance issues.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've dug into this a bit. This handles the case where layout is called with a None or Infinite width constraint (and no line-specific constraints are set during the layout). In that case we'd like alignment to be relative to the width of the longest line.

It's a bit of an edge case (you probably shouldn't be calling layout with an infinite constraint), but I think this is a nice fallback behaviour for alignment in case you do.

Comment thread parley/src/layout/layout.rs Outdated
Comment thread parley/src/layout/line_break.rs
Comment thread parley_tests/tests/wrap.rs
Comment thread parley/src/inline_box.rs
Comment thread parley/src/tests/utils/renderer.rs Outdated
Comment thread parley/src/layout/line_break.rs
Comment thread parley/src/layout/data.rs
Comment thread parley/src/layout/line_break.rs Outdated
Comment thread examples/swash_render/src/main.rs
@nicoburns nicoburns force-pushed the floats branch 4 times, most recently from 34f30bf to c85fecd Compare March 31, 2026 18:46
@taj-p taj-p self-requested a review April 2, 2026 18:11

@taj-p taj-p left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This very much LGTM - left some minor comments. Just want to ensure I understand everything.

When you've addressed the comments, hit the "re-request review" button so it shows up in my review queue and I PROMISE I'll review it the next business day

Comment thread parley/src/layout/line_break.rs Outdated
Comment thread parley/src/layout/line_break.rs Outdated
Comment thread parley/src/layout/line_break.rs Outdated
Comment thread parley/src/layout/line_break.rs Outdated
Comment thread parley/src/layout/layout.rs Outdated
Comment thread parley/src/layout/layout.rs Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added suggestion below

Comment thread parley/src/layout/line_break.rs
Comment on lines +81 to +82
pub line_y_start: f64,
pub line_y_end: f64,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to specify somewhere that coordinates use physical x/y axes. For horizontal text, x corresponds to the inline direction and y to the block direction? Or is this captured elsewhere in Parley?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um... big question! Perhaps something we can revist when we actually support vertical text? (he says hopefully)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM!

he says hopefully

👀 From my understanding, I think this will be worked on in 2026

Comment thread parley_tests/snapshots/trailing_whitespace_ltr-soft_wrap.png
Comment thread parley/src/layout/line_break.rs

@taj-p taj-p left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Nico! Just checking whether you saw the previous comments - if you disagree with them, that's fine. Just want to check that they weren't missed accidentally.

Comment thread parley/src/layout/line_break.rs
Comment thread parley/src/layout/line_break.rs Outdated
Comment thread parley/src/layout/line_break.rs
Comment thread parley/src/layout/line_break.rs
Comment thread parley/src/layout/line_break.rs Outdated
Comment thread parley/src/layout/line_break.rs Outdated
struct BreakerState {
/// Reason that the line breaker has yielded control flow
pub enum YieldData {
/// Control flow was yielded because a line break was encountered

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we not want to adopt any of the below suggestions?

Comment thread parley/src/layout/line_break.rs Outdated
@nicoburns

Copy link
Copy Markdown
Collaborator Author

@taj-p I had missed a few things! I think I've got everything now.

@taj-p taj-p left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉 !!!! Let's goo!! Nice work!! 🚀

Comment thread parley/src/layout/layout.rs Outdated
Signed-off-by: Nico Burns <nico@nicoburns.com>

Ignore out-of-flow boxes for the purpose of whitespace collapsing

Rename coords to block

Signed-off-by: Nico Burns <nico@nicoburns.com>

Fixup tests (inline box kind)

Allow the caller to set x/y/max_advance for each line

Signed-off-by: Nico Burns <nico@nicoburns.com>

Add advance to box yield

Align each line to it's own max-width

Don't include last line in layout's height if it's empty

Fix Cluster::from_point to work with offset lines

Signed-off-by: Nico Burns <nico@nicoburns.com>

Remove alignment_width argument from tests

Use inline min coord in more places

Use layout_max_advance for rendering tests

Accept new snapshots

Signed-off-by: Nico Burns <nico@nicoburns.com>

Fix max_advance of f32::MAX

MaxLineHeight WIP

Signed-off-by: Nico Burns <nico@nicoburns.com>

Fix: only InFlow InlineBox's contribute size to the layout

Make InlineBoxKind Copy

Including InlineBoxKind in PositionedInlineBox

Replace break_on_box with InlineBoxKind::CustomOutOfFlow

Signed-off-by: Nico Burns <nico@nicoburns.com>

Remove is_infinite check in alignment

Signed-off-by: Nico Burns <nico@nicoburns.com>

Implement yielding when height is exceeded

Signed-off-by: Nico Burns <nico@nicoburns.com>

Update content_widths_rtl test snapshot

Signed-off-by: Nico Burns <nico@nicoburns.com>

Fixup swash reference

Fixup new tests

Fixup vello_cpu_render example

Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Floated boxes (CSS Floats)

2 participants