1use std::default::Default;
6use std::str::FromStr;
7
8use base::Epoch;
9use euclid::Angle;
10use euclid::approxeq::ApproxEq;
11use euclid::default::{Point2D, Rect, Size2D, Transform2D};
12use fonts_traits::{FontDataAndIndex, FontIdentifier};
13use ipc_channel::ipc::IpcSender;
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 #[allow(clippy::too_many_arguments)]
231 pub fn arc(
233 &mut self,
234 x: f64,
235 y: f64,
236 radius: f64,
237 start_angle: f64,
238 end_angle: f64,
239 counterclockwise: bool,
240 ) -> Result<(), IndexSizeError> {
241 self.ellipse(
243 x,
244 y,
245 radius,
246 radius,
247 0.,
248 start_angle,
249 end_angle,
250 counterclockwise,
251 )
252 }
253
254 #[allow(clippy::too_many_arguments)]
255 pub fn ellipse(
257 &mut self,
258 x: f64,
259 y: f64,
260 radius_x: f64,
261 radius_y: f64,
262 rotation_angle: f64,
263 start_angle: f64,
264 end_angle: f64,
265 counterclockwise: bool,
266 ) -> Result<(), IndexSizeError> {
267 if !(x.is_finite() &&
269 y.is_finite() &&
270 radius_x.is_finite() &&
271 radius_y.is_finite() &&
272 rotation_angle.is_finite() &&
273 start_angle.is_finite() &&
274 end_angle.is_finite())
275 {
276 return Ok(());
277 }
278
279 if radius_x < 0.0 || radius_y < 0.0 {
281 return Err(IndexSizeError);
282 }
283
284 let mut start = Angle::radians(start_angle);
285 let mut end = Angle::radians(end_angle);
286
287 if !counterclockwise && start > end + Angle::two_pi() ||
289 counterclockwise && end > start + Angle::two_pi()
290 {
291 start = start.positive();
292 end = end.positive();
293 }
294
295 let sweep = match counterclockwise {
297 true => {
298 if end - start == Angle::two_pi() {
299 -Angle::two_pi()
300 } else if end > start {
301 -(Angle::two_pi() - (end - start))
302 } else {
303 -(start - end)
304 }
305 },
306 false => {
307 if start - end == Angle::two_pi() {
308 Angle::two_pi()
309 } else if start > end {
310 Angle::two_pi() - (start - end)
311 } else {
312 end - start
313 }
314 },
315 };
316
317 let arc = kurbo::Arc::new(
318 (x, y),
319 (radius_x, radius_y),
320 start.radians,
321 sweep.radians,
322 rotation_angle,
323 );
324
325 let mut iter = arc.path_elements(0.01);
326 let kurbo::PathEl::MoveTo(start_point) = iter.next().unwrap() else {
327 unreachable!()
328 };
329
330 self.line_to(start_point.x, start_point.y);
331
332 if sweep.radians.abs() > 1e-3 {
333 self.0.extend(iter);
334 }
335
336 Ok(())
337 }
338
339 pub fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
341 if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) {
343 return;
344 }
345
346 self.0.move_to((x, y));
350 self.0.line_to((x + w, y));
351 self.0.line_to((x + w, y + h));
352 self.0.line_to((x, y + h));
353
354 self.0.close_path();
356
357 self.0.move_to((x, y));
359 }
360
361 pub fn is_point_in_path(&self, x: f64, y: f64, fill_rule: FillRule) -> bool {
363 let p = Point::new(x, y);
364 if !p.is_finite() {
366 return false;
367 }
368
369 let mut path = self.clone();
376 path.close_path();
377 let winding = path.0.winding(p);
378 let is_inside = match fill_rule {
379 FillRule::Nonzero => winding != 0,
380 FillRule::Evenodd => (winding % 2) != 0,
381 };
382 if is_inside {
383 return true;
384 }
385 path.0
387 .segments()
388 .any(|seg| seg.nearest(p, 0.00001).distance_sq < 0.00001)
389 }
390
391 pub fn bounding_box(&self) -> Rect<f64> {
392 self.0.control_box().into()
393 }
394}
395
396#[derive(Clone, Debug, Deserialize, Serialize)]
397pub enum FillRule {
398 Nonzero,
399 Evenodd,
400}
401
402#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
403pub struct CanvasId(pub u64);
404
405#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
406pub struct CompositionOptions {
407 pub alpha: f64,
408 pub composition_operation: CompositionOrBlending,
409}
410
411#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
412pub struct ShadowOptions {
413 pub offset_x: f64,
414 pub offset_y: f64,
415 pub blur: f64,
416 pub color: AbsoluteColor,
417}
418
419impl ShadowOptions {
420 pub fn need_to_draw_shadow(&self) -> bool {
422 self.color.alpha != 0.0 &&
424 (self.offset_x != 0.0 ||
426 self.offset_y != 0.0 ||
427 self.blur != 0.0)
428 }
429}
430
431#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
432pub struct LineOptions {
433 pub width: f64,
434 pub cap_style: LineCapStyle,
435 pub join_style: LineJoinStyle,
436 pub miter_limit: f64,
437 pub dash: Vec<f32>,
438 pub dash_offset: f64,
439}
440
441#[allow(clippy::large_enum_variant)]
442#[derive(Debug, Deserialize, Serialize)]
443pub enum CanvasMsg {
444 Canvas2d(Canvas2dMsg, CanvasId),
445 Recreate(Option<Size2D<u64>>, CanvasId),
446 Close(CanvasId),
447}
448
449#[derive(Debug, Deserialize, Serialize, strum::Display)]
450pub enum Canvas2dMsg {
451 SetImageKey(ImageKey),
452 DrawImage(
453 SharedSnapshot,
454 Rect<f64>,
455 Rect<f64>,
456 bool,
457 ShadowOptions,
458 CompositionOptions,
459 Transform2D<f64>,
460 ),
461 DrawEmptyImage(
462 Size2D<u32>,
463 Rect<f64>,
464 Rect<f64>,
465 ShadowOptions,
466 CompositionOptions,
467 Transform2D<f64>,
468 ),
469 DrawImageInOther(
470 CanvasId,
471 Rect<f64>,
472 Rect<f64>,
473 bool,
474 ShadowOptions,
475 CompositionOptions,
476 Transform2D<f64>,
477 ),
478 ClearRect(Rect<f32>, Transform2D<f64>),
479 ClipPath(Path, FillRule, Transform2D<f64>),
480 PopClips(usize),
481 FillPath(
482 FillOrStrokeStyle,
483 Path,
484 FillRule,
485 ShadowOptions,
486 CompositionOptions,
487 Transform2D<f64>,
488 ),
489 FillText(
490 Rect<f64>,
491 Vec<TextRun>,
492 FillOrStrokeStyle,
493 ShadowOptions,
494 CompositionOptions,
495 Transform2D<f64>,
496 ),
497 StrokeText(
498 Rect<f64>,
499 Vec<TextRun>,
500 FillOrStrokeStyle,
501 LineOptions,
502 ShadowOptions,
503 CompositionOptions,
504 Transform2D<f64>,
505 ),
506 FillRect(
507 Rect<f32>,
508 FillOrStrokeStyle,
509 ShadowOptions,
510 CompositionOptions,
511 Transform2D<f64>,
512 ),
513 GetImageData(Option<Rect<u32>>, IpcSender<SharedSnapshot>),
514 PutImageData(Rect<u32>, SharedSnapshot),
515 StrokeRect(
516 Rect<f32>,
517 FillOrStrokeStyle,
518 LineOptions,
519 ShadowOptions,
520 CompositionOptions,
521 Transform2D<f64>,
522 ),
523 StrokePath(
524 Path,
525 FillOrStrokeStyle,
526 LineOptions,
527 ShadowOptions,
528 CompositionOptions,
529 Transform2D<f64>,
530 ),
531 UpdateImage(Option<Epoch>),
532}
533
534#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
535pub struct CanvasGradientStop {
536 pub offset: f64,
537 pub color: AbsoluteColor,
538}
539
540#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
541pub struct LinearGradientStyle {
542 pub x0: f64,
543 pub y0: f64,
544 pub x1: f64,
545 pub y1: f64,
546 pub stops: Vec<CanvasGradientStop>,
547}
548
549impl LinearGradientStyle {
550 pub fn new(
551 x0: f64,
552 y0: f64,
553 x1: f64,
554 y1: f64,
555 stops: Vec<CanvasGradientStop>,
556 ) -> LinearGradientStyle {
557 LinearGradientStyle {
558 x0,
559 y0,
560 x1,
561 y1,
562 stops,
563 }
564 }
565}
566
567#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
568pub struct RadialGradientStyle {
569 pub x0: f64,
570 pub y0: f64,
571 pub r0: f64,
572 pub x1: f64,
573 pub y1: f64,
574 pub r1: f64,
575 pub stops: Vec<CanvasGradientStop>,
576}
577
578impl RadialGradientStyle {
579 pub fn new(
580 x0: f64,
581 y0: f64,
582 r0: f64,
583 x1: f64,
584 y1: f64,
585 r1: f64,
586 stops: Vec<CanvasGradientStop>,
587 ) -> RadialGradientStyle {
588 RadialGradientStyle {
589 x0,
590 y0,
591 r0,
592 x1,
593 y1,
594 r1,
595 stops,
596 }
597 }
598}
599
600#[derive(Clone, Debug, Deserialize, Serialize)]
601pub struct SurfaceStyle {
602 pub surface_data: SharedSnapshot,
603 pub surface_size: Size2D<u32>,
604 pub repeat_x: bool,
605 pub repeat_y: bool,
606 pub transform: Transform2D<f32>,
607}
608
609impl SurfaceStyle {
610 pub fn new(
611 surface_data: SharedSnapshot,
612 surface_size: Size2D<u32>,
613 repeat_x: bool,
614 repeat_y: bool,
615 transform: Transform2D<f32>,
616 ) -> Self {
617 Self {
618 surface_data,
619 surface_size,
620 repeat_x,
621 repeat_y,
622 transform,
623 }
624 }
625}
626
627#[derive(Clone, Debug, Deserialize, Serialize)]
628pub enum FillOrStrokeStyle {
629 Color(AbsoluteColor),
630 LinearGradient(LinearGradientStyle),
631 RadialGradient(RadialGradientStyle),
632 Surface(SurfaceStyle),
633}
634
635impl FillOrStrokeStyle {
636 pub fn is_zero_size_gradient(&self) -> bool {
637 match self {
638 Self::RadialGradient(pattern) => {
639 let centers_equal = (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1);
640 let radii_equal = pattern.r0 == pattern.r1;
641 (centers_equal && radii_equal) || pattern.stops.is_empty()
642 },
643 Self::LinearGradient(pattern) => {
644 (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1) || pattern.stops.is_empty()
645 },
646 Self::Color(..) | Self::Surface(..) => false,
647 }
648 }
649
650 pub fn x_bound(&self) -> Option<u32> {
651 match self {
652 Self::Surface(pattern) => {
653 if pattern.repeat_x {
654 None
655 } else {
656 Some(pattern.surface_size.width)
657 }
658 },
659 Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
660 }
661 }
662
663 pub fn y_bound(&self) -> Option<u32> {
664 match self {
665 Self::Surface(pattern) => {
666 if pattern.repeat_y {
667 None
668 } else {
669 Some(pattern.surface_size.height)
670 }
671 },
672 Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
673 }
674 }
675}
676
677#[derive(
678 Clone, Copy, Debug, Display, Deserialize, EnumString, MallocSizeOf, PartialEq, Serialize,
679)]
680pub enum LineCapStyle {
681 Butt = 0,
682 Round = 1,
683 Square = 2,
684}
685
686#[derive(
687 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
688)]
689pub enum LineJoinStyle {
690 Round = 0,
691 Bevel = 1,
692 Miter = 2,
693}
694
695#[derive(Clone, Copy, Debug, Deserialize, Display, EnumString, PartialEq, Serialize)]
696#[strum(serialize_all = "kebab-case")]
697pub enum RepetitionStyle {
698 Repeat,
699 RepeatX,
700 RepeatY,
701 NoRepeat,
702}
703
704#[derive(
706 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
707)]
708#[strum(serialize_all = "kebab-case")]
709pub enum CompositionStyle {
710 Clear,
711 Copy,
712 SourceOver,
713 DestinationOver,
714 SourceIn,
715 DestinationIn,
716 SourceOut,
717 DestinationOut,
718 SourceAtop,
719 DestinationAtop,
720 Xor,
721 Lighter,
722 }
725
726#[derive(
728 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
729)]
730#[strum(serialize_all = "kebab-case")]
731pub enum BlendingStyle {
732 Multiply,
734 Screen,
735 Overlay,
736 Darken,
737 Lighten,
738 ColorDodge,
739 ColorBurn,
740 HardLight,
741 SoftLight,
742 Difference,
743 Exclusion,
744 Hue,
745 Saturation,
746 Color,
747 Luminosity,
748}
749
750#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
751pub enum CompositionOrBlending {
752 Composition(CompositionStyle),
753 Blending(BlendingStyle),
754}
755
756impl Default for CompositionOrBlending {
757 fn default() -> CompositionOrBlending {
758 CompositionOrBlending::Composition(CompositionStyle::SourceOver)
759 }
760}
761
762impl FromStr for CompositionOrBlending {
763 type Err = ();
764
765 fn from_str(string: &str) -> Result<CompositionOrBlending, ()> {
766 if let Ok(op) = CompositionStyle::from_str(string) {
767 return Ok(CompositionOrBlending::Composition(op));
768 }
769
770 if let Ok(op) = BlendingStyle::from_str(string) {
771 return Ok(CompositionOrBlending::Blending(op));
772 }
773
774 Err(())
775 }
776}
777
778#[derive(Debug, Deserialize, Serialize)]
779pub struct GlyphAndPosition {
780 pub id: u32,
781 pub point: Point2D<f32>,
782}
783
784#[derive(Deserialize, Serialize)]
785pub struct CanvasFont {
786 pub identifier: FontIdentifier,
788 pub data: Option<FontDataAndIndex>,
791}
792
793impl CanvasFont {
794 pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
795 self.data.clone().or_else(|| match &self.identifier {
796 FontIdentifier::Local(local_font_identifier) => {
797 local_font_identifier.font_data_and_index()
798 },
799 FontIdentifier::Web(_) => None,
800 })
801 }
802}
803
804#[derive(Deserialize, Serialize)]
805pub struct TextRun {
806 pub font: CanvasFont,
807 pub pt_size: f32,
808 pub glyphs_and_positions: Vec<GlyphAndPosition>,
809 pub advance: f32,
810 pub bounds: Rect<f64>,
811}
812
813impl std::fmt::Debug for TextRun {
814 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
815 f.debug_struct("TextRun")
816 .field("glyphs_and_positions", &self.glyphs_and_positions)
817 .field("size", &self.bounds)
818 .finish()
819 }
820}