1use utils::mm_to_pt;
2
3use crate::*;
4
5#[derive(Clone, Copy, Serialize, Deserialize)]
6pub enum Rotation {
7 QuarterLeft,
8 QuarterRight,
9}
10
11pub struct Rotate<E: Element> {
12 pub element: E,
13 pub rotation: Rotation,
14}
15
16impl<E: Element> Element for Rotate<E> {
17 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
18 let element_width_constraint = WidthConstraint {
19 max: ctx.full_height,
20 expand: false,
21 };
22
23 let size = self.element.measure(MeasureCtx {
24 text_pieces_cache: ctx.text_pieces_cache,
25 width: element_width_constraint,
26 first_height: ctx.width.max,
27 breakable: None,
28 });
29
30 if size.width.is_none() {
31 FirstLocationUsage::NoneHeight
32 } else if ctx.first_height < ctx.full_height && size.width > Some(ctx.first_height) {
33 FirstLocationUsage::WillSkip
34 } else {
35 FirstLocationUsage::WillUse
36 }
37 }
38
39 fn measure(&self, ctx: MeasureCtx) -> ElementSize {
40 let element_width_constraint = WidthConstraint {
41 max: ctx
42 .breakable
43 .as_ref()
44 .map(|b| b.full_height)
45 .unwrap_or(ctx.first_height),
46 expand: false,
47 };
48
49 let size = self.element.measure(MeasureCtx {
50 text_pieces_cache: ctx.text_pieces_cache,
51 width: element_width_constraint,
52 first_height: ctx.width.max,
53 breakable: None,
54 });
55
56 match ctx.breakable {
57 Some(breakable)
58 if ctx.first_height < breakable.full_height
59 && size.width > Some(ctx.first_height) =>
60 {
61 *breakable.break_count = 1;
62 }
63 _ => (),
64 }
65
66 ElementSize {
67 width: size.height,
68 height: size.width,
69 }
70 }
71
72 fn draw(&self, ctx: DrawCtx) -> ElementSize {
73 let element_width_constraint = WidthConstraint {
74 max: ctx
75 .breakable
76 .as_ref()
77 .map(|b| b.full_height)
78 .unwrap_or(ctx.first_height),
79 expand: false,
80 };
81
82 let size = self.element.measure(MeasureCtx {
83 text_pieces_cache: ctx.text_pieces_cache,
84 width: element_width_constraint,
85 first_height: ctx.width.max,
86 breakable: None,
87 });
88
89 let location;
90
91 match ctx.breakable {
92 Some(breakable)
93 if ctx.first_height < breakable.full_height
94 && size.width > Some(ctx.first_height) =>
95 {
96 location = (breakable.do_break)(ctx.pdf, 0, None);
97 }
98 _ => location = ctx.location,
99 }
100
101 if let (Some(width), Some(height)) = (size.width, size.height) {
102 let layer = location.layer(ctx.pdf);
103 layer.save_state();
104
105 let (x, y, rotation): (_, _, f32) = match self.rotation {
106 Rotation::QuarterLeft => (location.pos.0, location.pos.1 - width, -270.),
107 Rotation::QuarterRight => (location.pos.0 + height, location.pos.1, -90.),
108 };
109
110 let rad = rotation.to_radians();
111
112 layer.transform([1., 0., 0., 1., mm_to_pt(x), mm_to_pt(y)]);
114 layer.transform([rad.cos(), rad.sin(), -rad.sin(), -rad.cos(), 0., 0.]);
115
116 self.element.draw(DrawCtx {
120 pdf: ctx.pdf,
121 text_pieces_cache: ctx.text_pieces_cache,
122 location: Location {
123 pos: (0., 0.),
124 ..location
125 },
126 width: element_width_constraint,
127 first_height: ctx.width.max,
128 preferred_height: None,
129 breakable: None,
130 });
131
132 location.layer(ctx.pdf).restore_state();
133 }
134
135 ElementSize {
136 width: size.height,
137 height: size.width,
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::{
146 elements::{none::NoneElement, ref_element::RefElement},
147 test_utils::{record_passes::RecordPasses, *},
148 };
149 use insta::*;
150
151 #[test]
152 fn test_basic() {
153 let output = test_element(
154 TestElementParams {
155 width: WidthConstraint {
156 max: 16.,
157 expand: true,
158 },
159 first_height: 21.,
160 breakable: Some(TestElementParamsBreakable {
161 preferred_height_break_count: 0,
162 full_height: 500.,
163 }),
164 pos: (11., 29.0),
165 ..Default::default()
166 },
167 |assert, callback| {
168 let content = RecordPasses::new(FakeText {
169 lines: 3,
170 line_height: 5.,
171 width: 18.,
172 });
173
174 let element = Rotate {
175 element: RefElement(&content),
176 rotation: Rotation::QuarterRight,
177 };
178
179 let ret = callback.call(element);
180
181 if assert {
182 assert_debug_snapshot!(content.into_passes());
183 }
184
185 ret
186 },
187 );
188
189 assert_debug_snapshot!(output);
190 }
191
192 #[test]
193 fn test_pre_break() {
194 let output = test_element(
195 TestElementParams {
196 width: WidthConstraint {
197 max: 16.,
198 expand: true,
199 },
200 first_height: 21.,
201 breakable: Some(TestElementParamsBreakable {
202 preferred_height_break_count: 0,
203 full_height: 500.,
204 }),
205 pos: (11., 29.0),
206 ..Default::default()
207 },
208 |assert, callback| {
209 let content = RecordPasses::new(FakeText {
210 lines: 3,
211 line_height: 5.,
212 width: 100.,
213 });
214
215 let element = Rotate {
216 element: RefElement(&content),
217 rotation: Rotation::QuarterLeft,
218 };
219
220 let ret = callback.call(element);
221
222 if assert {
223 assert_debug_snapshot!(content.into_passes());
224 }
225
226 ret
227 },
228 );
229
230 assert_debug_snapshot!(output);
231 }
232
233 #[test]
234 fn test_none() {
235 let output = test_element(
236 TestElementParams {
237 width: WidthConstraint {
238 max: 16.,
239 expand: true,
240 },
241 first_height: 21.,
242 breakable: Some(TestElementParamsBreakable {
243 preferred_height_break_count: 0,
244 full_height: 500.,
245 }),
246 pos: (11., 29.0),
247 ..Default::default()
248 },
249 |assert, callback| {
250 let content = RecordPasses::new(NoneElement);
251
252 let element = Rotate {
253 element: RefElement(&content),
254 rotation: Rotation::QuarterRight,
255 };
256
257 let ret = callback.call(element);
258
259 if assert {
260 assert_debug_snapshot!(content.into_passes());
261 }
262
263 ret
264 },
265 );
266
267 assert_debug_snapshot!(output);
268 }
269}