diff --git a/src/attrs.rs b/src/attrs.rs index 7e16462..7301841 100644 --- a/src/attrs.rs +++ b/src/attrs.rs @@ -255,6 +255,12 @@ impl TextDecoration { } } +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct DecorationMetrics { + pub offset: f32, + pub thickness: f32, +} + /// Text attributes #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Attrs<'a> { diff --git a/src/layout.rs b/src/layout.rs index 6da7160..b1ac1ce 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -2,7 +2,7 @@ use core::fmt::Display; -use crate::{math, CacheKey, CacheKeyFlags, Color, TextDecoration}; +use crate::{math, CacheKey, CacheKeyFlags, Color, DecorationMetrics, TextDecoration}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; @@ -60,6 +60,10 @@ pub struct LayoutGlyph { pub cache_key_flags: CacheKeyFlags, /// Text decoration (underline, strikethough, overline) pub text_decoration: TextDecoration, + /// Underline offset and thickness extracted from the font + pub underline_metrics: DecorationMetrics, + /// Strikethrough offset and thickness extracted from the font + pub strikethrough_metrics: DecorationMetrics, } #[derive(Clone, Debug)] diff --git a/src/render.rs b/src/render.rs index 7218d49..57bea32 100644 --- a/src/render.rs +++ b/src/render.rs @@ -67,8 +67,13 @@ fn draw_decoration_group( let font_size = first.font_size; let x_start = first.x; let x_end = last.x + last.w; - let width = (x_end - x_start) as u32; - if width <= 0 { + let width = x_end - x_start; + if width <= 0.0 { + // first check to see if it's below 0.0 + return; + } + let width = width as u32; + if width == 0 { return; } // Underline @@ -79,8 +84,8 @@ fn draw_decoration_group( .underline_color_opt .or(first.color_opt) .unwrap_or(default_color); - let thickness = (font_size * 14.0).max(1.0); - let y = run.line_y + font_size * 0.125; + let thickness = (first.underline_metrics.thickness * font_size).max(1.0); + let y = run.line_y - first.underline_metrics.offset * font_size; renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color); } UnderlineStyle::Double => { @@ -88,9 +93,9 @@ fn draw_decoration_group( .underline_color_opt .or(first.color_opt) .unwrap_or(default_color); - let thickness = (font_size * 14.0).max(1.0); + let thickness = (first.underline_metrics.thickness * font_size).max(1.0); let gap = thickness; - let y = run.line_y + font_size * 0.125; + let y = run.line_y - first.underline_metrics.offset * font_size; renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color); renderer.rectangle( x_start as i32, @@ -108,8 +113,8 @@ fn draw_decoration_group( .strikethrough_color_opt .or(first.color_opt) .unwrap_or(default_color); - let thickness = (font_size / 14.0).max(1.0); - let y = run.line_y - font_size * 0.3; + let thickness = (first.strikethrough_metrics.thickness * font_size).max(1.0); + let y = run.line_y - first.strikethrough_metrics.offset * font_size; renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color); } @@ -119,8 +124,11 @@ fn draw_decoration_group( .overline_color_opt .or(first.color_opt) .unwrap_or(default_color); - let thickness = (font_size / 14.0).max(1.0); - let y = run.line_top; + // we're reusing underline thickness for overline + let thickness = (first.underline_metrics.thickness * font_size).max(1.0); + let y = run.line_top; //TODO: this should be run.line_y - ascent + // but we don't have ascent in GlyphLayout + // using line_top as an approximation for now, which should be good enough for most fonts renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color); } } diff --git a/src/shape.rs b/src/shape.rs index 1bef992..5787aa3 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -4,8 +4,9 @@ use crate::fallback::FontFallbackIter; use crate::{ - math, Align, Attrs, AttrsList, CacheKeyFlags, Color, Ellipsize, EllipsizeHeightLimit, Font, - FontSystem, Hinting, LayoutGlyph, LayoutLine, Metrics, TextDecoration, Wrap, + math, Align, Attrs, AttrsList, CacheKeyFlags, Color, DecorationMetrics, Ellipsize, + EllipsizeHeightLimit, Font, FontSystem, Hinting, LayoutGlyph, LayoutLine, Metrics, + TextDecoration, Wrap, }; #[cfg(not(feature = "std"))] use alloc::{format, vec, vec::Vec}; @@ -15,6 +16,7 @@ use core::cmp::{max, min}; use core::fmt; use core::mem; use core::ops::Range; +use skrifa::metrics::Decoration; #[cfg(not(feature = "std"))] use core_maths::CoreFloat; @@ -130,6 +132,7 @@ fn shape_fallback( let font_scale = font.metrics().units_per_em as f32; let ascent = font.metrics().ascent / font_scale; let descent = -font.metrics().descent / font_scale; + let (underline_metrics, strikethrough_metrics) = decoration_metrics(font); let mut buffer = scratch.harfrust_buffer.take().unwrap_or_default(); buffer.set_direction(if span_rtl { @@ -237,6 +240,8 @@ fn shape_fallback( cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs), metrics_opt: attrs.metrics_opt.map(Into::into), text_decoration: attrs.text_decoration, + underline_metrics, + strikethrough_metrics, }); } @@ -504,6 +509,8 @@ fn shape_skip( let metrics = swash_font.metrics(&[]); let glyph_metrics = swash_font.glyph_metrics(&[]).scale(1.0); + let (underline_metrics, strikethrough_metrics) = decoration_metrics(font.as_ref()); + let ascent = metrics.ascent / f32::from(metrics.units_per_em); let descent = metrics.descent / f32::from(metrics.units_per_em); @@ -538,6 +545,8 @@ fn shape_skip( ), metrics_opt: attrs.metrics_opt.map(Into::into), text_decoration: attrs.text_decoration, + underline_metrics, + strikethrough_metrics, } }), ); @@ -575,6 +584,8 @@ pub struct ShapeGlyph { pub cache_key_flags: CacheKeyFlags, pub metrics_opt: Option, pub text_decoration: TextDecoration, + pub underline_metrics: DecorationMetrics, + pub strikethrough_metrics: DecorationMetrics, } impl ShapeGlyph { @@ -605,6 +616,8 @@ impl ShapeGlyph { metadata: self.metadata, cache_key_flags: self.cache_key_flags, text_decoration: self.text_decoration, + underline_metrics: self.underline_metrics, + strikethrough_metrics: self.strikethrough_metrics, } } @@ -615,6 +628,24 @@ impl ShapeGlyph { } } +fn decoration_metrics(font: &Font) -> (DecorationMetrics, DecorationMetrics) { + let metrics = font.metrics(); + let upem = metrics.units_per_em as f32; + if upem == 0.0 { + return (DecorationMetrics::default(), DecorationMetrics::default()); + } + ( + DecorationMetrics { + offset: metrics.underline.map_or(-0.125, |d| d.offset / upem), + thickness: metrics.underline.map_or(1.0 / 14.0, |d| d.thickness / upem), + }, + DecorationMetrics { + offset: metrics.strikeout.map_or(0.3, |d| d.offset / upem), + thickness: metrics.strikeout.map_or(1.0 / 14.0, |d| d.thickness / upem), + }, + ) +} + /// span index used in VlRange to indicate this range is the ellipsis. const ELLIPSIS_SPAN: usize = usize::MAX;