chore: porting to decoration span
fix: BiDi Text Decoration improv: don't use glyph decorations at all
This commit is contained in:
parent
abdbad308f
commit
e8a6b0cc60
4 changed files with 74 additions and 57 deletions
|
|
@ -11,8 +11,9 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||
|
||||
use crate::{
|
||||
render_decoration, Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem,
|
||||
BufferLine, Color, Cursor, Ellipsize, FontSystem, Hinting, LayoutCursor, LayoutGlyph,
|
||||
LayoutLine, LineEnding, LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap,
|
||||
BufferLine, Color, Cursor, DecorationSpan, Ellipsize, FontSystem, Hinting, LayoutCursor,
|
||||
LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping,
|
||||
Wrap,
|
||||
};
|
||||
|
||||
/// A line of visible text for rendering
|
||||
|
|
@ -26,6 +27,8 @@ pub struct LayoutRun<'a> {
|
|||
pub rtl: bool,
|
||||
/// The array of layout glyphs to draw
|
||||
pub glyphs: &'a [LayoutGlyph],
|
||||
/// Text decoration spans covering ranges of glyphs
|
||||
pub decorations: &'a [DecorationSpan],
|
||||
/// Y offset to baseline of line
|
||||
pub line_y: f32,
|
||||
/// Y offset to top of line
|
||||
|
|
@ -147,6 +150,7 @@ impl<'b> Iterator for LayoutRunIter<'b> {
|
|||
text: line.text(),
|
||||
rtl: shape.rtl,
|
||||
glyphs: &layout_line.glyphs,
|
||||
decorations: &layout_line.decorations,
|
||||
line_y,
|
||||
line_top,
|
||||
line_height,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
use core::fmt::Display;
|
||||
|
||||
use core::ops::Range;
|
||||
|
||||
use crate::{math, CacheKey, CacheKeyFlags, Color, GlyphDecorationData};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use core_maths::CoreFloat;
|
||||
|
|
@ -58,8 +60,19 @@ pub struct LayoutGlyph {
|
|||
pub metadata: usize,
|
||||
/// [`CacheKeyFlags`]
|
||||
pub cache_key_flags: CacheKeyFlags,
|
||||
/// Decoration data, only allocated when decorations are active
|
||||
pub decoration_data: Option<Box<GlyphDecorationData>>,
|
||||
}
|
||||
|
||||
/// A span of consecutive glyphs sharing the same text decoration.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DecorationSpan {
|
||||
/// Range of glyph indices in `LayoutLine::glyphs` covered by this span
|
||||
pub glyph_range: Range<usize>,
|
||||
/// The decoration config and metrics
|
||||
pub data: GlyphDecorationData,
|
||||
/// Fallback color from the first glyph's `color_opt`
|
||||
pub color_opt: Option<Color>,
|
||||
/// Font size from the first glyph (used to scale EM-unit metrics)
|
||||
pub font_size: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -106,6 +119,8 @@ pub struct LayoutLine {
|
|||
pub line_height_opt: Option<f32>,
|
||||
/// Glyphs in line
|
||||
pub glyphs: Vec<LayoutGlyph>,
|
||||
/// Text decoration spans covering ranges of glyphs
|
||||
pub decorations: Vec<DecorationSpan>,
|
||||
}
|
||||
|
||||
/// Wrapping mode
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#[cfg(not(feature = "std"))]
|
||||
use core_maths::CoreFloat;
|
||||
|
||||
use crate::{Color, LayoutGlyph, LayoutRun, PhysicalGlyph, UnderlineStyle};
|
||||
use crate::{Color, DecorationSpan, LayoutRun, PhysicalGlyph, UnderlineStyle};
|
||||
#[cfg(feature = "swash")]
|
||||
use crate::{FontSystem, SwashCache};
|
||||
|
||||
|
|
@ -19,62 +19,35 @@ pub trait Renderer {
|
|||
|
||||
/// 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;
|
||||
}
|
||||
|
||||
let mut group_start: Option<usize> = None;
|
||||
|
||||
for (i, glyph) in run.glyphs.iter().enumerate() {
|
||||
let start_new_group = match group_start {
|
||||
None => true,
|
||||
Some(_) => {
|
||||
let prev = &run.glyphs[i - 1];
|
||||
glyph.decoration_data != prev.decoration_data
|
||||
}
|
||||
};
|
||||
|
||||
if start_new_group {
|
||||
if let Some(gs) = group_start {
|
||||
draw_decoration_group(renderer, run, &run.glyphs[gs..i], default_color);
|
||||
}
|
||||
group_start = if glyph.decoration_data.is_some() {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(gs) = group_start {
|
||||
draw_decoration_group(renderer, run, &run.glyphs[gs..], default_color);
|
||||
for span in run.decorations {
|
||||
draw_decoration_span(renderer, run, span, default_color);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_decoration_group<R: Renderer>(
|
||||
fn draw_decoration_span<R: Renderer>(
|
||||
renderer: &mut R,
|
||||
run: &LayoutRun,
|
||||
glyphs: &[LayoutGlyph],
|
||||
span: &DecorationSpan,
|
||||
default_color: Color,
|
||||
) {
|
||||
let glyphs = &run.glyphs[span.glyph_range.clone()];
|
||||
if glyphs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let first = &glyphs[0];
|
||||
let last = &glyphs[glyphs.len() - 1];
|
||||
|
||||
// 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 deco = &span.data;
|
||||
let td = &deco.text_decoration;
|
||||
let font_size = first.font_size;
|
||||
let font_size = span.font_size;
|
||||
|
||||
let x_start = first.x;
|
||||
let x_end = last.x + last.w;
|
||||
let width = x_end - x_start;
|
||||
// Compute x extent as min/max over all glyphs, not first/last,
|
||||
// because RTL paragraphs store glyphs in right-to-left order.
|
||||
let mut x_min = f32::INFINITY;
|
||||
let mut x_max = f32::NEG_INFINITY;
|
||||
for g in glyphs {
|
||||
x_min = x_min.min(g.x);
|
||||
x_max = x_max.max(g.x + g.w);
|
||||
}
|
||||
let width = x_max - x_min;
|
||||
if width <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
|
@ -82,6 +55,7 @@ fn draw_decoration_group<R: Renderer>(
|
|||
if w == 0 {
|
||||
return;
|
||||
}
|
||||
let x_start = x_min;
|
||||
|
||||
// Underline
|
||||
match td.underline {
|
||||
|
|
@ -89,7 +63,7 @@ fn draw_decoration_group<R: Renderer>(
|
|||
UnderlineStyle::Single => {
|
||||
let color = td
|
||||
.underline_color_opt
|
||||
.or(first.color_opt)
|
||||
.or(span.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||
.max(1.0)
|
||||
|
|
@ -100,7 +74,7 @@ fn draw_decoration_group<R: Renderer>(
|
|||
UnderlineStyle::Double => {
|
||||
let color = td
|
||||
.underline_color_opt
|
||||
.or(first.color_opt)
|
||||
.or(span.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||
.max(1.0)
|
||||
|
|
@ -122,7 +96,7 @@ fn draw_decoration_group<R: Renderer>(
|
|||
if td.strikethrough {
|
||||
let color = td
|
||||
.strikethrough_color_opt
|
||||
.or(first.color_opt)
|
||||
.or(span.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
let thickness = (deco.strikethrough_metrics.thickness * font_size)
|
||||
.max(1.0)
|
||||
|
|
@ -135,7 +109,7 @@ fn draw_decoration_group<R: Renderer>(
|
|||
if td.overline {
|
||||
let color = td
|
||||
.overline_color_opt
|
||||
.or(first.color_opt)
|
||||
.or(span.color_opt)
|
||||
.unwrap_or(default_color);
|
||||
// Reuse underline thickness for overline
|
||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||
|
|
|
|||
32
src/shape.rs
32
src/shape.rs
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
use crate::fallback::FontFallbackIter;
|
||||
use crate::{
|
||||
math, Align, Attrs, AttrsList, CacheKeyFlags, Color, DecorationMetrics, Ellipsize,
|
||||
EllipsizeHeightLimit, Font, FontSystem, GlyphDecorationData, Hinting, LayoutGlyph, LayoutLine,
|
||||
Metrics, Wrap,
|
||||
math, Align, Attrs, AttrsList, CacheKeyFlags, Color, DecorationMetrics, DecorationSpan,
|
||||
Ellipsize, EllipsizeHeightLimit, Font, FontSystem, GlyphDecorationData, Hinting, LayoutGlyph,
|
||||
LayoutLine, Metrics, Wrap,
|
||||
};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, format, vec, vec::Vec};
|
||||
|
|
@ -625,7 +625,6 @@ impl ShapeGlyph {
|
|||
color_opt: self.color_opt,
|
||||
metadata: self.metadata,
|
||||
cache_key_flags: self.cache_key_flags,
|
||||
decoration_data: self.decoration_data.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2688,10 +2687,13 @@ impl ShapeLine {
|
|||
None
|
||||
};
|
||||
|
||||
let mut decorations: Vec<DecorationSpan> = Vec::new();
|
||||
|
||||
let process_range = |range: Range<usize>,
|
||||
x: &mut f32,
|
||||
y: &mut f32,
|
||||
glyphs: &mut Vec<LayoutGlyph>,
|
||||
decorations: &mut Vec<DecorationSpan>,
|
||||
max_ascent: &mut f32,
|
||||
max_descent: &mut f32| {
|
||||
for r in visual_line.ranges[range.clone()].iter() {
|
||||
|
|
@ -2781,6 +2783,24 @@ impl ShapeLine {
|
|||
}
|
||||
}
|
||||
glyphs.push(layout_glyph);
|
||||
|
||||
// Build decoration spans inline: extend or close+open
|
||||
let glyph_idx = glyphs.len() - 1;
|
||||
let cur_deco = glyph.decoration_data.as_deref();
|
||||
let extends = match (decorations.last(), cur_deco) {
|
||||
(Some(span), Some(d)) if span.data == *d => true,
|
||||
_ => false,
|
||||
};
|
||||
if extends {
|
||||
decorations.last_mut().unwrap().glyph_range.end = glyph_idx + 1;
|
||||
} else if let Some(d) = cur_deco {
|
||||
decorations.push(DecorationSpan {
|
||||
glyph_range: glyph_idx..glyph_idx + 1,
|
||||
data: d.clone(),
|
||||
color_opt: glyphs[glyph_idx].color_opt,
|
||||
font_size: glyphs[glyph_idx].font_size,
|
||||
});
|
||||
}
|
||||
if !self.rtl {
|
||||
*x += x_advance;
|
||||
}
|
||||
|
|
@ -2799,6 +2819,7 @@ impl ShapeLine {
|
|||
&mut x,
|
||||
&mut y,
|
||||
&mut glyphs,
|
||||
&mut decorations,
|
||||
&mut max_ascent,
|
||||
&mut max_descent,
|
||||
);
|
||||
|
|
@ -2811,6 +2832,7 @@ impl ShapeLine {
|
|||
&mut x,
|
||||
&mut y,
|
||||
&mut glyphs,
|
||||
&mut decorations,
|
||||
&mut max_ascent,
|
||||
&mut max_descent,
|
||||
);
|
||||
|
|
@ -2839,6 +2861,7 @@ impl ShapeLine {
|
|||
max_descent,
|
||||
line_height_opt,
|
||||
glyphs,
|
||||
decorations,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2850,6 +2873,7 @@ impl ShapeLine {
|
|||
max_descent: 0.0,
|
||||
line_height_opt: self.metrics_opt.map(|x| x.line_height),
|
||||
glyphs: Vec::default(),
|
||||
decorations: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue