1#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::Cell;
8use std::fmt;
9use std::str::FromStr;
10use std::sync::Arc;
11
12use app_units::Au;
13use cssparser::Parser;
14use cssparser::color::clamp_unit_f32;
15use euclid::default::{Point2D, Rect, Size2D, Transform2D};
16use euclid::{Vector2D, vec2};
17use fonts::{
18 FontBaseline, FontContext, FontGroup, FontIdentifier, FontMetrics, FontRef, ShapingFlags,
19 ShapingOptions,
20};
21use icu_locale_core::subtags::Language;
22use js::context::{JSContext, NoGC};
23use net_traits::image_cache::{ImageCache, ImageResponse};
24use net_traits::request::CorsSettings;
25use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
26use script_bindings::cell::DomRefCell;
27use servo_arc::Arc as ServoArc;
28use servo_base::generic_channel::GenericBufferedSender;
29use servo_base::{Epoch, generic_channel};
30use servo_canvas_traits::canvas::{
31 CanvasCommand, CanvasFont, CanvasId, CanvasMsg, CompositionOptions, CompositionOrBlending,
32 FillOrStrokeStyle, FillRule, GlyphAndPosition, LineCapStyle, LineJoinStyle, LineOptions,
33 LinearGradientStyle, Path, RadialGradientStyle, RangeError, RepetitionStyle, RoundRectRadius,
34 ShadowOptions, TextRun,
35};
36use servo_constellation_traits::ScriptToConstellationMessage;
37use servo_url::{ImmutableOrigin, ServoUrl};
38use style::Zero;
39use style::color::{AbsoluteColor, ColorFlags, ColorSpace};
40use style::computed_values::font_variant_position::T as FontVariantPosition;
41use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
42use style::properties::style_structs::Font;
43use style::stylesheets::CssRuleType;
44use style::values::computed::color::Color as ComputedColor;
45use style::values::computed::font::FontStyle;
46use style::values::computed::{
47 FontFeatureSettings, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric,
48};
49use style::values::specified::color::Color;
50use style_traits::values::ToCss;
51use style_traits::{CssWriter, ParsingMode};
52use unicode_script::Script;
53use webrender_api::ImageKey;
54
55use crate::canvas_context::{CanvasContext, OffscreenRenderingContext, RenderingContext};
56use crate::conversions::Convert;
57use crate::css::css::{ANONYMOUS_CONTENT_URL_DATA, parser_context_for_anonymous_content};
58use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
59 CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin,
60 CanvasTextAlign, CanvasTextBaseline, ImageDataMethods,
61};
62use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::DOMMatrix2DInit;
63use crate::dom::bindings::codegen::UnionTypes::{
64 StringOrCanvasGradientOrCanvasPattern, UnrestrictedDoubleOrDOMPointInit,
65 UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence,
66};
67use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
68use crate::dom::bindings::inheritance::Castable;
69use crate::dom::bindings::num::Finite;
70use crate::dom::bindings::root::{Dom, DomRoot};
71use crate::dom::bindings::str::DOMString;
72use crate::dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle};
73use crate::dom::canvaspattern::CanvasPattern;
74use crate::dom::dommatrix::DOMMatrix;
75use crate::dom::dommatrixreadonly::dommatrix2dinit_to_matrix;
76use crate::dom::element::Element;
77use crate::dom::globalscope::GlobalScope;
78use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
79use crate::dom::html::htmlimageelement::HTMLImageElement;
80use crate::dom::html::htmlvideoelement::HTMLVideoElement;
81use crate::dom::imagebitmap::ImageBitmap;
82use crate::dom::imagedata::ImageData;
83use crate::dom::node::{Node, NodeTraits};
84use crate::dom::offscreencanvas::OffscreenCanvas;
85use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
86use crate::dom::textmetrics::TextMetrics;
87
88const HANGING_BASELINE_DEFAULT: f64 = 0.8;
89const IDEOGRAPHIC_BASELINE_DEFAULT: f64 = 0.5;
90
91#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
92#[derive(Clone, JSTraceable, MallocSizeOf)]
93pub(super) enum CanvasFillOrStrokeStyle {
94 Color(#[no_trace] AbsoluteColor),
95 Gradient(Dom<CanvasGradient>),
96 Pattern(Dom<CanvasPattern>),
97}
98
99impl CanvasFillOrStrokeStyle {
100 fn to_fill_or_stroke_style(&self) -> FillOrStrokeStyle {
101 match self {
102 CanvasFillOrStrokeStyle::Color(rgba) => FillOrStrokeStyle::Color(*rgba),
103 CanvasFillOrStrokeStyle::Gradient(gradient) => gradient.to_fill_or_stroke_style(),
104 CanvasFillOrStrokeStyle::Pattern(pattern) => pattern.to_fill_or_stroke_style(),
105 }
106 }
107}
108
109#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
110#[derive(Clone, JSTraceable, MallocSizeOf)]
111pub(super) struct CanvasContextState {
112 global_alpha: f64,
113 #[no_trace]
114 global_composition: CompositionOrBlending,
115 image_smoothing_enabled: bool,
116 fill_style: CanvasFillOrStrokeStyle,
117 stroke_style: CanvasFillOrStrokeStyle,
118 line_width: f64,
119 #[no_trace]
120 line_cap: LineCapStyle,
121 #[no_trace]
122 line_join: LineJoinStyle,
123 miter_limit: f64,
124 line_dash: Vec<f64>,
125 line_dash_offset: f64,
126 #[no_trace]
127 transform: Transform2D<f64>,
128 shadow_offset_x: f64,
129 shadow_offset_y: f64,
130 shadow_blur: f64,
131 #[no_trace]
132 shadow_color: AbsoluteColor,
133 #[no_trace]
134 #[conditional_malloc_size_of]
135 font_style: Option<ServoArc<Font>>,
136 text_align: CanvasTextAlign,
137 text_baseline: CanvasTextBaseline,
138 direction: CanvasDirection,
139 clips_pushed: usize,
142}
143
144impl CanvasContextState {
145 const DEFAULT_FONT_STYLE: &'static str = "10px sans-serif";
146
147 pub(super) fn new() -> CanvasContextState {
148 CanvasContextState {
149 global_alpha: 1.0,
150 global_composition: CompositionOrBlending::default(),
151 image_smoothing_enabled: true,
152 fill_style: CanvasFillOrStrokeStyle::Color(AbsoluteColor::BLACK),
153 stroke_style: CanvasFillOrStrokeStyle::Color(AbsoluteColor::BLACK),
154 line_width: 1.0,
155 line_cap: LineCapStyle::Butt,
156 line_join: LineJoinStyle::Miter,
157 miter_limit: 10.0,
158 transform: Transform2D::identity(),
159 shadow_offset_x: 0.0,
160 shadow_offset_y: 0.0,
161 shadow_blur: 0.0,
162 shadow_color: AbsoluteColor::TRANSPARENT_BLACK,
163 font_style: None,
164 text_align: CanvasTextAlign::Start,
165 text_baseline: CanvasTextBaseline::Alphabetic,
166 direction: CanvasDirection::Inherit,
167 line_dash: Vec::new(),
168 line_dash_offset: 0.0,
169 clips_pushed: 0,
170 }
171 }
172
173 fn composition_options(&self) -> CompositionOptions {
174 CompositionOptions {
175 alpha: self.global_alpha,
176 composition_operation: self.global_composition,
177 }
178 }
179
180 fn shadow_options(&self) -> ShadowOptions {
181 ShadowOptions {
182 offset_x: self.shadow_offset_x,
183 offset_y: self.shadow_offset_y,
184 blur: self.shadow_blur,
185 color: self.shadow_color,
186 }
187 }
188
189 fn line_options(&self) -> LineOptions {
190 LineOptions {
191 width: self.line_width,
192 cap_style: self.line_cap,
193 join_style: self.line_join,
194 miter_limit: self.miter_limit,
195 dash: self.line_dash.iter().map(|x| *x as f32).collect(),
196 dash_offset: self.line_dash_offset,
197 }
198 }
199}
200
201#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
202#[derive(JSTraceable, MallocSizeOf)]
203pub(super) struct CanvasState {
204 #[no_trace]
205 canvas_id: CanvasId,
206 #[no_trace]
207 size: Cell<Size2D<u64>>,
208 state: DomRefCell<CanvasContextState>,
209 origin_clean: Cell<bool>,
210 #[ignore_malloc_size_of = "ImageCache"]
211 #[no_trace]
212 image_cache: Arc<dyn ImageCache>,
213 #[no_trace]
216 base_url: ServoUrl,
217 #[no_trace]
218 origin: ImmutableOrigin,
219 #[no_trace]
221 missing_image_urls: DomRefCell<Vec<ServoUrl>>,
222 saved_states: DomRefCell<Vec<CanvasContextState>>,
223 #[no_trace]
225 current_default_path: DomRefCell<Path>,
226 #[no_trace]
228 pub(super) buffered_sender: GenericBufferedSender<CanvasMsg, CanvasCommand>,
229}
230
231pub(super) fn round_rect_radii(
234 radii: &UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence,
235) -> Vec<RoundRectRadius> {
236 fn from_inner(radius: &UnrestrictedDoubleOrDOMPointInit) -> RoundRectRadius {
237 match radius {
238 UnrestrictedDoubleOrDOMPointInit::UnrestrictedDouble(radius) => RoundRectRadius {
239 x: *radius,
240 y: *radius,
241 },
242 UnrestrictedDoubleOrDOMPointInit::DOMPointInit(point) => RoundRectRadius {
243 x: point.x,
244 y: point.y,
245 },
246 }
247 }
248
249 match radii {
250 UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence::UnrestrictedDouble(radius) => {
251 vec![RoundRectRadius {
252 x: *radius,
253 y: *radius,
254 }]
255 },
256 UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence::DOMPointInit(point) => {
257 vec![RoundRectRadius {
258 x: point.x,
259 y: point.y,
260 }]
261 },
262 UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence::UnrestrictedDoubleOrDOMPointInitSequence(radii) => {
263 radii.iter().map(from_inner).collect()
264 },
265 }
266}
267
268pub(super) fn round_rect_error(error: RangeError) -> Error {
270 match error {
271 RangeError::InvalidSize => {
272 Error::Range(c"radii must be a list of size one, two, three, or four".to_owned())
273 },
274 RangeError::NegativeRadius => Error::Range(c"radii must not be negative".to_owned()),
275 }
276}
277
278impl CanvasState {
279 pub(super) fn new(global: &GlobalScope, size: Size2D<u64>) -> Option<CanvasState> {
280 debug!("Creating new canvas rendering context.");
281 let (sender, receiver) =
282 profile_traits::generic_channel::channel(global.time_profiler_chan().clone()).unwrap();
283 let script_to_constellation_chan = global.script_to_constellation_chan();
284 debug!("Asking constellation to create new canvas thread.");
285 let size = adjust_canvas_size(size);
286 script_to_constellation_chan
287 .send(ScriptToConstellationMessage::CreateCanvasPaintThread(
288 size, sender,
289 ))
290 .unwrap();
291 let (canvas_thread_sender, canvas_id) = receiver.recv().ok()??;
292 debug!("Done.");
293 let origin = if global.is::<PaintWorkletGlobalScope>() {
297 global.api_base_url().origin()
298 } else {
299 global.origin().immutable().clone()
300 };
301 Some(CanvasState {
302 canvas_id,
303 size: Cell::new(size),
304 state: DomRefCell::new(CanvasContextState::new()),
305 origin_clean: Cell::new(true),
306 image_cache: global.image_cache(),
307 base_url: global.api_base_url(),
308 missing_image_urls: DomRefCell::new(Vec::new()),
309 saved_states: DomRefCell::new(Vec::new()),
310 origin,
311 current_default_path: DomRefCell::new(Path::new()),
312 buffered_sender: GenericBufferedSender::new(
313 canvas_thread_sender,
314 Box::new(move |cmds| (canvas_id, CanvasCommand::ProcessBatchMessages(cmds))),
315 servo_config::pref!(dom_canvas_msg_buffer_size) as usize,
316 ),
317 })
318 }
319
320 pub(super) fn set_image_key(&self, image_key: ImageKey) {
321 self.send_canvas_command_immediate(CanvasCommand::SetImageKey(image_key));
323 }
324
325 pub(super) fn get_missing_image_urls(&self) -> &DomRefCell<Vec<ServoUrl>> {
326 &self.missing_image_urls
327 }
328
329 pub(super) fn get_canvas_id(&self) -> CanvasId {
330 self.canvas_id
331 }
332
333 pub(super) fn bitmap_dimensions(&self) -> Size2D<u64> {
334 self.size.get()
335 }
336
337 pub(super) fn is_paintable(&self) -> bool {
338 !self.size.get().is_empty()
339 }
340
341 pub(super) fn send_canvas_command(&self, msg: CanvasCommand) {
342 if !self.is_paintable() {
343 return;
344 }
345 self.buffered_sender.send(msg).unwrap();
346 }
347
348 pub(super) fn send_canvas_command_immediate(&self, msg: CanvasCommand) {
353 if !self.is_paintable() {
354 self.buffered_sender.flush().unwrap();
355 } else {
356 self.buffered_sender.send_immediate(msg).unwrap();
357 }
358 }
359
360 pub(super) fn update_rendering(&self, canvas_epoch: Option<Epoch>) -> bool {
362 if !self.is_paintable() {
363 return false;
364 }
365
366 self.send_canvas_command_immediate(CanvasCommand::UpdateImage(canvas_epoch));
367 true
368 }
369
370 pub(super) fn set_bitmap_dimensions(&self, size: Size2D<u64>) {
372 self.reset_to_initial_state();
374
375 self.size.replace(adjust_canvas_size(size));
377 self.buffered_sender.discard();
379 self.send_canvas_command_immediate(CanvasCommand::Recreate(Some(self.size.get())));
381 }
382
383 pub(super) fn reset(&self) {
385 self.reset_to_initial_state();
386
387 if !self.is_paintable() {
388 return;
389 }
390
391 self.buffered_sender.discard();
394 self.send_canvas_command_immediate(CanvasCommand::Recreate(None));
396 }
397
398 fn reset_to_initial_state(&self) {
400 *self.current_default_path.borrow_mut() = Path::new();
402
403 self.saved_states.borrow_mut().clear();
405
406 *self.state.borrow_mut() = CanvasContextState::new();
408
409 self.set_origin_clean(true);
415 }
416
417 pub(super) fn reset_bitmap(&self) {
418 if !self.is_paintable() {
419 return;
420 }
421
422 self.send_canvas_command(CanvasCommand::ClearRect(
423 self.size.get().to_f32().into(),
424 Transform2D::identity(),
425 ));
426 }
427
428 fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> {
429 if !([x, y, w, h].iter().all(|val| val.is_finite())) {
430 return None;
431 }
432
433 if w == 0.0 && h == 0.0 {
434 return None;
435 }
436
437 Some(Rect::new(
438 Point2D::new(x as f32, y as f32),
439 Size2D::new(w as f32, h as f32),
440 ))
441 }
442
443 pub(super) fn origin_is_clean(&self) -> bool {
444 self.origin_clean.get()
445 }
446
447 fn set_origin_clean(&self, origin_clean: bool) {
448 self.origin_clean.set(origin_clean);
449 }
450
451 fn is_origin_clean(&self, source: CanvasImageSource) -> bool {
453 match source {
454 CanvasImageSource::HTMLImageElement(image) => {
455 image.same_origin(&GlobalScope::entry().origin())
456 },
457 CanvasImageSource::HTMLVideoElement(video) => video.origin_is_clean(),
458 CanvasImageSource::HTMLCanvasElement(canvas) => canvas.origin_is_clean(),
459 CanvasImageSource::ImageBitmap(bitmap) => bitmap.origin_is_clean(),
460 CanvasImageSource::OffscreenCanvas(canvas) => canvas.origin_is_clean(),
461 CanvasImageSource::CSSStyleValue(_) => true,
462 }
463 }
464
465 fn fetch_image_data(
466 &self,
467 url: ServoUrl,
468 cors_setting: Option<CorsSettings>,
469 ) -> Option<Snapshot> {
470 let raster_image = match self.request_image_from_cache(url, cors_setting) {
471 ImageResponse::Loaded(image, _) => {
472 if let Some(image) = image.as_raster_image() {
473 image
474 } else {
475 warn!("Vector images are not supported as image source in canvas2d");
477 return None;
478 }
479 },
480 ImageResponse::FailedToLoadOrDecode | ImageResponse::MetadataLoaded(_) => {
481 return None;
482 },
483 };
484
485 Some(raster_image.as_snapshot())
486 }
487
488 fn request_image_from_cache(
489 &self,
490 url: ServoUrl,
491 cors_setting: Option<CorsSettings>,
492 ) -> ImageResponse {
493 match self
494 .image_cache
495 .get_image(url.clone(), self.origin.clone(), cors_setting)
496 {
497 Some(image) => ImageResponse::Loaded(image, url),
498 None => {
499 self.missing_image_urls.borrow_mut().push(url);
502 ImageResponse::FailedToLoadOrDecode
503 },
504 }
505 }
506
507 #[expect(clippy::too_many_arguments)]
530 fn draw_image_internal(
531 &self,
532 htmlcanvas: Option<&HTMLCanvasElement>,
533 image: CanvasImageSource,
534 sx: f64,
535 sy: f64,
536 sw: Option<f64>,
537 sh: Option<f64>,
538 dx: f64,
539 dy: f64,
540 dw: Option<f64>,
541 dh: Option<f64>,
542 ) -> ErrorResult {
543 if !self.is_paintable() {
544 return Ok(());
545 }
546
547 let result = match image {
548 CanvasImageSource::HTMLImageElement(ref image) => {
549 if !image.is_usable()? {
553 return Ok(());
554 }
555
556 self.draw_html_image_element(image, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
557 Ok(())
558 },
559 CanvasImageSource::HTMLVideoElement(ref video) => {
560 if !video.is_usable() {
564 return Ok(());
565 }
566
567 self.draw_html_video_element(video, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
568 Ok(())
569 },
570 CanvasImageSource::HTMLCanvasElement(ref canvas) => {
571 if canvas.get_size().is_empty() {
573 return Err(Error::InvalidState(None));
574 }
575
576 self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
577 },
578 CanvasImageSource::ImageBitmap(ref bitmap) => {
579 if bitmap.is_detached() {
581 return Err(Error::InvalidState(None));
582 }
583
584 self.draw_image_bitmap(bitmap, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
585 Ok(())
586 },
587 CanvasImageSource::OffscreenCanvas(ref canvas) => {
588 if canvas.get_size().is_empty() {
590 return Err(Error::InvalidState(None));
591 }
592
593 self.draw_offscreen_canvas(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
594 },
595 CanvasImageSource::CSSStyleValue(ref value) => {
596 let url = value
597 .get_url(self.base_url.clone())
598 .ok_or(Error::InvalidState(None))?;
599 self.fetch_and_draw_image_data(
600 htmlcanvas, url, None, sx, sy, sw, sh, dx, dy, dw, dh,
601 )
602 },
603 };
604
605 if result.is_ok() && !self.is_origin_clean(image) {
606 self.set_origin_clean(false);
607 }
608 result
609 }
610
611 #[expect(clippy::too_many_arguments)]
613 fn draw_html_image_element(
614 &self,
615 image: &HTMLImageElement,
616 canvas: Option<&HTMLCanvasElement>,
617 sx: f64,
618 sy: f64,
619 sw: Option<f64>,
620 sh: Option<f64>,
621 dx: f64,
622 dy: f64,
623 dw: Option<f64>,
624 dh: Option<f64>,
625 ) {
626 let Some(snapshot) = image.get_raster_image_data() else {
627 return;
628 };
629
630 let image_size = snapshot.size();
632 let dw = dw.unwrap_or(image_size.width as f64);
633 let dh = dh.unwrap_or(image_size.height as f64);
634 let sw = sw.unwrap_or(image_size.width as f64);
635 let sh = sh.unwrap_or(image_size.height as f64);
636
637 let (source_rect, dest_rect) =
638 self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
639
640 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
642 return;
643 }
644
645 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
646
647 self.send_canvas_command(CanvasCommand::DrawImage(
648 snapshot.to_shared(),
649 dest_rect,
650 source_rect,
651 smoothing_enabled,
652 self.state.borrow().shadow_options(),
653 self.state.borrow().composition_options(),
654 self.state.borrow().transform,
655 ));
656
657 self.mark_as_dirty(canvas);
658 }
659
660 #[expect(clippy::too_many_arguments)]
662 fn draw_html_video_element(
663 &self,
664 video: &HTMLVideoElement,
665 canvas: Option<&HTMLCanvasElement>,
666 sx: f64,
667 sy: f64,
668 sw: Option<f64>,
669 sh: Option<f64>,
670 dx: f64,
671 dy: f64,
672 dw: Option<f64>,
673 dh: Option<f64>,
674 ) {
675 let Some(snapshot) = video.get_current_frame_data() else {
676 return;
677 };
678
679 let video_size = snapshot.size();
681 let dw = dw.unwrap_or(video_size.width as f64);
682 let dh = dh.unwrap_or(video_size.height as f64);
683 let sw = sw.unwrap_or(video_size.width as f64);
684 let sh = sh.unwrap_or(video_size.height as f64);
685
686 let (source_rect, dest_rect) =
687 self.adjust_source_dest_rects(video_size, sx, sy, sw, sh, dx, dy, dw, dh);
688
689 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
691 return;
692 }
693
694 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
695
696 self.send_canvas_command(CanvasCommand::DrawImage(
697 snapshot.to_shared(),
698 dest_rect,
699 source_rect,
700 smoothing_enabled,
701 self.state.borrow().shadow_options(),
702 self.state.borrow().composition_options(),
703 self.state.borrow().transform,
704 ));
705
706 self.mark_as_dirty(canvas);
707 }
708
709 #[expect(clippy::too_many_arguments)]
710 fn draw_offscreen_canvas(
711 &self,
712 canvas: &OffscreenCanvas,
713 htmlcanvas: Option<&HTMLCanvasElement>,
714 sx: f64,
715 sy: f64,
716 sw: Option<f64>,
717 sh: Option<f64>,
718 dx: f64,
719 dy: f64,
720 dw: Option<f64>,
721 dh: Option<f64>,
722 ) -> ErrorResult {
723 let canvas_size = canvas
724 .context()
725 .map_or_else(|| canvas.get_size(), |context| context.size());
726
727 let dw = dw.unwrap_or(canvas_size.width as f64);
728 let dh = dh.unwrap_or(canvas_size.height as f64);
729 let sw = sw.unwrap_or(canvas_size.width as f64);
730 let sh = sh.unwrap_or(canvas_size.height as f64);
731
732 let image_size = Size2D::new(canvas_size.width, canvas_size.height);
733 let (source_rect, dest_rect) =
735 self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
736
737 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
738 return Ok(());
739 }
740
741 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
742
743 if let Some(context) = canvas.context() {
744 match *context {
745 OffscreenRenderingContext::Context2d(ref context) => {
746 self.buffered_sender.flush().unwrap();
747 context.send_canvas_command_immediate(CanvasCommand::DrawImageInOther(
748 self.get_canvas_id(),
749 dest_rect,
750 source_rect,
751 smoothing_enabled,
752 self.state.borrow().shadow_options(),
753 self.state.borrow().composition_options(),
754 self.state.borrow().transform,
755 ));
756 },
757 OffscreenRenderingContext::BitmapRenderer(ref context) => {
758 let Some(snapshot) = context.get_image_data() else {
759 return Ok(());
760 };
761
762 self.send_canvas_command(CanvasCommand::DrawImage(
763 snapshot.to_shared(),
764 dest_rect,
765 source_rect,
766 smoothing_enabled,
767 self.state.borrow().shadow_options(),
768 self.state.borrow().composition_options(),
769 self.state.borrow().transform,
770 ));
771 },
772 #[cfg(feature = "webgl")]
773 OffscreenRenderingContext::WebGL(ref context) => {
774 let Some(snapshot) = context.get_image_data() else {
775 return Ok(());
776 };
777
778 self.send_canvas_command(CanvasCommand::DrawImage(
779 snapshot.to_shared(),
780 dest_rect,
781 source_rect,
782 smoothing_enabled,
783 self.state.borrow().shadow_options(),
784 self.state.borrow().composition_options(),
785 self.state.borrow().transform,
786 ));
787 },
788 #[cfg(feature = "webgl")]
789 OffscreenRenderingContext::WebGL2(ref context) => {
790 let Some(snapshot) = context.get_image_data() else {
791 return Ok(());
792 };
793
794 self.send_canvas_command(CanvasCommand::DrawImage(
795 snapshot.to_shared(),
796 dest_rect,
797 source_rect,
798 smoothing_enabled,
799 self.state.borrow().shadow_options(),
800 self.state.borrow().composition_options(),
801 self.state.borrow().transform,
802 ));
803 },
804 OffscreenRenderingContext::Detached => return Err(Error::InvalidState(None)),
805 }
806 } else {
807 self.send_canvas_command(CanvasCommand::DrawEmptyImage(
808 image_size,
809 dest_rect,
810 source_rect,
811 self.state.borrow().shadow_options(),
812 self.state.borrow().composition_options(),
813 self.state.borrow().transform,
814 ));
815 }
816
817 self.mark_as_dirty(htmlcanvas);
818 Ok(())
819 }
820
821 #[expect(clippy::too_many_arguments)]
822 fn draw_html_canvas_element(
823 &self,
824 canvas: &HTMLCanvasElement, htmlcanvas: Option<&HTMLCanvasElement>, sx: f64,
827 sy: f64,
828 sw: Option<f64>,
829 sh: Option<f64>,
830 dx: f64,
831 dy: f64,
832 dw: Option<f64>,
833 dh: Option<f64>,
834 ) -> ErrorResult {
835 let canvas_size = canvas
836 .context()
837 .map_or_else(|| canvas.get_size(), |context| context.size());
838
839 let dw = dw.unwrap_or(canvas_size.width as f64);
840 let dh = dh.unwrap_or(canvas_size.height as f64);
841 let sw = sw.unwrap_or(canvas_size.width as f64);
842 let sh = sh.unwrap_or(canvas_size.height as f64);
843
844 let image_size = Size2D::new(canvas_size.width, canvas_size.height);
845 let (source_rect, dest_rect) =
847 self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
848
849 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
850 return Ok(());
851 }
852
853 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
854
855 if let Some(context) = canvas.context() {
856 match *context {
857 RenderingContext::Context2d(ref context) => {
858 self.buffered_sender.flush().unwrap();
859 context.send_canvas_command_immediate(CanvasCommand::DrawImageInOther(
860 self.get_canvas_id(),
861 dest_rect,
862 source_rect,
863 smoothing_enabled,
864 self.state.borrow().shadow_options(),
865 self.state.borrow().composition_options(),
866 self.state.borrow().transform,
867 ));
868 },
869 RenderingContext::BitmapRenderer(ref context) => {
870 let Some(snapshot) = context.get_image_data() else {
871 return Ok(());
872 };
873
874 self.send_canvas_command(CanvasCommand::DrawImage(
875 snapshot.to_shared(),
876 dest_rect,
877 source_rect,
878 smoothing_enabled,
879 self.state.borrow().shadow_options(),
880 self.state.borrow().composition_options(),
881 self.state.borrow().transform,
882 ));
883 },
884 RenderingContext::Placeholder(ref context) => {
885 let Some(context) = context.context() else {
886 return Err(Error::InvalidState(None));
887 };
888 match *context {
889 OffscreenRenderingContext::Context2d(ref context) => {
890 self.buffered_sender.flush().unwrap();
891 context.send_canvas_command_immediate(CanvasCommand::DrawImageInOther(
892 self.get_canvas_id(),
893 dest_rect,
894 source_rect,
895 smoothing_enabled,
896 self.state.borrow().shadow_options(),
897 self.state.borrow().composition_options(),
898 self.state.borrow().transform,
899 ));
900 },
901 OffscreenRenderingContext::BitmapRenderer(ref context) => {
902 let Some(snapshot) = context.get_image_data() else {
903 return Ok(());
904 };
905
906 self.send_canvas_command(CanvasCommand::DrawImage(
907 snapshot.to_shared(),
908 dest_rect,
909 source_rect,
910 smoothing_enabled,
911 self.state.borrow().shadow_options(),
912 self.state.borrow().composition_options(),
913 self.state.borrow().transform,
914 ));
915 },
916 #[cfg(feature = "webgl")]
917 OffscreenRenderingContext::WebGL(ref context) => {
918 let Some(snapshot) = context.get_image_data() else {
919 return Ok(());
920 };
921
922 self.send_canvas_command(CanvasCommand::DrawImage(
923 snapshot.to_shared(),
924 dest_rect,
925 source_rect,
926 smoothing_enabled,
927 self.state.borrow().shadow_options(),
928 self.state.borrow().composition_options(),
929 self.state.borrow().transform,
930 ));
931 },
932
933 #[cfg(feature = "webgl")]
934 OffscreenRenderingContext::WebGL2(ref context) => {
935 let Some(snapshot) = context.get_image_data() else {
936 return Ok(());
937 };
938
939 self.send_canvas_command(CanvasCommand::DrawImage(
940 snapshot.to_shared(),
941 dest_rect,
942 source_rect,
943 smoothing_enabled,
944 self.state.borrow().shadow_options(),
945 self.state.borrow().composition_options(),
946 self.state.borrow().transform,
947 ));
948 },
949 OffscreenRenderingContext::Detached => {
950 return Err(Error::InvalidState(None));
951 },
952 }
953 },
954 _ => return Err(Error::InvalidState(None)),
955 }
956 } else {
957 self.send_canvas_command(CanvasCommand::DrawEmptyImage(
958 image_size,
959 dest_rect,
960 source_rect,
961 self.state.borrow().shadow_options(),
962 self.state.borrow().composition_options(),
963 self.state.borrow().transform,
964 ));
965 }
966
967 self.mark_as_dirty(htmlcanvas);
968 Ok(())
969 }
970
971 #[expect(clippy::too_many_arguments)]
972 fn fetch_and_draw_image_data(
973 &self,
974 canvas: Option<&HTMLCanvasElement>,
975 url: ServoUrl,
976 cors_setting: Option<CorsSettings>,
977 sx: f64,
978 sy: f64,
979 sw: Option<f64>,
980 sh: Option<f64>,
981 dx: f64,
982 dy: f64,
983 dw: Option<f64>,
984 dh: Option<f64>,
985 ) -> ErrorResult {
986 debug!("Fetching image {}.", url);
987 let snapshot = self
988 .fetch_image_data(url, cors_setting)
989 .ok_or(Error::InvalidState(None))?;
990 let image_size = snapshot.size();
991
992 let dw = dw.unwrap_or(image_size.width as f64);
993 let dh = dh.unwrap_or(image_size.height as f64);
994 let sw = sw.unwrap_or(image_size.width as f64);
995 let sh = sh.unwrap_or(image_size.height as f64);
996
997 let (source_rect, dest_rect) =
999 self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
1000
1001 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
1002 return Ok(());
1003 }
1004
1005 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
1006 self.send_canvas_command(CanvasCommand::DrawImage(
1007 snapshot.to_shared(),
1008 dest_rect,
1009 source_rect,
1010 smoothing_enabled,
1011 self.state.borrow().shadow_options(),
1012 self.state.borrow().composition_options(),
1013 self.state.borrow().transform,
1014 ));
1015 self.mark_as_dirty(canvas);
1016 Ok(())
1017 }
1018
1019 #[expect(clippy::too_many_arguments)]
1021 fn draw_image_bitmap(
1022 &self,
1023 bitmap: &ImageBitmap,
1024 canvas: Option<&HTMLCanvasElement>,
1025 sx: f64,
1026 sy: f64,
1027 sw: Option<f64>,
1028 sh: Option<f64>,
1029 dx: f64,
1030 dy: f64,
1031 dw: Option<f64>,
1032 dh: Option<f64>,
1033 ) {
1034 let Some(snapshot) = bitmap.bitmap_data().clone() else {
1035 return;
1036 };
1037
1038 let bitmap_size = snapshot.size();
1040 let dw = dw.unwrap_or(bitmap_size.width as f64);
1041 let dh = dh.unwrap_or(bitmap_size.height as f64);
1042 let sw = sw.unwrap_or(bitmap_size.width as f64);
1043 let sh = sh.unwrap_or(bitmap_size.height as f64);
1044
1045 let (source_rect, dest_rect) =
1046 self.adjust_source_dest_rects(bitmap_size, sx, sy, sw, sh, dx, dy, dw, dh);
1047
1048 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
1050 return;
1051 }
1052
1053 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
1054
1055 self.send_canvas_command(CanvasCommand::DrawImage(
1056 snapshot.to_shared(),
1057 dest_rect,
1058 source_rect,
1059 smoothing_enabled,
1060 self.state.borrow().shadow_options(),
1061 self.state.borrow().composition_options(),
1062 self.state.borrow().transform,
1063 ));
1064
1065 self.mark_as_dirty(canvas);
1066 }
1067
1068 pub(super) fn mark_as_dirty(&self, canvas: Option<&HTMLCanvasElement>) {
1069 if let Some(canvas) = canvas {
1070 canvas.mark_as_dirty();
1071 }
1072 }
1073
1074 #[expect(clippy::too_many_arguments)]
1079 fn adjust_source_dest_rects(
1080 &self,
1081 image_size: Size2D<u32>,
1082 sx: f64,
1083 sy: f64,
1084 sw: f64,
1085 sh: f64,
1086 dx: f64,
1087 dy: f64,
1088 dw: f64,
1089 dh: f64,
1090 ) -> (Rect<f64>, Rect<f64>) {
1091 let image_rect = Rect::new(
1092 Point2D::zero(),
1093 Size2D::new(image_size.width, image_size.height),
1094 );
1095
1096 let source_rect = Rect::new(
1099 Point2D::new(sx.min(sx + sw), sy.min(sy + sh)),
1100 Size2D::new(sw.abs(), sh.abs()),
1101 );
1102
1103 let source_rect_clipped = source_rect
1106 .intersection(&image_rect.to_f64())
1107 .unwrap_or(Rect::zero());
1108
1109 let width_ratio: f64 = source_rect_clipped.size.width / source_rect.size.width;
1111 let height_ratio: f64 = source_rect_clipped.size.height / source_rect.size.height;
1112
1113 let dest_rect_width_scaled: f64 = dw * width_ratio;
1116 let dest_rect_height_scaled: f64 = dh * height_ratio;
1117
1118 let dest_rect = Rect::new(
1121 Point2D::new(
1122 dx.min(dx + dest_rect_width_scaled),
1123 dy.min(dy + dest_rect_height_scaled),
1124 ),
1125 Size2D::new(dest_rect_width_scaled.abs(), dest_rect_height_scaled.abs()),
1126 );
1127
1128 let source_rect = Rect::new(
1129 Point2D::new(source_rect_clipped.origin.x, source_rect_clipped.origin.y),
1130 Size2D::new(
1131 source_rect_clipped.size.width,
1132 source_rect_clipped.size.height,
1133 ),
1134 );
1135
1136 (source_rect, dest_rect)
1137 }
1138
1139 fn update_transform(&self, transform: Transform2D<f64>) {
1140 let mut state = self.state.borrow_mut();
1141 self.current_default_path
1142 .borrow_mut()
1143 .transform(state.transform.cast());
1144 state.transform = transform;
1145 if let Some(inverse) = transform.inverse() {
1146 self.current_default_path
1147 .borrow_mut()
1148 .transform(inverse.cast());
1149 }
1150 }
1151
1152 pub(super) fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1154 if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1155 let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
1156 self.send_canvas_command(CanvasCommand::FillRect(
1157 rect,
1158 style,
1159 self.state.borrow().shadow_options(),
1160 self.state.borrow().composition_options(),
1161 self.state.borrow().transform,
1162 ));
1163 }
1164 }
1165
1166 pub(super) fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1168 if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1169 self.send_canvas_command(CanvasCommand::ClearRect(
1170 rect,
1171 self.state.borrow().transform,
1172 ));
1173 }
1174 }
1175
1176 pub(super) fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1178 if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1179 let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
1180 self.send_canvas_command(CanvasCommand::StrokeRect(
1181 rect,
1182 style,
1183 self.state.borrow().line_options(),
1184 self.state.borrow().shadow_options(),
1185 self.state.borrow().composition_options(),
1186 self.state.borrow().transform,
1187 ));
1188 }
1189 }
1190
1191 pub(super) fn shadow_offset_x(&self) -> f64 {
1193 self.state.borrow().shadow_offset_x
1194 }
1195
1196 pub(super) fn set_shadow_offset_x(&self, value: f64) {
1198 if !value.is_finite() || value == self.state.borrow().shadow_offset_x {
1199 return;
1200 }
1201 self.state.borrow_mut().shadow_offset_x = value;
1202 }
1203
1204 pub(super) fn shadow_offset_y(&self) -> f64 {
1206 self.state.borrow().shadow_offset_y
1207 }
1208
1209 pub(super) fn set_shadow_offset_y(&self, value: f64) {
1211 if !value.is_finite() || value == self.state.borrow().shadow_offset_y {
1212 return;
1213 }
1214 self.state.borrow_mut().shadow_offset_y = value;
1215 }
1216
1217 pub(super) fn shadow_blur(&self) -> f64 {
1219 self.state.borrow().shadow_blur
1220 }
1221
1222 pub(super) fn set_shadow_blur(&self, value: f64) {
1224 if !value.is_finite() || value < 0f64 || value == self.state.borrow().shadow_blur {
1225 return;
1226 }
1227 self.state.borrow_mut().shadow_blur = value;
1228 }
1229
1230 pub(super) fn shadow_color(&self) -> DOMString {
1232 let mut result = String::new();
1233 serialize(&self.state.borrow().shadow_color, &mut result).unwrap();
1234 DOMString::from(result)
1235 }
1236
1237 pub(super) fn set_shadow_color(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) {
1239 if let Ok(rgba) = parse_color(canvas, &value) {
1240 self.state.borrow_mut().shadow_color = rgba;
1241 }
1242 }
1243
1244 pub(super) fn stroke_style(&self) -> StringOrCanvasGradientOrCanvasPattern {
1246 match self.state.borrow().stroke_style {
1247 CanvasFillOrStrokeStyle::Color(ref rgba) => {
1248 let mut result = String::new();
1249 serialize(rgba, &mut result).unwrap();
1250 StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result))
1251 },
1252 CanvasFillOrStrokeStyle::Gradient(ref gradient) => {
1253 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(gradient))
1254 },
1255 CanvasFillOrStrokeStyle::Pattern(ref pattern) => {
1256 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(pattern))
1257 },
1258 }
1259 }
1260
1261 pub(super) fn set_stroke_style(
1263 &self,
1264 canvas: Option<&HTMLCanvasElement>,
1265 value: StringOrCanvasGradientOrCanvasPattern,
1266 ) {
1267 match value {
1268 StringOrCanvasGradientOrCanvasPattern::String(string) => {
1269 if let Ok(rgba) = parse_color(canvas, &string) {
1270 self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba);
1271 }
1272 },
1273 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
1274 self.state.borrow_mut().stroke_style =
1275 CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
1276 },
1277 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
1278 self.state.borrow_mut().stroke_style =
1279 CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
1280 if !pattern.origin_is_clean() {
1281 self.set_origin_clean(false);
1282 }
1283 },
1284 }
1285 }
1286
1287 pub(super) fn fill_style(&self) -> StringOrCanvasGradientOrCanvasPattern {
1289 match self.state.borrow().fill_style {
1290 CanvasFillOrStrokeStyle::Color(ref rgba) => {
1291 let mut result = String::new();
1292 serialize(rgba, &mut result).unwrap();
1293 StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result))
1294 },
1295 CanvasFillOrStrokeStyle::Gradient(ref gradient) => {
1296 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(gradient))
1297 },
1298 CanvasFillOrStrokeStyle::Pattern(ref pattern) => {
1299 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(pattern))
1300 },
1301 }
1302 }
1303
1304 pub(super) fn set_fill_style(
1306 &self,
1307 canvas: Option<&HTMLCanvasElement>,
1308 value: StringOrCanvasGradientOrCanvasPattern,
1309 ) {
1310 match value {
1311 StringOrCanvasGradientOrCanvasPattern::String(string) => {
1312 if let Ok(rgba) = parse_color(canvas, &string) {
1313 self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba);
1314 }
1315 },
1316 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
1317 self.state.borrow_mut().fill_style =
1318 CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
1319 },
1320 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
1321 self.state.borrow_mut().fill_style =
1322 CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
1323 if !pattern.origin_is_clean() {
1324 self.set_origin_clean(false);
1325 }
1326 },
1327 }
1328 }
1329
1330 pub(super) fn create_linear_gradient(
1332 &self,
1333 global: &GlobalScope,
1334 cx: &mut JSContext,
1335 x0: Finite<f64>,
1336 y0: Finite<f64>,
1337 x1: Finite<f64>,
1338 y1: Finite<f64>,
1339 ) -> DomRoot<CanvasGradient> {
1340 CanvasGradient::new(
1341 global,
1342 cx,
1343 CanvasGradientStyle::Linear(LinearGradientStyle::new(*x0, *y0, *x1, *y1, Vec::new())),
1344 )
1345 }
1346
1347 #[expect(clippy::too_many_arguments)]
1349 pub(super) fn create_radial_gradient(
1350 &self,
1351 global: &GlobalScope,
1352 cx: &mut JSContext,
1353 x0: Finite<f64>,
1354 y0: Finite<f64>,
1355 r0: Finite<f64>,
1356 x1: Finite<f64>,
1357 y1: Finite<f64>,
1358 r1: Finite<f64>,
1359 ) -> Fallible<DomRoot<CanvasGradient>> {
1360 if *r0 < 0. || *r1 < 0. {
1361 return Err(Error::IndexSize(None));
1362 }
1363
1364 Ok(CanvasGradient::new(
1365 global,
1366 cx,
1367 CanvasGradientStyle::Radial(RadialGradientStyle::new(
1368 *x0,
1369 *y0,
1370 *r0,
1371 *x1,
1372 *y1,
1373 *r1,
1374 Vec::new(),
1375 )),
1376 ))
1377 }
1378
1379 pub(super) fn create_pattern(
1381 &self,
1382 global: &GlobalScope,
1383 cx: &mut JSContext,
1384 image: CanvasImageSource,
1385 mut repetition: DOMString,
1386 ) -> Fallible<Option<DomRoot<CanvasPattern>>> {
1387 let snapshot = match image {
1388 CanvasImageSource::HTMLImageElement(ref image) => {
1389 if !image.is_usable()? {
1391 return Ok(None);
1392 }
1393
1394 image
1395 .get_raster_image_data()
1396 .ok_or(Error::InvalidState(None))?
1397 },
1398 CanvasImageSource::HTMLVideoElement(ref video) => {
1399 if !video.is_usable() {
1401 return Ok(None);
1402 }
1403
1404 video
1405 .get_current_frame_data()
1406 .ok_or(Error::InvalidState(None))?
1407 },
1408 CanvasImageSource::HTMLCanvasElement(ref canvas) => {
1409 if canvas.get_size().is_empty() {
1411 return Err(Error::InvalidState(None));
1412 }
1413
1414 canvas.get_image_data().ok_or(Error::InvalidState(None))?
1415 },
1416 CanvasImageSource::ImageBitmap(ref bitmap) => {
1417 if bitmap.is_detached() {
1419 return Err(Error::InvalidState(None));
1420 }
1421
1422 bitmap
1423 .bitmap_data()
1424 .clone()
1425 .ok_or(Error::InvalidState(None))?
1426 },
1427 CanvasImageSource::OffscreenCanvas(ref canvas) => {
1428 if canvas.get_size().is_empty() {
1430 return Err(Error::InvalidState(None));
1431 }
1432
1433 canvas.get_image_data().ok_or(Error::InvalidState(None))?
1434 },
1435 CanvasImageSource::CSSStyleValue(ref value) => value
1436 .get_url(self.base_url.clone())
1437 .and_then(|url| self.fetch_image_data(url, None))
1438 .ok_or(Error::InvalidState(None))?,
1439 };
1440
1441 if repetition.is_empty() {
1442 repetition.push_str("repeat");
1443 }
1444
1445 if let Ok(rep) = RepetitionStyle::from_str(&repetition.str()) {
1446 let size = snapshot.size();
1447 Ok(Some(CanvasPattern::new(
1448 global,
1449 cx,
1450 snapshot,
1451 size.cast(),
1452 rep,
1453 self.is_origin_clean(image),
1454 )))
1455 } else {
1456 Err(Error::Syntax(None))
1457 }
1458 }
1459
1460 pub(super) fn save(&self) {
1462 self.saved_states
1463 .borrow_mut()
1464 .push(self.state.borrow().clone());
1465 }
1466
1467 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
1468 pub(super) fn restore(&self) {
1470 let mut saved_states = self.saved_states.borrow_mut();
1471 if let Some(state) = saved_states.pop() {
1472 let clips_to_pop = self.state.borrow().clips_pushed;
1473 if clips_to_pop != 0 {
1474 self.send_canvas_command(CanvasCommand::PopClips(clips_to_pop));
1475 }
1476 self.state.borrow_mut().clone_from(&state);
1477 }
1478 }
1479
1480 pub(super) fn global_alpha(&self) -> f64 {
1482 self.state.borrow().global_alpha
1483 }
1484
1485 pub(super) fn set_global_alpha(&self, alpha: f64) {
1487 if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
1488 return;
1489 }
1490
1491 self.state.borrow_mut().global_alpha = alpha;
1492 }
1493
1494 pub(super) fn global_composite_operation(&self) -> DOMString {
1496 match self.state.borrow().global_composition {
1497 CompositionOrBlending::Composition(op) => DOMString::from(op.to_string()),
1498 CompositionOrBlending::Blending(op) => DOMString::from(op.to_string()),
1499 }
1500 }
1501
1502 pub(super) fn set_global_composite_operation(&self, op_str: DOMString) {
1504 if let Ok(op) = CompositionOrBlending::from_str(&op_str.str()) {
1505 self.state.borrow_mut().global_composition = op;
1506 }
1507 }
1508
1509 pub(super) fn image_smoothing_enabled(&self) -> bool {
1511 self.state.borrow().image_smoothing_enabled
1512 }
1513
1514 pub(super) fn set_image_smoothing_enabled(&self, value: bool) {
1516 self.state.borrow_mut().image_smoothing_enabled = value;
1517 }
1518
1519 pub(super) fn fill_text(
1521 &self,
1522 global_scope: &GlobalScope,
1523 canvas: Option<&HTMLCanvasElement>,
1524 text: DOMString,
1525 x: f64,
1526 y: f64,
1527 max_width: Option<f64>,
1528 ) {
1529 if !x.is_finite() ||
1531 !y.is_finite() ||
1532 max_width.is_some_and(|max_width| !max_width.is_finite())
1533 {
1534 return;
1535 }
1536
1537 if self.state.borrow().font_style.is_none() {
1538 self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into())
1539 }
1540 let size = self.font_style().font_size.computed_size().px() as f64;
1543
1544 let Some((bounds, text_run)) = self.text_with_size(
1545 global_scope,
1546 &text.str(),
1547 Point2D::new(x, y),
1548 size,
1549 max_width,
1550 ) else {
1551 return;
1552 };
1553 self.send_canvas_command(CanvasCommand::FillText(
1554 bounds,
1555 text_run,
1556 self.state.borrow().fill_style.to_fill_or_stroke_style(),
1557 self.state.borrow().shadow_options(),
1558 self.state.borrow().composition_options(),
1559 self.state.borrow().transform,
1560 ));
1561 }
1562
1563 pub(super) fn stroke_text(
1565 &self,
1566 global_scope: &GlobalScope,
1567 canvas: Option<&HTMLCanvasElement>,
1568 text: DOMString,
1569 x: f64,
1570 y: f64,
1571 max_width: Option<f64>,
1572 ) {
1573 if !x.is_finite() ||
1575 !y.is_finite() ||
1576 max_width.is_some_and(|max_width| !max_width.is_finite())
1577 {
1578 return;
1579 }
1580
1581 if self.state.borrow().font_style.is_none() {
1582 self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into())
1583 }
1584 let size = self.font_style().font_size.computed_size().px() as f64;
1587
1588 let Some((bounds, text_run)) = self.text_with_size(
1589 global_scope,
1590 &text.str(),
1591 Point2D::new(x, y),
1592 size,
1593 max_width,
1594 ) else {
1595 return;
1596 };
1597 self.send_canvas_command(CanvasCommand::StrokeText(
1598 bounds,
1599 text_run,
1600 self.state.borrow().stroke_style.to_fill_or_stroke_style(),
1601 self.state.borrow().line_options(),
1602 self.state.borrow().shadow_options(),
1603 self.state.borrow().composition_options(),
1604 self.state.borrow().transform,
1605 ));
1606 }
1607
1608 pub(super) fn measure_text(
1612 &self,
1613 global: &GlobalScope,
1614 canvas: Option<&HTMLCanvasElement>,
1615 text: DOMString,
1616 cx: &mut JSContext,
1617 ) -> DomRoot<TextMetrics> {
1618 let text = replace_ascii_whitespace(&text.str());
1623
1624 if self.state.borrow().font_style.is_none() {
1627 self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into());
1628 }
1629
1630 let font_context = &global.font_context();
1631 let font_style = self.font_style();
1632 let font_group = font_context.font_group(font_style);
1633 let Some(font) = font_group.first(font_context) else {
1634 warn!("Could not measure canvas text, because there was no first font.");
1635 return TextMetrics::new(global, cx, 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.);
1636 };
1637 let ascent = font.metrics().ascent.to_f64_px();
1638 let descent = font.metrics().descent.to_f64_px();
1639 let runs = self.build_unshaped_text_runs(font_context, &text, &font_group);
1640
1641 let mut total_advance = 0.0;
1642 let shaped_runs: Vec<_> = runs
1643 .into_iter()
1644 .filter_map(|unshaped_text_run| {
1645 let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?;
1646 total_advance += text_run.advance;
1647 Some(text_run)
1648 })
1649 .collect();
1650
1651 let bounding_box = shaped_runs
1652 .iter()
1653 .map(|text_run| text_run.bounds)
1654 .reduce(|a, b| a.union(&b))
1655 .unwrap_or_default();
1656
1657 let baseline = font.baseline().unwrap_or_else(|| FontBaseline {
1658 hanging_baseline: (ascent * HANGING_BASELINE_DEFAULT) as f32,
1659 ideographic_baseline: (-descent * IDEOGRAPHIC_BASELINE_DEFAULT) as f32,
1660 alphabetic_baseline: 0.,
1661 });
1662 let ideographic_baseline = baseline.ideographic_baseline as f64;
1663 let alphabetic_baseline = baseline.alphabetic_baseline as f64;
1664 let hanging_baseline = baseline.hanging_baseline as f64;
1665
1666 let state = self.state.borrow();
1667 let anchor_x = match state.text_align {
1668 CanvasTextAlign::End => total_advance,
1669 CanvasTextAlign::Center => total_advance / 2.,
1670 CanvasTextAlign::Right => total_advance,
1671 _ => 0.,
1672 } as f64;
1673 let anchor_y = match state.text_baseline {
1674 CanvasTextBaseline::Top => ascent,
1675 CanvasTextBaseline::Hanging => hanging_baseline,
1676 CanvasTextBaseline::Ideographic => ideographic_baseline,
1677 CanvasTextBaseline::Middle => (ascent - descent) / 2.,
1678 CanvasTextBaseline::Alphabetic => alphabetic_baseline,
1679 CanvasTextBaseline::Bottom => -descent,
1680 };
1681
1682 TextMetrics::new(
1683 global,
1684 cx,
1685 total_advance as f64,
1686 anchor_x - bounding_box.min_x(),
1687 bounding_box.max_x() - anchor_x,
1688 bounding_box.max_y() - anchor_y,
1689 anchor_y - bounding_box.min_y(),
1690 ascent - anchor_y,
1691 descent + anchor_y,
1692 ascent - anchor_y,
1693 descent + anchor_y,
1694 hanging_baseline - anchor_y,
1695 alphabetic_baseline - anchor_y,
1696 ideographic_baseline - anchor_y,
1697 )
1698 }
1699
1700 pub(super) fn set_font(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) {
1702 let canvas = match canvas {
1703 Some(element) => element,
1704 None => return, };
1706 let node = canvas.upcast::<Node>();
1707 let window = canvas.owner_window();
1708
1709 let Some(resolved_font_style) = window.resolved_font_style_query(node, String::from(value))
1710 else {
1711 return;
1713 };
1714 self.state.borrow_mut().font_style = Some(resolved_font_style);
1715 }
1716
1717 fn font_style(&self) -> ServoArc<Font> {
1718 self.state
1719 .borrow()
1720 .font_style
1721 .clone()
1722 .unwrap_or_else(|| ServoArc::new(Font::initial_values()))
1723 }
1724
1725 pub(super) fn font(&self) -> DOMString {
1727 self.state.borrow().font_style.as_ref().map_or_else(
1728 || CanvasContextState::DEFAULT_FONT_STYLE.into(),
1729 |style| {
1730 let mut result = String::new();
1731 serialize_font(style, &mut result).unwrap();
1732 DOMString::from(result)
1733 },
1734 )
1735 }
1736
1737 pub(super) fn text_align(&self) -> CanvasTextAlign {
1739 self.state.borrow().text_align
1740 }
1741
1742 pub(super) fn set_text_align(&self, value: CanvasTextAlign) {
1744 self.state.borrow_mut().text_align = value;
1745 }
1746
1747 pub(super) fn text_baseline(&self) -> CanvasTextBaseline {
1748 self.state.borrow().text_baseline
1749 }
1750
1751 pub(super) fn set_text_baseline(&self, value: CanvasTextBaseline) {
1752 self.state.borrow_mut().text_baseline = value;
1753 }
1754
1755 pub(super) fn direction(&self) -> CanvasDirection {
1757 self.state.borrow().direction
1758 }
1759
1760 pub(super) fn set_direction(&self, value: CanvasDirection) {
1762 self.state.borrow_mut().direction = value;
1763 }
1764
1765 pub(super) fn line_width(&self) -> f64 {
1767 self.state.borrow().line_width
1768 }
1769
1770 pub(super) fn set_line_width(&self, width: f64) {
1772 if !width.is_finite() || width <= 0.0 {
1773 return;
1774 }
1775
1776 self.state.borrow_mut().line_width = width;
1777 }
1778
1779 pub(super) fn line_cap(&self) -> CanvasLineCap {
1781 match self.state.borrow().line_cap {
1782 LineCapStyle::Butt => CanvasLineCap::Butt,
1783 LineCapStyle::Round => CanvasLineCap::Round,
1784 LineCapStyle::Square => CanvasLineCap::Square,
1785 }
1786 }
1787
1788 pub(super) fn set_line_cap(&self, cap: CanvasLineCap) {
1790 let line_cap = match cap {
1791 CanvasLineCap::Butt => LineCapStyle::Butt,
1792 CanvasLineCap::Round => LineCapStyle::Round,
1793 CanvasLineCap::Square => LineCapStyle::Square,
1794 };
1795 self.state.borrow_mut().line_cap = line_cap;
1796 }
1797
1798 pub(super) fn line_join(&self) -> CanvasLineJoin {
1800 match self.state.borrow().line_join {
1801 LineJoinStyle::Round => CanvasLineJoin::Round,
1802 LineJoinStyle::Bevel => CanvasLineJoin::Bevel,
1803 LineJoinStyle::Miter => CanvasLineJoin::Miter,
1804 }
1805 }
1806
1807 pub(super) fn set_line_join(&self, join: CanvasLineJoin) {
1809 let line_join = match join {
1810 CanvasLineJoin::Round => LineJoinStyle::Round,
1811 CanvasLineJoin::Bevel => LineJoinStyle::Bevel,
1812 CanvasLineJoin::Miter => LineJoinStyle::Miter,
1813 };
1814 self.state.borrow_mut().line_join = line_join;
1815 }
1816
1817 pub(super) fn miter_limit(&self) -> f64 {
1819 self.state.borrow().miter_limit
1820 }
1821
1822 pub(super) fn set_miter_limit(&self, limit: f64) {
1824 if !limit.is_finite() || limit <= 0.0 {
1825 return;
1826 }
1827
1828 self.state.borrow_mut().miter_limit = limit;
1829 }
1830
1831 pub(super) fn line_dash(&self) -> Vec<f64> {
1833 self.state.borrow().line_dash.clone()
1836 }
1837
1838 pub(super) fn set_line_dash(&self, segments: Vec<f64>) {
1840 if segments
1845 .iter()
1846 .any(|segment| !segment.is_finite() || *segment < 0.0)
1847 {
1848 return;
1849 }
1850
1851 let mut line_dash: Vec<_> = segments.clone();
1854 if segments.len() & 1 == 1 {
1855 line_dash.extend(line_dash.clone());
1856 }
1857
1858 self.state.borrow_mut().line_dash = line_dash.clone();
1860 }
1861
1862 pub(super) fn line_dash_offset(&self) -> f64 {
1864 self.state.borrow().line_dash_offset
1866 }
1867
1868 pub(super) fn set_line_dash_offset(&self, offset: f64) {
1870 if !offset.is_finite() {
1873 return;
1874 }
1875
1876 self.state.borrow_mut().line_dash_offset = offset;
1878 }
1879
1880 pub(super) fn create_image_data(
1882 &self,
1883 cx: &mut JSContext,
1884 global: &GlobalScope,
1885 sw: i32,
1886 sh: i32,
1887 ) -> Fallible<DomRoot<ImageData>> {
1888 if sw == 0 || sh == 0 {
1889 return Err(Error::IndexSize(None));
1890 }
1891 ImageData::new(cx, global, sw.unsigned_abs(), sh.unsigned_abs(), None)
1892 }
1893
1894 pub(super) fn create_image_data_(
1896 &self,
1897 cx: &mut JSContext,
1898 global: &GlobalScope,
1899 imagedata: &ImageData,
1900 ) -> Fallible<DomRoot<ImageData>> {
1901 ImageData::new(cx, global, imagedata.Width(), imagedata.Height(), None)
1902 }
1903
1904 #[expect(clippy::too_many_arguments)]
1906 pub(super) fn get_image_data(
1907 &self,
1908 cx: &mut JSContext,
1909 canvas_size: Size2D<u32>,
1910 global: &GlobalScope,
1911 sx: i32,
1912 sy: i32,
1913 sw: i32,
1914 sh: i32,
1915 ) -> Fallible<DomRoot<ImageData>> {
1916 if sw == 0 || sh == 0 {
1920 return Err(Error::IndexSize(None));
1921 }
1922
1923 if !self.origin_is_clean() {
1924 return Err(Error::Security(None));
1925 }
1926
1927 let (origin, size) = adjust_size_sign(Point2D::new(sx, sy), Size2D::new(sw, sh));
1928 let read_rect = match pixels::clip(origin, size.to_u32(), canvas_size) {
1929 Some(rect) => rect,
1930 None => {
1931 return ImageData::new(cx, global, size.width, size.height, None);
1933 },
1934 };
1935
1936 let data = if self.is_paintable() {
1937 let (sender, receiver) = generic_channel::channel().unwrap();
1938 self.send_canvas_command_immediate(CanvasCommand::GetImageData(
1939 Some(read_rect),
1940 sender,
1941 ));
1942
1943 let mut snapshot = receiver.recv().unwrap().to_owned();
1944 snapshot.transform(
1945 SnapshotAlphaMode::Transparent {
1946 premultiplied: false,
1947 },
1948 SnapshotPixelFormat::RGBA,
1949 );
1950 Some(snapshot.into())
1951 } else {
1952 None
1953 };
1954
1955 ImageData::new(cx, global, size.width, size.height, data)
1956 }
1957
1958 pub(super) fn put_image_data(
1960 &self,
1961 no_gc: &NoGC,
1962 canvas_size: Size2D<u32>,
1963 imagedata: &ImageData,
1964 dx: i32,
1965 dy: i32,
1966 ) {
1967 self.put_image_data_(
1968 no_gc,
1969 canvas_size,
1970 imagedata,
1971 dx,
1972 dy,
1973 0,
1974 0,
1975 imagedata.Width() as i32,
1976 imagedata.Height() as i32,
1977 )
1978 }
1979
1980 #[expect(clippy::too_many_arguments)]
1982 pub(super) fn put_image_data_(
1983 &self,
1984 no_gc: &NoGC,
1985 canvas_size: Size2D<u32>,
1986 imagedata: &ImageData,
1987 dx: i32,
1988 dy: i32,
1989 dirty_x: i32,
1990 dirty_y: i32,
1991 dirty_width: i32,
1992 dirty_height: i32,
1993 ) {
1994 if !self.is_paintable() {
1995 return;
1996 }
1997
1998 let imagedata_size = Size2D::new(imagedata.Width(), imagedata.Height());
2002 if imagedata_size.area() == 0 {
2003 return;
2004 }
2005
2006 let (src_origin, src_size) = adjust_size_sign(
2014 Point2D::new(dirty_x, dirty_y),
2015 Size2D::new(dirty_width, dirty_height),
2016 );
2017 let src_rect = match pixels::clip(src_origin, src_size.to_u32(), imagedata_size.to_u32()) {
2018 Some(rect) => rect,
2019 None => return,
2020 };
2021 let (dst_origin, _) = adjust_size_sign(
2022 Point2D::new(dirty_x.saturating_add(dx), dirty_y.saturating_add(dy)),
2023 Size2D::new(dirty_width, dirty_height),
2024 );
2025 let dst_rect = match pixels::clip(dst_origin, src_rect.size, canvas_size) {
2028 Some(rect) => rect,
2029 None => return,
2030 };
2031
2032 let snapshot =
2034 imagedata.get_snapshot_rect(no_gc, Rect::new(src_rect.origin, dst_rect.size));
2035 self.send_canvas_command_immediate(CanvasCommand::PutImageData(
2037 dst_rect,
2038 snapshot.to_shared(),
2039 ));
2040 }
2041
2042 pub(super) fn draw_image(
2044 &self,
2045 canvas: Option<&HTMLCanvasElement>,
2046 image: CanvasImageSource,
2047 dx: f64,
2048 dy: f64,
2049 ) -> ErrorResult {
2050 if !(dx.is_finite() && dy.is_finite()) {
2051 return Ok(());
2052 }
2053
2054 self.draw_image_internal(canvas, image, 0f64, 0f64, None, None, dx, dy, None, None)
2055 }
2056
2057 pub(super) fn draw_image_(
2059 &self,
2060 canvas: Option<&HTMLCanvasElement>,
2061 image: CanvasImageSource,
2062 dx: f64,
2063 dy: f64,
2064 dw: f64,
2065 dh: f64,
2066 ) -> ErrorResult {
2067 if !(dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) {
2068 return Ok(());
2069 }
2070
2071 self.draw_image_internal(
2072 canvas,
2073 image,
2074 0f64,
2075 0f64,
2076 None,
2077 None,
2078 dx,
2079 dy,
2080 Some(dw),
2081 Some(dh),
2082 )
2083 }
2084
2085 #[expect(clippy::too_many_arguments)]
2087 pub(super) fn draw_image__(
2088 &self,
2089 canvas: Option<&HTMLCanvasElement>,
2090 image: CanvasImageSource,
2091 sx: f64,
2092 sy: f64,
2093 sw: f64,
2094 sh: f64,
2095 dx: f64,
2096 dy: f64,
2097 dw: f64,
2098 dh: f64,
2099 ) -> ErrorResult {
2100 if !(sx.is_finite() &&
2101 sy.is_finite() &&
2102 sw.is_finite() &&
2103 sh.is_finite() &&
2104 dx.is_finite() &&
2105 dy.is_finite() &&
2106 dw.is_finite() &&
2107 dh.is_finite())
2108 {
2109 return Ok(());
2110 }
2111
2112 self.draw_image_internal(
2113 canvas,
2114 image,
2115 sx,
2116 sy,
2117 Some(sw),
2118 Some(sh),
2119 dx,
2120 dy,
2121 Some(dw),
2122 Some(dh),
2123 )
2124 }
2125
2126 pub(super) fn begin_path(&self) {
2128 *self.current_default_path.borrow_mut() = Path::new();
2129 }
2130
2131 pub(super) fn fill(&self, fill_rule: CanvasFillRule) {
2133 let path = self.current_default_path.borrow().clone();
2134 self.fill_(path, fill_rule);
2135 }
2136
2137 pub(super) fn fill_(&self, path: Path, fill_rule: CanvasFillRule) {
2139 let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
2140 self.send_canvas_command(CanvasCommand::FillPath(
2141 style,
2142 path,
2143 fill_rule.convert(),
2144 self.state.borrow().shadow_options(),
2145 self.state.borrow().composition_options(),
2146 self.state.borrow().transform,
2147 ));
2148 }
2149
2150 pub(super) fn stroke(&self) {
2152 let path = self.current_default_path.borrow().clone();
2153 self.stroke_(path);
2154 }
2155
2156 pub(super) fn stroke_(&self, path: Path) {
2157 let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
2158 self.send_canvas_command(CanvasCommand::StrokePath(
2159 path,
2160 style,
2161 self.state.borrow().line_options(),
2162 self.state.borrow().shadow_options(),
2163 self.state.borrow().composition_options(),
2164 self.state.borrow().transform,
2165 ));
2166 }
2167
2168 pub(super) fn clip(&self, fill_rule: CanvasFillRule) {
2170 let path = self.current_default_path.borrow().clone();
2171 self.clip_(path, fill_rule);
2172 }
2173
2174 pub(super) fn clip_(&self, path: Path, fill_rule: CanvasFillRule) {
2176 self.state.borrow_mut().clips_pushed += 1;
2177 self.send_canvas_command(CanvasCommand::ClipPath(
2178 path,
2179 fill_rule.convert(),
2180 self.state.borrow().transform,
2181 ));
2182 }
2183
2184 pub(super) fn is_point_in_path(
2186 &self,
2187 global: &GlobalScope,
2188 x: f64,
2189 y: f64,
2190 fill_rule: CanvasFillRule,
2191 ) -> bool {
2192 let mut path = self.current_default_path.borrow().clone();
2193 path.transform(self.state.borrow().transform.cast());
2194 self.is_point_in_path_(global, path, x, y, fill_rule)
2195 }
2196
2197 pub(super) fn is_point_in_path_(
2199 &self,
2200 _global: &GlobalScope,
2201 path: Path,
2202 x: f64,
2203 y: f64,
2204 fill_rule: CanvasFillRule,
2205 ) -> bool {
2206 let fill_rule = match fill_rule {
2207 CanvasFillRule::Nonzero => FillRule::Nonzero,
2208 CanvasFillRule::Evenodd => FillRule::Evenodd,
2209 };
2210 path.is_point_in_path(x, y, fill_rule)
2211 }
2212
2213 pub(super) fn scale(&self, x: f64, y: f64) {
2215 if !(x.is_finite() && y.is_finite()) {
2216 return;
2217 }
2218
2219 let transform = self.state.borrow().transform;
2220 self.update_transform(transform.pre_scale(x, y))
2221 }
2222
2223 pub(super) fn rotate(&self, angle: f64) {
2225 if angle == 0.0 || !angle.is_finite() {
2226 return;
2227 }
2228
2229 let (sin, cos) = (angle.sin(), angle.cos());
2230 let transform = self.state.borrow().transform;
2231 self.update_transform(Transform2D::new(cos, sin, -sin, cos, 0.0, 0.0).then(&transform))
2232 }
2233
2234 pub(super) fn translate(&self, x: f64, y: f64) {
2236 if !(x.is_finite() && y.is_finite()) {
2237 return;
2238 }
2239
2240 let transform = self.state.borrow().transform;
2241 self.update_transform(transform.pre_translate(vec2(x, y)))
2242 }
2243
2244 pub(super) fn transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
2246 if !(a.is_finite() &&
2247 b.is_finite() &&
2248 c.is_finite() &&
2249 d.is_finite() &&
2250 e.is_finite() &&
2251 f.is_finite())
2252 {
2253 return;
2254 }
2255
2256 let transform = self.state.borrow().transform;
2257 self.update_transform(Transform2D::new(a, b, c, d, e, f).then(&transform))
2258 }
2259
2260 pub(super) fn get_transform(
2262 &self,
2263 global: &GlobalScope,
2264 cx: &mut JSContext,
2265 ) -> DomRoot<DOMMatrix> {
2266 let transform = self.state.safe_borrow_mut(cx.no_gc()).transform;
2267 DOMMatrix::new(cx, global, true, transform.to_3d())
2268 }
2269
2270 pub(super) fn set_transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
2272 if !a.is_finite() ||
2274 !b.is_finite() ||
2275 !c.is_finite() ||
2276 !d.is_finite() ||
2277 !e.is_finite() ||
2278 !f.is_finite()
2279 {
2280 return;
2281 }
2282
2283 self.update_transform(Transform2D::new(a, b, c, d, e, f))
2285 }
2286
2287 pub(super) fn set_transform_(&self, transform: &DOMMatrix2DInit) -> ErrorResult {
2289 let matrix = dommatrix2dinit_to_matrix(transform)?;
2292
2293 if !matrix.m11.is_finite() ||
2297 !matrix.m12.is_finite() ||
2298 !matrix.m21.is_finite() ||
2299 !matrix.m22.is_finite() ||
2300 !matrix.m31.is_finite() ||
2301 !matrix.m32.is_finite()
2302 {
2303 return Ok(());
2304 }
2305
2306 self.update_transform(matrix.cast());
2308 Ok(())
2309 }
2310
2311 pub(super) fn reset_transform(&self) {
2313 self.update_transform(Transform2D::identity())
2314 }
2315
2316 pub(super) fn close_path(&self) {
2318 self.current_default_path.borrow_mut().close_path();
2319 }
2320
2321 pub(super) fn move_to(&self, x: f64, y: f64) {
2323 self.current_default_path.borrow_mut().move_to(x, y);
2324 }
2325
2326 pub(super) fn line_to(&self, x: f64, y: f64) {
2328 self.current_default_path.borrow_mut().line_to(x, y);
2329 }
2330
2331 pub(super) fn rect(&self, x: f64, y: f64, width: f64, height: f64) {
2333 self.current_default_path
2334 .borrow_mut()
2335 .rect(x, y, width, height);
2336 }
2337
2338 pub(super) fn round_rect(
2340 &self,
2341 x: f64,
2342 y: f64,
2343 width: f64,
2344 height: f64,
2345 radii: UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence,
2346 ) -> ErrorResult {
2347 let radii = round_rect_radii(&radii);
2348 self.current_default_path
2349 .borrow_mut()
2350 .round_rect(x, y, width, height, &radii)
2351 .map_err(round_rect_error)
2352 }
2353
2354 pub(super) fn quadratic_curve_to(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
2356 self.current_default_path
2357 .borrow_mut()
2358 .quadratic_curve_to(cpx, cpy, x, y);
2359 }
2360
2361 pub(super) fn bezier_curve_to(
2363 &self,
2364 cp1x: f64,
2365 cp1y: f64,
2366 cp2x: f64,
2367 cp2y: f64,
2368 x: f64,
2369 y: f64,
2370 ) {
2371 self.current_default_path
2372 .borrow_mut()
2373 .bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
2374 }
2375
2376 pub(super) fn arc(
2378 &self,
2379 x: f64,
2380 y: f64,
2381 r: f64,
2382 start: f64,
2383 end: f64,
2384 ccw: bool,
2385 ) -> ErrorResult {
2386 self.current_default_path
2387 .borrow_mut()
2388 .arc(x, y, r, start, end, ccw)
2389 .map_err(|_| Error::IndexSize(None))
2390 }
2391
2392 pub(super) fn arc_to(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult {
2394 self.current_default_path
2395 .borrow_mut()
2396 .arc_to(cp1x, cp1y, cp2x, cp2y, r)
2397 .map_err(|_| Error::IndexSize(None))
2398 }
2399
2400 #[expect(clippy::too_many_arguments)]
2402 pub(super) fn ellipse(
2403 &self,
2404 x: f64,
2405 y: f64,
2406 rx: f64,
2407 ry: f64,
2408 rotation: f64,
2409 start: f64,
2410 end: f64,
2411 ccw: bool,
2412 ) -> ErrorResult {
2413 self.current_default_path
2414 .borrow_mut()
2415 .ellipse(x, y, rx, ry, rotation, start, end, ccw)
2416 .map_err(|_| Error::IndexSize(None))
2417 }
2418
2419 fn text_with_size(
2420 &self,
2421 global_scope: &GlobalScope,
2422 text: &str,
2423 origin: Point2D<f64>,
2424 size: f64,
2425 max_width: Option<f64>,
2426 ) -> Option<(Rect<f64>, Vec<TextRun>)> {
2427 let font_context = &global_scope.font_context();
2428
2429 if max_width.is_some_and(|max_width| max_width.is_nan() || max_width <= 0.) {
2431 return None;
2432 }
2433
2434 let text = replace_ascii_whitespace(text);
2436
2437 let font_style = self.font_style();
2440 let font_group = font_context.font_group_with_size(font_style, Au::from_f64_px(size));
2441 let Some(first_font) = font_group.first(font_context) else {
2442 warn!("Could not render canvas text, because there was no first font.");
2443 return None;
2444 };
2445
2446 let runs = self.build_unshaped_text_runs(font_context, &text, &font_group);
2447
2448 let mut total_advance = 0.0;
2451 let mut shaped_runs: Vec<_> = runs
2452 .into_iter()
2453 .filter_map(|unshaped_text_run| {
2454 let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?;
2455 total_advance += text_run.advance;
2456 Some(text_run)
2457 })
2458 .collect();
2459
2460 let total_advance = total_advance as f64;
2470 if let Some(max_width) = max_width {
2471 let new_size = (max_width / total_advance * size).floor().max(5.);
2472 if total_advance > max_width && new_size != size {
2473 return self.text_with_size(global_scope, &text, origin, new_size, Some(max_width));
2474 }
2475 }
2476
2477 let start =
2479 self.find_anchor_point_for_line_of_text(origin, first_font.metrics(), total_advance);
2480
2481 let mut bounds = None;
2486 for text_run in shaped_runs.iter_mut() {
2487 for glyph_and_position in text_run.glyphs_and_positions.iter_mut() {
2488 glyph_and_position.point += Vector2D::new(start.x as f32, start.y as f32);
2489 }
2490 bounds
2491 .get_or_insert(text_run.bounds)
2492 .union(&text_run.bounds);
2493 }
2494
2495 Some((
2496 bounds
2497 .unwrap_or_default()
2498 .translate(start.to_vector().cast_unit()),
2499 shaped_runs,
2500 ))
2501 }
2502
2503 fn build_unshaped_text_runs<'text>(
2504 &self,
2505 font_context: &FontContext,
2506 text: &'text str,
2507 font_group: &FontGroup,
2508 ) -> Vec<UnshapedTextRun<'text>> {
2509 let mut runs = Vec::new();
2510 let x_language = self.font_style()._x_lang.clone();
2513 let language = x_language.0.parse().unwrap_or(Language::UNKNOWN);
2514 let mut current_text_run = UnshapedTextRun::new(language);
2515 let mut current_text_run_start_index = 0;
2516
2517 fn is_variation_selector(c: char) -> bool {
2523 matches!(c as u32, 0xFE00..=0xFE0F | 0xE0100..=0xE01EF)
2524 }
2525
2526 for (index, character) in text.char_indices() {
2527 let next_char = text[index + character.len_utf8()..].chars().next();
2528
2529 let script = Script::from(character);
2530
2531 let font = font_group.find_by_codepoint(font_context, character, next_char, language);
2532
2533 if !is_variation_selector(character) &&
2534 !current_text_run.script_and_font_compatible(script, &font)
2535 {
2536 let previous_text_run = std::mem::replace(
2537 &mut current_text_run,
2538 UnshapedTextRun {
2539 font: font.clone(),
2540 script,
2541 string: Default::default(),
2542 language,
2543 },
2544 );
2545 current_text_run_start_index = index;
2546 if !previous_text_run.string.is_empty() && previous_text_run.font.is_some() {
2547 runs.push(previous_text_run);
2548 }
2549 }
2550
2551 current_text_run.string =
2552 &text[current_text_run_start_index..index + character.len_utf8()];
2553 }
2554
2555 if !current_text_run.string.is_empty() && current_text_run.font.is_some() {
2556 runs.push(current_text_run);
2557 }
2558 runs
2559 }
2560
2561 fn find_anchor_point_for_line_of_text(
2564 &self,
2565 origin: Point2D<f64>,
2566 metrics: &FontMetrics,
2567 width: f64,
2568 ) -> Point2D<f64> {
2569 let state = self.state.borrow();
2570 let is_rtl = match state.direction {
2571 CanvasDirection::Ltr => false,
2572 CanvasDirection::Rtl => true,
2573 CanvasDirection::Inherit => false, };
2575
2576 let text_align = match self.text_align() {
2577 CanvasTextAlign::Start if is_rtl => CanvasTextAlign::Right,
2578 CanvasTextAlign::Start => CanvasTextAlign::Left,
2579 CanvasTextAlign::End if is_rtl => CanvasTextAlign::Left,
2580 CanvasTextAlign::End => CanvasTextAlign::Right,
2581 text_align => text_align,
2582 };
2583 let anchor_x = match text_align {
2584 CanvasTextAlign::Center => -width / 2.,
2585 CanvasTextAlign::Right => -width,
2586 _ => 0.,
2587 };
2588
2589 let ascent = metrics.ascent.to_f64_px();
2590 let descent = metrics.descent.to_f64_px();
2591 let anchor_y = match self.text_baseline() {
2592 CanvasTextBaseline::Top => ascent,
2593 CanvasTextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT,
2594 CanvasTextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT,
2595 CanvasTextBaseline::Middle => (ascent - descent) / 2.,
2596 CanvasTextBaseline::Alphabetic => 0.,
2597 CanvasTextBaseline::Bottom => -descent,
2598 };
2599
2600 origin + Vector2D::new(anchor_x, anchor_y)
2601 }
2602}
2603
2604impl Drop for CanvasState {
2605 fn drop(&mut self) {
2606 self.buffered_sender.discard();
2608 if let Err(err) = self.buffered_sender.send_immediate(CanvasCommand::Destroy) {
2609 warn!("Could not close canvas: {}", err)
2610 }
2611 }
2612}
2613
2614struct UnshapedTextRun<'a> {
2615 font: Option<FontRef>,
2616 script: Script,
2617 string: &'a str,
2618 language: Language,
2619}
2620
2621impl UnshapedTextRun<'_> {
2622 fn new(language: Language) -> Self {
2623 Self {
2624 font: Default::default(),
2625 script: Default::default(),
2626 string: Default::default(),
2627 language,
2628 }
2629 }
2630
2631 fn script_and_font_compatible(&self, script: Script, other_font: &Option<FontRef>) -> bool {
2632 if self.script != script {
2633 return false;
2634 }
2635
2636 match (&self.font, other_font) {
2637 (Some(font_a), Some(font_b)) => *font_a.identifier() == *font_b.identifier(),
2638 (None, None) => true,
2639 _ => false,
2640 }
2641 }
2642
2643 fn into_shaped_text_run(self, previous_advance: f32) -> Option<TextRun> {
2644 debug_assert!(!self.string.is_empty() && self.font.is_some());
2645 let font = self.font?;
2646
2647 let options = ShapingOptions {
2648 letter_spacing: Au::zero(),
2649 word_spacing: Au::zero(),
2650 script: self.script,
2651 language: self.language,
2652 flags: ShapingFlags::empty(),
2653 ligatures: FontVariantLigatures::NORMAL,
2654 numeric: FontVariantNumeric::NORMAL,
2655 east_asian: FontVariantEastAsian::NORMAL,
2656 feature_settings: FontFeatureSettings::normal(),
2657 position: FontVariantPosition::Normal,
2658 alternates: Default::default(),
2659 };
2660
2661 let glyphs = font.shape_text(self.string, &options);
2662
2663 let mut advance = 0.0;
2664 let mut bounds = None;
2665 let glyphs_and_positions = glyphs
2666 .glyphs()
2667 .map(|glyph| {
2668 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
2669 let glyph_and_position = GlyphAndPosition {
2670 id: glyph.id(),
2671 point: Point2D::new(previous_advance + advance, glyph_offset.y.to_f32_px()),
2672 };
2673
2674 let glyph_bounds = font
2675 .typographic_bounds(glyph.id())
2676 .translate(Vector2D::new(advance + previous_advance, 0.0));
2677 bounds = Some(bounds.get_or_insert(glyph_bounds).union(&glyph_bounds));
2678
2679 advance += glyph.advance().to_f32_px();
2680
2681 glyph_and_position
2682 })
2683 .collect();
2684
2685 let identifier = font.identifier();
2686 let font_data = match &*identifier {
2687 FontIdentifier::Local(_) => None,
2688 FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => {
2689 Some(font.font_data_and_index().ok()?)
2690 },
2691 }
2692 .cloned();
2693 let canvas_font = CanvasFont {
2694 identifier: identifier.to_owned(),
2695 data: font_data,
2696 };
2697
2698 Some(TextRun {
2699 font: canvas_font,
2700 pt_size: font.descriptor.pt_size.to_f32_px(),
2701 glyphs_and_positions,
2702 advance,
2703 bounds: bounds.unwrap_or_default().cast(),
2704 })
2705 }
2706}
2707
2708pub(super) fn parse_color(
2710 canvas: Option<&HTMLCanvasElement>,
2711 string: &DOMString,
2712) -> Result<AbsoluteColor, ()> {
2713 let string = string.str();
2714 let mut parser = Parser::new(&string);
2715 let context = parser_context_for_anonymous_content(
2716 CssRuleType::Style,
2717 ParsingMode::DEFAULT,
2718 &ANONYMOUS_CONTENT_URL_DATA,
2719 );
2720
2721 Color::parse_and_compute(&context, &mut parser, None).map(|color| {
2724 if let ComputedColor::Absolute(color) = color {
2731 return color;
2732 }
2733
2734 let current_color = match canvas {
2740 None => AbsoluteColor::BLACK,
2744 Some(canvas) => {
2745 let canvas_element = canvas.upcast::<Element>();
2746 match canvas_element.style() {
2747 Some(ref s) if canvas_element.has_css_layout_box() => {
2748 s.get_inherited_text().color
2749 },
2750 _ => AbsoluteColor::BLACK,
2751 }
2752 },
2753 };
2754
2755 color.resolve_to_absolute(¤t_color)
2756 })
2757}
2758
2759pub(super) fn is_rect_valid(rect: Rect<f64>) -> bool {
2762 rect.size.width > 0.0 && rect.size.height > 0.0
2763}
2764
2765pub(super) fn serialize<W>(color: &AbsoluteColor, dest: &mut W) -> fmt::Result
2767where
2768 W: fmt::Write,
2769{
2770 let srgb = match color.color_space {
2771 ColorSpace::Srgb if color.flags.contains(ColorFlags::IS_LEGACY_SRGB) => *color,
2772 ColorSpace::Hsl | ColorSpace::Hwb => color.into_srgb_legacy(),
2773 _ => return color.to_css(&mut CssWriter::new(dest)),
2774 };
2775 debug_assert!(srgb.flags.contains(ColorFlags::IS_LEGACY_SRGB));
2776 let red = clamp_unit_f32(srgb.components.0);
2777 let green = clamp_unit_f32(srgb.components.1);
2778 let blue = clamp_unit_f32(srgb.components.2);
2779 let alpha = srgb.alpha;
2780 if alpha == 1.0 {
2781 write!(
2782 dest,
2783 "#{:x}{:x}{:x}{:x}{:x}{:x}",
2784 red >> 4,
2785 red & 0xF,
2786 green >> 4,
2787 green & 0xF,
2788 blue >> 4,
2789 blue & 0xF
2790 )
2791 } else {
2792 write!(dest, "rgba({}, {}, {}, {})", red, green, blue, alpha)
2793 }
2794}
2795
2796pub(super) fn adjust_size_sign(
2797 mut origin: Point2D<i32>,
2798 mut size: Size2D<i32>,
2799) -> (Point2D<i32>, Size2D<u32>) {
2800 if size.width < 0 {
2801 size.width = -size.width;
2802 origin.x = origin.x.saturating_sub(size.width);
2803 }
2804 if size.height < 0 {
2805 size.height = -size.height;
2806 origin.y = origin.y.saturating_sub(size.height);
2807 }
2808 (origin, size.to_u32())
2809}
2810
2811fn serialize_font<W>(style: &Font, dest: &mut W) -> fmt::Result
2812where
2813 W: fmt::Write,
2814{
2815 if style.font_style == FontStyle::ITALIC {
2816 write!(dest, "{} ", style.font_style.to_css_string())?;
2817 }
2818 if style.font_weight.is_bold() {
2819 write!(dest, "{} ", style.font_weight.to_css_string())?;
2820 }
2821 if style.font_variant_caps == FontVariantCaps::SmallCaps {
2822 write!(dest, "{} ", style.font_variant_caps.to_css_string())?;
2823 }
2824 write!(
2825 dest,
2826 "{} {}",
2827 style.font_size.to_css_string(),
2828 style.font_family.to_css_string()
2829 )
2830}
2831
2832fn adjust_canvas_size(size: Size2D<u64>) -> Size2D<u64> {
2833 const MAX_CANVAS_AREA: u64 = 32768 * 8192;
2838 const MAX_CANVAS_SIZE: u64 = 65535;
2840
2841 if !size.is_empty() &&
2842 size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE))
2843 .none() &&
2844 size.area() < MAX_CANVAS_AREA
2845 {
2846 size
2847 } else {
2848 Size2D::zero()
2849 }
2850}
2851
2852impl Convert<FillRule> for CanvasFillRule {
2853 fn convert(self) -> FillRule {
2854 match self {
2855 CanvasFillRule::Nonzero => FillRule::Nonzero,
2856 CanvasFillRule::Evenodd => FillRule::Evenodd,
2857 }
2858 }
2859}
2860
2861fn replace_ascii_whitespace(text: &str) -> String {
2862 text.chars()
2863 .map(|c| match c {
2864 ' ' | '\t' | '\n' | '\r' | '\x0C' => '\x20',
2865 _ => c,
2866 })
2867 .collect()
2868}