diff --git a/src/attrs.rs b/src/attrs.rs index d7acc1c..996e13b 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -5,6 +5,7 @@ use alloc::vec::Vec; use core::ops::Range; use rangemap::RangeMap; use smol_str::SmolStr; +use std::hash::{Hash, Hasher}; use crate::{CacheKeyFlags, Metrics}; @@ -125,6 +126,37 @@ impl From for Metrics { } } +/// A wrapper for letter spacing to get around that f32 doesn't implement Eq and Hash +#[derive(Clone, Copy, Debug)] +pub struct LetterSpacing(pub f32); + +impl PartialEq for LetterSpacing { + fn eq(&self, other: &Self) -> bool { + if self.0.is_nan() { + other.0.is_nan() + } else { + self.0 == other.0 + } + } +} + +impl Eq for LetterSpacing {} + +impl Hash for LetterSpacing { + fn hash(&self, hasher: &mut H) { + const CANONICAL_NAN_BITS: u32 = 0x7fc0_0000; + + let bits = if self.0.is_nan() { + CANONICAL_NAN_BITS + } else { + // Add +0.0 to canonicalize -0.0 to +0.0 + (self.0 + 0.0).to_bits() + }; + + bits.hash(hasher); + } +} + /// Text attributes #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Attrs<'a> { @@ -137,6 +169,8 @@ pub struct Attrs<'a> { pub metadata: usize, pub cache_key_flags: CacheKeyFlags, pub metrics_opt: Option, + /// Letter spacing (tracking) in pixels + pub letter_spacing_opt: Option, } impl<'a> Attrs<'a> { @@ -153,6 +187,7 @@ impl<'a> Attrs<'a> { metadata: 0, cache_key_flags: CacheKeyFlags::empty(), metrics_opt: None, + letter_spacing_opt: None, } } @@ -204,6 +239,12 @@ impl<'a> Attrs<'a> { self } + /// Set letter spacing (tracking) in EM + pub fn letter_spacing(mut self, letter_spacing: f32) -> Self { + self.letter_spacing_opt = Some(LetterSpacing(letter_spacing)); + self + } + /// Check if font matches pub fn matches(&self, face: &fontdb::FaceInfo) -> bool { //TODO: smarter way of including emoji @@ -252,6 +293,8 @@ pub struct AttrsOwned { pub metadata: usize, pub cache_key_flags: CacheKeyFlags, pub metrics_opt: Option, + /// Letter spacing (tracking) in EM + pub letter_spacing_opt: Option, } impl AttrsOwned { @@ -265,6 +308,7 @@ impl AttrsOwned { metadata: attrs.metadata, cache_key_flags: attrs.cache_key_flags, metrics_opt: attrs.metrics_opt, + letter_spacing_opt: attrs.letter_spacing_opt, } } @@ -278,6 +322,7 @@ impl AttrsOwned { metadata: self.metadata, cache_key_flags: self.cache_key_flags, metrics_opt: self.metrics_opt, + letter_spacing_opt: self.letter_spacing_opt, } } } diff --git a/src/shape.rs b/src/shape.rs index 04a4d40..2b499a8 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -155,11 +155,6 @@ fn shape_fallback( glyphs.reserve(glyph_infos.len()); let glyph_start = glyphs.len(); for (info, pos) in glyph_infos.iter().zip(glyph_positions.iter()) { - let x_advance = pos.x_advance as f32 / font_scale; - let y_advance = pos.y_advance as f32 / font_scale; - let x_offset = pos.x_offset as f32 / font_scale; - let y_offset = pos.y_offset as f32 / font_scale; - let start_glyph = start_run + info.cluster as usize; if info.glyph_id == 0 { @@ -167,6 +162,12 @@ fn shape_fallback( } let attrs = attrs_list.get_span(start_glyph); + let x_advance = pos.x_advance as f32 / font_scale + + attrs.letter_spacing_opt.map_or(0.0, |spacing| spacing.0); + let y_advance = pos.y_advance as f32 / font_scale; + let x_offset = pos.x_offset as f32 / font_scale; + let y_offset = pos.y_offset as f32 / font_scale; + glyphs.push(ShapeGlyph { start: start_glyph, end: end_run, // Set later @@ -453,7 +454,8 @@ fn shape_skip( .char_indices() .map(|(chr_idx, codepoint)| { let glyph_id = charmap.map(codepoint); - let x_advance = glyph_metrics.advance_width(glyph_id); + let x_advance = glyph_metrics.advance_width(glyph_id) + + attrs.letter_spacing_opt.map_or(0.0, |spacing| spacing.0); let attrs = attrs_list.get_span(start_run + chr_idx); ShapeGlyph {