Skip to content

Split out parley_core crate#591

Open
nicoburns wants to merge 20 commits intolinebender:mainfrom
nicoburns:parley_core
Open

Split out parley_core crate#591
nicoburns wants to merge 20 commits intolinebender:mainfrom
nicoburns:parley_core

Conversation

@nicoburns
Copy link
Copy Markdown
Collaborator

@nicoburns nicoburns commented Mar 27, 2026

Splits out a parley_core crate from the parley crate.

Motivation

Provide a lower-level API to Parley's core shaping/itemization/analysis pipeline that doesn't require users to buy into the high-level and more opinionated layout APIs.

Changes made

Code moved to fontique

  • The FontContext type (the only code in the font module) was moved to fontiques lib.rs. This is a thin wrapper for fontique::Collection and fontique::SourceCache.

Code moved to parley_core

  • The shape, analysis, and bidi modules. These modules collectively form the "meat" of Parley's core pipeline.

  • The lru_cache and convert modules. These are utilities which are only used by the shape module.

  • The style and inline_box modules. These are shared types that are needed by the core.

  • The resolve/mod.rs module (but not resolve/tree.rs or resolve/range.rs) (as resolve.rs). This converts user input styles into "resolved styles". Long-term it may be possible for this to live outside of "core", but for now it's easiest if it's included.

    Two small methods that convert ResolvedStyle and ResolvedDecoration into layout::Style and layout::Decoration were moved out of resolve/mod.rs into the layout module to avoid a core module referencing a non-core module.

  • Most of layout/data.rs, and all of the small layout/glyph.rs. This includes:

    • A bunch of data types that represent the output of shaping (LayoutItem, LayoutItemKind, RunData, RunMetrics, ClusterData, ClusterInfo and Glyph). These have been moved to a new shape/data.rs module. The wrapper types (Run, Cluster, etc) that hold a reference to these data type and implement a bunch of useful methods on them have not been moved.
    • The LayoutData::push_run method (and it's two helpers process_clusters and push_cluster). These contain complex logic which is really part of the core pipeline, and have have moved into shape/push_run.rs.
  • A ParleyCoreContext has been split out of LayoutContext. The bulk of the context has been moved into ParleyCoreContext. LayoutContext now wraps ParleyCoreContext and also retains the TreeStyleBuilder and RangeStyleBuilder fields.

  • The StyleRunBuilder has been moved into parley_core (builder.rs).

  • The nearly_eq function from the util module was duplicated into parley_core (the rest of util remains in parley)

  • The tests/test_analysis module. This has been lightly modified to not depend on the RangeBuilder.

Code that remains in parley

  • The layout module (except for parts of layout/data.rs detailed above)
  • The editing module
  • The TreeBuilder, TreeStyleBuilder, RangedBuilder, and RangedStyleBuilder (resolve/tree.rs, resolve/range.rs, and most of builder.rs)
  • The setting module was folded into lib.rs. It was just a few re-exports.
  • The tests/test_builders and tests/utils modules.

LayoutContext and ParleyCoreContext

The ShapeSink trait

A few parts of the core pipeline (the shape_item, push_run functions in the shape module, and the build_into_layout method in the context module) reference the layout::Layout/layout::layoutData types in the layout module in order to push data into it.

This dependency does not work with the crate split, so in order to break it I have introduced ShapeSink trait. This trait is defined in shape/push_run.rs, the functions above call methods on the trait, and it implemented by the layout::LayoutData type in the parley crate.

I have also added a ShapedText struct in parley_core that also implements this trait, this provides a default concrete repesentation of "all of the output of shaping". Using this would likely make sense for non-parley Rust consumers of parley_core, and future it could be also be used internally by Parley's LayoutData struct. This type (or a wrapper of if) could also eventually gain some low-level layout primitives/helpers.

The ShapeSink trait is currently defined as follows:

pub trait ShapeSink<B: Brush> {
    /// Clear data from a previous shaping run ready to accept new data
    fn clear(&mut self);
    /// This method is called when Parley Core has finished pushed data into the sink, and can be
    /// used as a hook to post-process that data. Parley's `Layout` type uses this to implement 
    /// the `word-spacing` and `letter-spacing` properties.
    fn finish(&mut self);

    // Setters for simple variables
    fn set_scale(&mut self, scale: f32);
    fn set_quantize(&mut self, quantize: bool);
    fn set_base_level(&mut self, level: u8);
    fn set_text_len(&mut self, len: usize);

    // Push methods for styles and inline boxes. These two need some API design work. They are different
    // the other `push` methods below for allocation efficiency reasons, but this could probably be done better.

    /// Set the entire list of inline boxes. TODO: make a "push" method?
    fn set_inline_boxes(&mut self, boxes: Vec<InlineBox>) -> Vec<InlineBox>;
    /// Push an entire list of `ResolvedStyle<B>` into the list of resolved styles
    fn push_styles(&mut self, styles: &[ResolvedStyle<B>]);

    // Push methods. The layout should store these data types.
    fn push_coords(&mut self, coords: &[harfrust::NormalizedCoord]) -> (usize, usize);
    fn push_font(&mut self, font: &FontData) -> usize;
    fn push_cluster(&mut self, cluster: ClusterData);
    fn push_glyph(&mut self, glyph: Glyph);
    fn push_run(&mut self, run: RunData);
    fn push_item(&mut self, item: LayoutItem);
    fn push_inline_box_item(&mut self, index: usize);

    /// Getters methods. Returns how many of the data type that sink currently has stored
    fn cluster_count(&self) -> usize;
    fn glyph_count(&self) -> usize;
    fn run_count(&self) -> usize;

    /// The clusters should be stored in a `Vec` or similar ordered list type. This method
    /// should reverse the order of a subslice of that list specified by `range`.
    fn reverse_cluster_range(&mut self, range: Range<usize>);
}

Open Questions

  • Does the StyleRunBuilder belong in core? I currently have it there and the idea was that RangeBuilder and TreeBuilder could build on top of it. However, I've found the API awkward for that purpose (forcing less efficient allocation patterns), and I'm now thinking we ought to expose some of the fields in ParleyCoreContext publically, and just carefully document the invariants.

    Then for now, the StyleRunBuilder could move back to parley with the other builders. And we can follow-up by splitting these out into individual crates if that is desired.

Future possibilties

  • Refactor the "builders" (RangeBuilder, TreeBuilder, StyleRunBuilder) so that (along with their coresponding "style builders (RangeStyleBuilder, TreeStyleBuilder) they can each live in their own standalone crates that depend only on parley_core. The key here would be to have the builder (e.g. RangeBuilder) depend on ParleyCoreContext and it's style builder (e.g. RangeStyleBuilder) rather than on LayoutContext as they currently so in Parley main (and still do in this PR).
  • Add low-level layout capabilities to parley_core. These would be "primitives" like "find the next break point" that could be built on to build a variety of different high-level layout strategies.
  • Remove the concept of scale factor from parley_core. Consumers of parley_core should resolve scale themselves before passing data into core.
  • Remove all non-shaping properties from parley_core (Style incrementality #432)
  • Create a "font database" trait that decouples parley and parley_core from Fontique
  • Eliminate the concept of "style resolving" from parley_core (and possibly also parley), by creating a trait that allows users to provide resolved styles on demand.

@nicoburns nicoburns force-pushed the parley_core branch 9 times, most recently from 4bf9247 to 197ae43 Compare March 27, 2026 16:26
@nicoburns nicoburns marked this pull request as ready for review March 27, 2026 16:44
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>
Signed-off-by: Nico Burns <nico@nicoburns.com>
…stead

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.

1 participant