script/dom/canvas/2d/
canvas_state.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::fmt;
7use std::str::FromStr;
8use std::sync::Arc;
9
10use app_units::Au;
11use base::Epoch;
12use base::generic_channel::GenericSender;
13use canvas_traits::canvas::{
14    Canvas2dMsg, CanvasFont, CanvasId, CanvasMsg, CompositionOptions, CompositionOrBlending,
15    FillOrStrokeStyle, FillRule, GlyphAndPosition, LineCapStyle, LineJoinStyle, LineOptions,
16    LinearGradientStyle, Path, RadialGradientStyle, RepetitionStyle, ShadowOptions, TextRun,
17};
18use constellation_traits::ScriptToConstellationMessage;
19use cssparser::color::clamp_unit_f32;
20use cssparser::{Parser, ParserInput};
21use euclid::default::{Point2D, Rect, Size2D, Transform2D};
22use euclid::{Vector2D, vec2};
23use fonts::{
24    ByteIndex, FontBaseline, FontContext, FontGroup, FontIdentifier, FontMetrics, FontRef,
25    LAST_RESORT_GLYPH_ADVANCE, ShapingFlags, ShapingOptions,
26};
27use ipc_channel::ipc;
28use net_traits::image_cache::{ImageCache, ImageResponse};
29use net_traits::request::CorsSettings;
30use pixels::{PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
31use profile_traits::ipc as profiled_ipc;
32use range::Range;
33use servo_arc::Arc as ServoArc;
34use servo_url::{ImmutableOrigin, ServoUrl};
35use style::color::{AbsoluteColor, ColorFlags, ColorSpace};
36use style::context::QuirksMode;
37use style::parser::ParserContext;
38use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
39use style::properties::style_structs::Font;
40use style::stylesheets::{CssRuleType, Origin};
41use style::values::computed::font::FontStyle;
42use style::values::specified::color::Color;
43use style_traits::values::ToCss;
44use style_traits::{CssWriter, ParsingMode};
45use unicode_script::Script;
46use url::Url;
47use webrender_api::ImageKey;
48
49use crate::canvas_context::{CanvasContext, OffscreenRenderingContext, RenderingContext};
50use crate::conversions::Convert;
51use crate::dom::bindings::cell::DomRefCell;
52use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
53    CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin,
54    CanvasTextAlign, CanvasTextBaseline, ImageDataMethods,
55};
56use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::DOMMatrix2DInit;
57use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
58use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
59use crate::dom::bindings::inheritance::Castable;
60use crate::dom::bindings::num::Finite;
61use crate::dom::bindings::root::{Dom, DomRoot};
62use crate::dom::bindings::str::DOMString;
63use crate::dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle};
64use crate::dom::canvaspattern::CanvasPattern;
65use crate::dom::dommatrix::DOMMatrix;
66use crate::dom::dommatrixreadonly::dommatrix2dinit_to_matrix;
67use crate::dom::element::Element;
68use crate::dom::globalscope::GlobalScope;
69use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
70use crate::dom::html::htmlimageelement::HTMLImageElement;
71use crate::dom::html::htmlvideoelement::HTMLVideoElement;
72use crate::dom::imagebitmap::ImageBitmap;
73use crate::dom::imagedata::ImageData;
74use crate::dom::node::{Node, NodeTraits};
75use crate::dom::offscreencanvas::OffscreenCanvas;
76use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
77use crate::dom::textmetrics::TextMetrics;
78use crate::script_runtime::CanGc;
79
80const HANGING_BASELINE_DEFAULT: f64 = 0.8;
81const IDEOGRAPHIC_BASELINE_DEFAULT: f64 = 0.5;
82
83#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
84#[derive(Clone, JSTraceable, MallocSizeOf)]
85#[allow(dead_code)]
86pub(super) enum CanvasFillOrStrokeStyle {
87    Color(#[no_trace] AbsoluteColor),
88    Gradient(Dom<CanvasGradient>),
89    Pattern(Dom<CanvasPattern>),
90}
91
92impl CanvasFillOrStrokeStyle {
93    fn to_fill_or_stroke_style(&self) -> FillOrStrokeStyle {
94        match self {
95            CanvasFillOrStrokeStyle::Color(rgba) => FillOrStrokeStyle::Color(*rgba),
96            CanvasFillOrStrokeStyle::Gradient(gradient) => gradient.to_fill_or_stroke_style(),
97            CanvasFillOrStrokeStyle::Pattern(pattern) => pattern.to_fill_or_stroke_style(),
98        }
99    }
100}
101
102#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
103#[derive(Clone, JSTraceable, MallocSizeOf)]
104pub(super) struct CanvasContextState {
105    global_alpha: f64,
106    #[no_trace]
107    global_composition: CompositionOrBlending,
108    image_smoothing_enabled: bool,
109    fill_style: CanvasFillOrStrokeStyle,
110    stroke_style: CanvasFillOrStrokeStyle,
111    line_width: f64,
112    #[no_trace]
113    line_cap: LineCapStyle,
114    #[no_trace]
115    line_join: LineJoinStyle,
116    miter_limit: f64,
117    line_dash: Vec<f64>,
118    line_dash_offset: f64,
119    #[no_trace]
120    transform: Transform2D<f64>,
121    shadow_offset_x: f64,
122    shadow_offset_y: f64,
123    shadow_blur: f64,
124    #[no_trace]
125    shadow_color: AbsoluteColor,
126    #[no_trace]
127    #[conditional_malloc_size_of]
128    font_style: Option<ServoArc<Font>>,
129    text_align: CanvasTextAlign,
130    text_baseline: CanvasTextBaseline,
131    direction: CanvasDirection,
132    /// The number of clips pushed onto the context while in this state.
133    /// When restoring old state, same number of clips will be popped to restore state.
134    clips_pushed: usize,
135}
136
137impl CanvasContextState {
138    const DEFAULT_FONT_STYLE: &'static str = "10px sans-serif";
139
140    pub(super) fn new() -> CanvasContextState {
141        CanvasContextState {
142            global_alpha: 1.0,
143            global_composition: CompositionOrBlending::default(),
144            image_smoothing_enabled: true,
145            fill_style: CanvasFillOrStrokeStyle::Color(AbsoluteColor::BLACK),
146            stroke_style: CanvasFillOrStrokeStyle::Color(AbsoluteColor::BLACK),
147            line_width: 1.0,
148            line_cap: LineCapStyle::Butt,
149            line_join: LineJoinStyle::Miter,
150            miter_limit: 10.0,
151            transform: Transform2D::identity(),
152            shadow_offset_x: 0.0,
153            shadow_offset_y: 0.0,
154            shadow_blur: 0.0,
155            shadow_color: AbsoluteColor::TRANSPARENT_BLACK,
156            font_style: None,
157            text_align: CanvasTextAlign::Start,
158            text_baseline: CanvasTextBaseline::Alphabetic,
159            direction: CanvasDirection::Inherit,
160            line_dash: Vec::new(),
161            line_dash_offset: 0.0,
162            clips_pushed: 0,
163        }
164    }
165
166    fn composition_options(&self) -> CompositionOptions {
167        CompositionOptions {
168            alpha: self.global_alpha,
169            composition_operation: self.global_composition,
170        }
171    }
172
173    fn shadow_options(&self) -> ShadowOptions {
174        ShadowOptions {
175            offset_x: self.shadow_offset_x,
176            offset_y: self.shadow_offset_y,
177            blur: self.shadow_blur,
178            color: self.shadow_color,
179        }
180    }
181
182    fn line_options(&self) -> LineOptions {
183        LineOptions {
184            width: self.line_width,
185            cap_style: self.line_cap,
186            join_style: self.line_join,
187            miter_limit: self.miter_limit,
188            dash: self.line_dash.iter().map(|x| *x as f32).collect(),
189            dash_offset: self.line_dash_offset,
190        }
191    }
192}
193
194#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
195#[derive(JSTraceable, MallocSizeOf)]
196pub(super) struct CanvasState {
197    #[no_trace]
198    ipc_renderer: GenericSender<CanvasMsg>,
199    #[no_trace]
200    canvas_id: CanvasId,
201    #[no_trace]
202    image_key: ImageKey,
203    #[no_trace]
204    size: Cell<Size2D<u64>>,
205    state: DomRefCell<CanvasContextState>,
206    origin_clean: Cell<bool>,
207    #[ignore_malloc_size_of = "ImageCache"]
208    #[no_trace]
209    image_cache: Arc<dyn ImageCache>,
210    /// The base URL for resolving CSS image URL values.
211    /// Needed because of <https://github.com/servo/servo/issues/17625>
212    #[no_trace]
213    base_url: ServoUrl,
214    #[no_trace]
215    origin: ImmutableOrigin,
216    /// Any missing image URLs.
217    #[no_trace]
218    missing_image_urls: DomRefCell<Vec<ServoUrl>>,
219    saved_states: DomRefCell<Vec<CanvasContextState>>,
220    /// <https://html.spec.whatwg.org/multipage/#current-default-path>
221    #[no_trace]
222    current_default_path: DomRefCell<Path>,
223}
224
225impl CanvasState {
226    pub(super) fn new(global: &GlobalScope, size: Size2D<u64>) -> Option<CanvasState> {
227        debug!("Creating new canvas rendering context.");
228        let (sender, receiver) =
229            profiled_ipc::channel(global.time_profiler_chan().clone()).unwrap();
230        let script_to_constellation_chan = global.script_to_constellation_chan();
231        debug!("Asking constellation to create new canvas thread.");
232        let size = adjust_canvas_size(size);
233        script_to_constellation_chan
234            .send(ScriptToConstellationMessage::CreateCanvasPaintThread(
235                size, sender,
236            ))
237            .unwrap();
238        let (ipc_renderer, canvas_id, image_key) = receiver.recv().ok()??;
239        debug!("Done.");
240        // Worklets always receive a unique origin. This messes with fetching
241        // cached images in the case of paint worklets, since the image cache
242        // is keyed on the origin requesting the image data.
243        let origin = if global.is::<PaintWorkletGlobalScope>() {
244            global.api_base_url().origin()
245        } else {
246            global.origin().immutable().clone()
247        };
248        Some(CanvasState {
249            ipc_renderer,
250            canvas_id,
251            size: Cell::new(size),
252            state: DomRefCell::new(CanvasContextState::new()),
253            origin_clean: Cell::new(true),
254            image_cache: global.image_cache(),
255            base_url: global.api_base_url(),
256            missing_image_urls: DomRefCell::new(Vec::new()),
257            saved_states: DomRefCell::new(Vec::new()),
258            image_key,
259            origin,
260            current_default_path: DomRefCell::new(Path::new()),
261        })
262    }
263
264    pub(super) fn image_key(&self) -> ImageKey {
265        self.image_key
266    }
267
268    pub(super) fn get_missing_image_urls(&self) -> &DomRefCell<Vec<ServoUrl>> {
269        &self.missing_image_urls
270    }
271
272    pub(super) fn get_canvas_id(&self) -> CanvasId {
273        self.canvas_id
274    }
275
276    pub(super) fn is_paintable(&self) -> bool {
277        !self.size.get().is_empty()
278    }
279
280    pub(super) fn send_canvas_2d_msg(&self, msg: Canvas2dMsg) {
281        if !self.is_paintable() {
282            return;
283        }
284
285        self.ipc_renderer
286            .send(CanvasMsg::Canvas2d(msg, self.get_canvas_id()))
287            .unwrap()
288    }
289
290    /// Updates WR image and blocks on completion
291    pub(super) fn update_rendering(&self, canvas_epoch: Option<Epoch>) -> bool {
292        if !self.is_paintable() {
293            return false;
294        }
295
296        self.ipc_renderer
297            .send(CanvasMsg::Canvas2d(
298                Canvas2dMsg::UpdateImage(canvas_epoch),
299                self.canvas_id,
300            ))
301            .unwrap();
302        true
303    }
304
305    /// <https://html.spec.whatwg.org/multipage/#concept-canvas-set-bitmap-dimensions>
306    pub(super) fn set_bitmap_dimensions(&self, size: Size2D<u64>) {
307        self.reset_to_initial_state();
308
309        self.size.replace(adjust_canvas_size(size));
310
311        self.ipc_renderer
312            .send(CanvasMsg::Recreate(
313                Some(self.size.get()),
314                self.get_canvas_id(),
315            ))
316            .unwrap();
317    }
318
319    pub(super) fn reset(&self) {
320        self.reset_to_initial_state();
321
322        if !self.is_paintable() {
323            return;
324        }
325
326        self.ipc_renderer
327            .send(CanvasMsg::Recreate(None, self.get_canvas_id()))
328            .unwrap();
329    }
330
331    pub(super) fn reset_to_initial_state(&self) {
332        *self.current_default_path.borrow_mut() = Path::new();
333        self.saved_states.borrow_mut().clear();
334        *self.state.borrow_mut() = CanvasContextState::new();
335    }
336
337    pub(super) fn reset_bitmap(&self) {
338        if !self.is_paintable() {
339            return;
340        }
341
342        self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(
343            self.size.get().to_f32().into(),
344            self.state.borrow().transform,
345        ));
346    }
347
348    fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> {
349        if !([x, y, w, h].iter().all(|val| val.is_finite())) {
350            return None;
351        }
352
353        if w == 0.0 && h == 0.0 {
354            return None;
355        }
356
357        Some(Rect::new(
358            Point2D::new(x as f32, y as f32),
359            Size2D::new(w as f32, h as f32),
360        ))
361    }
362
363    pub(super) fn origin_is_clean(&self) -> bool {
364        self.origin_clean.get()
365    }
366
367    fn set_origin_unclean(&self) {
368        self.origin_clean.set(false)
369    }
370
371    /// <https://html.spec.whatwg.org/multipage/#the-image-argument-is-not-origin-clean>
372    fn is_origin_clean(&self, source: CanvasImageSource) -> bool {
373        match source {
374            CanvasImageSource::HTMLImageElement(image) => {
375                image.same_origin(GlobalScope::entry().origin())
376            },
377            CanvasImageSource::HTMLVideoElement(video) => video.origin_is_clean(),
378            CanvasImageSource::HTMLCanvasElement(canvas) => canvas.origin_is_clean(),
379            CanvasImageSource::ImageBitmap(bitmap) => bitmap.origin_is_clean(),
380            CanvasImageSource::OffscreenCanvas(canvas) => canvas.origin_is_clean(),
381            CanvasImageSource::CSSStyleValue(_) => true,
382        }
383    }
384
385    fn fetch_image_data(
386        &self,
387        url: ServoUrl,
388        cors_setting: Option<CorsSettings>,
389    ) -> Option<Snapshot> {
390        let img = match self.request_image_from_cache(url, cors_setting) {
391            ImageResponse::Loaded(image, _) => {
392                if let Some(image) = image.as_raster_image() {
393                    image
394                } else {
395                    // TODO: https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
396                    warn!("Vector images are not supported as image source in canvas2d");
397                    return None;
398                }
399            },
400            ImageResponse::PlaceholderLoaded(_, _) |
401            ImageResponse::None |
402            ImageResponse::MetadataLoaded(_) => {
403                return None;
404            },
405        };
406
407        let size = Size2D::new(img.metadata.width, img.metadata.height);
408        let format = match img.format {
409            PixelFormat::BGRA8 => SnapshotPixelFormat::BGRA,
410            PixelFormat::RGBA8 => SnapshotPixelFormat::RGBA,
411            pixel_format => unimplemented!("unsupported pixel format ({:?})", pixel_format),
412        };
413        let alpha_mode = SnapshotAlphaMode::Transparent {
414            premultiplied: false,
415        };
416
417        Some(Snapshot::from_vec(
418            size.cast(),
419            format,
420            alpha_mode,
421            img.first_frame().bytes.to_vec(),
422        ))
423    }
424
425    fn request_image_from_cache(
426        &self,
427        url: ServoUrl,
428        cors_setting: Option<CorsSettings>,
429    ) -> ImageResponse {
430        match self
431            .image_cache
432            .get_image(url.clone(), self.origin.clone(), cors_setting)
433        {
434            Some(image) => ImageResponse::Loaded(image, url),
435            None => {
436                // Rather annoyingly, we get the same response back from
437                // A load which really failed and from a load which hasn't started yet.
438                self.missing_image_urls.borrow_mut().push(url);
439                ImageResponse::None
440            },
441        }
442    }
443
444    ///
445    /// drawImage coordinates explained
446    ///
447    /// ```
448    ///  Source Image      Destination Canvas
449    /// +-------------+     +-------------+
450    /// |             |     |             |
451    /// |(sx,sy)      |     |(dx,dy)      |
452    /// |   +----+    |     |   +----+    |
453    /// |   |    |    |     |   |    |    |
454    /// |   |    |sh  |---->|   |    |dh  |
455    /// |   |    |    |     |   |    |    |
456    /// |   +----+    |     |   +----+    |
457    /// |     sw      |     |     dw      |
458    /// |             |     |             |
459    /// +-------------+     +-------------+
460    /// ```
461    ///
462    /// The rectangle (sx, sy, sw, sh) from the source image
463    /// is copied on the rectangle (dx, dy, dh, dw) of the destination canvas
464    ///
465    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
466    #[allow(clippy::too_many_arguments)]
467    fn draw_image_internal(
468        &self,
469        htmlcanvas: Option<&HTMLCanvasElement>,
470        image: CanvasImageSource,
471        sx: f64,
472        sy: f64,
473        sw: Option<f64>,
474        sh: Option<f64>,
475        dx: f64,
476        dy: f64,
477        dw: Option<f64>,
478        dh: Option<f64>,
479    ) -> ErrorResult {
480        if !self.is_paintable() {
481            return Ok(());
482        }
483
484        let result = match image {
485            CanvasImageSource::HTMLImageElement(ref image) => {
486                // https://html.spec.whatwg.org/multipage/#drawing-images
487                // 2. Let usability be the result of checking the usability of image.
488                // 3. If usability is bad, then return (without drawing anything).
489                if !image.is_usable()? {
490                    return Ok(());
491                }
492
493                self.draw_html_image_element(image, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
494                Ok(())
495            },
496            CanvasImageSource::HTMLVideoElement(ref video) => {
497                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
498                // Step 2. Let usability be the result of checking the usability of image.
499                // Step 3. If usability is bad, then return (without drawing anything).
500                if !video.is_usable() {
501                    return Ok(());
502                }
503
504                self.draw_html_video_element(video, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
505                Ok(())
506            },
507            CanvasImageSource::HTMLCanvasElement(ref canvas) => {
508                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
509                if canvas.get_size().is_empty() {
510                    return Err(Error::InvalidState(None));
511                }
512
513                self.draw_html_canvas_element(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
514            },
515            CanvasImageSource::ImageBitmap(ref bitmap) => {
516                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
517                if bitmap.is_detached() {
518                    return Err(Error::InvalidState(None));
519                }
520
521                self.draw_image_bitmap(bitmap, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh);
522                Ok(())
523            },
524            CanvasImageSource::OffscreenCanvas(ref canvas) => {
525                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
526                if canvas.get_size().is_empty() {
527                    return Err(Error::InvalidState(None));
528                }
529
530                self.draw_offscreen_canvas(canvas, htmlcanvas, sx, sy, sw, sh, dx, dy, dw, dh)
531            },
532            CanvasImageSource::CSSStyleValue(ref value) => {
533                let url = value
534                    .get_url(self.base_url.clone())
535                    .ok_or(Error::InvalidState(None))?;
536                self.fetch_and_draw_image_data(
537                    htmlcanvas, url, None, sx, sy, sw, sh, dx, dy, dw, dh,
538                )
539            },
540        };
541
542        if result.is_ok() && !self.is_origin_clean(image) {
543            self.set_origin_unclean()
544        }
545        result
546    }
547
548    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
549    #[allow(clippy::too_many_arguments)]
550    fn draw_html_image_element(
551        &self,
552        image: &HTMLImageElement,
553        canvas: Option<&HTMLCanvasElement>,
554        sx: f64,
555        sy: f64,
556        sw: Option<f64>,
557        sh: Option<f64>,
558        dx: f64,
559        dy: f64,
560        dw: Option<f64>,
561        dh: Option<f64>,
562    ) {
563        let Some(snapshot) = image.get_raster_image_data() else {
564            return;
565        };
566
567        // Step 4. Establish the source and destination rectangles.
568        let image_size = snapshot.size();
569        let dw = dw.unwrap_or(image_size.width as f64);
570        let dh = dh.unwrap_or(image_size.height as f64);
571        let sw = sw.unwrap_or(image_size.width as f64);
572        let sh = sh.unwrap_or(image_size.height as f64);
573
574        let (source_rect, dest_rect) =
575            self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
576
577        // Step 5. If one of the sw or sh arguments is zero, then return. Nothing is painted.
578        if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
579            return;
580        }
581
582        let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
583
584        self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
585            snapshot.as_ipc(),
586            dest_rect,
587            source_rect,
588            smoothing_enabled,
589            self.state.borrow().shadow_options(),
590            self.state.borrow().composition_options(),
591            self.state.borrow().transform,
592        ));
593
594        self.mark_as_dirty(canvas);
595    }
596
597    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
598    #[allow(clippy::too_many_arguments)]
599    fn draw_html_video_element(
600        &self,
601        video: &HTMLVideoElement,
602        canvas: Option<&HTMLCanvasElement>,
603        sx: f64,
604        sy: f64,
605        sw: Option<f64>,
606        sh: Option<f64>,
607        dx: f64,
608        dy: f64,
609        dw: Option<f64>,
610        dh: Option<f64>,
611    ) {
612        let Some(snapshot) = video.get_current_frame_data() else {
613            return;
614        };
615
616        // Step 4. Establish the source and destination rectangles.
617        let video_size = snapshot.size();
618        let dw = dw.unwrap_or(video_size.width as f64);
619        let dh = dh.unwrap_or(video_size.height as f64);
620        let sw = sw.unwrap_or(video_size.width as f64);
621        let sh = sh.unwrap_or(video_size.height as f64);
622
623        let (source_rect, dest_rect) =
624            self.adjust_source_dest_rects(video_size, sx, sy, sw, sh, dx, dy, dw, dh);
625
626        // Step 5. If one of the sw or sh arguments is zero, then return. Nothing is painted.
627        if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
628            return;
629        }
630
631        let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
632
633        self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
634            snapshot.as_ipc(),
635            dest_rect,
636            source_rect,
637            smoothing_enabled,
638            self.state.borrow().shadow_options(),
639            self.state.borrow().composition_options(),
640            self.state.borrow().transform,
641        ));
642
643        self.mark_as_dirty(canvas);
644    }
645
646    #[allow(clippy::too_many_arguments)]
647    fn draw_offscreen_canvas(
648        &self,
649        canvas: &OffscreenCanvas,
650        htmlcanvas: Option<&HTMLCanvasElement>,
651        sx: f64,
652        sy: f64,
653        sw: Option<f64>,
654        sh: Option<f64>,
655        dx: f64,
656        dy: f64,
657        dw: Option<f64>,
658        dh: Option<f64>,
659    ) -> ErrorResult {
660        let canvas_size = canvas
661            .context()
662            .map_or_else(|| canvas.get_size(), |context| context.size());
663
664        let dw = dw.unwrap_or(canvas_size.width as f64);
665        let dh = dh.unwrap_or(canvas_size.height as f64);
666        let sw = sw.unwrap_or(canvas_size.width as f64);
667        let sh = sh.unwrap_or(canvas_size.height as f64);
668
669        let image_size = Size2D::new(canvas_size.width, canvas_size.height);
670        // 2. Establish the source and destination rectangles
671        let (source_rect, dest_rect) =
672            self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
673
674        if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
675            return Ok(());
676        }
677
678        let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
679
680        if let Some(context) = canvas.context() {
681            match *context {
682                OffscreenRenderingContext::Context2d(ref context) => {
683                    context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther(
684                        self.get_canvas_id(),
685                        dest_rect,
686                        source_rect,
687                        smoothing_enabled,
688                        self.state.borrow().shadow_options(),
689                        self.state.borrow().composition_options(),
690                        self.state.borrow().transform,
691                    ));
692                },
693                OffscreenRenderingContext::BitmapRenderer(ref context) => {
694                    let Some(snapshot) = context.get_image_data() else {
695                        return Ok(());
696                    };
697
698                    self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
699                        snapshot.as_ipc(),
700                        dest_rect,
701                        source_rect,
702                        smoothing_enabled,
703                        self.state.borrow().shadow_options(),
704                        self.state.borrow().composition_options(),
705                        self.state.borrow().transform,
706                    ));
707                },
708                OffscreenRenderingContext::Detached => return Err(Error::InvalidState(None)),
709            }
710        } else {
711            self.send_canvas_2d_msg(Canvas2dMsg::DrawEmptyImage(
712                image_size,
713                dest_rect,
714                source_rect,
715                self.state.borrow().shadow_options(),
716                self.state.borrow().composition_options(),
717                self.state.borrow().transform,
718            ));
719        }
720
721        self.mark_as_dirty(htmlcanvas);
722        Ok(())
723    }
724
725    #[allow(clippy::too_many_arguments)]
726    fn draw_html_canvas_element(
727        &self,
728        canvas: &HTMLCanvasElement,             // source canvas
729        htmlcanvas: Option<&HTMLCanvasElement>, // destination canvas
730        sx: f64,
731        sy: f64,
732        sw: Option<f64>,
733        sh: Option<f64>,
734        dx: f64,
735        dy: f64,
736        dw: Option<f64>,
737        dh: Option<f64>,
738    ) -> ErrorResult {
739        let canvas_size = canvas
740            .context()
741            .map_or_else(|| canvas.get_size(), |context| context.size());
742
743        let dw = dw.unwrap_or(canvas_size.width as f64);
744        let dh = dh.unwrap_or(canvas_size.height as f64);
745        let sw = sw.unwrap_or(canvas_size.width as f64);
746        let sh = sh.unwrap_or(canvas_size.height as f64);
747
748        let image_size = Size2D::new(canvas_size.width, canvas_size.height);
749        // 2. Establish the source and destination rectangles
750        let (source_rect, dest_rect) =
751            self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
752
753        if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
754            return Ok(());
755        }
756
757        let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
758
759        if let Some(context) = canvas.context() {
760            match *context {
761                RenderingContext::Context2d(ref context) => {
762                    context.send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther(
763                        self.get_canvas_id(),
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                RenderingContext::BitmapRenderer(ref context) => {
773                    let Some(snapshot) = context.get_image_data() else {
774                        return Ok(());
775                    };
776
777                    self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
778                        snapshot.as_ipc(),
779                        dest_rect,
780                        source_rect,
781                        smoothing_enabled,
782                        self.state.borrow().shadow_options(),
783                        self.state.borrow().composition_options(),
784                        self.state.borrow().transform,
785                    ));
786                },
787                RenderingContext::Placeholder(ref context) => {
788                    let Some(context) = context.context() else {
789                        return Err(Error::InvalidState(None));
790                    };
791                    match *context {
792                        OffscreenRenderingContext::Context2d(ref context) => context
793                            .send_canvas_2d_msg(Canvas2dMsg::DrawImageInOther(
794                                self.get_canvas_id(),
795                                dest_rect,
796                                source_rect,
797                                smoothing_enabled,
798                                self.state.borrow().shadow_options(),
799                                self.state.borrow().composition_options(),
800                                self.state.borrow().transform,
801                            )),
802                        OffscreenRenderingContext::BitmapRenderer(ref context) => {
803                            let Some(snapshot) = context.get_image_data() else {
804                                return Ok(());
805                            };
806
807                            self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
808                                snapshot.as_ipc(),
809                                dest_rect,
810                                source_rect,
811                                smoothing_enabled,
812                                self.state.borrow().shadow_options(),
813                                self.state.borrow().composition_options(),
814                                self.state.borrow().transform,
815                            ));
816                        },
817                        OffscreenRenderingContext::Detached => {
818                            return Err(Error::InvalidState(None));
819                        },
820                    }
821                },
822                _ => return Err(Error::InvalidState(None)),
823            }
824        } else {
825            self.send_canvas_2d_msg(Canvas2dMsg::DrawEmptyImage(
826                image_size,
827                dest_rect,
828                source_rect,
829                self.state.borrow().shadow_options(),
830                self.state.borrow().composition_options(),
831                self.state.borrow().transform,
832            ));
833        }
834
835        self.mark_as_dirty(htmlcanvas);
836        Ok(())
837    }
838
839    #[allow(clippy::too_many_arguments)]
840    fn fetch_and_draw_image_data(
841        &self,
842        canvas: Option<&HTMLCanvasElement>,
843        url: ServoUrl,
844        cors_setting: Option<CorsSettings>,
845        sx: f64,
846        sy: f64,
847        sw: Option<f64>,
848        sh: Option<f64>,
849        dx: f64,
850        dy: f64,
851        dw: Option<f64>,
852        dh: Option<f64>,
853    ) -> ErrorResult {
854        debug!("Fetching image {}.", url);
855        let snapshot = self
856            .fetch_image_data(url, cors_setting)
857            .ok_or(Error::InvalidState(None))?;
858        let image_size = snapshot.size();
859
860        let dw = dw.unwrap_or(image_size.width as f64);
861        let dh = dh.unwrap_or(image_size.height as f64);
862        let sw = sw.unwrap_or(image_size.width as f64);
863        let sh = sh.unwrap_or(image_size.height as f64);
864
865        // Establish the source and destination rectangles
866        let (source_rect, dest_rect) =
867            self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
868
869        if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
870            return Ok(());
871        }
872
873        let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
874        self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
875            snapshot.as_ipc(),
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        self.mark_as_dirty(canvas);
884        Ok(())
885    }
886
887    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
888    #[allow(clippy::too_many_arguments)]
889    fn draw_image_bitmap(
890        &self,
891        bitmap: &ImageBitmap,
892        canvas: Option<&HTMLCanvasElement>,
893        sx: f64,
894        sy: f64,
895        sw: Option<f64>,
896        sh: Option<f64>,
897        dx: f64,
898        dy: f64,
899        dw: Option<f64>,
900        dh: Option<f64>,
901    ) {
902        let Some(snapshot) = bitmap.bitmap_data().clone() else {
903            return;
904        };
905
906        // Step 4. Establish the source and destination rectangles.
907        let bitmap_size = snapshot.size();
908        let dw = dw.unwrap_or(bitmap_size.width as f64);
909        let dh = dh.unwrap_or(bitmap_size.height as f64);
910        let sw = sw.unwrap_or(bitmap_size.width as f64);
911        let sh = sh.unwrap_or(bitmap_size.height as f64);
912
913        let (source_rect, dest_rect) =
914            self.adjust_source_dest_rects(bitmap_size, sx, sy, sw, sh, dx, dy, dw, dh);
915
916        // Step 5. If one of the sw or sh arguments is zero, then return. Nothing is painted.
917        if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
918            return;
919        }
920
921        let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
922
923        self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
924            snapshot.as_ipc(),
925            dest_rect,
926            source_rect,
927            smoothing_enabled,
928            self.state.borrow().shadow_options(),
929            self.state.borrow().composition_options(),
930            self.state.borrow().transform,
931        ));
932
933        self.mark_as_dirty(canvas);
934    }
935
936    pub(super) fn mark_as_dirty(&self, canvas: Option<&HTMLCanvasElement>) {
937        if let Some(canvas) = canvas {
938            canvas.mark_as_dirty();
939        }
940    }
941
942    /// It is used by DrawImage to calculate the size of the source and destination rectangles based
943    /// on the drawImage call arguments
944    /// source rectangle = area of the original image to be copied
945    /// destination rectangle = area of the destination canvas where the source image is going to be drawn
946    #[allow(clippy::too_many_arguments)]
947    fn adjust_source_dest_rects(
948        &self,
949        image_size: Size2D<u32>,
950        sx: f64,
951        sy: f64,
952        sw: f64,
953        sh: f64,
954        dx: f64,
955        dy: f64,
956        dw: f64,
957        dh: f64,
958    ) -> (Rect<f64>, Rect<f64>) {
959        let image_rect = Rect::new(
960            Point2D::zero(),
961            Size2D::new(image_size.width, image_size.height),
962        );
963
964        // The source rectangle is the rectangle whose corners are the four points (sx, sy),
965        // (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh).
966        let source_rect = Rect::new(
967            Point2D::new(sx.min(sx + sw), sy.min(sy + sh)),
968            Size2D::new(sw.abs(), sh.abs()),
969        );
970
971        // When the source rectangle is outside the source image,
972        // the source rectangle must be clipped to the source image
973        let source_rect_clipped = source_rect
974            .intersection(&image_rect.to_f64())
975            .unwrap_or(Rect::zero());
976
977        // Width and height ratios between the non clipped and clipped source rectangles
978        let width_ratio: f64 = source_rect_clipped.size.width / source_rect.size.width;
979        let height_ratio: f64 = source_rect_clipped.size.height / source_rect.size.height;
980
981        // When the source rectangle is outside the source image,
982        // the destination rectangle must be clipped in the same proportion.
983        let dest_rect_width_scaled: f64 = dw * width_ratio;
984        let dest_rect_height_scaled: f64 = dh * height_ratio;
985
986        // The destination rectangle is the rectangle whose corners are the four points (dx, dy),
987        // (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh).
988        let dest_rect = Rect::new(
989            Point2D::new(
990                dx.min(dx + dest_rect_width_scaled),
991                dy.min(dy + dest_rect_height_scaled),
992            ),
993            Size2D::new(dest_rect_width_scaled.abs(), dest_rect_height_scaled.abs()),
994        );
995
996        let source_rect = Rect::new(
997            Point2D::new(source_rect_clipped.origin.x, source_rect_clipped.origin.y),
998            Size2D::new(
999                source_rect_clipped.size.width,
1000                source_rect_clipped.size.height,
1001            ),
1002        );
1003
1004        (source_rect, dest_rect)
1005    }
1006
1007    fn update_transform(&self, transform: Transform2D<f64>) {
1008        let mut state = self.state.borrow_mut();
1009        self.current_default_path
1010            .borrow_mut()
1011            .transform(state.transform.cast());
1012        state.transform = transform;
1013        if let Some(inverse) = transform.inverse() {
1014            self.current_default_path
1015                .borrow_mut()
1016                .transform(inverse.cast());
1017        }
1018    }
1019
1020    // https://html.spec.whatwg.org/multipage/#dom-context-2d-fillrect
1021    pub(super) fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1022        if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1023            let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
1024            self.send_canvas_2d_msg(Canvas2dMsg::FillRect(
1025                rect,
1026                style,
1027                self.state.borrow().shadow_options(),
1028                self.state.borrow().composition_options(),
1029                self.state.borrow().transform,
1030            ));
1031        }
1032    }
1033
1034    // https://html.spec.whatwg.org/multipage/#dom-context-2d-clearrect
1035    pub(super) fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1036        if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1037            self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(rect, self.state.borrow().transform));
1038        }
1039    }
1040
1041    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokerect
1042    pub(super) fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1043        if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1044            let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
1045            self.send_canvas_2d_msg(Canvas2dMsg::StrokeRect(
1046                rect,
1047                style,
1048                self.state.borrow().line_options(),
1049                self.state.borrow().shadow_options(),
1050                self.state.borrow().composition_options(),
1051                self.state.borrow().transform,
1052            ));
1053        }
1054    }
1055
1056    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx
1057    pub(super) fn shadow_offset_x(&self) -> f64 {
1058        self.state.borrow().shadow_offset_x
1059    }
1060
1061    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsetx
1062    pub(super) fn set_shadow_offset_x(&self, value: f64) {
1063        if !value.is_finite() || value == self.state.borrow().shadow_offset_x {
1064            return;
1065        }
1066        self.state.borrow_mut().shadow_offset_x = value;
1067    }
1068
1069    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
1070    pub(super) fn shadow_offset_y(&self) -> f64 {
1071        self.state.borrow().shadow_offset_y
1072    }
1073
1074    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowoffsety
1075    pub(super) fn set_shadow_offset_y(&self, value: f64) {
1076        if !value.is_finite() || value == self.state.borrow().shadow_offset_y {
1077            return;
1078        }
1079        self.state.borrow_mut().shadow_offset_y = value;
1080    }
1081
1082    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
1083    pub(super) fn shadow_blur(&self) -> f64 {
1084        self.state.borrow().shadow_blur
1085    }
1086
1087    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowblur
1088    pub(super) fn set_shadow_blur(&self, value: f64) {
1089        if !value.is_finite() || value < 0f64 || value == self.state.borrow().shadow_blur {
1090            return;
1091        }
1092        self.state.borrow_mut().shadow_blur = value;
1093    }
1094
1095    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
1096    pub(super) fn shadow_color(&self) -> DOMString {
1097        let mut result = String::new();
1098        serialize(&self.state.borrow().shadow_color, &mut result).unwrap();
1099        DOMString::from(result)
1100    }
1101
1102    // https://html.spec.whatwg.org/multipage/#dom-context-2d-shadowcolor
1103    pub(super) fn set_shadow_color(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) {
1104        if let Ok(rgba) = parse_color(canvas, &value) {
1105            self.state.borrow_mut().shadow_color = rgba;
1106        }
1107    }
1108
1109    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
1110    pub(super) fn stroke_style(&self) -> StringOrCanvasGradientOrCanvasPattern {
1111        match self.state.borrow().stroke_style {
1112            CanvasFillOrStrokeStyle::Color(ref rgba) => {
1113                let mut result = String::new();
1114                serialize(rgba, &mut result).unwrap();
1115                StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result))
1116            },
1117            CanvasFillOrStrokeStyle::Gradient(ref gradient) => {
1118                StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(gradient))
1119            },
1120            CanvasFillOrStrokeStyle::Pattern(ref pattern) => {
1121                StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(pattern))
1122            },
1123        }
1124    }
1125
1126    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
1127    pub(super) fn set_stroke_style(
1128        &self,
1129        canvas: Option<&HTMLCanvasElement>,
1130        value: StringOrCanvasGradientOrCanvasPattern,
1131    ) {
1132        match value {
1133            StringOrCanvasGradientOrCanvasPattern::String(string) => {
1134                if let Ok(rgba) = parse_color(canvas, &string) {
1135                    self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba);
1136                }
1137            },
1138            StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
1139                self.state.borrow_mut().stroke_style =
1140                    CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
1141            },
1142            StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
1143                self.state.borrow_mut().stroke_style =
1144                    CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
1145                if !pattern.origin_is_clean() {
1146                    self.set_origin_unclean();
1147                }
1148            },
1149        }
1150    }
1151
1152    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
1153    pub(super) fn fill_style(&self) -> StringOrCanvasGradientOrCanvasPattern {
1154        match self.state.borrow().fill_style {
1155            CanvasFillOrStrokeStyle::Color(ref rgba) => {
1156                let mut result = String::new();
1157                serialize(rgba, &mut result).unwrap();
1158                StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result))
1159            },
1160            CanvasFillOrStrokeStyle::Gradient(ref gradient) => {
1161                StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(gradient))
1162            },
1163            CanvasFillOrStrokeStyle::Pattern(ref pattern) => {
1164                StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(pattern))
1165            },
1166        }
1167    }
1168
1169    // https://html.spec.whatwg.org/multipage/#dom-context-2d-strokestyle
1170    pub(super) fn set_fill_style(
1171        &self,
1172        canvas: Option<&HTMLCanvasElement>,
1173        value: StringOrCanvasGradientOrCanvasPattern,
1174    ) {
1175        match value {
1176            StringOrCanvasGradientOrCanvasPattern::String(string) => {
1177                if let Ok(rgba) = parse_color(canvas, &string) {
1178                    self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba);
1179                }
1180            },
1181            StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
1182                self.state.borrow_mut().fill_style =
1183                    CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
1184            },
1185            StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
1186                self.state.borrow_mut().fill_style =
1187                    CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
1188                if !pattern.origin_is_clean() {
1189                    self.set_origin_unclean();
1190                }
1191            },
1192        }
1193    }
1194
1195    // https://html.spec.whatwg.org/multipage/#dom-context-2d-createlineargradient
1196    pub(super) fn create_linear_gradient(
1197        &self,
1198        global: &GlobalScope,
1199        x0: Finite<f64>,
1200        y0: Finite<f64>,
1201        x1: Finite<f64>,
1202        y1: Finite<f64>,
1203        can_gc: CanGc,
1204    ) -> DomRoot<CanvasGradient> {
1205        CanvasGradient::new(
1206            global,
1207            CanvasGradientStyle::Linear(LinearGradientStyle::new(*x0, *y0, *x1, *y1, Vec::new())),
1208            can_gc,
1209        )
1210    }
1211
1212    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-createradialgradient>
1213    #[allow(clippy::too_many_arguments)]
1214    pub(super) fn create_radial_gradient(
1215        &self,
1216        global: &GlobalScope,
1217        x0: Finite<f64>,
1218        y0: Finite<f64>,
1219        r0: Finite<f64>,
1220        x1: Finite<f64>,
1221        y1: Finite<f64>,
1222        r1: Finite<f64>,
1223        can_gc: CanGc,
1224    ) -> Fallible<DomRoot<CanvasGradient>> {
1225        if *r0 < 0. || *r1 < 0. {
1226            return Err(Error::IndexSize);
1227        }
1228
1229        Ok(CanvasGradient::new(
1230            global,
1231            CanvasGradientStyle::Radial(RadialGradientStyle::new(
1232                *x0,
1233                *y0,
1234                *r0,
1235                *x1,
1236                *y1,
1237                *r1,
1238                Vec::new(),
1239            )),
1240            can_gc,
1241        ))
1242    }
1243
1244    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-createpattern>
1245    pub(super) fn create_pattern(
1246        &self,
1247        global: &GlobalScope,
1248        image: CanvasImageSource,
1249        mut repetition: DOMString,
1250        can_gc: CanGc,
1251    ) -> Fallible<Option<DomRoot<CanvasPattern>>> {
1252        let snapshot = match image {
1253            CanvasImageSource::HTMLImageElement(ref image) => {
1254                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
1255                if !image.is_usable()? {
1256                    return Ok(None);
1257                }
1258
1259                image
1260                    .get_raster_image_data()
1261                    .ok_or(Error::InvalidState(None))?
1262            },
1263            CanvasImageSource::HTMLVideoElement(ref video) => {
1264                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
1265                if !video.is_usable() {
1266                    return Ok(None);
1267                }
1268
1269                video
1270                    .get_current_frame_data()
1271                    .ok_or(Error::InvalidState(None))?
1272            },
1273            CanvasImageSource::HTMLCanvasElement(ref canvas) => {
1274                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
1275                if canvas.get_size().is_empty() {
1276                    return Err(Error::InvalidState(None));
1277                }
1278
1279                canvas.get_image_data().ok_or(Error::InvalidState(None))?
1280            },
1281            CanvasImageSource::ImageBitmap(ref bitmap) => {
1282                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
1283                if bitmap.is_detached() {
1284                    return Err(Error::InvalidState(None));
1285                }
1286
1287                bitmap
1288                    .bitmap_data()
1289                    .clone()
1290                    .ok_or(Error::InvalidState(None))?
1291            },
1292            CanvasImageSource::OffscreenCanvas(ref canvas) => {
1293                // <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
1294                if canvas.get_size().is_empty() {
1295                    return Err(Error::InvalidState(None));
1296                }
1297
1298                canvas.get_image_data().ok_or(Error::InvalidState(None))?
1299            },
1300            CanvasImageSource::CSSStyleValue(ref value) => value
1301                .get_url(self.base_url.clone())
1302                .and_then(|url| self.fetch_image_data(url, None))
1303                .ok_or(Error::InvalidState(None))?,
1304        };
1305
1306        if repetition.is_empty() {
1307            repetition.push_str("repeat");
1308        }
1309
1310        if let Ok(rep) = RepetitionStyle::from_str(&repetition.str()) {
1311            let size = snapshot.size();
1312            Ok(Some(CanvasPattern::new(
1313                global,
1314                snapshot,
1315                size.cast(),
1316                rep,
1317                self.is_origin_clean(image),
1318                can_gc,
1319            )))
1320        } else {
1321            Err(Error::Syntax(None))
1322        }
1323    }
1324
1325    // https://html.spec.whatwg.org/multipage/#dom-context-2d-save
1326    pub(super) fn save(&self) {
1327        self.saved_states
1328            .borrow_mut()
1329            .push(self.state.borrow().clone());
1330    }
1331
1332    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
1333    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-restore>
1334    pub(super) fn restore(&self) {
1335        let mut saved_states = self.saved_states.borrow_mut();
1336        if let Some(state) = saved_states.pop() {
1337            let clips_to_pop = self.state.borrow().clips_pushed;
1338            if clips_to_pop != 0 {
1339                self.send_canvas_2d_msg(Canvas2dMsg::PopClips(clips_to_pop));
1340            }
1341            self.state.borrow_mut().clone_from(&state);
1342        }
1343    }
1344
1345    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha
1346    pub(super) fn global_alpha(&self) -> f64 {
1347        self.state.borrow().global_alpha
1348    }
1349
1350    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalalpha
1351    pub(super) fn set_global_alpha(&self, alpha: f64) {
1352        if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
1353            return;
1354        }
1355
1356        self.state.borrow_mut().global_alpha = alpha;
1357    }
1358
1359    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation
1360    pub(super) fn global_composite_operation(&self) -> DOMString {
1361        match self.state.borrow().global_composition {
1362            CompositionOrBlending::Composition(op) => DOMString::from(op.to_string()),
1363            CompositionOrBlending::Blending(op) => DOMString::from(op.to_string()),
1364        }
1365    }
1366
1367    // https://html.spec.whatwg.org/multipage/#dom-context-2d-globalcompositeoperation
1368    pub(super) fn set_global_composite_operation(&self, op_str: DOMString) {
1369        if let Ok(op) = CompositionOrBlending::from_str(&op_str.str()) {
1370            self.state.borrow_mut().global_composition = op;
1371        }
1372    }
1373
1374    // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled
1375    pub(super) fn image_smoothing_enabled(&self) -> bool {
1376        self.state.borrow().image_smoothing_enabled
1377    }
1378
1379    // https://html.spec.whatwg.org/multipage/#dom-context-2d-imagesmoothingenabled
1380    pub(super) fn set_image_smoothing_enabled(&self, value: bool) {
1381        self.state.borrow_mut().image_smoothing_enabled = value;
1382    }
1383
1384    // https://html.spec.whatwg.org/multipage/#dom-context-2d-filltext
1385    pub(super) fn fill_text(
1386        &self,
1387        global_scope: &GlobalScope,
1388        canvas: Option<&HTMLCanvasElement>,
1389        text: DOMString,
1390        x: f64,
1391        y: f64,
1392        max_width: Option<f64>,
1393    ) {
1394        // Step 1: If any of the arguments are infinite or NaN, then return.
1395        if !x.is_finite() ||
1396            !y.is_finite() ||
1397            max_width.is_some_and(|max_width| !max_width.is_finite())
1398        {
1399            return;
1400        }
1401
1402        if self.state.borrow().font_style.is_none() {
1403            self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into())
1404        }
1405        // This may be `None` if if this is offscreen canvas, in which case just use
1406        // the initial values for the text style.
1407        let size = self.font_style().font_size.computed_size().px() as f64;
1408
1409        let Some((bounds, text_run)) = self.text_with_size(
1410            global_scope,
1411            &text.str(),
1412            Point2D::new(x, y),
1413            size,
1414            max_width,
1415        ) else {
1416            return;
1417        };
1418        self.send_canvas_2d_msg(Canvas2dMsg::FillText(
1419            bounds,
1420            text_run,
1421            self.state.borrow().fill_style.to_fill_or_stroke_style(),
1422            self.state.borrow().shadow_options(),
1423            self.state.borrow().composition_options(),
1424            self.state.borrow().transform,
1425        ));
1426    }
1427
1428    // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroketext
1429    pub(super) fn stroke_text(
1430        &self,
1431        global_scope: &GlobalScope,
1432        canvas: Option<&HTMLCanvasElement>,
1433        text: DOMString,
1434        x: f64,
1435        y: f64,
1436        max_width: Option<f64>,
1437    ) {
1438        // Step 1: If any of the arguments are infinite or NaN, then return.
1439        if !x.is_finite() ||
1440            !y.is_finite() ||
1441            max_width.is_some_and(|max_width| !max_width.is_finite())
1442        {
1443            return;
1444        }
1445
1446        if self.state.borrow().font_style.is_none() {
1447            self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into())
1448        }
1449        // This may be `None` if if this is offscreen canvas, in which case just use
1450        // the initial values for the text style.
1451        let size = self.font_style().font_size.computed_size().px() as f64;
1452
1453        let Some((bounds, text_run)) = self.text_with_size(
1454            global_scope,
1455            &text.str(),
1456            Point2D::new(x, y),
1457            size,
1458            max_width,
1459        ) else {
1460            return;
1461        };
1462        self.send_canvas_2d_msg(Canvas2dMsg::StrokeText(
1463            bounds,
1464            text_run,
1465            self.state.borrow().stroke_style.to_fill_or_stroke_style(),
1466            self.state.borrow().line_options(),
1467            self.state.borrow().shadow_options(),
1468            self.state.borrow().composition_options(),
1469            self.state.borrow().transform,
1470        ));
1471    }
1472
1473    /// <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>
1474    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-measuretext>
1475    /// <https://html.spec.whatwg.org/multipage/#textmetrics>
1476    pub(super) fn measure_text(
1477        &self,
1478        global: &GlobalScope,
1479        canvas: Option<&HTMLCanvasElement>,
1480        text: DOMString,
1481        can_gc: CanGc,
1482    ) -> DomRoot<TextMetrics> {
1483        // > Step 1: If maxWidth was provided but is less than or equal to zero or equal to NaN, then return an empty array.0
1484        // Max width is not provided for `measureText()`.
1485
1486        // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters.
1487        let text = replace_ascii_whitespace(&text.str());
1488
1489        // > Step 3: Let font be the current font of target, as given by that object's font
1490        // > attribute.
1491        if self.state.borrow().font_style.is_none() {
1492            self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into());
1493        }
1494
1495        let Some(font_context) = global.font_context() else {
1496            warn!("Tried to paint to a canvas of GlobalScope without a FontContext.");
1497            return TextMetrics::default(global, can_gc);
1498        };
1499
1500        let font_style = self.font_style();
1501        let font_group = font_context.font_group(font_style.clone());
1502        let mut font_group = font_group.write();
1503        let font = font_group.first(font_context).expect("couldn't find font");
1504        let ascent = font.metrics.ascent.to_f64_px();
1505        let descent = font.metrics.descent.to_f64_px();
1506        let runs = self.build_unshaped_text_runs(font_context, &text, &mut font_group);
1507
1508        let mut total_advance = 0.0;
1509        let shaped_runs: Vec<_> = runs
1510            .into_iter()
1511            .filter_map(|unshaped_text_run| {
1512                let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?;
1513                total_advance += text_run.advance;
1514                Some(text_run)
1515            })
1516            .collect();
1517
1518        let bounding_box = shaped_runs
1519            .iter()
1520            .map(|text_run| text_run.bounds)
1521            .reduce(|a, b| a.union(&b))
1522            .unwrap_or_default();
1523
1524        let baseline = font.baseline().unwrap_or_else(|| FontBaseline {
1525            hanging_baseline: (ascent * HANGING_BASELINE_DEFAULT) as f32,
1526            ideographic_baseline: (-descent * IDEOGRAPHIC_BASELINE_DEFAULT) as f32,
1527            alphabetic_baseline: 0.,
1528        });
1529        let ideographic_baseline = baseline.ideographic_baseline as f64;
1530        let alphabetic_baseline = baseline.alphabetic_baseline as f64;
1531        let hanging_baseline = baseline.hanging_baseline as f64;
1532
1533        let state = self.state.borrow();
1534        let anchor_x = match state.text_align {
1535            CanvasTextAlign::End => total_advance,
1536            CanvasTextAlign::Center => total_advance / 2.,
1537            CanvasTextAlign::Right => total_advance,
1538            _ => 0.,
1539        } as f64;
1540        let anchor_y = match state.text_baseline {
1541            CanvasTextBaseline::Top => ascent,
1542            CanvasTextBaseline::Hanging => hanging_baseline,
1543            CanvasTextBaseline::Ideographic => ideographic_baseline,
1544            CanvasTextBaseline::Middle => (ascent - descent) / 2.,
1545            CanvasTextBaseline::Alphabetic => alphabetic_baseline,
1546            CanvasTextBaseline::Bottom => -descent,
1547        };
1548
1549        TextMetrics::new(
1550            global,
1551            total_advance as f64,
1552            anchor_x - bounding_box.min_x(),
1553            bounding_box.max_x() - anchor_x,
1554            bounding_box.max_y() - anchor_y,
1555            anchor_y - bounding_box.min_y(),
1556            ascent - anchor_y,
1557            descent + anchor_y,
1558            ascent - anchor_y,
1559            descent + anchor_y,
1560            hanging_baseline - anchor_y,
1561            alphabetic_baseline - anchor_y,
1562            ideographic_baseline - anchor_y,
1563            can_gc,
1564        )
1565    }
1566
1567    // https://html.spec.whatwg.org/multipage/#dom-context-2d-font
1568    pub(super) fn set_font(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) {
1569        let canvas = match canvas {
1570            Some(element) => element,
1571            None => return, // offscreen canvas doesn't have a placeholder canvas
1572        };
1573        let node = canvas.upcast::<Node>();
1574        let window = canvas.owner_window();
1575
1576        let Some(resolved_font_style) = window.resolved_font_style_query(node, value.to_string())
1577        else {
1578            // This will happen when there is a syntax error.
1579            return;
1580        };
1581        self.state.borrow_mut().font_style = Some(resolved_font_style);
1582    }
1583
1584    fn font_style(&self) -> ServoArc<Font> {
1585        self.state
1586            .borrow()
1587            .font_style
1588            .clone()
1589            .unwrap_or_else(|| ServoArc::new(Font::initial_values()))
1590    }
1591
1592    // https://html.spec.whatwg.org/multipage/#dom-context-2d-font
1593    pub(super) fn font(&self) -> DOMString {
1594        self.state.borrow().font_style.as_ref().map_or_else(
1595            || CanvasContextState::DEFAULT_FONT_STYLE.into(),
1596            |style| {
1597                let mut result = String::new();
1598                serialize_font(style, &mut result).unwrap();
1599                DOMString::from(result)
1600            },
1601        )
1602    }
1603
1604    // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign
1605    pub(super) fn text_align(&self) -> CanvasTextAlign {
1606        self.state.borrow().text_align
1607    }
1608
1609    // https://html.spec.whatwg.org/multipage/#dom-context-2d-textalign
1610    pub(super) fn set_text_align(&self, value: CanvasTextAlign) {
1611        self.state.borrow_mut().text_align = value;
1612    }
1613
1614    pub(super) fn text_baseline(&self) -> CanvasTextBaseline {
1615        self.state.borrow().text_baseline
1616    }
1617
1618    pub(super) fn set_text_baseline(&self, value: CanvasTextBaseline) {
1619        self.state.borrow_mut().text_baseline = value;
1620    }
1621
1622    // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction
1623    pub(super) fn direction(&self) -> CanvasDirection {
1624        self.state.borrow().direction
1625    }
1626
1627    // https://html.spec.whatwg.org/multipage/#dom-context-2d-direction
1628    pub(super) fn set_direction(&self, value: CanvasDirection) {
1629        self.state.borrow_mut().direction = value;
1630    }
1631
1632    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth
1633    pub(super) fn line_width(&self) -> f64 {
1634        self.state.borrow().line_width
1635    }
1636
1637    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linewidth
1638    pub(super) fn set_line_width(&self, width: f64) {
1639        if !width.is_finite() || width <= 0.0 {
1640            return;
1641        }
1642
1643        self.state.borrow_mut().line_width = width;
1644    }
1645
1646    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
1647    pub(super) fn line_cap(&self) -> CanvasLineCap {
1648        match self.state.borrow().line_cap {
1649            LineCapStyle::Butt => CanvasLineCap::Butt,
1650            LineCapStyle::Round => CanvasLineCap::Round,
1651            LineCapStyle::Square => CanvasLineCap::Square,
1652        }
1653    }
1654
1655    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linecap
1656    pub(super) fn set_line_cap(&self, cap: CanvasLineCap) {
1657        let line_cap = match cap {
1658            CanvasLineCap::Butt => LineCapStyle::Butt,
1659            CanvasLineCap::Round => LineCapStyle::Round,
1660            CanvasLineCap::Square => LineCapStyle::Square,
1661        };
1662        self.state.borrow_mut().line_cap = line_cap;
1663    }
1664
1665    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
1666    pub(super) fn line_join(&self) -> CanvasLineJoin {
1667        match self.state.borrow().line_join {
1668            LineJoinStyle::Round => CanvasLineJoin::Round,
1669            LineJoinStyle::Bevel => CanvasLineJoin::Bevel,
1670            LineJoinStyle::Miter => CanvasLineJoin::Miter,
1671        }
1672    }
1673
1674    // https://html.spec.whatwg.org/multipage/#dom-context-2d-linejoin
1675    pub(super) fn set_line_join(&self, join: CanvasLineJoin) {
1676        let line_join = match join {
1677            CanvasLineJoin::Round => LineJoinStyle::Round,
1678            CanvasLineJoin::Bevel => LineJoinStyle::Bevel,
1679            CanvasLineJoin::Miter => LineJoinStyle::Miter,
1680        };
1681        self.state.borrow_mut().line_join = line_join;
1682    }
1683
1684    // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
1685    pub(super) fn miter_limit(&self) -> f64 {
1686        self.state.borrow().miter_limit
1687    }
1688
1689    // https://html.spec.whatwg.org/multipage/#dom-context-2d-miterlimit
1690    pub(super) fn set_miter_limit(&self, limit: f64) {
1691        if !limit.is_finite() || limit <= 0.0 {
1692            return;
1693        }
1694
1695        self.state.borrow_mut().miter_limit = limit;
1696    }
1697
1698    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-getlinedash>
1699    pub(super) fn line_dash(&self) -> Vec<f64> {
1700        // > return a sequence whose values are the values of
1701        // > the object's dash list, in the same order.
1702        self.state.borrow().line_dash.clone()
1703    }
1704
1705    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-setlinedash>
1706    pub(super) fn set_line_dash(&self, segments: Vec<f64>) {
1707        // > If any value in segments is not finite (e.g. an Infinity or a NaN value),
1708        // > or if any value is negative (less than zero), then return (without throwing
1709        // > an exception; user agents could show a message on a developer console,
1710        // > though, as that would be helpful for debugging).
1711        if segments
1712            .iter()
1713            .any(|segment| !segment.is_finite() || *segment < 0.0)
1714        {
1715            return;
1716        }
1717
1718        // > If the number of elements in segments is odd, then let segments
1719        // > be the concatenation of two copies of segments.
1720        let mut line_dash: Vec<_> = segments.clone();
1721        if segments.len() & 1 == 1 {
1722            line_dash.extend(line_dash.clone());
1723        }
1724
1725        // > Let the object's dash list be segments.
1726        self.state.borrow_mut().line_dash = line_dash.clone();
1727    }
1728
1729    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-linedashoffset>
1730    pub(super) fn line_dash_offset(&self) -> f64 {
1731        // > On getting, it must return the current value.
1732        self.state.borrow().line_dash_offset
1733    }
1734
1735    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-linedashoffset?
1736    pub(super) fn set_line_dash_offset(&self, offset: f64) {
1737        // > On setting, infinite and NaN values must be ignored,
1738        // > leaving the value unchanged;
1739        if !offset.is_finite() {
1740            return;
1741        }
1742
1743        // > other values must change the current value to the new value.
1744        self.state.borrow_mut().line_dash_offset = offset;
1745    }
1746
1747    // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata
1748    pub(super) fn create_image_data(
1749        &self,
1750        global: &GlobalScope,
1751        sw: i32,
1752        sh: i32,
1753        can_gc: CanGc,
1754    ) -> Fallible<DomRoot<ImageData>> {
1755        if sw == 0 || sh == 0 {
1756            return Err(Error::IndexSize);
1757        }
1758        ImageData::new(global, sw.unsigned_abs(), sh.unsigned_abs(), None, can_gc)
1759    }
1760
1761    // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata
1762    pub(super) fn create_image_data_(
1763        &self,
1764        global: &GlobalScope,
1765        imagedata: &ImageData,
1766        can_gc: CanGc,
1767    ) -> Fallible<DomRoot<ImageData>> {
1768        ImageData::new(global, imagedata.Width(), imagedata.Height(), None, can_gc)
1769    }
1770
1771    // https://html.spec.whatwg.org/multipage/#dom-context-2d-getimagedata
1772    #[allow(clippy::too_many_arguments)]
1773    pub(super) fn get_image_data(
1774        &self,
1775        canvas_size: Size2D<u32>,
1776        global: &GlobalScope,
1777        sx: i32,
1778        sy: i32,
1779        sw: i32,
1780        sh: i32,
1781        can_gc: CanGc,
1782    ) -> Fallible<DomRoot<ImageData>> {
1783        // FIXME(nox): There are many arithmetic operations here that can
1784        // overflow or underflow, this should probably be audited.
1785
1786        if sw == 0 || sh == 0 {
1787            return Err(Error::IndexSize);
1788        }
1789
1790        if !self.origin_is_clean() {
1791            return Err(Error::Security);
1792        }
1793
1794        let (origin, size) = adjust_size_sign(Point2D::new(sx, sy), Size2D::new(sw, sh));
1795        let read_rect = match pixels::clip(origin, size.to_u32(), canvas_size) {
1796            Some(rect) => rect,
1797            None => {
1798                // All the pixels are outside the canvas surface.
1799                return ImageData::new(global, size.width, size.height, None, can_gc);
1800            },
1801        };
1802
1803        let data = if self.is_paintable() {
1804            let (sender, receiver) = ipc::channel().unwrap();
1805            self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(Some(read_rect), sender));
1806            let snapshot = receiver.recv().unwrap().to_owned();
1807            Some(
1808                snapshot
1809                    .to_vec(
1810                        Some(SnapshotAlphaMode::Transparent {
1811                            premultiplied: false,
1812                        }),
1813                        Some(SnapshotPixelFormat::RGBA),
1814                    )
1815                    .0,
1816            )
1817        } else {
1818            None
1819        };
1820
1821        ImageData::new(global, size.width, size.height, data, can_gc)
1822    }
1823
1824    // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata
1825    pub(super) fn put_image_data(
1826        &self,
1827        canvas_size: Size2D<u32>,
1828        imagedata: &ImageData,
1829        dx: i32,
1830        dy: i32,
1831    ) {
1832        self.put_image_data_(
1833            canvas_size,
1834            imagedata,
1835            dx,
1836            dy,
1837            0,
1838            0,
1839            imagedata.Width() as i32,
1840            imagedata.Height() as i32,
1841        )
1842    }
1843
1844    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata>
1845    #[allow(unsafe_code, clippy::too_many_arguments)]
1846    pub(super) fn put_image_data_(
1847        &self,
1848        canvas_size: Size2D<u32>,
1849        imagedata: &ImageData,
1850        dx: i32,
1851        dy: i32,
1852        dirty_x: i32,
1853        dirty_y: i32,
1854        dirty_width: i32,
1855        dirty_height: i32,
1856    ) {
1857        if !self.is_paintable() {
1858            return;
1859        }
1860
1861        // FIXME(nox): There are many arithmetic operations here that can
1862        // overflow or underflow, this should probably be audited.
1863
1864        let imagedata_size = Size2D::new(imagedata.Width(), imagedata.Height());
1865        if imagedata_size.area() == 0 {
1866            return;
1867        }
1868
1869        // Step 1.
1870        // Done later.
1871
1872        // Step 2.
1873        // TODO: throw InvalidState if buffer is detached.
1874
1875        // Steps 3-6.
1876        let (src_origin, src_size) = adjust_size_sign(
1877            Point2D::new(dirty_x, dirty_y),
1878            Size2D::new(dirty_width, dirty_height),
1879        );
1880        let src_rect = match pixels::clip(src_origin, src_size.to_u32(), imagedata_size.to_u32()) {
1881            Some(rect) => rect,
1882            None => return,
1883        };
1884        let (dst_origin, _) = adjust_size_sign(
1885            Point2D::new(dirty_x.saturating_add(dx), dirty_y.saturating_add(dy)),
1886            Size2D::new(dirty_width, dirty_height),
1887        );
1888        // By clipping to the canvas surface, we avoid sending any pixel
1889        // that would fall outside it.
1890        let dst_rect = match pixels::clip(dst_origin, src_rect.size, canvas_size) {
1891            Some(rect) => rect,
1892            None => return,
1893        };
1894
1895        // Step 7.
1896        let snapshot = imagedata.get_snapshot_rect(Rect::new(src_rect.origin, dst_rect.size));
1897        self.send_canvas_2d_msg(Canvas2dMsg::PutImageData(dst_rect, snapshot.as_ipc()));
1898    }
1899
1900    // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
1901    pub(super) fn draw_image(
1902        &self,
1903        canvas: Option<&HTMLCanvasElement>,
1904        image: CanvasImageSource,
1905        dx: f64,
1906        dy: f64,
1907    ) -> ErrorResult {
1908        if !(dx.is_finite() && dy.is_finite()) {
1909            return Ok(());
1910        }
1911
1912        self.draw_image_internal(canvas, image, 0f64, 0f64, None, None, dx, dy, None, None)
1913    }
1914
1915    // https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage
1916    pub(super) fn draw_image_(
1917        &self,
1918        canvas: Option<&HTMLCanvasElement>,
1919        image: CanvasImageSource,
1920        dx: f64,
1921        dy: f64,
1922        dw: f64,
1923        dh: f64,
1924    ) -> ErrorResult {
1925        if !(dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) {
1926            return Ok(());
1927        }
1928
1929        self.draw_image_internal(
1930            canvas,
1931            image,
1932            0f64,
1933            0f64,
1934            None,
1935            None,
1936            dx,
1937            dy,
1938            Some(dw),
1939            Some(dh),
1940        )
1941    }
1942
1943    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-drawimage>
1944    #[allow(clippy::too_many_arguments)]
1945    pub(super) fn draw_image__(
1946        &self,
1947        canvas: Option<&HTMLCanvasElement>,
1948        image: CanvasImageSource,
1949        sx: f64,
1950        sy: f64,
1951        sw: f64,
1952        sh: f64,
1953        dx: f64,
1954        dy: f64,
1955        dw: f64,
1956        dh: f64,
1957    ) -> ErrorResult {
1958        if !(sx.is_finite() &&
1959            sy.is_finite() &&
1960            sw.is_finite() &&
1961            sh.is_finite() &&
1962            dx.is_finite() &&
1963            dy.is_finite() &&
1964            dw.is_finite() &&
1965            dh.is_finite())
1966        {
1967            return Ok(());
1968        }
1969
1970        self.draw_image_internal(
1971            canvas,
1972            image,
1973            sx,
1974            sy,
1975            Some(sw),
1976            Some(sh),
1977            dx,
1978            dy,
1979            Some(dw),
1980            Some(dh),
1981        )
1982    }
1983
1984    // https://html.spec.whatwg.org/multipage/#dom-context-2d-beginpath
1985    pub(super) fn begin_path(&self) {
1986        *self.current_default_path.borrow_mut() = Path::new();
1987    }
1988
1989    // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
1990    pub(super) fn fill(&self, fill_rule: CanvasFillRule) {
1991        let path = self.current_default_path.borrow().clone();
1992        self.fill_(path, fill_rule);
1993    }
1994
1995    // https://html.spec.whatwg.org/multipage/#dom-context-2d-fill
1996    pub(super) fn fill_(&self, path: Path, fill_rule: CanvasFillRule) {
1997        let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
1998        self.send_canvas_2d_msg(Canvas2dMsg::FillPath(
1999            style,
2000            path,
2001            fill_rule.convert(),
2002            self.state.borrow().shadow_options(),
2003            self.state.borrow().composition_options(),
2004            self.state.borrow().transform,
2005        ));
2006    }
2007
2008    // https://html.spec.whatwg.org/multipage/#dom-context-2d-stroke
2009    pub(super) fn stroke(&self) {
2010        let path = self.current_default_path.borrow().clone();
2011        self.stroke_(path);
2012    }
2013
2014    pub(super) fn stroke_(&self, path: Path) {
2015        let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
2016        self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(
2017            path,
2018            style,
2019            self.state.borrow().line_options(),
2020            self.state.borrow().shadow_options(),
2021            self.state.borrow().composition_options(),
2022            self.state.borrow().transform,
2023        ));
2024    }
2025
2026    // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
2027    pub(super) fn clip(&self, fill_rule: CanvasFillRule) {
2028        let path = self.current_default_path.borrow().clone();
2029        self.clip_(path, fill_rule);
2030    }
2031
2032    // https://html.spec.whatwg.org/multipage/#dom-context-2d-clip
2033    pub(super) fn clip_(&self, path: Path, fill_rule: CanvasFillRule) {
2034        self.state.borrow_mut().clips_pushed += 1;
2035        self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(
2036            path,
2037            fill_rule.convert(),
2038            self.state.borrow().transform,
2039        ));
2040    }
2041
2042    // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
2043    pub(super) fn is_point_in_path(
2044        &self,
2045        global: &GlobalScope,
2046        x: f64,
2047        y: f64,
2048        fill_rule: CanvasFillRule,
2049    ) -> bool {
2050        let mut path = self.current_default_path.borrow().clone();
2051        path.transform(self.state.borrow().transform.cast());
2052        self.is_point_in_path_(global, path, x, y, fill_rule)
2053    }
2054
2055    // https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath
2056    pub(super) fn is_point_in_path_(
2057        &self,
2058        _global: &GlobalScope,
2059        path: Path,
2060        x: f64,
2061        y: f64,
2062        fill_rule: CanvasFillRule,
2063    ) -> bool {
2064        let fill_rule = match fill_rule {
2065            CanvasFillRule::Nonzero => FillRule::Nonzero,
2066            CanvasFillRule::Evenodd => FillRule::Evenodd,
2067        };
2068        path.is_point_in_path(x, y, fill_rule)
2069    }
2070
2071    // https://html.spec.whatwg.org/multipage/#dom-context-2d-scale
2072    pub(super) fn scale(&self, x: f64, y: f64) {
2073        if !(x.is_finite() && y.is_finite()) {
2074            return;
2075        }
2076
2077        let transform = self.state.borrow().transform;
2078        self.update_transform(transform.pre_scale(x, y))
2079    }
2080
2081    // https://html.spec.whatwg.org/multipage/#dom-context-2d-rotate
2082    pub(super) fn rotate(&self, angle: f64) {
2083        if angle == 0.0 || !angle.is_finite() {
2084            return;
2085        }
2086
2087        let (sin, cos) = (angle.sin(), angle.cos());
2088        let transform = self.state.borrow().transform;
2089        self.update_transform(Transform2D::new(cos, sin, -sin, cos, 0.0, 0.0).then(&transform))
2090    }
2091
2092    // https://html.spec.whatwg.org/multipage/#dom-context-2d-translate
2093    pub(super) fn translate(&self, x: f64, y: f64) {
2094        if !(x.is_finite() && y.is_finite()) {
2095            return;
2096        }
2097
2098        let transform = self.state.borrow().transform;
2099        self.update_transform(transform.pre_translate(vec2(x, y)))
2100    }
2101
2102    // https://html.spec.whatwg.org/multipage/#dom-context-2d-transform
2103    pub(super) fn transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
2104        if !(a.is_finite() &&
2105            b.is_finite() &&
2106            c.is_finite() &&
2107            d.is_finite() &&
2108            e.is_finite() &&
2109            f.is_finite())
2110        {
2111            return;
2112        }
2113
2114        let transform = self.state.borrow().transform;
2115        self.update_transform(Transform2D::new(a, b, c, d, e, f).then(&transform))
2116    }
2117
2118    // https://html.spec.whatwg.org/multipage/#dom-context-2d-gettransform
2119    pub(super) fn get_transform(&self, global: &GlobalScope, can_gc: CanGc) -> DomRoot<DOMMatrix> {
2120        let transform = self.state.borrow_mut().transform;
2121        DOMMatrix::new(global, true, transform.to_3d(), can_gc)
2122    }
2123
2124    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform>
2125    pub(super) fn set_transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
2126        // Step 1. If any of the arguments are infinite or NaN, then return.
2127        if !a.is_finite() ||
2128            !b.is_finite() ||
2129            !c.is_finite() ||
2130            !d.is_finite() ||
2131            !e.is_finite() ||
2132            !f.is_finite()
2133        {
2134            return;
2135        }
2136
2137        // Step 2. Reset the current transformation matrix to the matrix described by:
2138        self.update_transform(Transform2D::new(a, b, c, d, e, f))
2139    }
2140
2141    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform-matrix>
2142    pub(super) fn set_transform_(&self, transform: &DOMMatrix2DInit) -> ErrorResult {
2143        // Step 1. Let matrix be the result of creating a DOMMatrix from the 2D
2144        // dictionary transform.
2145        let matrix = dommatrix2dinit_to_matrix(transform)?;
2146
2147        // Step 2. If one or more of matrix's m11 element, m12 element, m21
2148        // element, m22 element, m41 element, or m42 element are infinite or
2149        // NaN, then return.
2150        if !matrix.m11.is_finite() ||
2151            !matrix.m12.is_finite() ||
2152            !matrix.m21.is_finite() ||
2153            !matrix.m22.is_finite() ||
2154            !matrix.m31.is_finite() ||
2155            !matrix.m32.is_finite()
2156        {
2157            return Ok(());
2158        }
2159
2160        // Step 3. Reset the current transformation matrix to matrix.
2161        self.update_transform(matrix.cast());
2162        Ok(())
2163    }
2164
2165    // https://html.spec.whatwg.org/multipage/#dom-context-2d-resettransform
2166    pub(super) fn reset_transform(&self) {
2167        self.update_transform(Transform2D::identity())
2168    }
2169
2170    // https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath
2171    pub(super) fn close_path(&self) {
2172        self.current_default_path.borrow_mut().close_path();
2173    }
2174
2175    // https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto
2176    pub(super) fn move_to(&self, x: f64, y: f64) {
2177        self.current_default_path.borrow_mut().move_to(x, y);
2178    }
2179
2180    // https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto
2181    pub(super) fn line_to(&self, x: f64, y: f64) {
2182        self.current_default_path.borrow_mut().line_to(x, y);
2183    }
2184
2185    // https://html.spec.whatwg.org/multipage/#dom-context-2d-rect
2186    pub(super) fn rect(&self, x: f64, y: f64, width: f64, height: f64) {
2187        self.current_default_path
2188            .borrow_mut()
2189            .rect(x, y, width, height);
2190    }
2191
2192    // https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto
2193    pub(super) fn quadratic_curve_to(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
2194        self.current_default_path
2195            .borrow_mut()
2196            .quadratic_curve_to(cpx, cpy, x, y);
2197    }
2198
2199    // https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto
2200    pub(super) fn bezier_curve_to(
2201        &self,
2202        cp1x: f64,
2203        cp1y: f64,
2204        cp2x: f64,
2205        cp2y: f64,
2206        x: f64,
2207        y: f64,
2208    ) {
2209        self.current_default_path
2210            .borrow_mut()
2211            .bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
2212    }
2213
2214    // https://html.spec.whatwg.org/multipage/#dom-context-2d-arc
2215    pub(super) fn arc(
2216        &self,
2217        x: f64,
2218        y: f64,
2219        r: f64,
2220        start: f64,
2221        end: f64,
2222        ccw: bool,
2223    ) -> ErrorResult {
2224        self.current_default_path
2225            .borrow_mut()
2226            .arc(x, y, r, start, end, ccw)
2227            .map_err(|_| Error::IndexSize)
2228    }
2229
2230    // https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto
2231    pub(super) fn arc_to(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult {
2232        self.current_default_path
2233            .borrow_mut()
2234            .arc_to(cp1x, cp1y, cp2x, cp2y, r)
2235            .map_err(|_| Error::IndexSize)
2236    }
2237
2238    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse>
2239    #[allow(clippy::too_many_arguments)]
2240    pub(super) fn ellipse(
2241        &self,
2242        x: f64,
2243        y: f64,
2244        rx: f64,
2245        ry: f64,
2246        rotation: f64,
2247        start: f64,
2248        end: f64,
2249        ccw: bool,
2250    ) -> ErrorResult {
2251        self.current_default_path
2252            .borrow_mut()
2253            .ellipse(x, y, rx, ry, rotation, start, end, ccw)
2254            .map_err(|_| Error::IndexSize)
2255    }
2256
2257    fn text_with_size(
2258        &self,
2259        global_scope: &GlobalScope,
2260        text: &str,
2261        origin: Point2D<f64>,
2262        size: f64,
2263        max_width: Option<f64>,
2264    ) -> Option<(Rect<f64>, Vec<TextRun>)> {
2265        let Some(font_context) = global_scope.font_context() else {
2266            warn!("Tried to paint to a canvas of GlobalScope without a FontContext.");
2267            return None;
2268        };
2269
2270        // Step 1: If maxWidth was provided but is less than or equal to zero or equal to NaN, then return an empty array.
2271        if max_width.is_some_and(|max_width| max_width.is_nan() || max_width <= 0.) {
2272            return None;
2273        }
2274
2275        // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters.
2276        let text = replace_ascii_whitespace(text);
2277
2278        // > Step 3: Let font be the current font of target, as given by that object's font
2279        // > attribute.
2280        let font_style = self.font_style();
2281        let font_group = font_context.font_group_with_size(font_style, Au::from_f64_px(size));
2282        let mut font_group = font_group.write();
2283        let Some(first_font) = font_group.first(font_context) else {
2284            warn!("Could not render canvas text, because there was no first font.");
2285            return None;
2286        };
2287
2288        let runs = self.build_unshaped_text_runs(font_context, &text, &mut font_group);
2289
2290        // TODO: This doesn't do any kind of line layout at all. In particular, there needs
2291        // to be some alignment along a baseline and also support for bidi text.
2292        let mut total_advance = 0.0;
2293        let mut shaped_runs: Vec<_> = runs
2294            .into_iter()
2295            .filter_map(|unshaped_text_run| {
2296                let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?;
2297                total_advance += text_run.advance;
2298                Some(text_run)
2299            })
2300            .collect();
2301
2302        // > Step 6: If maxWidth was provided and the hypothetical width of the inline box in the
2303        // > hypothetical line box is greater than maxWidth CSS pixels, then change font to have a
2304        // > more condensed font (if one is available or if a reasonably readable one can be
2305        // > synthesized by applying a horizontal scale factor to the font) or a smaller font, and
2306        // > return to the previous step.
2307        //
2308        // TODO: We only try decreasing the font size here. Eventually it would make sense to use
2309        // other methods to try to decrease the size, such as finding a narrower font or decreasing
2310        // spacing.
2311        let total_advance = total_advance as f64;
2312        if let Some(max_width) = max_width {
2313            let new_size = (max_width / total_advance * size).floor().max(5.);
2314            if total_advance > max_width && new_size != size {
2315                return self.text_with_size(global_scope, &text, origin, new_size, Some(max_width));
2316            }
2317        }
2318
2319        // > Step 7: Find the anchor point for the line of text.
2320        let start =
2321            self.find_anchor_point_for_line_of_text(origin, &first_font.metrics, total_advance);
2322
2323        // > Step 8: Let result be an array constructed by iterating over each glyph in the inline box
2324        // > from left to right (if any), adding to the array, for each glyph, the shape of the glyph
2325        // > as it is in the inline box, positioned on a coordinate space using CSS pixels with its
2326        // > origin is at the anchor point.
2327        let mut bounds = None;
2328        for text_run in shaped_runs.iter_mut() {
2329            for glyph_and_position in text_run.glyphs_and_positions.iter_mut() {
2330                glyph_and_position.point += Vector2D::new(start.x as f32, start.y as f32);
2331            }
2332            bounds
2333                .get_or_insert(text_run.bounds)
2334                .union(&text_run.bounds);
2335        }
2336
2337        Some((
2338            bounds
2339                .unwrap_or_default()
2340                .translate(start.to_vector().cast_unit()),
2341            shaped_runs,
2342        ))
2343    }
2344
2345    fn build_unshaped_text_runs<'text>(
2346        &self,
2347        font_context: &FontContext,
2348        text: &'text str,
2349        font_group: &mut FontGroup,
2350    ) -> Vec<UnshapedTextRun<'text>> {
2351        let mut runs = Vec::new();
2352        let mut current_text_run = UnshapedTextRun::default();
2353        let mut current_text_run_start_index = 0;
2354
2355        for (index, character) in text.char_indices() {
2356            // TODO: This should ultimately handle emoji variation selectors, but raqote does not yet
2357            // have support for color glyphs.
2358            let script = Script::from(character);
2359            let font = font_group.find_by_codepoint(font_context, character, None, None, None);
2360
2361            if !current_text_run.script_and_font_compatible(script, &font) {
2362                let previous_text_run = std::mem::replace(
2363                    &mut current_text_run,
2364                    UnshapedTextRun {
2365                        font: font.clone(),
2366                        script,
2367                        ..Default::default()
2368                    },
2369                );
2370                current_text_run_start_index = index;
2371                runs.push(previous_text_run)
2372            }
2373
2374            current_text_run.string =
2375                &text[current_text_run_start_index..index + character.len_utf8()];
2376        }
2377
2378        runs.push(current_text_run);
2379        runs
2380    }
2381
2382    /// Find the *anchor_point* for the given parameters of a line of text.
2383    /// See <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>.
2384    fn find_anchor_point_for_line_of_text(
2385        &self,
2386        origin: Point2D<f64>,
2387        metrics: &FontMetrics,
2388        width: f64,
2389    ) -> Point2D<f64> {
2390        let state = self.state.borrow();
2391        let is_rtl = match state.direction {
2392            CanvasDirection::Ltr => false,
2393            CanvasDirection::Rtl => true,
2394            CanvasDirection::Inherit => false, // TODO: resolve direction wrt to canvas element
2395        };
2396
2397        let text_align = match self.text_align() {
2398            CanvasTextAlign::Start if is_rtl => CanvasTextAlign::Right,
2399            CanvasTextAlign::Start => CanvasTextAlign::Left,
2400            CanvasTextAlign::End if is_rtl => CanvasTextAlign::Left,
2401            CanvasTextAlign::End => CanvasTextAlign::Right,
2402            text_align => text_align,
2403        };
2404        let anchor_x = match text_align {
2405            CanvasTextAlign::Center => -width / 2.,
2406            CanvasTextAlign::Right => -width,
2407            _ => 0.,
2408        };
2409
2410        let ascent = metrics.ascent.to_f64_px();
2411        let descent = metrics.descent.to_f64_px();
2412        let anchor_y = match self.text_baseline() {
2413            CanvasTextBaseline::Top => ascent,
2414            CanvasTextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT,
2415            CanvasTextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT,
2416            CanvasTextBaseline::Middle => (ascent - descent) / 2.,
2417            CanvasTextBaseline::Alphabetic => 0.,
2418            CanvasTextBaseline::Bottom => -descent,
2419        };
2420
2421        origin + Vector2D::new(anchor_x, anchor_y)
2422    }
2423}
2424
2425impl Drop for CanvasState {
2426    fn drop(&mut self) {
2427        if let Err(err) = self.ipc_renderer.send(CanvasMsg::Close(self.canvas_id)) {
2428            warn!("Could not close canvas: {}", err)
2429        }
2430    }
2431}
2432
2433#[derive(Default)]
2434struct UnshapedTextRun<'a> {
2435    font: Option<FontRef>,
2436    script: Script,
2437    string: &'a str,
2438}
2439
2440impl UnshapedTextRun<'_> {
2441    fn script_and_font_compatible(&self, script: Script, other_font: &Option<FontRef>) -> bool {
2442        if self.script != script {
2443            return false;
2444        }
2445
2446        match (&self.font, other_font) {
2447            (Some(font_a), Some(font_b)) => font_a.identifier() == font_b.identifier(),
2448            (None, None) => true,
2449            _ => false,
2450        }
2451    }
2452
2453    fn into_shaped_text_run(self, previous_advance: f32) -> Option<TextRun> {
2454        let font = self.font?;
2455        if self.string.is_empty() {
2456            return None;
2457        }
2458
2459        let word_spacing = Au::from_f64_px(
2460            font.glyph_index(' ')
2461                .map(|glyph_id| font.glyph_h_advance(glyph_id))
2462                .unwrap_or(LAST_RESORT_GLYPH_ADVANCE),
2463        );
2464        let options = ShapingOptions {
2465            letter_spacing: None,
2466            word_spacing,
2467            script: self.script,
2468            flags: ShapingFlags::empty(),
2469        };
2470
2471        let glyphs = font.shape_text(self.string, &options);
2472
2473        let mut advance = 0.0;
2474        let mut bounds = None;
2475        let glyphs_and_positions = glyphs
2476            .iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len()))
2477            .map(|glyph| {
2478                let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
2479                let glyph_and_position = GlyphAndPosition {
2480                    id: glyph.id(),
2481                    point: Point2D::new(previous_advance + advance, glyph_offset.y.to_f32_px()),
2482                };
2483
2484                let glyph_bounds = font
2485                    .typographic_bounds(glyph.id())
2486                    .translate(Vector2D::new(advance + previous_advance, 0.0));
2487                bounds = Some(bounds.get_or_insert(glyph_bounds).union(&glyph_bounds));
2488
2489                advance += glyph.advance().to_f32_px();
2490
2491                glyph_and_position
2492            })
2493            .collect();
2494
2495        let identifier = font.identifier();
2496        let font_data = match &identifier {
2497            FontIdentifier::Local(_) => None,
2498            FontIdentifier::Web(_) => Some(font.font_data_and_index().ok()?),
2499        }
2500        .cloned();
2501        let canvas_font = CanvasFont {
2502            identifier,
2503            data: font_data,
2504        };
2505
2506        Some(TextRun {
2507            font: canvas_font,
2508            pt_size: font.descriptor.pt_size.to_f32_px(),
2509            glyphs_and_positions,
2510            advance,
2511            bounds: bounds.unwrap_or_default().cast(),
2512        })
2513    }
2514}
2515
2516pub(super) fn parse_color(
2517    canvas: Option<&HTMLCanvasElement>,
2518    string: &DOMString,
2519) -> Result<AbsoluteColor, ()> {
2520    let string = string.str();
2521    let mut input = ParserInput::new(&string);
2522    let mut parser = Parser::new(&mut input);
2523    let url = Url::parse("about:blank").unwrap().into();
2524    let context = ParserContext::new(
2525        Origin::Author,
2526        &url,
2527        Some(CssRuleType::Style),
2528        ParsingMode::DEFAULT,
2529        QuirksMode::NoQuirks,
2530        /* namespaces = */ Default::default(),
2531        None,
2532        None,
2533    );
2534    match Color::parse_and_compute(&context, &mut parser, None) {
2535        Some(color) => {
2536            // TODO: https://github.com/whatwg/html/issues/1099
2537            // Reconsider how to calculate currentColor in a display:none canvas
2538
2539            // TODO: will need to check that the context bitmap mode is fixed
2540            // once we implement CanvasProxy
2541            let current_color = match canvas {
2542                // https://drafts.css-houdini.org/css-paint-api/#2d-rendering-context
2543                // Whenever "currentColor" is used as a color in the PaintRenderingContext2D API,
2544                // it is treated as opaque black.
2545                None => AbsoluteColor::BLACK,
2546                Some(canvas) => {
2547                    let canvas_element = canvas.upcast::<Element>();
2548                    match canvas_element.style() {
2549                        Some(ref s) if canvas_element.has_css_layout_box() => {
2550                            s.get_inherited_text().color
2551                        },
2552                        _ => AbsoluteColor::BLACK,
2553                    }
2554                },
2555            };
2556
2557            Ok(color.resolve_to_absolute(&current_color))
2558        },
2559        None => Err(()),
2560    }
2561}
2562
2563// Used by drawImage to determine if a source or destination rectangle is valid
2564// Origin coordinates and size cannot be negative. Size has to be greater than zero
2565pub(super) fn is_rect_valid(rect: Rect<f64>) -> bool {
2566    rect.size.width > 0.0 && rect.size.height > 0.0
2567}
2568
2569// https://html.spec.whatwg.org/multipage/#serialisation-of-a-color
2570pub(super) fn serialize<W>(color: &AbsoluteColor, dest: &mut W) -> fmt::Result
2571where
2572    W: fmt::Write,
2573{
2574    let srgb = match color.color_space {
2575        ColorSpace::Srgb if color.flags.contains(ColorFlags::IS_LEGACY_SRGB) => *color,
2576        ColorSpace::Hsl | ColorSpace::Hwb => color.into_srgb_legacy(),
2577        _ => return color.to_css(&mut CssWriter::new(dest)),
2578    };
2579    debug_assert!(srgb.flags.contains(ColorFlags::IS_LEGACY_SRGB));
2580    let red = clamp_unit_f32(srgb.components.0);
2581    let green = clamp_unit_f32(srgb.components.1);
2582    let blue = clamp_unit_f32(srgb.components.2);
2583    let alpha = srgb.alpha;
2584    if alpha == 1.0 {
2585        write!(
2586            dest,
2587            "#{:x}{:x}{:x}{:x}{:x}{:x}",
2588            red >> 4,
2589            red & 0xF,
2590            green >> 4,
2591            green & 0xF,
2592            blue >> 4,
2593            blue & 0xF
2594        )
2595    } else {
2596        write!(dest, "rgba({}, {}, {}, {})", red, green, blue, alpha)
2597    }
2598}
2599
2600pub(super) fn adjust_size_sign(
2601    mut origin: Point2D<i32>,
2602    mut size: Size2D<i32>,
2603) -> (Point2D<i32>, Size2D<u32>) {
2604    if size.width < 0 {
2605        size.width = -size.width;
2606        origin.x = origin.x.saturating_sub(size.width);
2607    }
2608    if size.height < 0 {
2609        size.height = -size.height;
2610        origin.y = origin.y.saturating_sub(size.height);
2611    }
2612    (origin, size.to_u32())
2613}
2614
2615fn serialize_font<W>(style: &Font, dest: &mut W) -> fmt::Result
2616where
2617    W: fmt::Write,
2618{
2619    if style.font_style == FontStyle::ITALIC {
2620        write!(dest, "{} ", style.font_style.to_css_string())?;
2621    }
2622    if style.font_weight.is_bold() {
2623        write!(dest, "{} ", style.font_weight.to_css_string())?;
2624    }
2625    if style.font_variant_caps == FontVariantCaps::SmallCaps {
2626        write!(dest, "{} ", style.font_variant_caps.to_css_string())?;
2627    }
2628    write!(
2629        dest,
2630        "{} {}",
2631        style.font_size.to_css_string(),
2632        style.font_family.to_css_string()
2633    )
2634}
2635
2636fn adjust_canvas_size(size: Size2D<u64>) -> Size2D<u64> {
2637    // Firefox limits width/height to 32767 pixels and Chromium to 65535 pixels,
2638    // but slows down dramatically before it reaches that limit.
2639    // We limit by area instead, giving us larger maximum dimensions,
2640    // in exchange for a smaller maximum canvas size.
2641    const MAX_CANVAS_AREA: u64 = 32768 * 8192;
2642    // Max width/height to 65535 in CSS pixels.
2643    const MAX_CANVAS_SIZE: u64 = 65535;
2644
2645    if !size.is_empty() &&
2646        size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE))
2647            .none() &&
2648        size.area() < MAX_CANVAS_AREA
2649    {
2650        size
2651    } else {
2652        Size2D::zero()
2653    }
2654}
2655
2656impl Convert<FillRule> for CanvasFillRule {
2657    fn convert(self) -> FillRule {
2658        match self {
2659            CanvasFillRule::Nonzero => FillRule::Nonzero,
2660            CanvasFillRule::Evenodd => FillRule::Evenodd,
2661        }
2662    }
2663}
2664
2665fn replace_ascii_whitespace(text: &str) -> String {
2666    text.chars()
2667        .map(|c| match c {
2668            ' ' | '\t' | '\n' | '\r' | '\x0C' => '\x20',
2669            _ => c,
2670        })
2671        .collect()
2672}