use std::{cmp, f64, fmt};
use usvg;
pub use usvg::{Rect, Size};
#[inline]
pub(crate) fn f64_bound(min: f64, val: f64, max: f64) -> f64 {
debug_assert!(min.is_finite());
debug_assert!(val.is_finite());
debug_assert!(max.is_finite());
if val > max {
return max;
} else if val < min {
return min;
}
val
}
#[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)
}
#[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)
}
}
pub trait SizeExt {
fn to_screen_size(&self) -> ScreenSize;
}
impl SizeExt for Size {
#[inline]
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()
}
}
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()
}
}
pub trait RectExt: Sized {
fn bbox_transform(&self, bbox: Rect) -> Self;
fn transform(&self, ts: &usvg::Transform) -> Option<Self>;
fn to_screen_size(&self) -> ScreenSize;
fn to_screen_rect(&self) -> ScreenRect;
}
impl RectExt for Rect {
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()
}
fn transform(&self, ts: &usvg::Transform) -> Option<Self> {
if !ts.is_default() {
let path = &[
usvg::PathSegment::MoveTo {
x: self.x(), y: self.y()
},
usvg::PathSegment::LineTo {
x: self.right(), y: self.y()
},
usvg::PathSegment::LineTo {
x: self.right(), y: self.bottom()
},
usvg::PathSegment::LineTo {
x: self.x(), y: self.bottom()
},
usvg::PathSegment::ClosePath,
];
usvg::SubPathData(path).bbox_with_transform(*ts, None)
} else {
Some(*self)
}
}
#[inline]
fn to_screen_size(&self) -> ScreenSize {
self.size().to_screen_size()
}
#[inline]
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()
}
}
#[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::*;
use usvg::FuzzyEq;
#[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()));
}
}