-
Notifications
You must be signed in to change notification settings - Fork 281
[Sparse Strip]: Text API (outlines only) #883
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
Changes from 4 commits
87d1801
db226ef
897c7a0
6ccfb54
a57bed9
c458e8c
b25c507
f4742e6
f0af18e
18bf5cd
7fdd1af
c64fd57
3ea9728
31e2d85
6e661a4
8284434
3d6735d
72ac145
9ab7341
01174dc
d4d5658
249abcf
dffe263
d410f7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Copyright 2025 the Vello Authors | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| //! Types for glyphs. | ||
|
|
||
| /// Positioned glyph. | ||
| #[derive(Copy, Clone, Default, Debug)] | ||
| pub struct Glyph { | ||
| /// The font-specific identifier for this glyph. | ||
| /// | ||
| /// This ID is specific to the font being used and corresponds to the | ||
| /// glyph index within that font. It is *not* a Unicode code point. | ||
| pub id: u32, | ||
| /// X-offset in run, relative to transform. | ||
| pub x: f32, | ||
| /// Y-offset in run, relative to transform. | ||
| pub y: f32, | ||
| } | ||
|
taj-p marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,4 +12,5 @@ pub use peniko; | |
| pub use peniko::color; | ||
| pub use peniko::kurbo; | ||
| pub mod execute; | ||
| pub mod glyph; | ||
| pub mod paint; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| // Copyright 2025 the Vello Authors | ||
| // SPDX-License-Identifier: Apache-2.0 OR MIT | ||
|
|
||
| //! Processing and drawing glyphs. | ||
|
|
||
| use crate::peniko::Font; | ||
| use skrifa::instance::Size; | ||
| use skrifa::outline::DrawSettings; | ||
| use skrifa::{ | ||
| GlyphId, MetadataProvider, | ||
| outline::{HintingInstance, HintingOptions, OutlinePen}, | ||
| }; | ||
| use vello_api::kurbo::{Affine, BezPath, Vec2}; | ||
|
|
||
| pub use vello_api::glyph::*; | ||
|
|
||
| /// A glyph prepared for rendering. | ||
| #[derive(Debug)] | ||
| pub enum PreparedGlyph { | ||
| /// A glyph defined by its outline. | ||
| Outline(OutlineGlyph), | ||
| // TODO: Image and Colr variants. | ||
| } | ||
|
|
||
| /// A glyph defined by a path (its outline) and a local transform. | ||
| #[derive(Debug)] | ||
| pub struct OutlineGlyph { | ||
| /// The path of the glyph. | ||
| pub path: BezPath, | ||
| /// The local transform of the glyph. | ||
| pub local_transform: Affine, | ||
| } | ||
|
|
||
| /// Trait for types that can render glyphs. | ||
| pub trait GlyphRenderer { | ||
| /// Fill glyphs with the current paint and fill rule. | ||
| fn fill_glyphs(&mut self, glyphs: impl Iterator<Item = PreparedGlyph>); | ||
|
|
||
| /// Stroke glyphs with the current paint and stroke settings. | ||
| fn stroke_glyphs(&mut self, glyphs: impl Iterator<Item = PreparedGlyph>); | ||
| } | ||
|
|
||
| /// A builder for configuring and drawing glyphs. | ||
| #[derive(Debug)] | ||
| pub struct GlyphRunBuilder<'a, T: GlyphRenderer + 'a> { | ||
| run: GlyphRun<'a>, | ||
| renderer: &'a mut T, | ||
| } | ||
|
|
||
| impl<'a, T: GlyphRenderer + 'a> GlyphRunBuilder<'a, T> { | ||
| /// Creates a new builder for drawing glyphs. | ||
| pub fn new(font: Font, renderer: &'a mut T) -> Self { | ||
| Self { | ||
| run: GlyphRun { | ||
| font, | ||
| font_size: 16.0, | ||
| glyph_transform: None, | ||
| hint: true, | ||
| normalized_coords: &[], | ||
| }, | ||
| renderer, | ||
| } | ||
| } | ||
|
|
||
| /// Set the font size in pixels per em. | ||
| pub fn font_size(mut self, size: f32) -> Self { | ||
| self.run.font_size = size; | ||
| self | ||
| } | ||
|
|
||
| /// Set the per-glyph transform. Can be used to apply skew to simulate italic text. | ||
| pub fn glyph_transform(mut self, transform: Affine) -> Self { | ||
| self.run.glyph_transform = Some(transform); | ||
|
taj-p marked this conversation as resolved.
|
||
| self | ||
| } | ||
|
|
||
| /// Set whether font hinting is enabled. | ||
| pub fn hint(mut self, hint: bool) -> Self { | ||
| self.run.hint = hint; | ||
| self | ||
| } | ||
|
|
||
| /// Set normalized variation coordinates for variable fonts. | ||
| pub fn normalized_coords(mut self, coords: &'a [NormalizedCoord]) -> Self { | ||
| self.run.normalized_coords = bytemuck::cast_slice(coords); | ||
| self | ||
| } | ||
|
|
||
| /// Consumes the builder and fills the glyphs with the current configuration. | ||
| pub fn fill_glyphs(self, glyphs: impl Iterator<Item = &'a Glyph>) { | ||
| self.renderer | ||
| .fill_glyphs(Self::prepare_glyphs(&self.run, glyphs)); | ||
| } | ||
|
|
||
| /// Consumes the builder and strokes the glyphs with the current configuration. | ||
| pub fn stroke_glyphs(self, glyphs: impl Iterator<Item = &'a Glyph>) { | ||
| self.renderer | ||
| .stroke_glyphs(Self::prepare_glyphs(&self.run, glyphs)); | ||
| } | ||
|
|
||
| fn prepare_glyphs( | ||
| run: &GlyphRun<'a>, | ||
| glyphs: impl Iterator<Item = &'a Glyph>, | ||
| ) -> impl Iterator<Item = PreparedGlyph> { | ||
|
taj-p marked this conversation as resolved.
Outdated
|
||
| let font = skrifa::FontRef::from_index(run.font.data.as_ref(), run.font.index).unwrap(); | ||
| let outlines = font.outline_glyphs(); | ||
|
DJMcNab marked this conversation as resolved.
Outdated
|
||
| let size = Size::new(run.font_size); | ||
|
DJMcNab marked this conversation as resolved.
Outdated
|
||
| let hinting_instance = if run.hint { | ||
|
DJMcNab marked this conversation as resolved.
Outdated
|
||
| // TODO: Cache hinting instance. | ||
| HintingInstance::new(&outlines, size, run.normalized_coords, HINTING_OPTIONS).ok() | ||
| } else { | ||
| None | ||
| }; | ||
| glyphs.filter_map(move |glyph| { | ||
| let draw_settings = if let Some(hinting_instance) = &hinting_instance { | ||
| DrawSettings::hinted(hinting_instance, false) | ||
| } else { | ||
| DrawSettings::unhinted(size, run.normalized_coords) | ||
| }; | ||
| let outline = outlines.get(GlyphId::new(glyph.id))?; | ||
| let mut path = OutlinePath(BezPath::new()); | ||
| outline.draw(draw_settings, &mut path).ok()?; | ||
| let mut transform = Affine::translate(Vec2::new(glyph.x as f64, glyph.y as f64)); | ||
| if let Some(glyph_transform) = run.glyph_transform { | ||
| transform *= glyph_transform; | ||
| } | ||
| Some(PreparedGlyph::Outline(OutlineGlyph { | ||
| path: path.0, | ||
| local_transform: transform, | ||
| })) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| /// A sequence of glyphs with shared rendering properties. | ||
| #[derive(Clone, Debug)] | ||
| struct GlyphRun<'a> { | ||
| /// Font for all glyphs in the run. | ||
| pub font: Font, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not immediately obvious to me why the fields of this private struct are public. But OTOH, if clippy isn't complaining about it, that's probably my lack of understanding here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch! Made private in 6e661a4 |
||
| /// Size of the font in pixels per em. | ||
| pub font_size: f32, | ||
| /// Per-glyph transform. Can be used to apply skew to simulate italic text. | ||
| pub glyph_transform: Option<Affine>, | ||
| /// Normalized variation coordinates for variable fonts. | ||
| pub normalized_coords: &'a [skrifa::instance::NormalizedCoord], | ||
| /// Controls whether font hinting is enabled. | ||
| pub hint: bool, | ||
| } | ||
|
|
||
| // TODO: Although these are sane defaults, we might want to make them | ||
| // configurable. | ||
| const HINTING_OPTIONS: HintingOptions = HintingOptions { | ||
| engine: skrifa::outline::Engine::AutoFallback, | ||
| target: skrifa::outline::Target::Smooth { | ||
| mode: skrifa::outline::SmoothMode::Lcd, | ||
| symmetric_rendering: false, | ||
| preserve_linear_metrics: true, | ||
| }, | ||
| }; | ||
|
|
||
| struct OutlinePath(BezPath); | ||
|
|
||
| // Note that we flip the y-axis to match our coordinate system. | ||
| impl OutlinePen for OutlinePath { | ||
| #[inline] | ||
| fn move_to(&mut self, x: f32, y: f32) { | ||
| self.0.move_to((x, -y)); | ||
| } | ||
|
|
||
| #[inline] | ||
| fn line_to(&mut self, x: f32, y: f32) { | ||
| self.0.line_to((x, -y)); | ||
| } | ||
|
|
||
| #[inline] | ||
| fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) { | ||
| self.0.curve_to((cx0, -cy0), (cx1, -cy1), (x, -y)); | ||
| } | ||
|
|
||
| #[inline] | ||
| fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) { | ||
| self.0.quad_to((cx, -cy), (x, -y)); | ||
| } | ||
|
|
||
| #[inline] | ||
| fn close(&mut self) { | ||
| self.0.close_path(); | ||
| } | ||
| } | ||
|
|
||
| /// A normalized variation coordinate (for variable fonts) in 2.14 fixed point format. | ||
| /// | ||
| /// In most cases, this can be [cast](bytemuck::cast_slice) from the | ||
| /// normalised coords provided by your text layout library. | ||
| /// | ||
| /// Equivalent to [`skrifa::instance::NormalizedCoord`], but defined | ||
| /// in Vello so that Skrifa is not part of Vello's public API. | ||
| /// This allows Vello to update its Skrifa in a patch release, and limits | ||
| /// the need for updates only to align Skrifa versions. | ||
| pub type NormalizedCoord = i16; | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| const _NORMALISED_COORD_SIZE_MATCHES: () = | ||
| assert!(size_of::<skrifa::instance::NormalizedCoord>() == size_of::<NormalizedCoord>()); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.