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: Box2D = bounding_box(path:path.iter());
55 let transform: Transform2D = fit_box(&aabb, dst_rect:output_rect, style);
56
57 let mut builder: NoAttributes = Path::builder();
58 for evt: Event, …> 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}
168