Skip to content

parley: add style-run builder; accept precomputed runs from styled_text#516

Merged
waywardmonkeys merged 1 commit intolinebender:mainfrom
waywardmonkeys:resolved-runs-builder
Feb 20, 2026
Merged

parley: add style-run builder; accept precomputed runs from styled_text#516
waywardmonkeys merged 1 commit intolinebender:mainfrom
waywardmonkeys:resolved-runs-builder

Conversation

@waywardmonkeys
Copy link
Contributor

@waywardmonkeys waywardmonkeys commented Jan 16, 2026

  • Add LayoutContext::style_run_builder and a StyleRunBuilder API for constructing a Layout from contiguous, non-overlapping style runs, skipping Parley’s internal range-splitting step.
  • Update styled_text_parley to lower StyledText’s computed runs directly into StyleRunBuilder using TextStyle<'static, '_, B>.
  • Add a parity test ensuring StyleRunBuilder output matches RangedBuilder for equivalent styles.

@waywardmonkeys
Copy link
Contributor Author

This builds on top of PR #495 so only look at the right commit here ... but this shows a way to build a Layout going through less code and no resolution.

@waywardmonkeys
Copy link
Contributor Author

This is still doing some style resolution via resolve_entire_style_set ..

@nicoburns
Copy link
Collaborator

Makes a lot of sense. #387 does something very similar.

@nicoburns
Copy link
Collaborator

I will note that I think the "flat list of non-overlapping styles" model may be insufficient to represent some tree-shaped data. For example, it's going to be hard to represent <p><span style="padding: 5px"><span style="padding: 5px">hello</span></span> world</p>. Not sure what the solution is. Horizontal padding/margin/border seems like a very tricky problem in general due to bidi-reordering.

@xStrom
Copy link
Member

xStrom commented Jan 18, 2026

Why is that span example hard to represent? It's not clear to me, especially as there is no BiDi in that example.

@waywardmonkeys
Copy link
Contributor Author

@nicoburns This is probably just the first phase ... I think those sorts of things might change with time. There's a lot of stuff downstream from this.

@nicoburns
Copy link
Collaborator

Why is that span example hard to represent? It's not clear to me, especially as there is no BiDi in that example.

@xStrom The key thing is that the two spans start/end at exactly the same offset in the text. So in the model where the styles apply to glyphs in the text, how do you determine that you have "pushed" or "popped" two spans? For most styles (e.g. styles the modify the text itself) this doesn't matter: two nested spans isn't really any different to one span that applies both sets of styles. But for (horizontal) padding, the two nested spans here imply that the 5px padding should be applied twice (so an overall padding of 10px around the word "hello").

I think that to implement this you would need some kind of modelling of the tree structure within Parley (perhaps each style struct having depth: u8 and parent: Option<u16> fields or something?).

@nicoburns
Copy link
Collaborator

@waywardmonkeys Unrelated to my previous comment:

Is there a way to reuse styles with this API? For example, in <p>first<b>second</b>third</p>, the spans representing "first" and "third" would have the exact same set of styles. I believe the current TreeBuilder is taking advantage of this to simply refer to the already pushed Style rather than pushing a duplicate style. Although looking at build_layout_into it looks to me that this may result in incorrect "char_info` being set.

Basically, it might nice to have a layer of indirection between the style structs and the text ranges, so that more than one text range can point to the same style struct.

@xStrom
Copy link
Member

xStrom commented Jan 18, 2026

Regarding <p><span style="padding: 5px"><span style="padding: 5px">hello</span></span> world</p> and the double padding, maybe I'm missing something but it seems fairly trivial to flatten. You mentioned it yourself, you add the paddings together. Your style flattener needs to know which properties are additive and which are overrides. Of course for web compat you'll also get to deal with the juicy quirks of margin collapsing. However, I'm still not seeing any reason why it couldn't be described as flat resolved styles.


I haven't checked the PR but in genral style reuse via references seems like an important memory optimization. Just imagine a text where there's a bold word every now and then. You only need two actual style structs for this, not a multiple of how many bold words you have.

@waywardmonkeys
Copy link
Contributor Author

I haven't checked the PR but in genral style reuse via references seems like an important memory optimization. Just imagine a text where there's a bold word every now and then. You only need two actual style structs for this, not a multiple of how many bold words you have.

We're in the first steps of a long journey.

@nicoburns
Copy link
Collaborator

nicoburns commented Jan 18, 2026

You mentioned it yourself, you add the paddings together. Your style flattener needs to know which properties are additive and which are overrides.

Handling it as part of flattening is interesting possibility that for some reason I had not considered. I'd need to think it through, but I think this would probably work.

Of course for web compat you'll also get to deal with the juicy quirks of margin collapsing.

Luckily margin collapsing only applies to block-level elements, not inline-level elements, so we don't need to deal with this in Parley.

@dfrg
Copy link
Collaborator

dfrg commented Jan 27, 2026

I think that to implement this you would need some kind of modelling of the tree structure within Parley

The box-decoration-break property is the really tricky one, particularly with mixed direction text. My feeling is that these things are too complex to handle directly and my expectation is that parley will learn to generate fragments with breaking guided by the client and that these fragments can then be assembled in more complex ways by the calling code.

Copy link
Contributor

@taj-p taj-p left a comment

Choose a reason for hiding this comment

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

I love the idea of enabling an API into Parley that bypasses style resolution - to pass precomputed runs. Please re-request review once you're ready

@waywardmonkeys
Copy link
Contributor Author

@nicoburns

Is there a way to reuse styles with this API? For example, in

firstsecondthird

, the spans representing "first" and "third" would have the exact same set of styles. I believe the current TreeBuilder is taking advantage of this to simply refer to the already pushed Style rather than pushing a duplicate style. Although looking at build_layout_into it looks to me that this may result in incorrect "char_info` being set.

You’re right that first and third can have identical computed styles, but on main TreeBuilder did not actually share them in the flattened output; it emitted separate style entries per text run. The pipeline already used style indices, but those indices often pointed to duplicate-equal styles.

We can do better as part of a separate patch series that allows for style sharing and then adapt the builders to potentially support that (or a fast path for this StyleRunBuilder to take).

(I have several branches iterating on this concept and will submit some PRs soon.)

@waywardmonkeys
Copy link
Contributor Author

This has been updated now for the changes in #559. This includes stuff from #495 to show in action. I'll rebase it without those once it is ready for review.

@waywardmonkeys waywardmonkeys marked this pull request as ready for review February 19, 2026 00:57
@waywardmonkeys waywardmonkeys requested a review from taj-p February 19, 2026 00:57
@waywardmonkeys
Copy link
Contributor Author

I've now removed everything from this except for the changes intended to land. This is ready for review.

Comment on lines +89 to +97
/// Reserves additional capacity for styles and runs.
///
/// This is an optional optimization for callers that know counts
/// up front; call it before pushing styles and runs to reduce
/// reallocations.
pub fn reserve(&mut self, additional_styles: usize, additional_runs: usize) {
self.lcx.style_table.reserve(additional_styles);
self.lcx.style_runs.reserve(additional_runs);
}
Copy link
Member

Choose a reason for hiding this comment

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

Might be interesting at some point to have some reuse, so that there's not a reallocation cycle every time... but I guess I won't know until I'm using it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are reused already. This is just another knob.

@waywardmonkeys
Copy link
Contributor Author

This has an approval, so I am likely to land this in 12-24 hours unless I hear otherwise. It would be good to have feedback from the people who are interested in using it apart from my own code.

@nicoburns
Copy link
Collaborator

I'm currently unwell, so I only skimmed, but this looks pretty reasonable to me.

Copy link
Contributor

@taj-p taj-p left a comment

Choose a reason for hiding this comment

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

I ran out of time today. But this definitely looks reasonable 👍🎉

@waywardmonkeys waywardmonkeys added this pull request to the merge queue Feb 20, 2026
Merged via the queue into linebender:main with commit 1676ee9 Feb 20, 2026
24 checks passed
@waywardmonkeys waywardmonkeys deleted the resolved-runs-builder branch February 20, 2026 10:06
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.

6 participants

Comments