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