sparse_strips: Apply stroke adjustment
diff --git a/glifo/src/glyph.rs b/glifo/src/glyph.rs
index 67fe961..be0dc57 100644
--- a/glifo/src/glyph.rs
+++ b/glifo/src/glyph.rs
@@ -498,6 +498,18 @@
         }
     }
 
+    /// Return the scaling factor that should be applied to the stroke width when stroking this
+    /// glyph run.
+    pub fn stroke_adjustment(&self) -> f64 {
+        let run_size = self.prepared_run.run_size;
+
+        if run_size == 0.0 {
+            1.0
+        } else {
+            f64::from(self.prepared_run.draw_props.font_size / run_size)
+        }
+    }
+
     /// Render a decoration (like an underline) that skips over glyph descenders.
     ///
     /// This implements `text-decoration-skip-ink`-like behavior, where the decoration line is interrupted where it
diff --git a/sparse_strips/vello_cpu/src/render.rs b/sparse_strips/vello_cpu/src/render.rs
index 701e329..620112b 100644
--- a/sparse_strips/vello_cpu/src/render.rs
+++ b/sparse_strips/vello_cpu/src/render.rs
@@ -469,11 +469,17 @@
         self.state.stroke = stroke;
     }
 
-    /// Get the current stroke
+    /// Get the current stroke.
     pub fn stroke(&self) -> &Stroke {
         &self.state.stroke
     }
 
+    /// Get a mutable reference to the current stroke.
+    #[cfg(feature = "text")]
+    pub(crate) fn stroke_mut(&mut self) -> &mut Stroke {
+        &mut self.state.stroke
+    }
+
     /// Set the current paint.
     ///
     /// If the paint is an image with `ImageSource::OpaqueId`, it will be
diff --git a/sparse_strips/vello_cpu/src/text.rs b/sparse_strips/vello_cpu/src/text.rs
index d713130..e5aebd1 100644
--- a/sparse_strips/vello_cpu/src/text.rs
+++ b/sparse_strips/vello_cpu/src/text.rs
@@ -262,7 +262,13 @@
     where
         Glyphs: Iterator<Item = Glyph> + Clone,
     {
-        self.render_glyphs(run, glyphs, |glyph_run, ctx| glyph_run.stroke_glyphs(ctx));
+        self.render_glyphs(run, glyphs, |glyph_run, ctx| {
+            let stroke_adjustment = glyph_run.stroke_adjustment();
+            let original_width = ctx.stroke().width;
+            ctx.stroke_mut().width *= stroke_adjustment;
+            glyph_run.stroke_glyphs(ctx);
+            ctx.stroke_mut().width = original_width;
+        });
     }
 }
 
diff --git a/sparse_strips/vello_hybrid/src/scene.rs b/sparse_strips/vello_hybrid/src/scene.rs
index 41a6493..c22bb9c 100644
--- a/sparse_strips/vello_hybrid/src/scene.rs
+++ b/sparse_strips/vello_hybrid/src/scene.rs
@@ -738,6 +738,17 @@
         self.render_state.stroke = stroke;
     }
 
+    /// Get the current stroke.
+    pub fn stroke(&self) -> &Stroke {
+        &self.render_state.stroke
+    }
+
+    /// Get a mutable reference to the current stroke.
+    #[cfg(feature = "text")]
+    pub(crate) fn stroke_mut(&mut self) -> &mut Stroke {
+        &mut self.render_state.stroke
+    }
+
     /// Set the paint for subsequent rendering operations.
     // TODO: This API is not final. Supporting images from a pixmap is explicitly out of scope.
     //       Instead images should be passed via a backend-agnostic opaque id, and be hydrated at
diff --git a/sparse_strips/vello_hybrid/src/text.rs b/sparse_strips/vello_hybrid/src/text.rs
index 15ad4be..282c926 100644
--- a/sparse_strips/vello_hybrid/src/text.rs
+++ b/sparse_strips/vello_hybrid/src/text.rs
@@ -251,7 +251,11 @@
         Glyphs: Iterator<Item = Glyph> + Clone,
     {
         self.render_glyphs(run, glyphs, |glyph_run, scene| {
+            let stroke_adjustment = glyph_run.stroke_adjustment();
+            let original_width = scene.stroke().width;
+            scene.stroke_mut().width *= stroke_adjustment;
             glyph_run.stroke_glyphs(scene);
+            scene.stroke_mut().width = original_width;
         });
     }
 }
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_scaled_up.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_scaled_up.png
new file mode 100644
index 0000000..89f0b05
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_scaled_up.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:27d06e778645c3a5c41e4ebdc1e2e22e173009bd59906710762ce43de9874908
+size 3761
diff --git a/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_scaled_up_cached.png b/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_scaled_up_cached.png
new file mode 100644
index 0000000..89f0b05
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/glyphs_stroked_scaled_up_cached.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:27d06e778645c3a5c41e4ebdc1e2e22e173009bd59906710762ce43de9874908
+size 3761
diff --git a/sparse_strips/vello_sparse_tests/tests/glyph.rs b/sparse_strips/vello_sparse_tests/tests/glyph.rs
index 4451716..e79bcaf 100644
--- a/sparse_strips/vello_sparse_tests/tests/glyph.rs
+++ b/sparse_strips/vello_sparse_tests/tests/glyph.rs
@@ -155,6 +155,24 @@
 }
 
 #[vello_test(width = 300, height = 70, glyph)]
+fn glyphs_stroked_scaled_up(ctx: &mut impl Renderer, enable_caching: bool) {
+    let font_size: f32 = 5_f32;
+    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);
+
+    ctx.set_transform(Affine::translate((0., f64::from(font_size))).then_scale(10.0));
+    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
+    ctx.set_stroke(Stroke {
+        width: 0.3,
+        ..Stroke::default()
+    });
+    ctx.glyph_run(&font)
+        .font_size(font_size)
+        .atlas_cache(enable_caching)
+        .hint(false)
+        .stroke_glyphs(glyphs.into_iter());
+}
+
+#[vello_test(width = 300, height = 70, glyph)]
 fn glyphs_large_stroke_width(ctx: &mut impl Renderer, enable_caching: bool) {
     let font_size: f32 = 50_f32;
     let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);