1use pdfboss_core::content::Op;
12use pdfboss_core::{Matrix, Name, Point};
13
14use crate::color::Color;
15use crate::error::Result;
16use crate::font::Standard14;
17use crate::image::ImageData;
18
19#[allow(clippy::excessive_precision)] const KAPPA: f32 = 0.552284749831;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum LineCap {
25 #[default]
27 Butt,
28 Round,
30 Square,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub enum LineJoin {
37 #[default]
39 Miter,
40 Round,
42 Bevel,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct ImageHandle(pub(crate) usize);
49
50#[derive(Debug)]
52pub struct CanvasParts {
53 pub ops: Vec<Op>,
55 pub fonts: Vec<Standard14>,
57 pub images: Vec<ImageData>,
59}
60
61#[derive(Debug, Default)]
63pub struct Canvas {
64 ops: Vec<Op>,
65 fonts: Vec<Standard14>,
66 images: Vec<ImageData>,
67}
68
69impl Canvas {
70 pub fn new() -> Canvas {
72 Canvas::default()
73 }
74
75 pub fn save(&mut self) {
77 self.ops.push(Op::Save);
78 }
79
80 pub fn restore(&mut self) {
82 self.ops.push(Op::Restore);
83 }
84
85 pub fn transform(&mut self, m: Matrix) {
87 self.ops.push(Op::Concat(m));
88 }
89
90 pub fn set_line_width(&mut self, width: f32) {
92 self.ops.push(Op::SetLineWidth(width));
93 }
94
95 pub fn set_line_cap(&mut self, cap: LineCap) {
97 self.ops.push(Op::SetLineCap(cap as i32));
98 }
99
100 pub fn set_line_join(&mut self, join: LineJoin) {
102 self.ops.push(Op::SetLineJoin(join as i32));
103 }
104
105 pub fn set_miter_limit(&mut self, limit: f32) {
107 self.ops.push(Op::SetMiterLimit(limit));
108 }
109
110 pub fn set_dash(&mut self, pattern: &[f32], phase: f32) {
112 self.ops.push(Op::SetDash(pattern.to_vec(), phase));
113 }
114
115 pub fn set_fill(&mut self, color: Color) {
117 self.ops.push(color.fill_op());
118 }
119
120 pub fn set_stroke(&mut self, color: Color) {
122 self.ops.push(color.stroke_op());
123 }
124
125 pub fn move_to(&mut self, x: f32, y: f32) {
127 self.ops.push(Op::MoveTo(x, y));
128 }
129
130 pub fn line_to(&mut self, x: f32, y: f32) {
132 self.ops.push(Op::LineTo(x, y));
133 }
134
135 #[allow(clippy::too_many_arguments)] pub fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
138 self.ops.push(Op::CurveTo(x1, y1, x2, y2, x3, y3));
139 }
140
141 pub fn close(&mut self) {
143 self.ops.push(Op::ClosePath);
144 }
145
146 pub fn rect(&mut self, x: f32, y: f32, width: f32, height: f32) {
148 self.ops.push(Op::Rect(x, y, width, height));
149 }
150
151 pub fn circle(&mut self, cx: f32, cy: f32, r: f32) {
154 self.ellipse(cx, cy, r, r);
155 }
156
157 pub fn ellipse(&mut self, cx: f32, cy: f32, rx: f32, ry: f32) {
160 let ox = KAPPA * rx;
161 let oy = KAPPA * ry;
162 self.ops.push(Op::MoveTo(cx + rx, cy));
163 self.ops
164 .push(Op::CurveTo(cx + rx, cy + oy, cx + ox, cy + ry, cx, cy + ry));
165 self.ops
166 .push(Op::CurveTo(cx - ox, cy + ry, cx - rx, cy + oy, cx - rx, cy));
167 self.ops
168 .push(Op::CurveTo(cx - rx, cy - oy, cx - ox, cy - ry, cx, cy - ry));
169 self.ops
170 .push(Op::CurveTo(cx + ox, cy - ry, cx + rx, cy - oy, cx + rx, cy));
171 self.ops.push(Op::ClosePath);
172 }
173
174 pub fn polygon(&mut self, points: &[Point]) {
177 if points.len() < 3 {
178 return;
179 }
180 self.ops.push(Op::MoveTo(points[0].x, points[0].y));
181 for point in &points[1..] {
182 self.ops.push(Op::LineTo(point.x, point.y));
183 }
184 self.ops.push(Op::ClosePath);
185 }
186
187 pub fn fill(&mut self) {
189 self.ops.push(Op::Fill);
190 }
191
192 pub fn fill_even_odd(&mut self) {
194 self.ops.push(Op::FillEvenOdd);
195 }
196
197 pub fn stroke(&mut self) {
199 self.ops.push(Op::Stroke);
200 }
201
202 pub fn close_stroke(&mut self) {
204 self.ops.push(Op::CloseStroke);
205 }
206
207 pub fn fill_stroke(&mut self) {
209 self.ops.push(Op::FillStroke);
210 }
211
212 pub fn clip(&mut self) {
216 self.ops.push(Op::ClipNonZero);
217 self.ops.push(Op::EndPath);
218 }
219
220 pub fn clip_even_odd(&mut self) {
224 self.ops.push(Op::ClipEvenOdd);
225 self.ops.push(Op::EndPath);
226 }
227
228 pub fn end_path(&mut self) {
230 self.ops.push(Op::EndPath);
231 }
232
233 pub fn text(&mut self, text: &str, x: f32, y: f32, font: Standard14, size: f32) -> Result<()> {
237 let encoded = font.encode(text)?;
238 let index = match self.fonts.iter().position(|face| *face == font) {
239 Some(index) => index,
240 None => {
241 self.fonts.push(font);
242 self.fonts.len() - 1
243 }
244 };
245 self.ops.push(Op::BeginText);
246 self.ops
247 .push(Op::SetFont(Name(format!("F{}", index + 1)), size));
248 self.ops.push(Op::TextMove(x, y));
249 self.ops.push(Op::ShowText(encoded));
250 self.ops.push(Op::EndText);
251 Ok(())
252 }
253
254 pub fn add_image(&mut self, image: ImageData) -> ImageHandle {
256 let handle = ImageHandle(self.images.len());
257 self.images.push(image);
258 handle
259 }
260
261 pub fn draw_image(&mut self, image: ImageHandle, x: f32, y: f32, width: f32, height: f32) {
264 self.ops.push(Op::Save);
265 self.ops.push(Op::Concat(Matrix {
266 a: width,
267 b: 0.0,
268 c: 0.0,
269 d: height,
270 e: x,
271 f: y,
272 }));
273 self.ops
274 .push(Op::XObject(Name(format!("Im{}", image.0 + 1))));
275 self.ops.push(Op::Restore);
276 }
277
278 pub fn op(&mut self, op: Op) {
282 self.ops.push(op);
283 }
284
285 pub fn ops(&self) -> &[Op] {
287 &self.ops
288 }
289
290 pub(crate) fn into_parts(self) -> CanvasParts {
292 CanvasParts {
293 ops: self.ops,
294 fonts: self.fonts,
295 images: self.images,
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use pdfboss_core::Name;
303
304 use super::*;
305 use crate::error::Error;
306
307 fn name(text: &str) -> Name {
308 Name(text.into())
309 }
310
311 #[test]
312 fn state_ops_push_single_operators() {
313 let mut canvas = Canvas::new();
314 canvas.save();
315 canvas.restore();
316 canvas.transform(Matrix {
317 a: 1.0,
318 b: 2.0,
319 c: 3.0,
320 d: 4.0,
321 e: 5.0,
322 f: 6.0,
323 });
324 canvas.set_line_width(2.5);
325 canvas.set_miter_limit(4.0);
326 canvas.set_dash(&[3.0, 1.0], 0.5);
327 assert_eq!(
328 canvas.ops(),
329 [
330 Op::Save,
331 Op::Restore,
332 Op::Concat(Matrix {
333 a: 1.0,
334 b: 2.0,
335 c: 3.0,
336 d: 4.0,
337 e: 5.0,
338 f: 6.0,
339 }),
340 Op::SetLineWidth(2.5),
341 Op::SetMiterLimit(4.0),
342 Op::SetDash(vec![3.0, 1.0], 0.5),
343 ]
344 );
345 }
346
347 #[test]
348 fn line_cap_and_join_map_declaration_order() {
349 let mut canvas = Canvas::new();
350 canvas.set_line_cap(LineCap::Butt);
351 canvas.set_line_cap(LineCap::Round);
352 canvas.set_line_cap(LineCap::Square);
353 canvas.set_line_join(LineJoin::Miter);
354 canvas.set_line_join(LineJoin::Round);
355 canvas.set_line_join(LineJoin::Bevel);
356 assert_eq!(
357 canvas.ops(),
358 [
359 Op::SetLineCap(0),
360 Op::SetLineCap(1),
361 Op::SetLineCap(2),
362 Op::SetLineJoin(0),
363 Op::SetLineJoin(1),
364 Op::SetLineJoin(2),
365 ]
366 );
367 }
368
369 #[test]
370 fn fill_and_stroke_colors() {
371 let mut canvas = Canvas::new();
372 canvas.set_fill(Color::Gray(0.5));
373 canvas.set_fill(Color::Rgb(0.1, 0.2, 0.3));
374 canvas.set_fill(Color::Cmyk(0.1, 0.2, 0.3, 0.4));
375 canvas.set_stroke(Color::Gray(0.5));
376 canvas.set_stroke(Color::Rgb(0.1, 0.2, 0.3));
377 canvas.set_stroke(Color::Cmyk(0.1, 0.2, 0.3, 0.4));
378 assert_eq!(
379 canvas.ops(),
380 [
381 Op::SetFillGray(0.5),
382 Op::SetFillRGB(0.1, 0.2, 0.3),
383 Op::SetFillCMYK(0.1, 0.2, 0.3, 0.4),
384 Op::SetStrokeGray(0.5),
385 Op::SetStrokeRGB(0.1, 0.2, 0.3),
386 Op::SetStrokeCMYK(0.1, 0.2, 0.3, 0.4),
387 ]
388 );
389 }
390
391 #[test]
392 fn path_construction_ops() {
393 let mut canvas = Canvas::new();
394 canvas.move_to(1.0, 2.0);
395 canvas.line_to(3.0, 4.0);
396 canvas.curve_to(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
397 canvas.close();
398 canvas.rect(10.0, 20.0, 30.0, 40.0);
399 assert_eq!(
400 canvas.ops(),
401 [
402 Op::MoveTo(1.0, 2.0),
403 Op::LineTo(3.0, 4.0),
404 Op::CurveTo(1.0, 2.0, 3.0, 4.0, 5.0, 6.0),
405 Op::ClosePath,
406 Op::Rect(10.0, 20.0, 30.0, 40.0),
407 ]
408 );
409 }
410
411 #[test]
412 fn circle_appends_four_arcs_and_close() {
413 let mut canvas = Canvas::new();
414 canvas.circle(0.0, 0.0, 1.0);
415 assert_eq!(
416 canvas.ops(),
417 [
418 Op::MoveTo(1.0, 0.0),
419 Op::CurveTo(1.0, KAPPA, KAPPA, 1.0, 0.0, 1.0),
420 Op::CurveTo(-KAPPA, 1.0, -1.0, KAPPA, -1.0, 0.0),
421 Op::CurveTo(-1.0, -KAPPA, -KAPPA, -1.0, 0.0, -1.0),
422 Op::CurveTo(KAPPA, -1.0, 1.0, -KAPPA, 1.0, 0.0),
423 Op::ClosePath,
424 ]
425 );
426 }
427
428 #[test]
429 fn ellipse_appends_four_arcs_and_close() {
430 let (cx, cy, rx, ry) = (10.0f32, 20.0f32, 4.0f32, 2.0f32);
431 let ox = KAPPA * rx;
432 let oy = KAPPA * ry;
433 let mut canvas = Canvas::new();
434 canvas.ellipse(cx, cy, rx, ry);
435 assert_eq!(
436 canvas.ops(),
437 [
438 Op::MoveTo(cx + rx, cy),
439 Op::CurveTo(cx + rx, cy + oy, cx + ox, cy + ry, cx, cy + ry),
440 Op::CurveTo(cx - ox, cy + ry, cx - rx, cy + oy, cx - rx, cy),
441 Op::CurveTo(cx - rx, cy - oy, cx - ox, cy - ry, cx, cy - ry),
442 Op::CurveTo(cx + ox, cy - ry, cx + rx, cy - oy, cx + rx, cy),
443 Op::ClosePath,
444 ]
445 );
446 }
447
448 #[test]
449 fn polygon_appends_closed_path() {
450 let mut canvas = Canvas::new();
451 canvas.polygon(&[
452 Point::new(0.0, 0.0),
453 Point::new(10.0, 0.0),
454 Point::new(5.0, 8.0),
455 ]);
456 assert_eq!(
457 canvas.ops(),
458 [
459 Op::MoveTo(0.0, 0.0),
460 Op::LineTo(10.0, 0.0),
461 Op::LineTo(5.0, 8.0),
462 Op::ClosePath,
463 ]
464 );
465 }
466
467 #[test]
468 fn polygon_under_three_points_is_no_op() {
469 let mut canvas = Canvas::new();
470 canvas.polygon(&[]);
471 canvas.polygon(&[Point::new(1.0, 2.0)]);
472 canvas.polygon(&[Point::new(1.0, 2.0), Point::new(3.0, 4.0)]);
473 assert_eq!(canvas.ops(), []);
474 }
475
476 #[test]
477 fn paint_verbs_push_single_operators() {
478 let mut canvas = Canvas::new();
479 canvas.fill();
480 canvas.fill_even_odd();
481 canvas.stroke();
482 canvas.close_stroke();
483 canvas.fill_stroke();
484 canvas.end_path();
485 assert_eq!(
486 canvas.ops(),
487 [
488 Op::Fill,
489 Op::FillEvenOdd,
490 Op::Stroke,
491 Op::CloseStroke,
492 Op::FillStroke,
493 Op::EndPath,
494 ]
495 );
496 }
497
498 #[test]
499 fn clip_pairs_consume_the_path() {
500 let mut canvas = Canvas::new();
501 canvas.clip();
502 canvas.clip_even_odd();
503 assert_eq!(
504 canvas.ops(),
505 [Op::ClipNonZero, Op::EndPath, Op::ClipEvenOdd, Op::EndPath,]
506 );
507 }
508
509 #[test]
510 fn text_pushes_five_op_sequence() {
511 let mut canvas = Canvas::new();
512 canvas
513 .text("Hi", 72.0, 720.0, Standard14::Helvetica, 12.0)
514 .unwrap();
515 assert_eq!(
516 canvas.ops(),
517 [
518 Op::BeginText,
519 Op::SetFont(name("F1"), 12.0),
520 Op::TextMove(72.0, 720.0),
521 Op::ShowText(b"Hi".to_vec()),
522 Op::EndText,
523 ]
524 );
525 let parts = canvas.into_parts();
526 assert_eq!(parts.fonts, [Standard14::Helvetica]);
527 }
528
529 #[test]
530 fn texts_in_the_same_font_share_f1() {
531 let mut canvas = Canvas::new();
532 canvas
533 .text("one", 0.0, 0.0, Standard14::TimesRoman, 10.0)
534 .unwrap();
535 canvas
536 .text("two", 0.0, 20.0, Standard14::TimesRoman, 10.0)
537 .unwrap();
538 let font_names: Vec<&Op> = canvas
539 .ops()
540 .iter()
541 .filter(|op| matches!(op, Op::SetFont(..)))
542 .collect();
543 assert_eq!(
544 font_names,
545 [
546 &Op::SetFont(name("F1"), 10.0),
547 &Op::SetFont(name("F1"), 10.0),
548 ]
549 );
550 assert_eq!(canvas.into_parts().fonts, [Standard14::TimesRoman]);
551 }
552
553 #[test]
554 fn second_face_gets_f2() {
555 let mut canvas = Canvas::new();
556 canvas
557 .text("one", 0.0, 0.0, Standard14::Helvetica, 10.0)
558 .unwrap();
559 canvas
560 .text("two", 0.0, 20.0, Standard14::CourierBold, 10.0)
561 .unwrap();
562 canvas
563 .text("three", 0.0, 40.0, Standard14::Helvetica, 10.0)
564 .unwrap();
565 let font_names: Vec<&Op> = canvas
566 .ops()
567 .iter()
568 .filter(|op| matches!(op, Op::SetFont(..)))
569 .collect();
570 assert_eq!(
571 font_names,
572 [
573 &Op::SetFont(name("F1"), 10.0),
574 &Op::SetFont(name("F2"), 10.0),
575 &Op::SetFont(name("F1"), 10.0),
576 ]
577 );
578 assert_eq!(
579 canvas.into_parts().fonts,
580 [Standard14::Helvetica, Standard14::CourierBold]
581 );
582 }
583
584 #[test]
585 fn text_error_leaves_canvas_untouched() {
586 let mut canvas = Canvas::new();
587 canvas.save();
588 let result = canvas.text("\u{2318}", 0.0, 0.0, Standard14::Helvetica, 12.0);
589 assert!(matches!(
590 result,
591 Err(Error::Unencodable { ch: '\u{2318}', .. })
592 ));
593 assert_eq!(canvas.ops(), [Op::Save]);
594 assert!(canvas.into_parts().fonts.is_empty());
595 }
596
597 #[test]
598 fn draw_image_emits_save_concat_xobject_restore() {
599 let mut canvas = Canvas::new();
600 let first = canvas.add_image(ImageData::gray8(1, 1, vec![0]).unwrap());
601 let second = canvas.add_image(ImageData::gray8(1, 1, vec![255]).unwrap());
602 canvas.draw_image(first, 10.0, 20.0, 100.0, 50.0);
603 canvas.draw_image(second, 0.0, 0.0, 5.0, 5.0);
604 assert_eq!(
605 canvas.ops(),
606 [
607 Op::Save,
608 Op::Concat(Matrix {
609 a: 100.0,
610 b: 0.0,
611 c: 0.0,
612 d: 50.0,
613 e: 10.0,
614 f: 20.0,
615 }),
616 Op::XObject(name("Im1")),
617 Op::Restore,
618 Op::Save,
619 Op::Concat(Matrix {
620 a: 5.0,
621 b: 0.0,
622 c: 0.0,
623 d: 5.0,
624 e: 0.0,
625 f: 0.0,
626 }),
627 Op::XObject(name("Im2")),
628 Op::Restore,
629 ]
630 );
631 assert_eq!(canvas.into_parts().images.len(), 2);
632 }
633}