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