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