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::borrow::ToOwned;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::{f32, thread};
use canvas_traits::ConstellationCanvasMsg; use canvas_traits::ConstellationCanvasMsg;
use canvas_traits::canvas::*; use canvas_traits::canvas::*;
@ -134,76 +134,149 @@ impl<'a> CanvasPaintThread<'a> {
fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) { fn process_canvas_2d_message(&mut self, message: Canvas2dMsg, canvas_id: CanvasId) {
match message { match message {
Canvas2dMsg::FillText(text, x, y, max_width, style, is_rtl) => { Canvas2dMsg::FillText(
self.canvas(canvas_id).set_fill_style(style); text,
self.canvas(canvas_id) x,
.fill_text(text, x, y, max_width, is_rtl); 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) => { Canvas2dMsg::FillRect(rect, style, shadow_options, composition_options, transform) => {
self.canvas(canvas_id).set_fill_style(style); let canvas = self.canvas(canvas_id);
self.canvas(canvas_id).fill_rect(&rect); 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) => { Canvas2dMsg::StrokeRect(
self.canvas(canvas_id).set_stroke_style(style); rect,
self.canvas(canvas_id).stroke_rect(&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::ClearRect(ref rect, transform) => {
Canvas2dMsg::FillPath(style, path) => { self.canvas(canvas_id).set_transform(&transform);
self.canvas(canvas_id).set_fill_style(style); self.canvas(canvas_id).clear_rect(rect)
self.canvas(canvas_id).fill_path(&path);
}, },
Canvas2dMsg::StrokePath(style, path) => { Canvas2dMsg::FillPath(style, path, shadow_options, composition_options, transform) => {
self.canvas(canvas_id).set_stroke_style(style); let canvas = self.canvas(canvas_id);
self.canvas(canvas_id).stroke_path(&path); 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::StrokePath(
Canvas2dMsg::DrawImage(snapshot, dest_rect, source_rect, smoothing_enabled) => { path,
self.canvas(canvas_id).draw_image( 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(), snapshot.to_owned(),
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
) )
}, },
Canvas2dMsg::DrawEmptyImage(image_size, dest_rect, source_rect) => self Canvas2dMsg::DrawEmptyImage(
.canvas(canvas_id) image_size,
.draw_image(Snapshot::cleared(image_size), dest_rect, source_rect, false), 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( Canvas2dMsg::DrawImageInOther(
other_canvas_id, other_canvas_id,
image_size, image_size,
dest_rect, dest_rect,
source_rect, source_rect,
smoothing, smoothing,
shadow_options,
composition_options,
transform,
) => { ) => {
let snapshot = self let snapshot = self
.canvas(canvas_id) .canvas(canvas_id)
.read_pixels(Some(source_rect.to_u32()), Some(image_size)); .read_pixels(Some(source_rect.to_u32()), Some(image_size));
self.canvas(other_canvas_id).draw_image( let canvas = self.canvas(other_canvas_id);
snapshot, canvas.set_transform(&transform);
dest_rect, canvas.set_composition_options(composition_options);
source_rect, canvas.set_shadow_options(shadow_options);
smoothing, canvas.draw_image(snapshot, dest_rect, source_rect, smoothing);
);
}, },
Canvas2dMsg::MeasureText(text, sender) => { Canvas2dMsg::MeasureText(text, sender, text_options) => {
let metrics = self.canvas(canvas_id).measure_text(text); let canvas = self.canvas(canvas_id);
canvas.set_text_options(text_options);
let metrics = canvas.measure_text(text);
sender.send(metrics).unwrap(); sender.send(metrics).unwrap();
}, },
Canvas2dMsg::RestoreContext => self.canvas(canvas_id).restore_context_state(), Canvas2dMsg::RestoreContext => self.canvas(canvas_id).restore_context_state(),
Canvas2dMsg::SaveContext => self.canvas(canvas_id).save_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) => { Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender) => {
let snapshot = self let snapshot = self
.canvas(canvas_id) .canvas(canvas_id)
@ -214,21 +287,6 @@ impl<'a> CanvasPaintThread<'a> {
self.canvas(canvas_id) self.canvas(canvas_id)
.put_image_data(snapshot.to_owned(), rect); .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) => { Canvas2dMsg::UpdateImage(sender) => {
self.canvas(canvas_id).update_image_rendering(); self.canvas(canvas_id).update_image_rendering();
sender.send(()).unwrap(); sender.send(()).unwrap();
@ -455,4 +513,41 @@ impl Canvas<'_> {
Canvas::Raqote(canvas_data) => canvas_data.recreate(size), 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 std::sync::Arc;
use canvas_traits::canvas::{ use canvas_traits::canvas::{
Canvas2dMsg, CanvasId, CanvasMsg, CompositionOrBlending, Direction, FillOrStrokeStyle, Canvas2dMsg, CanvasId, CanvasMsg, CompositionOptions, CompositionOrBlending, Direction,
FillRule, LineCapStyle, LineJoinStyle, LinearGradientStyle, Path, RadialGradientStyle, FillOrStrokeStyle, FillRule, LineCapStyle, LineJoinStyle, LineOptions, LinearGradientStyle,
RepetitionStyle, TextAlign, TextBaseline, TextMetrics as CanvasTextMetrics, Path, RadialGradientStyle, RepetitionStyle, ShadowOptions, TextAlign, TextBaseline,
TextMetrics as CanvasTextMetrics, TextOptions,
}; };
use constellation_traits::ScriptToConstellationMessage; use constellation_traits::ScriptToConstellationMessage;
use cssparser::color::clamp_unit_f32; use cssparser::color::clamp_unit_f32;
@ -146,6 +147,41 @@ impl CanvasContextState {
line_dash_offset: 0.0, 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)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
@ -302,7 +338,10 @@ impl CanvasState {
return; 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>> { fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> {
@ -563,6 +602,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
self.mark_as_dirty(canvas); self.mark_as_dirty(canvas);
@ -609,6 +651,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
self.mark_as_dirty(canvas); self.mark_as_dirty(canvas);
@ -657,6 +702,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
}, },
OffscreenRenderingContext::BitmapRenderer(ref context) => { OffscreenRenderingContext::BitmapRenderer(ref context) => {
@ -669,6 +717,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
}, },
OffscreenRenderingContext::Detached => return Err(Error::InvalidState), OffscreenRenderingContext::Detached => return Err(Error::InvalidState),
@ -678,6 +729,9 @@ impl CanvasState {
image_size, image_size,
dest_rect, dest_rect,
source_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, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
}, },
RenderingContext::BitmapRenderer(ref context) => { RenderingContext::BitmapRenderer(ref context) => {
@ -740,6 +797,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
}, },
RenderingContext::Placeholder(ref context) => { RenderingContext::Placeholder(ref context) => {
@ -754,6 +814,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)), )),
OffscreenRenderingContext::BitmapRenderer(ref context) => { OffscreenRenderingContext::BitmapRenderer(ref context) => {
let Some(snapshot) = context.get_image_data() else { let Some(snapshot) = context.get_image_data() else {
@ -765,6 +828,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
}, },
OffscreenRenderingContext::Detached => return Err(Error::InvalidState), OffscreenRenderingContext::Detached => return Err(Error::InvalidState),
@ -777,6 +843,9 @@ impl CanvasState {
image_size, image_size,
dest_rect, dest_rect,
source_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, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
self.mark_as_dirty(canvas); self.mark_as_dirty(canvas);
Ok(()) Ok(())
@ -870,6 +942,9 @@ impl CanvasState {
dest_rect, dest_rect,
source_rect, source_rect,
smoothing_enabled, smoothing_enabled,
self.state.borrow().shadow_options(),
self.state.borrow().composition_options(),
self.state.borrow().transform,
)); ));
self.mark_as_dirty(canvas); self.mark_as_dirty(canvas);
@ -952,7 +1027,6 @@ impl CanvasState {
.borrow_mut() .borrow_mut()
.transform(state.transform.cast()); .transform(state.transform.cast());
state.transform = transform; state.transform = transform;
self.send_canvas_2d_msg(Canvas2dMsg::SetTransform(state.transform));
if let Some(inverse) = transform.inverse() { if let Some(inverse) = transform.inverse() {
self.current_default_path self.current_default_path
.borrow_mut() .borrow_mut()
@ -964,14 +1038,20 @@ impl CanvasState {
pub(crate) fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64) { 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) { if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
let style = self.state.borrow().fill_style.to_fill_or_stroke_style(); 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect
pub(crate) fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) { 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) { 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) { 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) { if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style(); 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; return;
} }
self.state.borrow_mut().shadow_offset_x = value; 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
@ -1008,7 +1094,6 @@ impl CanvasState {
return; return;
} }
self.state.borrow_mut().shadow_offset_y = value; 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
@ -1022,7 +1107,6 @@ impl CanvasState {
return; return;
} }
self.state.borrow_mut().shadow_blur = value; 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 // 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) { if let Ok(rgba) = parse_color(canvas, &value, can_gc) {
self.state.borrow_mut().shadow_color = rgba; 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.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 // 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) { pub(crate) fn set_global_composite_operation(&self, op_str: DOMString) {
if let Ok(op) = CompositionOrBlending::from_str(&op_str) { if let Ok(op) = CompositionOrBlending::from_str(&op_str) {
self.state.borrow_mut().global_composition = op; self.state.borrow_mut().global_composition = op;
self.send_canvas_2d_msg(Canvas2dMsg::SetGlobalComposition(op))
} }
} }
@ -1353,6 +1434,10 @@ impl CanvasState {
max_width, max_width,
style, style,
is_rtl, 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(); 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(); let metrics = receiver.recv().unwrap();
TextMetrics::new( TextMetrics::new(
@ -1413,7 +1502,6 @@ impl CanvasState {
None => return, // syntax error None => return, // syntax error
}; };
self.state.borrow_mut().font_style = Some((*resolved_font_style).clone()); 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-font
@ -1449,7 +1537,6 @@ impl CanvasState {
CanvasTextAlign::Center => TextAlign::Center, CanvasTextAlign::Center => TextAlign::Center,
}; };
self.state.borrow_mut().text_align = text_align; self.state.borrow_mut().text_align = text_align;
self.send_canvas_2d_msg(Canvas2dMsg::SetTextAlign(text_align));
} }
pub(crate) fn text_baseline(&self) -> CanvasTextBaseline { pub(crate) fn text_baseline(&self) -> CanvasTextBaseline {
@ -1473,7 +1560,6 @@ impl CanvasState {
CanvasTextBaseline::Bottom => TextBaseline::Bottom, CanvasTextBaseline::Bottom => TextBaseline::Bottom,
}; };
self.state.borrow_mut().text_baseline = text_baseline; 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction
@ -1507,7 +1593,6 @@ impl CanvasState {
} }
self.state.borrow_mut().line_width = width; 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
@ -1527,7 +1612,6 @@ impl CanvasState {
CanvasLineCap::Square => LineCapStyle::Square, CanvasLineCap::Square => LineCapStyle::Square,
}; };
self.state.borrow_mut().line_cap = line_cap; 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
@ -1547,7 +1631,6 @@ impl CanvasState {
CanvasLineJoin::Miter => LineJoinStyle::Miter, CanvasLineJoin::Miter => LineJoinStyle::Miter,
}; };
self.state.borrow_mut().line_join = line_join; 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
@ -1562,7 +1645,6 @@ impl CanvasState {
} }
self.state.borrow_mut().miter_limit = limit; 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> /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-getlinedash>
@ -1594,9 +1676,6 @@ impl CanvasState {
// > Let the object's dash list be segments. // > Let the object's dash list be segments.
self.state.borrow_mut().line_dash = line_dash.clone(); 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> /// <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. // > other values must change the current value to the new value.
self.state.borrow_mut().line_dash_offset = offset; 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 // 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) { pub(crate) fn fill_(&self, path: Path, _fill_rule: CanvasFillRule) {
// TODO: Process fill rule // TODO: Process fill rule
let style = self.state.borrow().fill_style.to_fill_or_stroke_style(); 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
@ -1869,7 +1953,14 @@ impl CanvasState {
pub(crate) fn stroke_(&self, path: Path) { pub(crate) fn stroke_(&self, path: Path) {
let style = self.state.borrow().stroke_style.to_fill_or_stroke_style(); 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 // 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 // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
pub(crate) fn clip_(&self, path: Path, _fill_rule: CanvasFillRule) { pub(crate) fn clip_(&self, path: Path, _fill_rule: CanvasFillRule) {
// TODO: Process fill rule // 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 // 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)] #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct CanvasId(pub u64); 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)] #[derive(Debug, Deserialize, Serialize)]
pub enum CanvasMsg { pub enum CanvasMsg {
Canvas2d(Canvas2dMsg, CanvasId), Canvas2d(Canvas2dMsg, CanvasId),
@ -410,37 +442,82 @@ pub enum CanvasMsg {
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum Canvas2dMsg { pub enum Canvas2dMsg {
DrawImage(IpcSnapshot, Rect<f64>, Rect<f64>, bool), DrawImage(
DrawEmptyImage(Size2D<u32>, Rect<f64>, Rect<f64>), IpcSnapshot,
DrawImageInOther(CanvasId, Size2D<u32>, Rect<f64>, Rect<f64>, bool), Rect<f64>,
ClearRect(Rect<f32>), Rect<f64>,
ClipPath(Path), bool,
FillPath(FillOrStrokeStyle, Path), ShadowOptions,
FillText(String, f64, f64, Option<f64>, FillOrStrokeStyle, bool), CompositionOptions,
FillRect(Rect<f32>, FillOrStrokeStyle), 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>), GetImageData(Rect<u32>, Size2D<u32>, IpcSender<IpcSnapshot>),
MeasureText(String, IpcSender<TextMetrics>), MeasureText(String, IpcSender<TextMetrics>, TextOptions),
PutImageData(Rect<u32>, IpcSnapshot), PutImageData(Rect<u32>, IpcSnapshot),
RestoreContext, RestoreContext,
SaveContext, SaveContext,
StrokeRect(Rect<f32>, FillOrStrokeStyle), StrokeRect(
StrokePath(FillOrStrokeStyle, Path), Rect<f32>,
SetLineWidth(f32), FillOrStrokeStyle,
SetLineCap(LineCapStyle), LineOptions,
SetLineJoin(LineJoinStyle), ShadowOptions,
SetMiterLimit(f32), CompositionOptions,
SetLineDash(Vec<f32>), Transform2D<f32>,
SetLineDashOffset(f32), ),
SetGlobalAlpha(f32), StrokePath(
SetGlobalComposition(CompositionOrBlending), Path,
SetTransform(Transform2D<f32>), FillOrStrokeStyle,
SetShadowOffsetX(f64), LineOptions,
SetShadowOffsetY(f64), ShadowOptions,
SetShadowBlur(f64), CompositionOptions,
SetShadowColor(AbsoluteColor), Transform2D<f32>,
SetFont(FontStyleStruct), ),
SetTextAlign(TextAlign),
SetTextBaseline(TextBaseline),
UpdateImage(IpcSender<()>), UpdateImage(IpcSender<()>),
} }