1#[derive(Debug, Clone, Copy)]
9pub struct Matrix {
10 pub a: f64,
12 pub b: f64,
14 pub c: f64,
16 pub d: f64,
18 pub e: f64,
20 pub f: f64,
22}
23
24impl Matrix {
25 pub fn identity() -> Self {
27 Self {
28 a: 1.0,
29 b: 0.0,
30 c: 0.0,
31 d: 1.0,
32 e: 0.0,
33 f: 0.0,
34 }
35 }
36
37 pub fn translate(tx: f64, ty: f64) -> Self {
39 Self {
40 a: 1.0,
41 b: 0.0,
42 c: 0.0,
43 d: 1.0,
44 e: tx,
45 f: ty,
46 }
47 }
48
49 pub fn multiply(&self, other: &Matrix) -> Matrix {
51 Matrix {
52 a: self.a * other.a + self.b * other.c,
53 b: self.a * other.b + self.b * other.d,
54 c: self.c * other.a + self.d * other.c,
55 d: self.c * other.b + self.d * other.d,
56 e: self.e * other.a + self.f * other.c + other.e,
57 f: self.e * other.b + self.f * other.d + other.f,
58 }
59 }
60
61 pub fn transform_point(&self, x: f64, y: f64) -> (f64, f64) {
63 (
64 self.a * x + self.c * y + self.e,
65 self.b * x + self.d * y + self.f,
66 )
67 }
68
69 pub fn font_size_factor(&self) -> f64 {
71 (self.b * self.b + self.d * self.d).sqrt()
72 }
73}
74
75impl Default for Matrix {
76 fn default() -> Self {
77 Self::identity()
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct TextState {
84 pub font_name: String,
86 pub font_size: f64,
88 pub char_spacing: f64,
90 pub word_spacing: f64,
92 pub horizontal_scaling: f64,
94 pub leading: f64,
96 pub rise: f64,
98 pub render_mode: i32,
100}
101
102impl Default for TextState {
103 fn default() -> Self {
104 Self {
105 font_name: String::new(),
106 font_size: 0.0,
107 char_spacing: 0.0,
108 word_spacing: 0.0,
109 horizontal_scaling: 100.0,
110 leading: 0.0,
111 rise: 0.0,
112 render_mode: 0,
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct GraphicsState {
120 pub ctm: Matrix,
122 pub text_matrix: Matrix,
124 pub text_line_matrix: Matrix,
126 pub text_state: TextState,
128 pub fill_color: Vec<f64>,
130 pub stroke_color: Vec<f64>,
132 pub fill_color_space_components: u8,
135 pub stroke_color_space_components: u8,
138 pub fill_pattern: Option<Vec<u8>>,
140 pub stroke_pattern: Option<Vec<u8>>,
142}
143
144impl Default for GraphicsState {
145 fn default() -> Self {
146 Self {
147 ctm: Matrix::identity(),
148 text_matrix: Matrix::identity(),
149 text_line_matrix: Matrix::identity(),
150 text_state: TextState::default(),
151 fill_color: vec![0.0], stroke_color: vec![0.0],
153 fill_color_space_components: 1, stroke_color_space_components: 1,
155 fill_pattern: None,
156 stroke_pattern: None,
157 }
158 }
159}
160
161impl GraphicsState {
162 pub fn begin_text(&mut self) {
164 self.text_matrix = Matrix::identity();
165 self.text_line_matrix = Matrix::identity();
166 }
167
168 pub fn text_rendering_matrix(&self) -> Matrix {
170 let font_matrix = Matrix {
171 a: self.text_state.font_size * (self.text_state.horizontal_scaling / 100.0),
172 b: 0.0,
173 c: 0.0,
174 d: self.text_state.font_size,
175 e: 0.0,
176 f: self.text_state.rise,
177 };
178 let tm_ctm = self.text_matrix.multiply(&self.ctm);
179 font_matrix.multiply(&tm_ctm)
180 }
181
182 pub fn text_position(&self) -> (f64, f64) {
184 let trm = self.text_rendering_matrix();
185 (trm.e, trm.f)
186 }
187
188 pub fn effective_font_size(&self) -> f64 {
190 let trm = self.text_rendering_matrix();
191 trm.font_size_factor()
192 }
193
194 pub fn translate_text(&mut self, tx: f64, ty: f64) {
196 let translation = Matrix::translate(tx, ty);
197 self.text_line_matrix = translation.multiply(&self.text_line_matrix);
198 self.text_matrix = self.text_line_matrix;
199 }
200
201 pub fn set_text_matrix(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
203 self.text_matrix = Matrix { a, b, c, d, e, f };
204 self.text_line_matrix = self.text_matrix;
205 }
206
207 pub fn next_line(&mut self) {
209 self.translate_text(0.0, -self.text_state.leading);
210 }
211
212 pub fn advance_text(&mut self, displacement: f64) {
214 let scaled = displacement * self.text_state.horizontal_scaling / 100.0;
215 self.text_matrix.e += scaled * self.text_matrix.a;
216 self.text_matrix.f += scaled * self.text_matrix.b;
217 }
218}
219
220#[derive(Default)]
222pub struct GraphicsStateStack {
223 stack: Vec<GraphicsState>,
224 pub current: GraphicsState,
226}
227
228impl GraphicsStateStack {
229 pub fn save(&mut self) {
231 self.stack.push(self.current.clone());
232 }
233
234 pub fn restore(&mut self) {
236 if let Some(state) = self.stack.pop() {
237 self.current = state;
238 }
239 }
240
241 pub fn concat_ctm(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
243 let new_matrix = Matrix { a, b, c, d, e, f };
244 self.current.ctm = new_matrix.multiply(&self.current.ctm);
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_matrix_identity() {
254 let m = Matrix::identity();
255 let (x, y) = m.transform_point(10.0, 20.0);
256 assert!((x - 10.0).abs() < 1e-10);
257 assert!((y - 20.0).abs() < 1e-10);
258 }
259
260 #[test]
261 fn test_matrix_translate() {
262 let m = Matrix::translate(100.0, 200.0);
263 let (x, y) = m.transform_point(10.0, 20.0);
264 assert!((x - 110.0).abs() < 1e-10);
265 assert!((y - 220.0).abs() < 1e-10);
266 }
267
268 #[test]
269 fn test_matrix_multiply() {
270 let a = Matrix::translate(10.0, 20.0);
271 let b = Matrix::translate(30.0, 40.0);
272 let c = a.multiply(&b);
273 let (x, y) = c.transform_point(0.0, 0.0);
274 assert!((x - 40.0).abs() < 1e-10);
275 assert!((y - 60.0).abs() < 1e-10);
276 }
277
278 #[test]
279 fn test_text_translate() {
280 let mut gs = GraphicsState::default();
281 gs.text_state.font_size = 12.0;
282 gs.begin_text();
283 gs.translate_text(100.0, 700.0);
284 let (x, y) = gs.text_position();
285 assert!((x - 100.0 * 12.0).abs() < 1e-6 || (x - 100.0).abs() < 1e-6);
286 assert!(y.abs() > 0.0 || y.abs() < 1e-6);
288 }
289
290 #[test]
291 fn test_graphics_state_stack() {
292 let mut stack = GraphicsStateStack::default();
293 stack.current.text_state.font_size = 12.0;
294 stack.save();
295 stack.current.text_state.font_size = 24.0;
296 assert!((stack.current.text_state.font_size - 24.0).abs() < 1e-10);
297 stack.restore();
298 assert!((stack.current.text_state.font_size - 12.0).abs() < 1e-10);
299 }
300}