1 | //! Fit paths into rectangles. |
2 | |
3 | use crate::aabb::bounding_box; |
4 | use crate::math::*; |
5 | use crate::path::iterator::*; |
6 | use crate::path::Path; |
7 | |
8 | /// The strategy to use when fitting (stretching, overflow, etc.) |
9 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
10 | pub 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. |
24 | pub 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. |
53 | pub 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 ] |
66 | fn 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 | |