canvas: Implement strokeText (#39183)

Mostly it's just reusing/copy&edit fillText stuff.

Testing: Existing WPT tests
Fixes: #29973

Try run: https://github.com/sagudev/servo/actions/runs/17511337550

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
Sam 2025-09-06 20:01:21 +02:00 committed by GitHub
parent bd3231847e
commit 643ac08cf0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 300 additions and 64 deletions

View file

@ -78,6 +78,14 @@ pub(crate) trait GenericDrawTarget {
composition_options: CompositionOptions,
transform: Transform2D<f64>,
);
fn stroke_text(
&mut self,
text_runs: Vec<TextRun>,
style: FillOrStrokeStyle,
line_options: LineOptions,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
);
fn stroke_rect(
&mut self,
rect: &Rect<f32>,

View file

@ -119,6 +119,33 @@ impl<DrawTarget: GenericDrawTarget> CanvasData<DrawTarget> {
);
}
pub(crate) fn stroke_text(
&mut self,
text_bounds: Rect<f64>,
text_runs: Vec<TextRun>,
fill_or_stroke_style: FillOrStrokeStyle,
line_options: LineOptions,
_shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
self.maybe_bound_shape_with_pattern(
fill_or_stroke_style,
composition_options,
&text_bounds,
transform,
|self_, style| {
self_.drawtarget.stroke_text(
text_runs,
style,
line_options,
composition_options,
transform,
);
},
);
}
pub(crate) fn fill_rect(
&mut self,
rect: &Rect<f32>,

View file

@ -124,6 +124,25 @@ impl CanvasPaintThread {
transform,
);
},
Canvas2dMsg::StrokeText(
text_bounds,
text_runs,
fill_or_stroke_style,
line_options,
shadow_options,
composition_options,
transform,
) => {
self.canvas(canvas_id).stroke_text(
text_bounds,
text_runs,
fill_or_stroke_style,
line_options,
shadow_options,
composition_options,
transform,
);
},
Canvas2dMsg::FillRect(rect, style, shadow_options, composition_options, transform) => {
self.canvas(canvas_id).fill_rect(
&rect,
@ -311,6 +330,40 @@ impl Canvas {
}
}
fn stroke_text(
&mut self,
text_bounds: Rect<f64>,
text_runs: Vec<TextRun>,
fill_or_stroke_style: FillOrStrokeStyle,
line_options: LineOptions,
shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
match self {
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.stroke_text(
text_bounds,
text_runs,
fill_or_stroke_style,
line_options,
shadow_options,
composition_options,
transform,
),
#[cfg(feature = "vello_cpu")]
Canvas::VelloCPU(canvas_data) => canvas_data.stroke_text(
text_bounds,
text_runs,
fill_or_stroke_style,
line_options,
shadow_options,
composition_options,
transform,
),
}
}
fn fill_text(
&mut self,
text_bounds: Rect<f64>,

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![deny(unsafe_code)]
#![allow(clippy::too_many_arguments)]
mod backend;

View file

@ -494,6 +494,57 @@ impl GenericDrawTarget for VelloDrawTarget {
})
}
fn stroke_text(
&mut self,
text_runs: Vec<TextRun>,
style: FillOrStrokeStyle,
line_options: LineOptions,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
self.ensure_drawing();
let pattern = convert_to_brush(style, composition_options);
let transform = transform.cast().into();
let line_options: kurbo::Stroke = line_options.convert();
self.with_composition(composition_options.composition_operation, |self_| {
for text_run in text_runs.iter() {
SHARED_FONT_CACHE.with(|font_cache| {
let identifier = &text_run.font.identifier;
if !font_cache.borrow().contains_key(identifier) {
let Some(font_data_and_index) = text_run.font.font_data_and_index() else {
return;
};
let font = font_data_and_index.convert();
font_cache.borrow_mut().insert(identifier.clone(), font);
}
let font_cache = font_cache.borrow();
let Some(font) = font_cache.get(identifier) else {
return;
};
self_
.scene
.draw_glyphs(font)
.transform(transform)
.brush(&pattern)
.font_size(text_run.pt_size)
.draw(
&line_options,
text_run
.glyphs_and_positions
.iter()
.map(|glyph_and_position| vello::Glyph {
id: glyph_and_position.id,
x: glyph_and_position.point.x,
y: glyph_and_position.point.y,
}),
);
});
}
})
}
fn stroke_rect(
&mut self,
rect: &Rect<f32>,

View file

@ -384,6 +384,50 @@ impl GenericDrawTarget for VelloCPUDrawTarget {
})
}
fn stroke_text(
&mut self,
text_runs: Vec<TextRun>,
style: FillOrStrokeStyle,
line_options: LineOptions,
composition_options: CompositionOptions,
transform: Transform2D<f64>,
) {
self.ensure_drawing();
self.ctx.set_paint(paint(style, composition_options.alpha));
self.ctx.set_stroke(line_options.convert());
self.ctx.set_transform(transform.cast().into());
self.with_composition(composition_options.composition_operation, |self_| {
for text_run in text_runs.iter() {
SHARED_FONT_CACHE.with(|font_cache| {
let identifier = &text_run.font.identifier;
if !font_cache.borrow().contains_key(identifier) {
let Some(font_data_and_index) = text_run.font.font_data_and_index() else {
return;
};
let font = font_data_and_index.convert();
font_cache.borrow_mut().insert(identifier.clone(), font);
}
let font_cache = font_cache.borrow();
let Some(font) = font_cache.get(identifier) else {
return;
};
self_
.ctx
.glyph_run(font)
.font_size(text_run.pt_size)
.stroke_glyphs(text_run.glyphs_and_positions.iter().map(
|glyph_and_position| vello_cpu::Glyph {
id: glyph_and_position.id,
x: glyph_and_position.point.x,
y: glyph_and_position.point.y,
},
));
});
}
})
}
fn stroke_rect(
&mut self,
rect: &Rect<f32>,