1use std::cell::Cell;
6use std::fmt;
7use std::str::FromStr;
8use std::sync::Arc;
9
10use app_units::Au;
11use base::Epoch;
12use canvas_traits::canvas::{
13 Canvas2dMsg, CanvasFont, CanvasId, CanvasMsg, CompositionOptions, CompositionOrBlending,
14 FillOrStrokeStyle, FillRule, GlyphAndPosition, LineCapStyle, LineJoinStyle, LineOptions,
15 LinearGradientStyle, Path, RadialGradientStyle, RepetitionStyle, ShadowOptions, TextRun,
16};
17use constellation_traits::ScriptToConstellationMessage;
18use cssparser::color::clamp_unit_f32;
19use cssparser::{Parser, ParserInput};
20use euclid::default::{Point2D, Rect, Size2D, Transform2D};
21use euclid::{Vector2D, vec2};
22use fonts::{
23 ByteIndex, FontBaseline, FontContext, FontGroup, FontIdentifier, FontMetrics, FontRef,
24 LAST_RESORT_GLYPH_ADVANCE, ShapingFlags, ShapingOptions,
25};
26use ipc_channel::ipc::{self, IpcSender};
27use net_traits::image_cache::{ImageCache, ImageResponse};
28use net_traits::request::CorsSettings;
29use pixels::{PixelFormat, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
30use profile_traits::ipc as profiled_ipc;
31use range::Range;
32use servo_arc::Arc as ServoArc;
33use servo_url::{ImmutableOrigin, ServoUrl};
34use style::color::{AbsoluteColor, ColorFlags, ColorSpace};
35use style::context::QuirksMode;
36use style::parser::ParserContext;
37use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
38use style::properties::style_structs::Font;
39use style::stylesheets::{CssRuleType, Origin};
40use style::values::computed::font::FontStyle;
41use style::values::specified::color::Color;
42use style_traits::values::ToCss;
43use style_traits::{CssWriter, ParsingMode};
44use unicode_script::Script;
45use url::Url;
46use webrender_api::ImageKey;
47
48use crate::canvas_context::{CanvasContext, OffscreenRenderingContext, RenderingContext};
49use crate::conversions::Convert;
50use crate::dom::bindings::cell::DomRefCell;
51use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::{
52 CanvasDirection, CanvasFillRule, CanvasImageSource, CanvasLineCap, CanvasLineJoin,
53 CanvasTextAlign, CanvasTextBaseline, ImageDataMethods,
54};
55use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::DOMMatrix2DInit;
56use crate::dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern;
57use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
58use crate::dom::bindings::inheritance::Castable;
59use crate::dom::bindings::num::Finite;
60use crate::dom::bindings::root::{Dom, DomRoot};
61use crate::dom::bindings::str::DOMString;
62use crate::dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle};
63use crate::dom::canvaspattern::CanvasPattern;
64use crate::dom::dommatrix::DOMMatrix;
65use crate::dom::dommatrixreadonly::dommatrix2dinit_to_matrix;
66use crate::dom::element::Element;
67use crate::dom::globalscope::GlobalScope;
68use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
69use crate::dom::html::htmlimageelement::HTMLImageElement;
70use crate::dom::html::htmlvideoelement::HTMLVideoElement;
71use crate::dom::imagebitmap::ImageBitmap;
72use crate::dom::imagedata::ImageData;
73use crate::dom::node::{Node, NodeTraits};
74use crate::dom::offscreencanvas::OffscreenCanvas;
75use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
76use crate::dom::textmetrics::TextMetrics;
77use crate::script_runtime::CanGc;
78
79const HANGING_BASELINE_DEFAULT: f64 = 0.8;
80const IDEOGRAPHIC_BASELINE_DEFAULT: f64 = 0.5;
81
82#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
83#[derive(Clone, JSTraceable, MallocSizeOf)]
84#[allow(dead_code)]
85pub(crate) enum CanvasFillOrStrokeStyle {
86 Color(#[no_trace] AbsoluteColor),
87 Gradient(Dom<CanvasGradient>),
88 Pattern(Dom<CanvasPattern>),
89}
90
91impl CanvasFillOrStrokeStyle {
92 fn to_fill_or_stroke_style(&self) -> FillOrStrokeStyle {
93 match self {
94 CanvasFillOrStrokeStyle::Color(rgba) => FillOrStrokeStyle::Color(*rgba),
95 CanvasFillOrStrokeStyle::Gradient(gradient) => gradient.to_fill_or_stroke_style(),
96 CanvasFillOrStrokeStyle::Pattern(pattern) => pattern.to_fill_or_stroke_style(),
97 }
98 }
99}
100
101#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
102#[derive(Clone, JSTraceable, MallocSizeOf)]
103pub(crate) struct CanvasContextState {
104 global_alpha: f64,
105 #[no_trace]
106 global_composition: CompositionOrBlending,
107 image_smoothing_enabled: bool,
108 fill_style: CanvasFillOrStrokeStyle,
109 stroke_style: CanvasFillOrStrokeStyle,
110 line_width: f64,
111 #[no_trace]
112 line_cap: LineCapStyle,
113 #[no_trace]
114 line_join: LineJoinStyle,
115 miter_limit: f64,
116 line_dash: Vec<f64>,
117 line_dash_offset: f64,
118 #[no_trace]
119 transform: Transform2D<f64>,
120 shadow_offset_x: f64,
121 shadow_offset_y: f64,
122 shadow_blur: f64,
123 #[no_trace]
124 shadow_color: AbsoluteColor,
125 #[no_trace]
126 #[conditional_malloc_size_of]
127 font_style: Option<ServoArc<Font>>,
128 text_align: CanvasTextAlign,
129 text_baseline: CanvasTextBaseline,
130 direction: CanvasDirection,
131 clips_pushed: usize,
134}
135
136impl CanvasContextState {
137 const DEFAULT_FONT_STYLE: &'static str = "10px sans-serif";
138
139 pub(crate) fn new() -> CanvasContextState {
140 CanvasContextState {
141 global_alpha: 1.0,
142 global_composition: CompositionOrBlending::default(),
143 image_smoothing_enabled: true,
144 fill_style: CanvasFillOrStrokeStyle::Color(AbsoluteColor::BLACK),
145 stroke_style: CanvasFillOrStrokeStyle::Color(AbsoluteColor::BLACK),
146 line_width: 1.0,
147 line_cap: LineCapStyle::Butt,
148 line_join: LineJoinStyle::Miter,
149 miter_limit: 10.0,
150 transform: Transform2D::identity(),
151 shadow_offset_x: 0.0,
152 shadow_offset_y: 0.0,
153 shadow_blur: 0.0,
154 shadow_color: AbsoluteColor::TRANSPARENT_BLACK,
155 font_style: None,
156 text_align: CanvasTextAlign::Start,
157 text_baseline: CanvasTextBaseline::Alphabetic,
158 direction: CanvasDirection::Inherit,
159 line_dash: Vec::new(),
160 line_dash_offset: 0.0,
161 clips_pushed: 0,
162 }
163 }
164
165 fn composition_options(&self) -> CompositionOptions {
166 CompositionOptions {
167 alpha: self.global_alpha,
168 composition_operation: self.global_composition,
169 }
170 }
171
172 fn shadow_options(&self) -> ShadowOptions {
173 ShadowOptions {
174 offset_x: self.shadow_offset_x,
175 offset_y: self.shadow_offset_y,
176 blur: self.shadow_blur,
177 color: self.shadow_color,
178 }
179 }
180
181 fn line_options(&self) -> LineOptions {
182 LineOptions {
183 width: self.line_width,
184 cap_style: self.line_cap,
185 join_style: self.line_join,
186 miter_limit: self.miter_limit,
187 dash: self.line_dash.iter().map(|x| *x as f32).collect(),
188 dash_offset: self.line_dash_offset,
189 }
190 }
191}
192
193#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
194#[derive(JSTraceable, MallocSizeOf)]
195pub(crate) struct CanvasState {
196 #[ignore_malloc_size_of = "Defined in ipc-channel"]
197 #[no_trace]
198 ipc_renderer: IpcSender<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 = "Arc"]
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(crate) 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(crate) fn image_key(&self) -> ImageKey {
265 self.image_key
266 }
267
268 pub(crate) fn get_missing_image_urls(&self) -> &DomRefCell<Vec<ServoUrl>> {
269 &self.missing_image_urls
270 }
271
272 pub(crate) fn get_canvas_id(&self) -> CanvasId {
273 self.canvas_id
274 }
275
276 pub(crate) fn is_paintable(&self) -> bool {
277 !self.size.get().is_empty()
278 }
279
280 pub(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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);
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);
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);
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)?;
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),
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);
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 => return Err(Error::InvalidState),
818 }
819 },
820 _ => return Err(Error::InvalidState),
821 }
822 } else {
823 self.send_canvas_2d_msg(Canvas2dMsg::DrawEmptyImage(
824 image_size,
825 dest_rect,
826 source_rect,
827 self.state.borrow().shadow_options(),
828 self.state.borrow().composition_options(),
829 self.state.borrow().transform,
830 ));
831 }
832
833 self.mark_as_dirty(htmlcanvas);
834 Ok(())
835 }
836
837 #[allow(clippy::too_many_arguments)]
838 fn fetch_and_draw_image_data(
839 &self,
840 canvas: Option<&HTMLCanvasElement>,
841 url: ServoUrl,
842 cors_setting: Option<CorsSettings>,
843 sx: f64,
844 sy: f64,
845 sw: Option<f64>,
846 sh: Option<f64>,
847 dx: f64,
848 dy: f64,
849 dw: Option<f64>,
850 dh: Option<f64>,
851 ) -> ErrorResult {
852 debug!("Fetching image {}.", url);
853 let snapshot = self
854 .fetch_image_data(url, cors_setting)
855 .ok_or(Error::InvalidState)?;
856 let image_size = snapshot.size();
857
858 let dw = dw.unwrap_or(image_size.width as f64);
859 let dh = dh.unwrap_or(image_size.height as f64);
860 let sw = sw.unwrap_or(image_size.width as f64);
861 let sh = sh.unwrap_or(image_size.height as f64);
862
863 let (source_rect, dest_rect) =
865 self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh);
866
867 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
868 return Ok(());
869 }
870
871 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
872 self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
873 snapshot.as_ipc(),
874 dest_rect,
875 source_rect,
876 smoothing_enabled,
877 self.state.borrow().shadow_options(),
878 self.state.borrow().composition_options(),
879 self.state.borrow().transform,
880 ));
881 self.mark_as_dirty(canvas);
882 Ok(())
883 }
884
885 #[allow(clippy::too_many_arguments)]
887 fn draw_image_bitmap(
888 &self,
889 bitmap: &ImageBitmap,
890 canvas: Option<&HTMLCanvasElement>,
891 sx: f64,
892 sy: f64,
893 sw: Option<f64>,
894 sh: Option<f64>,
895 dx: f64,
896 dy: f64,
897 dw: Option<f64>,
898 dh: Option<f64>,
899 ) {
900 let Some(snapshot) = bitmap.bitmap_data().clone() else {
901 return;
902 };
903
904 let bitmap_size = snapshot.size();
906 let dw = dw.unwrap_or(bitmap_size.width as f64);
907 let dh = dh.unwrap_or(bitmap_size.height as f64);
908 let sw = sw.unwrap_or(bitmap_size.width as f64);
909 let sh = sh.unwrap_or(bitmap_size.height as f64);
910
911 let (source_rect, dest_rect) =
912 self.adjust_source_dest_rects(bitmap_size, sx, sy, sw, sh, dx, dy, dw, dh);
913
914 if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) {
916 return;
917 }
918
919 let smoothing_enabled = self.state.borrow().image_smoothing_enabled;
920
921 self.send_canvas_2d_msg(Canvas2dMsg::DrawImage(
922 snapshot.as_ipc(),
923 dest_rect,
924 source_rect,
925 smoothing_enabled,
926 self.state.borrow().shadow_options(),
927 self.state.borrow().composition_options(),
928 self.state.borrow().transform,
929 ));
930
931 self.mark_as_dirty(canvas);
932 }
933
934 pub(crate) fn mark_as_dirty(&self, canvas: Option<&HTMLCanvasElement>) {
935 if let Some(canvas) = canvas {
936 canvas.mark_as_dirty();
937 }
938 }
939
940 #[allow(clippy::too_many_arguments)]
945 fn adjust_source_dest_rects(
946 &self,
947 image_size: Size2D<u32>,
948 sx: f64,
949 sy: f64,
950 sw: f64,
951 sh: f64,
952 dx: f64,
953 dy: f64,
954 dw: f64,
955 dh: f64,
956 ) -> (Rect<f64>, Rect<f64>) {
957 let image_rect = Rect::new(
958 Point2D::zero(),
959 Size2D::new(image_size.width, image_size.height),
960 );
961
962 let source_rect = Rect::new(
965 Point2D::new(sx.min(sx + sw), sy.min(sy + sh)),
966 Size2D::new(sw.abs(), sh.abs()),
967 );
968
969 let source_rect_clipped = source_rect
972 .intersection(&image_rect.to_f64())
973 .unwrap_or(Rect::zero());
974
975 let width_ratio: f64 = source_rect_clipped.size.width / source_rect.size.width;
977 let height_ratio: f64 = source_rect_clipped.size.height / source_rect.size.height;
978
979 let dest_rect_width_scaled: f64 = dw * width_ratio;
982 let dest_rect_height_scaled: f64 = dh * height_ratio;
983
984 let dest_rect = Rect::new(
987 Point2D::new(
988 dx.min(dx + dest_rect_width_scaled),
989 dy.min(dy + dest_rect_height_scaled),
990 ),
991 Size2D::new(dest_rect_width_scaled.abs(), dest_rect_height_scaled.abs()),
992 );
993
994 let source_rect = Rect::new(
995 Point2D::new(source_rect_clipped.origin.x, source_rect_clipped.origin.y),
996 Size2D::new(
997 source_rect_clipped.size.width,
998 source_rect_clipped.size.height,
999 ),
1000 );
1001
1002 (source_rect, dest_rect)
1003 }
1004
1005 fn update_transform(&self, transform: Transform2D<f64>) {
1006 let mut state = self.state.borrow_mut();
1007 self.current_default_path
1008 .borrow_mut()
1009 .transform(state.transform.cast());
1010 state.transform = transform;
1011 if let Some(inverse) = transform.inverse() {
1012 self.current_default_path
1013 .borrow_mut()
1014 .transform(inverse.cast());
1015 }
1016 }
1017
1018 pub(crate) fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1020 if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1021 let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
1022 self.send_canvas_2d_msg(Canvas2dMsg::FillRect(
1023 rect,
1024 style,
1025 self.state.borrow().shadow_options(),
1026 self.state.borrow().composition_options(),
1027 self.state.borrow().transform,
1028 ));
1029 }
1030 }
1031
1032 pub(crate) fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1034 if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1035 self.send_canvas_2d_msg(Canvas2dMsg::ClearRect(rect, self.state.borrow().transform));
1036 }
1037 }
1038
1039 pub(crate) fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64) {
1041 if let Some(rect) = self.create_drawable_rect(x, y, width, height) {
1042 let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
1043 self.send_canvas_2d_msg(Canvas2dMsg::StrokeRect(
1044 rect,
1045 style,
1046 self.state.borrow().line_options(),
1047 self.state.borrow().shadow_options(),
1048 self.state.borrow().composition_options(),
1049 self.state.borrow().transform,
1050 ));
1051 }
1052 }
1053
1054 pub(crate) fn shadow_offset_x(&self) -> f64 {
1056 self.state.borrow().shadow_offset_x
1057 }
1058
1059 pub(crate) fn set_shadow_offset_x(&self, value: f64) {
1061 if !value.is_finite() || value == self.state.borrow().shadow_offset_x {
1062 return;
1063 }
1064 self.state.borrow_mut().shadow_offset_x = value;
1065 }
1066
1067 pub(crate) fn shadow_offset_y(&self) -> f64 {
1069 self.state.borrow().shadow_offset_y
1070 }
1071
1072 pub(crate) fn set_shadow_offset_y(&self, value: f64) {
1074 if !value.is_finite() || value == self.state.borrow().shadow_offset_y {
1075 return;
1076 }
1077 self.state.borrow_mut().shadow_offset_y = value;
1078 }
1079
1080 pub(crate) fn shadow_blur(&self) -> f64 {
1082 self.state.borrow().shadow_blur
1083 }
1084
1085 pub(crate) fn set_shadow_blur(&self, value: f64) {
1087 if !value.is_finite() || value < 0f64 || value == self.state.borrow().shadow_blur {
1088 return;
1089 }
1090 self.state.borrow_mut().shadow_blur = value;
1091 }
1092
1093 pub(crate) fn shadow_color(&self) -> DOMString {
1095 let mut result = String::new();
1096 serialize(&self.state.borrow().shadow_color, &mut result).unwrap();
1097 DOMString::from(result)
1098 }
1099
1100 pub(crate) fn set_shadow_color(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) {
1102 if let Ok(rgba) = parse_color(canvas, &value) {
1103 self.state.borrow_mut().shadow_color = rgba;
1104 }
1105 }
1106
1107 pub(crate) fn stroke_style(&self) -> StringOrCanvasGradientOrCanvasPattern {
1109 match self.state.borrow().stroke_style {
1110 CanvasFillOrStrokeStyle::Color(ref rgba) => {
1111 let mut result = String::new();
1112 serialize(rgba, &mut result).unwrap();
1113 StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result))
1114 },
1115 CanvasFillOrStrokeStyle::Gradient(ref gradient) => {
1116 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(gradient))
1117 },
1118 CanvasFillOrStrokeStyle::Pattern(ref pattern) => {
1119 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(pattern))
1120 },
1121 }
1122 }
1123
1124 pub(crate) fn set_stroke_style(
1126 &self,
1127 canvas: Option<&HTMLCanvasElement>,
1128 value: StringOrCanvasGradientOrCanvasPattern,
1129 ) {
1130 match value {
1131 StringOrCanvasGradientOrCanvasPattern::String(string) => {
1132 if let Ok(rgba) = parse_color(canvas, &string) {
1133 self.state.borrow_mut().stroke_style = CanvasFillOrStrokeStyle::Color(rgba);
1134 }
1135 },
1136 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
1137 self.state.borrow_mut().stroke_style =
1138 CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
1139 },
1140 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
1141 self.state.borrow_mut().stroke_style =
1142 CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
1143 if !pattern.origin_is_clean() {
1144 self.set_origin_unclean();
1145 }
1146 },
1147 }
1148 }
1149
1150 pub(crate) fn fill_style(&self) -> StringOrCanvasGradientOrCanvasPattern {
1152 match self.state.borrow().fill_style {
1153 CanvasFillOrStrokeStyle::Color(ref rgba) => {
1154 let mut result = String::new();
1155 serialize(rgba, &mut result).unwrap();
1156 StringOrCanvasGradientOrCanvasPattern::String(DOMString::from(result))
1157 },
1158 CanvasFillOrStrokeStyle::Gradient(ref gradient) => {
1159 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(DomRoot::from_ref(gradient))
1160 },
1161 CanvasFillOrStrokeStyle::Pattern(ref pattern) => {
1162 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(DomRoot::from_ref(pattern))
1163 },
1164 }
1165 }
1166
1167 pub(crate) fn set_fill_style(
1169 &self,
1170 canvas: Option<&HTMLCanvasElement>,
1171 value: StringOrCanvasGradientOrCanvasPattern,
1172 ) {
1173 match value {
1174 StringOrCanvasGradientOrCanvasPattern::String(string) => {
1175 if let Ok(rgba) = parse_color(canvas, &string) {
1176 self.state.borrow_mut().fill_style = CanvasFillOrStrokeStyle::Color(rgba);
1177 }
1178 },
1179 StringOrCanvasGradientOrCanvasPattern::CanvasGradient(gradient) => {
1180 self.state.borrow_mut().fill_style =
1181 CanvasFillOrStrokeStyle::Gradient(Dom::from_ref(&*gradient));
1182 },
1183 StringOrCanvasGradientOrCanvasPattern::CanvasPattern(pattern) => {
1184 self.state.borrow_mut().fill_style =
1185 CanvasFillOrStrokeStyle::Pattern(Dom::from_ref(&*pattern));
1186 if !pattern.origin_is_clean() {
1187 self.set_origin_unclean();
1188 }
1189 },
1190 }
1191 }
1192
1193 pub(crate) fn create_linear_gradient(
1195 &self,
1196 global: &GlobalScope,
1197 x0: Finite<f64>,
1198 y0: Finite<f64>,
1199 x1: Finite<f64>,
1200 y1: Finite<f64>,
1201 can_gc: CanGc,
1202 ) -> DomRoot<CanvasGradient> {
1203 CanvasGradient::new(
1204 global,
1205 CanvasGradientStyle::Linear(LinearGradientStyle::new(*x0, *y0, *x1, *y1, Vec::new())),
1206 can_gc,
1207 )
1208 }
1209
1210 #[allow(clippy::too_many_arguments)]
1212 pub(crate) fn create_radial_gradient(
1213 &self,
1214 global: &GlobalScope,
1215 x0: Finite<f64>,
1216 y0: Finite<f64>,
1217 r0: Finite<f64>,
1218 x1: Finite<f64>,
1219 y1: Finite<f64>,
1220 r1: Finite<f64>,
1221 can_gc: CanGc,
1222 ) -> Fallible<DomRoot<CanvasGradient>> {
1223 if *r0 < 0. || *r1 < 0. {
1224 return Err(Error::IndexSize);
1225 }
1226
1227 Ok(CanvasGradient::new(
1228 global,
1229 CanvasGradientStyle::Radial(RadialGradientStyle::new(
1230 *x0,
1231 *y0,
1232 *r0,
1233 *x1,
1234 *y1,
1235 *r1,
1236 Vec::new(),
1237 )),
1238 can_gc,
1239 ))
1240 }
1241
1242 pub(crate) fn create_pattern(
1244 &self,
1245 global: &GlobalScope,
1246 image: CanvasImageSource,
1247 mut repetition: DOMString,
1248 can_gc: CanGc,
1249 ) -> Fallible<Option<DomRoot<CanvasPattern>>> {
1250 let snapshot = match image {
1251 CanvasImageSource::HTMLImageElement(ref image) => {
1252 if !image.is_usable()? {
1254 return Ok(None);
1255 }
1256
1257 image.get_raster_image_data().ok_or(Error::InvalidState)?
1258 },
1259 CanvasImageSource::HTMLVideoElement(ref video) => {
1260 if !video.is_usable() {
1262 return Ok(None);
1263 }
1264
1265 video.get_current_frame_data().ok_or(Error::InvalidState)?
1266 },
1267 CanvasImageSource::HTMLCanvasElement(ref canvas) => {
1268 if canvas.get_size().is_empty() {
1270 return Err(Error::InvalidState);
1271 }
1272
1273 canvas.get_image_data().ok_or(Error::InvalidState)?
1274 },
1275 CanvasImageSource::ImageBitmap(ref bitmap) => {
1276 if bitmap.is_detached() {
1278 return Err(Error::InvalidState);
1279 }
1280
1281 bitmap.bitmap_data().clone().ok_or(Error::InvalidState)?
1282 },
1283 CanvasImageSource::OffscreenCanvas(ref canvas) => {
1284 if canvas.get_size().is_empty() {
1286 return Err(Error::InvalidState);
1287 }
1288
1289 canvas.get_image_data().ok_or(Error::InvalidState)?
1290 },
1291 CanvasImageSource::CSSStyleValue(ref value) => value
1292 .get_url(self.base_url.clone())
1293 .and_then(|url| self.fetch_image_data(url, None))
1294 .ok_or(Error::InvalidState)?,
1295 };
1296
1297 if repetition.is_empty() {
1298 repetition.push_str("repeat");
1299 }
1300
1301 if let Ok(rep) = RepetitionStyle::from_str(&repetition) {
1302 let size = snapshot.size();
1303 Ok(Some(CanvasPattern::new(
1304 global,
1305 snapshot,
1306 size.cast(),
1307 rep,
1308 self.is_origin_clean(image),
1309 can_gc,
1310 )))
1311 } else {
1312 Err(Error::Syntax)
1313 }
1314 }
1315
1316 pub(crate) fn save(&self) {
1318 self.saved_states
1319 .borrow_mut()
1320 .push(self.state.borrow().clone());
1321 }
1322
1323 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
1324 pub(crate) fn restore(&self) {
1326 let mut saved_states = self.saved_states.borrow_mut();
1327 if let Some(state) = saved_states.pop() {
1328 let clips_to_pop = self.state.borrow().clips_pushed;
1329 if clips_to_pop != 0 {
1330 self.send_canvas_2d_msg(Canvas2dMsg::PopClips(clips_to_pop));
1331 }
1332 self.state.borrow_mut().clone_from(&state);
1333 }
1334 }
1335
1336 pub(crate) fn global_alpha(&self) -> f64 {
1338 self.state.borrow().global_alpha
1339 }
1340
1341 pub(crate) fn set_global_alpha(&self, alpha: f64) {
1343 if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
1344 return;
1345 }
1346
1347 self.state.borrow_mut().global_alpha = alpha;
1348 }
1349
1350 pub(crate) fn global_composite_operation(&self) -> DOMString {
1352 match self.state.borrow().global_composition {
1353 CompositionOrBlending::Composition(op) => DOMString::from(op.to_string()),
1354 CompositionOrBlending::Blending(op) => DOMString::from(op.to_string()),
1355 }
1356 }
1357
1358 pub(crate) fn set_global_composite_operation(&self, op_str: DOMString) {
1360 if let Ok(op) = CompositionOrBlending::from_str(&op_str) {
1361 self.state.borrow_mut().global_composition = op;
1362 }
1363 }
1364
1365 pub(crate) fn image_smoothing_enabled(&self) -> bool {
1367 self.state.borrow().image_smoothing_enabled
1368 }
1369
1370 pub(crate) fn set_image_smoothing_enabled(&self, value: bool) {
1372 self.state.borrow_mut().image_smoothing_enabled = value;
1373 }
1374
1375 pub(crate) fn fill_text(
1377 &self,
1378 global_scope: &GlobalScope,
1379 canvas: Option<&HTMLCanvasElement>,
1380 text: DOMString,
1381 x: f64,
1382 y: f64,
1383 max_width: Option<f64>,
1384 ) {
1385 if !x.is_finite() || !y.is_finite() {
1386 return;
1387 }
1388 if max_width.is_some_and(|max_width| !max_width.is_finite() || max_width <= 0.) {
1389 return;
1390 }
1391
1392 if self.state.borrow().font_style.is_none() {
1393 self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into())
1394 }
1395 let size = self.font_style().font_size.computed_size().px() as f64;
1398
1399 self.fill_text_with_size(
1400 global_scope,
1401 text.str(),
1402 Point2D::new(x, y),
1403 size,
1404 max_width,
1405 );
1406 }
1407
1408 pub(crate) fn measure_text(
1412 &self,
1413 global: &GlobalScope,
1414 canvas: Option<&HTMLCanvasElement>,
1415 text: DOMString,
1416 can_gc: CanGc,
1417 ) -> DomRoot<TextMetrics> {
1418 let text = replace_ascii_whitespace(text.str());
1423
1424 if self.state.borrow().font_style.is_none() {
1427 self.set_font(canvas, CanvasContextState::DEFAULT_FONT_STYLE.into());
1428 }
1429
1430 let Some(font_context) = global.font_context() else {
1431 warn!("Tried to paint to a canvas of GlobalScope without a FontContext.");
1432 return TextMetrics::default(global, can_gc);
1433 };
1434
1435 let font_style = self.font_style();
1436 let font_group = font_context.font_group(font_style.clone());
1437 let mut font_group = font_group.write();
1438 let font = font_group.first(font_context).expect("couldn't find font");
1439 let ascent = font.metrics.ascent.to_f64_px();
1440 let descent = font.metrics.descent.to_f64_px();
1441 let runs = self.build_unshaped_text_runs(font_context, &text, &mut font_group);
1442
1443 let mut total_advance = 0.0;
1444 let shaped_runs: Vec<_> = runs
1445 .into_iter()
1446 .filter_map(|unshaped_text_run| {
1447 let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?;
1448 total_advance += text_run.advance;
1449 Some(text_run)
1450 })
1451 .collect();
1452
1453 let bounding_box = shaped_runs
1454 .iter()
1455 .map(|text_run| text_run.bounds)
1456 .reduce(|a, b| a.union(&b))
1457 .unwrap_or_default();
1458
1459 let baseline = font.baseline().unwrap_or_else(|| FontBaseline {
1460 hanging_baseline: (ascent * HANGING_BASELINE_DEFAULT) as f32,
1461 ideographic_baseline: (-descent * IDEOGRAPHIC_BASELINE_DEFAULT) as f32,
1462 alphabetic_baseline: 0.,
1463 });
1464 let ideographic_baseline = baseline.ideographic_baseline as f64;
1465 let alphabetic_baseline = baseline.alphabetic_baseline as f64;
1466 let hanging_baseline = baseline.hanging_baseline as f64;
1467
1468 let state = self.state.borrow();
1469 let anchor_x = match state.text_align {
1470 CanvasTextAlign::End => total_advance,
1471 CanvasTextAlign::Center => total_advance / 2.,
1472 CanvasTextAlign::Right => total_advance,
1473 _ => 0.,
1474 } as f64;
1475 let anchor_y = match state.text_baseline {
1476 CanvasTextBaseline::Top => ascent,
1477 CanvasTextBaseline::Hanging => hanging_baseline,
1478 CanvasTextBaseline::Ideographic => ideographic_baseline,
1479 CanvasTextBaseline::Middle => (ascent - descent) / 2.,
1480 CanvasTextBaseline::Alphabetic => alphabetic_baseline,
1481 CanvasTextBaseline::Bottom => -descent,
1482 };
1483
1484 TextMetrics::new(
1485 global,
1486 total_advance as f64,
1487 anchor_x - bounding_box.min_x(),
1488 bounding_box.max_x() - anchor_x,
1489 bounding_box.max_y() - anchor_y,
1490 anchor_y - bounding_box.min_y(),
1491 ascent - anchor_y,
1492 descent + anchor_y,
1493 ascent - anchor_y,
1494 descent + anchor_y,
1495 hanging_baseline - anchor_y,
1496 alphabetic_baseline - anchor_y,
1497 ideographic_baseline - anchor_y,
1498 can_gc,
1499 )
1500 }
1501
1502 pub(crate) fn set_font(&self, canvas: Option<&HTMLCanvasElement>, value: DOMString) {
1504 let canvas = match canvas {
1505 Some(element) => element,
1506 None => return, };
1508 let node = canvas.upcast::<Node>();
1509 let window = canvas.owner_window();
1510
1511 let Some(resolved_font_style) = window.resolved_font_style_query(node, value.to_string())
1512 else {
1513 return;
1515 };
1516 self.state.borrow_mut().font_style = Some(resolved_font_style);
1517 }
1518
1519 fn font_style(&self) -> ServoArc<Font> {
1520 self.state
1521 .borrow()
1522 .font_style
1523 .clone()
1524 .unwrap_or_else(|| ServoArc::new(Font::initial_values()))
1525 }
1526
1527 pub(crate) fn font(&self) -> DOMString {
1529 self.state.borrow().font_style.as_ref().map_or_else(
1530 || CanvasContextState::DEFAULT_FONT_STYLE.into(),
1531 |style| {
1532 let mut result = String::new();
1533 serialize_font(style, &mut result).unwrap();
1534 DOMString::from(result)
1535 },
1536 )
1537 }
1538
1539 pub(crate) fn text_align(&self) -> CanvasTextAlign {
1541 self.state.borrow().text_align
1542 }
1543
1544 pub(crate) fn set_text_align(&self, value: CanvasTextAlign) {
1546 self.state.borrow_mut().text_align = value;
1547 }
1548
1549 pub(crate) fn text_baseline(&self) -> CanvasTextBaseline {
1550 self.state.borrow().text_baseline
1551 }
1552
1553 pub(crate) fn set_text_baseline(&self, value: CanvasTextBaseline) {
1554 self.state.borrow_mut().text_baseline = value;
1555 }
1556
1557 pub(crate) fn direction(&self) -> CanvasDirection {
1559 self.state.borrow().direction
1560 }
1561
1562 pub(crate) fn set_direction(&self, value: CanvasDirection) {
1564 self.state.borrow_mut().direction = value;
1565 }
1566
1567 pub(crate) fn line_width(&self) -> f64 {
1569 self.state.borrow().line_width
1570 }
1571
1572 pub(crate) fn set_line_width(&self, width: f64) {
1574 if !width.is_finite() || width <= 0.0 {
1575 return;
1576 }
1577
1578 self.state.borrow_mut().line_width = width;
1579 }
1580
1581 pub(crate) fn line_cap(&self) -> CanvasLineCap {
1583 match self.state.borrow().line_cap {
1584 LineCapStyle::Butt => CanvasLineCap::Butt,
1585 LineCapStyle::Round => CanvasLineCap::Round,
1586 LineCapStyle::Square => CanvasLineCap::Square,
1587 }
1588 }
1589
1590 pub(crate) fn set_line_cap(&self, cap: CanvasLineCap) {
1592 let line_cap = match cap {
1593 CanvasLineCap::Butt => LineCapStyle::Butt,
1594 CanvasLineCap::Round => LineCapStyle::Round,
1595 CanvasLineCap::Square => LineCapStyle::Square,
1596 };
1597 self.state.borrow_mut().line_cap = line_cap;
1598 }
1599
1600 pub(crate) fn line_join(&self) -> CanvasLineJoin {
1602 match self.state.borrow().line_join {
1603 LineJoinStyle::Round => CanvasLineJoin::Round,
1604 LineJoinStyle::Bevel => CanvasLineJoin::Bevel,
1605 LineJoinStyle::Miter => CanvasLineJoin::Miter,
1606 }
1607 }
1608
1609 pub(crate) fn set_line_join(&self, join: CanvasLineJoin) {
1611 let line_join = match join {
1612 CanvasLineJoin::Round => LineJoinStyle::Round,
1613 CanvasLineJoin::Bevel => LineJoinStyle::Bevel,
1614 CanvasLineJoin::Miter => LineJoinStyle::Miter,
1615 };
1616 self.state.borrow_mut().line_join = line_join;
1617 }
1618
1619 pub(crate) fn miter_limit(&self) -> f64 {
1621 self.state.borrow().miter_limit
1622 }
1623
1624 pub(crate) fn set_miter_limit(&self, limit: f64) {
1626 if !limit.is_finite() || limit <= 0.0 {
1627 return;
1628 }
1629
1630 self.state.borrow_mut().miter_limit = limit;
1631 }
1632
1633 pub(crate) fn line_dash(&self) -> Vec<f64> {
1635 self.state.borrow().line_dash.clone()
1638 }
1639
1640 pub(crate) fn set_line_dash(&self, segments: Vec<f64>) {
1642 if segments
1647 .iter()
1648 .any(|segment| !segment.is_finite() || *segment < 0.0)
1649 {
1650 return;
1651 }
1652
1653 let mut line_dash: Vec<_> = segments.clone();
1656 if segments.len() & 1 == 1 {
1657 line_dash.extend(line_dash.clone());
1658 }
1659
1660 self.state.borrow_mut().line_dash = line_dash.clone();
1662 }
1663
1664 pub(crate) fn line_dash_offset(&self) -> f64 {
1666 self.state.borrow().line_dash_offset
1668 }
1669
1670 pub(crate) fn set_line_dash_offset(&self, offset: f64) {
1672 if !offset.is_finite() {
1675 return;
1676 }
1677
1678 self.state.borrow_mut().line_dash_offset = offset;
1680 }
1681
1682 pub(crate) fn create_image_data(
1684 &self,
1685 global: &GlobalScope,
1686 sw: i32,
1687 sh: i32,
1688 can_gc: CanGc,
1689 ) -> Fallible<DomRoot<ImageData>> {
1690 if sw == 0 || sh == 0 {
1691 return Err(Error::IndexSize);
1692 }
1693 ImageData::new(global, sw.unsigned_abs(), sh.unsigned_abs(), None, can_gc)
1694 }
1695
1696 pub(crate) fn create_image_data_(
1698 &self,
1699 global: &GlobalScope,
1700 imagedata: &ImageData,
1701 can_gc: CanGc,
1702 ) -> Fallible<DomRoot<ImageData>> {
1703 ImageData::new(global, imagedata.Width(), imagedata.Height(), None, can_gc)
1704 }
1705
1706 #[allow(clippy::too_many_arguments)]
1708 pub(crate) fn get_image_data(
1709 &self,
1710 canvas_size: Size2D<u32>,
1711 global: &GlobalScope,
1712 sx: i32,
1713 sy: i32,
1714 sw: i32,
1715 sh: i32,
1716 can_gc: CanGc,
1717 ) -> Fallible<DomRoot<ImageData>> {
1718 if sw == 0 || sh == 0 {
1722 return Err(Error::IndexSize);
1723 }
1724
1725 if !self.origin_is_clean() {
1726 return Err(Error::Security);
1727 }
1728
1729 let (origin, size) = adjust_size_sign(Point2D::new(sx, sy), Size2D::new(sw, sh));
1730 let read_rect = match pixels::clip(origin, size.to_u32(), canvas_size) {
1731 Some(rect) => rect,
1732 None => {
1733 return ImageData::new(global, size.width, size.height, None, can_gc);
1735 },
1736 };
1737
1738 let data = if self.is_paintable() {
1739 let (sender, receiver) = ipc::channel().unwrap();
1740 self.send_canvas_2d_msg(Canvas2dMsg::GetImageData(Some(read_rect), sender));
1741 let snapshot = receiver.recv().unwrap().to_owned();
1742 Some(
1743 snapshot
1744 .to_vec(
1745 Some(SnapshotAlphaMode::Transparent {
1746 premultiplied: false,
1747 }),
1748 Some(SnapshotPixelFormat::RGBA),
1749 )
1750 .0,
1751 )
1752 } else {
1753 None
1754 };
1755
1756 ImageData::new(global, size.width, size.height, data, can_gc)
1757 }
1758
1759 pub(crate) fn put_image_data(
1761 &self,
1762 canvas_size: Size2D<u32>,
1763 imagedata: &ImageData,
1764 dx: i32,
1765 dy: i32,
1766 ) {
1767 self.put_image_data_(
1768 canvas_size,
1769 imagedata,
1770 dx,
1771 dy,
1772 0,
1773 0,
1774 imagedata.Width() as i32,
1775 imagedata.Height() as i32,
1776 )
1777 }
1778
1779 #[allow(unsafe_code, clippy::too_many_arguments)]
1781 pub(crate) fn put_image_data_(
1782 &self,
1783 canvas_size: Size2D<u32>,
1784 imagedata: &ImageData,
1785 dx: i32,
1786 dy: i32,
1787 dirty_x: i32,
1788 dirty_y: i32,
1789 dirty_width: i32,
1790 dirty_height: i32,
1791 ) {
1792 if !self.is_paintable() {
1793 return;
1794 }
1795
1796 let imagedata_size = Size2D::new(imagedata.Width(), imagedata.Height());
1800 if imagedata_size.area() == 0 {
1801 return;
1802 }
1803
1804 let (src_origin, src_size) = adjust_size_sign(
1812 Point2D::new(dirty_x, dirty_y),
1813 Size2D::new(dirty_width, dirty_height),
1814 );
1815 let src_rect = match pixels::clip(src_origin, src_size.to_u32(), imagedata_size.to_u32()) {
1816 Some(rect) => rect,
1817 None => return,
1818 };
1819 let (dst_origin, _) = adjust_size_sign(
1820 Point2D::new(dirty_x.saturating_add(dx), dirty_y.saturating_add(dy)),
1821 Size2D::new(dirty_width, dirty_height),
1822 );
1823 let dst_rect = match pixels::clip(dst_origin, src_rect.size, canvas_size) {
1826 Some(rect) => rect,
1827 None => return,
1828 };
1829
1830 let snapshot = imagedata.get_snapshot_rect(Rect::new(src_rect.origin, dst_rect.size));
1832 self.send_canvas_2d_msg(Canvas2dMsg::PutImageData(dst_rect, snapshot.as_ipc()));
1833 }
1834
1835 pub(crate) fn draw_image(
1837 &self,
1838 canvas: Option<&HTMLCanvasElement>,
1839 image: CanvasImageSource,
1840 dx: f64,
1841 dy: f64,
1842 ) -> ErrorResult {
1843 if !(dx.is_finite() && dy.is_finite()) {
1844 return Ok(());
1845 }
1846
1847 self.draw_image_internal(canvas, image, 0f64, 0f64, None, None, dx, dy, None, None)
1848 }
1849
1850 pub(crate) fn draw_image_(
1852 &self,
1853 canvas: Option<&HTMLCanvasElement>,
1854 image: CanvasImageSource,
1855 dx: f64,
1856 dy: f64,
1857 dw: f64,
1858 dh: f64,
1859 ) -> ErrorResult {
1860 if !(dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) {
1861 return Ok(());
1862 }
1863
1864 self.draw_image_internal(
1865 canvas,
1866 image,
1867 0f64,
1868 0f64,
1869 None,
1870 None,
1871 dx,
1872 dy,
1873 Some(dw),
1874 Some(dh),
1875 )
1876 }
1877
1878 #[allow(clippy::too_many_arguments)]
1880 pub(crate) fn draw_image__(
1881 &self,
1882 canvas: Option<&HTMLCanvasElement>,
1883 image: CanvasImageSource,
1884 sx: f64,
1885 sy: f64,
1886 sw: f64,
1887 sh: f64,
1888 dx: f64,
1889 dy: f64,
1890 dw: f64,
1891 dh: f64,
1892 ) -> ErrorResult {
1893 if !(sx.is_finite() &&
1894 sy.is_finite() &&
1895 sw.is_finite() &&
1896 sh.is_finite() &&
1897 dx.is_finite() &&
1898 dy.is_finite() &&
1899 dw.is_finite() &&
1900 dh.is_finite())
1901 {
1902 return Ok(());
1903 }
1904
1905 self.draw_image_internal(
1906 canvas,
1907 image,
1908 sx,
1909 sy,
1910 Some(sw),
1911 Some(sh),
1912 dx,
1913 dy,
1914 Some(dw),
1915 Some(dh),
1916 )
1917 }
1918
1919 pub(crate) fn begin_path(&self) {
1921 *self.current_default_path.borrow_mut() = Path::new();
1922 }
1923
1924 pub(crate) fn fill(&self, fill_rule: CanvasFillRule) {
1926 let path = self.current_default_path.borrow().clone();
1927 self.fill_(path, fill_rule);
1928 }
1929
1930 pub(crate) fn fill_(&self, path: Path, fill_rule: CanvasFillRule) {
1932 let style = self.state.borrow().fill_style.to_fill_or_stroke_style();
1933 self.send_canvas_2d_msg(Canvas2dMsg::FillPath(
1934 style,
1935 path,
1936 fill_rule.convert(),
1937 self.state.borrow().shadow_options(),
1938 self.state.borrow().composition_options(),
1939 self.state.borrow().transform,
1940 ));
1941 }
1942
1943 pub(crate) fn stroke(&self) {
1945 let path = self.current_default_path.borrow().clone();
1946 self.stroke_(path);
1947 }
1948
1949 pub(crate) fn stroke_(&self, path: Path) {
1950 let style = self.state.borrow().stroke_style.to_fill_or_stroke_style();
1951 self.send_canvas_2d_msg(Canvas2dMsg::StrokePath(
1952 path,
1953 style,
1954 self.state.borrow().line_options(),
1955 self.state.borrow().shadow_options(),
1956 self.state.borrow().composition_options(),
1957 self.state.borrow().transform,
1958 ));
1959 }
1960
1961 pub(crate) fn clip(&self, fill_rule: CanvasFillRule) {
1963 let path = self.current_default_path.borrow().clone();
1964 self.clip_(path, fill_rule);
1965 }
1966
1967 pub(crate) fn clip_(&self, path: Path, fill_rule: CanvasFillRule) {
1969 self.state.borrow_mut().clips_pushed += 1;
1970 self.send_canvas_2d_msg(Canvas2dMsg::ClipPath(
1971 path,
1972 fill_rule.convert(),
1973 self.state.borrow().transform,
1974 ));
1975 }
1976
1977 pub(crate) fn is_point_in_path(
1979 &self,
1980 global: &GlobalScope,
1981 x: f64,
1982 y: f64,
1983 fill_rule: CanvasFillRule,
1984 ) -> bool {
1985 let mut path = self.current_default_path.borrow().clone();
1986 path.transform(self.state.borrow().transform.cast());
1987 self.is_point_in_path_(global, path, x, y, fill_rule)
1988 }
1989
1990 pub(crate) fn is_point_in_path_(
1992 &self,
1993 _global: &GlobalScope,
1994 path: Path,
1995 x: f64,
1996 y: f64,
1997 fill_rule: CanvasFillRule,
1998 ) -> bool {
1999 let fill_rule = match fill_rule {
2000 CanvasFillRule::Nonzero => FillRule::Nonzero,
2001 CanvasFillRule::Evenodd => FillRule::Evenodd,
2002 };
2003 path.is_point_in_path(x, y, fill_rule)
2004 }
2005
2006 pub(crate) fn scale(&self, x: f64, y: f64) {
2008 if !(x.is_finite() && y.is_finite()) {
2009 return;
2010 }
2011
2012 let transform = self.state.borrow().transform;
2013 self.update_transform(transform.pre_scale(x, y))
2014 }
2015
2016 pub(crate) fn rotate(&self, angle: f64) {
2018 if angle == 0.0 || !angle.is_finite() {
2019 return;
2020 }
2021
2022 let (sin, cos) = (angle.sin(), angle.cos());
2023 let transform = self.state.borrow().transform;
2024 self.update_transform(Transform2D::new(cos, sin, -sin, cos, 0.0, 0.0).then(&transform))
2025 }
2026
2027 pub(crate) fn translate(&self, x: f64, y: f64) {
2029 if !(x.is_finite() && y.is_finite()) {
2030 return;
2031 }
2032
2033 let transform = self.state.borrow().transform;
2034 self.update_transform(transform.pre_translate(vec2(x, y)))
2035 }
2036
2037 pub(crate) fn transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
2039 if !(a.is_finite() &&
2040 b.is_finite() &&
2041 c.is_finite() &&
2042 d.is_finite() &&
2043 e.is_finite() &&
2044 f.is_finite())
2045 {
2046 return;
2047 }
2048
2049 let transform = self.state.borrow().transform;
2050 self.update_transform(Transform2D::new(a, b, c, d, e, f).then(&transform))
2051 }
2052
2053 pub(crate) fn get_transform(&self, global: &GlobalScope, can_gc: CanGc) -> DomRoot<DOMMatrix> {
2055 let transform = self.state.borrow_mut().transform;
2056 DOMMatrix::new(global, true, transform.to_3d(), can_gc)
2057 }
2058
2059 pub(crate) fn set_transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
2061 if !a.is_finite() ||
2063 !b.is_finite() ||
2064 !c.is_finite() ||
2065 !d.is_finite() ||
2066 !e.is_finite() ||
2067 !f.is_finite()
2068 {
2069 return;
2070 }
2071
2072 self.update_transform(Transform2D::new(a, b, c, d, e, f))
2074 }
2075
2076 pub(crate) fn set_transform_(&self, transform: &DOMMatrix2DInit) -> ErrorResult {
2078 let matrix = dommatrix2dinit_to_matrix(transform)?;
2081
2082 if !matrix.m11.is_finite() ||
2086 !matrix.m12.is_finite() ||
2087 !matrix.m21.is_finite() ||
2088 !matrix.m22.is_finite() ||
2089 !matrix.m31.is_finite() ||
2090 !matrix.m32.is_finite()
2091 {
2092 return Ok(());
2093 }
2094
2095 self.update_transform(matrix.cast());
2097 Ok(())
2098 }
2099
2100 pub(crate) fn reset_transform(&self) {
2102 self.update_transform(Transform2D::identity())
2103 }
2104
2105 pub(crate) fn close_path(&self) {
2107 self.current_default_path.borrow_mut().close_path();
2108 }
2109
2110 pub(crate) fn move_to(&self, x: f64, y: f64) {
2112 self.current_default_path.borrow_mut().move_to(x, y);
2113 }
2114
2115 pub(crate) fn line_to(&self, x: f64, y: f64) {
2117 self.current_default_path.borrow_mut().line_to(x, y);
2118 }
2119
2120 pub(crate) fn rect(&self, x: f64, y: f64, width: f64, height: f64) {
2122 self.current_default_path
2123 .borrow_mut()
2124 .rect(x, y, width, height);
2125 }
2126
2127 pub(crate) fn quadratic_curve_to(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
2129 self.current_default_path
2130 .borrow_mut()
2131 .quadratic_curve_to(cpx, cpy, x, y);
2132 }
2133
2134 pub(crate) fn bezier_curve_to(
2136 &self,
2137 cp1x: f64,
2138 cp1y: f64,
2139 cp2x: f64,
2140 cp2y: f64,
2141 x: f64,
2142 y: f64,
2143 ) {
2144 self.current_default_path
2145 .borrow_mut()
2146 .bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
2147 }
2148
2149 pub(crate) fn arc(
2151 &self,
2152 x: f64,
2153 y: f64,
2154 r: f64,
2155 start: f64,
2156 end: f64,
2157 ccw: bool,
2158 ) -> ErrorResult {
2159 self.current_default_path
2160 .borrow_mut()
2161 .arc(x, y, r, start, end, ccw)
2162 .map_err(|_| Error::IndexSize)
2163 }
2164
2165 pub(crate) fn arc_to(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, r: f64) -> ErrorResult {
2167 self.current_default_path
2168 .borrow_mut()
2169 .arc_to(cp1x, cp1y, cp2x, cp2y, r)
2170 .map_err(|_| Error::IndexSize)
2171 }
2172
2173 #[allow(clippy::too_many_arguments)]
2175 pub(crate) fn ellipse(
2176 &self,
2177 x: f64,
2178 y: f64,
2179 rx: f64,
2180 ry: f64,
2181 rotation: f64,
2182 start: f64,
2183 end: f64,
2184 ccw: bool,
2185 ) -> ErrorResult {
2186 self.current_default_path
2187 .borrow_mut()
2188 .ellipse(x, y, rx, ry, rotation, start, end, ccw)
2189 .map_err(|_| Error::IndexSize)
2190 }
2191
2192 fn fill_text_with_size(
2193 &self,
2194 global_scope: &GlobalScope,
2195 text: &str,
2196 origin: Point2D<f64>,
2197 size: f64,
2198 max_width: Option<f64>,
2199 ) {
2200 let Some(font_context) = global_scope.font_context() else {
2201 warn!("Tried to paint to a canvas of GlobalScope without a FontContext.");
2202 return;
2203 };
2204
2205 let text = replace_ascii_whitespace(text);
2207
2208 let font_style = self.font_style();
2211 let font_group = font_context.font_group_with_size(font_style, Au::from_f64_px(size));
2212 let mut font_group = font_group.write();
2213 let Some(first_font) = font_group.first(font_context) else {
2214 warn!("Could not render canvas text, because there was no first font.");
2215 return;
2216 };
2217
2218 let runs = self.build_unshaped_text_runs(font_context, &text, &mut font_group);
2219
2220 let mut total_advance = 0.0;
2223 let mut shaped_runs: Vec<_> = runs
2224 .into_iter()
2225 .filter_map(|unshaped_text_run| {
2226 let text_run = unshaped_text_run.into_shaped_text_run(total_advance)?;
2227 total_advance += text_run.advance;
2228 Some(text_run)
2229 })
2230 .collect();
2231
2232 let total_advance = total_advance as f64;
2242 if let Some(max_width) = max_width {
2243 let new_size = (max_width / total_advance * size).floor().max(5.);
2244 if total_advance > max_width && new_size != size {
2245 self.fill_text_with_size(global_scope, &text, origin, new_size, Some(max_width));
2246 return;
2247 }
2248 }
2249
2250 let start =
2252 self.find_anchor_point_for_line_of_text(origin, &first_font.metrics, total_advance);
2253
2254 let mut bounds = None;
2259 for text_run in shaped_runs.iter_mut() {
2260 for glyph_and_position in text_run.glyphs_and_positions.iter_mut() {
2261 glyph_and_position.point += Vector2D::new(start.x as f32, start.y as f32);
2262 }
2263 bounds
2264 .get_or_insert(text_run.bounds)
2265 .union(&text_run.bounds);
2266 }
2267
2268 self.send_canvas_2d_msg(Canvas2dMsg::FillText(
2269 bounds
2270 .unwrap_or_default()
2271 .translate(start.to_vector().cast_unit()),
2272 shaped_runs,
2273 self.state.borrow().fill_style.to_fill_or_stroke_style(),
2274 self.state.borrow().shadow_options(),
2275 self.state.borrow().composition_options(),
2276 self.state.borrow().transform,
2277 ));
2278 }
2279
2280 fn build_unshaped_text_runs<'text>(
2281 &self,
2282 font_context: &FontContext,
2283 text: &'text str,
2284 font_group: &mut FontGroup,
2285 ) -> Vec<UnshapedTextRun<'text>> {
2286 let mut runs = Vec::new();
2287 let mut current_text_run = UnshapedTextRun::default();
2288 let mut current_text_run_start_index = 0;
2289
2290 for (index, character) in text.char_indices() {
2291 let script = Script::from(character);
2294 let font = font_group.find_by_codepoint(font_context, character, None, None);
2295
2296 if !current_text_run.script_and_font_compatible(script, &font) {
2297 let previous_text_run = std::mem::replace(
2298 &mut current_text_run,
2299 UnshapedTextRun {
2300 font: font.clone(),
2301 script,
2302 ..Default::default()
2303 },
2304 );
2305 current_text_run_start_index = index;
2306 runs.push(previous_text_run)
2307 }
2308
2309 current_text_run.string =
2310 &text[current_text_run_start_index..index + character.len_utf8()];
2311 }
2312
2313 runs.push(current_text_run);
2314 runs
2315 }
2316
2317 fn find_anchor_point_for_line_of_text(
2320 &self,
2321 origin: Point2D<f64>,
2322 metrics: &FontMetrics,
2323 width: f64,
2324 ) -> Point2D<f64> {
2325 let state = self.state.borrow();
2326 let is_rtl = match state.direction {
2327 CanvasDirection::Ltr => false,
2328 CanvasDirection::Rtl => true,
2329 CanvasDirection::Inherit => false, };
2331
2332 let text_align = match self.text_align() {
2333 CanvasTextAlign::Start if is_rtl => CanvasTextAlign::Right,
2334 CanvasTextAlign::Start => CanvasTextAlign::Left,
2335 CanvasTextAlign::End if is_rtl => CanvasTextAlign::Left,
2336 CanvasTextAlign::End => CanvasTextAlign::Right,
2337 text_align => text_align,
2338 };
2339 let anchor_x = match text_align {
2340 CanvasTextAlign::Center => -width / 2.,
2341 CanvasTextAlign::Right => -width,
2342 _ => 0.,
2343 };
2344
2345 let ascent = metrics.ascent.to_f64_px();
2346 let descent = metrics.descent.to_f64_px();
2347 let anchor_y = match self.text_baseline() {
2348 CanvasTextBaseline::Top => ascent,
2349 CanvasTextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT,
2350 CanvasTextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT,
2351 CanvasTextBaseline::Middle => (ascent - descent) / 2.,
2352 CanvasTextBaseline::Alphabetic => 0.,
2353 CanvasTextBaseline::Bottom => -descent,
2354 };
2355
2356 origin + Vector2D::new(anchor_x, anchor_y)
2357 }
2358}
2359
2360impl Drop for CanvasState {
2361 fn drop(&mut self) {
2362 if let Err(err) = self.ipc_renderer.send(CanvasMsg::Close(self.canvas_id)) {
2363 warn!("Could not close canvas: {}", err)
2364 }
2365 }
2366}
2367
2368#[derive(Default)]
2369struct UnshapedTextRun<'a> {
2370 font: Option<FontRef>,
2371 script: Script,
2372 string: &'a str,
2373}
2374
2375impl UnshapedTextRun<'_> {
2376 fn script_and_font_compatible(&self, script: Script, other_font: &Option<FontRef>) -> bool {
2377 if self.script != script {
2378 return false;
2379 }
2380
2381 match (&self.font, other_font) {
2382 (Some(font_a), Some(font_b)) => font_a.identifier() == font_b.identifier(),
2383 (None, None) => true,
2384 _ => false,
2385 }
2386 }
2387
2388 fn into_shaped_text_run(self, previous_advance: f32) -> Option<TextRun> {
2389 let font = self.font?;
2390 if self.string.is_empty() {
2391 return None;
2392 }
2393
2394 let word_spacing = Au::from_f64_px(
2395 font.glyph_index(' ')
2396 .map(|glyph_id| font.glyph_h_advance(glyph_id))
2397 .unwrap_or(LAST_RESORT_GLYPH_ADVANCE),
2398 );
2399 let options = ShapingOptions {
2400 letter_spacing: None,
2401 word_spacing,
2402 script: self.script,
2403 flags: ShapingFlags::empty(),
2404 };
2405
2406 let glyphs = font.shape_text(self.string, &options);
2407
2408 let mut advance = 0.0;
2409 let mut bounds = None;
2410 let glyphs_and_positions = glyphs
2411 .iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len()))
2412 .map(|glyph| {
2413 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
2414 let glyph_and_position = GlyphAndPosition {
2415 id: glyph.id(),
2416 point: Point2D::new(previous_advance + advance, glyph_offset.y.to_f32_px()),
2417 };
2418
2419 let glyph_bounds = font
2420 .typographic_bounds(glyph.id())
2421 .translate(Vector2D::new(advance + previous_advance, 0.0));
2422 bounds = Some(bounds.get_or_insert(glyph_bounds).union(&glyph_bounds));
2423
2424 advance += glyph.advance().to_f32_px();
2425
2426 glyph_and_position
2427 })
2428 .collect();
2429
2430 let identifier = font.identifier();
2431 let font_data = match &identifier {
2432 FontIdentifier::Local(_) => None,
2433 FontIdentifier::Web(_) => Some(font.font_data_and_index().ok()?),
2434 }
2435 .cloned();
2436 let canvas_font = CanvasFont {
2437 identifier,
2438 data: font_data,
2439 };
2440
2441 Some(TextRun {
2442 font: canvas_font,
2443 pt_size: font.descriptor.pt_size.to_f32_px(),
2444 glyphs_and_positions,
2445 advance,
2446 bounds: bounds.unwrap_or_default().cast(),
2447 })
2448 }
2449}
2450
2451pub(crate) fn parse_color(
2452 canvas: Option<&HTMLCanvasElement>,
2453 string: &str,
2454) -> Result<AbsoluteColor, ()> {
2455 let mut input = ParserInput::new(string);
2456 let mut parser = Parser::new(&mut input);
2457 let url = Url::parse("about:blank").unwrap().into();
2458 let context = ParserContext::new(
2459 Origin::Author,
2460 &url,
2461 Some(CssRuleType::Style),
2462 ParsingMode::DEFAULT,
2463 QuirksMode::NoQuirks,
2464 Default::default(),
2465 None,
2466 None,
2467 );
2468 match Color::parse_and_compute(&context, &mut parser, None) {
2469 Some(color) => {
2470 let current_color = match canvas {
2476 None => AbsoluteColor::BLACK,
2480 Some(canvas) => {
2481 let canvas_element = canvas.upcast::<Element>();
2482 match canvas_element.style() {
2483 Some(ref s) if canvas_element.has_css_layout_box() => {
2484 s.get_inherited_text().color
2485 },
2486 _ => AbsoluteColor::BLACK,
2487 }
2488 },
2489 };
2490
2491 Ok(color.resolve_to_absolute(¤t_color))
2492 },
2493 None => Err(()),
2494 }
2495}
2496
2497pub(crate) fn is_rect_valid(rect: Rect<f64>) -> bool {
2500 rect.size.width > 0.0 && rect.size.height > 0.0
2501}
2502
2503pub(crate) fn serialize<W>(color: &AbsoluteColor, dest: &mut W) -> fmt::Result
2505where
2506 W: fmt::Write,
2507{
2508 let srgb = match color.color_space {
2509 ColorSpace::Srgb if color.flags.contains(ColorFlags::IS_LEGACY_SRGB) => *color,
2510 ColorSpace::Hsl | ColorSpace::Hwb => color.into_srgb_legacy(),
2511 _ => return color.to_css(&mut CssWriter::new(dest)),
2512 };
2513 debug_assert!(srgb.flags.contains(ColorFlags::IS_LEGACY_SRGB));
2514 let red = clamp_unit_f32(srgb.components.0);
2515 let green = clamp_unit_f32(srgb.components.1);
2516 let blue = clamp_unit_f32(srgb.components.2);
2517 let alpha = srgb.alpha;
2518 if alpha == 1.0 {
2519 write!(
2520 dest,
2521 "#{:x}{:x}{:x}{:x}{:x}{:x}",
2522 red >> 4,
2523 red & 0xF,
2524 green >> 4,
2525 green & 0xF,
2526 blue >> 4,
2527 blue & 0xF
2528 )
2529 } else {
2530 write!(dest, "rgba({}, {}, {}, {})", red, green, blue, alpha)
2531 }
2532}
2533
2534pub(crate) fn adjust_size_sign(
2535 mut origin: Point2D<i32>,
2536 mut size: Size2D<i32>,
2537) -> (Point2D<i32>, Size2D<u32>) {
2538 if size.width < 0 {
2539 size.width = -size.width;
2540 origin.x = origin.x.saturating_sub(size.width);
2541 }
2542 if size.height < 0 {
2543 size.height = -size.height;
2544 origin.y = origin.y.saturating_sub(size.height);
2545 }
2546 (origin, size.to_u32())
2547}
2548
2549fn serialize_font<W>(style: &Font, dest: &mut W) -> fmt::Result
2550where
2551 W: fmt::Write,
2552{
2553 if style.font_style == FontStyle::ITALIC {
2554 write!(dest, "{} ", style.font_style.to_css_string())?;
2555 }
2556 if style.font_weight.is_bold() {
2557 write!(dest, "{} ", style.font_weight.to_css_string())?;
2558 }
2559 if style.font_variant_caps == FontVariantCaps::SmallCaps {
2560 write!(dest, "{} ", style.font_variant_caps.to_css_string())?;
2561 }
2562 write!(
2563 dest,
2564 "{} {}",
2565 style.font_size.to_css_string(),
2566 style.font_family.to_css_string()
2567 )
2568}
2569
2570fn adjust_canvas_size(size: Size2D<u64>) -> Size2D<u64> {
2571 const MAX_CANVAS_AREA: u64 = 32768 * 8192;
2576 const MAX_CANVAS_SIZE: u64 = 65535;
2578
2579 if !size.is_empty() &&
2580 size.greater_than(Size2D::new(MAX_CANVAS_SIZE, MAX_CANVAS_SIZE))
2581 .none() &&
2582 size.area() < MAX_CANVAS_AREA
2583 {
2584 size
2585 } else {
2586 Size2D::zero()
2587 }
2588}
2589
2590impl Convert<FillRule> for CanvasFillRule {
2591 fn convert(self) -> FillRule {
2592 match self {
2593 CanvasFillRule::Nonzero => FillRule::Nonzero,
2594 CanvasFillRule::Evenodd => FillRule::Evenodd,
2595 }
2596 }
2597}
2598
2599fn replace_ascii_whitespace(text: &str) -> String {
2600 text.chars()
2601 .map(|c| match c {
2602 ' ' | '\t' | '\n' | '\r' | '\x0C' => '\x20',
2603 _ => c,
2604 })
2605 .collect()
2606}