lyon_algorithms/
fit.rs

1//! Fit paths into rectangles.
2
3use crate::aabb::bounding_box;
4use crate::math::*;
5use crate::path::iterator::*;
6use crate::path::Path;
7
8/// The strategy to use when fitting (stretching, overflow, etc.)
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
10pub enum FitStyle {
11    /// Stretch vertically and horizontally to fit the destination rectangle exactly.
12    Stretch,
13    /// Uniformly scale without overflow.
14    Min,
15    /// Uniformly scale with overflow.
16    Max,
17    /// Uniformly scale to fit horizontally.
18    Horizontal,
19    /// Uniformly scale to fit vertically.
20    Vertical,
21}
22
23/// Computes a transform that fits a rectangle into another one.
24pub fn fit_box(src_rect: &Box2D, dst_rect: &Box2D, style: FitStyle) -> Transform {
25    let scale: Vector = vector(
26        dst_rect.width() / src_rect.width(),
27        dst_rect.height() / src_rect.height(),
28    );
29
30    let scale = match style {
31        FitStyle::Stretch => scale,
32        FitStyle::Min => {
33            let s = f32::min(scale.x, scale.y);
34            vector(s, s)
35        }
36        FitStyle::Max => {
37            let s = f32::max(scale.x, scale.y);
38            vector(s, s)
39        }
40        FitStyle::Horizontal => vector(scale.x, scale.x),
41        FitStyle::Vertical => vector(scale.y, scale.y),
42    };
43
44    let src_center = src_rect.min.lerp(src_rect.max, 0.5);
45    let dst_center = dst_rect.min.lerp(dst_rect.max, 0.5);
46
47    Transform::translation(-src_center.x, -src_center.y)
48        .then_scale(scale.x, scale.y)
49        .then_translate(dst_center.to_vector())
50}
51
52/// Fits a path into a rectangle.
53pub fn fit_path(path: &Path, output_rect: &Box2D, style: FitStyle) -> Path {
54    let aabb = bounding_box(path.iter());
55    let transform = fit_box(&aabb, output_rect, style);
56
57    let mut builder = Path::builder();
58    for evt in path.iter().transformed(&transform) {
59        builder.path_event(evt)
60    }
61
62    builder.build()
63}
64
65#[test]
66fn simple_fit() {
67    fn approx_eq(a: &Box2D, b: &Box2D) -> bool {
68        use crate::geom::euclid::approxeq::ApproxEq;
69        let result = a.min.approx_eq(&b.min) && a.max.approx_eq(&b.max);
70        if !result {
71            std::println!("{a:?} == {b:?}");
72        }
73        result
74    }
75
76    let t = fit_box(
77        &Box2D {
78            min: point(0.0, 0.0),
79            max: point(1.0, 1.0),
80        },
81        &Box2D {
82            min: point(0.0, 0.0),
83            max: point(2.0, 2.0),
84        },
85        FitStyle::Stretch,
86    );
87
88    assert!(approx_eq(
89        &t.outer_transformed_box(&Box2D {
90            min: point(0.0, 0.0),
91            max: point(1.0, 1.0)
92        }),
93        &Box2D {
94            min: point(0.0, 0.0),
95            max: point(2.0, 2.0)
96        },
97    ));
98
99    let t = fit_box(
100        &Box2D {
101            min: point(1.0, 2.0),
102            max: point(5.0, 6.0),
103        },
104        &Box2D {
105            min: point(0.0, 0.0),
106            max: point(2.0, 8.0),
107        },
108        FitStyle::Stretch,
109    );
110
111    assert!(approx_eq(
112        &t.outer_transformed_box(&Box2D {
113            min: point(1.0, 2.0),
114            max: point(5.0, 6.0)
115        }),
116        &Box2D {
117            min: point(0.0, 0.0),
118            max: point(2.0, 8.0)
119        },
120    ));
121
122    let t = fit_box(
123        &Box2D {
124            min: point(1.0, 2.0),
125            max: point(3.0, 6.0),
126        },
127        &Box2D {
128            min: point(0.0, 0.0),
129            max: point(2.0, 2.0),
130        },
131        FitStyle::Horizontal,
132    );
133
134    assert!(approx_eq(
135        &t.outer_transformed_box(&Box2D {
136            min: point(1.0, 2.0),
137            max: point(3.0, 6.0)
138        }),
139        &Box2D {
140            min: point(0.0, -1.0),
141            max: point(2.0, 3.0)
142        },
143    ));
144
145    let t = fit_box(
146        &Box2D {
147            min: point(1.0, 2.0),
148            max: point(3.0, 4.0),
149        },
150        &Box2D {
151            min: point(0.0, 0.0),
152            max: point(4.0, 2.0),
153        },
154        FitStyle::Horizontal,
155    );
156
157    assert!(approx_eq(
158        &t.outer_transformed_box(&Box2D {
159            min: point(1.0, 2.0),
160            max: point(3.0, 4.0)
161        }),
162        &Box2D {
163            min: point(0.0, -1.0),
164            max: point(4.0, 3.0)
165        },
166    ));
167}