style/
piecewise_linear.rs1use crate::values::computed::Percentage;
7use core::slice::Iter;
8use euclid::approxeq::ApproxEq;
10use itertools::Itertools;
11use std::fmt::{self, Write};
12use style_traits::{CssWriter, ToCss};
13
14use crate::values::CSSFloat;
15
16type ValueType = CSSFloat;
17#[allow(missing_docs)]
19#[derive(
20 Clone,
21 Copy,
22 Debug,
23 MallocSizeOf,
24 PartialEq,
25 SpecifiedValueInfo,
26 ToResolvedValue,
27 ToShmem,
28 Serialize,
29 Deserialize,
30)]
31#[repr(C)]
32pub struct PiecewiseLinearFunctionEntry {
33 pub x: ValueType,
34 pub y: ValueType,
35}
36
37impl ToCss for PiecewiseLinearFunctionEntry {
38 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
39 where
40 W: fmt::Write,
41 {
42 self.y.to_css(dest)?;
43 dest.write_char(' ')?;
44 Percentage(self.x).to_css(dest)
45 }
46}
47
48#[derive(
50 Default,
51 Clone,
52 Debug,
53 MallocSizeOf,
54 PartialEq,
55 SpecifiedValueInfo,
56 ToResolvedValue,
57 ToCss,
58 ToShmem,
59 Serialize,
60 Deserialize,
61)]
62#[repr(C)]
63#[css(comma)]
64pub struct PiecewiseLinearFunction {
65 #[css(iterable)]
66 #[ignore_malloc_size_of = "Arc"]
67 #[shmem(field_bound)]
68 entries: crate::ArcSlice<PiecewiseLinearFunctionEntry>,
69}
70
71pub type PiecewiseLinearFunctionBuildParameters = (CSSFloat, Option<CSSFloat>);
73
74impl PiecewiseLinearFunction {
75 fn interpolate(
77 x: ValueType,
78 prev: PiecewiseLinearFunctionEntry,
79 next: PiecewiseLinearFunctionEntry,
80 asymptote: &PiecewiseLinearFunctionEntry,
81 ) -> ValueType {
82 if x.approx_eq(&next.x) {
85 return next.y;
86 }
87 if x.approx_eq(&prev.x) {
88 return prev.y;
89 }
90 if prev.x.approx_eq(&next.x) {
92 return next.y;
93 }
94 let slope = (next.y - prev.y) / (next.x - prev.x);
95 return slope * (x - asymptote.x) + asymptote.y;
96 }
97
98 pub fn at(&self, x: ValueType) -> ValueType {
101 if !x.is_finite() {
102 return if x > 0.0 { 1.0 } else { 0.0 };
103 }
104 if self.entries.is_empty() {
105 return x;
107 }
108 if self.entries.len() == 1 {
109 return self.entries[0].y;
111 }
112 if x < self.entries[0].x {
119 return Self::interpolate(x, self.entries[0], self.entries[1], &self.entries[0]);
120 }
121 let mut rev_iter = self.entries.iter().rev();
122 let last = rev_iter.next().unwrap();
123 if x >= last.x {
124 let second_last = rev_iter.next().unwrap();
125 return Self::interpolate(x, *second_last, *last, last);
126 }
127
128 for (point_b, point_a) in self.entries.iter().rev().tuple_windows() {
130 if x < point_a.x {
133 continue;
134 }
135 return Self::interpolate(x, *point_a, *point_b, point_a);
136 }
137 unreachable!("Input is supposed to be within the entries' min & max!");
138 }
139
140 #[allow(missing_docs)]
141 pub fn iter(&self) -> Iter<PiecewiseLinearFunctionEntry> {
142 self.entries.iter()
143 }
144}
145
146#[derive(Clone, Copy)]
148struct BuildEntry {
149 x: Option<ValueType>,
150 y: ValueType,
151}
152
153#[derive(Default)]
155pub struct PiecewiseLinearFunctionBuilder {
156 largest_x: Option<ValueType>,
157 smallest_x: Option<ValueType>,
158 entries: Vec<BuildEntry>,
159}
160
161impl PiecewiseLinearFunctionBuilder {
162 pub fn with_capacity(len: usize) -> Self {
164 PiecewiseLinearFunctionBuilder {
165 largest_x: None,
166 smallest_x: None,
167 entries: Vec::with_capacity(len),
168 }
169 }
170
171 fn create_entry(&mut self, y: ValueType, x: Option<ValueType>) {
172 let x = match x {
173 Some(x) if x.is_finite() => x,
174 _ if self.entries.is_empty() => 0.0, _ => {
176 self.entries.push(BuildEntry { x: None, y });
177 return;
178 },
179 };
180 let x = match self.largest_x {
182 Some(largest_x) => x.max(largest_x),
183 None => x,
184 };
185 self.largest_x = Some(x);
186 if self.smallest_x.is_none() {
188 self.smallest_x = Some(x);
189 }
190 self.entries.push(BuildEntry { x: Some(x), y });
191 }
192
193 pub fn push(&mut self, y: CSSFloat, x_start: Option<CSSFloat>) {
199 self.create_entry(y, x_start)
200 }
201
202 pub fn build(mut self) -> PiecewiseLinearFunction {
205 if self.entries.is_empty() {
206 return PiecewiseLinearFunction::default();
207 }
208 if self.entries.len() == 1 {
209 return PiecewiseLinearFunction {
211 entries: crate::ArcSlice::from_iter(std::iter::once(
212 PiecewiseLinearFunctionEntry {
213 x: 0.,
214 y: self.entries[0].y,
215 },
216 )),
217 };
218 }
219 debug_assert!(
222 self.entries[0].x.is_some(),
223 "Expected an entry with x defined!"
224 );
225 self.entries
227 .last_mut()
228 .unwrap()
229 .x
230 .get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0));
231 let mut result = Vec::with_capacity(self.entries.len());
234 result.push(PiecewiseLinearFunctionEntry {
235 x: self.entries[0].x.unwrap(),
236 y: self.entries[0].y,
237 });
238 for (i, e) in self.entries.iter().enumerate().skip(1) {
239 if e.x.is_none() {
240 continue;
243 }
244 let divisor = i - result.len() + 1;
246 if divisor != 1 {
248 let start_x = result.last().unwrap().x;
250 let increment = (e.x.unwrap() - start_x) / divisor as ValueType;
251 result.extend(
255 self.entries[result.len()..i]
256 .iter()
257 .enumerate()
258 .map(|(j, e)| {
259 debug_assert!(e.x.is_none(), "Expected an entry with x undefined!");
260 PiecewiseLinearFunctionEntry {
261 x: increment * (j + 1) as ValueType + start_x,
262 y: e.y,
263 }
264 }),
265 );
266 }
267 result.push(PiecewiseLinearFunctionEntry {
268 x: e.x.unwrap(),
269 y: e.y,
270 });
271 }
272 debug_assert_eq!(
273 result.len(),
274 self.entries.len(),
275 "Should've mapped one-to-one!"
276 );
277 PiecewiseLinearFunction {
278 entries: crate::ArcSlice::from_iter(result.into_iter()),
279 }
280 }
281}