diff --git a/Cargo.lock b/Cargo.lock index b36d31c5..b4fb1892 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4801,6 +4801,7 @@ dependencies = [ "skrifa", "smallvec", "softbuffer", + "swash", "thiserror 2.0.18", "tiny-skia 0.12.0", "tracing", @@ -4825,6 +4826,17 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" +[[package]] +name = "swash" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + [[package]] name = "syn" version = "2.0.117" diff --git a/frontends/rioterm/src/renderer/font_cache.rs b/frontends/rioterm/src/renderer/font_cache.rs index 85e06874..2db137bf 100644 --- a/frontends/rioterm/src/renderer/font_cache.rs +++ b/frontends/rioterm/src/renderer/font_cache.rs @@ -1,5 +1,5 @@ use lru::LruCache; -use rio_backend::sugarloaf::font_introspector::Attributes; +use rio_backend::sugarloaf::swash::Attributes; use std::collections::HashMap; use std::num::NonZeroUsize; use tracing::debug; @@ -105,19 +105,19 @@ impl FontCache { let common_attrs = [ Attributes::new( - rio_backend::sugarloaf::font_introspector::Stretch::NORMAL, - rio_backend::sugarloaf::font_introspector::Weight::NORMAL, - rio_backend::sugarloaf::font_introspector::Style::Normal, + rio_backend::sugarloaf::swash::Stretch::NORMAL, + rio_backend::sugarloaf::swash::Weight::NORMAL, + rio_backend::sugarloaf::swash::Style::Normal, ), Attributes::new( - rio_backend::sugarloaf::font_introspector::Stretch::NORMAL, - rio_backend::sugarloaf::font_introspector::Weight::BOLD, - rio_backend::sugarloaf::font_introspector::Style::Normal, + rio_backend::sugarloaf::swash::Stretch::NORMAL, + rio_backend::sugarloaf::swash::Weight::BOLD, + rio_backend::sugarloaf::swash::Style::Normal, ), Attributes::new( - rio_backend::sugarloaf::font_introspector::Stretch::NORMAL, - rio_backend::sugarloaf::font_introspector::Weight::NORMAL, - rio_backend::sugarloaf::font_introspector::Style::Italic, + rio_backend::sugarloaf::swash::Stretch::NORMAL, + rio_backend::sugarloaf::swash::Weight::NORMAL, + rio_backend::sugarloaf::swash::Style::Italic, ), ]; @@ -163,7 +163,7 @@ impl Default for FontCache { #[cfg(test)] mod tests { use super::*; - use rio_backend::sugarloaf::font_introspector::{Stretch, Style, Weight}; + use rio_backend::sugarloaf::swash::{Stretch, Style, Weight}; #[test] fn test_font_cache_basic_operations() { diff --git a/frontends/rioterm/src/renderer/mod.rs b/frontends/rioterm/src/renderer/mod.rs index 7d00925e..c8e608f7 100644 --- a/frontends/rioterm/src/renderer/mod.rs +++ b/frontends/rioterm/src/renderer/mod.rs @@ -27,7 +27,7 @@ use rio_backend::config::colors::{ use rio_backend::config::navigation::Navigation; use rio_backend::config::Config; use rio_backend::event::EventProxy; -use rio_backend::sugarloaf::font_introspector::Attributes; +use rio_backend::sugarloaf::swash::Attributes; use rio_backend::sugarloaf::{ drawable_character, is_private_user_area, CursorKind, Graphic, SpanStyle, SpanStyleDecoration, Stretch, Style, SugarCursor, Sugarloaf, UnderlineInfo, @@ -521,7 +521,7 @@ impl Renderer { } // Make hint labels bold for better visibility - use rio_backend::sugarloaf::font_introspector::{Attributes, Weight}; + use rio_backend::sugarloaf::swash::{Attributes, Weight}; let current_attrs = style.font_attrs; style.font_attrs = Attributes::new( current_attrs.stretch(), diff --git a/sugarloaf/Cargo.toml b/sugarloaf/Cargo.toml index cc7c0c2a..0aef6605 100644 --- a/sugarloaf/Cargo.toml +++ b/sugarloaf/Cargo.toml @@ -53,7 +53,8 @@ halfbrown = "0.3.0" half = "2.6.0" num-traits = "0.2.19" yazi = { version = "0.2.1", optional = true } -zeno = { version = "0.3.3", optional = true } +zeno = "0.3.3" +swash = "0.2.7" futures = { workspace = true } tiny-skia = "0.12.0" wide = "1.2.0" @@ -101,7 +102,7 @@ criterion = { workspace = true } [features] default = ["scale", "render"] -scale = ["yazi", "zeno"] +scale = ["yazi"] render = ["scale", "zeno/eval"] [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/sugarloaf/src/font/macos.rs b/sugarloaf/src/font/macos.rs index 973c1817..0f758a55 100644 --- a/sugarloaf/src/font/macos.rs +++ b/sugarloaf/src/font/macos.rs @@ -299,7 +299,7 @@ impl FontHandle { } /// Output of a single glyph rasterization. Mirrors the fields of -/// `font_introspector::scale::image::Image` the zeno path fills in. +/// `swash::scale::image::Image` the zeno path fills in. #[derive(Debug)] pub struct RasterizedGlyph { /// Bitmap width in device pixels. `0` signals a zero-area glyph @@ -653,7 +653,7 @@ fn bgra_to_rgba_in_place(bytes: &mut [u8]) { } } -/// Build a `font_introspector::Metrics` populated from CoreText, in font +/// Build a `swash::Metrics` populated from CoreText, in font /// design units. Used by `FontData::get_metrics` on macOS so the metrics /// path works without raw font bytes. /// @@ -661,7 +661,7 @@ fn bgra_to_rgba_in_place(bytes: &mut [u8]) { /// underline, x-height, cap-height, units_per_em). Strikeout has no CT /// API — we derive it like `font::macos::font_metrics` does, from the /// OS/2 table if available or x-height/2 as a fallback. -pub fn design_unit_metrics(handle: &FontHandle) -> crate::font_introspector::Metrics { +pub fn design_unit_metrics(handle: &FontHandle) -> swash::Metrics { let ct = &handle.base_font; let upem = ct.units_per_em() as f32; @@ -683,7 +683,7 @@ pub fn design_unit_metrics(handle: &FontHandle) -> crate::font_introspector::Met // u32 traits instead. 1 << 10 is `kCTFontTraitMonoSpace`. let is_monospace = (ct.symbolic_traits() & (1 << 10)) != 0; - crate::font_introspector::Metrics { + swash::Metrics { units_per_em: upem as u16, glyph_count: ct.glyph_count() as u16, is_monospace, @@ -835,7 +835,7 @@ pub fn max_ascii_advance_px(handle: &FontHandle, size_px: f32) -> Option { } /// Font-level attributes read straight from a `CTFont`. Mirrors the subset -/// of `font_introspector::Attributes` that Rio stores on `FontData` — used +/// of `swash::Attributes` that Rio stores on `FontData` — used /// to build a `FontData` from a path (or static bytes) without parsing the /// font file ourselves. #[derive(Debug, Clone, Copy)] @@ -907,7 +907,7 @@ pub fn discover_fallback(primary: &FontHandle, ch: char) -> Option { /// Check whether `handle`'s font has a real glyph for `ch`. /// -/// Replaces the `font_introspector::FontRef::charmap().map(ch)` path on +/// Replaces the `swash::FontRef::charmap().map(ch)` path on /// macOS so the fallback walk in `lookup_for_font_match` doesn't need the /// font's raw bytes — only the CTFont. Combined with path-based FontHandle /// construction, this lets us drop `FONT_DATA_CACHE` entirely. diff --git a/sugarloaf/src/font/metrics.rs b/sugarloaf/src/font/metrics.rs index 1d40fe8d..ef0f2257 100644 --- a/sugarloaf/src/font/metrics.rs +++ b/sugarloaf/src/font/metrics.rs @@ -1,7 +1,7 @@ // Font metrics implementation similar to consistent font metrics approach // Key insight: Primary font determines cell dimensions for ALL fonts -use crate::font_introspector::Metrics as FontIntrospectorMetrics; +use swash::Metrics as FontIntrospectorMetrics; /// Font metrics similar to Rio's Metrics struct #[derive(Debug, Clone, Copy, PartialEq)] @@ -84,7 +84,7 @@ impl FaceMetrics { /// the CJK water ideograph "水" (U+6C34) for proper font size normalization /// when mixing CJK and Latin fonts. pub fn from_font( - font_ref: &crate::font_introspector::FontRef, + font_ref: &swash::FontRef, metrics: &FontIntrospectorMetrics, ) -> Self { Self { @@ -114,7 +114,7 @@ impl FaceMetrics { /// - Its width is representative of typical CJK character width /// - It avoids edge cases like punctuation or rare characters fn measure_cjk_character_width( - font_ref: &crate::font_introspector::FontRef, + font_ref: &swash::FontRef, ) -> Option { const CJK_WATER_IDEOGRAPH: u32 = 0x6C34; // "水" @@ -129,8 +129,7 @@ impl FaceMetrics { } // Get glyph metrics - let glyph_metrics = - crate::font_introspector::GlyphMetrics::from_font(font_ref, &[]); + let glyph_metrics = font_ref.glyph_metrics(&[]); // Get advance width for the glyph let advance_width = glyph_metrics.advance_width(glyph_id); diff --git a/sugarloaf/src/font/mod.rs b/sugarloaf/src/font/mod.rs index 8681a294..743bb089 100644 --- a/sugarloaf/src/font/mod.rs +++ b/sugarloaf/src/font/mod.rs @@ -17,12 +17,12 @@ pub const FONT_ID_REGULAR: usize = 0; use crate::font::constants::*; use crate::font::fonts::{parse_unicode, SugarloafFontStyle, SugarloafFontWidth}; use crate::font::metrics::{FaceMetrics, Metrics}; -use crate::font_introspector::text::cluster::Parser; -use crate::font_introspector::text::cluster::Token; -use crate::font_introspector::text::cluster::{CharCluster, Status}; -use crate::font_introspector::text::Codepoint; -use crate::font_introspector::text::Script; -use crate::font_introspector::{tag_from_bytes, CacheKey, FontRef, Synthesis}; +use swash::text::cluster::Parser; +use swash::text::cluster::Token; +use swash::text::cluster::{CharCluster, Status}; +use swash::text::Codepoint; +use swash::text::Script; +use swash::{tag_from_bytes, CacheKey, FontRef, Synthesis}; use crate::layout::SpanStyle; use crate::SugarloafErrors; use dashmap::DashMap; @@ -32,7 +32,7 @@ use std::ops::Range; use std::path::PathBuf; use std::sync::{Arc, OnceLock}; -pub use crate::font_introspector::{Style, Weight}; +pub use swash::{Style, Weight}; /// Cross-platform shim: non-macOS threads `&loader::Database` through to /// `find_font`; macOS drops it since CoreText handles matching directly and @@ -78,7 +78,7 @@ pub fn lookup_for_font_match( cluster: &mut CharCluster, synth: &mut Synthesis, library: &FontLibraryData, - spec_font_attr_opt: Option<&(crate::font_introspector::Style, bool)>, + spec_font_attr_opt: Option<&(swash::Style, bool)>, ) -> Option<(usize, bool)> { let mut search_result = None; let mut font_synth = Synthesis::default(); @@ -936,9 +936,9 @@ pub struct FontData { offset: u32, // Cache key pub key: CacheKey, - pub weight: crate::font_introspector::Weight, - pub style: crate::font_introspector::Style, - pub stretch: crate::font_introspector::Stretch, + pub weight: swash::Weight, + pub style: swash::Style, + pub stretch: swash::Stretch, pub synth: Synthesis, pub should_embolden: bool, pub should_italicize: bool, @@ -1047,15 +1047,13 @@ impl FontData { // Calculate metrics if not cached if let Some(ref data) = self.data { - let font_ref = crate::font_introspector::FontRef { + let font_ref = swash::FontRef { data: data.as_ref(), offset: self.offset, key: self.key, }; - let font_metrics = - crate::font_introspector::Metrics::from_font(&font_ref, &[]); - let scaled_metrics = font_metrics.scale(font_size); + let scaled_metrics = font_ref.metrics(&[]).scale(font_size); // Use the unified method that always includes CJK measurement let face_metrics = FaceMetrics::from_font(&font_ref, &scaled_metrics); @@ -1160,11 +1158,11 @@ impl FontData { pub fn from_ctfont_macos(handle: crate::font::macos::FontHandle) -> Self { let attrs = crate::font::macos::font_attributes(&handle); let style = if attrs.is_italic { - crate::font_introspector::Style::Italic + swash::Style::Italic } else { - crate::font_introspector::Style::Normal + swash::Style::Normal }; - let weight = crate::font_introspector::Weight(attrs.weight); + let weight = swash::Weight(attrs.weight); Self { data: None, path: None, @@ -1172,7 +1170,7 @@ impl FontData { key: CacheKey::new(), weight, style, - stretch: crate::font_introspector::Stretch::NORMAL, + stretch: swash::Stretch::NORMAL, synth: Synthesis::default(), should_embolden: false, should_italicize: false, @@ -1192,11 +1190,11 @@ impl FontData { let attrs = crate::font::macos::font_attributes(&handle); let style = if attrs.is_italic { - crate::font_introspector::Style::Italic + swash::Style::Italic } else { - crate::font_introspector::Style::Normal + swash::Style::Normal }; - let weight = crate::font_introspector::Weight(attrs.weight); + let weight = swash::Weight(attrs.weight); let should_italicize = font_spec.style == SugarloafFontStyle::Italic && !attrs.is_italic; @@ -1209,7 +1207,7 @@ impl FontData { key: CacheKey::new(), weight, style, - stretch: crate::font_introspector::Stretch::NORMAL, + stretch: swash::Stretch::NORMAL, synth: Synthesis::default(), should_embolden, should_italicize, @@ -1562,7 +1560,7 @@ fn load_from_font_source(path: &PathBuf) -> Option { // Memory-map the file rather than reading it into a `Vec`. The // kernel backs the bytes with the font file and only pages in what - // font_introspector's charmap / metrics queries actually touch, so a + // swash's charmap / metrics queries actually touch, so a // large fallback (e.g. a CJK font, an emoji file) costs negligible // resident RAM instead of its full on-disk size. Mmap is unsafe // because the file can change underneath us or the mapping can fault; diff --git a/sugarloaf/src/font_cache.rs b/sugarloaf/src/font_cache.rs index 40420b56..9cc05117 100644 --- a/sugarloaf/src/font_cache.rs +++ b/sugarloaf/src/font_cache.rs @@ -3,7 +3,7 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use crate::font_introspector::Attributes; +use swash::Attributes; use crate::sugarloaf::primitives::is_private_user_area; use crate::SpanStyle; use rustc_hash::FxHashMap; @@ -155,9 +155,9 @@ pub(crate) fn compute_advance( ch: char, ) -> Option { let (data, offset, _key) = font_ctx.get_data(&font_id)?; - let font_ref = crate::font_introspector::FontRef::from_index(&data, offset as usize)?; + let font_ref = swash::FontRef::from_index(&data, offset as usize)?; let glyph_id = font_ref.charmap().map(ch as u32); - let metrics = crate::font_introspector::GlyphMetrics::from_font(&font_ref, &[]); + let metrics = swash::GlyphMetrics::from_font(&font_ref, &[]); Some(AdvanceInfo { advance_units: metrics.advance_width(glyph_id), units_per_em: font_ref.metrics(&[]).units_per_em, diff --git a/sugarloaf/src/font_introspector/attributes.rs b/sugarloaf/src/font_introspector/attributes.rs deleted file mode 100644 index 2f00f26f..00000000 --- a/sugarloaf/src/font_introspector/attributes.rs +++ /dev/null @@ -1,684 +0,0 @@ -// font_introspector was retired from https://github.com/dfrg/swash -// which is licensed under MIT license - -//! Basic font attributes: stretch, weight and style. - -use super::internal::{head::Os2, RawFont}; -use super::{tag_from_bytes, FontRef, Setting, Tag}; - -use core::fmt; -use core::hash::{Hash, Hasher}; - -// Variations that apply to attributes. -const WDTH: Tag = tag_from_bytes(b"wdth"); -const WGHT: Tag = tag_from_bytes(b"wght"); -const SLNT: Tag = tag_from_bytes(b"slnt"); -const ITAL: Tag = tag_from_bytes(b"ital"); - -/// Primary attributes for font classification: stretch, weight and style. -/// -/// This struct is created by the [`attributes`](FontRef::attributes) method on [`FontRef`]. -#[derive(Copy, Clone)] -pub struct Attributes(pub u32); - -impl Attributes { - /// Creates new font attributes from the specified stretch, weight and - /// style. - pub const fn new(stretch: Stretch, weight: Weight, style: Style) -> Self { - let stretch = stretch.0 as u32 & 0x1FF; - let weight = weight.0 as u32 & 0x3FF; - let style = style.pack(); - Self(style | weight << 9 | stretch << 19) - } - - /// Extracts the attributes from the specified font. - pub fn from_font<'a>(font: &FontRef<'a>) -> Self { - let mut attrs = Self::from_os2(font.os2().as_ref()); - let mut var_bits = 0; - for var in font.variations() { - match var.tag() { - WDTH => var_bits |= 1, - WGHT => var_bits |= 2, - SLNT => var_bits |= 4, - ITAL => var_bits |= 8, - _ => {} - } - } - attrs.0 |= var_bits << 28; - attrs - } - - pub(crate) fn from_os2(os2: Option<&Os2>) -> Self { - if let Some(os2) = os2 { - let flags = os2.selection_flags(); - let style = if flags.italic() { - Style::Italic - } else if flags.oblique() { - Style::Oblique(ObliqueAngle::default()) - } else { - Style::Normal - }; - let weight = Weight(os2.weight_class() as u16); - let stretch = Stretch::from_raw(os2.width_class() as u16); - Self::new(stretch, weight, style) - } else { - Self::default() - } - } - - /// Returns the stretch attribute. - #[inline] - pub fn stretch(&self) -> Stretch { - Stretch((self.0 >> 19 & 0x1FF) as u16) - } - - /// Returns the weight attribute. - #[inline] - pub fn weight(&self) -> Weight { - Weight((self.0 >> 9 & 0x3FF) as u16) - } - - /// Returns the style attribute. - #[inline] - pub fn style(&self) -> Style { - Style::unpack(self.0 & 0x1FF) - } - - /// Returns a tuple containing all attributes. - pub fn parts(&self) -> (Stretch, Weight, Style) { - (self.stretch(), self.weight(), self.style()) - } - - /// Returns true if the font has variations corresponding to primary - /// attributes. - pub fn has_variations(&self) -> bool { - (self.0 >> 28) != 0 - } - - /// Returns true if the font has a variation for the stretch attribute. - pub fn has_stretch_variation(&self) -> bool { - let var_bits = self.0 >> 28; - var_bits & 1 != 0 - } - - /// Returns true if the font has a variation for the weight attribute. - pub fn has_weight_variation(&self) -> bool { - let var_bits = self.0 >> 28; - var_bits & 2 != 0 - } - - /// Returns true if the font has a variation for the oblique style - /// attribute. - pub fn has_oblique_variation(&self) -> bool { - let var_bits = self.0 >> 28; - var_bits & 4 != 0 - } - - /// Returns true if the font has a variation for the italic style - /// attribute. - pub fn has_italic_variation(&self) -> bool { - let var_bits = self.0 >> 28; - var_bits & 8 != 0 - } - - /// Returns a synthesis analysis based on the requested attributes with - /// respect to this set of attributes. - pub fn synthesize(&self, requested: Attributes) -> Synthesis { - let mut synth = Synthesis::default(); - if self.0 << 4 == requested.0 << 4 { - return synth; - } - let mut len = 0usize; - if self.has_stretch_variation() { - let stretch = self.stretch(); - let req_stretch = requested.stretch(); - if stretch != requested.stretch() { - synth.vars[len] = Setting { - tag: WDTH, - value: req_stretch.to_percentage(), - }; - len += 1; - } - } - let (weight, req_weight) = (self.weight(), requested.weight()); - if weight != req_weight { - if self.has_weight_variation() { - synth.vars[len] = Setting { - tag: WGHT, - value: req_weight.0 as f32, - }; - len += 1; - } else if req_weight > weight { - synth.embolden = true; - } - } - let (style, req_style) = (self.style(), requested.style()); - if style != req_style { - match req_style { - Style::Normal => {} - Style::Italic => { - if style == Style::Normal { - if self.has_italic_variation() { - synth.vars[len] = Setting { - tag: ITAL, - value: 1., - }; - len += 1; - } else if self.has_oblique_variation() { - synth.vars[len] = Setting { - tag: SLNT, - value: 14., - }; - len += 1; - } else { - synth.skew = 14; - } - } - } - Style::Oblique(angle) => { - if style == Style::Normal { - let degrees = angle.to_degrees(); - if self.has_oblique_variation() { - synth.vars[len] = Setting { - tag: SLNT, - value: degrees, - }; - len += 1; - } else if self.has_italic_variation() && degrees > 0. { - synth.vars[len] = Setting { - tag: ITAL, - value: 1., - }; - len += 1; - } else { - synth.skew = degrees as i8; - } - } - } - } - } - synth.len = len as u8; - synth - } -} - -impl Default for Attributes { - fn default() -> Self { - Self::new(Stretch::NORMAL, Weight::NORMAL, Style::Normal) - } -} - -impl PartialEq for Attributes { - fn eq(&self, other: &Self) -> bool { - self.0 << 4 == other.0 << 4 - } -} - -impl Eq for Attributes {} - -impl Hash for Attributes { - fn hash(&self, state: &mut H) { - (self.0 << 4).hash(state); - } -} - -impl fmt::Display for Attributes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut space = ""; - let (stretch, weight, style) = self.parts(); - if style == Style::Normal - && weight == Weight::NORMAL - && stretch == Stretch::NORMAL - { - return write!(f, "regular"); - } - if stretch != Stretch::NORMAL { - write!(f, "{stretch}")?; - space = " "; - } - if style != Style::Normal { - write!(f, "{space}{style}")?; - space = " "; - } - if weight != Weight::NORMAL { - write!(f, "{space}{weight}")?; - } - Ok(()) - } -} - -impl fmt::Debug for Attributes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.parts())?; - if self.has_stretch_variation() { - write!(f, "+wdth")?; - } - if self.has_weight_variation() { - write!(f, "+wght")?; - } - if self.has_italic_variation() { - write!(f, "+ital")?; - } - if self.has_oblique_variation() { - write!(f, "+slnt")?; - } - Ok(()) - } -} - -impl From for Attributes { - fn from(s: Stretch) -> Self { - Self::new(s, Weight::default(), Style::default()) - } -} - -impl From for Attributes { - fn from(w: Weight) -> Self { - Self::new(Stretch::default(), w, Style::default()) - } -} - -impl From