canvas_traits/
canvas.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// <https://html.spec.whatwg.org/multipage/#ensure-there-is-a-subpath>
48    pub fn ensure_there_is_a_subpath(&mut self, x: f64, y: f64) {
49        // The user agent must check to see if the path has its need new subpath flag set.
50        if self.0.elements().is_empty() {
51            // If it does, then the user agent must create a new subpath with the point (x, y)
52            // as its first (and only) point,
53            // as if the moveTo() method had been called,
54            // and must then unset the path's need new subpath flag.
55            self.0.move_to((x, y));
56        }
57    }
58
59    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath>
60    pub fn close_path(&mut self) {
61        // must do nothing if the object's path has no subpaths
62        if matches!(self.0.elements().last(), None | Some(PathEl::ClosePath)) {
63            return;
64        }
65        // Otherwise, it must mark the last subpath as closed,
66        // create a new subpath whose first point is the same as the previous subpath's first point,
67        // and finally add this new subpath to the path.
68        self.0.close_path();
69    }
70
71    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto>
72    pub fn move_to(&mut self, x: f64, y: f64) {
73        // Step 1. If either of the arguments are infinite or NaN, then return.
74        if !(x.is_finite() && y.is_finite()) {
75            return;
76        }
77
78        // Step 2. Create a new subpath with the specified point as its first (and only) point.
79        self.0.move_to((x, y));
80    }
81
82    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto>
83    pub fn line_to(&mut self, x: f64, y: f64) {
84        // Step 1. If either of the arguments are infinite or NaN, then return.
85        if !(x.is_finite() && y.is_finite()) {
86            return;
87        }
88
89        // Step 2. If the object's path has no subpaths, then ensure there is a subpath for (x, y).
90        self.ensure_there_is_a_subpath(x, y);
91
92        // Step 3. Otherwise, connect the last point in the subpath to the given point (x, y) using a straight line,
93        // and then add the given point (x, y) to the subpath.
94        self.0.line_to((x, y));
95    }
96
97    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto>
98    pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
99        // Step 1. If any of the arguments are infinite or NaN, then return.
100        if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
101            return;
102        }
103
104        // Step 2. Ensure there is a subpath for (cpx, cpy).
105        self.ensure_there_is_a_subpath(cpx, cpy);
106
107        // 3. Connect the last point in the subpath to the given point (x, y)
108        // using a quadratic Bézier curve with control point (cpx, cpy). [BEZIER]
109        // 4. Add the given point (x, y) to the subpath.
110        self.0.quad_to((cpx, cpy), (x, y));
111    }
112
113    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto>
114    pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
115        // Step 1. If any of the arguments are infinite or NaN, then return.
116        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        // Step 2. Ensure there is a subpath for (cp1x, cp1y).
127        self.ensure_there_is_a_subpath(cp1x, cp1y);
128
129        // Step 3. Connect the last point in the subpath to the given point (x, y)
130        // using a cubic Bézier curve with control points (cp1x, cp1y) and (cp2x, cp2y). [BEZIER]
131        // Step 4. Add the point (x, y) to the subpath.
132        self.0.curve_to((cp1x, cp1y), (cp2x, cp2y), (x, y));
133    }
134
135    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto>
136    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        // Step 1. If any of the arguments are infinite or NaN, then return.
145        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        // Step 2. Ensure there is a subpath for (x1, y1).
155        self.ensure_there_is_a_subpath(x1, y1);
156
157        // Step 3. If either radius is negative, then throw an "IndexSizeError" DOMException.
158        if radius < 0.0 {
159            return Err(IndexSizeError);
160        }
161
162        // Step 4. Let the point (x0, y0) be the last point in the subpath.
163        let Point { x: x0, y: y0 } = self.last_point().unwrap();
164
165        // Step 5. If the point (x0, y0) is equal to the point (x1, y1),
166        // or if the point (x1, y1) is equal to the point (x2, y2),
167        // or if radius is zero, then add the point (x1, y1) to the subpath,
168        // and connect that point to the previous point (x0, y0) by a straight line.
169        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        // Step 6. Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2)
175        // all lie on a single straight line, then add the point (x1, y1) to the subpath,
176        // and connect that point to the previous point (x0, y0) by a straight line.
177        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        // Step 7. Otherwise, let The Arc be the shortest arc given by circumference of the circle
184        // that has radius radius, and that has one point tangent to the half-infinite line
185        // that crosses the point (x0, y0) and ends at the point (x1, y1),
186        // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1)
187        // and crosses the point (x2, y2).
188        // The points at which this circle touches these two lines are called the start
189        // and end tangent points respectively.
190        // Connect the point (x0, y0) to the start tangent point by a straight line,
191        // adding the start tangent point to the subpath,
192        // and then connect the start tangent point to the end tangent point by The Arc,
193        // adding the end tangent point to the subpath.
194
195        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        // first tangent point
205        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        // second tangent point
210        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        // arc center and angles
215        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    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arc>
232    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        // ellipse() with both radii are equal and rotation is 0.
242        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    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse>
256    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        // Step 1. If any of the arguments are infinite or NaN, then return.
268        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        // Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
280        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        // Wrap angles mod 2 * PI if necessary
288        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        // Calculate the total arc we're going to sweep.
296        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    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-rect>
340    pub fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
341        // Step 1. If any of the arguments are infinite or NaN, then return.
342        if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) {
343            return;
344        }
345
346        // Step 2. Create a new subpath containing just the four points
347        // (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order,
348        // with those four points connected by straight lines.
349        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        // Step 3. Mark the subpath as closed.
355        self.0.close_path();
356
357        // Step 4. Create a new subpath with the point (x, y) as the only point in the subpath.
358        self.0.move_to((x, y));
359    }
360
361    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath>
362    pub fn is_point_in_path(&self, x: f64, y: f64, fill_rule: FillRule) -> bool {
363        let p = Point::new(x, y);
364        // Step 1. If x or y are infinite or NaN, then return false.
365        if !p.is_finite() {
366            return false;
367        }
368
369        // Step 2. If the point given by the x and y coordinates,
370        // when treated as coordinates in the canvas coordinate space unaffected by the current transformation,
371        // is inside the intended path for path as determined by the fill rule indicated by fillRule,
372        // then return true.
373        // Open subpaths must be implicitly closed when computing the area inside the path,
374        // without affecting the actual subpaths.
375        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        // Points on the path itself must be considered to be inside the path.
386        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    /// <https://html.spec.whatwg.org/multipage/#when-shadows-are-drawn>
421    pub fn need_to_draw_shadow(&self) -> bool {
422        // Shadows are only drawn if the opacity component of the alpha component of the shadow color is nonzero
423        self.color.alpha != 0.0 &&
424        // and either the shadowBlur is nonzero, or the shadowOffsetX is nonzero, or the shadowOffsetY is nonzero.
425            (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/// <https://drafts.fxtf.org/compositing/#compositemode>
705#[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    // PlusDarker,
723    // PlusLighter,
724}
725
726/// <https://drafts.fxtf.org/compositing/#ltblendmodegt>
727#[derive(
728    Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
729)]
730#[strum(serialize_all = "kebab-case")]
731pub enum BlendingStyle {
732    // Normal,
733    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    /// A [`FontIdentifier`] for this [`CanvasFont`], maybe either `Local` or `Web`.
787    pub identifier: FontIdentifier,
788    /// If this font is a web font, this field contains the data for the font. If
789    /// the font is a local font, it will be `None`.
790    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}