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