1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 
5 use std::{cmp, f64, fmt};
6 
7 use svgtypes::FuzzyEq;
8 
9 use crate::{tree, IsValidLength};
10 
11 
12 /// Line representation.
13 #[allow(missing_docs)]
14 #[derive(Clone, Copy, Debug)]
15 pub(crate) struct Line {
16     pub x1: f64,
17     pub y1: f64,
18     pub x2: f64,
19     pub y2: f64,
20 }
21 
22 impl Line {
23     /// Creates a new line.
24     #[inline]
new(x1: f64, y1: f64, x2: f64, y2: f64) -> Line25     pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Line {
26         Line { x1, y1, x2, y2 }
27     }
28 
29     /// Calculates the line length.
30     #[inline]
length(&self) -> f6431     pub fn length(&self) -> f64 {
32         let x = self.x2 - self.x1;
33         let y = self.y2 - self.y1;
34         (x*x + y*y).sqrt()
35     }
36 
37     /// Sets the line length.
set_length(&mut self, len: f64)38     pub fn set_length(&mut self, len: f64) {
39         let x = self.x2 - self.x1;
40         let y = self.y2 - self.y1;
41         let len2 = (x*x + y*y).sqrt();
42         let line = Line {
43             x1: self.x1, y1: self.y1,
44             x2: self.x1 + x/len2, y2: self.y1 + y/len2
45         };
46 
47         self.x2 = self.x1 + (line.x2 - line.x1) * len;
48         self.y2 = self.y1 + (line.y2 - line.y1) * len;
49     }
50 }
51 
52 
53 /// A 2D point representation.
54 #[derive(Clone, Copy)]
55 pub struct Point<T> {
56     /// Position along the X-axis.
57     pub x: T,
58 
59     /// Position along the Y-axis.
60     pub y: T,
61 }
62 
63 impl<T> Point<T> {
64     /// Create a new point.
new(x: T, y: T) -> Self65     pub fn new(x: T, y: T) -> Self {
66         Point { x, y }
67     }
68 }
69 
70 impl<T: fmt::Display> fmt::Debug for Point<T> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result71     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72         write!(f, "Point({} {})", self.x, self.y)
73     }
74 }
75 
76 impl<T: fmt::Display> fmt::Display for Point<T> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result77     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78         write!(f, "{:?}", self)
79     }
80 }
81 
82 
83 /// A 2D size representation.
84 ///
85 /// Width and height are guarantee to be > 0.
86 #[derive(Clone, Copy)]
87 pub struct Size {
88     width: f64,
89     height: f64,
90 }
91 
92 impl Size {
93     /// Creates a new `Size` from values.
94     #[inline]
new(width: f64, height: f64) -> Option<Self>95     pub fn new(width: f64, height: f64) -> Option<Self> {
96         if width.is_valid_length() && height.is_valid_length() {
97             Some(Size { width, height })
98         } else {
99             None
100         }
101     }
102 
103     /// Returns width.
104     #[inline]
width(&self) -> f64105     pub fn width(&self) -> f64 {
106         self.width
107     }
108 
109     /// Returns height.
110     #[inline]
height(&self) -> f64111     pub fn height(&self) -> f64 {
112         self.height
113     }
114 
115     /// Converts `Size` to `ScreenSize`.
116     #[inline]
to_screen_size(&self) -> ScreenSize117     pub fn to_screen_size(&self) -> ScreenSize {
118         ScreenSize::new(
119             cmp::max(1, self.width().round() as u32),
120             cmp::max(1, self.height().round() as u32),
121         ).unwrap()
122     }
123 
124     /// Converts the current size to `Rect` at provided position.
125     #[inline]
to_rect(&self, x: f64, y: f64) -> Rect126     pub fn to_rect(&self, x: f64, y: f64) -> Rect {
127         Rect::new(x, y, self.width, self.height).unwrap()
128     }
129 }
130 
131 impl fmt::Debug for Size {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result132     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133         write!(f, "Size({} {})", self.width, self.height)
134     }
135 }
136 
137 impl fmt::Display for Size {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result138     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139         write!(f, "{:?}", self)
140     }
141 }
142 
143 impl FuzzyEq for Size {
144     #[inline]
fuzzy_eq(&self, other: &Self) -> bool145     fn fuzzy_eq(&self, other: &Self) -> bool {
146            self.width.fuzzy_eq(&other.width)
147         && self.height.fuzzy_eq(&other.height)
148     }
149 }
150 
151 
152 /// A 2D screen size representation.
153 ///
154 /// Width and height are guarantee to be > 0.
155 #[allow(missing_docs)]
156 #[derive(Clone, Copy, PartialEq)]
157 pub struct ScreenSize {
158     width: u32,
159     height: u32,
160 }
161 
162 impl ScreenSize {
163     /// Creates a new `ScreenSize` from values.
164     #[inline]
new(width: u32, height: u32) -> Option<Self>165     pub fn new(width: u32, height: u32) -> Option<Self> {
166         if width > 0 && height > 0 {
167             Some(ScreenSize { width, height })
168         } else {
169             None
170         }
171     }
172 
173     /// Returns width.
174     #[inline]
width(&self) -> u32175     pub fn width(&self) -> u32 {
176         self.width
177     }
178 
179     /// Returns height.
180     #[inline]
height(&self) -> u32181     pub fn height(&self) -> u32 {
182         self.height
183     }
184 
185     /// Returns width and height as a tuple.
186     #[inline]
dimensions(&self) -> (u32, u32)187     pub fn dimensions(&self) -> (u32, u32) {
188         (self.width, self.height)
189     }
190 
191     /// Scales current size to specified size.
192     #[inline]
scale_to(&self, to: Self) -> Self193     pub fn scale_to(&self, to: Self) -> Self {
194         size_scale(*self, to, false)
195     }
196 
197     /// Expands current size to specified size.
198     #[inline]
expand_to(&self, to: Self) -> Self199     pub fn expand_to(&self, to: Self) -> Self {
200         size_scale(*self, to, true)
201     }
202 
203     /// Fits size into a viewbox.
fit_view_box(&self, vb: &tree::ViewBox) -> Self204     pub fn fit_view_box(&self, vb: &tree::ViewBox) -> Self {
205         let s = vb.rect.to_screen_size();
206 
207         if vb.aspect.align == tree::Align::None {
208             s
209         } else {
210             if vb.aspect.slice {
211                 self.expand_to(s)
212             } else {
213                 self.scale_to(s)
214             }
215         }
216     }
217 
218     /// Converts the current `ScreenSize` to `Size`.
219     #[inline]
to_size(&self) -> Size220     pub fn to_size(&self) -> Size {
221         // Can't fail, because `ScreenSize` is always valid.
222         Size::new(self.width as f64, self.height as f64).unwrap()
223     }
224 }
225 
226 impl fmt::Debug for ScreenSize {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result227     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228         write!(f, "ScreenSize({} {})", self.width, self.height)
229     }
230 }
231 
232 impl fmt::Display for ScreenSize {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result233     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234         write!(f, "{:?}", self)
235     }
236 }
237 
size_scale( s1: ScreenSize, s2: ScreenSize, expand: bool, ) -> ScreenSize238 fn size_scale(
239     s1: ScreenSize,
240     s2: ScreenSize,
241     expand: bool,
242 ) -> ScreenSize {
243     let rw = (s2.height as f64 * s1.width as f64 / s1.height as f64).ceil() as u32;
244     let with_h = if expand { rw <= s2.width } else { rw >= s2.width };
245     if !with_h {
246         ScreenSize::new(rw, s2.height).unwrap()
247     } else {
248         let h = (s2.width as f64 * s1.height as f64 / s1.width as f64).ceil() as u32;
249         ScreenSize::new(s2.width, h).unwrap()
250     }
251 }
252 
253 
254 /// A rect representation.
255 ///
256 /// Width and height are guarantee to be > 0.
257 #[derive(Clone, Copy)]
258 pub struct Rect {
259     x: f64,
260     y: f64,
261     width: f64,
262     height: f64,
263 }
264 
265 impl Rect {
266     /// Creates a new `Rect` from values.
267     #[inline]
new(x: f64, y: f64, width: f64, height: f64) -> Option<Self>268     pub fn new(x: f64, y: f64, width: f64, height: f64) -> Option<Self> {
269         if width.is_valid_length() && height.is_valid_length() {
270             Some(Rect { x, y, width, height })
271         } else {
272             None
273         }
274     }
275 
276     /// Creates a new `Rect` for bounding box calculation.
277     ///
278     /// Shorthand for `Rect::new(f64::MAX, f64::MAX, 1.0, 1.0)`.
279     #[inline]
new_bbox() -> Self280     pub fn new_bbox() -> Self {
281         Rect::new(f64::MAX, f64::MAX, 1.0, 1.0).unwrap()
282     }
283 
284     /// Returns rect's size.
285     #[inline]
size(&self) -> Size286     pub fn size(&self) -> Size {
287         Size::new(self.width, self.height).unwrap()
288     }
289 
290     /// Returns rect's X position.
291     #[inline]
x(&self) -> f64292     pub fn x(&self) -> f64 {
293         self.x
294     }
295 
296     /// Returns rect's Y position.
297     #[inline]
y(&self) -> f64298     pub fn y(&self) -> f64 {
299         self.y
300     }
301 
302     /// Returns rect's width.
303     #[inline]
width(&self) -> f64304     pub fn width(&self) -> f64 {
305         self.width
306     }
307 
308     /// Returns rect's height.
309     #[inline]
height(&self) -> f64310     pub fn height(&self) -> f64 {
311         self.height
312     }
313 
314     /// Returns rect's left edge position.
315     #[inline]
left(&self) -> f64316     pub fn left(&self) -> f64 {
317         self.x
318     }
319 
320     /// Returns rect's right edge position.
321     #[inline]
right(&self) -> f64322     pub fn right(&self) -> f64 {
323         self.x + self.width
324     }
325 
326     /// Returns rect's top edge position.
327     #[inline]
top(&self) -> f64328     pub fn top(&self) -> f64 {
329         self.y
330     }
331 
332     /// Returns rect's bottom edge position.
333     #[inline]
bottom(&self) -> f64334     pub fn bottom(&self) -> f64 {
335         self.y + self.height
336     }
337 
338     /// Translates the rect by the specified offset.
339     #[inline]
translate(&self, tx: f64, ty: f64) -> Self340     pub fn translate(&self, tx: f64, ty: f64) -> Self {
341         Rect {
342             x: self.x + tx,
343             y: self.y + ty,
344             width: self.width,
345             height: self.height,
346         }
347     }
348 
349     /// Translates the rect to the specified position.
350     #[inline]
translate_to(&self, x: f64, y: f64) -> Self351     pub fn translate_to(&self, x: f64, y: f64) -> Self {
352         Rect {
353             x,
354             y,
355             width: self.width,
356             height: self.height,
357         }
358     }
359 
360     /// Checks that the rect contains a point.
361     #[inline]
contains(&self, x: f64, y: f64) -> bool362     pub fn contains(&self, x: f64, y: f64) -> bool {
363         if x < self.x || x > self.x + self.width - 1.0 {
364             return false;
365         }
366 
367         if y < self.y || y > self.y + self.height - 1.0 {
368             return false;
369         }
370 
371         true
372     }
373 
374     /// Expands the `Rect` to the provided size.
375     #[inline]
expand(&self, r: Rect) -> Self376     pub fn expand(&self, r: Rect) -> Self {
377         #[inline]
378         fn f64_min(v1: f64, v2: f64) -> f64 {
379             if v1 < v2 { v1 } else { v2 }
380         }
381 
382         #[inline]
383         fn f64_max(v1: f64, v2: f64) -> f64 {
384             if v1 > v2 { v1 } else { v2 }
385         }
386 
387         if self.fuzzy_eq(&Rect::new_bbox()) {
388             r
389         } else {
390             let x1 = f64_min(self.x(), r.x());
391             let y1 = f64_min(self.y(), r.y());
392 
393             let x2 = f64_max(self.right(), r.right());
394             let y2 = f64_max(self.bottom(), r.bottom());
395 
396             Rect::new(x1, y1, x2 - x1, y2 - y1).unwrap()
397         }
398     }
399 
400     /// Transforms the `Rect` using the provided `bbox`.
bbox_transform(&self, bbox: Rect) -> Self401     pub fn bbox_transform(&self, bbox: Rect) -> Self {
402         let x = self.x() * bbox.width() + bbox.x();
403         let y = self.y() * bbox.height() + bbox.y();
404         let w = self.width() * bbox.width();
405         let h = self.height() * bbox.height();
406         Rect::new(x, y, w, h).unwrap()
407     }
408 
409     /// Transforms the `Rect` using the provided `Transform`.
410     ///
411     /// This method is expensive.
transform(&self, ts: &tree::Transform) -> Option<Self>412     pub fn transform(&self, ts: &tree::Transform) -> Option<Self> {
413         if !ts.is_default() {
414             let path = &[
415                 tree::PathSegment::MoveTo {
416                     x: self.x(), y: self.y()
417                 },
418                 tree::PathSegment::LineTo {
419                     x: self.right(), y: self.y()
420                 },
421                 tree::PathSegment::LineTo {
422                     x: self.right(), y: self.bottom()
423                 },
424                 tree::PathSegment::LineTo {
425                     x: self.x(), y: self.bottom()
426                 },
427                 tree::PathSegment::ClosePath,
428             ];
429 
430             tree::SubPathData(path).bbox_with_transform(*ts, None)
431         } else {
432             Some(*self)
433         }
434     }
435 
436     /// Returns rect's size in screen units.
437     #[inline]
to_screen_size(&self) -> ScreenSize438     pub fn to_screen_size(&self) -> ScreenSize {
439         self.size().to_screen_size()
440     }
441 
442     /// Returns rect in screen units.
443     #[inline]
to_screen_rect(&self) -> ScreenRect444     pub fn to_screen_rect(&self) -> ScreenRect {
445         ScreenRect::new(
446             self.x() as i32,
447             self.y() as i32,
448             cmp::max(1, self.width().round() as u32),
449             cmp::max(1, self.height().round() as u32),
450         ).unwrap()
451     }
452 }
453 
454 impl FuzzyEq for Rect {
455     #[inline]
fuzzy_eq(&self, other: &Self) -> bool456     fn fuzzy_eq(&self, other: &Self) -> bool {
457            self.x.fuzzy_eq(&other.x)
458         && self.y.fuzzy_eq(&other.y)
459         && self.width.fuzzy_eq(&other.width)
460         && self.height.fuzzy_eq(&other.height)
461     }
462 }
463 
464 impl fmt::Debug for Rect {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result465     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466         write!(f, "Rect({} {} {} {})", self.x, self.y, self.width, self.height)
467     }
468 }
469 
470 impl fmt::Display for Rect {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result471     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
472         write!(f, "{:?}", self)
473     }
474 }
475 
476 
477 /// A 2D screen rect representation.
478 ///
479 /// Width and height are guarantee to be > 0.
480 #[allow(missing_docs)]
481 #[derive(Clone, Copy, PartialEq)]
482 pub struct ScreenRect {
483     x: i32,
484     y: i32,
485     width: u32,
486     height: u32,
487 }
488 
489 impl ScreenRect {
490     /// Creates a new `Rect` from values.
491     #[inline]
new(x: i32, y: i32, width: u32, height: u32) -> Option<Self>492     pub fn new(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
493         if width > 0 && height > 0 {
494             Some(ScreenRect { x, y, width, height })
495         } else {
496             None
497         }
498     }
499 
500     /// Returns rect's size.
501     #[inline]
size(&self) -> ScreenSize502     pub fn size(&self) -> ScreenSize {
503         // Can't fail, because `ScreenSize` is always valid.
504         ScreenSize::new(self.width, self.height).unwrap()
505     }
506 
507     /// Returns rect's X position.
508     #[inline]
x(&self) -> i32509     pub fn x(&self) -> i32 {
510         self.x
511     }
512 
513     /// Returns rect's Y position.
514     #[inline]
y(&self) -> i32515     pub fn y(&self) -> i32 {
516         self.y
517     }
518 
519     /// Returns rect's width.
520     #[inline]
width(&self) -> u32521     pub fn width(&self) -> u32 {
522         self.width
523     }
524 
525     /// Returns rect's height.
526     #[inline]
height(&self) -> u32527     pub fn height(&self) -> u32 {
528         self.height
529     }
530 
531     /// Returns rect's left edge position.
532     #[inline]
left(&self) -> i32533     pub fn left(&self) -> i32 {
534         self.x
535     }
536 
537     /// Returns rect's right edge position.
538     #[inline]
right(&self) -> i32539     pub fn right(&self) -> i32 {
540         self.x + self.width as i32
541     }
542 
543     /// Returns rect's top edge position.
544     #[inline]
top(&self) -> i32545     pub fn top(&self) -> i32 {
546         self.y
547     }
548 
549     /// Returns rect's bottom edge position.
550     #[inline]
bottom(&self) -> i32551     pub fn bottom(&self) -> i32 {
552         self.y + self.height as i32
553     }
554 
555     /// Translates the rect by the specified offset.
556     #[inline]
translate(&self, tx: i32, ty: i32) -> Self557     pub fn translate(&self, tx: i32, ty: i32) -> Self {
558         ScreenRect {
559             x: self.x + tx,
560             y: self.y + ty,
561             width: self.width,
562             height: self.height,
563         }
564     }
565 
566     /// Translates the rect to the specified position.
567     #[inline]
translate_to(&self, x: i32, y: i32) -> Self568     pub fn translate_to(&self, x: i32, y: i32) -> Self {
569         ScreenRect {
570             x,
571             y,
572             width: self.width,
573             height: self.height,
574         }
575     }
576 
577     /// Checks that rect contains a point.
578     #[inline]
contains(&self, x: i32, y: i32) -> bool579     pub fn contains(&self, x: i32, y: i32) -> bool {
580         if x < self.x || x > self.x + self.width as i32 - 1 {
581             return false;
582         }
583 
584         if y < self.y || y > self.y + self.height as i32 - 1 {
585             return false;
586         }
587 
588         true
589     }
590 
591     /// Fits the current rect into the specified bounds.
592     #[inline]
fit_to_rect(&self, bounds: ScreenRect) -> Self593     pub fn fit_to_rect(&self, bounds: ScreenRect) -> Self {
594         let mut r = *self;
595 
596         if r.x < 0 { r.x = 0; }
597         if r.y < 0 { r.y = 0; }
598 
599         if r.right() > bounds.width as i32 {
600             r.width = cmp::max(1, bounds.width as i32 - r.x) as u32;
601         }
602 
603         if r.bottom() > bounds.height as i32 {
604             r.height = cmp::max(1, bounds.height as i32 - r.y) as u32;
605         }
606 
607         r
608     }
609 
610     /// Converts into `Rect`.
611     #[inline]
to_rect(&self) -> Rect612     pub fn to_rect(&self) -> Rect {
613         // Can't fail, because `ScreenRect` is always valid.
614         Rect::new(self.x as f64, self.y as f64, self.width as f64, self.height as f64).unwrap()
615     }
616 }
617 
618 impl fmt::Debug for ScreenRect {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result619     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
620         write!(f, "ScreenRect({} {} {} {})", self.x, self.y, self.width, self.height)
621     }
622 }
623 
624 impl fmt::Display for ScreenRect {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result625     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
626         write!(f, "{:?}", self)
627     }
628 }
629 
630 
631 #[cfg(test)]
632 mod tests {
633     use super::*;
634 
635     #[test]
bbox_transform_1()636     fn bbox_transform_1() {
637         let r = Rect::new(10.0, 20.0, 30.0, 40.0).unwrap();
638         assert!(r.bbox_transform(Rect::new(0.2, 0.3, 0.4, 0.5).unwrap())
639             .fuzzy_eq(&Rect::new(4.2, 10.3, 12.0, 20.0).unwrap()));
640     }
641 }
642