Force encoding of transform/style following glyph run (#425)

Fixes #424 which describes a case where encoding a glyph run breaks the optimization that omits duplicate transforms/styles from their respective streams.

This uses flags in the encoding to force the next transform/style to be emitted. An alternative implementation would be to unconditionally encode a transform/style after each glyph run but this seemed like the better approach to me.

Also changes SimpleText::add to use the glyph run API. This means all text in the vello demos now uses that code path rather than encoding glyph outlines directly.
diff --git a/Cargo.toml b/Cargo.toml
index 246c3e3..1e0efd6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,7 +58,7 @@
 [workspace.dependencies]
 bytemuck = { version = "1.12.1", features = ["derive"] }
 skrifa = "0.15.4"
-peniko = { git = "https://github.com/linebender/peniko", rev = "629fc3325b016a8c98b1cd6204cb4ddf1c6b3daa" }
+peniko = { git = "https://github.com/linebender/peniko", rev = "8717635681dedfab3e9f3741fcbc7f3318a82ff0" }
 
 # NOTE: Make sure to keep this in sync with the version badge in README.md
 wgpu = { version = "0.18" }
diff --git a/crates/encoding/src/encoding.rs b/crates/encoding/src/encoding.rs
index 019de23..a9eee83 100644
--- a/crates/encoding/src/encoding.rs
+++ b/crates/encoding/src/encoding.rs
@@ -41,9 +41,19 @@
     pub n_clips: u32,
     /// Number of unclosed clips/layers.
     pub n_open_clips: u32,
+    /// Flags that capture the current state of the encoding.
+    pub flags: u32,
 }
 
 impl Encoding {
+    /// Forces encoding of the next transform even if it matches
+    /// the current transform in the stream.
+    pub const FORCE_NEXT_TRANSFORM: u32 = 1;
+
+    /// Forces encoding of the next style even if it matches
+    /// the current style in the stream.
+    pub const FORCE_NEXT_STYLE: u32 = 2;
+
     /// Creates a new encoding.
     pub fn new() -> Self {
         Self::default()
@@ -66,6 +76,7 @@
         self.n_path_segments = 0;
         self.n_clips = 0;
         self.n_open_clips = 0;
+        self.flags = 0;
         #[cfg(feature = "full")]
         self.resources.reset();
         if !is_fragment {
@@ -93,6 +104,7 @@
                 .glyph_runs
                 .extend(other.resources.glyph_runs.iter().cloned().map(|mut run| {
                     run.glyphs.start += glyphs_base;
+                    run.glyphs.end += glyphs_base;
                     run.normalized_coords.start += coords_base;
                     run.stream_offsets.path_tags += offsets.path_tags;
                     run.stream_offsets.path_data += offsets.path_data;
@@ -141,6 +153,7 @@
         self.n_path_segments += other.n_path_segments;
         self.n_clips += other.n_clips;
         self.n_open_clips += other.n_open_clips;
+        self.flags = other.flags;
         if let Some(transform) = *transform {
             self.transforms
                 .extend(other.transforms.iter().map(|x| transform * *x));
@@ -168,19 +181,19 @@
 
     /// Encodes a fill style.
     pub fn encode_fill_style(&mut self, fill: Fill) {
-        let style = Style::from_fill(fill);
-        if self.styles.last() != Some(&style) {
-            self.path_tags.push(PathTag::STYLE);
-            self.styles.push(style);
-        }
+        self.encode_style(Style::from_fill(fill));
     }
 
     /// Encodes a stroke style.
     pub fn encode_stroke_style(&mut self, stroke: &Stroke) {
-        let style = Style::from_stroke(stroke);
-        if self.styles.last() != Some(&style) {
+        self.encode_style(Style::from_stroke(stroke));
+    }
+
+    fn encode_style(&mut self, style: Style) {
+        if self.flags & Self::FORCE_NEXT_STYLE != 0 || self.styles.last() != Some(&style) {
             self.path_tags.push(PathTag::STYLE);
             self.styles.push(style);
+            self.flags &= !Self::FORCE_NEXT_STYLE;
         }
     }
 
@@ -189,9 +202,12 @@
     /// If the given transform is different from the current one, encodes it and
     /// returns true. Otherwise, encodes nothing and returns false.
     pub fn encode_transform(&mut self, transform: Transform) -> bool {
-        if self.transforms.last() != Some(&transform) {
+        if self.flags & Self::FORCE_NEXT_TRANSFORM != 0
+            || self.transforms.last() != Some(&transform)
+        {
             self.path_tags.push(PathTag::TRANSFORM);
             self.transforms.push(transform);
+            self.flags &= !Self::FORCE_NEXT_TRANSFORM;
             true
         } else {
             false
@@ -381,6 +397,12 @@
         }
     }
 
+    /// Forces the next transform and style to be encoded even if they match
+    /// the current state.
+    pub fn force_next_transform_and_style(&mut self) {
+        self.flags |= Self::FORCE_NEXT_TRANSFORM | Self::FORCE_NEXT_STYLE;
+    }
+
     // Swap the last two tags in the path tag stream; used for transformed
     // gradients.
     pub fn swap_last_path_tags(&mut self) {
diff --git a/examples/scenes/src/simple_text.rs b/examples/scenes/src/simple_text.rs
index 060a015..a79d3b6 100644
--- a/examples/scenes/src/simple_text.rs
+++ b/examples/scenes/src/simple_text.rs
@@ -17,7 +17,7 @@
 use std::sync::Arc;
 
 use vello::{
-    glyph::{Glyph, GlyphContext},
+    glyph::Glyph,
     kurbo::Affine,
     peniko::{Blob, Brush, BrushRef, Font, StyleRef},
     skrifa::{raw::FontRef, MetadataProvider},
@@ -136,35 +136,18 @@
         transform: Affine,
         text: &str,
     ) {
-        let default_font = FontRef::new(ROBOTO_FONT).unwrap();
-        let font = font.and_then(to_font_ref).unwrap_or(default_font);
-        let font_size = vello::skrifa::instance::Size::new(size);
-        let var_loc = vello::skrifa::instance::LocationRef::default();
-        let charmap = font.charmap();
-        let metrics = font.metrics(font_size, var_loc);
-        let line_height = metrics.ascent - metrics.descent + metrics.leading;
-        let glyph_metrics = font.glyph_metrics(font_size, var_loc);
-        let mut pen_x = 0f64;
-        let mut pen_y = 0f64;
-        let vars: [(&str, f32); 0] = [];
-        let mut gcx = GlyphContext::new();
-        let mut provider = gcx.new_provider(&font, size, false, &vars);
-        for ch in text.chars() {
-            if ch == '\n' {
-                pen_y += line_height as f64;
-                pen_x = 0.0;
-                continue;
-            }
-            let gid = charmap.map(ch).unwrap_or_default();
-            let advance = glyph_metrics.advance_width(gid).unwrap_or_default() as f64;
-            if let Some(glyph) = provider.get(gid.to_u16(), brush) {
-                let xform = transform
-                    * Affine::translate((pen_x, pen_y))
-                    * Affine::scale_non_uniform(1.0, -1.0);
-                builder.append(&glyph, Some(xform));
-            }
-            pen_x += advance;
-        }
+        use vello::peniko::{Color, Fill};
+        let brush = brush.unwrap_or(&Brush::Solid(Color::WHITE));
+        self.add_run(
+            builder,
+            font,
+            size,
+            brush,
+            transform,
+            None,
+            Fill::NonZero,
+            text,
+        );
     }
 }
 
diff --git a/src/scene.rs b/src/scene.rs
index 084bd24..86ebe97 100644
--- a/src/scene.rs
+++ b/src/scene.rs
@@ -346,5 +346,9 @@
         resources.glyph_runs.push(self.run);
         resources.patches.push(Patch::GlyphRun { index });
         self.encoding.encode_brush(self.brush, self.brush_alpha);
+        // Glyph run resolve step affects transform and style state in a way
+        // that is opaque to the current encoding.
+        // See <https://github.com/linebender/vello/issues/424>
+        self.encoding.force_next_transform_and_style();
     }
 }