Browse / github.com/storytold/wordcraft / patch
deps: harfrust 0.15 (split from the cargo-dependencies group)
deps: harfrust 0.15 (split from the cargo-dependencies group) Splits the harfrust bump out of the red cargo-dependencies group PR (#4418), which fails to compile because harfrust 0.15 replaced `FontRef`, `ShaperData`, `ShaperInstance` and `UnicodeBuffer`. - `harfrust` 0.13 → 0.15 (`Cargo.toml`, `Cargo.lock`). - `FontFace` holds one `harfrust::Font` (variable-font named instances built with `instance_builder().variations(..)`) instead of `ShaperData` plus `ShaperInstance`; `FontFace::hb()` is gone (no other caller). - `shape_dir` shapes with `ShaperFont`, `Buffer` and `harfrust::shape`; a run that fails to shape falls through to the cmap fallback as befo
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Harrison-Booth (imported from pull request #4582, open)
- Base
4b8b06fe03f93c9fc81783ee3e5b01977be89efc- Size
- +73 −72 in 5 files
- Review
- pending
- Shared
- 2026-10-28
- Id
76f5c802a6a3384f40a77896
How it went
No other operator has reported using it yet.
Diff
raw · Cargo.lock Cargo.toml crates/fonts/src/fontdb/face.rs crates/fonts/src/fontdb/names.rs crates/fonts/src/lib.rs
@@ -1567,7 +1567,7 @@ dependencies = [ "ecolor", "emath", "epaint_default_fonts",- "font-types 0.12.6",+ "font-types 0.12.7", "harfrust 0.12.0", "log", "nohash-hasher",@@ -1793,9 +1793,9 @@ dependencies = [ [[package]] name = "font-types"-version = "0.12.6"+version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274"+checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d" dependencies = [ "bytemuck", ]@@ -2170,15 +2170,15 @@ dependencies = [ [[package]] name = "harfrust"-version = "0.13.3"+version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "948d0741125ba89cd3e1c23e5642415b6ade7e1d29d67ba25fb925b533e989d6"+checksum = "865dcc4105d51c323d8fedbb2d75b5e9c8ecddf5613f74c82f851d0afbc97114" dependencies = [ "bitflags 2.13.2", "bytemuck",- "once_cell",- "read-fonts 0.43.3",+ "read-fonts 0.46.0", "smallvec",+ "yoke", ] [[package]]@@ -4192,30 +4192,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" dependencies = [ "bytemuck",- "font-types 0.12.6",+ "font-types 0.12.7", "once_cell", ] [[package]] name = "read-fonts"-version = "0.43.3"+version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea"+checksum = "d5a0cf4bb6cbce4c29756270d189a5e400ac05e91192161d83a0bcb6e68492e8" dependencies = [ "bytemuck",- "font-types 0.12.6",+ "font-types 0.12.7", "once_cell", ] [[package]] name = "read-fonts"-version = "0.44.0"+version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "d5a0cf4bb6cbce4c29756270d189a5e400ac05e91192161d83a0bcb6e68492e8"+checksum = "bdaa4fffe9ff3ca2c353fa9729792805b9b3e324695ff5a081f860612e81b330" dependencies = [ "bytemuck",- "font-types 0.12.6",+ "foldhash 0.2.0",+ "font-types 0.12.7", "once_cell",+ "smallvec",+ "yoke", ] [[package]]@@ -6788,7 +6791,7 @@ name = "wordcraft-fonts" version = "0.11.0" dependencies = [ "brotli-decompressor",- "harfrust 0.13.3",+ "harfrust 0.15.0", "kurbo", "log", "skrifa 0.47.0",@@ -48,7 +48,7 @@ regex-automata = { version = "0.4", default-features = false, features = ["std", # text / rendering / formats skrifa = "0.47"-harfrust = "0.13"+harfrust = "0.15" # WOFF / WOFF2 web fonts added by the user (#582): pure Rust (MIT); its brotli and zlib decoders # (brotli-decompressor, flate2) are already in the tree. wuff = "0.2.9"@@ -64,12 +64,12 @@ pub struct FontFace { /// values). pub cap_height: f64, pub x_height: f64,- pub shaper: harfrust::ShaperData,- /// Variable fonts: the named instance's axis settings (user units; empty = default instance),- /// the normalised location and the shaper's view of it.+ /// The shaper's view of the face, at the named instance's location for variable fonts.+ pub hb: harfrust::Font,+ /// Variable fonts: the named instance's axis settings (user units; empty = default instance)+ /// and the normalised location. pub coords: Vec<([u8; 4], f32)>, pub(super) location: Location,- pub instance: Option<harfrust::ShaperInstance>, /// Basic Multilingual Plane coverage bitset, built on first use. pub(super) bmp: std::sync::OnceLock<Box<[u64]>>, // Some legacy CJK fonts assemble their contours with TrueType instructions.@@ -185,9 +185,6 @@ impl FontFace { pub fn skrifa(&self) -> Option<skrifa::FontRef<'_>> { skrifa::FontRef::from_index(self.data(), self.index).ok() }- pub fn hb(&self) -> Option<harfrust::FontRef<'_>> {- harfrust::FontRef::from_index(self.data(), self.index).ok()- } /// Where in the design space glyphs, metrics and outlines come from. pub fn location(&self) -> LocationRef<'_> { (&self.location).into()@@ -352,10 +352,12 @@ pub(super) fn make_face( let location = if coords.is_empty() { Location::default() } else { f.axes().location(settings.iter().copied()) }; let m = f.metrics(Size::unscaled(), &location); let a = f.attributes();- let hb = harfrust::FontRef::from_index(data, index).ok()?;- let shaper = harfrust::ShaperData::new(&hb);- let instance = (!coords.is_empty())- .then(|| harfrust::ShaperInstance::from_variations(&hb, settings.iter().map(|(t, v)| harfrust::Variation { tag: *t, value: *v })));+ let source: harfrust::font::Source = match &bytes {+ FontBytes::Static(b) => (*b).into(),+ FontBytes::Owned(v) => (v.clone() as Arc<dyn AsRef<[u8]> + Send + Sync>).into(),+ };+ let hb = harfrust::Font::new(source, index)?;+ let hb = if coords.is_empty() { hb } else { hb.instance_builder().variations(coords.iter().map(|(t, v)| (harfrust::Tag::new(t), *v))).build() }; let axis = |tag: &[u8; 4]| coords.iter().find(|(t, _)| t == tag).map(|(_, v)| *v); // Old releases of heavy faces (Arial Black in the core fonts) claim weight 400 where current // ones say 900, which Word doesn't embolden: a family named for its weight is that heavy.@@ -379,10 +381,9 @@ pub(super) fn make_face( descent: -(m.descent as f64), cap_height: m.cap_height.map(|v| v as f64).filter(|v| *v > 0.0).unwrap_or(m.ascent as f64 * 0.72), x_height: m.x_height.map(|v| v as f64).filter(|v| *v > 0.0).unwrap_or(m.ascent as f64 * 0.5),- shaper,+ hb, coords, location,- instance, bytes, index, bmp: std::sync::OnceLock::new(),@@ -19,7 +19,7 @@ pub use fontdb::{ base_style, bundled, decode_font_file, is_font_file_name, name_language_matches, sniff, system_font_dirs, }; pub use harfrust::Feature;-use harfrust::{Direction, ShapeOptions, ShapePlan, ShapePlanKey, Shaper, Tag, UnicodeBuffer};+use harfrust::{Buffer, Direction, ShapeOptions, ShapePlan, ShapePlanKey, ShaperFont, Tag}; pub use kurbo::BezPath; pub use script::{fallback_rank, is_emoji, needs_layout_tables, script_fallbacks}; use skrifa::MetadataProvider;@@ -208,35 +208,35 @@ pub fn shape_run(face: &FontFace, text: &str, features: &[Feature], map: impl Fn fn shape_dir(face: &FontFace, text: &str, features: &[Feature], map: &impl Fn(char) -> char, rtl: bool) -> Vec<ShapedGlyph> { let mut out = Vec::with_capacity(text.len());- let shaped = face.hb().map(|hb| {- let shaper = face.shaper.shaper(&hb).instance(face.instance.as_ref()).build();- // One script per shaping call: the shaper picks its script engine (Myanmar, Khmer,- // Indic, USE reordering…) from the run's script, so "Hi မြန်မာ" in one face must not be- // shaped as Latin. Runs come in text order; right-to-left callers regroup by cluster.- for r in script::script_runs(text) {- let Some(sub) = text.get(r.clone()) else { continue };- let mut buf = UnicodeBuffer::new();- for (i, c) in sub.char_indices() {- buf.add(map(c), (r.start + i) as u32);- }- buf.guess_segment_properties();- buf.set_direction(if rtl { Direction::RightToLeft } else { Direction::LeftToRight });- let plan = shape_plan(face, &shaper, &buf, features);- let gb = shaper.shape(buf, ShapeOptions::new().plan(plan.as_deref()).features(features));- for (info, pos) in gb.glyph_infos().iter().zip(gb.glyph_positions()) {- out.push(ShapedGlyph {- gid: info.glyph_id,- cluster: info.cluster as usize,- x_advance: pos.x_advance,- x_offset: pos.x_offset,- y_offset: pos.y_offset,- });- }+ let sf = ShaperFont::new(&face.hb);+ // One script per shaping call: the shaper picks its script engine (Myanmar, Khmer,+ // Indic, USE reordering…) from the run's script, so "Hi မြန်မာ" in one face must not be+ // shaped as Latin. Runs come in text order; right-to-left callers regroup by cluster.+ let mut shaped = false;+ for r in script::script_runs(text) {+ let Some(sub) = text.get(r.clone()) else { continue };+ let mut buf = Buffer::new();+ for (i, c) in sub.char_indices() {+ buf.push(map(c) as u32, (r.start + i) as u32); }- });- if shaped.is_none()- && let Some(f) = face.skrifa()- {+ buf.guess_segment_properties();+ buf.set_direction(if rtl { Direction::RightToLeft } else { Direction::LeftToRight });+ let plan = shape_plan(face, &buf, features);+ if harfrust::shape(&sf, &mut buf, ShapeOptions::new().plan(plan.as_deref()).features(features)).is_err() {+ continue;+ }+ shaped = true;+ for (info, pos) in buf.glyph_infos().iter().zip(buf.glyph_positions()) {+ out.push(ShapedGlyph {+ gid: info.glyph_id,+ cluster: info.cluster as usize,+ x_advance: pos.x_advance,+ x_offset: pos.x_offset,+ y_offset: pos.y_offset,+ });+ }+ }+ if !shaped && let Some(f) = face.skrifa() { let cmap = f.charmap(); let gm = f.glyph_metrics(Size::unscaled(), face.location()); for (i, c) in text.char_indices() {@@ -281,22 +281,22 @@ thread_local! { /// oldest dropped past [`MAX_SHAPE_PLANS`]). A kept plan is used only for the same face and when /// harfrust's own [`ShapePlanKey::matches`] accepts it (script, direction, language, the face's /// variation instance and the features). `None` leaves making it to the shaper.-fn shape_plan(face: &FontFace, shaper: &Shaper, buf: &UnicodeBuffer, features: &[Feature]) -> Option<std::rc::Rc<ShapePlan>> {+fn shape_plan(face: &FontFace, buf: &Buffer, features: &[Feature]) -> Option<std::rc::Rc<ShapePlan>> { // The buffer's script is unset when it reads as unknown: the guess never sets that.- let script = Some(buf.script()).filter(|s| *s != harfrust::script::UNKNOWN);+ let script = buf.script().filter(|s| *s != harfrust::Script::UNKNOWN); let language = buf.language(); let direction = buf.direction(); if direction == Direction::Invalid { return None; }- let key = ShapePlanKey::new(script, direction).language(language.as_ref()).instance(face.instance.as_ref()).features(features);+ let key = ShapePlanKey::new(&face.hb, script, direction).language(language).features(features); let of = PlanFace::of(face); SHAPE_PLANS.with(|cell| { let mut plans = cell.try_borrow_mut().ok()?; if let Some((_, plan)) = plans.iter().find(|(f, plan)| *f == of && key.matches(plan)) { return Some(plan.clone()); }- let plan = std::rc::Rc::new(ShapePlan::new(shaper, direction, script, language.as_ref(), features));+ let plan = std::rc::Rc::new(ShapePlan::new(&face.hb, direction, script, language, features)); if plans.len() >= MAX_SHAPE_PLANS { plans.remove(0); }@@ -361,14 +361,14 @@ mod tests { #[test] fn kept_shape_plans_shape_as_fresh_ones() { let fresh = |face: &FontFace, text: &str, features: &[Feature], rtl: bool| -> Vec<(u32, u32, i32, i32, i32)> {- let Some(hb) = face.hb() else { return Vec::new() };- let shaper = face.shaper.shaper(&hb).instance(face.instance.as_ref()).build();- let mut buf = UnicodeBuffer::new();+ let mut buf = Buffer::new(); buf.push_str(text); buf.guess_segment_properties(); buf.set_direction(if rtl { Direction::RightToLeft } else { Direction::LeftToRight });- let gb = shaper.shape(buf, ShapeOptions::new().features(features));- gb.glyph_infos().iter().zip(gb.glyph_positions()).map(|(i, p)| (i.glyph_id, i.cluster, p.x_advance, p.x_offset, p.y_offset)).collect()+ if harfrust::shape(&ShaperFont::new(&face.hb), &mut buf, ShapeOptions::new().features(features)).is_err() {+ return Vec::new();+ }+ buf.glyph_infos().iter().zip(buf.glyph_positions()).map(|(i, p)| (i.glyph_id, i.cluster, p.x_advance, p.x_offset, p.y_offset)).collect() }; let kept = |face: &FontFace, text: &str, features: &[Feature], rtl: bool| -> Vec<(u32, u32, i32, i32, i32)> { shape_dir(face, text, features, &|c| c, rtl).iter().map(|g| (g.gid, g.cluster as u32, g.x_advance, g.x_offset, g.y_offset)).collect()@@ -394,14 +394,14 @@ mod tests { fn kept_shape_plans_are_bounded() { let face = FontDb::global().face(DEFAULT_FAMILY, "Regular"); let fresh = |features: &[Feature], rtl: bool| -> Vec<(u32, i32)> {- let Some(hb) = face.hb() else { return Vec::new() };- let shaper = face.shaper.shaper(&hb).instance(face.instance.as_ref()).build();- let mut buf = UnicodeBuffer::new();+ let mut buf = Buffer::new(); buf.push_str("office"); buf.guess_segment_properties(); buf.set_direction(if rtl { Direction::RightToLeft } else { Direction::LeftToRight });- let gb = shaper.shape(buf, ShapeOptions::new().features(features));- gb.glyph_infos().iter().zip(gb.glyph_positions()).map(|(i, p)| (i.glyph_id, p.x_advance)).collect()+ if harfrust::shape(&ShaperFont::new(&face.hb), &mut buf, ShapeOptions::new().features(features)).is_err() {+ return Vec::new();+ }+ buf.glyph_infos().iter().zip(buf.glyph_positions()).map(|(i, p)| (i.glyph_id, p.x_advance)).collect() }; for n in 0..3 * MAX_SHAPE_PLANS as u32 { let features = [feature_value(b"kern", n % 2), feature_value(b"liga", n)];