Restore underline and strikethrough in render_text (#763)

Another regression from https://github.com/linebender/xilem/pull/754 ...

Needed for #762

---------

Co-authored-by: Tom Churchman <thomas@kepow.org>
This commit is contained in:
Daniel McNab 2024-11-28 16:08:01 +00:00 committed by GitHub
parent 14f3dc2e3e
commit d981f0dc55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 104 additions and 6 deletions

View File

@ -17,7 +17,8 @@ extend-ignore-re = [
# is treated as always incorrect.
[default.extend-identifiers]
wdth = "wdth" # Variable font parameter
wdth = "wdth" # Variable font parameter
Tpyo = "Tpyo" # Intentional typo for a strikethrough test
# Case insensitive
[default.extend-words]

View File

@ -4,7 +4,7 @@
//! Helper functions for working with text in Masonry.
use parley::{Layout, PositionedLayoutItem};
use vello::kurbo::Affine;
use vello::kurbo::{Affine, Line, Stroke};
use vello::peniko::{Brush, Fill};
use vello::Scene;
@ -24,6 +24,39 @@ pub fn render_text(
let PositionedLayoutItem::GlyphRun(glyph_run) = item else {
continue;
};
let style = glyph_run.style();
// We draw underlines under the text, then the strikethrough on top, following:
// https://drafts.csswg.org/css-text-decor/#painting-order
if let Some(underline) = &style.underline {
let underline_brush = &brushes[underline.brush.0];
let run_metrics = glyph_run.run().metrics();
let offset = match underline.offset {
Some(offset) => offset,
None => run_metrics.underline_offset,
};
let width = match underline.size {
Some(size) => size,
None => run_metrics.underline_size,
};
// The `offset` is the distance from the baseline to the top of the underline
// so we move the line down by half the width
// Remember that we are using a y-down coordinate system
// If there's a custom width, because this is an underline, we want the custom
// width to go down from the default expectation
let y = glyph_run.baseline() - offset + width / 2.;
let line = Line::new(
(glyph_run.offset() as f64, y as f64),
((glyph_run.offset() + glyph_run.advance()) as f64, y as f64),
);
scene.stroke(
&Stroke::new(width.into()),
transform,
underline_brush,
None,
&line,
);
}
let mut x = glyph_run.offset();
let y = glyph_run.baseline();
let run = glyph_run.run();
@ -38,7 +71,7 @@ pub fn render_text(
.iter()
.map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord))
.collect::<Vec<_>>();
let brush = &brushes[glyph_run.style().brush.0];
let brush = &brushes[style.brush.0];
scene
.draw_glyphs(font)
.brush(brush)
@ -60,6 +93,36 @@ pub fn render_text(
}
}),
);
if let Some(strikethrough) = &style.strikethrough {
let strikethrough_brush = &brushes[strikethrough.brush.0];
let run_metrics = glyph_run.run().metrics();
let offset = match strikethrough.offset {
Some(offset) => offset,
None => run_metrics.strikethrough_offset,
};
let width = match strikethrough.size {
Some(size) => size,
None => run_metrics.strikethrough_size,
};
// The `offset` is the distance from the baseline to the *top* of the strikethrough
// so we calculate the middle y-position of the strikethrough based on the font's
// standard strikethrough width.
// Remember that we are using a y-down coordinate system
let y = glyph_run.baseline() - offset + run_metrics.strikethrough_size / 2.;
let line = Line::new(
(glyph_run.offset() as f64, y as f64),
((glyph_run.offset() + glyph_run.advance()) as f64, y as f64),
);
scene.stroke(
&Stroke::new(width.into()),
transform,
strikethrough_brush,
None,
&line,
);
}
}
}
}

View File

@ -187,7 +187,10 @@ impl Label {
/// Shared logic between `with_style` and `insert_style`
fn insert_style_inner(&mut self, property: StyleProperty) -> Option<StyleProperty> {
if let StyleProperty::Brush(idx @ BrushIndex(1..)) = &property {
if let StyleProperty::Brush(idx @ BrushIndex(1..))
| StyleProperty::UnderlineBrush(Some(idx @ BrushIndex(1..)))
| StyleProperty::StrikethroughBrush(Some(idx @ BrushIndex(1..))) = &property
{
debug_panic!(
"Can't set a non-zero brush index ({idx:?}) on a `Label`, as it only supports global styling."
);
@ -443,7 +446,7 @@ impl Widget for Label {
mod tests {
use insta::assert_debug_snapshot;
use parley::style::GenericFamily;
use parley::FontFamily;
use parley::{FontFamily, StyleProperty};
use super::*;
use crate::assert_render_snapshot;
@ -475,6 +478,28 @@ mod tests {
assert_render_snapshot!(harness, "styled_label");
}
#[test]
fn underline_label() {
let label = Label::new("Emphasis")
.with_line_break_mode(LineBreaking::WordWrap)
.with_style(StyleProperty::Underline(true));
let mut harness = TestHarness::create_with_size(label, Size::new(100.0, 20.));
assert_render_snapshot!(harness, "underline_label");
}
#[test]
fn strikethrough_label() {
let label = Label::new("Tpyo")
.with_line_break_mode(LineBreaking::WordWrap)
.with_style(StyleProperty::Strikethrough(true))
.with_style(StyleProperty::StrikethroughSize(Some(4.)));
let mut harness = TestHarness::create_with_size(label, Size::new(100.0, 20.));
assert_render_snapshot!(harness, "strikethrough_label");
}
#[test]
/// A wrapping label's alignment should be respected, regardkess of
/// its parent's alignment.

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:593f26a2dfc082d5757d5dd05dc8b09709dcc290bac3313d697238e412f1aca5
size 920

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:97e86abe15e06ca8ad5961e2d101dfa5060d6957067cdf5ea2f9d7d0a03fb39a
size 1617

View File

@ -277,7 +277,10 @@ impl<const EDITABLE: bool> TextArea<EDITABLE> {
/// Shared logic between `with_style` and `insert_style`
#[track_caller]
fn insert_style_inner(&mut self, property: StyleProperty) -> Option<StyleProperty> {
if let StyleProperty::Brush(idx @ BrushIndex(1..)) = &property {
if let StyleProperty::Brush(idx @ BrushIndex(1..))
| StyleProperty::UnderlineBrush(Some(idx @ BrushIndex(1..)))
| StyleProperty::StrikethroughBrush(Some(idx @ BrushIndex(1..))) = &property
{
debug_panic!(
"Can't set a non-zero brush index ({idx:?}) on a `TextArea`, as it only supports global styling.\n\
To modify the active brush, use `set_brush` or `with_brush` instead"