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::IpcSnapshot;
18use serde::{Deserialize, Serialize};
19use strum::{Display, EnumString};
20use style::color::AbsoluteColor;
21
22#[derive(Clone, Debug, Default, Deserialize, Serialize)]
23pub struct Path(pub BezPath);
24
25impl MallocSizeOf for Path {
26 fn size_of(&self, _ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
27 std::mem::size_of_val(self.0.elements())
28 }
29}
30
31pub struct IndexSizeError;
32
33impl Path {
34 pub fn new() -> Self {
35 Self(BezPath::new())
36 }
37
38 pub fn from_svg(s: &str) -> Self {
39 Self(BezPath::from_svg(s).unwrap_or_default())
40 }
41
42 pub fn transform(&mut self, transform: Transform2D<f64>) {
43 self.0.apply_affine(transform.into());
44 }
45
46 pub fn ensure_there_is_a_subpath(&mut self, x: f64, y: f64) {
48 if self.0.elements().is_empty() {
50 self.0.move_to((x, y));
55 }
56 }
57
58 pub fn close_path(&mut self) {
60 if matches!(self.0.elements().last(), None | Some(PathEl::ClosePath)) {
62 return;
63 }
64 self.0.close_path();
68 }
69
70 pub fn move_to(&mut self, x: f64, y: f64) {
72 if !(x.is_finite() && y.is_finite()) {
74 return;
75 }
76
77 self.0.move_to((x, y));
79 }
80
81 pub fn line_to(&mut self, x: f64, y: f64) {
83 if !(x.is_finite() && y.is_finite()) {
85 return;
86 }
87
88 self.ensure_there_is_a_subpath(x, y);
90
91 self.0.line_to((x, y));
94 }
95
96 pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
98 if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
100 return;
101 }
102
103 self.ensure_there_is_a_subpath(cpx, cpy);
105
106 self.0.quad_to((cpx, cpy), (x, y));
110 }
111
112 pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
114 if !(cp1x.is_finite() &&
116 cp1y.is_finite() &&
117 cp2x.is_finite() &&
118 cp2y.is_finite() &&
119 x.is_finite() &&
120 y.is_finite())
121 {
122 return;
123 }
124
125 self.ensure_there_is_a_subpath(cp1x, cp1y);
127
128 self.0.curve_to((cp1x, cp1y), (cp2x, cp2y), (x, y));
132 }
133
134 pub fn arc_to(
136 &mut self,
137 x1: f64,
138 y1: f64,
139 x2: f64,
140 y2: f64,
141 radius: f64,
142 ) -> Result<(), IndexSizeError> {
143 if !(x1.is_finite() &&
145 y1.is_finite() &&
146 x2.is_finite() &&
147 y2.is_finite() &&
148 radius.is_finite())
149 {
150 return Ok(());
151 }
152
153 self.ensure_there_is_a_subpath(x1, y1);
155
156 if radius < 0.0 {
158 return Err(IndexSizeError);
159 }
160
161 let Point { x: x0, y: y0 } = self.last_point().unwrap();
163
164 if ((x0, y0) == (x1, y1)) || ((x1, y1) == (x2, y2)) || radius.approx_eq(&0.0) {
169 self.0.line_to((x1, y1));
170 return Ok(());
171 }
172
173 let direction = Triangle::from_coords((x0, y0), (x1, y1), (x2, y2)).area();
177 if direction.approx_eq(&0.0) {
178 self.0.line_to((x1, y1));
179 return Ok(());
180 }
181
182 let a2 = (x0 - x1).powi(2) + (y0 - y1).powi(2);
195 let b2 = (x1 - x2).powi(2) + (y1 - y2).powi(2);
196 let d = {
197 let c2 = (x0 - x2).powi(2) + (y0 - y2).powi(2);
198 let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt());
199 let sinx = (1.0 - cosx.powi(2)).sqrt();
200 radius / ((1.0 - cosx) / sinx)
201 };
202
203 let anx = (x1 - x0) / a2.sqrt();
205 let any = (y1 - y0) / a2.sqrt();
206 let tp1 = Point2D::new(x1 - anx * d, y1 - any * d);
207
208 let bnx = (x1 - x2) / b2.sqrt();
210 let bny = (y1 - y2) / b2.sqrt();
211 let tp2 = Point2D::new(x1 - bnx * d, y1 - bny * d);
212
213 let anticlockwise = direction < 0.0;
215 let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 };
216 let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 };
217 let angle_start = (tp1.y - cy).atan2(tp1.x - cx);
218 let angle_end = (tp2.y - cy).atan2(tp2.x - cx);
219
220 self.0.line_to((tp1.x, tp1.y));
221
222 self.arc(cx, cy, radius, angle_start, angle_end, anticlockwise)
223 }
224
225 pub fn last_point(&mut self) -> Option<Point> {
226 self.0.current_position()
227 }
228
229 #[allow(clippy::too_many_arguments)]
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 #[allow(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#[allow(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, strum::Display)]
449pub enum Canvas2dMsg {
450 DrawImage(
451 IpcSnapshot,
452 Rect<f64>,
453 Rect<f64>,
454 bool,
455 ShadowOptions,
456 CompositionOptions,
457 Transform2D<f64>,
458 ),
459 DrawEmptyImage(
460 Size2D<u32>,
461 Rect<f64>,
462 Rect<f64>,
463 ShadowOptions,
464 CompositionOptions,
465 Transform2D<f64>,
466 ),
467 DrawImageInOther(
468 CanvasId,
469 Rect<f64>,
470 Rect<f64>,
471 bool,
472 ShadowOptions,
473 CompositionOptions,
474 Transform2D<f64>,
475 ),
476 ClearRect(Rect<f32>, Transform2D<f64>),
477 ClipPath(Path, FillRule, Transform2D<f64>),
478 PopClips(usize),
479 FillPath(
480 FillOrStrokeStyle,
481 Path,
482 FillRule,
483 ShadowOptions,
484 CompositionOptions,
485 Transform2D<f64>,
486 ),
487 FillText(
488 Rect<f64>,
489 Vec<TextRun>,
490 FillOrStrokeStyle,
491 ShadowOptions,
492 CompositionOptions,
493 Transform2D<f64>,
494 ),
495 StrokeText(
496 Rect<f64>,
497 Vec<TextRun>,
498 FillOrStrokeStyle,
499 LineOptions,
500 ShadowOptions,
501 CompositionOptions,
502 Transform2D<f64>,
503 ),
504 FillRect(
505 Rect<f32>,
506 FillOrStrokeStyle,
507 ShadowOptions,
508 CompositionOptions,
509 Transform2D<f64>,
510 ),
511 GetImageData(Option<Rect<u32>>, IpcSender<IpcSnapshot>),
512 PutImageData(Rect<u32>, IpcSnapshot),
513 StrokeRect(
514 Rect<f32>,
515 FillOrStrokeStyle,
516 LineOptions,
517 ShadowOptions,
518 CompositionOptions,
519 Transform2D<f64>,
520 ),
521 StrokePath(
522 Path,
523 FillOrStrokeStyle,
524 LineOptions,
525 ShadowOptions,
526 CompositionOptions,
527 Transform2D<f64>,
528 ),
529 UpdateImage(Option<Epoch>),
530}
531
532#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
533pub struct CanvasGradientStop {
534 pub offset: f64,
535 pub color: AbsoluteColor,
536}
537
538#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
539pub struct LinearGradientStyle {
540 pub x0: f64,
541 pub y0: f64,
542 pub x1: f64,
543 pub y1: f64,
544 pub stops: Vec<CanvasGradientStop>,
545}
546
547impl LinearGradientStyle {
548 pub fn new(
549 x0: f64,
550 y0: f64,
551 x1: f64,
552 y1: f64,
553 stops: Vec<CanvasGradientStop>,
554 ) -> LinearGradientStyle {
555 LinearGradientStyle {
556 x0,
557 y0,
558 x1,
559 y1,
560 stops,
561 }
562 }
563}
564
565#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
566pub struct RadialGradientStyle {
567 pub x0: f64,
568 pub y0: f64,
569 pub r0: f64,
570 pub x1: f64,
571 pub y1: f64,
572 pub r1: f64,
573 pub stops: Vec<CanvasGradientStop>,
574}
575
576impl RadialGradientStyle {
577 pub fn new(
578 x0: f64,
579 y0: f64,
580 r0: f64,
581 x1: f64,
582 y1: f64,
583 r1: f64,
584 stops: Vec<CanvasGradientStop>,
585 ) -> RadialGradientStyle {
586 RadialGradientStyle {
587 x0,
588 y0,
589 r0,
590 x1,
591 y1,
592 r1,
593 stops,
594 }
595 }
596}
597
598#[derive(Clone, Debug, Deserialize, Serialize)]
599pub struct SurfaceStyle {
600 pub surface_data: IpcSnapshot,
601 pub surface_size: Size2D<u32>,
602 pub repeat_x: bool,
603 pub repeat_y: bool,
604 pub transform: Transform2D<f32>,
605}
606
607impl SurfaceStyle {
608 pub fn new(
609 surface_data: IpcSnapshot,
610 surface_size: Size2D<u32>,
611 repeat_x: bool,
612 repeat_y: bool,
613 transform: Transform2D<f32>,
614 ) -> Self {
615 Self {
616 surface_data,
617 surface_size,
618 repeat_x,
619 repeat_y,
620 transform,
621 }
622 }
623}
624
625#[derive(Clone, Debug, Deserialize, Serialize)]
626pub enum FillOrStrokeStyle {
627 Color(AbsoluteColor),
628 LinearGradient(LinearGradientStyle),
629 RadialGradient(RadialGradientStyle),
630 Surface(SurfaceStyle),
631}
632
633impl FillOrStrokeStyle {
634 pub fn is_zero_size_gradient(&self) -> bool {
635 match self {
636 Self::RadialGradient(pattern) => {
637 let centers_equal = (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1);
638 let radii_equal = pattern.r0 == pattern.r1;
639 (centers_equal && radii_equal) || pattern.stops.is_empty()
640 },
641 Self::LinearGradient(pattern) => {
642 (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1) || pattern.stops.is_empty()
643 },
644 Self::Color(..) | Self::Surface(..) => false,
645 }
646 }
647
648 pub fn x_bound(&self) -> Option<u32> {
649 match self {
650 Self::Surface(pattern) => {
651 if pattern.repeat_x {
652 None
653 } else {
654 Some(pattern.surface_size.width)
655 }
656 },
657 Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
658 }
659 }
660
661 pub fn y_bound(&self) -> Option<u32> {
662 match self {
663 Self::Surface(pattern) => {
664 if pattern.repeat_y {
665 None
666 } else {
667 Some(pattern.surface_size.height)
668 }
669 },
670 Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
671 }
672 }
673}
674
675#[derive(
676 Clone, Copy, Debug, Display, Deserialize, EnumString, MallocSizeOf, PartialEq, Serialize,
677)]
678pub enum LineCapStyle {
679 Butt = 0,
680 Round = 1,
681 Square = 2,
682}
683
684#[derive(
685 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
686)]
687pub enum LineJoinStyle {
688 Round = 0,
689 Bevel = 1,
690 Miter = 2,
691}
692
693#[derive(Clone, Copy, Debug, Deserialize, Display, EnumString, PartialEq, Serialize)]
694#[strum(serialize_all = "kebab-case")]
695pub enum RepetitionStyle {
696 Repeat,
697 RepeatX,
698 RepeatY,
699 NoRepeat,
700}
701
702#[derive(
704 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
705)]
706#[strum(serialize_all = "kebab-case")]
707pub enum CompositionStyle {
708 Clear,
709 Copy,
710 SourceOver,
711 DestinationOver,
712 SourceIn,
713 DestinationIn,
714 SourceOut,
715 DestinationOut,
716 SourceAtop,
717 DestinationAtop,
718 Xor,
719 Lighter,
720 }
723
724#[derive(
726 Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
727)]
728#[strum(serialize_all = "kebab-case")]
729pub enum BlendingStyle {
730 Multiply,
732 Screen,
733 Overlay,
734 Darken,
735 Lighten,
736 ColorDodge,
737 ColorBurn,
738 HardLight,
739 SoftLight,
740 Difference,
741 Exclusion,
742 Hue,
743 Saturation,
744 Color,
745 Luminosity,
746}
747
748#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
749pub enum CompositionOrBlending {
750 Composition(CompositionStyle),
751 Blending(BlendingStyle),
752}
753
754impl Default for CompositionOrBlending {
755 fn default() -> CompositionOrBlending {
756 CompositionOrBlending::Composition(CompositionStyle::SourceOver)
757 }
758}
759
760impl FromStr for CompositionOrBlending {
761 type Err = ();
762
763 fn from_str(string: &str) -> Result<CompositionOrBlending, ()> {
764 if let Ok(op) = CompositionStyle::from_str(string) {
765 return Ok(CompositionOrBlending::Composition(op));
766 }
767
768 if let Ok(op) = BlendingStyle::from_str(string) {
769 return Ok(CompositionOrBlending::Blending(op));
770 }
771
772 Err(())
773 }
774}
775
776#[derive(Debug, Deserialize, Serialize)]
777pub struct GlyphAndPosition {
778 pub id: u32,
779 pub point: Point2D<f32>,
780}
781
782#[derive(Deserialize, Serialize)]
783pub struct CanvasFont {
784 pub identifier: FontIdentifier,
786 pub data: Option<FontDataAndIndex>,
789}
790
791impl CanvasFont {
792 pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
793 self.data.clone().or_else(|| match &self.identifier {
794 FontIdentifier::Local(local_font_identifier) => {
795 local_font_identifier.font_data_and_index()
796 },
797 FontIdentifier::Web(_) => None,
798 })
799 }
800}
801
802#[derive(Deserialize, Serialize)]
803pub struct TextRun {
804 pub font: CanvasFont,
805 pub pt_size: f32,
806 pub glyphs_and_positions: Vec<GlyphAndPosition>,
807 pub advance: f32,
808 pub bounds: Rect<f64>,
809}
810
811impl std::fmt::Debug for TextRun {
812 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
813 f.debug_struct("TextRun")
814 .field("glyphs_and_positions", &self.glyphs_and_positions)
815 .field("size", &self.bounds)
816 .finish()
817 }
818}