inset_embedder/geometry/
shadow.rs1use std::fmt::{self, Debug};
4
5use super::color::Color;
6use super::geometry::Offset;
7use super::lerp::lerp_double_non_null;
8
9#[derive(Clone, Copy, PartialEq)]
11pub struct Shadow {
12 pub color: Color,
17 pub offset: Offset,
23 pub blur_radius: f64,
25}
26
27impl Shadow {
28 pub fn new(color: Color, offset: Offset, blur_radius: f64) -> Shadow {
39 debug_assert!(
40 blur_radius >= 0.0,
41 "Text shadow blur radius should be non-negative."
42 );
43 Shadow {
44 color,
45 offset,
46 blur_radius,
47 }
48 }
49
50 pub fn convert_radius_to_sigma(radius: f64) -> f64 {
54 if radius > 0.0 {
55 radius * 0.57735 + 0.5
56 } else {
57 0.0
58 }
59 }
60
61 pub fn blur_sigma(&self) -> f64 {
63 Self::convert_radius_to_sigma(self.blur_radius)
64 }
65
66 pub fn scale(&self, factor: f64) -> Shadow {
69 Shadow::new(self.color, self.offset * factor, self.blur_radius * factor)
70 }
71
72 pub fn to_paint(&self) -> crate::Paint {
79 crate::Paint {
80 color: self.color.into(),
81 mask_blur: Some(crate::MaskBlur::new(self.blur_sigma() as f32)),
82 ..crate::Paint::default()
83 }
84 }
85
86 pub fn lerp(a: Option<Shadow>, b: Option<Shadow>, t: f64) -> Option<Shadow> {
92 match (a, b) {
93 (None, None) => None,
94 (Some(a), None) => Some(a.scale(1.0 - t)),
95 (None, Some(b)) => Some(b.scale(t)),
96 (Some(a), Some(b)) => Some(Shadow::new(
97 Color::lerp(Some(a.color), Some(b.color), t).unwrap(),
98 Offset::lerp(Some(a.offset), Some(b.offset), t).unwrap(),
99 lerp_double_non_null(a.blur_radius, b.blur_radius, t),
100 )),
101 }
102 }
103
104 pub fn lerp_list(a: Option<&[Shadow]>, b: Option<&[Shadow]>, t: f64) -> Option<Vec<Shadow>> {
108 if a.is_none() && b.is_none() {
109 return None;
110 }
111 let a = a.unwrap_or(&[]);
112 let b = b.unwrap_or(&[]);
113 let common_length = a.len().min(b.len());
114 let mut result = Vec::new();
115 for i in 0..common_length {
116 result.push(Shadow::lerp(Some(a[i]), Some(b[i]), t).unwrap());
117 }
118 for shadow in &a[common_length..] {
119 result.push(shadow.scale(1.0 - t));
120 }
121 for shadow in &b[common_length..] {
122 result.push(shadow.scale(t));
123 }
124 Some(result)
125 }
126}
127
128impl Default for Shadow {
129 fn default() -> Shadow {
130 Shadow::new(Color::new(0xFF000000), Offset::ZERO, 0.0)
131 }
132}
133
134impl Debug for Shadow {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 write!(
137 f,
138 "TextShadow({:?}, {:?}, {})",
139 self.color, self.offset, self.blur_radius
140 )
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn convert_radius_to_sigma_matches_skia() {
150 assert_eq!(Shadow::convert_radius_to_sigma(0.0), 0.0);
151 assert_eq!(Shadow::convert_radius_to_sigma(-1.0), 0.0);
152 assert_eq!(Shadow::convert_radius_to_sigma(1.0), 1.0 * 0.57735 + 0.5);
153 }
154
155 #[test]
156 fn lerp_null_arms_scale_toward_nothing() {
157 let shadow = Shadow::new(Color::new(0xFF000000), Offset::new(8.0, 0.0), 4.0);
158 assert_eq!(
159 Shadow::lerp(None, Some(shadow), 0.25).unwrap(),
160 shadow.scale(0.25)
161 );
162 assert_eq!(
163 Shadow::lerp(Some(shadow), None, 0.25).unwrap(),
164 shadow.scale(0.75)
165 );
166 assert_eq!(Shadow::lerp(None, None, 0.25), None);
167 }
168}