Skip to main content

laser_pdf/elements/
debug.rs

1use utils::mm_to_pt;
2
3use crate::*;
4
5pub struct Debug<E: Element> {
6    pub element: E,
7    pub color: u8,
8    pub show_max_width: bool,
9    pub show_last_location_max_height: bool,
10}
11
12impl<E: Element> Debug<E> {
13    pub fn show_max_width(self) -> Self {
14        Self {
15            show_max_width: true,
16            ..self
17        }
18    }
19
20    pub fn show_last_location_max_height(self) -> Self {
21        Self {
22            show_last_location_max_height: true,
23            ..self
24        }
25    }
26}
27
28impl<E: Element> Element for Debug<E> {
29    fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
30        self.element.first_location_usage(ctx)
31    }
32
33    fn measure(&self, ctx: MeasureCtx) -> ElementSize {
34        self.element.measure(ctx)
35    }
36
37    fn draw(&self, ctx: DrawCtx) -> ElementSize {
38        let size;
39        let mut last_location = ctx.location.clone();
40
41        let color = calculate_color(self.color);
42        let max_width = ctx.width.max;
43        let first_height = ctx.first_height;
44        let full_height = ctx.breakable.as_ref().map(|b| b.full_height);
45
46        let mut break_heights = Vec::new();
47
48        if let Some(breakable) = ctx.breakable {
49            size = self.element.draw(DrawCtx {
50                pdf: ctx.pdf,
51                breakable: Some(BreakableDraw {
52                    do_break: &mut |pdf, location_idx, height| {
53                        let break_count = break_heights.len() as u32;
54
55                        if location_idx >= break_count {
56                            break_heights.reserve((location_idx - break_count + 1) as usize);
57
58                            break_heights.extend(
59                                std::iter::repeat(None).take((location_idx - break_count) as usize),
60                            );
61
62                            break_heights.push(height);
63                            last_location = (breakable.do_break)(pdf, location_idx, height);
64                            last_location.clone()
65                        } else {
66                            let previous = break_heights[location_idx as usize];
67
68                            // TODO: A visual indication would probably be better here than a panic.
69                            assert_eq!(previous, height);
70
71                            (breakable.do_break)(pdf, location_idx, height)
72                        }
73                    },
74                    ..breakable
75                }),
76                location: ctx.location.clone(),
77                ..ctx
78            });
79
80            if size.width.is_some() || self.show_max_width {
81                for (i, &height) in break_heights.iter().enumerate() {
82                    let full_height;
83                    let location;
84
85                    if i == 0 {
86                        full_height = ctx.first_height;
87                        location = ctx.location.clone();
88                    } else {
89                        full_height = breakable.full_height;
90                        location =
91                            (breakable.do_break)(ctx.pdf, i as u32 - 1, break_heights[i - 1]);
92                    }
93
94                    let dashed_size = (
95                        if self.show_max_width {
96                            max_width
97                        } else {
98                            size.width.unwrap()
99                        },
100                        full_height,
101                    );
102
103                    let dashed = match size.width.zip(height) {
104                        Some(solid_size) => {
105                            draw_box(ctx.pdf, location.clone(), solid_size, color, false);
106                            solid_size != dashed_size
107                        }
108                        _ => true,
109                    };
110
111                    if dashed {
112                        draw_box(ctx.pdf, location, dashed_size, color, dashed);
113                    }
114                }
115            }
116        } else {
117            size = self.element.draw(DrawCtx {
118                pdf: ctx.pdf,
119                ..ctx
120            });
121        }
122
123        let dashed_size = (
124            if self.show_max_width {
125                Some(max_width)
126            } else {
127                size.width
128            },
129            if self.show_last_location_max_height {
130                Some(if break_heights.len() == 0 {
131                    first_height
132                } else {
133                    full_height.unwrap()
134                })
135            } else {
136                size.height
137            },
138        );
139
140        let dashed = if let (Some(width), Some(height)) = (size.width, size.height) {
141            draw_box(
142                ctx.pdf,
143                last_location.clone(),
144                (width, height),
145                color,
146                false,
147            );
148            dashed_size != (Some(width), Some(height))
149        } else {
150            true
151        };
152
153        if let Some((width, height)) = dashed.then_some(dashed_size.0.zip(dashed_size.1)).flatten()
154        {
155            draw_box(ctx.pdf, last_location, (width, height), color, true);
156        }
157
158        size
159    }
160}
161
162fn hue_to_rgb(hue: u8) -> [u8; 3] {
163    let x = 6u8.saturating_mul(43 - 43u8.abs_diff(hue % 85));
164
165    match hue / 43 {
166        0 => [255, x, 0],
167        1 => [x, 255, 0],
168        2 => [0, 255, x],
169        3 => [0, x, 255],
170        4 => [x, 0, 255],
171        5 => [255, 0, x],
172        _ => unreachable!(),
173    }
174}
175
176fn calculate_color(input: u8) -> [f32; 3] {
177    hue_to_rgb(input.reverse_bits()).map(|c| c as f32 / 255.)
178}
179
180fn draw_box(pdf: &mut Pdf, location: Location, size: (f32, f32), color: [f32; 3], dashed: bool) {
181    let layer = location.layer(pdf);
182
183    layer
184        .save_state()
185        .set_line_width(0.)
186        .set_stroke_rgb(color[0], color[1], color[2]);
187
188    if dashed {
189        layer.set_dash_pattern([2., 2.], 0.);
190    }
191
192    layer
193        .rect(
194            mm_to_pt(location.pos.0),
195            mm_to_pt(location.pos.1 - size.1),
196            mm_to_pt(size.0),
197            mm_to_pt(size.1),
198        )
199        .stroke()
200        .restore_state();
201}