[harfrust] Reuse cached per-shape metadata (#6200)
diff --git a/src/rust/shape.rs b/src/rust/shape.rs
index 45a47ea..fa81c25 100644
--- a/src/rust/shape.rs
+++ b/src/rust/shape.rs
@@ -135,6 +135,9 @@
 
 pub struct HBHarfRustFontData {
     instance: FontInstance,
+    x_scale: i32,
+    y_scale: i32,
+    ptem: Option<f32>,
 }
 
 struct HBHarfBuzzFontFuncs {
@@ -251,7 +254,18 @@
     let face_data = face_data as *const HBHarfRustFaceData;
 
     let instance = font_to_instance(font, &(*face_data).font);
-    let hr_font_data = HBHarfRustFontData { instance };
+    let mut x_scale = 0;
+    let mut y_scale = 0;
+    hb_font_get_scale(font, &mut x_scale, &mut y_scale);
+    let ptem = hb_font_get_ptem(font);
+    let ptem = (ptem > 0.0).then_some(ptem);
+    // HarfBuzz invalidates shaper font data when any of these values change.
+    let hr_font_data = HBHarfRustFontData {
+        instance,
+        x_scale,
+        y_scale,
+        ptem,
+    };
 
     let hr_font_data = Box::new(hr_font_data);
     let hr_font_data_ptr = Box::into_raw(hr_font_data);
@@ -366,6 +380,7 @@
     let hr_buffer_box = hr_buffer_box as *mut harfrust::UnicodeBuffer;
     let mut hr_buffer_box = Box::from_raw(hr_buffer_box);
     let mut hr_buffer = *hr_buffer_box;
+    let shape_plan = (shape_plan as *const harfrust::ShapePlan).as_ref();
 
     // Set buffer properties
     let cluster_level = hb_buffer_get_cluster_level(buffer);
@@ -393,27 +408,32 @@
         hr_buffer.set_not_found_variation_selector_glyph(not_found_variation_selector_glyph);
     }
 
-    // Segment properties:
-    let script = hb_buffer_get_script(buffer);
-    let language = hb_buffer_get_language(buffer);
-    let direction = hb_buffer_get_direction(buffer);
-    // Convert to HarfRust types
-    let script = harfrust::Script::from_iso15924_tag(Tag::from_u32(script as u32))
-        .unwrap_or(harfrust::script::UNKNOWN);
-    let language = hb_language_to_hr_language(language);
-    let direction = match direction {
-        hb_direction_t_HB_DIRECTION_LTR => harfrust::Direction::LeftToRight,
-        hb_direction_t_HB_DIRECTION_RTL => harfrust::Direction::RightToLeft,
-        hb_direction_t_HB_DIRECTION_TTB => harfrust::Direction::TopToBottom,
-        hb_direction_t_HB_DIRECTION_BTT => harfrust::Direction::BottomToTop,
-        _ => harfrust::Direction::Invalid,
-    };
-    // Set properties on the buffer
-    hr_buffer.set_script(script);
-    if let Some(lang) = language {
-        hr_buffer.set_language(lang);
+    if let Some(plan) = shape_plan {
+        // Language is only used to build the plan; shaping with an explicit
+        // plan reads its cached segment properties instead of buffer language.
+        hr_buffer.set_script(plan.script().unwrap_or(harfrust::script::UNKNOWN));
+        hr_buffer.set_direction(plan.direction());
+    } else {
+        // Convert segment properties when shaping without a cached plan.
+        let script = hb_buffer_get_script(buffer);
+        let language = hb_buffer_get_language(buffer);
+        let direction = hb_buffer_get_direction(buffer);
+        let script = harfrust::Script::from_iso15924_tag(Tag::from_u32(script as u32))
+            .unwrap_or(harfrust::script::UNKNOWN);
+        let language = hb_language_to_hr_language(language);
+        let direction = match direction {
+            hb_direction_t_HB_DIRECTION_LTR => harfrust::Direction::LeftToRight,
+            hb_direction_t_HB_DIRECTION_RTL => harfrust::Direction::RightToLeft,
+            hb_direction_t_HB_DIRECTION_TTB => harfrust::Direction::TopToBottom,
+            hb_direction_t_HB_DIRECTION_BTT => harfrust::Direction::BottomToTop,
+            _ => harfrust::Direction::Invalid,
+        };
+        hr_buffer.set_script(script);
+        if let Some(lang) = language {
+            hr_buffer.set_language(lang);
+        }
+        hr_buffer.set_direction(direction);
     }
-    hr_buffer.set_direction(direction);
 
     // Populate buffer
     let count = hb_buffer_get_length(buffer);
@@ -428,19 +448,12 @@
     let post_context = std::slice::from_raw_parts(post_context, post_context_length as usize);
     hr_buffer.set_post_context_codepoints(post_context);
 
-    let ptem = hb_font_get_ptem(font);
-    let ptem = if ptem > 0.0 { Some(ptem) } else { None };
-
     let features = hb_feature_to_hr_feature(features, num_features);
-    let shape_plan = (shape_plan as *const harfrust::ShapePlan).as_ref();
-    let mut x_scale = 0;
-    let mut y_scale = 0;
-    hb_font_get_scale(font, &mut x_scale, &mut y_scale);
     let mut font_funcs = HBHarfBuzzFontFuncs { font };
     let options = ShapeOptions::new()
         .plan(shape_plan)
-        .scale_separate(Some((x_scale, y_scale)))
-        .point_size(ptem)
+        .scale_separate(Some(((*font_data).x_scale, (*font_data).y_scale)))
+        .point_size((*font_data).ptem)
         .features(&features)
         .font_funcs(Some(&mut font_funcs));
     let glyphs = harfrust::shape(&(*font_data).instance, hr_buffer, options);