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