mirror of https://github.com/linebender/xilem
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:
parent
14f3dc2e3e
commit
d981f0dc55
|
@ -18,6 +18,7 @@ extend-ignore-re = [
|
||||||
|
|
||||||
[default.extend-identifiers]
|
[default.extend-identifiers]
|
||||||
wdth = "wdth" # Variable font parameter
|
wdth = "wdth" # Variable font parameter
|
||||||
|
Tpyo = "Tpyo" # Intentional typo for a strikethrough test
|
||||||
|
|
||||||
# Case insensitive
|
# Case insensitive
|
||||||
[default.extend-words]
|
[default.extend-words]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//! Helper functions for working with text in Masonry.
|
//! Helper functions for working with text in Masonry.
|
||||||
|
|
||||||
use parley::{Layout, PositionedLayoutItem};
|
use parley::{Layout, PositionedLayoutItem};
|
||||||
use vello::kurbo::Affine;
|
use vello::kurbo::{Affine, Line, Stroke};
|
||||||
use vello::peniko::{Brush, Fill};
|
use vello::peniko::{Brush, Fill};
|
||||||
use vello::Scene;
|
use vello::Scene;
|
||||||
|
|
||||||
|
@ -24,6 +24,39 @@ pub fn render_text(
|
||||||
let PositionedLayoutItem::GlyphRun(glyph_run) = item else {
|
let PositionedLayoutItem::GlyphRun(glyph_run) = item else {
|
||||||
continue;
|
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 mut x = glyph_run.offset();
|
||||||
let y = glyph_run.baseline();
|
let y = glyph_run.baseline();
|
||||||
let run = glyph_run.run();
|
let run = glyph_run.run();
|
||||||
|
@ -38,7 +71,7 @@ pub fn render_text(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord))
|
.map(|coord| vello::skrifa::instance::NormalizedCoord::from_bits(*coord))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let brush = &brushes[glyph_run.style().brush.0];
|
let brush = &brushes[style.brush.0];
|
||||||
scene
|
scene
|
||||||
.draw_glyphs(font)
|
.draw_glyphs(font)
|
||||||
.brush(brush)
|
.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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,10 @@ impl Label {
|
||||||
|
|
||||||
/// Shared logic between `with_style` and `insert_style`
|
/// Shared logic between `with_style` and `insert_style`
|
||||||
fn insert_style_inner(&mut self, property: StyleProperty) -> Option<StyleProperty> {
|
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!(
|
debug_panic!(
|
||||||
"Can't set a non-zero brush index ({idx:?}) on a `Label`, as it only supports global styling."
|
"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 {
|
mod tests {
|
||||||
use insta::assert_debug_snapshot;
|
use insta::assert_debug_snapshot;
|
||||||
use parley::style::GenericFamily;
|
use parley::style::GenericFamily;
|
||||||
use parley::FontFamily;
|
use parley::{FontFamily, StyleProperty};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::assert_render_snapshot;
|
use crate::assert_render_snapshot;
|
||||||
|
@ -475,6 +478,28 @@ mod tests {
|
||||||
assert_render_snapshot!(harness, "styled_label");
|
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]
|
#[test]
|
||||||
/// A wrapping label's alignment should be respected, regardkess of
|
/// A wrapping label's alignment should be respected, regardkess of
|
||||||
/// its parent's alignment.
|
/// its parent's alignment.
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:593f26a2dfc082d5757d5dd05dc8b09709dcc290bac3313d697238e412f1aca5
|
||||||
|
size 920
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:97e86abe15e06ca8ad5961e2d101dfa5060d6957067cdf5ea2f9d7d0a03fb39a
|
||||||
|
size 1617
|
|
@ -277,7 +277,10 @@ impl<const EDITABLE: bool> TextArea<EDITABLE> {
|
||||||
/// Shared logic between `with_style` and `insert_style`
|
/// Shared logic between `with_style` and `insert_style`
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn insert_style_inner(&mut self, property: StyleProperty) -> Option<StyleProperty> {
|
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!(
|
debug_panic!(
|
||||||
"Can't set a non-zero brush index ({idx:?}) on a `TextArea`, as it only supports global styling.\n\
|
"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"
|
To modify the active brush, use `set_brush` or `with_brush` instead"
|
||||||
|
|
Loading…
Reference in New Issue