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::{
|
use crate::{
|
||||||
render_decoration, Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem,
|
render_decoration, Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem,
|
||||||
BufferLine, Color, Cursor, Ellipsize, FontSystem, Hinting, LayoutCursor, LayoutGlyph,
|
BufferLine, Color, Cursor, DecorationSpan, Ellipsize, FontSystem, Hinting, LayoutCursor,
|
||||||
LayoutLine, LineEnding, LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap,
|
LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping,
|
||||||
|
Wrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A line of visible text for rendering
|
/// A line of visible text for rendering
|
||||||
|
|
@ -26,6 +27,8 @@ pub struct LayoutRun<'a> {
|
||||||
pub rtl: bool,
|
pub rtl: bool,
|
||||||
/// The array of layout glyphs to draw
|
/// The array of layout glyphs to draw
|
||||||
pub glyphs: &'a [LayoutGlyph],
|
pub glyphs: &'a [LayoutGlyph],
|
||||||
|
/// Text decoration spans covering ranges of glyphs
|
||||||
|
pub decorations: &'a [DecorationSpan],
|
||||||
/// Y offset to baseline of line
|
/// Y offset to baseline of line
|
||||||
pub line_y: f32,
|
pub line_y: f32,
|
||||||
/// Y offset to top of line
|
/// Y offset to top of line
|
||||||
|
|
@ -147,6 +150,7 @@ impl<'b> Iterator for LayoutRunIter<'b> {
|
||||||
text: line.text(),
|
text: line.text(),
|
||||||
rtl: shape.rtl,
|
rtl: shape.rtl,
|
||||||
glyphs: &layout_line.glyphs,
|
glyphs: &layout_line.glyphs,
|
||||||
|
decorations: &layout_line.decorations,
|
||||||
line_y,
|
line_y,
|
||||||
line_top,
|
line_top,
|
||||||
line_height,
|
line_height,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
|
|
||||||
|
use core::ops::Range;
|
||||||
|
|
||||||
use crate::{math, CacheKey, CacheKeyFlags, Color, GlyphDecorationData};
|
use crate::{math, CacheKey, CacheKeyFlags, Color, GlyphDecorationData};
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{boxed::Box, vec::Vec};
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use core_maths::CoreFloat;
|
use core_maths::CoreFloat;
|
||||||
|
|
@ -58,8 +60,19 @@ pub struct LayoutGlyph {
|
||||||
pub metadata: usize,
|
pub metadata: usize,
|
||||||
/// [`CacheKeyFlags`]
|
/// [`CacheKeyFlags`]
|
||||||
pub cache_key_flags: 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)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -106,6 +119,8 @@ pub struct LayoutLine {
|
||||||
pub line_height_opt: Option<f32>,
|
pub line_height_opt: Option<f32>,
|
||||||
/// Glyphs in line
|
/// Glyphs in line
|
||||||
pub glyphs: Vec<LayoutGlyph>,
|
pub glyphs: Vec<LayoutGlyph>,
|
||||||
|
/// Text decoration spans covering ranges of glyphs
|
||||||
|
pub decorations: Vec<DecorationSpan>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapping mode
|
/// Wrapping mode
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use core_maths::CoreFloat;
|
use core_maths::CoreFloat;
|
||||||
|
|
||||||
use crate::{Color, LayoutGlyph, LayoutRun, PhysicalGlyph, UnderlineStyle};
|
use crate::{Color, DecorationSpan, LayoutRun, PhysicalGlyph, UnderlineStyle};
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
use crate::{FontSystem, SwashCache};
|
use crate::{FontSystem, SwashCache};
|
||||||
|
|
||||||
|
|
@ -19,62 +19,35 @@ pub trait Renderer {
|
||||||
|
|
||||||
/// Draw text decoration lines (underline, strikethrough, overline) for a layout run.
|
/// 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) {
|
pub fn render_decoration<R: Renderer>(renderer: &mut R, run: &LayoutRun, default_color: Color) {
|
||||||
if run.glyphs.is_empty() {
|
for span in run.decorations {
|
||||||
return;
|
draw_decoration_span(renderer, run, span, default_color);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_decoration_group<R: Renderer>(
|
fn draw_decoration_span<R: Renderer>(
|
||||||
renderer: &mut R,
|
renderer: &mut R,
|
||||||
run: &LayoutRun,
|
run: &LayoutRun,
|
||||||
glyphs: &[LayoutGlyph],
|
span: &DecorationSpan,
|
||||||
default_color: Color,
|
default_color: Color,
|
||||||
) {
|
) {
|
||||||
|
let glyphs = &run.glyphs[span.glyph_range.clone()];
|
||||||
if glyphs.is_empty() {
|
if glyphs.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let first = &glyphs[0];
|
let deco = &span.data;
|
||||||
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 td = &deco.text_decoration;
|
let td = &deco.text_decoration;
|
||||||
let font_size = first.font_size;
|
let font_size = span.font_size;
|
||||||
|
|
||||||
let x_start = first.x;
|
// Compute x extent as min/max over all glyphs, not first/last,
|
||||||
let x_end = last.x + last.w;
|
// because RTL paragraphs store glyphs in right-to-left order.
|
||||||
let width = x_end - x_start;
|
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 {
|
if width <= 0.0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -82,6 +55,7 @@ fn draw_decoration_group<R: Renderer>(
|
||||||
if w == 0 {
|
if w == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let x_start = x_min;
|
||||||
|
|
||||||
// Underline
|
// Underline
|
||||||
match td.underline {
|
match td.underline {
|
||||||
|
|
@ -89,7 +63,7 @@ fn draw_decoration_group<R: Renderer>(
|
||||||
UnderlineStyle::Single => {
|
UnderlineStyle::Single => {
|
||||||
let color = td
|
let color = td
|
||||||
.underline_color_opt
|
.underline_color_opt
|
||||||
.or(first.color_opt)
|
.or(span.color_opt)
|
||||||
.unwrap_or(default_color);
|
.unwrap_or(default_color);
|
||||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||||
.max(1.0)
|
.max(1.0)
|
||||||
|
|
@ -100,7 +74,7 @@ fn draw_decoration_group<R: Renderer>(
|
||||||
UnderlineStyle::Double => {
|
UnderlineStyle::Double => {
|
||||||
let color = td
|
let color = td
|
||||||
.underline_color_opt
|
.underline_color_opt
|
||||||
.or(first.color_opt)
|
.or(span.color_opt)
|
||||||
.unwrap_or(default_color);
|
.unwrap_or(default_color);
|
||||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
let thickness = (deco.underline_metrics.thickness * font_size)
|
||||||
.max(1.0)
|
.max(1.0)
|
||||||
|
|
@ -122,7 +96,7 @@ fn draw_decoration_group<R: Renderer>(
|
||||||
if td.strikethrough {
|
if td.strikethrough {
|
||||||
let color = td
|
let color = td
|
||||||
.strikethrough_color_opt
|
.strikethrough_color_opt
|
||||||
.or(first.color_opt)
|
.or(span.color_opt)
|
||||||
.unwrap_or(default_color);
|
.unwrap_or(default_color);
|
||||||
let thickness = (deco.strikethrough_metrics.thickness * font_size)
|
let thickness = (deco.strikethrough_metrics.thickness * font_size)
|
||||||
.max(1.0)
|
.max(1.0)
|
||||||
|
|
@ -135,7 +109,7 @@ fn draw_decoration_group<R: Renderer>(
|
||||||
if td.overline {
|
if td.overline {
|
||||||
let color = td
|
let color = td
|
||||||
.overline_color_opt
|
.overline_color_opt
|
||||||
.or(first.color_opt)
|
.or(span.color_opt)
|
||||||
.unwrap_or(default_color);
|
.unwrap_or(default_color);
|
||||||
// Reuse underline thickness for overline
|
// Reuse underline thickness for overline
|
||||||
let thickness = (deco.underline_metrics.thickness * font_size)
|
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::fallback::FontFallbackIter;
|
||||||
use crate::{
|
use crate::{
|
||||||
math, Align, Attrs, AttrsList, CacheKeyFlags, Color, DecorationMetrics, Ellipsize,
|
math, Align, Attrs, AttrsList, CacheKeyFlags, Color, DecorationMetrics, DecorationSpan,
|
||||||
EllipsizeHeightLimit, Font, FontSystem, GlyphDecorationData, Hinting, LayoutGlyph, LayoutLine,
|
Ellipsize, EllipsizeHeightLimit, Font, FontSystem, GlyphDecorationData, Hinting, LayoutGlyph,
|
||||||
Metrics, Wrap,
|
LayoutLine, Metrics, Wrap,
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{boxed::Box, format, vec, vec::Vec};
|
use alloc::{boxed::Box, format, vec, vec::Vec};
|
||||||
|
|
@ -625,7 +625,6 @@ impl ShapeGlyph {
|
||||||
color_opt: self.color_opt,
|
color_opt: self.color_opt,
|
||||||
metadata: self.metadata,
|
metadata: self.metadata,
|
||||||
cache_key_flags: self.cache_key_flags,
|
cache_key_flags: self.cache_key_flags,
|
||||||
decoration_data: self.decoration_data.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2688,10 +2687,13 @@ impl ShapeLine {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut decorations: Vec<DecorationSpan> = Vec::new();
|
||||||
|
|
||||||
let process_range = |range: Range<usize>,
|
let process_range = |range: Range<usize>,
|
||||||
x: &mut f32,
|
x: &mut f32,
|
||||||
y: &mut f32,
|
y: &mut f32,
|
||||||
glyphs: &mut Vec<LayoutGlyph>,
|
glyphs: &mut Vec<LayoutGlyph>,
|
||||||
|
decorations: &mut Vec<DecorationSpan>,
|
||||||
max_ascent: &mut f32,
|
max_ascent: &mut f32,
|
||||||
max_descent: &mut f32| {
|
max_descent: &mut f32| {
|
||||||
for r in visual_line.ranges[range.clone()].iter() {
|
for r in visual_line.ranges[range.clone()].iter() {
|
||||||
|
|
@ -2781,6 +2783,24 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glyphs.push(layout_glyph);
|
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 {
|
if !self.rtl {
|
||||||
*x += x_advance;
|
*x += x_advance;
|
||||||
}
|
}
|
||||||
|
|
@ -2799,6 +2819,7 @@ impl ShapeLine {
|
||||||
&mut x,
|
&mut x,
|
||||||
&mut y,
|
&mut y,
|
||||||
&mut glyphs,
|
&mut glyphs,
|
||||||
|
&mut decorations,
|
||||||
&mut max_ascent,
|
&mut max_ascent,
|
||||||
&mut max_descent,
|
&mut max_descent,
|
||||||
);
|
);
|
||||||
|
|
@ -2811,6 +2832,7 @@ impl ShapeLine {
|
||||||
&mut x,
|
&mut x,
|
||||||
&mut y,
|
&mut y,
|
||||||
&mut glyphs,
|
&mut glyphs,
|
||||||
|
&mut decorations,
|
||||||
&mut max_ascent,
|
&mut max_ascent,
|
||||||
&mut max_descent,
|
&mut max_descent,
|
||||||
);
|
);
|
||||||
|
|
@ -2839,6 +2861,7 @@ impl ShapeLine {
|
||||||
max_descent,
|
max_descent,
|
||||||
line_height_opt,
|
line_height_opt,
|
||||||
glyphs,
|
glyphs,
|
||||||
|
decorations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2850,6 +2873,7 @@ impl ShapeLine {
|
||||||
max_descent: 0.0,
|
max_descent: 0.0,
|
||||||
line_height_opt: self.metrics_opt.map(|x| x.line_height),
|
line_height_opt: self.metrics_opt.map(|x| x.line_height),
|
||||||
glyphs: Vec::default(),
|
glyphs: Vec::default(),
|
||||||
|
decorations: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue