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,
134 pub stroke_color_space_components: u8,
136}
137
138impl Default for GraphicsState {
139 fn default() -> Self {
140 Self {
141 ctm: Matrix::identity(),
142 text_matrix: Matrix::identity(),
143 text_line_matrix: Matrix::identity(),
144 text_state: TextState::default(),
145 fill_color: vec![0.0], stroke_color: vec![0.0],
147 fill_color_space_components: 1, stroke_color_space_components: 1,
149 }
150 }
151}
152
153impl GraphicsState {
154 pub fn begin_text(&mut self) {
156 self.text_matrix = Matrix::identity();
157 self.text_line_matrix = Matrix::identity();
158 }
159
160 pub fn text_rendering_matrix(&self) -> Matrix {
162 let font_matrix = Matrix {
163 a: self.text_state.font_size * (self.text_state.horizontal_scaling / 100.0),
164 b: 0.0,
165 c: 0.0,
166 d: self.text_state.font_size,
167 e: 0.0,
168 f: self.text_state.rise,
169 };
170 let tm_ctm = self.text_matrix.multiply(&self.ctm);
171 font_matrix.multiply(&tm_ctm)
172 }
173
174 pub fn text_position(&self) -> (f64, f64) {
176 let trm = self.text_rendering_matrix();
177 (trm.e, trm.f)
178 }
179
180 pub fn effective_font_size(&self) -> f64 {
182 let trm = self.text_rendering_matrix();
183 trm.font_size_factor()
184 }
185
186 pub fn translate_text(&mut self, tx: f64, ty: f64) {
188 let translation = Matrix::translate(tx, ty);
189 self.text_line_matrix = translation.multiply(&self.text_line_matrix);
190 self.text_matrix = self.text_line_matrix;
191 }
192
193 pub fn set_text_matrix(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
195 self.text_matrix = Matrix { a, b, c, d, e, f };
196 self.text_line_matrix = self.text_matrix;
197 }
198
199 pub fn next_line(&mut self) {
201 self.translate_text(0.0, -self.text_state.leading);
202 }
203
204 pub fn advance_text(&mut self, displacement: f64) {
206 let scaled = displacement * self.text_state.horizontal_scaling / 100.0;
207 self.text_matrix.e += scaled * self.text_matrix.a;
208 self.text_matrix.f += scaled * self.text_matrix.b;
209 }
210}
211
212#[derive(Default)]
214pub struct GraphicsStateStack {
215 stack: Vec<GraphicsState>,
216 pub current: GraphicsState,
218}
219
220impl GraphicsStateStack {
221 pub fn save(&mut self) {
223 self.stack.push(self.current.clone());
224 }
225
226 pub fn restore(&mut self) {
228 if let Some(state) = self.stack.pop() {
229 self.current = state;
230 }
231 }
232
233 pub fn concat_ctm(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
235 let new_matrix = Matrix { a, b, c, d, e, f };
236 self.current.ctm = new_matrix.multiply(&self.current.ctm);
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_matrix_identity() {
246 let m = Matrix::identity();
247 let (x, y) = m.transform_point(10.0, 20.0);
248 assert!((x - 10.0).abs() < 1e-10);
249 assert!((y - 20.0).abs() < 1e-10);
250 }
251
252 #[test]
253 fn test_matrix_translate() {
254 let m = Matrix::translate(100.0, 200.0);
255 let (x, y) = m.transform_point(10.0, 20.0);
256 assert!((x - 110.0).abs() < 1e-10);
257 assert!((y - 220.0).abs() < 1e-10);
258 }
259
260 #[test]
261 fn test_matrix_multiply() {
262 let a = Matrix::translate(10.0, 20.0);
263 let b = Matrix::translate(30.0, 40.0);
264 let c = a.multiply(&b);
265 let (x, y) = c.transform_point(0.0, 0.0);
266 assert!((x - 40.0).abs() < 1e-10);
267 assert!((y - 60.0).abs() < 1e-10);
268 }
269
270 #[test]
271 fn test_text_translate() {
272 let mut gs = GraphicsState::default();
273 gs.text_state.font_size = 12.0;
274 gs.begin_text();
275 gs.translate_text(100.0, 700.0);
276 let (x, y) = gs.text_position();
277 assert!((x - 100.0 * 12.0).abs() < 1e-6 || (x - 100.0).abs() < 1e-6);
278 assert!(y.abs() > 0.0 || y.abs() < 1e-6);
280 }
281
282 #[test]
283 fn test_graphics_state_stack() {
284 let mut stack = GraphicsStateStack::default();
285 stack.current.text_state.font_size = 12.0;
286 stack.save();
287 stack.current.text_state.font_size = 24.0;
288 assert!((stack.current.text_state.font_size - 24.0).abs() < 1e-10);
289 stack.restore();
290 assert!((stack.current.text_state.font_size - 12.0).abs() < 1e-10);
291 }
292}