canvas: Make script/canvas thread boundry mostly stateless (#38164)

This PR removes all `Set*` commands as we will send needed options on
each request. This will allow us to remove most of state handling from
canvas paint thread (currently it's still stateful).

Testing: Existing WPT tests
work towards #38022

try run: https://github.com/sagudev/servo/actions/runs/16413823963

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
sagudev 2025-07-22 14:30:15 +02:00 committed by GitHub
parent 8a1cc69717
commit cd340fa8b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 379 additions and 116 deletions

View file

@ -5,7 +5,7 @@
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::{f32, thread};
use canvas_traits::ConstellationCanvasMsg;
use canvas_traits::canvas::*;
@ -134,76 +134,149 @@ impl<'a> CanvasPaintThread<'a> {
fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) {
match message {
Canvas2dMsg::FillText(text, x, y, max_width, style, is_rtl) => {
self.canvas(canvas_id).set_fill_style(style);
self.canvas(canvas_id)
.fill_text(text, x, y, max_width, is_rtl);
Canvas2dMsg::FillText(
text,
x,
y,
max_width,
style,
is_rtl,
text_options,
shadow_options,
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_fill_style(style);
canvas.set_text_options(text_options);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.fill_text(text, x, y, max_width, is_rtl);
},
Canvas2dMsg::FillRect(rect, style) => {
self.canvas(canvas_id).set_fill_style(style);
self.canvas(canvas_id).fill_rect(&rect);
Canvas2dMsg::FillRect(rect, style, shadow_options, composition_options, transform) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_fill_style(style);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.fill_rect(&rect);
},
Canvas2dMsg::StrokeRect(rect, style) => {
self.canvas(canvas_id).set_stroke_style(style);
self.canvas(canvas_id).stroke_rect(&rect);
Canvas2dMsg::StrokeRect(
rect,
style,
line_options,
shadow_options,
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_stroke_style(style);
canvas.set_line_options(line_options);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.stroke_rect(&rect);
},
Canvas2dMsg::ClearRect(ref rect) => self.canvas(canvas_id).clear_rect(rect),
Canvas2dMsg::FillPath(style, path) => {
self.canvas(canvas_id).set_fill_style(style);
self.canvas(canvas_id).fill_path(&path);
Canvas2dMsg::ClearRect(ref rect, transform) => {
self.canvas(canvas_id).set_transform(&transform);
self.canvas(canvas_id).clear_rect(rect)
},
Canvas2dMsg::StrokePath(style, path) => {
self.canvas(canvas_id).set_stroke_style(style);
self.canvas(canvas_id).stroke_path(&path);
Canvas2dMsg::FillPath(style, path, shadow_options, composition_options, transform) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_fill_style(style);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.fill_path(&path);
},
Canvas2dMsg::ClipPath(path) => self.canvas(canvas_id).clip_path(&path),
Canvas2dMsg::DrawImage(snapshot, dest_rect, source_rect, smoothing_enabled) => {
self.canvas(canvas_id).draw_image(
Canvas2dMsg::StrokePath(
path,
style,
line_options,
shadow_options,
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_stroke_style(style);
canvas.set_line_options(line_options);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.stroke_path(&path);
},
Canvas2dMsg::ClipPath(path, transform) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.clip_path(&path);
},
Canvas2dMsg::DrawImage(
snapshot,
dest_rect,
source_rect,
smoothing_enabled,
shadow_options,
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.draw_image(
snapshot.to_owned(),
dest_rect,
source_rect,
smoothing_enabled,
)
},
Canvas2dMsg::DrawEmptyImage(image_size, dest_rect, source_rect) => self
.canvas(canvas_id)
.draw_image(Snapshot::cleared(image_size), dest_rect, source_rect, false),
Canvas2dMsg::DrawEmptyImage(
image_size,
dest_rect,
source_rect,
shadow_options,
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
self.canvas(canvas_id).draw_image(
Snapshot::cleared(image_size),
dest_rect,
source_rect,
false,
)
},
Canvas2dMsg::DrawImageInOther(
other_canvas_id,
image_size,
dest_rect,
source_rect,
smoothing,
shadow_options,
composition_options,
transform,
) => {
let snapshot = self
.canvas(canvas_id)
.read_pixels(Some(source_rect.to_u32()), Some(image_size));
self.canvas(other_canvas_id).draw_image(
snapshot,
dest_rect,
source_rect,
smoothing,
);
let canvas = self.canvas(other_canvas_id);
canvas.set_transform(&transform);
canvas.set_composition_options(composition_options);
canvas.set_shadow_options(shadow_options);
canvas.draw_image(snapshot, dest_rect, source_rect, smoothing);
},
Canvas2dMsg::MeasureText(text, sender) => {
let metrics = self.canvas(canvas_id).measure_text(text);
Canvas2dMsg::MeasureText(text, sender, text_options) => {
let canvas = self.canvas(canvas_id);
canvas.set_text_options(text_options);
let metrics = canvas.measure_text(text);
sender.send(metrics).unwrap();
},
Canvas2dMsg::RestoreContext => self.canvas(canvas_id).restore_context_state(),
Canvas2dMsg::SaveContext => self.canvas(canvas_id).save_context_state(),
Canvas2dMsg::SetLineWidth(width) => self.canvas(canvas_id).set_line_width(width),
Canvas2dMsg::SetLineCap(cap) => self.canvas(canvas_id).set_line_cap(cap),
Canvas2dMsg::SetLineJoin(join) => self.canvas(canvas_id).set_line_join(join),
Canvas2dMsg::SetMiterLimit(limit) => self.canvas(canvas_id).set_miter_limit(limit),
Canvas2dMsg::SetLineDash(items) => self.canvas(canvas_id).set_line_dash(items),
Canvas2dMsg::SetLineDashOffset(offset) => {
self.canvas(canvas_id).set_line_dash_offset(offset)
},
Canvas2dMsg::SetTransform(ref matrix) => self.canvas(canvas_id).set_transform(matrix),
Canvas2dMsg::SetGlobalAlpha(alpha) => self.canvas(canvas_id).set_global_alpha(alpha),
Canvas2dMsg::SetGlobalComposition(op) => {
self.canvas(canvas_id).set_global_composition(op)
},
Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender) => {
let snapshot = self
.canvas(canvas_id)
@ -214,21 +287,6 @@ impl<'a> CanvasPaintThread<'a> {
self.canvas(canvas_id)
.put_image_data(snapshot.to_owned(), rect);
},
Canvas2dMsg::SetShadowOffsetX(value) => {
self.canvas(canvas_id).set_shadow_offset_x(value)
},
Canvas2dMsg::SetShadowOffsetY(value) => {
self.canvas(canvas_id).set_shadow_offset_y(value)
},
Canvas2dMsg::SetShadowBlur(value) => self.canvas(canvas_id).set_shadow_blur(value),
Canvas2dMsg::SetShadowColor(color) => self.canvas(canvas_id).set_shadow_color(color),
Canvas2dMsg::SetFont(font_style) => self.canvas(canvas_id).set_font(font_style),
Canvas2dMsg::SetTextAlign(text_align) => {
self.canvas(canvas_id).set_text_align(text_align)
},
Canvas2dMsg::SetTextBaseline(text_baseline) => {
self.canvas(canvas_id).set_text_baseline(text_baseline)
},
Canvas2dMsg::UpdateImage(sender) => {
self.canvas(canvas_id).update_image_rendering();
sender.send(()).unwrap();
@ -455,4 +513,41 @@ impl Canvas<'_> {
Canvas::Raqote(canvas_data) => canvas_data.recreate(size),
}
}
fn set_text_options(&mut self, text_options: TextOptions) {
if let Some(font) = text_options.font {
self.set_font(font);
}
self.set_text_align(text_options.align);
self.set_text_baseline(text_options.baseline);
}
fn set_shadow_options(&mut self, shadow_options: ShadowOptions) {
self.set_shadow_color(shadow_options.color);
self.set_shadow_offset_x(shadow_options.offset_x);
self.set_shadow_offset_y(shadow_options.offset_y);
self.set_shadow_blur(shadow_options.blur);
}
fn set_composition_options(&mut self, composition_options: CompositionOptions) {
self.set_global_alpha(composition_options.alpha as f32);
self.set_global_composition(composition_options.composition_operation);
}
fn set_line_options(&mut self, line_options: LineOptions) {
let LineOptions {
width,
cap_style,
join_style,
miter_limit,
dash,
dash_offset,
} = line_options;
self.set_line_width(width as f32);
self.set_line_cap(cap_style);
self.set_line_join(join_style);
self.set_miter_limit(miter_limit as f32);
self.set_line_dash(dash);
self.set_line_dash_offset(dash_offset as f32);
}
}

View file

@ -8,9 +8,10 @@ use std::str::FromStr;
use std::sync::Arc;
use canvas_traits::canvas::{
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle,
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, Path, RadialGradientStyle,
RepetitionStyle, TextAlign, TextBaseline, TextMetrics as CanvasTextMetrics,
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOptions, CompositionOrBlending, Direction,
FillOrStrokeStyle, FillRule, LineCapStyle, LineJoinStyle, LineOptions, LinearGradientStyle,
Path, RadialGradientStyle, RepetitionStyle, ShadowOptions, TextAlign, TextBaseline,
TextMetrics as CanvasTextMetrics, TextOptions,
};
use constellation_traits::ScriptToConstellationMessage;
use cssparser::color::clamp_unit_f32;
@ -146,6 +147,41 @@ impl CanvasContextState {
line_dash_offset: 0.0,
}
}
fn text_options(&self) -> TextOptions {
TextOptions {
font: self.font_style.clone(),
align: self.text_align,
baseline: self.text_baseline,
}
}
fn composition_options(&self) -> CompositionOptions {
CompositionOptions {
alpha: self.global_alpha,
composition_operation: self.global_composition,
}
}
fn shadow_options(&self) -> ShadowOptions {
ShadowOptions {
offset_x: self.shadow_offset_x,
offset_y: self.shadow_offset_y,
blur: self.shadow_blur,
color: self.shadow_color,
}
}
fn line_options(&self) -> LineOptions {
LineOptions {
width: self.line_width,
cap_style: self.line_cap,
join_style: self.line_join,
miter_limit: self.miter_limit,
dash: self.line_dash.iter().map(|x| *x as f32).collect(),
dash_offset: self.line_dash_offset,
}
}
}
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
@ -302,7 +338,10 @@ impl CanvasState {
return;
}
self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(self.size.get().to_f32().into()));
self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(
self.size.get().to_f32().into(),
self.state.borrow().transform,
));
}
fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> {
@ -563,6 +602,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
self.mark_as_dirty(canvas);
@ -609,6 +651,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
self.mark_as_dirty(canvas);
@ -657,6 +702,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
},
OffscreenRenderingContext::BitmapRenderer(ref context) => {
@ -669,6 +717,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
},
OffscreenRenderingContext::Detached => return Err(Error::InvalidState),
@ -678,6 +729,9 @@ impl CanvasState {
image_size,
dest_rect,
source_rect,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
}
@ -728,6 +782,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
},
RenderingContext::BitmapRenderer(ref context) => {
@ -740,6 +797,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
},
RenderingContext::Placeholder(ref context) => {
@ -754,6 +814,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)),
OffscreenRenderingContext::BitmapRenderer(ref context) => {
let Some(snapshot) = context.get_image_data() else {
@ -765,6 +828,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
},
OffscreenRenderingContext::Detached => return Err(Error::InvalidState),
@ -777,6 +843,9 @@ impl CanvasState {
image_size,
dest_rect,
source_rect,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
}
@ -824,6 +893,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
self.mark_as_dirty(canvas);
Ok(())
@ -870,6 +942,9 @@ impl CanvasState {
dest_rect,
source_rect,
smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
self.mark_as_dirty(canvas);
@ -952,7 +1027,6 @@ impl CanvasState {
.borrow_mut()
.transform(state.transform.cast());
state.transform = transform;
self.send_canvas_2d_msg(Canvas2dMsg::SetTransform(state.transform));
if let Some(inverse) = transform.inverse() {
self.current_default_path
.borrow_mut()
@ -964,14 +1038,20 @@ impl CanvasState {
pub(crate) fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64) {
if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
self.send_canvas_2d_msg(Canvas2dMsg::FillRect(rect, style));
self.send_canvas_2d_msg(Canvas2dMsg::FillRect(
rect,
style,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
}
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect
pub(crate) fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) {
if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(rect));
self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(rect, self.state.borrow().transform));
}
}
@ -979,7 +1059,14 @@ impl CanvasState {
pub(crate) fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64) {
if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
self.send_canvas_2d_msg(Canvas2dMsg::StrokeRect(rect, style));
self.send_canvas_2d_msg(Canvas2dMsg::StrokeRect(
rect,
style,
self.state.borrow().line_options(),
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
}
}
@ -994,7 +1081,6 @@ impl CanvasState {
return;
}
self.state.borrow_mut().shadow_offset_x = value;
self.send_canvas_2d_msg(Canvas2dMsg::SetShadowOffsetX(value))
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
@ -1008,7 +1094,6 @@ impl CanvasState {
return;
}
self.state.borrow_mut().shadow_offset_y = value;
self.send_canvas_2d_msg(Canvas2dMsg::SetShadowOffsetY(value))
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
@ -1022,7 +1107,6 @@ impl CanvasState {
return;
}
self.state.borrow_mut().shadow_blur = value;
self.send_canvas_2d_msg(Canvas2dMsg::SetShadowBlur(value))
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
@ -1041,7 +1125,6 @@ impl CanvasState {
) {
if let Ok(rgba) = parse_color(canvas, &value, can_gc) {
self.state.borrow_mut().shadow_color = rgba;
self.send_canvas_2d_msg(Canvas2dMsg::SetShadowColor(rgba))
}
}
@ -1286,7 +1369,6 @@ impl CanvasState {
}
self.state.borrow_mut().global_alpha = alpha;
self.send_canvas_2d_msg(Canvas2dMsg::SetGlobalAlpha(alpha as f32))
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation
@ -1301,7 +1383,6 @@ impl CanvasState {
pub(crate) fn set_global_composite_operation(&self, op_str: DOMString) {
if let Ok(op) = CompositionOrBlending::from_str(&op_str) {
self.state.borrow_mut().global_composition = op;
self.send_canvas_2d_msg(Canvas2dMsg::SetGlobalComposition(op))
}
}
@ -1353,6 +1434,10 @@ impl CanvasState {
max_width,
style,
is_rtl,
self.state.borrow().text_options(),
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
}
@ -1373,7 +1458,11 @@ impl CanvasState {
}
let (sender, receiver) = ipc::channel::<CanvasTextMetrics>().unwrap();
self.send_canvas_2d_msg(Canvas2dMsg::MeasureText(text.into(), sender));
self.send_canvas_2d_msg(Canvas2dMsg::MeasureText(
text.into(),
sender,
self.state.borrow().text_options(),
));
let metrics = receiver.recv().unwrap();
TextMetrics::new(
@ -1413,7 +1502,6 @@ impl CanvasState {
None => return, // syntax error
};
self.state.borrow_mut().font_style = Some((*resolved_font_style).clone());
self.send_canvas_2d_msg(Canvas2dMsg::SetFont((*resolved_font_style).clone()));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-font
@ -1449,7 +1537,6 @@ impl CanvasState {
CanvasTextAlign::Center => TextAlign::Center,
};
self.state.borrow_mut().text_align = text_align;
self.send_canvas_2d_msg(Canvas2dMsg::SetTextAlign(text_align));
}
pub(crate) fn text_baseline(&self) -> CanvasTextBaseline {
@ -1473,7 +1560,6 @@ impl CanvasState {
CanvasTextBaseline::Bottom => TextBaseline::Bottom,
};
self.state.borrow_mut().text_baseline = text_baseline;
self.send_canvas_2d_msg(Canvas2dMsg::SetTextBaseline(text_baseline));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-direction
@ -1507,7 +1593,6 @@ impl CanvasState {
}
self.state.borrow_mut().line_width = width;
self.send_canvas_2d_msg(Canvas2dMsg::SetLineWidth(width as f32))
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
@ -1527,7 +1612,6 @@ impl CanvasState {
CanvasLineCap::Square => LineCapStyle::Square,
};
self.state.borrow_mut().line_cap = line_cap;
self.send_canvas_2d_msg(Canvas2dMsg::SetLineCap(line_cap));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
@ -1547,7 +1631,6 @@ impl CanvasState {
CanvasLineJoin::Miter => LineJoinStyle::Miter,
};
self.state.borrow_mut().line_join = line_join;
self.send_canvas_2d_msg(Canvas2dMsg::SetLineJoin(line_join));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
@ -1562,7 +1645,6 @@ impl CanvasState {
}
self.state.borrow_mut().miter_limit = limit;
self.send_canvas_2d_msg(Canvas2dMsg::SetMiterLimit(limit as f32))
}
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-getlinedash>
@ -1594,9 +1676,6 @@ impl CanvasState {
// > Let the object's dash list be segments.
self.state.borrow_mut().line_dash = line_dash.clone();
self.send_canvas_2d_msg(Canvas2dMsg::SetLineDash(
line_dash.into_iter().map(|dash| dash as f32).collect(),
))
}
/// <https://html.spec.whatwg.org/multipage/#dom-context-2d-linedashoffset>
@ -1615,7 +1694,6 @@ impl CanvasState {
// > other values must change the current value to the new value.
self.state.borrow_mut().line_dash_offset = offset;
self.send_canvas_2d_msg(Canvas2dMsg::SetLineDashOffset(offset as f32));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata
@ -1858,7 +1936,13 @@ impl CanvasState {
pub(crate) fn fill_(&self, path: Path, _fill_rule: CanvasFillRule) {
// TODO: Process fill rule
let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
self.send_canvas_2d_msg(Canvas2dMsg::FillPath(style, path));
self.send_canvas_2d_msg(Canvas2dMsg::FillPath(
style,
path,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
@ -1869,7 +1953,14 @@ impl CanvasState {
pub(crate) fn stroke_(&self, path: Path) {
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(style, path));
self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(
path,
style,
self.state.borrow().line_options(),
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
@ -1881,7 +1972,7 @@ impl CanvasState {
// https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
pub(crate) fn clip_(&self, path: Path, _fill_rule: CanvasFillRule) {
// TODO: Process fill rule
self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(path));
self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(path, self.state.borrow().transform));
}
// https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath

View file

@ -400,6 +400,38 @@ pub enum FillRule {
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct CanvasId(pub u64);
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct CompositionOptions {
pub alpha: f64,
pub composition_operation: CompositionOrBlending,
}
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct ShadowOptions {
pub offset_x: f64,
pub offset_y: f64,
pub blur: f64,
pub color: AbsoluteColor,
}
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct LineOptions {
pub width: f64,
pub cap_style: LineCapStyle,
pub join_style: LineJoinStyle,
pub miter_limit: f64,
pub dash: Vec<f32>,
pub dash_offset: f64,
}
#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct TextOptions {
pub font: Option<FontStyleStruct>,
pub align: TextAlign,
pub baseline: TextBaseline,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Deserialize, Serialize)]
pub enum CanvasMsg {
Canvas2d(Canvas2dMsg, CanvasId),
@ -410,37 +442,82 @@ pub enum CanvasMsg {
#[derive(Debug, Deserialize, Serialize)]
pub enum Canvas2dMsg {
DrawImage(IpcSnapshot, Rect<f64>, Rect<f64>, bool),
DrawEmptyImage(Size2D<u32>, Rect<f64>, Rect<f64>),
DrawImageInOther(CanvasId, Size2D<u32>, Rect<f64>, Rect<f64>, bool),
ClearRect(Rect<f32>),
ClipPath(Path),
FillPath(FillOrStrokeStyle, Path),
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool),
FillRect(Rect<f32>, FillOrStrokeStyle),
DrawImage(
IpcSnapshot,
Rect<f64>,
Rect<f64>,
bool,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
DrawEmptyImage(
Size2D<u32>,
Rect<f64>,
Rect<f64>,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
DrawImageInOther(
CanvasId,
Size2D<u32>,
Rect<f64>,
Rect<f64>,
bool,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
ClearRect(Rect<f32>, Transform2D<f32>),
ClipPath(Path, Transform2D<f32>),
FillPath(
FillOrStrokeStyle,
Path,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
FillText(
String,
f64,
f64,
Option<f64>,
FillOrStrokeStyle,
bool,
TextOptions,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
FillRect(
Rect<f32>,
FillOrStrokeStyle,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
GetImageData(Rect<u32>, Size2D<u32>, IpcSender<IpcSnapshot>),
MeasureText(String, IpcSender<TextMetrics>),
MeasureText(String, IpcSender<TextMetrics>, TextOptions),
PutImageData(Rect<u32>, IpcSnapshot),
RestoreContext,
SaveContext,
StrokeRect(Rect<f32>, FillOrStrokeStyle),
StrokePath(FillOrStrokeStyle, Path),
SetLineWidth(f32),
SetLineCap(LineCapStyle),
SetLineJoin(LineJoinStyle),
SetMiterLimit(f32),
SetLineDash(Vec<f32>),
SetLineDashOffset(f32),
SetGlobalAlpha(f32),
SetGlobalComposition(CompositionOrBlending),
SetTransform(Transform2D<f32>),
SetShadowOffsetX(f64),
SetShadowOffsetY(f64),
SetShadowBlur(f64),
SetShadowColor(AbsoluteColor),
SetFont(FontStyleStruct),
SetTextAlign(TextAlign),
SetTextBaseline(TextBaseline),
StrokeRect(
Rect<f32>,
FillOrStrokeStyle,
LineOptions,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
StrokePath(
Path,
FillOrStrokeStyle,
LineOptions,
ShadowOptions,
CompositionOptions,
Transform2D<f32>,
),
UpdateImage(IpcSender<()>),
}