Skip to content

[Sparse Strip]: Text API (outlines only)#3

Closed
taj-p wants to merge 23 commits intomainfrom
tajp/vello/outlineText
Closed

[Sparse Strip]: Text API (outlines only)#3
taj-p wants to merge 23 commits intomainfrom
tajp/vello/outlineText

Conversation

@taj-p
Copy link
Owner

@taj-p taj-p commented Mar 31, 2025

Intent

Adds an API to vello_cpu and vello_hybrid for drawing text:

    // A Vec of glyph positions: Vec<Glyph { id: GlyphId,  x: f32, y: f32 }>
    let glyphs = // ...

    ctx.glyph_run(&font)
        .normalized_coords(...)
        .font_size(size)
        .hint(true)
        .fill_glyphs(glyphs.iter());

Background

This API does not perform text shaping and layout. Those functions should be performed by other libraries like Parley or Cosmic Text. The API proposed here merely takes glyph positions and rendering attributes and renders them to screen.

Comparison with Vello

The API should feel familiar to Vello's implementation with one change:

  • The glyph run doesn't support a global transform as in Vello. Happy to change this, but I don't see why the consumer can't use the existing transform API on Scene. In other words, the scene's transform applying to every primitive except text seems unexpected and confusing. If text deserves special attention, I'm keen to understand that choice and this PR is certainly flexible to change. (This is my current understanding).

Design

To support both CPU and Hybrid variants of the renderer, we expose a GlyphRunBuilder from vello_common that accepts a GlyphRenderer trait. The builder encapsulates the logic to "prepare" some glyph for rendering by the GlyphRenderer. I've implemented GlyphRenderer for both our CPU and Hybrid variants as shown below.

impl GlyphRenderer for RenderContext {
fn fill_glyphs(&mut self, glyphs: impl Iterator<Item = PreparedGlyph>) {
for glyph in glyphs {
match glyph {
PreparedGlyph::Outline(glyph) => {
let transform = self.transform * glyph.local_transform;
flatten::fill(&glyph.path, transform, &mut self.line_buf);
self.render_path(self.fill_rule, self.paint.clone());
}
}
}
}
fn stroke_glyphs(&mut self, glyphs: impl Iterator<Item = PreparedGlyph>) {
for glyph in glyphs {
match glyph {
PreparedGlyph::Outline(glyph) => {
let transform = self.transform * glyph.local_transform;
flatten::stroke(&glyph.path, &self.stroke, transform, &mut self.line_buf);
self.render_path(Fill::NonZero, self.paint.clone());
}
}
}
}
}

impl GlyphRenderer for Scene {
fn fill_glyphs(&mut self, glyphs: impl Iterator<Item = PreparedGlyph>) {
for glyph in glyphs {
match glyph {
PreparedGlyph::Outline(glyph) => {
let transform = self.transform * glyph.local_transform;
flatten::fill(&glyph.path, transform, &mut self.line_buf);
self.render_path(self.fill_rule, self.paint.clone());
}
}
}
}
fn stroke_glyphs(&mut self, glyphs: impl Iterator<Item = PreparedGlyph>) {
for glyph in glyphs {
match glyph {
PreparedGlyph::Outline(glyph) => {
let transform = self.transform * glyph.local_transform;
flatten::stroke(&glyph.path, &self.stroke, transform, &mut self.line_buf);
self.render_path(Fill::NonZero, self.paint.clone());
}
}
}
}
}

Next Steps

This PR only supports outlined glyphs. We need to support Emoji through bitmap and colr variants. I'd prefer to do that in separate PRs and keep this PR focused on the API and overall strategy.

Notes

I haven't implemented caching of glyphs nor hinting instances. As per discussions, we wanted to keep the text API free from caching for now.

@taj-p taj-p changed the title Outline text support [Sparse Strip]: Text API (outlines only) Mar 31, 2025
@taj-p
Copy link
Owner Author

taj-p commented Mar 31, 2025

Closed in favour of linebender#883

@taj-p taj-p closed this Mar 31, 2025
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.

2 participants