Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Floated boxes (CSS Floats) #99

Open
Tracked by #208
nicoburns opened this issue Aug 2, 2024 · 4 comments
Open
Tracked by #208

Floated boxes (CSS Floats) #99

nicoburns opened this issue Aug 2, 2024 · 4 comments
Labels
enhancement New feature or request

Comments

@nicoburns
Copy link
Contributor

nicoburns commented Aug 2, 2024

This issue tracks the implementation of CSS Floats in Parley (/API enhancements that enable CSS Floats to be implemented on top of Parley).


Code Flow

If we go with the latter option (APIs that enable Floats to implemented on top of Parley) then I think the requirements for Parley itself might actually be quite simple. Parley would need to:

  • Allow the max_advance to be set independently for each line
    When a float is present it takes up space meaning that the line is effectively shorter as far as line-breaking is concerned.

  • Allow each line's Y position to set as a parameter.
    Floats may take up all of the space in a line, meaning a line should start further down that it otherwise should.

  • Allow each line to have an horizontal offset (set externally).
    This would be similar to the offset used for end/center alignment, but would be in addition to it (this is to account for left floats which cause the text on lines they intersect to be shifted to the right).

  • To be able to yield control flow when a "floated box" is encountered in the layout. A "floated box" would be specified like the existing "inline boxes" but a simple enum field would be added to specify whether the box is "inline" or "floated".

    Parley would not necessarily need to be able to compute the correct position for the box. But it should yield to external code, making available the following information:

    • The id of the box
    • The Y position of the top of the current line
    • The X advance where the next glyph would be placed if it fit on the current line
    • The remaining space in the current line.
  • Use the per-line max_advance (or a separately specified alignment width) for alignment

The wider layout engine (that handles both box layout and text layout) would then be responsible for:

  • Computing the correct position of the floated box
  • Fixing up the max_advance and offset of the current line. Parley would be able to rely on the invariant that the new max_advance would be greater than the already-consumed space (and should perhaps enforce that).
  • Either:
    • Resuming layout of the current line
    • Commit the current line and starting layout of a new line

Parley API

In concrete API terms this would probably look like:

  • Float layout would be feature flagged
  • BreakLines::break_next would layout up to "the end of the line OR the next floated box". Returning the reason for which it has yielded (line end or floated box).
  • There would be a new method on BreakLines allowing callers to access the current state of the breaker for the purpose of positioning floated boxes (making this a separate method would keep it out of the way for the float-free case).
  • There would be methods on BreakLines to set the offset and max_advance of the current line. And to commit the current line.
  • BreakLines::break_remaining would become the simple implementation ignoring floats
  • Either a new method like break_remaining but float-aware would be added. Or it would be left up to external code to implement this itself on top of break_next.

Appendix A: The position of the floated box

The position of the floated box would be either:

  • At the end of the current line (the box fits on the current line and is float: right). In this case:
    • The line is shortened by the with of the box
    • Text layout (by parley) of the line is continued.
  • At the start of the current line (the box fits on the current line and is float: left). In this case:
    • The line is shortened by the width of the box
    • The line is offset by the width of the box
    • Text layout (by parley) of the line is continued.
  • At the start/end of the next line (the box does not fit on the current line). In this case:
    • The line is not altered but it is marked as a completed and a new line is started
    • External layout code will need to be able to specify the start Y position for the new line
    • External layout code may offset/shorten the new line prior to starting layout to account for tall floats from earlier in the layout that are already taking up space in the next line.
@nicoburns nicoburns added the enhancement New feature or request label Aug 2, 2024
@nicoburns nicoburns changed the title Feature: Floated boxes (CSS Floats) Floated boxes (CSS Floats) Dec 7, 2024
@wfdewith
Copy link
Contributor

To be able to yield control flow when a "floated box" is encountered in the layout. A "floated box" would be specified like the existing "inline boxes" but a simple enum field would be added to specify whether the box is "inline" or "floated".

Parley would not necessarily need to be able to compute the correct position for the box. But it should yield to external code, making available the following information:

* The id of the box

* The Y position of the top of the current line

* The X advance where the next glyph would be placed if it fit on the current line

* The remaining space in the current line.

It's not clear to me why this is necessary. If float positioning is delegated to a higher level layout engine, the only thing Parley needs to know is where a line can start and where a line can end. The high level layout engine can just compute the location of the floats and fill the remaining space with text. This would even work with non-square floats, such as floats with a shape-outline.

@nicoburns
Copy link
Contributor Author

nicoburns commented Jan 15, 2025

It's not clear to me why this is necessary. If float positioning is delegated to a higher level layout engine, the only thing Parley needs to know is where a line can start and where a line can end. The high level layout engine can just compute the location of the floats and fill the remaining space with text. This would even work with non-square floats, such as floats with a shape-outline.

@wfdewith It's because the position of the float depends on line-breaking. The position of a float is not specified in terms of pixels, it's specified as an index into the string of text that makes up a paragraph, and the position of float's top edge (y offset) is floored by the position of the top of the line of text that it belongs. Which you can only work out by running the line-breaking algorithm (which is of course something that Parley does).

I believe it also depends on the remaining space within that line when it encountered. So if you have:

text before the float [float] text after the float

Then the float will be pushed to the next line if it doesn't fit on the line in the space left after "text before the float" has already been laid out.

@wfdewith
Copy link
Contributor

@wfdewith It's because the position of the float depends on line-breaking. The position of a float is not specified in terms of pixels, it's specified as an index into the string of text that makes up a paragraph, and the position of float's top edge (y offset) is floored by the position of the top of the line of text that it belongs. Which you can only work out by running the line-breaking algorithm (which is of course something that Parley does).

I see now, it does make a difference when placing floats in the middle of the text. I'm still not sure what the exact behavior is supposed to be, but I'll try to figure that out.

I believe it also depends on the remaining space within that line when it encountered. So if you have:

text before the float [float] text after the float

Then the float will be pushed to the next line if it doesn't fit on the line in the space left after "text before the float" has already been laid out.

Ah right, it makes sense to want to support that, but as far as I know you can't make floats like the one below with just CSS, unless there is some trick I'm not aware of.

image

@nicoburns
Copy link
Contributor Author

I'm still not sure what the exact behavior is supposed to be, but I'll try to figure that out.

The rules are here: https://www.w3.org/TR/CSS22/visuren.html#float-position. But you'll probably find that isn't enough to fully grok how it works. I'd recommend: reading that a few times, reading some other articles on float positioning, and playing around with test snippets of HTML.

I would also add that we could start with an implementation that isn't perfect and evolve it towards actually being compliant. I have a very early start of support in Taffy here: https://github.com/DioxusLabs/taffy/blob/float-layout/src/compute/float.rs. I should probably do a write up for the Taffy side like I've done for Parley. The place_floated_box function probably needs min_y_position: f32 and occupied_line_width: f32 parameters.

as far as I know you can't make floats like the one below with just CSS, unless there is some trick I'm not aware of.

image

Yeah, you can't really do that. The text can only flow around one side. There are some kinda tricks as detailed here https://stackoverflow.com/a/41004410, but it involves duplicating content. The CSS specs for this https://drafts.csswg.org/css-exclusions/ and https://drafts.csswg.org/css-shapes-2/ haven't been finalised or widely implemented yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants