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