layout: Support wavy and double for text-decoration-line (#37079)

- Add support for `text-decoration-line: double`: Line drawing is done
similar to how it works in Firefox and Chromium. A gap of half of line
thickness is added between each line.
- Fix support for `text-decoration-line: wavy`: Wavy lines rectangles
were not calcualted properly, which meant they were rendered as solid
lines. Now the amplitude of the wave is 1.5 times line thickness.

Testing: A manual test is updated `tests/html/text_deco_simple.html`
to cover more cases. In general, rendering of text-decorations is
difficult
to test via reftesting.
Fixes #17887.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-05-22 19:42:50 +02:00 committed by GitHub
parent 0ea40d6365
commit 7f0cebd442
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 84 additions and 20 deletions

View file

@ -125,11 +125,15 @@ impl ToWebRender for ComputedTextDecorationStyle {
type Type = LineStyle;
fn to_webrender(&self) -> Self::Type {
match *self {
ComputedTextDecorationStyle::Solid => LineStyle::Solid,
ComputedTextDecorationStyle::Solid | ComputedTextDecorationStyle::Double => {
LineStyle::Solid
},
ComputedTextDecorationStyle::Dotted => LineStyle::Dotted,
ComputedTextDecorationStyle::Dashed => LineStyle::Dashed,
ComputedTextDecorationStyle::Wavy => LineStyle::Wavy,
_ => LineStyle::Solid,
ComputedTextDecorationStyle::MozNone => {
unreachable!("Should never try to draw a moz-none text decoration")
},
}
}
}

View file

@ -11,7 +11,7 @@ use base::id::ScrollTreeNodeId;
use clip::{Clip, ClipId};
use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo};
use embedder_traits::Cursor;
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit};
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
use fonts::GlyphStore;
use gradient::WebRenderGradient;
use range::Range as ServoRange;
@ -21,7 +21,9 @@ use servo_geometry::MaxRect;
use style::Zero;
use style::color::{AbsoluteColor, ColorSpace};
use style::computed_values::border_image_outset::T as BorderImageOutset;
use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
use style::computed_values::text_decoration_style::{
T as ComputedTextDecorationStyle, T as TextDecorationStyle,
};
use style::dom::OpaqueNode;
use style::properties::ComputedValues;
use style::properties::longhands::visibility::computed_value::T as Visibility;
@ -737,6 +739,7 @@ impl Fragment {
let rect = fragment.rect.translate(containing_block.origin.to_vector());
let mut baseline_origin = rect.origin;
baseline_origin.y += fragment.font_metrics.ascent;
let glyphs = glyphs(
&fragment.glyphs,
baseline_origin,
@ -785,11 +788,13 @@ impl Fragment {
rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
rect.size.height =
Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
self.build_display_list_for_text_decoration(
&parent_style,
builder,
&rect,
text_decoration,
TextDecorationLine::UNDERLINE,
);
}
}
@ -804,6 +809,7 @@ impl Fragment {
builder,
&rect,
text_decoration,
TextDecorationLine::OVERLINE,
);
}
}
@ -896,6 +902,7 @@ impl Fragment {
builder,
&rect,
text_decoration,
TextDecorationLine::LINE_THROUGH,
);
}
}
@ -911,22 +918,46 @@ impl Fragment {
builder: &mut DisplayListBuilder,
rect: &PhysicalRect<Au>,
text_decoration: &FragmentTextDecoration,
line: TextDecorationLine,
) {
let rect = rect.to_webrender();
let wavy_line_thickness = (0.33 * rect.size().height).ceil();
if text_decoration.style == ComputedTextDecorationStyle::MozNone {
return;
}
let mut rect = rect.to_webrender();
let line_thickness = rect.height().ceil();
if text_decoration.style == ComputedTextDecorationStyle::Wavy {
rect = rect.inflate(0.0, line_thickness * 1.0);
}
let common_properties = builder.common_properties(rect, parent_style);
builder.wr().push_line(
&common_properties,
&rect,
wavy_line_thickness,
line_thickness,
wr::LineOrientation::Horizontal,
&rgba(text_decoration.color),
text_decoration.style.to_webrender(),
);
// XXX(ferjm) support text-decoration-style: double
if text_decoration.style == TextDecorationStyle::Double {
let half_height = (rect.height() / 2.0).floor().max(1.0);
let y_offset = match line {
TextDecorationLine::OVERLINE => -rect.height() - half_height,
_ => rect.height() + half_height,
};
let rect = rect.translate(Vector2D::new(0.0, y_offset));
let common_properties = builder.common_properties(rect, parent_style);
builder.wr().push_line(
&common_properties,
&rect,
line_thickness,
wr::LineOrientation::Horizontal,
&rgba(text_decoration.color),
text_decoration.style.to_webrender(),
);
}
}
}

View file

@ -1,17 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<p style="text-decoration:none;"> none </p>
<p style="text-decoration:underline;"> text underline </p>
<p style="text-decoration:overline;"> text underline </p>
<p style="text-decoration:line-through;"> text underline </p>
<p>
<p style="font-size:40px; text-decoration:underline; font-family:Verdana;"> text underline pqrstg </p>
<p style="text-decoration:overline;"> text overline xxxxxxxxXXXXXXX</p>
<p style="font-size:50px; text-decoration:line-through;"> text line-through xxxxxxxxXXXXX</p>
<p style="text-decoration:blink;"> text blink </p>
<style>
p {
font-family: "Times New Roman";
background: lightgrey;
padding: 5px;
margin: 5px;
}
</style>
<p style="text-decoration: none blue;">text-decoration: </p>
<p style="text-decoration: underline blue;">text-decoration: </p>
<p style="text-decoration: overline blue;">text-decoration: </p>
<p style="text-decoration: line-through blue;">text-decoration: </p>
<p style="text-decoration: underline double blue;">text-decoration: </p>
<p style="text-decoration: overline double blue;">text-decoration: </p>
<p style="text-decoration: line-through double blue;">text-decoration: </p>
<p style="text-decoration: underline wavy blue;">text-decoration: </p>
<p style="text-decoration: overline wavy blue;">text-decoration: </p>
<p style="text-decoration: line-through wavy blue;">text-decoration: </p>
<p style="text-decoration: underline dashed blue;">text-decoration: </p>
<p style="text-decoration: overline dashed blue;">text-decoration: </p>
<p style="text-decoration: line-through dashed blue;">text-decoration: </p>
<p style="text-decoration: underline dotted blue;">text-decoration: </p>
<p style="text-decoration: overline dotted blue;">text-decoration: </p>
<p style="text-decoration: line-through dotted blue;">text-decoration: </p>
<script>
for (paragraph of document.querySelectorAll("p")) {
paragraph.innerText += " " +
getComputedStyle(paragraph).textDecorationLine + " " +
getComputedStyle(paragraph).textDecorationStyle;
}
</script>
</body>
</html>