Skip to main content

servo_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::f64::consts::{FRAC_PI_2, PI};
7use std::str::FromStr;
8
9use euclid::Angle;
10use euclid::approxeq::ApproxEq;
11use euclid::default::{Point2D, Rect, Size2D, Transform2D};
12use fonts_traits::{FontDataAndIndex, FontIdentifier};
13use kurbo::{BezPath, ParamCurveNearest as _, PathEl, Point, Shape, Triangle};
14use malloc_size_of::MallocSizeOf;
15use malloc_size_of_derive::MallocSizeOf;
16use pixels::SharedSnapshot;
17use serde::{Deserialize, Serialize};
18use servo_base::Epoch;
19use servo_base::generic_channel::GenericSender;
20use strum::{Display, EnumString};
21use style::color::AbsoluteColor;
22use webrender_api::ImageKey;
23
24#[derive(Clone, Debug, Default, Deserialize, Serialize)]
25pub struct Path(pub BezPath);
26
27impl MallocSizeOf for Path {
28    fn size_of(&self, _ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
29        std::mem::size_of_val(self.0.elements())
30    }
31}
32
33pub struct IndexSizeError;
34
35#[derive(Clone, Copy, Debug)]
36pub struct RoundRectRadius {
37    pub x: f64,
38    pub y: f64,
39}
40
41pub enum RangeError {
42    /// `radii` was not a list of size one, two, three, or four.
43    InvalidSize,
44    /// A radius was negative.
45    NegativeRadius,
46}
47
48impl Path {
49    pub fn new() -> Self {
50        Self(BezPath::new())
51    }
52
53    pub fn from_svg(s: &str) -> Self {
54        Self(BezPath::from_svg(s).unwrap_or_default())
55    }
56
57    pub fn transform(&mut self, transform: Transform2D<f64>) {
58        self.0.apply_affine(transform.into());
59    }
60
61    /// <https://html.spec.whatwg.org/multipage/#ensure-there-is-a-subpath>
62    pub fn ensure_there_is_a_subpath(&mut self, x: f64, y: f64) {
63        // The user agent must check to see if the path has its need new subpath flag set.
64        if self.0.elements().is_empty() {
65            // If it does, then the user agent must create a new subpath with the point (x, y)
66            // as its first (and only) point,
67            // as if the moveTo() method had been called,
68            // and must then unset the path's need new subpath flag.
69            self.0.move_to((x, y));
70        }
71    }
72
73    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath>
74    pub fn close_path(&mut self) {
75        // must do nothing if the object's path has no subpaths
76        if matches!(self.0.elements().last(), None | Some(PathEl::ClosePath)) {
77            return;
78        }
79        // Otherwise, it must mark the last subpath as closed,
80        // create a new subpath whose first point is the same as the previous subpath's first point,
81        // and finally add this new subpath to the path.
82        self.0.close_path();
83    }
84
85    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto>
86    pub fn move_to(&mut self, x: f64, y: f64) {
87        // Step 1. If either of the arguments are infinite or NaN, then return.
88        if !(x.is_finite() && y.is_finite()) {
89            return;
90        }
91
92        // Step 2. Create a new subpath with the specified point as its first (and only) point.
93        self.0.move_to((x, y));
94    }
95
96    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto>
97    pub fn line_to(&mut self, x: f64, y: f64) {
98        // Step 1. If either of the arguments are infinite or NaN, then return.
99        if !(x.is_finite() && y.is_finite()) {
100            return;
101        }
102
103        // Step 2. If the object's path has no subpaths, then ensure there is a subpath for (x, y).
104        self.ensure_there_is_a_subpath(x, y);
105
106        // Step 3. Otherwise, connect the last point in the subpath to the given point (x, y) using a straight line,
107        // and then add the given point (x, y) to the subpath.
108        self.0.line_to((x, y));
109    }
110
111    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto>
112    pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
113        // Step 1. If any of the arguments are infinite or NaN, then return.
114        if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
115            return;
116        }
117
118        // Step 2. Ensure there is a subpath for (cpx, cpy).
119        self.ensure_there_is_a_subpath(cpx, cpy);
120
121        // 3. Connect the last point in the subpath to the given point (x, y)
122        // using a quadratic Bézier curve with control point (cpx, cpy). [BEZIER]
123        // 4. Add the given point (x, y) to the subpath.
124        self.0.quad_to((cpx, cpy), (x, y));
125    }
126
127    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto>
128    pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
129        // Step 1. If any of the arguments are infinite or NaN, then return.
130        if !(cp1x.is_finite() &&
131            cp1y.is_finite() &&
132            cp2x.is_finite() &&
133            cp2y.is_finite() &&
134            x.is_finite() &&
135            y.is_finite())
136        {
137            return;
138        }
139
140        // Step 2. Ensure there is a subpath for (cp1x, cp1y).
141        self.ensure_there_is_a_subpath(cp1x, cp1y);
142
143        // Step 3. Connect the last point in the subpath to the given point (x, y)
144        // using a cubic Bézier curve with control points (cp1x, cp1y) and (cp2x, cp2y). [BEZIER]
145        // Step 4. Add the point (x, y) to the subpath.
146        self.0.curve_to((cp1x, cp1y), (cp2x, cp2y), (x, y));
147    }
148
149    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto>
150    pub fn arc_to(
151        &mut self,
152        x1: f64,
153        y1: f64,
154        x2: f64,
155        y2: f64,
156        radius: f64,
157    ) -> Result<(), IndexSizeError> {
158        // Step 1. If any of the arguments are infinite or NaN, then return.
159        if !(x1.is_finite() &&
160            y1.is_finite() &&
161            x2.is_finite() &&
162            y2.is_finite() &&
163            radius.is_finite())
164        {
165            return Ok(());
166        }
167
168        // Step 2. Ensure there is a subpath for (x1, y1).
169        self.ensure_there_is_a_subpath(x1, y1);
170
171        // Step 3. If either radius is negative, then throw an "IndexSizeError" DOMException.
172        if radius < 0.0 {
173            return Err(IndexSizeError);
174        }
175
176        // Step 4. Let the point (x0, y0) be the last point in the subpath.
177        let Point { x: x0, y: y0 } = self.last_point().unwrap();
178
179        // Step 5. If the point (x0, y0) is equal to the point (x1, y1),
180        // or if the point (x1, y1) is equal to the point (x2, y2),
181        // or if radius is zero, then add the point (x1, y1) to the subpath,
182        // and connect that point to the previous point (x0, y0) by a straight line.
183        if ((x0, y0) == (x1, y1)) || ((x1, y1) == (x2, y2)) || radius.approx_eq(&0.0) {
184            self.0.line_to((x1, y1));
185            return Ok(());
186        }
187
188        // Step 6. Otherwise, if the points (x0, y0), (x1, y1), and (x2, y2)
189        // all lie on a single straight line, then add the point (x1, y1) to the subpath,
190        // and connect that point to the previous point (x0, y0) by a straight line.
191        let direction = Triangle::from_coords((x0, y0), (x1, y1), (x2, y2)).area();
192        if direction.approx_eq(&0.0) {
193            self.0.line_to((x1, y1));
194            return Ok(());
195        }
196
197        // Step 7. Otherwise, let The Arc be the shortest arc given by circumference of the circle
198        // that has radius radius, and that has one point tangent to the half-infinite line
199        // that crosses the point (x0, y0) and ends at the point (x1, y1),
200        // and that has a different point tangent to the half-infinite line that ends at the point (x1, y1)
201        // and crosses the point (x2, y2).
202        // The points at which this circle touches these two lines are called the start
203        // and end tangent points respectively.
204        // Connect the point (x0, y0) to the start tangent point by a straight line,
205        // adding the start tangent point to the subpath,
206        // and then connect the start tangent point to the end tangent point by The Arc,
207        // adding the end tangent point to the subpath.
208
209        let a2 = (x0 - x1).powi(2) + (y0 - y1).powi(2);
210        let b2 = (x1 - x2).powi(2) + (y1 - y2).powi(2);
211        let d = {
212            let c2 = (x0 - x2).powi(2) + (y0 - y2).powi(2);
213            let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt());
214            let sinx = (1.0 - cosx.powi(2)).sqrt();
215            radius / ((1.0 - cosx) / sinx)
216        };
217
218        // first tangent point
219        let anx = (x1 - x0) / a2.sqrt();
220        let any = (y1 - y0) / a2.sqrt();
221        let tp1 = Point2D::new(x1 - anx * d, y1 - any * d);
222
223        // second tangent point
224        let bnx = (x1 - x2) / b2.sqrt();
225        let bny = (y1 - y2) / b2.sqrt();
226        let tp2 = Point2D::new(x1 - bnx * d, y1 - bny * d);
227
228        // arc center and angles
229        let anticlockwise = direction < 0.0;
230        let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 };
231        let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 };
232        let angle_start = (tp1.y - cy).atan2(tp1.x - cx);
233        let angle_end = (tp2.y - cy).atan2(tp2.x - cx);
234
235        self.0.line_to((tp1.x, tp1.y));
236
237        self.arc(cx, cy, radius, angle_start, angle_end, anticlockwise)
238    }
239
240    pub fn last_point(&mut self) -> Option<Point> {
241        self.0.current_position()
242    }
243
244    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arc>
245    pub fn arc(
246        &mut self,
247        x: f64,
248        y: f64,
249        radius: f64,
250        start_angle: f64,
251        end_angle: f64,
252        counterclockwise: bool,
253    ) -> Result<(), IndexSizeError> {
254        // ellipse() with both radii are equal and rotation is 0.
255        self.ellipse(
256            x,
257            y,
258            radius,
259            radius,
260            0.,
261            start_angle,
262            end_angle,
263            counterclockwise,
264        )
265    }
266
267    #[expect(clippy::too_many_arguments)]
268    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse>
269    pub fn ellipse(
270        &mut self,
271        x: f64,
272        y: f64,
273        radius_x: f64,
274        radius_y: f64,
275        rotation_angle: f64,
276        start_angle: f64,
277        end_angle: f64,
278        counterclockwise: bool,
279    ) -> Result<(), IndexSizeError> {
280        // Step 1. If any of the arguments are infinite or NaN, then return.
281        if !(x.is_finite() &&
282            y.is_finite() &&
283            radius_x.is_finite() &&
284            radius_y.is_finite() &&
285            rotation_angle.is_finite() &&
286            start_angle.is_finite() &&
287            end_angle.is_finite())
288        {
289            return Ok(());
290        }
291
292        // Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException.
293        if radius_x < 0.0 || radius_y < 0.0 {
294            return Err(IndexSizeError);
295        }
296
297        let mut start = Angle::radians(start_angle);
298        let mut end = Angle::radians(end_angle);
299
300        // Wrap angles mod 2 * PI if necessary
301        if !counterclockwise && start > end + Angle::two_pi() ||
302            counterclockwise && end > start + Angle::two_pi()
303        {
304            start = start.positive();
305            end = end.positive();
306        }
307
308        // Calculate the total arc we're going to sweep.
309        let sweep = match counterclockwise {
310            true => {
311                if end - start == Angle::two_pi() {
312                    -Angle::two_pi()
313                } else if end > start {
314                    -(Angle::two_pi() - (end - start))
315                } else {
316                    -(start - end)
317                }
318            },
319            false => {
320                if start - end == Angle::two_pi() {
321                    Angle::two_pi()
322                } else if start > end {
323                    Angle::two_pi() - (start - end)
324                } else {
325                    end - start
326                }
327            },
328        };
329
330        self.append_ellipse_arc(
331            (x, y),
332            (radius_x, radius_y),
333            start.radians,
334            sweep.radians,
335            rotation_angle,
336        );
337
338        Ok(())
339    }
340
341    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-rect>
342    pub fn rect(&mut self, x: f64, y: f64, w: f64, h: f64) {
343        // Step 1. If any of the arguments are infinite or NaN, then return.
344        if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) {
345            return;
346        }
347
348        // Step 2. Create a new subpath containing just the four points
349        // (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order,
350        // with those four points connected by straight lines.
351        self.0.move_to((x, y));
352        self.0.line_to((x + w, y));
353        self.0.line_to((x + w, y + h));
354        self.0.line_to((x, y + h));
355
356        // Step 3. Mark the subpath as closed.
357        self.0.close_path();
358
359        // Step 4. Create a new subpath with the point (x, y) as the only point in the subpath.
360        self.0.move_to((x, y));
361    }
362
363    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-roundrect>
364    pub fn round_rect(
365        &mut self,
366        mut x: f64,
367        mut y: f64,
368        mut w: f64,
369        mut h: f64,
370        radii: &[RoundRectRadius],
371    ) -> Result<(), RangeError> {
372        // Step 1. If any of x, y, w, or h are infinite or NaN, then return.
373        if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) {
374            return Ok(());
375        }
376
377        // Step 3. If radii is not a list of size one, two, three, or four, then throw a
378        // RangeError.
379        if radii.is_empty() || radii.len() > 4 {
380            return Err(RangeError::InvalidSize);
381        }
382
383        // Steps 4 - 5. If any radius is infinite or NaN, then return; if any radius is negative,
384        // then throw a RangeError.
385        for radius in radii {
386            if !(radius.x.is_finite() && radius.y.is_finite()) {
387                return Ok(());
388            }
389            if radius.x < 0.0 || radius.y < 0.0 {
390                return Err(RangeError::NegativeRadius);
391            }
392        }
393        // From now on, radii is called normalizedRadii in spec.
394        let normalized_radii = radii;
395        // Steps 6 - 10. Assign upperLeft, upperRight, lowerRight and lowerLeft.
396        let (mut upper_left, mut upper_right, mut lower_right, mut lower_left) =
397            match normalized_radii {
398                // If normalizedRadii's size is 1, then set upperLeft, upperRight, lowerRight,
399                // and lowerLeft to normalizedRadii[0].
400                [a] => (*a, *a, *a, *a),
401                // If normalizedRadii's size is 2, then set upperLeft and lowerRight to
402                // normalizedRadii[0] and set upperRight and lowerLeft to normalizedRadii[1].
403                [a, b] => (*a, *b, *a, *b),
404                // If normalizedRadii's size is 3, then set upperLeft to normalizedRadii[0],
405                // set upperRight and lowerLeft to normalizedRadii[1],
406                // and set lowerRight to normalizedRadii[2].
407                [a, b, c] => (*a, *b, *c, *b),
408                // If normalizedRadii's size is 4, then set upperLeft to normalizedRadii[0],
409                // set upperRight to normalizedRadii[1], set lowerRight to normalizedRadii[2],
410                // and set lowerLeft to normalizedRadii[3].
411                [a, b, c, d] => (*a, *b, *c, *d),
412                _ => unreachable!(),
413            };
414
415        // Not explicitly stated in steps. See non-normative part of `roundRect` in
416        // <https://html.spec.whatwg.org/multipage/#building-paths>
417        // When w is negative, the rounded rectangle is flipped horizontally, which means that
418        // the radius values that normally apply to the left corners are used on the right and
419        // vice versa. Similarly, when h is negative, the rounded rect is flipped vertically.
420        let (orig_x, orig_y) = (x, y);
421        let counterclockwise = (w < 0.0) != (h < 0.0);
422        use std::mem::swap;
423        if w < 0.0 {
424            swap(&mut upper_left, &mut upper_right);
425            swap(&mut lower_left, &mut lower_right);
426            x += w;
427            w = -w;
428        }
429        if h < 0.0 {
430            swap(&mut upper_left, &mut lower_left);
431            swap(&mut upper_right, &mut lower_right);
432            y += h;
433            h = -h;
434        }
435
436        // Step 11. Corner curves must not overlap. Scale all radii to prevent this.
437        let top = upper_left.x + upper_right.x;
438        let right = upper_right.y + lower_right.y;
439        let bottom = lower_right.x + lower_left.x;
440        let left = upper_left.y + lower_left.y;
441        let scale = (w / top).min(h / right).min(w / bottom).min(h / left);
442        if scale < 1.0 {
443            upper_left.x *= scale;
444            upper_left.y *= scale;
445            upper_right.x *= scale;
446            upper_right.y *= scale;
447            lower_right.x *= scale;
448            lower_right.y *= scale;
449            lower_left.x *= scale;
450            lower_left.y *= scale;
451        }
452
453        // Step 12. Create a new subpath.
454        let mut subpath = Path::new();
455        // Step 12.1. Move to the point (x + upperLeft["x"], y).
456        subpath.0.move_to((x + upper_left.x, y));
457        // Step 12.2. Draw a straight line to the point (x + w − upperRight["x"], y).
458        subpath.0.line_to((x + w - upper_right.x, y));
459        // Step 12.3. Draw an arc to the point (x + w, y + upperRight["y"]).
460        subpath.round_rect_arc(
461            x + w - upper_right.x,
462            y + upper_right.y,
463            upper_right.x,
464            upper_right.y,
465            -FRAC_PI_2,
466        );
467        // Step 12.4. Draw a straight line to the point (x + w, y + h − lowerRight["y"]).
468        subpath.0.line_to((x + w, y + h - lower_right.y));
469        // Step 12.5. Draw an arc to the point (x + w − lowerRight["x"], y + h).
470        subpath.round_rect_arc(
471            x + w - lower_right.x,
472            y + h - lower_right.y,
473            lower_right.x,
474            lower_right.y,
475            0.0,
476        );
477        // Step 12.6. Draw a straight line to the point (x + lowerLeft["x"], y + h).
478        subpath.0.line_to((x + lower_left.x, y + h));
479        // Step 12.7. Draw an arc to the point (x, y + h − lowerLeft["y"]).
480        subpath.round_rect_arc(
481            x + lower_left.x,
482            y + h - lower_left.y,
483            lower_left.x,
484            lower_left.y,
485            FRAC_PI_2,
486        );
487        // Step 12.8. Draw a straight line to the point (x, y + upperLeft["y"]).
488        subpath.0.line_to((x, y + upper_left.y));
489        // Step 12.9. Draw an arc to the point (x + upperLeft["x"], y).
490        subpath.round_rect_arc(
491            x + upper_left.x,
492            y + upper_left.y,
493            upper_left.x,
494            upper_left.y,
495            PI,
496        );
497
498        // Step 13. Mark the subpath as closed.
499        subpath.0.close_path();
500
501        if counterclockwise {
502            subpath.0 = subpath.0.reverse_subpaths();
503        }
504        self.0.extend(subpath.0.elements().iter().cloned());
505
506        // Step 14. Create a new subpath with the original point (x, y) as the only point in the
507        // subpath.
508        self.0.move_to((orig_x, orig_y));
509
510        Ok(())
511    }
512
513    fn append_ellipse_arc(
514        &mut self,
515        center: (f64, f64),
516        radii: (f64, f64),
517        start_angle: f64,
518        sweep: f64,
519        rotation: f64,
520    ) {
521        let arc = kurbo::Arc::new(center, radii, start_angle, sweep, rotation);
522        let mut iter = arc.path_elements(0.01);
523
524        let Some(PathEl::MoveTo(start_point)) = iter.next() else {
525            unreachable!()
526        };
527        self.line_to(start_point.x, start_point.y);
528        if sweep.abs() > 1e-3 {
529            self.0.extend(iter);
530        }
531    }
532
533    /// Appends a quarter arc, sweeping clockwise by [`FRAC_PI_2`], for a `roundRect` corner.
534    #[inline]
535    fn round_rect_arc(&mut self, cx: f64, cy: f64, rx: f64, ry: f64, start_angle: f64) {
536        self.append_ellipse_arc((cx, cy), (rx, ry), start_angle, FRAC_PI_2, 0.0);
537    }
538
539    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ispointinpath>
540    pub fn is_point_in_path(&self, x: f64, y: f64, fill_rule: FillRule) -> bool {
541        let p = Point::new(x, y);
542        // Step 1. If x or y are infinite or NaN, then return false.
543        if !p.is_finite() {
544            return false;
545        }
546
547        // Step 2. If the point given by the x and y coordinates,
548        // when treated as coordinates in the canvas coordinate space unaffected by the current transformation,
549        // is inside the intended path for path as determined by the fill rule indicated by fillRule,
550        // then return true.
551        // Open subpaths must be implicitly closed when computing the area inside the path,
552        // without affecting the actual subpaths.
553        let mut path = self.clone();
554        path.close_path();
555        let winding = path.0.winding(p);
556        let is_inside = match fill_rule {
557            FillRule::Nonzero => winding != 0,
558            FillRule::Evenodd => (winding % 2) != 0,
559        };
560        if is_inside {
561            return true;
562        }
563        // Points on the path itself must be considered to be inside the path.
564        path.0
565            .segments()
566            .any(|seg| seg.nearest(p, 0.00001).distance_sq < 0.00001)
567    }
568
569    pub fn bounding_box(&self) -> Rect<f64> {
570        self.0.control_box().into()
571    }
572}
573
574#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
575pub enum FillRule {
576    Nonzero,
577    Evenodd,
578}
579
580#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
581pub struct CanvasId(pub u64);
582
583#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
584pub struct CompositionOptions {
585    pub alpha: f64,
586    pub composition_operation: CompositionOrBlending,
587}
588
589#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
590pub struct ShadowOptions {
591    pub offset_x: f64,
592    pub offset_y: f64,
593    pub blur: f64,
594    pub color: AbsoluteColor,
595}
596
597impl ShadowOptions {
598    /// <https://html.spec.whatwg.org/multipage/#when-shadows-are-drawn>
599    pub fn need_to_draw_shadow(&self) -> bool {
600        // Shadows are only drawn if the opacity component of the alpha component of the shadow color is nonzero
601        self.color.alpha != 0.0 &&
602        // and either the shadowBlur is nonzero, or the shadowOffsetX is nonzero, or the shadowOffsetY is nonzero.
603            (self.offset_x != 0.0 ||
604                self.offset_y != 0.0 ||
605                self.blur != 0.0)
606    }
607}
608
609#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
610pub struct LineOptions {
611    pub width: f64,
612    pub cap_style: LineCapStyle,
613    pub join_style: LineJoinStyle,
614    pub miter_limit: f64,
615    pub dash: Vec<f32>,
616    pub dash_offset: f64,
617}
618
619pub type CanvasMsg = (CanvasId, CanvasCommand);
620
621#[derive(Debug, Deserialize, Serialize, Display, MallocSizeOf)]
622pub enum CanvasCommand {
623    /// This is used for resizing (when size is provided) or just clearing the canvas (when size is `None`).
624    Recreate(Option<Size2D<u64>>),
625    /// Destroy the canvas (its id will be invalidated).
626    Destroy,
627    SetImageKey(ImageKey),
628    DrawImage(
629        SharedSnapshot,
630        Rect<f64>,
631        Rect<f64>,
632        bool,
633        ShadowOptions,
634        CompositionOptions,
635        Transform2D<f64>,
636    ),
637    DrawEmptyImage(
638        Size2D<u32>,
639        Rect<f64>,
640        Rect<f64>,
641        ShadowOptions,
642        CompositionOptions,
643        Transform2D<f64>,
644    ),
645    DrawImageInOther(
646        CanvasId,
647        Rect<f64>,
648        Rect<f64>,
649        bool,
650        ShadowOptions,
651        CompositionOptions,
652        Transform2D<f64>,
653    ),
654    ClearRect(Rect<f32>, Transform2D<f64>),
655    ClipPath(Path, FillRule, Transform2D<f64>),
656    PopClips(usize),
657    FillPath(
658        FillOrStrokeStyle,
659        Path,
660        FillRule,
661        ShadowOptions,
662        CompositionOptions,
663        Transform2D<f64>,
664    ),
665    FillText(
666        Rect<f64>,
667        Vec<TextRun>,
668        FillOrStrokeStyle,
669        ShadowOptions,
670        CompositionOptions,
671        Transform2D<f64>,
672    ),
673    StrokeText(
674        Rect<f64>,
675        Vec<TextRun>,
676        FillOrStrokeStyle,
677        LineOptions,
678        ShadowOptions,
679        CompositionOptions,
680        Transform2D<f64>,
681    ),
682    FillRect(
683        Rect<f32>,
684        FillOrStrokeStyle,
685        ShadowOptions,
686        CompositionOptions,
687        Transform2D<f64>,
688    ),
689    GetImageData(Option<Rect<u32>>, GenericSender<SharedSnapshot>),
690    PutImageData(Rect<u32>, SharedSnapshot),
691    StrokeRect(
692        Rect<f32>,
693        FillOrStrokeStyle,
694        LineOptions,
695        ShadowOptions,
696        CompositionOptions,
697        Transform2D<f64>,
698    ),
699    StrokePath(
700        Path,
701        FillOrStrokeStyle,
702        LineOptions,
703        ShadowOptions,
704        CompositionOptions,
705        Transform2D<f64>,
706    ),
707    UpdateImage(Option<Epoch>),
708    ProcessBatchMessages(Vec<CanvasCommand>),
709}
710
711#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
712pub struct CanvasGradientStop {
713    pub offset: f64,
714    pub color: AbsoluteColor,
715}
716
717#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
718pub struct LinearGradientStyle {
719    pub x0: f64,
720    pub y0: f64,
721    pub x1: f64,
722    pub y1: f64,
723    pub stops: Vec<CanvasGradientStop>,
724}
725
726impl LinearGradientStyle {
727    pub fn new(
728        x0: f64,
729        y0: f64,
730        x1: f64,
731        y1: f64,
732        stops: Vec<CanvasGradientStop>,
733    ) -> LinearGradientStyle {
734        LinearGradientStyle {
735            x0,
736            y0,
737            x1,
738            y1,
739            stops,
740        }
741    }
742}
743
744#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
745pub struct RadialGradientStyle {
746    pub x0: f64,
747    pub y0: f64,
748    pub r0: f64,
749    pub x1: f64,
750    pub y1: f64,
751    pub r1: f64,
752    pub stops: Vec<CanvasGradientStop>,
753}
754
755impl RadialGradientStyle {
756    pub fn new(
757        x0: f64,
758        y0: f64,
759        r0: f64,
760        x1: f64,
761        y1: f64,
762        r1: f64,
763        stops: Vec<CanvasGradientStop>,
764    ) -> RadialGradientStyle {
765        RadialGradientStyle {
766            x0,
767            y0,
768            r0,
769            x1,
770            y1,
771            r1,
772            stops,
773        }
774    }
775}
776
777#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
778pub struct SurfaceStyle {
779    pub surface_data: SharedSnapshot,
780    pub surface_size: Size2D<u32>,
781    pub repeat_x: bool,
782    pub repeat_y: bool,
783    pub transform: Transform2D<f32>,
784}
785
786impl SurfaceStyle {
787    pub fn new(
788        surface_data: SharedSnapshot,
789        surface_size: Size2D<u32>,
790        repeat_x: bool,
791        repeat_y: bool,
792        transform: Transform2D<f32>,
793    ) -> Self {
794        Self {
795            surface_data,
796            surface_size,
797            repeat_x,
798            repeat_y,
799            transform,
800        }
801    }
802}
803
804#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
805pub enum FillOrStrokeStyle {
806    Color(AbsoluteColor),
807    LinearGradient(LinearGradientStyle),
808    RadialGradient(RadialGradientStyle),
809    Surface(SurfaceStyle),
810}
811
812impl FillOrStrokeStyle {
813    pub fn is_zero_size_gradient(&self) -> bool {
814        match self {
815            Self::RadialGradient(pattern) => {
816                let centers_equal = (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1);
817                let radii_equal = pattern.r0 == pattern.r1;
818                (centers_equal && radii_equal) || pattern.stops.is_empty()
819            },
820            Self::LinearGradient(pattern) => {
821                (pattern.x0, pattern.y0) == (pattern.x1, pattern.y1) || pattern.stops.is_empty()
822            },
823            Self::Color(..) | Self::Surface(..) => false,
824        }
825    }
826
827    pub fn x_bound(&self) -> Option<u32> {
828        match self {
829            Self::Surface(pattern) => {
830                if pattern.repeat_x {
831                    None
832                } else {
833                    Some(pattern.surface_size.width)
834                }
835            },
836            Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
837        }
838    }
839
840    pub fn y_bound(&self) -> Option<u32> {
841        match self {
842            Self::Surface(pattern) => {
843                if pattern.repeat_y {
844                    None
845                } else {
846                    Some(pattern.surface_size.height)
847                }
848            },
849            Self::Color(..) | Self::LinearGradient(..) | Self::RadialGradient(..) => None,
850        }
851    }
852}
853
854#[derive(
855    Clone, Copy, Debug, Display, Deserialize, EnumString, MallocSizeOf, PartialEq, Serialize,
856)]
857pub enum LineCapStyle {
858    Butt = 0,
859    Round = 1,
860    Square = 2,
861}
862
863#[derive(
864    Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
865)]
866pub enum LineJoinStyle {
867    Round = 0,
868    Bevel = 1,
869    Miter = 2,
870}
871
872#[derive(Clone, Copy, Debug, Deserialize, Display, EnumString, PartialEq, Serialize)]
873#[strum(serialize_all = "kebab-case")]
874pub enum RepetitionStyle {
875    Repeat,
876    RepeatX,
877    RepeatY,
878    NoRepeat,
879}
880
881/// <https://drafts.csswg.org/compositing/#compositemode>
882#[derive(
883    Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
884)]
885#[strum(serialize_all = "kebab-case")]
886pub enum CompositionStyle {
887    Clear,
888    Copy,
889    SourceOver,
890    DestinationOver,
891    SourceIn,
892    DestinationIn,
893    SourceOut,
894    DestinationOut,
895    SourceAtop,
896    DestinationAtop,
897    Xor,
898    Lighter,
899    // PlusDarker,
900    // PlusLighter,
901}
902
903/// <https://drafts.csswg.org/compositing/#ltblendmodegt>
904#[derive(
905    Clone, Copy, Debug, Deserialize, Display, EnumString, MallocSizeOf, PartialEq, Serialize,
906)]
907#[strum(serialize_all = "kebab-case")]
908pub enum BlendingStyle {
909    // Normal,
910    Multiply,
911    Screen,
912    Overlay,
913    Darken,
914    Lighten,
915    ColorDodge,
916    ColorBurn,
917    HardLight,
918    SoftLight,
919    Difference,
920    Exclusion,
921    Hue,
922    Saturation,
923    Color,
924    Luminosity,
925}
926
927#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
928pub enum CompositionOrBlending {
929    Composition(CompositionStyle),
930    Blending(BlendingStyle),
931}
932
933impl Default for CompositionOrBlending {
934    fn default() -> CompositionOrBlending {
935        CompositionOrBlending::Composition(CompositionStyle::SourceOver)
936    }
937}
938
939impl FromStr for CompositionOrBlending {
940    type Err = ();
941
942    fn from_str(string: &str) -> Result<CompositionOrBlending, ()> {
943        if let Ok(op) = CompositionStyle::from_str(string) {
944            return Ok(CompositionOrBlending::Composition(op));
945        }
946
947        if let Ok(op) = BlendingStyle::from_str(string) {
948            return Ok(CompositionOrBlending::Blending(op));
949        }
950
951        Err(())
952    }
953}
954
955#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
956pub struct GlyphAndPosition {
957    pub id: u32,
958    pub point: Point2D<f32>,
959}
960
961#[derive(Deserialize, Serialize, MallocSizeOf)]
962pub struct CanvasFont {
963    /// A [`FontIdentifier`] for this [`CanvasFont`], maybe either `Local` or `Web`.
964    pub identifier: FontIdentifier,
965    /// If this font is a web font, this field contains the data for the font. If
966    /// the font is a local font, it will be `None`.
967    pub data: Option<FontDataAndIndex>,
968}
969
970impl CanvasFont {
971    pub fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
972        match &self.identifier {
973            FontIdentifier::Local(local_font_identifier) => {
974                local_font_identifier.font_data_and_index()
975            },
976            FontIdentifier::Web(_) => self.data.clone(),
977            FontIdentifier::ArrayBuffer(_) => self.data.clone(),
978        }
979    }
980}
981
982#[derive(Deserialize, Serialize, MallocSizeOf)]
983pub struct TextRun {
984    pub font: CanvasFont,
985    pub pt_size: f32,
986    pub glyphs_and_positions: Vec<GlyphAndPosition>,
987    pub advance: f32,
988    pub bounds: Rect<f64>,
989}
990
991impl std::fmt::Debug for TextRun {
992    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
993        f.debug_struct("TextRun")
994            .field("glyphs_and_positions", &self.glyphs_and_positions)
995            .field("size", &self.bounds)
996            .finish()
997    }
998}