1use crate::geometry::Point;
27
28#[derive(Debug, Clone, Copy, PartialEq)]
30pub enum CoordinateSystem {
31 PdfStandard,
34
35 ScreenSpace,
38
39 Custom(TransformMatrix),
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
58pub struct TransformMatrix {
59 pub a: f64,
61 pub b: f64,
63 pub c: f64,
65 pub d: f64,
67 pub e: f64,
69 pub f: f64,
71}
72
73impl Default for CoordinateSystem {
74 fn default() -> Self {
75 Self::PdfStandard
76 }
77}
78
79impl TransformMatrix {
80 pub const IDENTITY: Self = Self {
82 a: 1.0,
83 b: 0.0,
84 c: 0.0,
85 d: 1.0,
86 e: 0.0,
87 f: 0.0,
88 };
89
90 pub fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
92 Self { a, b, c, d, e, f }
93 }
94
95 pub fn translate(tx: f64, ty: f64) -> Self {
97 Self {
98 a: 1.0,
99 b: 0.0,
100 c: 0.0,
101 d: 1.0,
102 e: tx,
103 f: ty,
104 }
105 }
106
107 pub fn scale(sx: f64, sy: f64) -> Self {
109 Self {
110 a: sx,
111 b: 0.0,
112 c: 0.0,
113 d: sy,
114 e: 0.0,
115 f: 0.0,
116 }
117 }
118
119 pub fn rotate(angle: f64) -> Self {
121 let cos = angle.cos();
122 let sin = angle.sin();
123 Self {
124 a: cos,
125 b: sin,
126 c: -sin,
127 d: cos,
128 e: 0.0,
129 f: 0.0,
130 }
131 }
132
133 pub fn flip_y(page_height: f64) -> Self {
135 Self {
136 a: 1.0,
137 b: 0.0,
138 c: 0.0,
139 d: -1.0,
140 e: 0.0,
141 f: page_height,
142 }
143 }
144
145 pub fn multiply(&self, other: &TransformMatrix) -> Self {
147 Self {
148 a: self.a * other.a + self.c * other.b,
149 b: self.b * other.a + self.d * other.b,
150 c: self.a * other.c + self.c * other.d,
151 d: self.b * other.c + self.d * other.d,
152 e: self.a * other.e + self.c * other.f + self.e,
153 f: self.b * other.e + self.d * other.f + self.f,
154 }
155 }
156
157 pub fn transform_point(&self, point: Point) -> Point {
159 Point::new(
160 self.a * point.x + self.c * point.y + self.e,
161 self.b * point.x + self.d * point.y + self.f,
162 )
163 }
164
165 pub fn to_pdf_ctm(&self) -> String {
167 format!(
168 "{:.6} {:.6} {:.6} {:.6} {:.6} {:.6} cm",
169 self.a, self.b, self.c, self.d, self.e, self.f
170 )
171 }
172}
173
174impl CoordinateSystem {
175 pub fn to_pdf_standard_matrix(&self, page_height: f64) -> TransformMatrix {
177 match *self {
178 Self::PdfStandard => TransformMatrix::IDENTITY,
179 Self::ScreenSpace => TransformMatrix::flip_y(page_height),
180 Self::Custom(matrix) => matrix,
181 }
182 }
183
184 pub fn to_pdf_standard(&self, point: Point, page_height: f64) -> Point {
186 let matrix = self.to_pdf_standard_matrix(page_height);
187 matrix.transform_point(point)
188 }
189
190 pub fn y_to_pdf_standard(&self, y: f64, page_height: f64) -> f64 {
192 match *self {
193 Self::PdfStandard => y,
194 Self::ScreenSpace => page_height - y,
195 Self::Custom(matrix) => {
196 let transformed = matrix.transform_point(Point::new(0.0, y));
198 transformed.y
199 }
200 }
201 }
202
203 pub fn grows_upward(&self) -> bool {
205 match *self {
206 Self::PdfStandard => true,
207 Self::ScreenSpace => false,
208 Self::Custom(matrix) => matrix.d > 0.0, }
210 }
211}
212
213#[derive(Debug)]
215pub struct RenderContext {
216 pub coordinate_system: CoordinateSystem,
218 pub page_width: f64,
220 pub page_height: f64,
221 pub current_transform: TransformMatrix,
223}
224
225impl RenderContext {
226 pub fn new(coordinate_system: CoordinateSystem, page_width: f64, page_height: f64) -> Self {
228 let current_transform = coordinate_system.to_pdf_standard_matrix(page_height);
229
230 Self {
231 coordinate_system,
232 page_width,
233 page_height,
234 current_transform,
235 }
236 }
237
238 pub fn to_pdf_standard(&self, point: Point) -> Point {
240 self.coordinate_system
241 .to_pdf_standard(point, self.page_height)
242 }
243
244 pub fn y_to_pdf(&self, y: f64) -> f64 {
246 self.coordinate_system
247 .y_to_pdf_standard(y, self.page_height)
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::geometry::Point;
255
256 #[test]
257 fn test_transform_matrix_identity() {
258 let identity = TransformMatrix::IDENTITY;
259 let point = Point::new(10.0, 20.0);
260 let transformed = identity.transform_point(point);
261
262 assert_eq!(transformed, point);
263 }
264
265 #[test]
266 fn test_transform_matrix_translate() {
267 let translate = TransformMatrix::translate(5.0, 10.0);
268 let point = Point::new(1.0, 2.0);
269 let transformed = translate.transform_point(point);
270
271 assert_eq!(transformed, Point::new(6.0, 12.0));
272 }
273
274 #[test]
275 fn test_transform_matrix_scale() {
276 let scale = TransformMatrix::scale(2.0, 3.0);
277 let point = Point::new(4.0, 5.0);
278 let transformed = scale.transform_point(point);
279
280 assert_eq!(transformed, Point::new(8.0, 15.0));
281 }
282
283 #[test]
284 fn test_coordinate_system_pdf_standard() {
285 let coord_system = CoordinateSystem::PdfStandard;
286 let page_height = 842.0;
287 let point = Point::new(100.0, 200.0);
288
289 let pdf_point = coord_system.to_pdf_standard(point, page_height);
290 assert_eq!(pdf_point, point); }
292
293 #[test]
294 fn test_coordinate_system_screen_space() {
295 let coord_system = CoordinateSystem::ScreenSpace;
296 let page_height = 842.0;
297 let point = Point::new(100.0, 200.0);
298
299 let pdf_point = coord_system.to_pdf_standard(point, page_height);
300 assert_eq!(pdf_point, Point::new(100.0, 642.0)); }
302
303 #[test]
304 fn test_y_flip_matrix() {
305 let page_height = 800.0;
306 let flip = TransformMatrix::flip_y(page_height);
307
308 let top_screen = Point::new(0.0, 0.0);
310 let top_pdf = flip.transform_point(top_screen);
311 assert_eq!(top_pdf, Point::new(0.0, 800.0));
312
313 let bottom_screen = Point::new(0.0, 800.0);
315 let bottom_pdf = flip.transform_point(bottom_screen);
316 assert_eq!(bottom_pdf, Point::new(0.0, 0.0));
317 }
318
319 #[test]
320 fn test_render_context() {
321 let context = RenderContext::new(CoordinateSystem::ScreenSpace, 595.0, 842.0);
322
323 let screen_point = Point::new(100.0, 100.0);
324 let pdf_point = context.to_pdf_standard(screen_point);
325
326 assert_eq!(pdf_point, Point::new(100.0, 742.0));
327 }
328}