| 1 | // Copyright 2018 the Resvg Authors |
| 2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
| 3 | |
| 4 | use strict_num::ApproxEqUlps; |
| 5 | use svgtypes::{Align, AspectRatio}; |
| 6 | pub use tiny_skia_path::{NonZeroRect, Rect, Size, Transform}; |
| 7 | |
| 8 | /// Approximate zero equality comparisons. |
| 9 | pub trait ApproxZeroUlps: ApproxEqUlps { |
| 10 | /// Checks if the number is approximately zero. |
| 11 | fn approx_zero_ulps(&self, ulps: <Self::Flt as strict_num::Ulps>::U) -> bool; |
| 12 | } |
| 13 | |
| 14 | impl ApproxZeroUlps for f32 { |
| 15 | fn approx_zero_ulps(&self, ulps: i32) -> bool { |
| 16 | self.approx_eq_ulps(&0.0, ulps) |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | impl ApproxZeroUlps for f64 { |
| 21 | fn approx_zero_ulps(&self, ulps: i64) -> bool { |
| 22 | self.approx_eq_ulps(&0.0, ulps) |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | /// Checks that the current number is > 0. |
| 27 | pub(crate) trait IsValidLength { |
| 28 | /// Checks that the current number is > 0. |
| 29 | fn is_valid_length(&self) -> bool; |
| 30 | } |
| 31 | |
| 32 | impl IsValidLength for f32 { |
| 33 | #[inline ] |
| 34 | fn is_valid_length(&self) -> bool { |
| 35 | *self > 0.0 && self.is_finite() |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | impl IsValidLength for f64 { |
| 40 | #[inline ] |
| 41 | fn is_valid_length(&self) -> bool { |
| 42 | *self > 0.0 && self.is_finite() |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | /// View box. |
| 47 | #[derive (Clone, Copy, Debug)] |
| 48 | pub(crate) struct ViewBox { |
| 49 | /// Value of the `viewBox` attribute. |
| 50 | pub rect: NonZeroRect, |
| 51 | |
| 52 | /// Value of the `preserveAspectRatio` attribute. |
| 53 | pub aspect: AspectRatio, |
| 54 | } |
| 55 | |
| 56 | impl ViewBox { |
| 57 | /// Converts `viewBox` into `Transform`. |
| 58 | pub fn to_transform(&self, img_size: Size) -> Transform { |
| 59 | let vr = self.rect; |
| 60 | |
| 61 | let sx = img_size.width() / vr.width(); |
| 62 | let sy = img_size.height() / vr.height(); |
| 63 | |
| 64 | let (sx, sy) = if self.aspect.align == Align::None { |
| 65 | (sx, sy) |
| 66 | } else { |
| 67 | let s = if self.aspect.slice { |
| 68 | if sx < sy { |
| 69 | sy |
| 70 | } else { |
| 71 | sx |
| 72 | } |
| 73 | } else { |
| 74 | if sx > sy { |
| 75 | sy |
| 76 | } else { |
| 77 | sx |
| 78 | } |
| 79 | }; |
| 80 | |
| 81 | (s, s) |
| 82 | }; |
| 83 | |
| 84 | let x = -vr.x() * sx; |
| 85 | let y = -vr.y() * sy; |
| 86 | let w = img_size.width() - vr.width() * sx; |
| 87 | let h = img_size.height() - vr.height() * sy; |
| 88 | |
| 89 | let (tx, ty) = aligned_pos(self.aspect.align, x, y, w, h); |
| 90 | Transform::from_row(sx, 0.0, 0.0, sy, tx, ty) |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | /// A bounding box calculator. |
| 95 | #[derive (Clone, Copy, Debug)] |
| 96 | pub(crate) struct BBox { |
| 97 | left: f32, |
| 98 | top: f32, |
| 99 | right: f32, |
| 100 | bottom: f32, |
| 101 | } |
| 102 | |
| 103 | impl From<Rect> for BBox { |
| 104 | fn from(r: Rect) -> Self { |
| 105 | Self { |
| 106 | left: r.left(), |
| 107 | top: r.top(), |
| 108 | right: r.right(), |
| 109 | bottom: r.bottom(), |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | impl From<NonZeroRect> for BBox { |
| 115 | fn from(r: NonZeroRect) -> Self { |
| 116 | Self { |
| 117 | left: r.left(), |
| 118 | top: r.top(), |
| 119 | right: r.right(), |
| 120 | bottom: r.bottom(), |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | impl Default for BBox { |
| 126 | fn default() -> Self { |
| 127 | Self { |
| 128 | left: f32::MAX, |
| 129 | top: f32::MAX, |
| 130 | right: f32::MIN, |
| 131 | bottom: f32::MIN, |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | impl BBox { |
| 137 | /// Checks if the bounding box is default, i.e. invalid. |
| 138 | pub fn is_default(&self) -> bool { |
| 139 | self.left == f32::MAX |
| 140 | && self.top == f32::MAX |
| 141 | && self.right == f32::MIN |
| 142 | && self.bottom == f32::MIN |
| 143 | } |
| 144 | |
| 145 | /// Expand the bounding box to the specified bounds. |
| 146 | #[must_use ] |
| 147 | pub fn expand(&self, r: impl Into<Self>) -> Self { |
| 148 | self.expand_impl(r.into()) |
| 149 | } |
| 150 | |
| 151 | fn expand_impl(&self, r: Self) -> Self { |
| 152 | Self { |
| 153 | left: self.left.min(r.left), |
| 154 | top: self.top.min(r.top), |
| 155 | right: self.right.max(r.right), |
| 156 | bottom: self.bottom.max(r.bottom), |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /// Converts a bounding box into [`Rect`]. |
| 161 | pub fn to_rect(&self) -> Option<Rect> { |
| 162 | if !self.is_default() { |
| 163 | Rect::from_ltrb(self.left, self.top, self.right, self.bottom) |
| 164 | } else { |
| 165 | None |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | /// Converts a bounding box into [`NonZeroRect`]. |
| 170 | pub fn to_non_zero_rect(&self) -> Option<NonZeroRect> { |
| 171 | if !self.is_default() { |
| 172 | NonZeroRect::from_ltrb(self.left, self.top, self.right, self.bottom) |
| 173 | } else { |
| 174 | None |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | /// Returns object aligned position. |
| 180 | pub(crate) fn aligned_pos(align: Align, x: f32, y: f32, w: f32, h: f32) -> (f32, f32) { |
| 181 | match align { |
| 182 | Align::None => (x, y), |
| 183 | Align::XMinYMin => (x, y), |
| 184 | Align::XMidYMin => (x + w / 2.0, y), |
| 185 | Align::XMaxYMin => (x + w, y), |
| 186 | Align::XMinYMid => (x, y + h / 2.0), |
| 187 | Align::XMidYMid => (x + w / 2.0, y + h / 2.0), |
| 188 | Align::XMaxYMid => (x + w, y + h / 2.0), |
| 189 | Align::XMinYMax => (x, y + h), |
| 190 | Align::XMidYMax => (x + w / 2.0, y + h), |
| 191 | Align::XMaxYMax => (x + w, y + h), |
| 192 | } |
| 193 | } |
| 194 | |