/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A piecewise linear function, following CSS linear easing usecrate::values::computed::Percentage; use core::slice::Iter; /// draft as in https://github.com/w3c/csswg-drafts/pull/6533. use euclid::approxeq::ApproxEq; use itertools::Itertools; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss};
usecrate::values::CSSFloat;
type ValueType = CSSFloat; /// a single entry in a piecewise linear function. #[allow(missing_docs)] #[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToResolvedValue,
ToShmem,
Serialize,
Deserialize,
)] #[repr(C)] pubstruct PiecewiseLinearFunctionEntry { pub x: ValueType, pub y: ValueType,
}
/// Representation of a piecewise linear function, a series of linear functions. #[derive(
Default,
Clone,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToResolvedValue,
ToCss,
ToShmem,
Serialize,
Deserialize,
)] #[repr(C)] #[css(comma)] pubstruct PiecewiseLinearFunction { #[css(iterable)] #[ignore_malloc_size_of = "Arc"] #[shmem(field_bound)]
entries: crate::ArcSlice<PiecewiseLinearFunctionEntry>,
}
/// Parameters to define one linear stop. pubtype PiecewiseLinearFunctionBuildParameters = (CSSFloat, Option<CSSFloat>);
impl PiecewiseLinearFunction { /// Interpolate y value given x and two points. The linear function will be rooted at the asymptote. fn interpolate(
x: ValueType,
prev: PiecewiseLinearFunctionEntry,
next: PiecewiseLinearFunctionEntry,
asymptote: &PiecewiseLinearFunctionEntry,
) -> ValueType { // Short circuit if the x is on prev or next. // `next` point is preferred as per spec. if x.approx_eq(&next.x) { return next.y;
} if x.approx_eq(&prev.x) { return prev.y;
} // Avoid division by zero. if prev.x.approx_eq(&next.x) { return next.y;
} let slope = (next.y - prev.y) / (next.x - prev.x); return slope * (x - asymptote.x) + asymptote.y;
}
/// Get the y value of the piecewise linear function given the x value, as per /// https://drafts.csswg.org/css-easing-2/#linear-easing-function-output pubfn at(&self, x: ValueType) -> ValueType { if !x.is_finite() { returnif x > 0.0 { 1.0 } else { 0.0 };
} ifself.entries.is_empty() { // Implied y = x, as per spec. return x;
} ifself.entries.len() == 1 { // Implied y = <constant>, as per spec. returnself.entries[0].y;
} // Spec dictates the valid input domain is [0, 1]. Outside of this range, the output // should be calculated as if the slopes at start and end extend to infinity. However, if the // start/end have two points of the same position, the line should extend along the x-axis. // The function doesn't have to cover the input domain, in which case the extension logic // applies even if the input falls in the input domain. // Also, we're guaranteed to have at least two elements at this point. if x < self.entries[0].x { returnSelf::interpolate(x, self.entries[0], self.entries[1], &>self.entries[0]);
} letmut rev_iter = self.entries.iter().rev(); let last = rev_iter.next().unwrap(); if x >= last.x { let second_last = rev_iter.next().unwrap(); returnSelf::interpolate(x, *second_last, *last, last);
}
// Now we know the input sits within the domain explicitly defined by our function. for (point_b, point_a) inself.entries.iter().rev().tuple_windows() { // Need to let point A be the _last_ point where its x is less than the input x, // hence the reverse traversal. if x < point_a.x { continue;
} returnSelf::interpolate(x, *point_a, *point_b, point_a);
}
unreachable!("Input is supposed to be within the entries' min & max!");
}
/// Entry of a piecewise linear function while building, where the calculation of x value can be deferred. #[derive(Clone, Copy)] struct BuildEntry {
x: Option<ValueType>,
y: ValueType,
}
/// Builder object to generate a linear function. #[derive(Default)] pubstruct PiecewiseLinearFunctionBuilder {
largest_x: Option<ValueType>,
smallest_x: Option<ValueType>,
entries: Vec<BuildEntry>,
}
impl PiecewiseLinearFunctionBuilder { /// Create a builder for a known amount of linear stop entries. pubfn with_capacity(len: usize) -> Self {
PiecewiseLinearFunctionBuilder {
largest_x: None,
smallest_x: None,
entries: Vec::with_capacity(len),
}
}
fn create_entry(&mutself, y: ValueType, x: Option<ValueType>) { let x = match x {
Some(x) if x.is_finite() => x,
_ ifself.entries.is_empty() => 0.0, // First x is 0 if not specified (Or not finite)
_ => { self.entries.push(BuildEntry { x: None, y }); return;
},
}; // Specified x value cannot regress, as per spec. let x = matchself.largest_x {
Some(largest_x) => x.max(largest_x),
None => x,
}; self.largest_x = Some(x); // Whatever we see the earliest is the smallest value. ifself.smallest_x.is_none() { self.smallest_x = Some(x);
} self.entries.push(BuildEntry { x: Some(x), y });
}
/// Add a new entry into the piecewise linear function with specified y value. /// If the start x value is given, that is where the x value will be. Otherwise, /// the x value is calculated later. If the end x value is specified, a flat segment /// is generated. If start x value is not specified but end x is, it is treated as /// start x. pubfn push(&mutself, y: CSSFloat, x_start: Option<CSSFloat>) { self.create_entry(y, x_start)
}
/// Finish building the piecewise linear function by resolving all undefined x values, /// then return the result. pubfn build(mutself) -> PiecewiseLinearFunction { ifself.entries.is_empty() { return PiecewiseLinearFunction::default();
} ifself.entries.len() == 1 { // Don't bother resolving anything. return PiecewiseLinearFunction {
entries: crate::ArcSlice::from_iter(std::iter::once(
PiecewiseLinearFunctionEntry {
x: 0.,
y: self.entries[0].y,
},
)),
};
} // Guaranteed at least two elements. // Start element's x value should've been assigned when the first value was pushed.
debug_assert!( self.entries[0].x.is_some(), "Expected an entry with x defined!"
); // Spec asserts that if the last entry does not have an x value, it is assigned the largest seen x value. self.entries
.last_mut()
.unwrap()
.x
.get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0)); // Now we have at least two elements with x values, with start & end x values guaranteed.
letmut result = Vec::with_capacity(self.entries.len());
result.push(PiecewiseLinearFunctionEntry {
x: self.entries[0].x.unwrap(),
y: self.entries[0].y,
}); for (i, e) inself.entries.iter().enumerate().skip(1) { if e.x.is_none() { // Need to calculate x values by first finding an entry with the first // defined x value (Guaranteed to exist as the list end has it defined). continue;
} // x is defined for this element. let divisor = i - result.len() + 1; // Any element(s) with undefined x to assign? if divisor != 1 { // Have at least one element in result at all times. let start_x = result.last().unwrap().x; let increment = (e.x.unwrap() - start_x) / divisor as ValueType; // Grab every element with undefined x to this point, which starts at the end of the result // array, and ending right before the current index. Then, assigned the evenly divided // x values.
result.extend( self.entries[result.len()..i]
.iter()
.enumerate()
.map(|(j, e)| {
debug_assert!(e.x.is_none(), "Expected an entry with x undefined!");
PiecewiseLinearFunctionEntry {
x: increment * (j + 1) as ValueType + start_x,
y: e.y,
}
}),
);
}
result.push(PiecewiseLinearFunctionEntry {
x: e.x.unwrap(),
y: e.y,
});
}
debug_assert_eq!(
result.len(), self.entries.len(), "Should've mapped one-to-one!"
);
PiecewiseLinearFunction {
entries: crate::ArcSlice::from_iter(result.into_iter()),
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.