pub const MAX_FONT_SCALE_KNOTS: usize = 12;
const COLLINEAR_EPSILON_DP: f32 = 1.0e-3;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FontScaleCurve {
scale: f32,
knots: [(f32, f32); MAX_FONT_SCALE_KNOTS],
len: usize,
fingerprint: u32,
}
impl Default for FontScaleCurve {
fn default() -> Self {
Self::linear(1.0)
}
}
impl FontScaleCurve {
pub const fn linear(scale: f32) -> Self {
Self {
scale,
knots: [(0.0, 0.0); MAX_FONT_SCALE_KNOTS],
len: 0,
fingerprint: 0,
}
}
pub fn from_samples(scale: f32, samples: &[(f32, f32)]) -> Self {
let Some(kept) = compress(samples) else {
return Self::linear(scale);
};
let mut curve = Self::linear(scale);
for (index, knot) in kept.iter().enumerate() {
curve.knots[index] = *knot;
}
curve.len = kept.len();
curve.fingerprint = fingerprint(scale, &kept);
curve
}
pub fn scale(self) -> f32 {
self.scale
}
pub fn is_linear(self) -> bool {
self.len == 0
}
pub fn is_identity(self) -> bool {
if self.len == 0 {
return (self.scale - 1.0).abs() <= f32::EPSILON;
}
self.knots[..self.len]
.iter()
.all(|(sp, dp)| (dp - sp).abs() <= COLLINEAR_EPSILON_DP)
}
pub fn knots(self) -> [(f32, f32); MAX_FONT_SCALE_KNOTS] {
self.knots
}
pub fn knot_count(self) -> usize {
self.len
}
pub fn fingerprint(self) -> u32 {
self.fingerprint ^ self.scale.to_bits()
}
pub fn sp_to_dp(self, sp: f32) -> f32 {
if !sp.is_finite() {
return sp;
}
if self.len == 0 {
return sp * self.scale;
}
let magnitude = sp.abs();
let sign = if sp.is_sign_negative() { -1.0 } else { 1.0 };
let knots = &self.knots[..self.len];
let (first_sp, first_dp) = knots[0];
if magnitude <= first_sp {
return sign * magnitude * (first_dp / first_sp);
}
let (last_sp, last_dp) = knots[self.len - 1];
if magnitude >= last_sp {
return sign * magnitude * (last_dp / last_sp);
}
for window in knots.windows(2) {
let (low_sp, low_dp) = window[0];
let (high_sp, high_dp) = window[1];
if magnitude <= high_sp {
let t = (magnitude - low_sp) / (high_sp - low_sp);
return sign * (low_dp + (high_dp - low_dp) * t);
}
}
sign * magnitude * (last_dp / last_sp)
}
}
fn compress(samples: &[(f32, f32)]) -> Option<Vec<(f32, f32)>> {
if samples.len() < 2 {
return None;
}
let mut previous_sp = 0.0f32;
for (sp, dp) in samples {
if !sp.is_finite() || !dp.is_finite() || *sp <= previous_sp || *dp <= 0.0 {
return None;
}
previous_sp = *sp;
}
let mut kept: Vec<(f32, f32)> = Vec::with_capacity(samples.len());
kept.push(samples[0]);
for index in 1..samples.len() - 1 {
let (low_sp, low_dp) = *kept.last().expect("the first sample was pushed");
let (sp, dp) = samples[index];
let (high_sp, high_dp) = samples[index + 1];
let t = (sp - low_sp) / (high_sp - low_sp);
let straight = low_dp + (high_dp - low_dp) * t;
if (dp - straight).abs() > COLLINEAR_EPSILON_DP {
kept.push(samples[index]);
}
}
kept.push(samples[samples.len() - 1]);
if kept.len() > MAX_FONT_SCALE_KNOTS {
return None;
}
Some(kept)
}
fn fingerprint(scale: f32, knots: &[(f32, f32)]) -> u32 {
let mut hash = 2166136261u32;
let mut mix = |bits: u32| {
hash ^= bits;
hash = hash.wrapping_mul(16777619);
};
mix(scale.to_bits());
for (sp, dp) in knots {
mix(sp.to_bits());
mix(dp.to_bits());
}
hash
}
#[cfg(test)]
#[path = "tests/font_scale_tests.rs"]
mod tests;