1use super::*;
4
5pub mod path_smoothing;
6pub mod path_trim;
7pub mod stroke_utils;
8
9#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
11#[serde(crate = "crate::serde")]
12pub enum PathElement {
13 #[default]
15 Empty,
16 Point(Size, Size),
18 Line,
20 Quadratic(Size, Size),
22 Cubic(Size, Size, Size, Size),
24 Close,
26}
27
28impl Interpolatable for PathElement {}
29impl HelperFunctions for PathElement {}
30
31#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
37#[serde(crate = "crate::serde")]
38pub enum PathSmoothing {
39 #[default]
41 None,
42 Light,
44 Strong,
46}
47
48impl Interpolatable for PathSmoothing {
49 fn interpolate(&self, other: &Self, t: f64) -> Self {
50 if t < 1.0 {
51 *self
52 } else {
53 *other
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
63#[serde(crate = "crate::serde")]
64pub enum StrokeCap {
65 #[default]
67 Butt,
68 Round,
70 Square,
72}
73
74impl Interpolatable for StrokeCap {}
75
76#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
78#[serde(crate = "crate::serde")]
79pub enum StrokeJoin {
80 #[default]
82 Miter,
83 Round,
85 Bevel,
87}
88
89impl Interpolatable for StrokeJoin {}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(crate = "crate::serde")]
98pub struct Stroke {
99 pub color: Property<Color>,
101 pub width: Property<Size>,
106 pub cap: Property<StrokeCap>,
108 pub join: Property<StrokeJoin>,
110}
111
112impl Default for Stroke {
113 fn default() -> Self {
114 Self {
115 color: Default::default(),
116 width: Property::new(Size::Pixels(Numeric::F64(0.0))),
117 cap: Property::new(StrokeCap::default()),
118 join: Property::new(StrokeJoin::default()),
119 }
120 }
121}
122
123impl PartialEq for Stroke {
124 fn eq(&self, other: &Self) -> bool {
125 self.color.get() == other.color.get()
126 && self.width.get() == other.width.get()
127 && self.cap.get() == other.cap.get()
128 && self.join.get() == other.join.get()
129 }
130}
131
132impl Interpolatable for Stroke {
133 fn interpolate(&self, other: &Self, t: f64) -> Self {
134 Self {
135 color: Property::new(self.color.get().interpolate(&other.color.get(), t)),
136 width: Property::new(self.width.get().interpolate(&other.width.get(), t)),
137 cap: Property::new(if t < 1.0 {
138 self.cap.get()
139 } else {
140 other.cap.get()
141 }),
142 join: Property::new(if t < 1.0 {
143 self.join.get()
144 } else {
145 other.join.get()
146 }),
147 }
148 }
149}
150
151pub enum NavigationTarget {
153 Current,
155 New,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
161#[serde(crate = "crate::serde")]
162pub enum Fill {
163 Solid(Color),
165 LinearGradient(LinearGradient),
167 RadialGradient(RadialGradient),
169}
170
171impl Hash for Fill {
172 fn hash<H: Hasher>(&self, state: &mut H) {
173 match self {
174 Fill::Solid(color) => {
175 state.write_u8(0);
176 color.hash(state);
177 }
178 Fill::LinearGradient(linear) => {
179 state.write_u8(1);
180 linear.start.hash(state);
181 linear.end.hash(state);
182 linear.stops.hash(state);
183 }
184 Fill::RadialGradient(radial) => {
185 state.write_u8(2);
186 radial.start.hash(state);
187 radial.end.hash(state);
188 radial.radius.to_bits().hash(state);
189 radial.stops.hash(state);
190 }
191 }
192 }
193}
194
195impl Hash for Stroke {
196 fn hash<H: Hasher>(&self, state: &mut H) {
197 self.width.get().hash(state);
198 self.color.get().hash(state);
199 self.cap.get().hash(state);
200 self.join.get().hash(state);
201 }
202}
203
204#[derive(Debug, Copy, Clone, Serialize, Deserialize, Default, PartialEq, PartialOrd)]
210#[serde(crate = "crate::serde")]
211pub struct Depth(pub Numeric);
212
213impl Depth {
214 pub fn to_float(&self) -> f64 {
216 self.0.to_float()
217 }
218}
219
220impl From<Numeric> for Depth {
221 fn from(value: Numeric) -> Self {
222 Self(value)
223 }
224}
225
226impl From<f64> for Depth {
227 fn from(value: f64) -> Self {
228 Self(Numeric::F64(value))
229 }
230}
231
232impl Hash for Depth {
233 fn hash<H: Hasher>(&self, state: &mut H) {
234 self.0.hash(state);
235 }
236}
237
238impl Interpolatable for Depth {
239 fn interpolate(&self, other: &Self, t: f64) -> Self {
240 Self(self.0.interpolate(&other.0, t))
241 }
242}
243
244impl HelperFunctions for Depth {}
245
246#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]
248#[serde(crate = "crate::serde")]
249pub struct Vector3 {
250 pub x: f64,
252 pub y: f64,
254 pub z: f64,
256}
257
258impl Vector3 {
259 pub fn new(x: f64, y: f64, z: f64) -> Self {
261 Self { x, y, z }
262 }
263}
264
265impl Default for Vector3 {
266 fn default() -> Self {
267 Self {
268 x: 0.0,
269 y: 0.0,
270 z: 0.0,
271 }
272 }
273}
274
275impl Hash for Vector3 {
276 fn hash<H: Hasher>(&self, state: &mut H) {
277 self.x.to_bits().hash(state);
278 self.y.to_bits().hash(state);
279 self.z.to_bits().hash(state);
280 }
281}
282
283impl Interpolatable for Vector3 {
284 fn interpolate(&self, other: &Self, t: f64) -> Self {
285 Self {
286 x: self.x.interpolate(&other.x, t),
287 y: self.y.interpolate(&other.y, t),
288 z: self.z.interpolate(&other.z, t),
289 }
290 }
291}
292
293impl HelperFunctions for Vector3 {
294 fn register_all_functions() {
295 register_function(
296 "Vector3".to_string(),
297 "new".to_string(),
298 std::sync::Arc::new(|args| {
299 if args.len() != 3 {
300 return Err("Expected 3 arguments for function Vector3::new".to_string());
301 }
302 let mut itr = args.into_iter();
303 let x = f64::try_coerce(itr.next().unwrap())?;
304 let y = f64::try_coerce(itr.next().unwrap())?;
305 let z = f64::try_coerce(itr.next().unwrap())?;
306 Ok(Vector3::new(x, y, z).to_pax_value())
307 }),
308 );
309 }
310}
311
312#[derive(Debug, Copy, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
314#[serde(crate = "crate::serde")]
315pub enum LightShape {
316 #[default]
318 Point,
319 Directional,
321}
322
323impl Interpolatable for LightShape {}
324impl HelperFunctions for LightShape {}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328#[serde(crate = "crate::serde")]
329pub struct MaterialParams {
330 pub ambient: Property<f64>,
332 pub diffuse: Property<f64>,
334 pub specular: Property<f64>,
336 pub roughness: Property<f64>,
338 pub metallic: Property<f64>,
340 pub emissive: Property<Color>,
342 pub emissive_intensity: Property<f64>,
344}
345
346impl Default for MaterialParams {
347 fn default() -> Self {
348 Self {
349 ambient: Property::new(1.0),
350 diffuse: Property::new(0.82),
351 specular: Property::new(0.08),
352 roughness: Property::new(0.78),
353 metallic: Property::new(0.0),
354 emissive: Property::new(Color::BLACK),
355 emissive_intensity: Property::new(0.0),
356 }
357 }
358}
359
360impl PartialEq for MaterialParams {
361 fn eq(&self, other: &Self) -> bool {
362 self.ambient.get() == other.ambient.get()
363 && self.diffuse.get() == other.diffuse.get()
364 && self.specular.get() == other.specular.get()
365 && self.roughness.get() == other.roughness.get()
366 && self.metallic.get() == other.metallic.get()
367 && self.emissive.get() == other.emissive.get()
368 && self.emissive_intensity.get() == other.emissive_intensity.get()
369 }
370}
371
372impl Hash for MaterialParams {
373 fn hash<H: Hasher>(&self, state: &mut H) {
374 self.ambient.get().to_bits().hash(state);
375 self.diffuse.get().to_bits().hash(state);
376 self.specular.get().to_bits().hash(state);
377 self.roughness.get().to_bits().hash(state);
378 self.metallic.get().to_bits().hash(state);
379 self.emissive.get().hash(state);
380 self.emissive_intensity.get().to_bits().hash(state);
381 }
382}
383
384impl Interpolatable for MaterialParams {
385 fn interpolate(&self, other: &Self, t: f64) -> Self {
386 Self {
387 ambient: Property::new(self.ambient.get().interpolate(&other.ambient.get(), t)),
388 diffuse: Property::new(self.diffuse.get().interpolate(&other.diffuse.get(), t)),
389 specular: Property::new(self.specular.get().interpolate(&other.specular.get(), t)),
390 roughness: Property::new(self.roughness.get().interpolate(&other.roughness.get(), t)),
391 metallic: Property::new(self.metallic.get().interpolate(&other.metallic.get(), t)),
392 emissive: Property::new(self.emissive.get().interpolate(&other.emissive.get(), t)),
393 emissive_intensity: Property::new(
394 self.emissive_intensity
395 .get()
396 .interpolate(&other.emissive_intensity.get(), t),
397 ),
398 }
399 }
400}
401
402impl HelperFunctions for MaterialParams {}
403
404#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
409#[serde(crate = "crate::serde")]
410pub enum Material {
411 Lit(MaterialParams),
413 Unlit,
415}
416
417impl Default for Material {
418 fn default() -> Self {
419 Self::matte()
420 }
421}
422
423impl Material {
424 pub fn matte() -> Self {
426 Self::Lit(MaterialParams::default())
427 }
428
429 pub fn glossy(specular: f64) -> Self {
431 let mut params = MaterialParams::default();
432 params.specular = Property::new(specular.clamp(0.0, 1.0));
433 params.roughness = Property::new(0.28);
434 Self::Lit(params)
435 }
436
437 pub fn metallic(metallic: f64) -> Self {
439 let mut params = MaterialParams::default();
440 params.metallic = Property::new(metallic.clamp(0.0, 1.0));
441 params.diffuse = Property::new(0.42);
442 params.specular = Property::new(0.55);
443 params.roughness = Property::new(0.36);
444 Self::Lit(params)
445 }
446
447 pub fn emissive(color: Color, intensity: f64) -> Self {
449 let mut params = MaterialParams::default();
450 params.emissive = Property::new(color);
451 params.emissive_intensity = Property::new(intensity.max(0.0));
452 Self::Lit(params)
453 }
454
455 pub fn custom(params: MaterialParams) -> Self {
457 Self::Lit(params)
458 }
459
460 pub fn unlit() -> Self {
462 Self::Unlit
463 }
464}
465
466impl Hash for Material {
467 fn hash<H: Hasher>(&self, state: &mut H) {
468 match self {
469 Material::Lit(params) => {
470 state.write_u8(0);
471 params.hash(state);
472 }
473 Material::Unlit => {
474 state.write_u8(1);
475 }
476 }
477 }
478}
479
480impl Interpolatable for Material {
481 fn interpolate(&self, other: &Self, t: f64) -> Self {
482 match (self, other) {
483 (Self::Lit(lhs), Self::Lit(rhs)) => Self::Lit(lhs.interpolate(rhs, t)),
484 _ if t < 1.0 => self.clone(),
485 _ => other.clone(),
486 }
487 }
488}
489
490impl HelperFunctions for Material {
491 fn register_all_functions() {
492 register_function(
493 "Material".to_string(),
494 "matte".to_string(),
495 std::sync::Arc::new(|args| {
496 if !args.is_empty() {
497 return Err("Expected 0 arguments for function Material::matte".to_string());
498 }
499 Ok(Material::matte().to_pax_value())
500 }),
501 );
502 register_function(
503 "Material".to_string(),
504 "glossy".to_string(),
505 std::sync::Arc::new(|args| {
506 if args.len() != 1 {
507 return Err("Expected 1 argument for function Material::glossy".to_string());
508 }
509 let specular = f64::try_coerce(args.into_iter().next().unwrap())?;
510 Ok(Material::glossy(specular).to_pax_value())
511 }),
512 );
513 register_function(
514 "Material".to_string(),
515 "metallic".to_string(),
516 std::sync::Arc::new(|args| {
517 if args.len() != 1 {
518 return Err("Expected 1 argument for function Material::metallic".to_string());
519 }
520 let metallic = f64::try_coerce(args.into_iter().next().unwrap())?;
521 Ok(Material::metallic(metallic).to_pax_value())
522 }),
523 );
524 register_function(
525 "Material".to_string(),
526 "emissive".to_string(),
527 std::sync::Arc::new(|args| {
528 if args.len() != 2 {
529 return Err("Expected 2 arguments for function Material::emissive".to_string());
530 }
531 let mut itr = args.into_iter();
532 let color = Color::try_coerce(itr.next().unwrap())?;
533 let intensity = f64::try_coerce(itr.next().unwrap())?;
534 Ok(Material::emissive(color, intensity).to_pax_value())
535 }),
536 );
537 register_function(
538 "Material".to_string(),
539 "custom".to_string(),
540 std::sync::Arc::new(|args| {
541 if args.len() != 1 {
542 return Err("Expected 1 argument for function Material::custom".to_string());
543 }
544 let params = MaterialParams::try_coerce(args.into_iter().next().unwrap())?;
545 Ok(Material::custom(params).to_pax_value())
546 }),
547 );
548 register_function(
549 "Material".to_string(),
550 "unlit".to_string(),
551 std::sync::Arc::new(|args| {
552 if !args.is_empty() {
553 return Err("Expected 0 arguments for function Material::unlit".to_string());
554 }
555 Ok(Material::unlit().to_pax_value())
556 }),
557 );
558 }
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
563#[serde(crate = "crate::serde")]
564pub struct SceneAmbientLight {
565 pub color: Color,
567 pub intensity: f64,
569}
570
571impl Default for SceneAmbientLight {
572 fn default() -> Self {
573 Self {
574 color: Color::WHITE,
575 intensity: 1.0,
576 }
577 }
578}
579
580impl Interpolatable for SceneAmbientLight {
581 fn interpolate(&self, other: &Self, t: f64) -> Self {
582 Self {
583 color: self.color.interpolate(&other.color, t),
584 intensity: self.intensity.interpolate(&other.intensity, t),
585 }
586 }
587}
588
589impl HelperFunctions for SceneAmbientLight {}
590
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
593#[serde(crate = "crate::serde")]
594pub struct SceneLight {
595 pub shape: LightShape,
597 pub position: Vector3,
599 pub direction: Vector3,
601 pub color: Color,
603 pub intensity: f64,
605 pub radius: f64,
607 pub enabled: bool,
609}
610
611impl Default for SceneLight {
612 fn default() -> Self {
613 Self {
614 shape: LightShape::Point,
615 position: Vector3::default(),
616 direction: Vector3::new(0.0, 0.0, -1.0),
617 color: Color::WHITE,
618 intensity: 1.0,
619 radius: 240.0,
620 enabled: true,
621 }
622 }
623}
624
625impl Interpolatable for SceneLight {
626 fn interpolate(&self, other: &Self, t: f64) -> Self {
627 Self {
628 shape: if t < 1.0 { self.shape } else { other.shape },
629 position: self.position.interpolate(&other.position, t),
630 direction: self.direction.interpolate(&other.direction, t),
631 color: self.color.interpolate(&other.color, t),
632 intensity: self.intensity.interpolate(&other.intensity, t),
633 radius: self.radius.interpolate(&other.radius, t),
634 enabled: if t < 1.0 { self.enabled } else { other.enabled },
635 }
636 }
637}
638
639impl HelperFunctions for SceneLight {}
640
641#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
643#[serde(crate = "crate::serde")]
644pub struct SceneLighting {
645 pub active: bool,
647 pub ambient_is_authored: bool,
652 pub ambient: SceneAmbientLight,
654 pub lights: Vec<SceneLight>,
656}
657
658impl Default for SceneLighting {
659 fn default() -> Self {
660 Self {
661 active: false,
662 ambient_is_authored: false,
663 ambient: SceneAmbientLight::default(),
664 lights: vec![],
665 }
666 }
667}
668
669impl SceneLighting {
670 pub const MAX_LIGHTS: usize = 8;
672
673 pub const DEFAULT_AMBIENT_INTENSITY: f64 = 0.35;
675
676 pub fn identity() -> Self {
678 Self::default()
679 }
680
681 pub fn with_default_ambient(lights: Vec<SceneLight>) -> Self {
683 Self {
684 active: !lights.is_empty(),
685 ambient_is_authored: false,
686 ambient: SceneAmbientLight {
687 color: Color::WHITE,
688 intensity: Self::DEFAULT_AMBIENT_INTENSITY,
689 },
690 lights,
691 }
692 }
693}
694
695impl Interpolatable for SceneLighting {}
696impl HelperFunctions for SceneLighting {}
697
698impl Interpolatable for Fill {
699 fn interpolate(&self, _other: &Self, _t: f64) -> Self {
700 self.clone()
702 }
703}
704
705#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash)]
710#[serde(crate = "crate::serde")]
711pub struct LinearGradient {
712 pub start: (Size, Size),
714 pub end: (Size, Size),
716 pub stops: Vec<GradientStop>,
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
725#[serde(crate = "crate::serde")]
726pub struct RadialGradient {
727 pub end: (Size, Size),
729 pub start: (Size, Size),
731 pub radius: f64,
733 pub stops: Vec<GradientStop>,
735}
736
737#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash)]
739#[serde(crate = "crate::serde")]
740pub struct GradientStop {
741 pub position: Size,
743 pub color: Color,
745}
746
747impl GradientStop {
748 pub fn get(color: Color, position: Size) -> GradientStop {
750 GradientStop { position, color }
751 }
752
753 pub fn with_alpha_factor(&self, factor: f64) -> GradientStop {
755 GradientStop {
756 position: self.position.clone(),
757 color: self.color.with_alpha_factor(factor),
758 }
759 }
760}
761
762impl Default for Fill {
763 fn default() -> Self {
764 Self::Solid(Color::default())
765 }
766}
767
768impl Fill {
769 fn gradient_stop_position_0_1(position: &Size) -> Option<f64> {
770 match position {
771 Size::Percent(p) => Some((p.to_float() / 100.0).clamp(0.0, 1.0)),
772 Size::Pixels(_) | Size::Combined(_, _) => None,
773 }
774 }
775
776 fn integrated_gradient_alpha_0_1(stops: &[GradientStop]) -> f64 {
777 if stops.is_empty() {
778 return 0.0;
779 }
780
781 let mut positioned_stops = stops
782 .iter()
783 .filter_map(|stop| {
784 Self::gradient_stop_position_0_1(&stop.position)
785 .map(|position| (position, stop.color.alpha_0_1()))
786 })
787 .collect::<Vec<_>>();
788
789 if positioned_stops.is_empty() {
790 return stops
791 .iter()
792 .map(|stop| stop.color.alpha_0_1())
793 .fold(0.0, f64::max);
794 }
795
796 positioned_stops.sort_by(|lhs, rhs| lhs.0.total_cmp(&rhs.0));
797
798 if positioned_stops
799 .first()
800 .map(|stop| stop.0 > 0.0)
801 .unwrap_or(false)
802 {
803 let alpha = positioned_stops[0].1;
804 positioned_stops.insert(0, (0.0, alpha));
805 }
806
807 if positioned_stops
808 .last()
809 .map(|stop| stop.0 < 1.0)
810 .unwrap_or(false)
811 {
812 let alpha = positioned_stops.last().map(|stop| stop.1).unwrap_or(0.0);
813 positioned_stops.push((1.0, alpha));
814 }
815
816 let mut integrated_alpha = 0.0;
817 for window in positioned_stops.windows(2) {
818 let [(start_pos, start_alpha), (end_pos, end_alpha)] = window else {
819 continue;
820 };
821 let span = (end_pos - start_pos).clamp(0.0, 1.0);
822 if span <= f64::EPSILON {
823 continue;
824 }
825 integrated_alpha += (start_alpha + end_alpha) * 0.5 * span;
826 }
827
828 integrated_alpha.clamp(0.0, 1.0)
829 }
830
831 pub fn to_unit_point((x, y): (Size, Size), (width, height): (f64, f64)) -> UnitPoint {
833 let normalized_x = match x {
834 Size::Pixels(val) => val.to_float() / width,
835 Size::Percent(val) => val.to_float() / 100.0,
836 Size::Combined(pix, per) => (pix.to_float() / width) + (per.to_float() / 100.0),
837 };
838
839 let normalized_y = match y {
840 Size::Pixels(val) => val.to_float() / height,
841 Size::Percent(val) => val.to_float() / 100.0,
842 Size::Combined(pix, per) => (pix.to_float() / width) + (per.to_float() / 100.0),
843 };
844 UnitPoint::new(normalized_x, normalized_y)
845 }
846
847 pub fn to_piet_gradient_stops(stops: Vec<GradientStop>) -> Vec<piet::GradientStop> {
849 let mut ret = Vec::new();
850 for gradient_stop in stops {
851 match gradient_stop.position {
852 Size::Pixels(_) => {
853 panic!("Gradient stops must be specified in percentages");
854 }
855 Size::Percent(p) => {
856 ret.push(piet::GradientStop {
857 pos: (p.to_float() / 100.0) as f32,
858 color: gradient_stop.color.to_piet_color(),
859 });
860 }
861 Size::Combined(_, _) => {
862 panic!("Gradient stops must be specified in percentages");
863 }
864 }
865 }
866 ret
867 }
868
869 #[allow(non_snake_case)]
870 pub fn linearGradient(
875 start: (Size, Size),
876 end: (Size, Size),
877 stops: Vec<GradientStop>,
878 ) -> Fill {
879 Fill::LinearGradient(LinearGradient { start, end, stops })
880 }
881
882 pub fn with_alpha_factor(&self, factor: f64) -> Fill {
884 match self {
885 Fill::Solid(color) => Fill::Solid(color.with_alpha_factor(factor)),
886 Fill::LinearGradient(gradient) => Fill::LinearGradient(LinearGradient {
887 start: gradient.start.clone(),
888 end: gradient.end.clone(),
889 stops: gradient
890 .stops
891 .iter()
892 .map(|stop| stop.with_alpha_factor(factor))
893 .collect(),
894 }),
895 Fill::RadialGradient(gradient) => Fill::RadialGradient(RadialGradient {
896 start: gradient.start.clone(),
897 end: gradient.end.clone(),
898 radius: gradient.radius,
899 stops: gradient
900 .stops
901 .iter()
902 .map(|stop| stop.with_alpha_factor(factor))
903 .collect(),
904 }),
905 }
906 }
907
908 pub fn max_alpha_0_1(&self) -> f64 {
910 match self {
911 Fill::Solid(color) => color.alpha_0_1(),
912 Fill::LinearGradient(gradient) => gradient
913 .stops
914 .iter()
915 .map(|stop| stop.color.alpha_0_1())
916 .fold(0.0, f64::max),
917 Fill::RadialGradient(gradient) => gradient
918 .stops
919 .iter()
920 .map(|stop| stop.color.alpha_0_1())
921 .fold(0.0, f64::max),
922 }
923 }
924
925 pub fn coverage_alpha_0_1(&self) -> f64 {
927 match self {
928 Fill::Solid(color) => color.alpha_0_1(),
929 Fill::LinearGradient(gradient) => Self::integrated_gradient_alpha_0_1(&gradient.stops),
930 Fill::RadialGradient(gradient) => Self::integrated_gradient_alpha_0_1(&gradient.stops),
931 }
932 }
933}
934
935#[cfg(test)]
936mod fill_coverage_tests {
937 use super::{Color, ColorChannel, Fill, GradientStop, LinearGradient, Numeric, Size};
938
939 fn rgba_alpha(alpha: u8) -> Color {
940 Color::rgba(
941 ColorChannel::Integer(0),
942 ColorChannel::Integer(0),
943 ColorChannel::Integer(0),
944 ColorChannel::Integer(alpha),
945 )
946 }
947
948 #[test]
949 fn coverage_alpha_matches_solid_alpha() {
950 let fill = Fill::Solid(rgba_alpha(128));
951 assert!((fill.coverage_alpha_0_1() - (128.0 / 255.0)).abs() < 1e-6);
952 }
953
954 #[test]
955 fn coverage_alpha_integrates_gradient_stops() {
956 let fill = Fill::LinearGradient(LinearGradient {
957 start: (
958 Size::Percent(Numeric::F64(0.0)),
959 Size::Percent(Numeric::F64(50.0)),
960 ),
961 end: (
962 Size::Percent(Numeric::F64(100.0)),
963 Size::Percent(Numeric::F64(50.0)),
964 ),
965 stops: vec![
966 GradientStop::get(rgba_alpha(0), Size::Percent(Numeric::F64(0.0))),
967 GradientStop::get(rgba_alpha(255), Size::Percent(Numeric::F64(50.0))),
968 GradientStop::get(rgba_alpha(0), Size::Percent(Numeric::F64(100.0))),
969 ],
970 });
971 assert!((fill.coverage_alpha_0_1() - 0.5).abs() < 1e-6);
972 }
973}