oxidize_pdf/graphics/
mod.rs

1mod color;
2mod image;
3mod path;
4
5pub use color::Color;
6pub use image::{ColorSpace as ImageColorSpace, Image, ImageFormat};
7pub use path::{LineCap, LineJoin, PathBuilder};
8
9use crate::error::Result;
10use std::fmt::Write;
11
12#[derive(Clone)]
13pub struct GraphicsContext {
14    operations: String,
15    current_color: Color,
16    stroke_color: Color,
17    line_width: f64,
18}
19
20impl Default for GraphicsContext {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl GraphicsContext {
27    pub fn new() -> Self {
28        Self {
29            operations: String::new(),
30            current_color: Color::black(),
31            stroke_color: Color::black(),
32            line_width: 1.0,
33        }
34    }
35
36    pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
37        writeln!(&mut self.operations, "{x:.2} {y:.2} m").unwrap();
38        self
39    }
40
41    pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
42        writeln!(&mut self.operations, "{x:.2} {y:.2} l").unwrap();
43        self
44    }
45
46    pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
47        writeln!(
48            &mut self.operations,
49            "{x1:.2} {y1:.2} {x2:.2} {y2:.2} {x3:.2} {y3:.2} c"
50        )
51        .unwrap();
52        self
53    }
54
55    pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
56        writeln!(
57            &mut self.operations,
58            "{x:.2} {y:.2} {width:.2} {height:.2} re"
59        )
60        .unwrap();
61        self
62    }
63
64    pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
65        let k = 0.552284749831;
66        let r = radius;
67
68        self.move_to(cx + r, cy);
69        self.curve_to(cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r);
70        self.curve_to(cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy);
71        self.curve_to(cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r);
72        self.curve_to(cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy);
73        self.close_path()
74    }
75
76    pub fn close_path(&mut self) -> &mut Self {
77        self.operations.push_str("h\n");
78        self
79    }
80
81    pub fn stroke(&mut self) -> &mut Self {
82        self.apply_stroke_color();
83        self.operations.push_str("S\n");
84        self
85    }
86
87    pub fn fill(&mut self) -> &mut Self {
88        self.apply_fill_color();
89        self.operations.push_str("f\n");
90        self
91    }
92
93    pub fn fill_stroke(&mut self) -> &mut Self {
94        self.apply_fill_color();
95        self.apply_stroke_color();
96        self.operations.push_str("B\n");
97        self
98    }
99
100    pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
101        self.stroke_color = color;
102        self
103    }
104
105    pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
106        self.current_color = color;
107        self
108    }
109
110    pub fn set_line_width(&mut self, width: f64) -> &mut Self {
111        self.line_width = width;
112        writeln!(&mut self.operations, "{width:.2} w").unwrap();
113        self
114    }
115
116    pub fn set_line_cap(&mut self, cap: LineCap) -> &mut Self {
117        writeln!(&mut self.operations, "{} J", cap as u8).unwrap();
118        self
119    }
120
121    pub fn set_line_join(&mut self, join: LineJoin) -> &mut Self {
122        writeln!(&mut self.operations, "{} j", join as u8).unwrap();
123        self
124    }
125
126    pub fn save_state(&mut self) -> &mut Self {
127        self.operations.push_str("q\n");
128        self
129    }
130
131    pub fn restore_state(&mut self) -> &mut Self {
132        self.operations.push_str("Q\n");
133        self
134    }
135
136    pub fn translate(&mut self, tx: f64, ty: f64) -> &mut Self {
137        writeln!(&mut self.operations, "1 0 0 1 {tx:.2} {ty:.2} cm").unwrap();
138        self
139    }
140
141    pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
142        writeln!(&mut self.operations, "{sx:.2} 0 0 {sy:.2} 0 0 cm").unwrap();
143        self
144    }
145
146    pub fn rotate(&mut self, angle: f64) -> &mut Self {
147        let cos = angle.cos();
148        let sin = angle.sin();
149        writeln!(
150            &mut self.operations,
151            "{:.6} {:.6} {:.6} {:.6} 0 0 cm",
152            cos, sin, -sin, cos
153        )
154        .unwrap();
155        self
156    }
157
158    pub fn transform(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> &mut Self {
159        writeln!(
160            &mut self.operations,
161            "{a:.2} {b:.2} {c:.2} {d:.2} {e:.2} {f:.2} cm"
162        )
163        .unwrap();
164        self
165    }
166
167    pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
168        self.rect(x, y, width, height)
169    }
170
171    pub fn draw_image(
172        &mut self,
173        image_name: &str,
174        x: f64,
175        y: f64,
176        width: f64,
177        height: f64,
178    ) -> &mut Self {
179        // Save graphics state
180        self.save_state();
181
182        // Set up transformation matrix for image placement
183        // PDF coordinate system has origin at bottom-left, so we need to translate and scale
184        writeln!(
185            &mut self.operations,
186            "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
187        )
188        .unwrap();
189
190        // Draw the image XObject
191        writeln!(&mut self.operations, "/{image_name} Do").unwrap();
192
193        // Restore graphics state
194        self.restore_state();
195
196        self
197    }
198
199    fn apply_stroke_color(&mut self) {
200        match self.stroke_color {
201            Color::Rgb(r, g, b) => {
202                writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} RG").unwrap();
203            }
204            Color::Gray(g) => {
205                writeln!(&mut self.operations, "{g:.3} G").unwrap();
206            }
207            Color::Cmyk(c, m, y, k) => {
208                writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} K").unwrap();
209            }
210        }
211    }
212
213    fn apply_fill_color(&mut self) {
214        match self.current_color {
215            Color::Rgb(r, g, b) => {
216                writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} rg").unwrap();
217            }
218            Color::Gray(g) => {
219                writeln!(&mut self.operations, "{g:.3} g").unwrap();
220            }
221            Color::Cmyk(c, m, y, k) => {
222                writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} k").unwrap();
223            }
224        }
225    }
226
227    pub(crate) fn generate_operations(&self) -> Result<Vec<u8>> {
228        Ok(self.operations.as_bytes().to_vec())
229    }
230}