perf: minimize the performance impact of text decoration
Boxed the decoration data to go from 40b to 8b. The performance is almost unchanged for text without decoration.
This commit is contained in:
parent
6ef1ccbeed
commit
78665aab3b
4 changed files with 78 additions and 53 deletions
17
src/attrs.rs
17
src/attrs.rs
|
|
@ -253,14 +253,31 @@ impl TextDecoration {
|
|||
overline_color_opt: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn has_decoration(&self) -> bool {
|
||||
!matches!(self.underline, UnderlineStyle::None) || self.strikethrough || self.overline
|
||||
}
|
||||
}
|
||||
|
||||
/// Offset and thickness for a text decoration line, in EM units.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct DecorationMetrics {
|
||||
/// Offset from baseline in EM units
|
||||
pub offset: f32,
|
||||
/// Thickness in EM units
|
||||
pub thickness: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct GlyphDecorationData {
|
||||
/// The text decoration configuration from the user
|
||||
pub text_decoration: TextDecoration,
|
||||
/// Underline offset and thickness from the font
|
||||
pub underline_metrics: DecorationMetrics,
|
||||
/// Strikethrough offset and thickness from the font
|
||||
pub strikethrough_metrics: DecorationMetrics,
|
||||
}
|
||||
|
||||
/// Text attributes
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Attrs<'a> {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
use core::fmt::Display;
|
||||
|
||||
use crate::{math, CacheKey, CacheKeyFlags, Color, DecorationMetrics, TextDecoration};
|
||||
use crate::{math, CacheKey, CacheKeyFlags, Color, GlyphDecorationData};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use core_maths::CoreFloat;
|
||||
|
|
@ -58,12 +58,8 @@ pub struct LayoutGlyph {
|
|||
pub metadata: usize,
|
||||
/// [`CacheKeyFlags`]
|
||||
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,
|
||||
/// Decoration data, only allocated when decorations are active
|
||||
pub decoration_data: Option<Box<GlyphDecorationData>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Helpers for rendering buffers and editors
|
||||
|
||||
use crate::{Color, LayoutGlyph, LayoutRun, PhysicalGlyph, TextDecoration, UnderlineStyle};
|
||||
use crate::{Color, LayoutGlyph, LayoutRun, PhysicalGlyph, UnderlineStyle};
|
||||
#[cfg(feature = "swash")]
|
||||
use crate::{FontSystem, SwashCache};
|
||||
|
||||
|
|
@ -14,6 +14,7 @@ pub trait Renderer {
|
|||
fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color);
|
||||
}
|
||||
|
||||
/// Draw text decoration lines (underline, strikethrough, overline) for a layout run.
|
||||
pub fn render_decoration<R: Renderer>(renderer: &mut R, run: &LayoutRun, default_color: Color) {
|
||||
if run.glyphs.is_empty() {
|
||||
return;
|
||||
|
|
@ -26,7 +27,7 @@ pub fn render_decoration<R: Renderer>(renderer: &mut R, run: &LayoutRun, default
|
|||
None => true,
|
||||
Some(_) => {
|
||||
let prev = &run.glyphs[i - 1];
|
||||
glyph.text_decoration != prev.text_decoration
|
||||
glyph.decoration_data != prev.decoration_data
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ pub fn render_decoration<R: Renderer>(renderer: &mut R, run: &LayoutRun, default
|
|||
if let Some(gs) = group_start {
|
||||
draw_decoration_group(renderer, run, &run.glyphs[gs..i], default_color);
|
||||
}
|
||||
group_start = if has_any_decoration(&glyph.text_decoration) {
|
||||
group_start = if glyph.decoration_data.is_some() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -47,10 +48,6 @@ pub fn render_decoration<R: Renderer>(renderer: &mut R, run: &LayoutRun, default
|
|||
}
|
||||
}
|
||||
|
||||
fn has_any_decoration(td: &TextDecoration) -> bool {
|
||||
td.underline != UnderlineStyle::None || td.overline || td.strikethrough
|
||||
}
|
||||
|
||||
fn draw_decoration_group<R: Renderer>(
|
||||
renderer: &mut R,
|
||||
run: &LayoutRun,
|
||||
|
|
@ -63,19 +60,26 @@ fn draw_decoration_group<R: Renderer>(
|
|||
|
||||
let first = &glyphs[0];
|
||||
let last = &glyphs[glyphs.len() - 1];
|
||||
let td = &glyphs[0].text_decoration;
|
||||
|
||||
// All glyphs in a group have the same decoration_data (guaranteed by grouping logic)
|
||||
let deco = match &first.decoration_data {
|
||||
Some(d) => d,
|
||||
None => return,
|
||||
};
|
||||
let td = &deco.text_decoration;
|
||||
let font_size = first.font_size;
|
||||
|
||||
let x_start = first.x;
|
||||
let x_end = last.x + last.w;
|
||||
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 {
|
||||
let w = width as u32;
|
||||
if w == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Underline
|
||||
match td.underline {
|
||||
UnderlineStyle::None => {}
|
||||
|
|
@ -84,27 +88,27 @@ fn draw_decoration_group<R: Renderer>(
|
|||
.underline_color_opt
|
||||
.or(first.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
let thickness = (first.underline_metrics.thickness * font_size)
|
||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||
.max(1.0)
|
||||
.ceil();
|
||||
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);
|
||||
let y = run.line_y - deco.underline_metrics.offset * font_size;
|
||||
renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
|
||||
}
|
||||
UnderlineStyle::Double => {
|
||||
let color = td
|
||||
.underline_color_opt
|
||||
.or(first.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
let thickness = (first.underline_metrics.thickness * font_size)
|
||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||
.max(1.0)
|
||||
.ceil();
|
||||
let gap = thickness;
|
||||
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);
|
||||
let y = run.line_y - deco.underline_metrics.offset * font_size;
|
||||
renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
|
||||
renderer.rectangle(
|
||||
x_start as i32,
|
||||
(y + thickness + gap) as i32,
|
||||
width,
|
||||
w,
|
||||
thickness as u32,
|
||||
color,
|
||||
);
|
||||
|
|
@ -117,11 +121,11 @@ fn draw_decoration_group<R: Renderer>(
|
|||
.strikethrough_color_opt
|
||||
.or(first.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
let thickness = (first.strikethrough_metrics.thickness * font_size)
|
||||
let thickness = (deco.strikethrough_metrics.thickness * font_size)
|
||||
.max(1.0)
|
||||
.ceil();
|
||||
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);
|
||||
let y = run.line_y - deco.strikethrough_metrics.offset * font_size;
|
||||
renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
|
||||
}
|
||||
|
||||
// Overline
|
||||
|
|
@ -130,14 +134,14 @@ fn draw_decoration_group<R: Renderer>(
|
|||
.overline_color_opt
|
||||
.or(first.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
// we're reusing underline thickness for overline
|
||||
let thickness = (first.underline_metrics.thickness * font_size)
|
||||
// Reuse underline thickness for overline
|
||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||
.max(1.0)
|
||||
.ceil();
|
||||
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);
|
||||
//TODO: this should be run.line_y - ascent, but we don't have per-glyph ascent
|
||||
// in LayoutGlyph. Using line_top as an approximation for now.
|
||||
let y = run.line_top;
|
||||
renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
44
src/shape.rs
44
src/shape.rs
|
|
@ -5,8 +5,8 @@
|
|||
use crate::fallback::FontFallbackIter;
|
||||
use crate::{
|
||||
math, Align, Attrs, AttrsList, CacheKeyFlags, Color, DecorationMetrics, Ellipsize,
|
||||
EllipsizeHeightLimit, Font, FontSystem, Hinting, LayoutGlyph, LayoutLine, Metrics,
|
||||
TextDecoration, Wrap,
|
||||
EllipsizeHeightLimit, Font, FontSystem, GlyphDecorationData, Hinting, LayoutGlyph, LayoutLine,
|
||||
Metrics, Wrap,
|
||||
};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{format, vec, vec::Vec};
|
||||
|
|
@ -16,7 +16,6 @@ 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;
|
||||
|
|
@ -132,7 +131,6 @@ 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 {
|
||||
|
|
@ -239,9 +237,16 @@ fn shape_fallback(
|
|||
metadata: attrs.metadata,
|
||||
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,
|
||||
decoration_data: if attrs.text_decoration.has_decoration() {
|
||||
let (ul_metrics, st_metrics) = decoration_metrics(font);
|
||||
Some(Box::new(GlyphDecorationData {
|
||||
text_decoration: attrs.text_decoration,
|
||||
underline_metrics: ul_metrics,
|
||||
strikethrough_metrics: st_metrics,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -509,7 +514,7 @@ 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 deco_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);
|
||||
|
|
@ -544,9 +549,15 @@ fn shape_skip(
|
|||
&attrs,
|
||||
),
|
||||
metrics_opt: attrs.metrics_opt.map(Into::into),
|
||||
text_decoration: attrs.text_decoration,
|
||||
underline_metrics,
|
||||
strikethrough_metrics,
|
||||
decoration_data: if attrs.text_decoration.has_decoration() {
|
||||
Some(Box::new(GlyphDecorationData {
|
||||
text_decoration: attrs.text_decoration,
|
||||
underline_metrics: deco_metrics.0,
|
||||
strikethrough_metrics: deco_metrics.1,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
|
@ -583,13 +594,12 @@ pub struct ShapeGlyph {
|
|||
pub metadata: usize,
|
||||
pub cache_key_flags: CacheKeyFlags,
|
||||
pub metrics_opt: Option<Metrics>,
|
||||
pub text_decoration: TextDecoration,
|
||||
pub underline_metrics: DecorationMetrics,
|
||||
pub strikethrough_metrics: DecorationMetrics,
|
||||
/// Decoration data, only allocated when decorations are active
|
||||
pub decoration_data: Option<Box<GlyphDecorationData>>,
|
||||
}
|
||||
|
||||
impl ShapeGlyph {
|
||||
const fn layout(
|
||||
fn layout(
|
||||
&self,
|
||||
font_size: f32,
|
||||
line_height_opt: Option<f32>,
|
||||
|
|
@ -615,9 +625,7 @@ impl ShapeGlyph {
|
|||
color_opt: self.color_opt,
|
||||
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,
|
||||
decoration_data: self.decoration_data.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue