mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 11:37:59 -06:00
184 lines
5.1 KiB
Rust
184 lines
5.1 KiB
Rust
//! A number of utility methods.
|
|
|
|
use kurbo::{BezPath, PathEl, Rect};
|
|
use siphasher::sip128::{Hasher128, SipHasher13};
|
|
use std::hash::Hash;
|
|
use std::ops::Sub;
|
|
|
|
pub(crate) trait OptionLog {
|
|
fn warn_none(self, f: &str) -> Self;
|
|
}
|
|
|
|
impl<T> OptionLog for Option<T> {
|
|
#[inline]
|
|
fn warn_none(self, _f: &str) -> Self {
|
|
self.or_else(|| {
|
|
warn!("{_f}");
|
|
|
|
None
|
|
})
|
|
}
|
|
}
|
|
|
|
const SCALAR_NEARLY_ZERO: f32 = 1.0 / (1 << 8) as f32;
|
|
|
|
/// A number of useful methods for f32 numbers.
|
|
pub trait Float32Ext: Sized + Sub<f32, Output = f32> + Copy + PartialOrd<f32> {
|
|
/// Whether the number is approximately 0.
|
|
fn is_nearly_zero(&self) -> bool {
|
|
self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO)
|
|
}
|
|
|
|
/// Whether the number is nearly equal to another number.
|
|
fn is_nearly_equal(&self, other: f32) -> bool {
|
|
(*self - other).is_nearly_zero()
|
|
}
|
|
|
|
/// Whether the number is nearly equal to another number.
|
|
fn is_nearly_less_or_equal(&self, other: f32) -> bool {
|
|
(*self - other).is_nearly_zero() || *self < other
|
|
}
|
|
|
|
/// Whether the number is nearly equal to another number.
|
|
fn is_nearly_greater_or_equal(&self, other: f32) -> bool {
|
|
(*self - other).is_nearly_zero() || *self > other
|
|
}
|
|
|
|
/// Whether the number is approximately 0, with a given tolerance.
|
|
fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool;
|
|
}
|
|
|
|
impl Float32Ext for f32 {
|
|
fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool {
|
|
debug_assert!(tolerance >= 0.0, "tolerance must be non-negative");
|
|
|
|
self.abs() <= tolerance
|
|
}
|
|
}
|
|
|
|
/// A number of useful methods for f64 numbers.
|
|
pub trait Float64Ext: Sized + Sub<f64, Output = f64> + Copy + PartialOrd<f64> {
|
|
/// Whether the number is approximately 0.
|
|
fn is_nearly_zero(&self) -> bool {
|
|
self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO as f64)
|
|
}
|
|
|
|
/// Whether the number is nearly equal to another number.
|
|
fn is_nearly_equal(&self, other: f64) -> bool {
|
|
(*self - other).is_nearly_zero()
|
|
}
|
|
|
|
/// Whether the number is nearly equal to another number.
|
|
fn is_nearly_less_or_equal(&self, other: f64) -> bool {
|
|
(*self - other).is_nearly_zero() || *self < other
|
|
}
|
|
|
|
/// Whether the number is nearly equal to another number.
|
|
fn is_nearly_greater_or_equal(&self, other: f64) -> bool {
|
|
(*self - other).is_nearly_zero() || *self > other
|
|
}
|
|
|
|
/// Whether the number is approximately 0, with a given tolerance.
|
|
fn is_nearly_zero_within_tolerance(&self, tolerance: f64) -> bool;
|
|
}
|
|
|
|
impl Float64Ext for f64 {
|
|
fn is_nearly_zero_within_tolerance(&self, tolerance: f64) -> bool {
|
|
debug_assert!(tolerance >= 0.0, "tolerance must be non-negative");
|
|
|
|
self.abs() <= tolerance
|
|
}
|
|
}
|
|
|
|
pub(crate) trait PointExt: Sized {
|
|
fn x(&self) -> f32;
|
|
fn y(&self) -> f32;
|
|
|
|
fn nearly_same(&self, other: Self) -> bool {
|
|
self.x().is_nearly_equal(other.x()) && self.y().is_nearly_equal(other.y())
|
|
}
|
|
}
|
|
|
|
impl PointExt for kurbo::Point {
|
|
fn x(&self) -> f32 {
|
|
self.x as f32
|
|
}
|
|
|
|
fn y(&self) -> f32 {
|
|
self.y as f32
|
|
}
|
|
}
|
|
|
|
/// Calculate a 128-bit siphash of a value.
|
|
pub(crate) fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
|
|
let mut state = SipHasher13::new();
|
|
value.hash(&mut state);
|
|
state.finish128().as_u128()
|
|
}
|
|
|
|
pub(crate) trait BezPathExt {
|
|
fn fast_bounding_box(&self) -> Rect;
|
|
}
|
|
|
|
impl BezPathExt for BezPath {
|
|
fn fast_bounding_box(&self) -> Rect {
|
|
let mut min_x = f64::INFINITY;
|
|
let mut min_y = f64::INFINITY;
|
|
let mut max_x = f64::NEG_INFINITY;
|
|
let mut max_y = f64::NEG_INFINITY;
|
|
|
|
let mut include = |x: f64, y: f64| {
|
|
min_x = min_x.min(x);
|
|
min_y = min_y.min(y);
|
|
max_x = max_x.max(x);
|
|
max_y = max_y.max(y);
|
|
};
|
|
|
|
for el in self.elements() {
|
|
match *el {
|
|
PathEl::MoveTo(p) | PathEl::LineTo(p) => include(p.x, p.y),
|
|
PathEl::QuadTo(p1, p2) => {
|
|
include(p1.x, p1.y);
|
|
include(p2.x, p2.y);
|
|
}
|
|
PathEl::CurveTo(p1, p2, p3) => {
|
|
include(p1.x, p1.y);
|
|
include(p2.x, p2.y);
|
|
include(p3.x, p3.y);
|
|
}
|
|
PathEl::ClosePath => {}
|
|
}
|
|
}
|
|
|
|
if min_x > max_x {
|
|
Rect::ZERO
|
|
} else {
|
|
Rect::new(min_x, min_y, max_x, max_y)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extension methods for converting a [`hayro_syntax::transform::Transform`] to a [`kurbo::Affine`].
|
|
pub trait TransformExt {
|
|
/// Convert to a `kurbo::Affine`.
|
|
fn to_kurbo(&self) -> kurbo::Affine;
|
|
}
|
|
|
|
impl TransformExt for hayro_syntax::transform::Transform {
|
|
fn to_kurbo(&self) -> kurbo::Affine {
|
|
kurbo::Affine::new(self.as_coeffs())
|
|
}
|
|
}
|
|
|
|
/// Extension methods for rectangles.
|
|
pub trait RectExt {
|
|
/// Convert the rectangle to a `kurbo` rectangle.
|
|
fn to_kurbo(&self) -> Rect;
|
|
}
|
|
|
|
impl RectExt for hayro_syntax::object::Rect {
|
|
fn to_kurbo(&self) -> Rect {
|
|
Rect::new(self.x0, self.y0, self.x1, self.y1)
|
|
}
|
|
}
|