diff --git a/examples/rich-text/src/main.rs b/examples/rich-text/src/main.rs index 671fa96..4762e43 100644 --- a/examples/rich-text/src/main.rs +++ b/examples/rich-text/src/main.rs @@ -26,6 +26,13 @@ fn set_buffer_text<'a>(buffer: &mut BorrowedWithFontSystem<'a, Buffer>) { let comic_attrs = attrs.family(Family::Name("Comic Neue")); let spans: &[(&str, Attrs)] = &[ + ("Font size 8 ", attrs.metrics(Metrics::relative(8.0, 1.2))), + ("Font size 20 ", attrs.metrics(Metrics::relative(20.0, 1.2))), + ("Font size 14 ", attrs.metrics(Metrics::relative(14.0, 1.2))), + ( + "Font size 48\n", + attrs.metrics(Metrics::relative(48.0, 1.2)), + ), ("B", attrs.weight(Weight::BOLD)), ("old ", attrs), ("I", attrs.style(Style::Italic)), diff --git a/src/attrs.rs b/src/attrs.rs index 1694920..d241fe4 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -8,7 +8,7 @@ use alloc::{ use core::ops::Range; use rangemap::RangeMap; -use crate::CacheKeyFlags; +use crate::{CacheKeyFlags, Metrics}; pub use fontdb::{Family, Stretch, Style, Weight}; @@ -101,6 +101,32 @@ impl FamilyOwned { } } +/// Metrics, but implementing Eq and Hash using u32 representation of f32 +//TODO: what are the edge cases of this? +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct CacheMetrics { + pub font_size_bits: u32, + pub line_height_bits: u32, +} + +impl From for CacheMetrics { + fn from(metrics: Metrics) -> Self { + Self { + font_size_bits: metrics.font_size.to_bits(), + line_height_bits: metrics.line_height.to_bits(), + } + } +} + +impl From for Metrics { + fn from(metrics: CacheMetrics) -> Self { + Self { + font_size: f32::from_bits(metrics.font_size_bits), + line_height: f32::from_bits(metrics.line_height_bits), + } + } +} + /// Text attributes #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Attrs<'a> { @@ -112,6 +138,7 @@ pub struct Attrs<'a> { pub weight: Weight, pub metadata: usize, pub cache_key_flags: CacheKeyFlags, + pub metrics_opt: Option, } impl<'a> Attrs<'a> { @@ -127,6 +154,7 @@ impl<'a> Attrs<'a> { weight: Weight::NORMAL, metadata: 0, cache_key_flags: CacheKeyFlags::empty(), + metrics_opt: None, } } @@ -172,6 +200,12 @@ impl<'a> Attrs<'a> { self } + /// Set [`Metrics`], overriding values in buffer + pub fn metrics(mut self, metrics: Metrics) -> Self { + self.metrics_opt = Some(metrics.into()); + self + } + /// Check if font matches pub fn matches(&self, face: &fontdb::FaceInfo) -> bool { //TODO: smarter way of including emoji @@ -219,6 +253,7 @@ pub struct AttrsOwned { pub weight: Weight, pub metadata: usize, pub cache_key_flags: CacheKeyFlags, + pub metrics_opt: Option, } impl AttrsOwned { @@ -231,6 +266,7 @@ impl AttrsOwned { weight: attrs.weight, metadata: attrs.metadata, cache_key_flags: attrs.cache_key_flags, + metrics_opt: attrs.metrics_opt, } } @@ -243,6 +279,7 @@ impl AttrsOwned { weight: self.weight, metadata: self.metadata, cache_key_flags: self.cache_key_flags, + metrics_opt: self.metrics_opt, } } } diff --git a/src/buffer.rs b/src/buffer.rs index 31e370c..8da5942 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -199,6 +199,7 @@ pub struct Metrics { } impl Metrics { + /// Create metrics with given font size and line height pub const fn new(font_size: f32, line_height: f32) -> Self { Self { font_size, @@ -206,6 +207,15 @@ impl Metrics { } } + /// Create metrics with given font size and calculate line height using relative scale + pub fn relative(font_size: f32, line_height_scale: f32) -> Self { + Self { + font_size, + line_height: font_size * line_height_scale, + } + } + + /// Scale font size and line height pub fn scale(self, scale: f32) -> Self { Self { font_size: self.font_size * scale, diff --git a/src/shape.rs b/src/shape.rs index ac28287..75ab7f0 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -14,7 +14,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::fallback::FontFallbackIter; use crate::{ math, Align, AttrsList, CacheKeyFlags, Color, Font, FontSystem, LayoutGlyph, LayoutLine, - ShapePlanCache, Wrap, + Metrics, ShapePlanCache, Wrap, }; /// The shaping strategy of some text. @@ -163,6 +163,7 @@ fn shape_fallback( color_opt: attrs.color_opt, metadata: attrs.metadata, cache_key_flags: attrs.cache_key_flags, + metrics_opt: attrs.metrics_opt.map(|x| x.into()), }); } @@ -463,6 +464,7 @@ fn shape_skip( color_opt: attrs.color_opt, metadata: attrs.metadata, cache_key_flags: attrs.cache_key_flags, + metrics_opt: attrs.metrics_opt.map(|x| x.into()), } }), ); @@ -485,6 +487,7 @@ pub struct ShapeGlyph { pub color_opt: Option, pub metadata: usize, pub cache_key_flags: CacheKeyFlags, + pub metrics_opt: Option, } impl ShapeGlyph { @@ -513,6 +516,10 @@ impl ShapeGlyph { cache_key_flags: self.cache_key_flags, } } + + pub fn width(&self, font_size: f32) -> f32 { + self.metrics_opt.map_or(font_size, |x| x.font_size) * self.x_advance + } } /// A shaped word (for word wrapping) @@ -520,8 +527,6 @@ impl ShapeGlyph { pub struct ShapeWord { pub blank: bool, pub glyphs: Vec, - pub x_advance: f32, - pub y_advance: f32, } impl ShapeWord { @@ -603,19 +608,15 @@ impl ShapeWord { ); } - let mut x_advance = 0.0; - let mut y_advance = 0.0; - for glyph in &glyphs { - x_advance += glyph.x_advance; - y_advance += glyph.y_advance; - } + Self { blank, glyphs } + } - Self { - blank, - glyphs, - x_advance, - y_advance, + pub fn width(&self, font_size: f32) -> f32 { + let mut width = 0.0; + for glyph in self.glyphs.iter() { + width += glyph.width(font_size); } + width } } @@ -1025,7 +1026,7 @@ impl ShapeLine { let mut word_range_width = 0.; let mut number_of_blanks: u32 = 0; for word in span.words.iter() { - let word_width = font_size * word.x_advance; + let word_width = word.width(font_size); word_range_width += word_width; if word.blank { number_of_blanks += 1; @@ -1051,7 +1052,7 @@ impl ShapeLine { // incongruent directions let mut fitting_start = (span.words.len(), 0); for (i, word) in span.words.iter().enumerate().rev() { - let word_width = font_size * word.x_advance; + let word_width = word.width(font_size); // Addition in the same order used to compute the final width, so that // relayouts with that width as the `line_width` will produce the same @@ -1098,7 +1099,7 @@ impl ShapeLine { } for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() { - let glyph_width = font_size * glyph.x_advance; + let glyph_width = glyph.width(font_size); if current_visual_line.w + (word_range_width + glyph_width) <= line_width { @@ -1179,7 +1180,7 @@ impl ShapeLine { // congruent direction let mut fitting_start = (0, 0); for (i, word) in span.words.iter().enumerate() { - let word_width = font_size * word.x_advance; + let word_width = word.width(font_size); if current_visual_line.w + (word_range_width + word_width) <= line_width // Include one blank word over the width limit since it won't be @@ -1222,7 +1223,7 @@ impl ShapeLine { } for (glyph_i, glyph) in word.glyphs.iter().enumerate() { - let glyph_width = font_size * glyph.x_advance; + let glyph_width = glyph.width(font_size); if current_visual_line.w + (word_range_width + glyph_width) <= line_width { @@ -1383,9 +1384,12 @@ impl ShapeLine { (true, true) => &word.glyphs[starting_glyph..ending_glyph], }; - let match_mono_em_width = match_mono_width.map(|w| w / font_size); - for glyph in included_glyphs { + // Use overridden font size + let font_size = glyph.metrics_opt.map_or(font_size, |x| x.font_size); + + let match_mono_em_width = match_mono_width.map(|w| w / font_size); + let glyph_font_size = match ( match_mono_em_width, glyph.font_monospace_em_width, @@ -1419,8 +1423,8 @@ impl ShapeLine { x += x_advance; } y += y_advance; - max_ascent = max_ascent.max(glyph.ascent); - max_descent = max_descent.max(glyph.descent); + max_ascent = max_ascent.max(glyph_font_size * glyph.ascent); + max_descent = max_descent.max(glyph_font_size * glyph.descent); } } } @@ -1445,8 +1449,8 @@ impl ShapeLine { } else { x }, - max_ascent: max_ascent * font_size, - max_descent: max_descent * font_size, + max_ascent, + max_descent, glyphs, }); }