Skip to main content

laser_pdf/elements/
line.rs

1use crate::{utils::*, *};
2
3/// A horizontal line element with configurable styling.
4/// 
5/// The line spans the full available width when drawn in an expanding context.
6/// Line thickness, color, dash patterns, and cap styles are configurable.
7pub struct Line {
8    /// Line styling including thickness, color, dash pattern, and cap style
9    pub style: LineStyle,
10}
11
12impl Line {
13    pub fn new(thickness: f32) -> Self {
14        Line {
15            style: LineStyle {
16                thickness,
17                color: 0x00_00_00_FF,
18                dash_pattern: None,
19                cap_style: LineCapStyle::Butt,
20            },
21        }
22    }
23}
24
25impl Element for Line {
26    fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
27        ctx.break_if_appropriate_for_min_height(self.style.thickness);
28
29        size(self, ctx.width)
30    }
31
32    fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
33        ctx.break_if_appropriate_for_min_height(self.style.thickness);
34
35        if ctx.width.expand {
36            let (color, _alpha) = u32_to_color_and_alpha(self.style.color);
37            let style = self.style;
38
39            let layer = ctx.location.layer(ctx.pdf);
40
41            layer
42                .save_state()
43                .set_line_width(mm_to_pt(style.thickness))
44                .set_stroke_rgb(color[0], color[1], color[2])
45                .set_line_cap(style.cap_style.into());
46
47            if let Some(pattern) = style.dash_pattern {
48                layer.set_dash_pattern(pattern.dashes.map(f32::from), pattern.offset as f32);
49            }
50
51            let line_y = ctx.location.pos.1 - self.style.thickness / 2.0;
52
53            layer
54                .move_to(mm_to_pt(ctx.location.pos.0), mm_to_pt(line_y))
55                .line_to(
56                    mm_to_pt(ctx.location.pos.0 + ctx.width.max),
57                    mm_to_pt(line_y),
58                )
59                .stroke()
60                .restore_state();
61        }
62
63        size(self, ctx.width)
64    }
65}
66
67fn size(line: &Line, width: WidthConstraint) -> ElementSize {
68    ElementSize {
69        width: Some(width.constrain(0.)),
70        height: Some(line.style.thickness),
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::test_utils::*;
78
79    #[test]
80    fn test_line() {
81        for output in (ElementTestParams {
82            first_height: 0.2,
83            ..Default::default()
84        })
85        .run(&Line {
86            style: LineStyle {
87                thickness: 1.,
88                color: 0,
89                dash_pattern: None,
90                cap_style: LineCapStyle::Butt,
91            },
92        }) {
93            output.assert_size(ElementSize {
94                width: Some(output.width.constrain(0.)),
95                height: Some(1.),
96            });
97
98            if let Some(b) = output.breakable {
99                if output.first_height == 0.2 {
100                    b.assert_break_count(1);
101                } else {
102                    b.assert_break_count(0);
103                }
104
105                b.assert_extra_location_min_height(None);
106            }
107        }
108    }
109}