laser_pdf/elements/
rectangle.rs1use pdf_writer::Name;
2
3use crate::{utils::*, *};
4
5pub struct Rectangle {
10 pub size: (f32, f32),
12 pub fill: Option<u32>,
14 pub outline: Option<(f32, u32)>,
16}
17
18impl Element for Rectangle {
19 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
20 let outline_thickness = outline_thickness(self);
21 if ctx.break_appropriate_for_min_height(self.size.1 + outline_thickness) {
22 FirstLocationUsage::WillSkip
23 } else {
24 FirstLocationUsage::WillUse
25 }
26 }
27
28 fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
29 let outline_thickness = outline_thickness(self);
30 ctx.break_if_appropriate_for_min_height(self.size.1 + outline_thickness);
31
32 size(self)
33 }
34
35 fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
36 let outline_thickness = outline_thickness(self);
37 ctx.break_if_appropriate_for_min_height(self.size.1 + outline_thickness);
38
39 let extra_outline_offset = outline_thickness as f32 / 2.0;
40
41 let resource_id;
42
43 let fill_alpha = self
44 .fill
45 .map(|c| u32_to_color_and_alpha(c).1)
46 .filter(|&a| a != 1.);
47
48 let outline_alpha = self
49 .outline
50 .map(|(_, c)| u32_to_color_and_alpha(c).1)
51 .filter(|&a| a != 1.);
52
53 if fill_alpha.is_some() || outline_alpha.is_some() {
54 let ext_graphics_ref = ctx.pdf.alloc();
55
56 let mut ext_graphics = ctx.pdf.pdf.ext_graphics(ext_graphics_ref);
57 fill_alpha.inspect(|&a| {
58 ext_graphics.non_stroking_alpha(a);
59 });
60 outline_alpha.inspect(|&a| {
61 ext_graphics.stroking_alpha(a);
62 });
63
64 resource_id =
65 Some(ctx.pdf.pages[ctx.location.page_idx].add_ext_g_state(ext_graphics_ref));
66 } else {
67 resource_id = None;
68 }
69
70 let layer = ctx.location.layer(ctx.pdf);
71
72 layer.save_state();
73
74 if let Some(color) = self.fill {
75 set_fill_color(layer, color);
76 }
77
78 if let Some((thickness, color)) = self.outline {
79 layer.set_line_width(mm_to_pt(thickness) as f32);
80
81 set_stroke_color(layer, color);
82 }
83
84 if let Some(ext_graphics) = resource_id {
85 layer.set_parameters(Name(format!("{}", ext_graphics).as_bytes()));
86 }
87
88 layer.rect(
89 mm_to_pt(ctx.location.pos.0 as f32 + extra_outline_offset),
90 mm_to_pt((ctx.location.pos.1 - self.size.1) as f32 - extra_outline_offset),
91 mm_to_pt(self.size.0 as f32),
92 mm_to_pt(self.size.1 as f32),
93 );
94
95 match (self.fill.is_some(), self.outline.is_some()) {
96 (true, true) => layer.fill_nonzero_and_stroke(),
97 (true, false) => layer.fill_nonzero(),
98 (false, true) => layer.stroke(),
99 (false, false) => layer,
100 };
101
102 layer.restore_state();
103
104 size(self)
105 }
106}
107
108fn outline_thickness(rectangle: &Rectangle) -> f32 {
109 rectangle.outline.map(|o| o.0).unwrap_or(0.0)
110}
111
112fn size(rectangle: &Rectangle) -> ElementSize {
113 let outline_thickness = outline_thickness(rectangle);
114
115 ElementSize {
116 width: Some(rectangle.size.0 + outline_thickness),
117 height: Some(rectangle.size.1 + outline_thickness),
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::test_utils::*;
125
126 #[test]
127 fn test_rectangle() {
128 for output in (ElementTestParams {
129 first_height: 12.,
130 ..Default::default()
131 })
132 .run(&Rectangle {
133 size: (11., 12.),
134 fill: None,
135 outline: Some((1., 0)),
136 }) {
137 output.assert_size(ElementSize {
138 width: Some(12.),
139 height: Some(13.),
140 });
141
142 if let Some(b) = output.breakable {
143 if output.first_height == 12. {
144 b.assert_break_count(1);
145 } else {
146 b.assert_break_count(0);
147 }
148
149 b.assert_extra_location_min_height(None);
150 }
151 }
152 }
153}