glifo: Add proper support for caching stroked glyphs (#1550)
As in https://github.com/linebender/parley/pull/582, but now for here
since we did the migration.
---------
Co-authored-by: Laurenz Stampfl <laurenz@canva.com>
diff --git a/glifo/src/glyph.rs b/glifo/src/glyph.rs
index 30cf30e..e2952bd 100644
--- a/glifo/src/glyph.rs
+++ b/glifo/src/glyph.rs
@@ -285,8 +285,13 @@
let font_index = self.prepared_run.font.index;
let hinted = hinting_instance.is_some();
- let cache_enabled = self.atlas_cache_enabled
+ let colr_bitmap_cache_enabled = self.atlas_cache_enabled
&& hinted_size <= self.glyph_atlas.config().max_cached_font_size;
+ let outline_cache_enabled = colr_bitmap_cache_enabled
+ // Due to the various parameters that would need to be considered in the cache key,
+ // we never cache stroked outlines for now. For COLR and bitmap, this doesn't matter
+ // because they are always filled anyway.
+ && style == Style::Fill;
let render_glyph: fn(&mut R, PreparedGlyph<'_>, &mut C, &mut ImageCache) = match style {
Style::Fill => R::fill_glyph,
@@ -310,7 +315,7 @@
self.prepared_run.run_transform,
hinting_instance,
);
- let outline_cache_key = cache_enabled.then(|| {
+ let outline_cache_key = outline_cache_enabled.then(|| {
let fractional_x = outline_transform.translation().x.fract() as f32;
GlyphCacheKey::new(
font_id,
@@ -349,7 +354,7 @@
// COLR glyphs are never hinted and have no sub-pixel offset;
// context_color is part of the key because it affects painted layers.
- let cache_key = cache_enabled.then(|| GlyphCacheKey {
+ let cache_key = colr_bitmap_cache_enabled.then(|| GlyphCacheKey {
font_id,
font_index,
glyph_id: glyph.id,
@@ -428,7 +433,7 @@
// Bitmaps are not hinted and have no sub-pixel offset or
// context color; variation coords are irrelevant for fixed strikes.
- let cache_key = cache_enabled.then(|| GlyphCacheKey {
+ let cache_key = colr_bitmap_cache_enabled.then(|| GlyphCacheKey {
font_id,
font_index,
glyph_id: glyph.id,
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_bitmap_noto_stroked.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_bitmap_noto_stroked.png
new file mode 100644
index 0000000..5751b9d
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_bitmap_noto_stroked.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:42c171625da5fcbbb9037fc45ec81c884abbda617bea8b8f41496f60456e3a78
+size 10033
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_noto_stroked.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_noto_stroked.png
new file mode 100644
index 0000000..5e46b0f
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_colr_noto_stroked.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1b8e91836dcb499bbfc71170e12583de5ee8f5faf1c24d97b11918433a8fe97a
+size 9927
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_filled_then_stroked.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_filled_then_stroked.png
new file mode 100644
index 0000000..1a82977
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_filled_then_stroked.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f9ca8a3f2d206c18b80afa1fce8ef9e0e6ee28e449a18baa680d02a022916937
+size 6098
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_large_stroke_width.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_large_stroke_width.png
new file mode 100644
index 0000000..c8e79a4
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_large_stroke_width.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a409728ccaf05d8c21b23ef7a98f4e888ff4a925b4f8a5fa10841aaba64333b0
+size 3671
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_then_filled.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_then_filled.png
new file mode 100644
index 0000000..bfceb20
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_then_filled.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:688c2b64098483aef590957dd368b8da98d6c0c123ba2e3fd6162b8b340adfb7
+size 6069
diff --git a/sparse_strips/vello_sparse_tests/tests/glyph.rs b/sparse_strips/vello_sparse_tests/tests/glyph.rs
index 73badd5..b1f366d 100644
--- a/sparse_strips/vello_sparse_tests/tests/glyph.rs
+++ b/sparse_strips/vello_sparse_tests/tests/glyph.rs
@@ -11,7 +11,7 @@
use std::sync::Arc;
use vello_common::color::palette::css::{BLACK, BLUE, GREEN, REBECCA_PURPLE};
use vello_common::glyph::Glyph;
-use vello_common::kurbo::Affine;
+use vello_common::kurbo::{Affine, Stroke};
use vello_common::peniko::{Blob, FontData};
use vello_dev_macros::vello_test;
@@ -68,6 +68,68 @@
}
#[vello_test(width = 300, height = 70)]
+fn glyphs_large_stroke_width(ctx: &mut impl Renderer) {
+ let font_size: f32 = 50_f32;
+ let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);
+
+ ctx.set_transform(Affine::translate((0., f64::from(font_size))));
+ ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
+ ctx.set_stroke(Stroke {
+ width: 3.0,
+ ..Stroke::default()
+ });
+ ctx.glyph_run(&font)
+ .font_size(font_size)
+ .stroke_glyphs(glyphs.into_iter());
+}
+
+#[vello_test(width = 300, height = 120)]
+fn glyphs_stroked_then_filled(ctx: &mut impl Renderer) {
+ let font_size: f32 = 50_f32;
+ let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);
+
+ render_roboto_with_mode(
+ ctx,
+ &font,
+ font_size,
+ glyphs.iter().copied(),
+ Affine::translate((0., f64::from(font_size))),
+ DrawMode::Stroke,
+ );
+ render_roboto_with_mode(
+ ctx,
+ &font,
+ font_size,
+ glyphs.into_iter(),
+ Affine::translate((0., f64::from(font_size * 2.0))),
+ DrawMode::Fill,
+ );
+}
+
+#[vello_test(width = 300, height = 120)]
+fn glyphs_filled_then_stroked(ctx: &mut impl Renderer) {
+ let font_size: f32 = 50_f32;
+ let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);
+
+ render_roboto_with_mode(
+ ctx,
+ &font,
+ font_size,
+ glyphs.iter().copied(),
+ Affine::translate((0., f64::from(font_size))),
+ DrawMode::Fill,
+ );
+ render_roboto_with_mode(
+ ctx,
+ &font,
+ font_size,
+ glyphs.into_iter(),
+ Affine::translate((0., f64::from(font_size * 2.0))),
+ DrawMode::Stroke,
+ );
+}
+
+#[vello_test(width = 300, height = 70)]
fn glyphs_skewed(ctx: &mut impl Renderer) {
let font_size: f32 = 50_f32;
let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);
@@ -254,19 +316,64 @@
.fill_glyphs(glyphs.into_iter());
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum DrawMode {
+ Fill,
+ Stroke,
+}
+
+fn render_roboto_with_mode(
+ ctx: &mut impl Renderer,
+ font: &FontData,
+ font_size: f32,
+ glyphs: impl Iterator<Item = Glyph>,
+ transform: Affine,
+ mode: DrawMode,
+) {
+ ctx.set_transform(transform);
+ ctx.set_paint(REBECCA_PURPLE);
+ ctx.set_stroke(Stroke {
+ width: 3.0,
+ ..Stroke::default()
+ });
+ let builder = ctx.glyph_run(font).font_size(font_size);
+
+ match mode {
+ DrawMode::Fill => {
+ builder.fill_glyphs(glyphs);
+ }
+ DrawMode::Stroke => {
+ builder.stroke_glyphs(glyphs);
+ }
+ }
+}
+
#[vello_test(width = 250, height = 70, skip_hybrid, cpu_u8_tolerance = 1)]
fn glyphs_colr_noto(ctx: &mut impl Renderer) {
- render_colr_noto_with_transform(ctx, Affine::translate((0., 50.)));
+ render_colr_noto_with_transform(ctx, Affine::translate((0., 50.)), DrawMode::Fill);
+}
+
+#[vello_test(width = 250, height = 70, skip_hybrid, cpu_u8_tolerance = 1)]
+fn glyphs_colr_noto_stroked(ctx: &mut impl Renderer) {
+ render_colr_noto_with_transform(ctx, Affine::translate((0., 50.)), DrawMode::Stroke);
}
#[vello_test(width = 500, height = 140, skip_hybrid, cpu_u8_tolerance = 1)]
fn glyphs_colr_noto_scaled_2x(ctx: &mut impl Renderer) {
- render_colr_noto_with_transform(ctx, Affine::translate((0., 50.)).then_scale(2.0));
+ render_colr_noto_with_transform(
+ ctx,
+ Affine::translate((0., 50.)).then_scale(2.0),
+ DrawMode::Fill,
+ );
}
#[vello_test(width = 125, height = 35, skip_hybrid, cpu_u8_tolerance = 1)]
fn glyphs_colr_noto_scaled_half(ctx: &mut impl Renderer) {
- render_colr_noto_with_transform(ctx, Affine::translate((0., 50.)).then_scale(0.5));
+ render_colr_noto_with_transform(
+ ctx,
+ Affine::translate((0., 50.)).then_scale(0.5),
+ DrawMode::Fill,
+ );
}
#[vello_test(width = 350, height = 350, skip_hybrid, cpu_u8_tolerance = 3)]
@@ -274,6 +381,7 @@
render_colr_noto_with_transform(
ctx,
Affine::translate((175., 100.)) * Affine::rotate(std::f64::consts::FRAC_PI_4),
+ DrawMode::Fill,
);
}
@@ -284,6 +392,7 @@
Affine::translate((300., 150.))
* Affine::rotate(std::f64::consts::FRAC_PI_4)
* Affine::scale(2.0),
+ DrawMode::Fill,
);
}
@@ -292,6 +401,7 @@
render_colr_noto_with_transform(
ctx,
Affine::translate((0., 50.)) * Affine::scale_non_uniform(1.0, 2.0),
+ DrawMode::Fill,
);
}
@@ -302,9 +412,21 @@
Affine::translate((150., 150.))
* Affine::rotate(std::f64::consts::FRAC_PI_4)
* Affine::scale_non_uniform(1.0, 2.0),
+ DrawMode::Fill,
);
}
+#[vello_test(width = 250, height = 70, skip_hybrid)]
+fn glyphs_bitmap_noto_stroked(ctx: &mut impl Renderer) {
+ let font_size: f32 = 50_f32;
+ let (font, glyphs) = layout_glyphs_noto_cbtf("✅👀🎉🤠", font_size);
+
+ ctx.set_transform(Affine::translate((0., f64::from(font_size))));
+ ctx.glyph_run(&font)
+ .font_size(font_size)
+ .stroke_glyphs(glyphs.into_iter());
+}
+
#[cfg(target_os = "macos")]
#[vello_test(width = 200, height = 70, skip_hybrid, cpu_u8_tolerance = 2)]
fn glyphs_bitmap_apple(ctx: &mut impl Renderer) {
@@ -385,13 +507,16 @@
}
/// Hinting is disabled to preserve transforms passed to `prepare_colr_glyph`.
-fn render_colr_noto_with_transform(ctx: &mut impl Renderer, transform: Affine) {
+fn render_colr_noto_with_transform(ctx: &mut impl Renderer, transform: Affine, mode: DrawMode) {
let font_size: f32 = 50_f32;
let (font, glyphs) = layout_glyphs_noto_colr("✅👀🎉🤠", font_size);
ctx.set_transform(transform);
- ctx.glyph_run(&font)
- .font_size(font_size)
- .hint(false)
- .fill_glyphs(glyphs.into_iter());
+ let run = ctx.glyph_run(&font).font_size(font_size).hint(false);
+
+ if mode == DrawMode::Stroke {
+ run.stroke_glyphs(glyphs.into_iter());
+ } else {
+ run.fill_glyphs(glyphs.into_iter());
+ }
}