1use std::default::Default;
6use std::str::FromStr;
7
8use base::Epoch;
9use base::generic_channel::GenericSender;
10use euclid::Angle;
11use euclid::approxeq::ApproxEq;
12use euclid::default::{Point2D, Rect, Size2D, Transform2D};
13use fonts_traits::{FontDataAndIndex, FontIdentifier};
14use kurbo::{BezPath, ParamCurveNearest as _, PathEl, Point, Shape, Triangle};
15use malloc_size_of::MallocSizeOf;
16use malloc_size_of_derive::MallocSizeOf;
17use pixels::SharedSnapshot;
18use serde::{Deserialize, Serialize};
19use strum::{Display, EnumString};
20use style::color::AbsoluteColor;
21use webrender_api::ImageKey;
22
23#[derive(Clone, Debug, Default, Deserialize, Serialize)]
24pub struct Path(pub BezPath);
25
26impl MallocSizeOf for Path {
27 fn size_of(&self, _ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
28 std::mem::size_of_val(self.0.elements())
29 }
30}
31
32pub struct IndexSizeError;
33
34impl Path {
35 pub fn new() -> Self {
36 Self(BezPath::new())
37 }
38
39 pub fn from_svg(s: &str) -> Self {
40 Self(BezPath::from_svg(s).unwrap_or_default())
41 }
42
43 pub fn transform(&mut self, transform: Transform2D<f64>) {
44 self.0.apply_affine(transform.into());
45 }
46
47 pub fn ensure_there_is_a_subpath(&mut self, x: f64, y: f64) {
49 if self.0.elements().is_empty() {
51 self.0.move_to((x, y));
56 }
57 }
58
59 pub fn close_path(&mut self) {
61 if matches!(self.0.elements().last(), None | Some(PathEl::ClosePath)) {
63 return;
64 }
65 self.0.close_path();
69 }
70
71 pub fn move_to(&mut self, x: f64, y: f64) {
73 if !(x.is_finite() && y.is_finite()) {
75 return;
76 }
77
78 self.0.move_to((x, y));
80 }
81
82 pub fn line_to(&mut self, x: f64, y: f64) {
84 if !(x.is_finite() && y.is_finite()) {
86 return;
87 }
88
89 self.ensure_there_is_a_subpath(x, y);
91
92 self.0.line_to((x, y));
95 }
96
97 pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
99 if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
101 return;
102 }
103
104 self.ensure_there_is_a_subpath(cpx, cpy);
106
107 self.0.quad_to((cpx, cpy), (x, y));
111 }
112
113 pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
115 if !(cp1x.is_finite() &&
117 cp1y.is_finite() &&
118 cp2x.is_finite() &&
119 cp2y.is_finite() &&
120 x.is_finite() &&
121 y.is_finite())
122 {
123 return;
124 }
125
126 self.ensure_there_is_a_subpath(cp1x, cp1y);
128
129 self.0.curve_to((cp1x, cp1y), (cp2x, cp2y), (x, y));
133 }
134
135 pub fn arc_to(
137 &mut self,
138 x1: f64,
139 y1: f64,
140 x2: f64,
141 y2: f64,
142 radius: f64,
143 ) -> Result<(), IndexSizeError> {
144 if !(x1.is_finite() &&
146 y1.is_finite() &&
147 x2.is_finite() &&
148 y2.is_finite() &&
149 radius.is_finite())
150 {
151 return Ok(());
152 }
153
154 self.ensure_there_is_a_subpath(x1, y1);
156
157 if radius < 0.0 {
159 return Err(IndexSizeError);
160 }
161
162 let Point { x: x0, y: y0 } = self.last_point().unwrap();
164
165 if ((x0, y0) == (x1, y1)) || ((x1, y1) == (x2, y2)) || radius.approx_eq(&0.0) {
170 self.0.line_to((x1, y1));
171 return Ok(());
172 }
173
174 let direction = Triangle::from_coords((x0, y0), (x1, y1), (x2, y2)).area();
178 if direction.approx_eq(&0.0) {
179 self.0.line_to((x1, y1));
180 return Ok(());
181 }
182
183 let a2 = (x0 - x1).powi(2) + (y0 - y1).powi(2);
196 let b2 = (x1 - x2).powi(2) + (y1 - y2).powi(2);
197 let d = {
198 let c2 = (x0 - x2).powi(2) + (y0 - y2).powi(2);
199 let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt());
200 let sinx = (1.0 - cosx.powi(2)).sqrt();
201 radius / ((1.0 - cosx) / sinx)
202 };
203
204 let anx = (x1 - x0) / a2.sqrt();
206 let any = (y1 - y0) / a2.sqrt();
207 let tp1 = Point2D::new(x1 - anx * d, y1 - any * d);
208
209 let bnx = (x1 - x2) / b2.sqrt();
211 let bny = (y1 - y2) / b2.sqrt();
212 let tp2 = Point2D::new(x1 - bnx * d, y1 - bny * d);
213
214 let anticlockwise = direction < 0.0;
216 let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 };
217 let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 };
218 let angle_start = (tp1.y - cy).atan2(tp1.x - cx);
219 let angle_end = (tp2.y - cy).atan2(tp2.x - cx);
220
221 self.0.line_to((tp1.x, tp1.y));
222
223 self.arc(cx, cy, radius, angle_start, angle_end, anticlockwise)
224 }
225
226 pub fn last_point(&mut self) -> Option<Point> {
227 self.0.current_position()
228 }
229
230 pub fn arc(
232 &mut self,
233 x: f64,
234 y: f64,
235 radius: f64,
236 start_angle: f64,
237 end_angle: f64,
238 counterclockwise: bool,
239 ) -> Result<(), IndexSizeError> {
240 self.ellipse(
242 x,
243 y,
244 radius,
245 radius,
246 0.,
247 start_angle,
248 end_angle,
249 counterclockwise,
250 )
251 }
252
253 #[expect(clippy::too_many_arguments)]
254 pub fn ellipse(
256 &mut self,
257 x: f64,
258 y: f64,
259 radius_x: f64,
260 radius_y: f64,
261 rotation_angle: f64,
262 start_angle: f64,
263 end_angle: f64,
264 counterclockwise: bool,
265 ) -> Result<(), IndexSizeError> {
266 if !(x.is_finite() &&
268 y.is_finite() &&
269 radius_x.is_finite() &&
270 radius_y.is_finite() &&
271 rotation_angle.is_finite() &&
272 start_angle.is_finite() &&
273 end_angle.is_finite())
274 {
275 return Ok(());
276 }
277
278 if radius_x < 0.0 || radius_y < 0.0 {
280 return Err(IndexSizeError);
281 }
282
283 let mut start = Angle::radians(start_angle);
284 let mut end = Angle::radians(end_angle);
285
286 if !counterclockwise && start > end + Angle::two_pi() ||
288 counterclockwise && end > start + Angle::two_pi()
289 {
290 start = start.positive();
291 end = end.positive();
292 }
293
294 let sweep = match counterclockwise {
296 true => {
297 if end - start == Angle::two_pi() {
298 -Angle::two_pi()
299 } else if end > start {
300 -(Angle::two_pi() - (end - start))
301 } else {
302 -(start - end)
303 }
304 },
305 false => {
306 if start - end == Angle::two_pi() {
307 Angle::two_pi()
308 } else if start > end {
309 Angle::two_pi() - (start - end)
310 } else {
311 end - start
312 }
313 },
314 };
315
316 let arc = kurbo::Arc::new(
317 (x, y),
318 (radius_x, radius_y),
319 start.radians,
320 sweep.radians,
321 rotation_angle,
322 );
323
324 let mut iter = arc.path_elements(0.01);
325 let kurbo::PathEl::MoveTo(start_point) = iter.next().unwrap() else {
326 unreachable!()
327 };
328
329 self.line_to(start_point.x, start_point.y);
330
331 if sweep.radians.abs() > 1e-3 {
332 self.0.extend(iter);
333 }
334
335 Ok(())
336 }
337
338 pub fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
340 if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) {
342 return;
343 }
344
345 self.0.move_to((x, y));
349 self.0.line_to((x + w, y));
350 self.0.line_to((x + w, y + h));
351 self.0.line_to((x, y + h));
352
353 self.0.close_path();
355
356 self.0.move_to((x, y));
358 }
359
360 pub fn is_point_in_path(&self, x: f64, y: f64, fill_rule: FillRule) -> bool {
362 let p = Point::new(x, y);
363 if !p.is_finite() {
365 return false;
366 }
367
368 let mut path = self.clone();
375 path.close_path();
376 let winding = path.0.winding(p);
377 let is_inside = match fill_rule {
378 FillRule::Nonzero => winding != 0,
379 FillRule::Evenodd => (winding % 2) != 0,
380 };
381 if is_inside {
382 return true;
383 }
384 path.0
386 .segments()
387 .any(|seg| seg.nearest(p, 0.00001).distance_sq < 0.00001)
388 }
389
390 pub fn bounding_box(&self) -> Rect<f64> {
391 self.0.control_box().into()
392 }
393}
394
395#[derive(Clone, Debug, Deserialize, Serialize)]
396pub enum FillRule {
397 Nonzero,
398 Evenodd,
399}
400
401#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
402pub struct CanvasId(pub u64);
403
404#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
405pub struct CompositionOptions {
406 pub alpha: f64,
407 pub composition_operation: CompositionOrBlending,
408}
409
410#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
411pub struct ShadowOptions {
412 pub offset_x: f64,
413 pub offset_y: f64,
414 pub blur: f64,
415 pub color: AbsoluteColor,
416}
417
418impl ShadowOptions {
419 pub fn need_to_draw_shadow(&self) -> bool {
421 self.color.alpha != 0.0 &&
423 (self.offset_x != 0.0 ||
425 self.offset_y != 0.0 ||
426 self.blur != 0.0)
427 }
428}
429
430#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
431pub struct LineOptions {
432 pub width: f64,
433 pub cap_style: LineCapStyle,
434 pub join_style: LineJoinStyle,
435 pub miter_limit: f64,
436 pub dash: Vec<f32>,
437 pub dash_offset: f64,
438}
439
440#[expect(clippy::large_enum_variant)]
441#[derive(Debug, Deserialize, Serialize)]
442pub enum CanvasMsg {
443 Canvas2d(Canvas2dMsg, CanvasId),
444 Recreate(Option<Size2D<u64>>, CanvasId),
445 Close(CanvasId),
446}
447
448#[derive(Debug, Deserialize, Serialize, Display)]
449pub enum Canvas2dMsg {
450 SetImageKey(ImageKey),
451 DrawImage(
452 SharedSnapshot,
453 Rect<f64>,
454 Rect<f64>,
455 bool,
456 ShadowOptions,
457 CompositionOptions,
458 Transform2D<f64>,
459 ),
460 DrawEmptyImage(
461 Size2D<u32>,
462 Rect<f64>,
463 Rect<f64>,
464 ShadowOptions,
465 CompositionOptions,
466 Transform2D<f64>,
467 ),
468 DrawImageInOther(
469 CanvasId,
470 Rect<f64>,
471 Rect<f64>,
472 bool,
473 ShadowOptions,
474 CompositionOptions,
475 Transform2D<f64>,
476 ),
477 ClearRect(Rect<f32>, Transform2D<f64>),
478 ClipPath(Path, FillRule, Transform2D<f64>),
479 PopClips(usize),
480 FillPath(
481 FillOrStrokeStyle,
482 Path,
483 FillRule,
484 ShadowOptions,
485 CompositionOptions,
486 Transform2D<f64>,
487 ),
488 FillText(
489 Rect<f64>,
490 Vec<TextRun>,
491 FillOrStrokeStyle,
492 ShadowOptions,
493 CompositionOptions,
494 Transform2D<f64>,
495 ),
496 StrokeText(
497 Rect<f64>,
498 Vec<TextRun>,
499 FillOrStrokeStyle,
500 LineOptions,
501 ShadowOptions,
502 CompositionOptions,
503 Transform2D<f64>,
504 ),
505 FillRect(
506 Rect<f32>,
507 FillOrStrokeStyle,
508 ShadowOptions,
509 CompositionOptions,
510 Transform2D<f64>,
511 ),
512 GetImageData(Option<Rect<u32>>, GenericSender<SharedSnapshot>),
513 PutImageData(Rect<u32>, SharedSnapshot),
514 StrokeRect(
515 Rect<f32>,
516 FillOrStrokeStyle,
517 LineOptions,
518 ShadowOptions,
519 CompositionOptions,
520 Transform2D<f64>,
521 ),
522 StrokePath(
523 Path,
524 FillOrStrokeStyle,
525 LineOptions,
526 ShadowOptions,
527 CompositionOptions,
528 Transform2D<f64>,
529 ),
530 UpdateImage(Option<Epoch>),
531}
532
533#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
534pub struct CanvasGradientStop {
535 pub offset: f64,
536 pub color: AbsoluteColor,
537}
538
539#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
540pub struct LinearGradientStyle {
541 pub x0: f64,
542 pub y0: f64,
543 pub x1: f64,
544 pub y1: f64,
545 pub stops: Vec<CanvasGradientStop>,
546}
547
548impl LinearGradientStyle {
549 pub fn new(
550 x0: f64,
551 y0: f64,
552 x1: f64,
553 y1: f64,
554 stops: Vec<CanvasGradientStop>,
555 ) -> LinearGradientStyle {
556 LinearGradientStyle {
557 x0,
558 y0,
559 x1,
560 y1,
561 stops,
562 }
563 }
564}
565
566#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
567pub struct RadialGradientStyle {
568 pub x0: f64,
569 pub y0: f64,
570 pub r0: f64,
571 pub x1: f64,
572 pub y1: f64,
573 pub r1: f64,
574 pub stops: Vec<CanvasGradientStop>,
575}
576
577impl RadialGradientStyle {
578 pub fn new(
579 x0: f64,
580 y0: f64,
581 r0: f64,
582 x1: f64,
583 y1: f64,
584 r1: f64,
585 stops: Vec<CanvasGradientStop>,
586 ) -> RadialGradientStyle {
587 RadialGradientStyle {
588 x0,
589 y0,
590 r0,
591 x1,
592 y1,
593 r1,
594 stops,
595 }
596 }
597}
598
599#[derive(Clone, Debug, Deserialize, Serialize)]
600pub struct SurfaceStyle {
601 pub surface_data: SharedSnapshot,
602 pub surface_size: Size2D<u32>,
603 pub repeat_x: bool,
604 pub repeat_y: bool,
605 pub transform: Transform2D<f32>,
606}
607
608impl SurfaceStyle {
609 pub fn new(
610 surface_data: SharedSnapshot,
611 surface_size: Size2D<u32>,
612 repeat_x: bool,
613 repeat_y: bool,
614 transform: Transform2D<f32>,
615 ) -> Self {
616 Self {
617 surface_data,
618 surface_size,
619 repeat_x,
620 repeat_y,
621 transform,
622 }
623 }
624}
625
626#[derive(Clone, Debug, Deserialize, Serialize)]
627pub enum FillOrStrokeStyle {
628 Color(AbsoluteColor),
629 LinearGradient(LinearGradientStyle),
630 RadialGradient(RadialGradientStyle),
631 Surface(SurfaceStyle),
632}
633
634impl FillOrStrokeStyle {
635 pub fn is_zero_size_gradient(&self) -> bool {
636 match self {
637 Self::RadialGradient(pattern) => {
638 let centers_equal = (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1);
639 let radii_equal = pattern.r0 == pattern.r1;
640 (centers_equal && radii_equal) || pattern.stops.is_empty()
641 },
642 Self::LinearGradient(pattern) => {
643 (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1) || pattern.stops.is_empty()
644 },
645 Self::Color(..) | Self::Surface(..) => false,
646 }
647 }
648
649 pub fn x_bound(&self) -> Option<u32> {
650 match self {
651 Self::Surface(pattern) => {
652 if pattern.repeat_x {
653 None
654 } else {
655 Some(pattern.surface_size.width)
656 }
657 },
658 Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
659 }
660 }
661
662 pub fn y_bound(&self) -> Option<u32> {
663 match self {
664 Self::Surface(pattern) => {
665 if pattern.repeat_y {
666 None
667 } else {
668 Some(pattern.surface_size.height)
669 }
670 },
671 Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
672 }
673 }
674}
675
676#[derive(
677 Clone, Copy, Debug, Display, Deserialize, EnumString, MallocSizeOf, PartialEq, Serialize,
678)]
679pub enum LineCapStyle {
680 Butt = 0,
681 Round = 1,
682 Square = 2,
683}
684
685#[derive(
686 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
687)]
688pub enum LineJoinStyle {
689 Round = 0,
690 Bevel = 1,
691 Miter = 2,
692}
693
694#[derive(Clone, Copy, Debug, Deserialize, Display, EnumString, PartialEq, Serialize)]
695#[strum(serialize_all = "kebab-case")]
696pub enum RepetitionStyle {
697 Repeat,
698 RepeatX,
699 RepeatY,
700 NoRepeat,
701}
702
703#[derive(
705 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
706)]
707#[strum(serialize_all = "kebab-case")]
708pub enum CompositionStyle {
709 Clear,
710 Copy,
711 SourceOver,
712 DestinationOver,
713 SourceIn,
714 DestinationIn,
715 SourceOut,
716 DestinationOut,
717 SourceAtop,
718 DestinationAtop,
719 Xor,
720 Lighter,
721 }
724
725#[derive(
727 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
728)]
729#[strum(serialize_all = "kebab-case")]
730pub enum BlendingStyle {
731 Multiply,
733 Screen,
734 Overlay,
735 Darken,
736 Lighten,
737 ColorDodge,
738 ColorBurn,
739 HardLight,
740 SoftLight,
741 Difference,
742 Exclusion,
743 Hue,
744 Saturation,
745 Color,
746 Luminosity,
747}
748
749#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
750pub enum CompositionOrBlending {
751 Composition(CompositionStyle),
752 Blending(BlendingStyle),
753}
754
755impl Default for CompositionOrBlending {
756 fn default() -> CompositionOrBlending {
757 CompositionOrBlending::Composition(CompositionStyle::SourceOver)
758 }
759}
760
761impl FromStr for CompositionOrBlending {
762 type Err = ();
763
764 fn from_str(string: &str) -> Result<CompositionOrBlending, ()> {
765 if let Ok(op) = CompositionStyle::from_str(string) {
766 return Ok(CompositionOrBlending::Composition(op));
767 }
768
769 if let Ok(op) = BlendingStyle::from_str(string) {
770 return Ok(CompositionOrBlending::Blending(op));
771 }
772
773 Err(())
774 }
775}
776
777#[derive(Debug, Deserialize, Serialize)]
778pub struct GlyphAndPosition {
779 pub id: u32,
780 pub point: Point2D<f32>,
781}
782
783#[derive(Deserialize, Serialize)]
784pub struct CanvasFont {
785 pub identifier: FontIdentifier,
787 pub data: Option<FontDataAndIndex>,
790}
791
792impl CanvasFont {
793 pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
794 self.data.clone().or_else(|| match &self.identifier {
795 FontIdentifier::Local(local_font_identifier) => {
796 local_font_identifier.font_data_and_index()
797 },
798 FontIdentifier::Web(_) => None,
799 })
800 }
801}
802
803#[derive(Deserialize, Serialize)]
804pub struct TextRun {
805 pub font: CanvasFont,
806 pub pt_size: f32,
807 pub glyphs_and_positions: Vec<GlyphAndPosition>,
808 pub advance: f32,
809 pub bounds: Rect<f64>,
810}
811
812impl std::fmt::Debug for TextRun {
813 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
814 f.debug_struct("TextRun")
815 .field("glyphs_and_positions", &self.glyphs_and_positions)
816 .field("size", &self.bounds)
817 .finish()
818 }
819}