use strict_num::ApproxEqUlps;
use crate::{Align, AspectRatio};
pub trait FuzzyEq<Rhs: ?Sized = Self> {
fn fuzzy_eq(&self, other: &Rhs) -> bool;
#[inline]
fn fuzzy_ne(&self, other: &Rhs) -> bool {
!self.fuzzy_eq(other)
}
}
impl<T: FuzzyEq> FuzzyEq for Vec<T> {
fn fuzzy_eq(&self, other: &Self) -> bool {
if self.len() != other.len() {
return false;
}
for (a, b) in self.iter().zip(other.iter()) {
if a.fuzzy_ne(b) {
return false;
}
}
true
}
}
pub trait FuzzyZero: FuzzyEq {
fn is_fuzzy_zero(&self) -> bool;
}
impl FuzzyEq for f32 {
#[inline]
fn fuzzy_eq(&self, other: &f32) -> bool {
self.approx_eq_ulps(other, 4)
}
}
impl FuzzyEq for f64 {
#[inline]
fn fuzzy_eq(&self, other: &f64) -> bool {
self.approx_eq_ulps(other, 4)
}
}
impl FuzzyZero for f32 {
#[inline]
fn is_fuzzy_zero(&self) -> bool {
self.fuzzy_eq(&0.0)
}
}
impl FuzzyZero for f64 {
#[inline]
fn is_fuzzy_zero(&self) -> bool {
self.fuzzy_eq(&0.0)
}
}
pub trait IsValidLength {
fn is_valid_length(&self) -> bool;
}
impl IsValidLength for f64 {
#[inline]
fn is_valid_length(&self) -> bool {
*self > 0.0 && self.is_finite()
}
}
#[derive(Clone, Copy)]
pub struct Point<T> {
pub x: T,
pub y: T,
}
impl<T> Point<T> {
pub fn new(x: T, y: T) -> Self {
Point { x, y }
}
}
impl<T: std::fmt::Display> std::fmt::Debug for Point<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Point({} {})", self.x, self.y)
}
}
impl<T: std::fmt::Display> std::fmt::Display for Point<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Copy)]
pub struct Size {
width: f64,
height: f64,
}
impl Size {
#[inline]
pub fn new(width: f64, height: f64) -> Option<Self> {
if width.is_valid_length() && height.is_valid_length() {
Some(Size { width, height })
} else {
None
}
}
#[inline]
pub fn width(&self) -> f64 {
self.width
}
#[inline]
pub fn height(&self) -> f64 {
self.height
}
#[inline]
pub fn scale_to(&self, to: Self) -> Self {
size_scale_f64(*self, to, false)
}
#[inline]
pub fn expand_to(&self, to: Self) -> Self {
size_scale_f64(*self, to, true)
}
pub fn fit_view_box(&self, vb: &ViewBox) -> Self {
let s = vb.rect.size();
if vb.aspect.align == Align::None {
s
} else {
if vb.aspect.slice {
self.expand_to(s)
} else {
self.scale_to(s)
}
}
}
#[inline]
pub fn to_screen_size(&self) -> ScreenSize {
ScreenSize::new(
std::cmp::max(1, self.width().round() as u32),
std::cmp::max(1, self.height().round() as u32),
)
.unwrap()
}
#[inline]
pub fn to_rect(&self, x: f64, y: f64) -> Rect {
Rect::new(x, y, self.width, self.height).unwrap()
}
}
impl std::fmt::Debug for Size {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Size({} {})", self.width, self.height)
}
}
impl std::fmt::Display for Size {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl FuzzyEq for Size {
#[inline]
fn fuzzy_eq(&self, other: &Self) -> bool {
self.width.fuzzy_eq(&other.width) && self.height.fuzzy_eq(&other.height)
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq)]
pub struct ScreenSize {
width: u32,
height: u32,
}
impl ScreenSize {
#[inline]
pub fn new(width: u32, height: u32) -> Option<Self> {
if width > 0 && height > 0 {
Some(ScreenSize { width, height })
} else {
None
}
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[inline]
pub fn scale_to(&self, to: Self) -> Self {
size_scale(*self, to, false)
}
#[inline]
pub fn expand_to(&self, to: Self) -> Self {
size_scale(*self, to, true)
}
pub fn fit_view_box(&self, vb: &ViewBox) -> Self {
let s = vb.rect.to_screen_size();
if vb.aspect.align == Align::None {
s
} else {
if vb.aspect.slice {
self.expand_to(s)
} else {
self.scale_to(s)
}
}
}
#[inline]
pub fn to_size(&self) -> Size {
Size::new(self.width as f64, self.height as f64).unwrap()
}
}
impl std::fmt::Debug for ScreenSize {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ScreenSize({} {})", self.width, self.height)
}
}
impl std::fmt::Display for ScreenSize {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
fn size_scale(s1: ScreenSize, s2: ScreenSize, expand: bool) -> ScreenSize {
let rw = (s2.height as f64 * s1.width as f64 / s1.height as f64).ceil() as u32;
let with_h = if expand {
rw <= s2.width
} else {
rw >= s2.width
};
if !with_h {
ScreenSize::new(rw, s2.height).unwrap()
} else {
let h = (s2.width as f64 * s1.height as f64 / s1.width as f64).ceil() as u32;
ScreenSize::new(s2.width, h).unwrap()
}
}
fn size_scale_f64(s1: Size, s2: Size, expand: bool) -> Size {
let rw = s2.height * s1.width / s1.height;
let with_h = if expand {
rw <= s2.width
} else {
rw >= s2.width
};
if !with_h {
Size::new(rw, s2.height).unwrap()
} else {
let h = s2.width * s1.height / s1.width;
Size::new(s2.width, h).unwrap()
}
}
#[derive(Clone, Copy)]
pub struct PathBbox {
x: f64,
y: f64,
width: f64,
height: f64,
}
impl PathBbox {
#[inline]
pub fn new(x: f64, y: f64, width: f64, height: f64) -> Option<Self> {
if width.is_valid_length() || height.is_valid_length() {
Some(PathBbox {
x,
y,
width,
height,
})
} else {
None
}
}
#[inline]
pub fn new_bbox() -> Self {
PathBbox::new(f64::MAX, f64::MAX, 1.0, 1.0).unwrap()
}
#[inline]
pub fn x(&self) -> f64 {
self.x
}
#[inline]
pub fn y(&self) -> f64 {
self.y
}
#[inline]
pub fn width(&self) -> f64 {
self.width
}
#[inline]
pub fn height(&self) -> f64 {
self.height
}
#[inline]
pub fn left(&self) -> f64 {
self.x
}
#[inline]
pub fn right(&self) -> f64 {
self.x + self.width
}
#[inline]
pub fn top(&self) -> f64 {
self.y
}
#[inline]
pub fn bottom(&self) -> f64 {
self.y + self.height
}
#[inline]
pub fn expand(&self, r: PathBbox) -> Self {
if self.fuzzy_eq(&PathBbox::new_bbox()) {
r
} else {
let x1 = self.x().min(r.x());
let y1 = self.y().min(r.y());
let x2 = self.right().max(r.right());
let y2 = self.bottom().max(r.bottom());
PathBbox::new(x1, y1, x2 - x1, y2 - y1).unwrap()
}
}
pub fn bbox_transform(&self, bbox: Rect) -> Self {
let x = self.x() * bbox.width() + bbox.x();
let y = self.y() * bbox.height() + bbox.y();
let w = self.width() * bbox.width();
let h = self.height() * bbox.height();
PathBbox::new(x, y, w, h).unwrap()
}
pub fn transform(&self, ts: &Transform) -> Option<Self> {
use crate::pathdata::PathData;
if !ts.is_default() {
let path = PathData::from_rect(self.to_rect()?);
path.bbox_with_transform(*ts, None)
} else {
Some(*self)
}
}
pub fn to_rect(&self) -> Option<Rect> {
Rect::new(self.x, self.y, self.width, self.height)
}
}
impl FuzzyEq for PathBbox {
#[inline]
fn fuzzy_eq(&self, other: &Self) -> bool {
self.x.fuzzy_eq(&other.x)
&& self.y.fuzzy_eq(&other.y)
&& self.width.fuzzy_eq(&other.width)
&& self.height.fuzzy_eq(&other.height)
}
}
impl std::fmt::Debug for PathBbox {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"PathBbox({} {} {} {})",
self.x, self.y, self.width, self.height
)
}
}
impl std::fmt::Display for PathBbox {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Copy)]
pub struct Rect {
x: f64,
y: f64,
width: f64,
height: f64,
}
impl Rect {
#[inline]
pub fn new(x: f64, y: f64, width: f64, height: f64) -> Option<Self> {
if width.is_valid_length() && height.is_valid_length() {
Some(Rect {
x,
y,
width,
height,
})
} else {
None
}
}
#[inline]
pub fn new_bbox() -> Self {
Rect::new(f64::MAX, f64::MAX, 1.0, 1.0).unwrap()
}
#[inline]
pub fn size(&self) -> Size {
Size::new(self.width, self.height).unwrap()
}
#[inline]
pub fn x(&self) -> f64 {
self.x
}
#[inline]
pub fn y(&self) -> f64 {
self.y
}
#[inline]
pub fn width(&self) -> f64 {
self.width
}
#[inline]
pub fn height(&self) -> f64 {
self.height
}
#[inline]
pub fn left(&self) -> f64 {
self.x
}
#[inline]
pub fn right(&self) -> f64 {
self.x + self.width
}
#[inline]
pub fn top(&self) -> f64 {
self.y
}
#[inline]
pub fn bottom(&self) -> f64 {
self.y + self.height
}
#[inline]
pub fn translate(&self, tx: f64, ty: f64) -> Self {
Rect {
x: self.x + tx,
y: self.y + ty,
width: self.width,
height: self.height,
}
}
#[inline]
pub fn translate_to(&self, x: f64, y: f64) -> Self {
Rect {
x,
y,
width: self.width,
height: self.height,
}
}
#[inline]
pub fn contains(&self, x: f64, y: f64) -> bool {
if x < self.x || x > self.x + self.width - 1.0 {
return false;
}
if y < self.y || y > self.y + self.height - 1.0 {
return false;
}
true
}
#[inline]
pub fn expand(&self, r: Rect) -> Self {
if self.fuzzy_eq(&Rect::new_bbox()) {
r
} else {
let x1 = self.x().min(r.x());
let y1 = self.y().min(r.y());
let x2 = self.right().max(r.right());
let y2 = self.bottom().max(r.bottom());
Rect::new(x1, y1, x2 - x1, y2 - y1).unwrap()
}
}
pub fn bbox_transform(&self, bbox: Rect) -> Self {
let x = self.x() * bbox.width() + bbox.x();
let y = self.y() * bbox.height() + bbox.y();
let w = self.width() * bbox.width();
let h = self.height() * bbox.height();
Rect::new(x, y, w, h).unwrap()
}
pub fn transform(&self, ts: &Transform) -> Option<Self> {
use crate::pathdata::PathData;
if !ts.is_default() {
let path = PathData::from_rect(*self);
path.bbox_with_transform(*ts, None)
.and_then(|r| r.to_rect())
} else {
Some(*self)
}
}
#[inline]
pub fn to_path_bbox(&self) -> PathBbox {
PathBbox::new(self.x, self.y, self.width, self.height).unwrap()
}
#[inline]
pub fn to_screen_size(&self) -> ScreenSize {
self.size().to_screen_size()
}
#[inline]
pub fn to_screen_rect(&self) -> ScreenRect {
ScreenRect::new(
self.x() as i32,
self.y() as i32,
std::cmp::max(1, self.width().round() as u32),
std::cmp::max(1, self.height().round() as u32),
)
.unwrap()
}
}
impl FuzzyEq for Rect {
#[inline]
fn fuzzy_eq(&self, other: &Self) -> bool {
self.x.fuzzy_eq(&other.x)
&& self.y.fuzzy_eq(&other.y)
&& self.width.fuzzy_eq(&other.width)
&& self.height.fuzzy_eq(&other.height)
}
}
impl std::fmt::Debug for Rect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Rect({} {} {} {})",
self.x, self.y, self.width, self.height
)
}
}
impl std::fmt::Display for Rect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq)]
pub struct ScreenRect {
x: i32,
y: i32,
width: u32,
height: u32,
}
impl ScreenRect {
#[inline]
pub fn new(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
if width > 0 && height > 0 {
Some(ScreenRect {
x,
y,
width,
height,
})
} else {
None
}
}
#[inline]
pub fn size(&self) -> ScreenSize {
ScreenSize::new(self.width, self.height).unwrap()
}
#[inline]
pub fn x(&self) -> i32 {
self.x
}
#[inline]
pub fn y(&self) -> i32 {
self.y
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn left(&self) -> i32 {
self.x
}
#[inline]
pub fn right(&self) -> i32 {
self.x + self.width as i32
}
#[inline]
pub fn top(&self) -> i32 {
self.y
}
#[inline]
pub fn bottom(&self) -> i32 {
self.y + self.height as i32
}
#[inline]
pub fn translate(&self, tx: i32, ty: i32) -> Self {
ScreenRect {
x: self.x + tx,
y: self.y + ty,
width: self.width,
height: self.height,
}
}
#[inline]
pub fn translate_to(&self, x: i32, y: i32) -> Self {
ScreenRect {
x,
y,
width: self.width,
height: self.height,
}
}
#[inline]
pub fn contains(&self, x: i32, y: i32) -> bool {
if x < self.x || x > self.x + self.width as i32 - 1 {
return false;
}
if y < self.y || y > self.y + self.height as i32 - 1 {
return false;
}
true
}
#[inline]
pub fn fit_to_rect(&self, bounds: ScreenRect) -> Self {
let mut r = *self;
if r.x < 0 {
r.x = 0;
}
if r.y < 0 {
r.y = 0;
}
if r.right() > bounds.width as i32 {
r.width = std::cmp::max(1, bounds.width as i32 - r.x) as u32;
}
if r.bottom() > bounds.height as i32 {
r.height = std::cmp::max(1, bounds.height as i32 - r.y) as u32;
}
r
}
#[inline]
pub fn to_rect(&self) -> Rect {
Rect::new(
self.x as f64,
self.y as f64,
self.width as f64,
self.height as f64,
)
.unwrap()
}
}
impl std::fmt::Debug for ScreenRect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"ScreenRect({} {} {} {})",
self.x, self.y, self.width, self.height
)
}
}
impl std::fmt::Display for ScreenRect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub struct Transform {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
pub e: f64,
pub f: f64,
}
impl From<svgtypes::Transform> for Transform {
fn from(ts: svgtypes::Transform) -> Self {
Transform::new(ts.a, ts.b, ts.c, ts.d, ts.e, ts.f)
}
}
impl Transform {
#[inline]
pub fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
Transform { a, b, c, d, e, f }
}
#[inline]
pub fn new_translate(x: f64, y: f64) -> Self {
Transform::new(1.0, 0.0, 0.0, 1.0, x, y)
}
#[inline]
pub fn new_scale(sx: f64, sy: f64) -> Self {
Transform::new(sx, 0.0, 0.0, sy, 0.0, 0.0)
}
#[inline]
pub fn new_rotate(angle: f64) -> Self {
let v = angle.to_radians();
let a = v.cos();
let b = v.sin();
let c = -b;
let d = a;
Transform::new(a, b, c, d, 0.0, 0.0)
}
#[inline]
pub fn from_bbox(bbox: Rect) -> Self {
Self::new(bbox.width(), 0.0, 0.0, bbox.height(), bbox.x(), bbox.y())
}
#[inline]
pub fn translate(&mut self, x: f64, y: f64) {
self.append(&Transform::new_translate(x, y));
}
#[inline]
pub fn scale(&mut self, sx: f64, sy: f64) {
self.append(&Transform::new_scale(sx, sy));
}
#[inline]
pub fn rotate(&mut self, angle: f64) {
self.append(&Transform::new_rotate(angle));
}
#[inline]
pub fn rotate_at(&mut self, angle: f64, x: f64, y: f64) {
self.translate(x, y);
self.rotate(angle);
self.translate(-x, -y);
}
#[inline]
pub fn append(&mut self, other: &Transform) {
let ts = multiply(self, other);
self.a = ts.a;
self.b = ts.b;
self.c = ts.c;
self.d = ts.d;
self.e = ts.e;
self.f = ts.f;
}
#[inline]
pub fn prepend(&mut self, other: &Transform) {
let ts = multiply(other, self);
self.a = ts.a;
self.b = ts.b;
self.c = ts.c;
self.d = ts.d;
self.e = ts.e;
self.f = ts.f;
}
pub fn is_default(&self) -> bool {
self.a.fuzzy_eq(&1.0)
&& self.b.fuzzy_eq(&0.0)
&& self.c.fuzzy_eq(&0.0)
&& self.d.fuzzy_eq(&1.0)
&& self.e.fuzzy_eq(&0.0)
&& self.f.fuzzy_eq(&0.0)
}
#[inline]
pub fn get_translate(&self) -> (f64, f64) {
(self.e, self.f)
}
#[inline]
pub fn get_scale(&self) -> (f64, f64) {
let x_scale = (self.a * self.a + self.c * self.c).sqrt();
let y_scale = (self.b * self.b + self.d * self.d).sqrt();
(x_scale, y_scale)
}
#[inline]
pub fn apply(&self, x: f64, y: f64) -> (f64, f64) {
let new_x = self.a * x + self.c * y + self.e;
let new_y = self.b * x + self.d * y + self.f;
(new_x, new_y)
}
#[inline]
pub fn apply_to(&self, x: &mut f64, y: &mut f64) {
let tx = *x;
let ty = *y;
*x = self.a * tx + self.c * ty + self.e;
*y = self.b * tx + self.d * ty + self.f;
}
}
#[inline(never)]
fn multiply(ts1: &Transform, ts2: &Transform) -> Transform {
Transform {
a: ts1.a * ts2.a + ts1.c * ts2.b,
b: ts1.b * ts2.a + ts1.d * ts2.b,
c: ts1.a * ts2.c + ts1.c * ts2.d,
d: ts1.b * ts2.c + ts1.d * ts2.d,
e: ts1.a * ts2.e + ts1.c * ts2.f + ts1.e,
f: ts1.b * ts2.e + ts1.d * ts2.f + ts1.f,
}
}
impl Default for Transform {
#[inline]
fn default() -> Transform {
Transform::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
}
}
impl FuzzyEq for Transform {
fn fuzzy_eq(&self, other: &Self) -> bool {
self.a.fuzzy_eq(&other.a)
&& self.b.fuzzy_eq(&other.b)
&& self.c.fuzzy_eq(&other.c)
&& self.d.fuzzy_eq(&other.d)
&& self.e.fuzzy_eq(&other.e)
&& self.f.fuzzy_eq(&other.f)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ViewBox {
pub rect: Rect,
pub aspect: AspectRatio,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bbox_transform_1() {
let r = Rect::new(10.0, 20.0, 30.0, 40.0).unwrap();
assert!(r
.bbox_transform(Rect::new(0.2, 0.3, 0.4, 0.5).unwrap())
.fuzzy_eq(&Rect::new(4.2, 10.3, 12.0, 20.0).unwrap()));
}
}