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