Skip to main content

laser_pdf/elements/
rotate.rs

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            // TODO: test
113            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            // TODO: Make layers work inside here. Maybe this could be done when we migrate to
117            // pdfwriter.
118
119            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}