1use std::default::Default;
6use std::str::FromStr;
7
8use euclid::Angle;
9use euclid::approxeq::ApproxEq;
10use euclid::default::{Point2D, Rect, Size2D, Transform2D};
11use fonts_traits::{FontDataAndIndex, FontIdentifier};
12use kurbo::{BezPath, ParamCurveNearest as _, PathEl, Point, Shape, Triangle};
13use malloc_size_of::MallocSizeOf;
14use malloc_size_of_derive::MallocSizeOf;
15use pixels::SharedSnapshot;
16use serde::{Deserialize, Serialize};
17use servo_base::Epoch;
18use servo_base::generic_channel::GenericSender;
19use strum::{Display, EnumString};
20use style::color::AbsoluteColor;
21use webrender_api::ImageKey;
22
23#[derive(Clone, Debug, Default, Deserialize, Serialize)]
24pub struct Path(pub BezPath);
25
26impl MallocSizeOf for Path {
27 fn size_of(&self, _ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
28 std::mem::size_of_val(self.0.elements())
29 }
30}
31
32pub struct IndexSizeError;
33
34impl Path {
35 pub fn new() -> Self {
36 Self(BezPath::new())
37 }
38
39 pub fn from_svg(s: &str) -> Self {
40 Self(BezPath::from_svg(s).unwrap_or_default())
41 }
42
43 pub fn transform(&mut self, transform: Transform2D<f64>) {
44 self.0.apply_affine(transform.into());
45 }
46
47 pub fn ensure_there_is_a_subpath(&mut self, x: f64, y: f64) {
49 if self.0.elements().is_empty() {
51 self.0.move_to((x, y));
56 }
57 }
58
59 pub fn close_path(&mut self) {
61 if matches!(self.0.elements().last(), None | Some(PathEl::ClosePath)) {
63 return;
64 }
65 self.0.close_path();
69 }
70
71 pub fn move_to(&mut self, x: f64, y: f64) {
73 if !(x.is_finite() && y.is_finite()) {
75 return;
76 }
77
78 self.0.move_to((x, y));
80 }
81
82 pub fn line_to(&mut self, x: f64, y: f64) {
84 if !(x.is_finite() && y.is_finite()) {
86 return;
87 }
88
89 self.ensure_there_is_a_subpath(x, y);
91
92 self.0.line_to((x, y));
95 }
96
97 pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
99 if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) {
101 return;
102 }
103
104 self.ensure_there_is_a_subpath(cpx, cpy);
106
107 self.0.quad_to((cpx, cpy), (x, y));
111 }
112
113 pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
115 if !(cp1x.is_finite() &&
117 cp1y.is_finite() &&
118 cp2x.is_finite() &&
119 cp2y.is_finite() &&
120 x.is_finite() &&
121 y.is_finite())
122 {
123 return;
124 }
125
126 self.ensure_there_is_a_subpath(cp1x, cp1y);
128
129 self.0.curve_to((cp1x, cp1y), (cp2x, cp2y), (x, y));
133 }
134
135 pub fn arc_to(
137 &mut self,
138 x1: f64,
139 y1: f64,
140 x2: f64,
141 y2: f64,
142 radius: f64,
143 ) -> Result<(), IndexSizeError> {
144 if !(x1.is_finite() &&
146 y1.is_finite() &&
147 x2.is_finite() &&
148 y2.is_finite() &&
149 radius.is_finite())
150 {
151 return Ok(());
152 }
153
154 self.ensure_there_is_a_subpath(x1, y1);
156
157 if radius < 0.0 {
159 return Err(IndexSizeError);
160 }
161
162 let Point { x: x0, y: y0 } = self.last_point().unwrap();
164
165 if ((x0, y0) == (x1, y1)) || ((x1, y1) == (x2, y2)) || radius.approx_eq(&0.0) {
170 self.0.line_to((x1, y1));
171 return Ok(());
172 }
173
174 let direction = Triangle::from_coords((x0, y0), (x1, y1), (x2, y2)).area();
178 if direction.approx_eq(&0.0) {
179 self.0.line_to((x1, y1));
180 return Ok(());
181 }
182
183 let a2 = (x0 - x1).powi(2) + (y0 - y1).powi(2);
196 let b2 = (x1 - x2).powi(2) + (y1 - y2).powi(2);
197 let d = {
198 let c2 = (x0 - x2).powi(2) + (y0 - y2).powi(2);
199 let cosx = (a2 + b2 - c2) / (2.0 * (a2 * b2).sqrt());
200 let sinx = (1.0 - cosx.powi(2)).sqrt();
201 radius / ((1.0 - cosx) / sinx)
202 };
203
204 let anx = (x1 - x0) / a2.sqrt();
206 let any = (y1 - y0) / a2.sqrt();
207 let tp1 = Point2D::new(x1 - anx * d, y1 - any * d);
208
209 let bnx = (x1 - x2) / b2.sqrt();
211 let bny = (y1 - y2) / b2.sqrt();
212 let tp2 = Point2D::new(x1 - bnx * d, y1 - bny * d);
213
214 let anticlockwise = direction < 0.0;
216 let cx = tp1.x + any * radius * if anticlockwise { 1.0 } else { -1.0 };
217 let cy = tp1.y - anx * radius * if anticlockwise { 1.0 } else { -1.0 };
218 let angle_start = (tp1.y - cy).atan2(tp1.x - cx);
219 let angle_end = (tp2.y - cy).atan2(tp2.x - cx);
220
221 self.0.line_to((tp1.x, tp1.y));
222
223 self.arc(cx, cy, radius, angle_start, angle_end, anticlockwise)
224 }
225
226 pub fn last_point(&mut self) -> Option<Point> {
227 self.0.current_position()
228 }
229
230 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 #[expect(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
440pub type CanvasMsg = (CanvasId, CanvasCommand);
441
442#[derive(Debug, Deserialize, Serialize, Display)]
443pub enum CanvasCommand {
444 Recreate(Option<Size2D<u64>>),
446 Destroy,
448 SetImageKey(ImageKey),
449 DrawImage(
450 SharedSnapshot,
451 Rect<f64>,
452 Rect<f64>,
453 bool,
454 ShadowOptions,
455 CompositionOptions,
456 Transform2D<f64>,
457 ),
458 DrawEmptyImage(
459 Size2D<u32>,
460 Rect<f64>,
461 Rect<f64>,
462 ShadowOptions,
463 CompositionOptions,
464 Transform2D<f64>,
465 ),
466 DrawImageInOther(
467 CanvasId,
468 Rect<f64>,
469 Rect<f64>,
470 bool,
471 ShadowOptions,
472 CompositionOptions,
473 Transform2D<f64>,
474 ),
475 ClearRect(Rect<f32>, Transform2D<f64>),
476 ClipPath(Path, FillRule, Transform2D<f64>),
477 PopClips(usize),
478 FillPath(
479 FillOrStrokeStyle,
480 Path,
481 FillRule,
482 ShadowOptions,
483 CompositionOptions,
484 Transform2D<f64>,
485 ),
486 FillText(
487 Rect<f64>,
488 Vec<TextRun>,
489 FillOrStrokeStyle,
490 ShadowOptions,
491 CompositionOptions,
492 Transform2D<f64>,
493 ),
494 StrokeText(
495 Rect<f64>,
496 Vec<TextRun>,
497 FillOrStrokeStyle,
498 LineOptions,
499 ShadowOptions,
500 CompositionOptions,
501 Transform2D<f64>,
502 ),
503 FillRect(
504 Rect<f32>,
505 FillOrStrokeStyle,
506 ShadowOptions,
507 CompositionOptions,
508 Transform2D<f64>,
509 ),
510 GetImageData(Option<Rect<u32>>, GenericSender<SharedSnapshot>),
511 PutImageData(Rect<u32>, SharedSnapshot),
512 StrokeRect(
513 Rect<f32>,
514 FillOrStrokeStyle,
515 LineOptions,
516 ShadowOptions,
517 CompositionOptions,
518 Transform2D<f64>,
519 ),
520 StrokePath(
521 Path,
522 FillOrStrokeStyle,
523 LineOptions,
524 ShadowOptions,
525 CompositionOptions,
526 Transform2D<f64>,
527 ),
528 UpdateImage(Option<Epoch>),
529 ProcessBatchMessages(Vec<CanvasCommand>),
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: SharedSnapshot,
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: SharedSnapshot,
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(_) | FontIdentifier::ArrayBuffer(_) => 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}