Coverage Report

Created: 2026-02-14 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/kurbo-0.13.0/src/translate_scale.rs
Line
Count
Source
1
// Copyright 2019 the Kurbo Authors
2
// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4
//! A transformation that includes both scale and translation.
5
6
use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
7
8
use crate::{
9
    Affine, Circle, CubicBez, Line, Point, QuadBez, Rect, RoundedRect, RoundedRectRadii, Vec2,
10
};
11
12
/// A transformation consisting of a uniform scaling followed by a translation.
13
///
14
/// If the translation is `(x, y)` and the scale is `s`, then this
15
/// transformation represents this augmented matrix:
16
///
17
/// ```text
18
/// | s 0 x |
19
/// | 0 s y |
20
/// | 0 0 1 |
21
/// ```
22
///
23
/// See [`Affine`] for more details about the
24
/// equivalence with augmented matrices.
25
///
26
/// Various multiplication ops are defined, and these are all defined
27
/// to be consistent with matrix multiplication. Therefore,
28
/// `TranslateScale * Point` is defined but not the other way around.
29
///
30
/// Also note that multiplication is not commutative. Thus,
31
/// `TranslateScale::scale(2.0) * TranslateScale::translate(Vec2::new(1.0, 0.0))`
32
/// has a translation of (2, 0), while
33
/// `TranslateScale::translate(Vec2::new(1.0, 0.0)) * TranslateScale::scale(2.0)`
34
/// has a translation of (1, 0). (Both have a scale of 2; also note that
35
/// the first case can be written
36
/// `2.0 * TranslateScale::translate(Vec2::new(1.0, 0.0))` as this case
37
/// has an implicit conversion).
38
///
39
/// This transformation is less powerful than [`Affine`], but can be applied
40
/// to more primitives, especially including [`Rect`].
41
#[derive(Clone, Copy, Debug)]
42
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
43
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44
pub struct TranslateScale {
45
    /// The translation component of this transformation
46
    pub translation: Vec2,
47
    /// The scale component of this transformation
48
    pub scale: f64,
49
}
50
51
impl TranslateScale {
52
    /// Create a new transformation from translation and scale.
53
    #[inline(always)]
54
0
    pub const fn new(translation: Vec2, scale: f64) -> TranslateScale {
55
0
        TranslateScale { translation, scale }
56
0
    }
57
58
    /// Create a new transformation with scale only.
59
    #[inline(always)]
60
0
    pub const fn scale(s: f64) -> TranslateScale {
61
0
        TranslateScale::new(Vec2::ZERO, s)
62
0
    }
63
64
    /// Create a new transformation with translation only.
65
    #[inline(always)]
66
0
    pub fn translate(translation: impl Into<Vec2>) -> TranslateScale {
67
0
        TranslateScale::new(translation.into(), 1.0)
68
0
    }
69
70
    /// Create a transform that scales about a point other than the origin.
71
    ///
72
    /// # Examples
73
    ///
74
    /// ```
75
    /// # use kurbo::{Point, TranslateScale};
76
    /// # fn assert_near(p0: Point, p1: Point) {
77
    /// #   assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
78
    /// # }
79
    /// let center = Point::new(1., 1.);
80
    /// let ts = TranslateScale::from_scale_about(2., center);
81
    /// // Should keep the point (1., 1.) stationary
82
    /// assert_near(ts * center, center);
83
    /// // (2., 2.) -> (3., 3.)
84
    /// assert_near(ts * Point::new(2., 2.), Point::new(3., 3.));
85
    /// ```
86
    #[inline]
87
0
    pub fn from_scale_about(scale: f64, focus: impl Into<Point>) -> Self {
88
        // We need to create a transform that is equivalent to translating `focus`
89
        // to the origin, followed by a normal scale, followed by reversing the translation.
90
        // We need to find the (translation ∘ scale) that matches this.
91
0
        let focus = focus.into().to_vec2();
92
0
        let translation = focus - focus * scale;
93
0
        Self::new(translation, scale)
94
0
    }
95
96
    /// Compute the inverse transform.
97
    ///
98
    /// Multiplying a transform with its inverse (either on the
99
    /// left or right) results in the identity transform
100
    /// (modulo floating point rounding errors).
101
    ///
102
    /// Produces NaN values when scale is zero.
103
    #[inline]
104
0
    pub fn inverse(self) -> TranslateScale {
105
0
        let scale_recip = self.scale.recip();
106
0
        TranslateScale {
107
0
            translation: self.translation * -scale_recip,
108
0
            scale: scale_recip,
109
0
        }
110
0
    }
111
112
    /// Is this translate/scale [finite]?
113
    ///
114
    /// [finite]: f64::is_finite
115
    #[inline]
116
0
    pub fn is_finite(&self) -> bool {
117
0
        self.translation.is_finite() && self.scale.is_finite()
118
0
    }
119
120
    /// Is this translate/scale [NaN]?
121
    ///
122
    /// [NaN]: f64::is_nan
123
    #[inline]
124
0
    pub fn is_nan(&self) -> bool {
125
0
        self.translation.is_nan() || self.scale.is_nan()
126
0
    }
127
}
128
129
impl Default for TranslateScale {
130
    #[inline(always)]
131
0
    fn default() -> TranslateScale {
132
0
        TranslateScale::new(Vec2::ZERO, 1.0)
133
0
    }
134
}
135
136
impl From<TranslateScale> for Affine {
137
    #[inline(always)]
138
0
    fn from(ts: TranslateScale) -> Affine {
139
0
        let TranslateScale { translation, scale } = ts;
140
0
        Affine::new([scale, 0.0, 0.0, scale, translation.x, translation.y])
141
0
    }
142
}
143
144
impl Mul<Point> for TranslateScale {
145
    type Output = Point;
146
147
    #[inline]
148
0
    fn mul(self, other: Point) -> Point {
149
0
        (self.scale * other.to_vec2()).to_point() + self.translation
150
0
    }
151
}
152
153
impl Mul for TranslateScale {
154
    type Output = TranslateScale;
155
156
    #[inline]
157
0
    fn mul(self, other: TranslateScale) -> TranslateScale {
158
0
        TranslateScale {
159
0
            translation: self.translation + self.scale * other.translation,
160
0
            scale: self.scale * other.scale,
161
0
        }
162
0
    }
163
}
164
165
impl MulAssign for TranslateScale {
166
    #[inline]
167
0
    fn mul_assign(&mut self, other: TranslateScale) {
168
0
        *self = self.mul(other);
169
0
    }
170
}
171
172
impl Mul<TranslateScale> for f64 {
173
    type Output = TranslateScale;
174
175
    #[inline]
176
0
    fn mul(self, other: TranslateScale) -> TranslateScale {
177
0
        TranslateScale {
178
0
            translation: other.translation * self,
179
0
            scale: other.scale * self,
180
0
        }
181
0
    }
182
}
183
184
impl Add<Vec2> for TranslateScale {
185
    type Output = TranslateScale;
186
187
    #[inline]
188
0
    fn add(self, other: Vec2) -> TranslateScale {
189
0
        TranslateScale {
190
0
            translation: self.translation + other,
191
0
            scale: self.scale,
192
0
        }
193
0
    }
194
}
195
196
impl Add<TranslateScale> for Vec2 {
197
    type Output = TranslateScale;
198
199
    #[inline]
200
0
    fn add(self, other: TranslateScale) -> TranslateScale {
201
0
        other + self
202
0
    }
203
}
204
205
impl AddAssign<Vec2> for TranslateScale {
206
    #[inline]
207
0
    fn add_assign(&mut self, other: Vec2) {
208
0
        *self = self.add(other);
209
0
    }
210
}
211
212
impl Sub<Vec2> for TranslateScale {
213
    type Output = TranslateScale;
214
215
    #[inline]
216
0
    fn sub(self, other: Vec2) -> TranslateScale {
217
0
        TranslateScale {
218
0
            translation: self.translation - other,
219
0
            scale: self.scale,
220
0
        }
221
0
    }
222
}
223
224
impl SubAssign<Vec2> for TranslateScale {
225
    #[inline]
226
0
    fn sub_assign(&mut self, other: Vec2) {
227
0
        *self = self.sub(other);
228
0
    }
229
}
230
231
impl Mul<Circle> for TranslateScale {
232
    type Output = Circle;
233
234
    #[inline]
235
0
    fn mul(self, other: Circle) -> Circle {
236
0
        Circle::new(self * other.center, self.scale * other.radius)
237
0
    }
238
}
239
240
impl Mul<Line> for TranslateScale {
241
    type Output = Line;
242
243
    #[inline]
244
0
    fn mul(self, other: Line) -> Line {
245
0
        Line::new(self * other.p0, self * other.p1)
246
0
    }
247
}
248
249
impl Mul<Rect> for TranslateScale {
250
    type Output = Rect;
251
252
    #[inline]
253
0
    fn mul(self, other: Rect) -> Rect {
254
0
        let pt0 = self * Point::new(other.x0, other.y0);
255
0
        let pt1 = self * Point::new(other.x1, other.y1);
256
0
        (pt0, pt1).into()
257
0
    }
258
}
259
260
impl Mul<RoundedRect> for TranslateScale {
261
    type Output = RoundedRect;
262
263
    #[inline]
264
0
    fn mul(self, other: RoundedRect) -> RoundedRect {
265
0
        RoundedRect::from_rect(self * other.rect(), self * other.radii())
266
0
    }
267
}
268
269
impl Mul<RoundedRectRadii> for TranslateScale {
270
    type Output = RoundedRectRadii;
271
272
    #[inline]
273
0
    fn mul(self, other: RoundedRectRadii) -> RoundedRectRadii {
274
0
        RoundedRectRadii::new(
275
0
            self.scale * other.top_left,
276
0
            self.scale * other.top_right,
277
0
            self.scale * other.bottom_right,
278
0
            self.scale * other.bottom_left,
279
        )
280
0
    }
281
}
282
283
impl Mul<QuadBez> for TranslateScale {
284
    type Output = QuadBez;
285
286
    #[inline]
287
0
    fn mul(self, other: QuadBez) -> QuadBez {
288
0
        QuadBez::new(self * other.p0, self * other.p1, self * other.p2)
289
0
    }
290
}
291
292
impl Mul<CubicBez> for TranslateScale {
293
    type Output = CubicBez;
294
295
    #[inline]
296
0
    fn mul(self, other: CubicBez) -> CubicBez {
297
0
        CubicBez::new(
298
0
            self * other.p0,
299
0
            self * other.p1,
300
0
            self * other.p2,
301
0
            self * other.p3,
302
        )
303
0
    }
304
}
305
306
#[cfg(test)]
307
mod tests {
308
    use crate::{Affine, Point, TranslateScale, Vec2};
309
310
    fn assert_near(p0: Point, p1: Point) {
311
        assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
312
    }
313
314
    #[test]
315
    fn translate_scale() {
316
        let p = Point::new(3.0, 4.0);
317
        let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
318
319
        assert_near(ts * p, Point::new(11.0, 14.0));
320
    }
321
322
    #[test]
323
    fn conversions() {
324
        let p = Point::new(3.0, 4.0);
325
        let s = 2.0;
326
        let t = Vec2::new(5.0, 6.0);
327
        let ts = TranslateScale::new(t, s);
328
329
        // Test that conversion to affine is consistent.
330
        let a: Affine = ts.into();
331
        assert_near(ts * p, a * p);
332
333
        assert_near((s * p.to_vec2()).to_point(), TranslateScale::scale(s) * p);
334
        assert_near(p + t, TranslateScale::translate(t) * p);
335
    }
336
337
    #[test]
338
    fn inverse() {
339
        let p = Point::new(3.0, 4.0);
340
        let ts = TranslateScale::new(Vec2::new(5.0, 6.0), 2.0);
341
342
        assert_near(p, (ts * ts.inverse()) * p);
343
        assert_near(p, (ts.inverse() * ts) * p);
344
    }
345
}