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 fill_opacity: f64,
19 stroke_opacity: f64,
20}
21
22impl Default for GraphicsContext {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl GraphicsContext {
29 pub fn new() -> Self {
30 Self {
31 operations: String::new(),
32 current_color: Color::black(),
33 stroke_color: Color::black(),
34 line_width: 1.0,
35 fill_opacity: 1.0,
36 stroke_opacity: 1.0,
37 }
38 }
39
40 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
41 writeln!(&mut self.operations, "{x:.2} {y:.2} m").unwrap();
42 self
43 }
44
45 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
46 writeln!(&mut self.operations, "{x:.2} {y:.2} l").unwrap();
47 self
48 }
49
50 pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
51 writeln!(
52 &mut self.operations,
53 "{x1:.2} {y1:.2} {x2:.2} {y2:.2} {x3:.2} {y3:.2} c"
54 )
55 .unwrap();
56 self
57 }
58
59 pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
60 writeln!(
61 &mut self.operations,
62 "{x:.2} {y:.2} {width:.2} {height:.2} re"
63 )
64 .unwrap();
65 self
66 }
67
68 pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
69 let k = 0.552284749831;
70 let r = radius;
71
72 self.move_to(cx + r, cy);
73 self.curve_to(cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r);
74 self.curve_to(cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy);
75 self.curve_to(cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r);
76 self.curve_to(cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy);
77 self.close_path()
78 }
79
80 pub fn close_path(&mut self) -> &mut Self {
81 self.operations.push_str("h\n");
82 self
83 }
84
85 pub fn stroke(&mut self) -> &mut Self {
86 self.apply_stroke_color();
87 self.operations.push_str("S\n");
88 self
89 }
90
91 pub fn fill(&mut self) -> &mut Self {
92 self.apply_fill_color();
93 self.operations.push_str("f\n");
94 self
95 }
96
97 pub fn fill_stroke(&mut self) -> &mut Self {
98 self.apply_fill_color();
99 self.apply_stroke_color();
100 self.operations.push_str("B\n");
101 self
102 }
103
104 pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
105 self.stroke_color = color;
106 self
107 }
108
109 pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
110 self.current_color = color;
111 self
112 }
113
114 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
115 self.line_width = width;
116 writeln!(&mut self.operations, "{width:.2} w").unwrap();
117 self
118 }
119
120 pub fn set_line_cap(&mut self, cap: LineCap) -> &mut Self {
121 writeln!(&mut self.operations, "{} J", cap as u8).unwrap();
122 self
123 }
124
125 pub fn set_line_join(&mut self, join: LineJoin) -> &mut Self {
126 writeln!(&mut self.operations, "{} j", join as u8).unwrap();
127 self
128 }
129
130 pub fn set_opacity(&mut self, opacity: f64) -> &mut Self {
132 let opacity = opacity.clamp(0.0, 1.0);
133 self.fill_opacity = opacity;
134 self.stroke_opacity = opacity;
135 self
136 }
137
138 pub fn set_fill_opacity(&mut self, opacity: f64) -> &mut Self {
140 self.fill_opacity = opacity.clamp(0.0, 1.0);
141 self
142 }
143
144 pub fn set_stroke_opacity(&mut self, opacity: f64) -> &mut Self {
146 self.stroke_opacity = opacity.clamp(0.0, 1.0);
147 self
148 }
149
150 pub fn save_state(&mut self) -> &mut Self {
151 self.operations.push_str("q\n");
152 self
153 }
154
155 pub fn restore_state(&mut self) -> &mut Self {
156 self.operations.push_str("Q\n");
157 self
158 }
159
160 pub fn translate(&mut self, tx: f64, ty: f64) -> &mut Self {
161 writeln!(&mut self.operations, "1 0 0 1 {tx:.2} {ty:.2} cm").unwrap();
162 self
163 }
164
165 pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
166 writeln!(&mut self.operations, "{sx:.2} 0 0 {sy:.2} 0 0 cm").unwrap();
167 self
168 }
169
170 pub fn rotate(&mut self, angle: f64) -> &mut Self {
171 let cos = angle.cos();
172 let sin = angle.sin();
173 writeln!(
174 &mut self.operations,
175 "{:.6} {:.6} {:.6} {:.6} 0 0 cm",
176 cos, sin, -sin, cos
177 )
178 .unwrap();
179 self
180 }
181
182 pub fn transform(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> &mut Self {
183 writeln!(
184 &mut self.operations,
185 "{a:.2} {b:.2} {c:.2} {d:.2} {e:.2} {f:.2} cm"
186 )
187 .unwrap();
188 self
189 }
190
191 pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
192 self.rect(x, y, width, height)
193 }
194
195 pub fn draw_image(
196 &mut self,
197 image_name: &str,
198 x: f64,
199 y: f64,
200 width: f64,
201 height: f64,
202 ) -> &mut Self {
203 self.save_state();
205
206 writeln!(
209 &mut self.operations,
210 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
211 )
212 .unwrap();
213
214 writeln!(&mut self.operations, "/{image_name} Do").unwrap();
216
217 self.restore_state();
219
220 self
221 }
222
223 fn apply_stroke_color(&mut self) {
224 match self.stroke_color {
225 Color::Rgb(r, g, b) => {
226 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} RG").unwrap();
227 }
228 Color::Gray(g) => {
229 writeln!(&mut self.operations, "{g:.3} G").unwrap();
230 }
231 Color::Cmyk(c, m, y, k) => {
232 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} K").unwrap();
233 }
234 }
235 }
236
237 fn apply_fill_color(&mut self) {
238 match self.current_color {
239 Color::Rgb(r, g, b) => {
240 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} rg").unwrap();
241 }
242 Color::Gray(g) => {
243 writeln!(&mut self.operations, "{g:.3} g").unwrap();
244 }
245 Color::Cmyk(c, m, y, k) => {
246 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} k").unwrap();
247 }
248 }
249 }
250
251 pub(crate) fn generate_operations(&self) -> Result<Vec<u8>> {
252 Ok(self.operations.as_bytes().to_vec())
253 }
254
255 pub fn uses_transparency(&self) -> bool {
257 self.fill_opacity < 1.0 || self.stroke_opacity < 1.0
258 }
259
260 pub fn generate_graphics_state_dict(&self) -> Option<String> {
262 if !self.uses_transparency() {
263 return None;
264 }
265
266 let mut dict = String::from("<< /Type /ExtGState");
267
268 if self.fill_opacity < 1.0 {
269 write!(&mut dict, " /ca {:.3}", self.fill_opacity).unwrap();
270 }
271
272 if self.stroke_opacity < 1.0 {
273 write!(&mut dict, " /CA {:.3}", self.stroke_opacity).unwrap();
274 }
275
276 dict.push_str(" >>");
277 Some(dict)
278 }
279
280 pub fn fill_color(&self) -> Color {
282 self.current_color
283 }
284
285 pub fn stroke_color(&self) -> Color {
287 self.stroke_color
288 }
289
290 pub fn line_width(&self) -> f64 {
292 self.line_width
293 }
294
295 pub fn fill_opacity(&self) -> f64 {
297 self.fill_opacity
298 }
299
300 pub fn stroke_opacity(&self) -> f64 {
302 self.stroke_opacity
303 }
304
305 pub fn operations(&self) -> &str {
307 &self.operations
308 }
309
310 pub fn clear(&mut self) {
312 self.operations.clear();
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_graphics_context_new() {
322 let ctx = GraphicsContext::new();
323 assert_eq!(ctx.fill_color(), Color::black());
324 assert_eq!(ctx.stroke_color(), Color::black());
325 assert_eq!(ctx.line_width(), 1.0);
326 assert_eq!(ctx.fill_opacity(), 1.0);
327 assert_eq!(ctx.stroke_opacity(), 1.0);
328 assert!(ctx.operations().is_empty());
329 }
330
331 #[test]
332 fn test_graphics_context_default() {
333 let ctx = GraphicsContext::default();
334 assert_eq!(ctx.fill_color(), Color::black());
335 assert_eq!(ctx.stroke_color(), Color::black());
336 assert_eq!(ctx.line_width(), 1.0);
337 }
338
339 #[test]
340 fn test_move_to() {
341 let mut ctx = GraphicsContext::new();
342 ctx.move_to(10.0, 20.0);
343 assert!(ctx.operations().contains("10.00 20.00 m\n"));
344 }
345
346 #[test]
347 fn test_line_to() {
348 let mut ctx = GraphicsContext::new();
349 ctx.line_to(30.0, 40.0);
350 assert!(ctx.operations().contains("30.00 40.00 l\n"));
351 }
352
353 #[test]
354 fn test_curve_to() {
355 let mut ctx = GraphicsContext::new();
356 ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
357 assert!(ctx
358 .operations()
359 .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
360 }
361
362 #[test]
363 fn test_rect() {
364 let mut ctx = GraphicsContext::new();
365 ctx.rect(10.0, 20.0, 100.0, 50.0);
366 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
367 }
368
369 #[test]
370 fn test_rectangle_alias() {
371 let mut ctx = GraphicsContext::new();
372 ctx.rectangle(10.0, 20.0, 100.0, 50.0);
373 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
374 }
375
376 #[test]
377 fn test_circle() {
378 let mut ctx = GraphicsContext::new();
379 ctx.circle(50.0, 50.0, 25.0);
380
381 let ops = ctx.operations();
382 assert!(ops.contains("75.00 50.00 m\n"));
384 assert!(ops.contains(" c\n"));
386 assert!(ops.contains("h\n"));
388 }
389
390 #[test]
391 fn test_close_path() {
392 let mut ctx = GraphicsContext::new();
393 ctx.close_path();
394 assert!(ctx.operations().contains("h\n"));
395 }
396
397 #[test]
398 fn test_stroke() {
399 let mut ctx = GraphicsContext::new();
400 ctx.set_stroke_color(Color::red());
401 ctx.rect(0.0, 0.0, 10.0, 10.0);
402 ctx.stroke();
403
404 let ops = ctx.operations();
405 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
406 assert!(ops.contains("S\n"));
407 }
408
409 #[test]
410 fn test_fill() {
411 let mut ctx = GraphicsContext::new();
412 ctx.set_fill_color(Color::blue());
413 ctx.rect(0.0, 0.0, 10.0, 10.0);
414 ctx.fill();
415
416 let ops = ctx.operations();
417 assert!(ops.contains("0.000 0.000 1.000 rg\n"));
418 assert!(ops.contains("f\n"));
419 }
420
421 #[test]
422 fn test_fill_stroke() {
423 let mut ctx = GraphicsContext::new();
424 ctx.set_fill_color(Color::green());
425 ctx.set_stroke_color(Color::red());
426 ctx.rect(0.0, 0.0, 10.0, 10.0);
427 ctx.fill_stroke();
428
429 let ops = ctx.operations();
430 assert!(ops.contains("0.000 1.000 0.000 rg\n"));
431 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
432 assert!(ops.contains("B\n"));
433 }
434
435 #[test]
436 fn test_set_stroke_color() {
437 let mut ctx = GraphicsContext::new();
438 ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
439 assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
440 }
441
442 #[test]
443 fn test_set_fill_color() {
444 let mut ctx = GraphicsContext::new();
445 ctx.set_fill_color(Color::gray(0.5));
446 assert_eq!(ctx.fill_color(), Color::Gray(0.5));
447 }
448
449 #[test]
450 fn test_set_line_width() {
451 let mut ctx = GraphicsContext::new();
452 ctx.set_line_width(2.5);
453 assert_eq!(ctx.line_width(), 2.5);
454 assert!(ctx.operations().contains("2.50 w\n"));
455 }
456
457 #[test]
458 fn test_set_line_cap() {
459 let mut ctx = GraphicsContext::new();
460 ctx.set_line_cap(LineCap::Round);
461 assert!(ctx.operations().contains("1 J\n"));
462
463 ctx.set_line_cap(LineCap::Butt);
464 assert!(ctx.operations().contains("0 J\n"));
465
466 ctx.set_line_cap(LineCap::Square);
467 assert!(ctx.operations().contains("2 J\n"));
468 }
469
470 #[test]
471 fn test_set_line_join() {
472 let mut ctx = GraphicsContext::new();
473 ctx.set_line_join(LineJoin::Round);
474 assert!(ctx.operations().contains("1 j\n"));
475
476 ctx.set_line_join(LineJoin::Miter);
477 assert!(ctx.operations().contains("0 j\n"));
478
479 ctx.set_line_join(LineJoin::Bevel);
480 assert!(ctx.operations().contains("2 j\n"));
481 }
482
483 #[test]
484 fn test_save_restore_state() {
485 let mut ctx = GraphicsContext::new();
486 ctx.save_state();
487 assert!(ctx.operations().contains("q\n"));
488
489 ctx.restore_state();
490 assert!(ctx.operations().contains("Q\n"));
491 }
492
493 #[test]
494 fn test_translate() {
495 let mut ctx = GraphicsContext::new();
496 ctx.translate(50.0, 100.0);
497 assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
498 }
499
500 #[test]
501 fn test_scale() {
502 let mut ctx = GraphicsContext::new();
503 ctx.scale(2.0, 3.0);
504 assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
505 }
506
507 #[test]
508 fn test_rotate() {
509 let mut ctx = GraphicsContext::new();
510 let angle = std::f64::consts::PI / 4.0; ctx.rotate(angle);
512
513 let ops = ctx.operations();
514 assert!(ops.contains(" cm\n"));
515 assert!(ops.contains("0.707107")); }
518
519 #[test]
520 fn test_transform() {
521 let mut ctx = GraphicsContext::new();
522 ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
523 assert!(ctx
524 .operations()
525 .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
526 }
527
528 #[test]
529 fn test_draw_image() {
530 let mut ctx = GraphicsContext::new();
531 ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
532
533 let ops = ctx.operations();
534 assert!(ops.contains("q\n")); assert!(ops.contains("100.00 0 0 150.00 10.00 20.00 cm\n")); assert!(ops.contains("/Image1 Do\n")); assert!(ops.contains("Q\n")); }
539
540 #[test]
541 fn test_gray_color_operations() {
542 let mut ctx = GraphicsContext::new();
543 ctx.set_stroke_color(Color::gray(0.5));
544 ctx.set_fill_color(Color::gray(0.7));
545 ctx.stroke();
546 ctx.fill();
547
548 let ops = ctx.operations();
549 assert!(ops.contains("0.500 G\n")); assert!(ops.contains("0.700 g\n")); }
552
553 #[test]
554 fn test_cmyk_color_operations() {
555 let mut ctx = GraphicsContext::new();
556 ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
557 ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
558 ctx.stroke();
559 ctx.fill();
560
561 let ops = ctx.operations();
562 assert!(ops.contains("0.100 0.200 0.300 0.400 K\n")); assert!(ops.contains("0.500 0.600 0.700 0.800 k\n")); }
565
566 #[test]
567 fn test_method_chaining() {
568 let mut ctx = GraphicsContext::new();
569 ctx.move_to(0.0, 0.0)
570 .line_to(10.0, 0.0)
571 .line_to(10.0, 10.0)
572 .line_to(0.0, 10.0)
573 .close_path()
574 .set_fill_color(Color::red())
575 .fill();
576
577 let ops = ctx.operations();
578 assert!(ops.contains("0.00 0.00 m\n"));
579 assert!(ops.contains("10.00 0.00 l\n"));
580 assert!(ops.contains("10.00 10.00 l\n"));
581 assert!(ops.contains("0.00 10.00 l\n"));
582 assert!(ops.contains("h\n"));
583 assert!(ops.contains("f\n"));
584 }
585
586 #[test]
587 fn test_generate_operations() {
588 let mut ctx = GraphicsContext::new();
589 ctx.rect(0.0, 0.0, 10.0, 10.0);
590
591 let result = ctx.generate_operations();
592 assert!(result.is_ok());
593 let bytes = result.unwrap();
594 let ops_string = String::from_utf8(bytes).unwrap();
595 assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
596 }
597
598 #[test]
599 fn test_clear_operations() {
600 let mut ctx = GraphicsContext::new();
601 ctx.rect(0.0, 0.0, 10.0, 10.0);
602 assert!(!ctx.operations().is_empty());
603
604 ctx.clear();
605 assert!(ctx.operations().is_empty());
606 }
607
608 #[test]
609 fn test_complex_path() {
610 let mut ctx = GraphicsContext::new();
611 ctx.save_state()
612 .translate(100.0, 100.0)
613 .rotate(std::f64::consts::PI / 6.0)
614 .scale(2.0, 2.0)
615 .set_line_width(2.0)
616 .set_stroke_color(Color::blue())
617 .move_to(0.0, 0.0)
618 .line_to(50.0, 0.0)
619 .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
620 .close_path()
621 .stroke()
622 .restore_state();
623
624 let ops = ctx.operations();
625 assert!(ops.contains("q\n"));
626 assert!(ops.contains("cm\n"));
627 assert!(ops.contains("2.00 w\n"));
628 assert!(ops.contains("0.000 0.000 1.000 RG\n"));
629 assert!(ops.contains("S\n"));
630 assert!(ops.contains("Q\n"));
631 }
632
633 #[test]
634 fn test_graphics_context_clone() {
635 let mut ctx = GraphicsContext::new();
636 ctx.set_fill_color(Color::red());
637 ctx.set_stroke_color(Color::blue());
638 ctx.set_line_width(3.0);
639 ctx.set_opacity(0.5);
640 ctx.rect(0.0, 0.0, 10.0, 10.0);
641
642 let ctx_clone = ctx.clone();
643 assert_eq!(ctx_clone.fill_color(), Color::red());
644 assert_eq!(ctx_clone.stroke_color(), Color::blue());
645 assert_eq!(ctx_clone.line_width(), 3.0);
646 assert_eq!(ctx_clone.fill_opacity(), 0.5);
647 assert_eq!(ctx_clone.stroke_opacity(), 0.5);
648 assert_eq!(ctx_clone.operations(), ctx.operations());
649 }
650
651 #[test]
652 fn test_set_opacity() {
653 let mut ctx = GraphicsContext::new();
654
655 ctx.set_opacity(0.5);
657 assert_eq!(ctx.fill_opacity(), 0.5);
658 assert_eq!(ctx.stroke_opacity(), 0.5);
659
660 ctx.set_opacity(1.5);
662 assert_eq!(ctx.fill_opacity(), 1.0);
663 assert_eq!(ctx.stroke_opacity(), 1.0);
664
665 ctx.set_opacity(-0.5);
666 assert_eq!(ctx.fill_opacity(), 0.0);
667 assert_eq!(ctx.stroke_opacity(), 0.0);
668 }
669
670 #[test]
671 fn test_set_fill_opacity() {
672 let mut ctx = GraphicsContext::new();
673
674 ctx.set_fill_opacity(0.3);
675 assert_eq!(ctx.fill_opacity(), 0.3);
676 assert_eq!(ctx.stroke_opacity(), 1.0); ctx.set_fill_opacity(2.0);
680 assert_eq!(ctx.fill_opacity(), 1.0);
681 }
682
683 #[test]
684 fn test_set_stroke_opacity() {
685 let mut ctx = GraphicsContext::new();
686
687 ctx.set_stroke_opacity(0.7);
688 assert_eq!(ctx.stroke_opacity(), 0.7);
689 assert_eq!(ctx.fill_opacity(), 1.0); ctx.set_stroke_opacity(-1.0);
693 assert_eq!(ctx.stroke_opacity(), 0.0);
694 }
695
696 #[test]
697 fn test_uses_transparency() {
698 let mut ctx = GraphicsContext::new();
699
700 assert!(!ctx.uses_transparency());
702
703 ctx.set_fill_opacity(0.5);
705 assert!(ctx.uses_transparency());
706
707 ctx.set_fill_opacity(1.0);
709 assert!(!ctx.uses_transparency());
710 ctx.set_stroke_opacity(0.8);
711 assert!(ctx.uses_transparency());
712
713 ctx.set_fill_opacity(0.5);
715 assert!(ctx.uses_transparency());
716 }
717
718 #[test]
719 fn test_generate_graphics_state_dict() {
720 let mut ctx = GraphicsContext::new();
721
722 assert_eq!(ctx.generate_graphics_state_dict(), None);
724
725 ctx.set_fill_opacity(0.5);
727 let dict = ctx.generate_graphics_state_dict().unwrap();
728 assert!(dict.contains("/Type /ExtGState"));
729 assert!(dict.contains("/ca 0.500"));
730 assert!(!dict.contains("/CA"));
731
732 ctx.set_fill_opacity(1.0);
734 ctx.set_stroke_opacity(0.75);
735 let dict = ctx.generate_graphics_state_dict().unwrap();
736 assert!(dict.contains("/Type /ExtGState"));
737 assert!(dict.contains("/CA 0.750"));
738 assert!(!dict.contains("/ca"));
739
740 ctx.set_fill_opacity(0.25);
742 let dict = ctx.generate_graphics_state_dict().unwrap();
743 assert!(dict.contains("/Type /ExtGState"));
744 assert!(dict.contains("/ca 0.250"));
745 assert!(dict.contains("/CA 0.750"));
746 }
747
748 #[test]
749 fn test_opacity_with_graphics_operations() {
750 let mut ctx = GraphicsContext::new();
751
752 ctx.set_fill_color(Color::red())
753 .set_opacity(0.5)
754 .rect(10.0, 10.0, 100.0, 100.0)
755 .fill();
756
757 assert_eq!(ctx.fill_opacity(), 0.5);
758 assert_eq!(ctx.stroke_opacity(), 0.5);
759
760 let ops = ctx.operations();
761 assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
762 assert!(ops.contains("1.000 0.000 0.000 rg")); assert!(ops.contains("f")); }
765}