use std::{cmp, f64, fmt};
use svgtypes::FuzzyEq;
use crate::{tree, IsValidLength};
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct Line {
pub x1: f64,
pub y1: f64,
pub x2: f64,
pub y2: f64,
}
impl Line {
#[inline]
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Line {
Line { x1, y1, x2, y2 }
}
#[inline]
pub fn length(&self) -> f64 {
let x = self.x2 - self.x1;
let y = self.y2 - self.y1;
(x*x + y*y).sqrt()
}
pub fn set_length(&mut self, len: f64) {
let x = self.x2 - self.x1;
let y = self.y2 - self.y1;
let len2 = (x*x + y*y).sqrt();
let line = Line {
x1: self.x1, y1: self.y1,
x2: self.x1 + x/len2, y2: self.y1 + y/len2
};
self.x2 = self.x1 + (line.x2 - line.x1) * len;
self.y2 = self.y1 + (line.y2 - line.y1) * len;
}
}
#[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: fmt::Display> fmt::Debug for Point<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Point({} {})", self.x, self.y)
}
}
impl<T: fmt::Display> fmt::Display for Point<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> 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: &tree::ViewBox) -> Self {
let s = vb.rect.size();
if vb.aspect.align == tree::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(
cmp::max(1, self.width().round() as u32),
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 fmt::Debug for Size {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Size({} {})", self.width, self.height)
}
}
impl fmt::Display for Size {
fn fmt(&self, f: &mut fmt::Formatter) -> 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: &tree::ViewBox) -> Self {
let s = vb.rect.to_screen_size();
if vb.aspect.align == tree::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 fmt::Debug for ScreenSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ScreenSize({} {})", self.width, self.height)
}
}
impl fmt::Display for ScreenSize {
fn fmt(&self, f: &mut fmt::Formatter) -> 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 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 {
#[inline]
fn f64_min(v1: f64, v2: f64) -> f64 {
if v1 < v2 { v1 } else { v2 }
}
#[inline]
fn f64_max(v1: f64, v2: f64) -> f64 {
if v1 > v2 { v1 } else { v2 }
}
if self.fuzzy_eq(&Rect::new_bbox()) {
r
} else {
let x1 = f64_min(self.x(), r.x());
let y1 = f64_min(self.y(), r.y());
let x2 = f64_max(self.right(), r.right());
let y2 = f64_max(self.bottom(), 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: &tree::Transform) -> Option<Self> {
if !ts.is_default() {
let path = &[
tree::PathSegment::MoveTo {
x: self.x(), y: self.y()
},
tree::PathSegment::LineTo {
x: self.right(), y: self.y()
},
tree::PathSegment::LineTo {
x: self.right(), y: self.bottom()
},
tree::PathSegment::LineTo {
x: self.x(), y: self.bottom()
},
tree::PathSegment::ClosePath,
];
tree::SubPathData(path).bbox_with_transform(*ts, None)
} else {
Some(*self)
}
}
#[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,
cmp::max(1, self.width().round() as u32),
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 fmt::Debug for Rect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Rect({} {} {} {})", self.x, self.y, self.width, self.height)
}
}
impl fmt::Display for Rect {
fn fmt(&self, f: &mut fmt::Formatter) -> 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 = cmp::max(1, bounds.width as i32 - r.x) as u32;
}
if r.bottom() > bounds.height as i32 {
r.height = 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 fmt::Debug for ScreenRect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ScreenRect({} {} {} {})", self.x, self.y, self.width, self.height)
}
}
impl fmt::Display for ScreenRect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[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()));
}
}