shapdf/
shapes.rs

1use crate::units::*;
2use once_cell::sync::Lazy;
3use std::sync::Mutex;
4
5#[derive(Debug, Copy, Clone)]
6pub enum CapType {
7    Butt,
8    Round,
9    Square,
10}
11
12impl CapType {
13    pub fn to_int(&self) -> i32 {
14        match self {
15            CapType::Butt => 0,
16            CapType::Round => 1,
17            CapType::Square => 2,
18        }
19    }
20}
21
22#[derive(Debug, Clone, Copy)]
23pub enum Anchor {
24    Center,
25    North,
26    South,
27    East,
28    West,
29    NorthEast,
30    NorthWest,
31    SouthEast,
32    SouthWest,
33    Point(f64, f64),
34}
35
36impl Default for Anchor {
37    fn default() -> Self {
38        Anchor::SouthWest
39    }
40}
41
42#[derive(Debug, Clone, Copy)]
43pub enum ShapeType {
44    Line,
45    Circle,
46    Rectangle,
47    Polygon,
48    Unknown,
49}
50
51impl Default for ShapeType {
52    fn default() -> Self {
53        ShapeType::Unknown
54    }
55}
56
57/// A shape to draw on the PDF.
58///
59/// This struct is mostly internal and should not be used directly.
60/// You should use the methods provided by the [`Generator`](crate::Generator) struct.
61#[derive(Debug, Default)]
62pub struct Shape<'a> {
63    pub enum_type: ShapeType,
64    pub content_stream: Option<&'a mut Vec<u8>>,
65    pub x: Vec<f64>,
66    pub y: Vec<f64>,
67    pub width: Option<f64>,
68    pub radius: Option<f64>,
69    pub angle: Option<f64>, // angle in radius
70    pub anchor: Option<Anchor>,
71    pub cap_type: Option<CapType>,
72    pub color: Option<(f64, f64, f64)>,
73}
74
75static DEFAULT_WIDTH: Lazy<Mutex<f64>> = Lazy::new(|| Pt(1.).to_points().into());
76static DEFAULT_CAP_TYPE: Lazy<Mutex<CapType>> = Lazy::new(|| CapType::Butt.into());
77static DEFAULT_COLOR: Lazy<Mutex<(f64, f64, f64)>> =
78    Lazy::new(|| NamedColor("black").to_rgb().into());
79static DEFAULT_ANGLE: Lazy<Mutex<f64>> = Lazy::new(|| Degree(0.).to_degrees().into());
80static DEFAULT_ANCHOR: Lazy<Mutex<Anchor>> = Lazy::new(|| Anchor::SouthWest.into());
81
82impl<'a> Shape<'a> {
83    pub fn draw(&mut self) {
84        if let Some(content) = self.content_stream.as_mut() {
85            let (r, g, b) = self.color.unwrap_or(*DEFAULT_COLOR.lock().unwrap());
86            let width = self.width.unwrap_or(*DEFAULT_WIDTH.lock().unwrap());
87            let cap_type = self
88                .cap_type
89                .unwrap_or(*DEFAULT_CAP_TYPE.lock().unwrap())
90                .to_int();
91            match self.enum_type {
92                ShapeType::Line => {
93                    content.extend_from_slice(format!("{} {} {} RG\n", r, g, b).as_bytes());
94                    content.extend_from_slice(
95                        format!(
96                            "{} w\n{} J\n{} {} m\n{} {} l\nS\n",
97                            width, cap_type, self.x[0], self.y[0], self.x[1], self.y[1]
98                        )
99                        .as_bytes(),
100                    );
101                }
102                ShapeType::Circle => {
103                    content.extend_from_slice(format!("{} {} {} RG\n", r, g, b).as_bytes());
104                    content.extend_from_slice(
105                        // Ref: https://stackoverflow.com/a/46897816/15080514
106                        format!(
107                            "{} w\n1 J\n{} {} m\n{} {} l\nS\n",
108                            self.radius.unwrap() * 2.0,
109                            self.x[0],
110                            self.y[0],
111                            self.x[0],
112                            self.y[0]
113                        )
114                        .as_bytes(),
115                    );
116                }
117                ShapeType::Rectangle => {
118                    content.extend_from_slice(format!("{} {} {} rg\n", r, g, b).as_bytes());
119                    let angle = self.angle.unwrap_or(*DEFAULT_ANGLE.lock().unwrap());
120                    let cos_theta = angle.cos();
121                    let sin_theta = angle.sin();
122                    let (width, height) = (self.x[1], self.y[1]);
123                    // (cx, cy): rotation center
124                    let (cx, cy) = (self.x[0], self.y[0]);
125                    // (x0, y0): south west corner of the rectangle before rotation
126                    let (x0, y0) = match self.anchor.unwrap_or(*DEFAULT_ANCHOR.lock().unwrap()) {
127                        Anchor::Center => (self.x[0] - width / 2.0, self.y[0] - height / 2.0),
128                        Anchor::North => (self.x[0] - width / 2.0, self.y[0] - height),
129                        Anchor::South => (self.x[0] - width / 2.0, self.y[0]),
130                        Anchor::East => (self.x[0] - width, self.y[0] - height / 2.0),
131                        Anchor::West => (self.x[0], self.y[0] - height / 2.0),
132                        Anchor::NorthEast => (self.x[0] - width, self.y[0] - height),
133                        Anchor::NorthWest => (self.x[0], self.y[0] - height),
134                        Anchor::SouthEast => (self.x[0] - width, self.y[0]),
135                        Anchor::SouthWest => (self.x[0], self.y[0]),
136                        Anchor::Point(px, py) => (px, py),
137                    };
138                    let translate_x = cx - cos_theta * cx + sin_theta * cy;
139                    let translate_y = cy - sin_theta * cx - cos_theta * cy;
140                    content.extend_from_slice(
141                        format!(
142                            "{} {} {} {} {} {} cm\n{} {} {} {} re f\n",
143                            cos_theta,
144                            sin_theta,
145                            -sin_theta,
146                            cos_theta,
147                            translate_x,
148                            translate_y,
149                            x0,
150                            y0,
151                            width,
152                            height
153                        )
154                        .as_bytes(),
155                    );
156                }
157                _ => {}
158            };
159        }
160    }
161
162    /// Set the width of the shape and return a mutable reference to self.
163    pub fn with_width(&mut self, width: impl Length) -> &mut Self {
164        self.width = Some(width.to_points());
165        self
166    }
167
168    pub fn with_angle(&mut self, angle: impl Angle) -> &mut Self {
169        self.angle = Some(angle.to_radians());
170        self
171    }
172
173    pub fn with_anchor(&mut self, anchor: Anchor) -> &mut Self {
174        self.anchor = Some(anchor);
175        self
176    }
177
178    pub fn with_cap_type(&mut self, cap_type: CapType) -> &mut Self {
179        self.cap_type = Some(cap_type);
180        self
181    }
182
183    pub fn with_color(&mut self, color: impl Color) -> &mut Self {
184        self.color = Some(color.to_rgb());
185        self
186    }
187
188    pub fn get_default_width() -> Pt {
189        Pt(*DEFAULT_WIDTH.lock().unwrap())
190    }
191
192    pub fn set_default_width(width: impl Length) {
193        *DEFAULT_WIDTH.lock().unwrap() = width.to_points();
194    }
195
196    pub fn get_default_cap_type() -> CapType {
197        *DEFAULT_CAP_TYPE.lock().unwrap()
198    }
199
200    pub fn set_default_cap_type(cap_type: CapType) {
201        *DEFAULT_CAP_TYPE.lock().unwrap() = cap_type;
202    }
203
204    pub fn get_default_color() -> Rgb {
205        let (r, g, b) = *DEFAULT_COLOR.lock().unwrap();
206        Rgb(r, g, b)
207    }
208
209    pub fn set_default_color(color: impl Color) {
210        *DEFAULT_COLOR.lock().unwrap() = color.to_rgb();
211    }
212
213    pub fn get_default_angle() -> Degree {
214        Degree(*DEFAULT_ANGLE.lock().unwrap())
215    }
216
217    pub fn set_default_angle(angle: impl Angle) {
218        *DEFAULT_ANGLE.lock().unwrap() = angle.to_radians();
219    }
220
221    pub fn get_default_anchor() -> Anchor {
222        *DEFAULT_ANCHOR.lock().unwrap()
223    }
224
225    pub fn set_default_anchor(anchor: Anchor) {
226        *DEFAULT_ANCHOR.lock().unwrap() = anchor;
227    }
228}