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