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