blob: 2affff271a31a7a8e25101735ac20d134a33b406 [file] [edit]
// Copyright 2025 the Vello Authors and the Parley Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Utility helper functions.
use core::ops::Sub;
use peniko::kurbo::Affine;
// From <https://github.com/linebender/tiny-skia/blob/68b198a7210a6bbf752b43d6bc4db62445730313/path/src/scalar.rs#L12>
const SCALAR_NEARLY_ZERO_F32: f32 = 1.0 / (1 << 12) as f32;
const SCALAR_NEARLY_ZERO_F64: f64 = 1.0 / (1 << 12) as f64;
/// A number of useful methods for f32 numbers.
pub(crate) trait FloatExt: Sized + Sub<f32, Output = f32> {
/// Whether the number is approximately 0.
fn is_nearly_zero(&self) -> bool {
self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO_F32)
}
/// Whether the number is approximately 0, with a given tolerance.
fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool;
}
impl FloatExt for f32 {
#[inline(always)]
fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool {
debug_assert!(tolerance >= 0.0, "tolerance must be positive");
self.abs() <= tolerance
}
}
pub(crate) trait AffineExt {
/// Whether the transform has any skewing coefficient.
fn has_skew(&self) -> bool;
/// Whether the transform has a vertical skew.
fn has_vertical_skew(&self) -> bool;
/// Whether the transform has positive, uniform scaling factors and no skew.
fn is_positive_uniform_scale_without_skew(&self) -> bool;
/// Whether the transform has positive, uniform scaling factors and no vertical skew.
fn is_positive_uniform_scale_without_vertical_skew(&self) -> bool;
/// Whether the transform has non-unit scale or skew.
///
/// Note that negative scales (i.e. -1.0) are explicitly allowed.
fn has_non_unit_skew_or_scale(&self) -> bool;
}
impl AffineExt for Affine {
#[inline]
fn has_skew(&self) -> bool {
let [_, b, c, _, _, _] = self.as_coeffs();
b.abs() > SCALAR_NEARLY_ZERO_F64 || c.abs() > SCALAR_NEARLY_ZERO_F64
}
#[inline]
fn is_positive_uniform_scale_without_skew(&self) -> bool {
let [a, _, _, d, _, _] = self.as_coeffs();
(a - d).abs() <= SCALAR_NEARLY_ZERO_F64 && a > 0.0 && d > 0.0 && !self.has_skew()
}
#[inline]
fn has_non_unit_skew_or_scale(&self) -> bool {
let [a, _, _, d, _, _] = self.as_coeffs();
self.has_skew()
|| (1.0 - a.abs()).abs() > SCALAR_NEARLY_ZERO_F64
|| (1.0 - d.abs()).abs() > SCALAR_NEARLY_ZERO_F64
}
/// Whether the transform has a vertical skew.
#[inline]
fn has_vertical_skew(&self) -> bool {
let [_, b, _, _, _, _] = self.as_coeffs();
b.abs() > SCALAR_NEARLY_ZERO_F64
}
#[inline]
fn is_positive_uniform_scale_without_vertical_skew(&self) -> bool {
let [a, _, _, d, _, _] = self.as_coeffs();
(a - d).abs() <= SCALAR_NEARLY_ZERO_F64 && a > 0.0 && d > 0.0 && !self.has_vertical_skew()
}
}
#[cfg(test)]
mod tests {
use super::AffineExt;
use peniko::kurbo::Affine;
#[test]
fn detects_positive_uniform_scale_without_skew() {
let transform = Affine::scale(2.0);
assert!(!transform.has_skew());
assert!(!transform.has_vertical_skew());
assert!(transform.is_positive_uniform_scale_without_skew());
assert!(transform.is_positive_uniform_scale_without_vertical_skew());
}
#[test]
fn rejects_positive_uniform_scale_without_skew_when_horizontally_skewed() {
let transform = Affine::new([2.0, 0.0, 0.25, 2.0, 0.0, 0.0]);
assert!(transform.has_skew());
assert!(!transform.has_vertical_skew());
assert!(!transform.is_positive_uniform_scale_without_skew());
assert!(transform.is_positive_uniform_scale_without_vertical_skew());
}
#[test]
fn rejects_positive_uniform_scale_without_vertical_skew_when_vertically_skewed() {
let transform = Affine::new([2.0, 0.25, 0.0, 2.0, 0.0, 0.0]);
assert!(transform.has_skew());
assert!(transform.has_vertical_skew());
assert!(!transform.is_positive_uniform_scale_without_skew());
assert!(!transform.is_positive_uniform_scale_without_vertical_skew());
}
#[test]
fn rejects_non_uniform_or_non_positive_scale() {
let non_uniform = Affine::new([2.0, 0.0, 0.0, 3.0, 0.0, 0.0]);
let flipped = Affine::new([-2.0, 0.0, 0.0, -2.0, 0.0, 0.0]);
assert!(non_uniform.has_non_unit_skew_or_scale());
assert!(!non_uniform.is_positive_uniform_scale_without_skew());
assert!(!non_uniform.is_positive_uniform_scale_without_vertical_skew());
assert!(flipped.has_non_unit_skew_or_scale());
assert!(!flipped.is_positive_uniform_scale_without_skew());
assert!(!flipped.is_positive_uniform_scale_without_vertical_skew());
}
#[test]
fn allows_unit_axis_flips() {
let flip_x = Affine::new([-1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
let flip_y = Affine::new([1.0, 0.0, 0.0, -1.0, 0.0, 0.0]);
let flip_xy = Affine::new([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0]);
assert!(!flip_x.has_non_unit_skew_or_scale());
assert!(!flip_y.has_non_unit_skew_or_scale());
assert!(!flip_xy.has_non_unit_skew_or_scale());
}
}