Skip to content

Implement parley_core::ShapedText#679

Open
tomcur wants to merge 4 commits into
linebender:mainfrom
tomcur:parley-core-shapedtext
Open

Implement parley_core::ShapedText#679
tomcur wants to merge 4 commits into
linebender:mainfrom
tomcur:parley-core-shapedtext

Conversation

@tomcur

@tomcur tomcur commented Jul 9, 2026

Copy link
Copy Markdown
Member

This moves shaped run processing from parley to parley_core, and lets parley_core own the shaped results in ShapedText. parley_core now fully owns shaping.

parley stores ShapedText in its LayoutData, dropping its cluster/glyph/etc. vecs. It keeps some run data as a parallel vector to ShapedText::runs, storing some data that parley_core is not aware of.

A large part of the diff (process_clusters) was moved verbatim to core. ShapedContext::push_run is mostly blocks moved out of the old fn push_run that was in parley/src/layout/data.rs.

There's one escape hatch that we should try to get rid of: because parley was mutating shaped data in-place for justification and letter spacing and such, parley_core exposes #[doc(hidden)] mutable access to two ShapedText fields to keep parley happy. This in-place mutation isn't really great in itself, e.g., for justification, parley already has a hack to "unjustify" in-place... but with reshaping I suspect it's untenable.

Benches close to neutral on my machine
Default Style - arabic 20 characters               [   8.8 us ...   9.0 us ]      +2.10%*
Default Style - latin 20 characters                [   4.3 us ...   4.3 us ]      -0.18%
Default Style - japanese 20 characters             [   8.2 us ...   8.3 us ]      +0.88%
Default Style - arabic 1 paragraph                 [  47.7 us ...  47.9 us ]      +0.48%
Default Style - latin 1 paragraph                  [  16.6 us ...  16.6 us ]      +0.40%
Default Style - japanese 1 paragraph               [  70.1 us ...  70.7 us ]      +0.81%
Default Style - arabic 4 paragraph                 [ 200.7 us ... 200.4 us ]      -0.17%
Default Style - latin 4 paragraph                  [  62.2 us ...  62.9 us ]      +1.07%*
Default Style - japanese 4 paragraph               [  99.5 us ... 100.0 us ]      +0.52%
Styled - arabic 20 characters                      [   9.8 us ...  10.0 us ]      +1.85%*
Styled - latin 20 characters                       [   5.4 us ...   5.4 us ]      +0.19%
Styled - japanese 20 characters                    [   8.7 us ...   8.8 us ]      +0.92%
Styled - arabic 1 paragraph                        [  50.2 us ...  50.5 us ]      +0.63%
Styled - latin 1 paragraph                         [  20.9 us ...  21.2 us ]      +1.17%*
Styled - japanese 1 paragraph                      [  75.9 us ...  76.8 us ]      +1.27%*
Styled - arabic 4 paragraph                        [ 219.2 us ... 219.3 us ]      +0.06%
Styled - latin 4 paragraph                         [  81.6 us ...  82.4 us ]      +0.96%
Styled - japanese 4 paragraph                      [ 107.1 us ... 108.4 us ]      +1.23%*

@tomcur tomcur force-pushed the parley-core-shapedtext branch 2 times, most recently from b413454 to 41e0700 Compare July 9, 2026 15:19
@nicoburns

Copy link
Copy Markdown
Collaborator

There's one escape hatch that we should try to get rid of: because parley was mutating shaped data in-place for justification and letter spacing and such, parley_core exposes #[doc(hidden)] mutable access to two ShapedText fields to keep parley happy.

Why are the fields of ShapedText private at all? Wouldn't it be useful to consumers to have full access to the data?

@tomcur tomcur force-pushed the parley-core-shapedtext branch from 41e0700 to 8203d81 Compare July 9, 2026 15:40
@tomcur

tomcur commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Why are the fields of ShapedText private at all? Wouldn't it be useful to consumers to have full access to the data?

The API gives full read access. I'm not sure about mutable access, as there are quite a few invariants that need to be upheld. There's a lot of cross-referencing; e.g., runs contain ranges tiling ShapedText's vecs, and e.g., run advances must be the sum of their glyphs' advances as well as their clusters' advances. Once we have reshaping, there'll be a bit more going on still.

Of course if a user has mutable access and breaks something, that's mostly fine as it's their own fault, but I'm not sure mutation is strongly needed. Parley currently only mutates advances in-place, but it's hacky. E.g. if a layout is justified, the advances are changed, and relayout requires a pass to unjustify first. As the original values are now lost, it's doing the same calculations in reverse; this kind of thing is going to drift because of compounding rounding errors after enough passes.

If ShapedText is a static source of truth for a paragraph, mutations are always applied from a clean slate. With what I have in mind currently, reshaping also only reads ShapedText and doesn't mutate it.

(I think having a bit of a closer look at what exactly the API exposes would be good, after this PR lands we've mostly arrived at the targeted seam between parley_core and parley.)

@tomcur tomcur force-pushed the parley-core-shapedtext branch from 13a1cf7 to 99881e5 Compare July 10, 2026 14:30
tomcur added 4 commits July 13, 2026 00:16
This moves run processing from `parley` to `parley_core`, and lets
`parley_core` own the shaped results. `parley_core` now fully owns
shaping.

`parley` replaces its cluster/glyph/etc. stores in `LayoutData` with
`ShapedText`, and keeping its run data (though dropping some fields) to
store some run data `parley_core` is not aware of.

There's one escape hatch that we should try to get rid of soon: because
`parley` was mutating shaped data in-place for justification and letter
spacing and such, `parley_core` exposes `#[doc(hidden)]` mutable access to two `ShapedText` fields to keep `parley` happy. That won't work anymore once we get reshaping across breaks. (For justification, that also meant we have a hack
that has to unjustify in-place...)
@tomcur tomcur force-pushed the parley-core-shapedtext branch from 0c5b94e to f94e80e Compare July 12, 2026 22:16
Comment on lines +17 to +23
/// A normalized font coordinate.
///
/// This is a 16-bit fixed-point number with a 14-bit fractional part. For font coordinates, its
/// useful values are in the range -1.0..=1.0.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct NormalizedCoord(i16);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This prevents exposing font_types::F2Dot14, but perhaps we should add bytemuck support so users can easily cast slices.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this type makes sense, but it ought to live in parlance (we have also previously just used plain i16 for this).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

3 participants