1pub mod calibrated_color;
2pub mod clipping;
3mod color;
4mod color_profiles;
5pub mod devicen_color;
6pub mod extraction;
7pub mod form_xobject;
8mod indexed_color;
9pub mod lab_color;
10mod path;
11mod patterns;
12mod pdf_image;
13mod png_decoder;
14pub mod separation_color;
15mod shadings;
16pub mod soft_mask;
17pub mod state;
18pub mod transparency;
19
20pub use calibrated_color::{CalGrayColorSpace, CalRgbColorSpace, CalibratedColor};
21pub use clipping::{ClippingPath, ClippingRegion};
22pub use color::Color;
23pub use color_profiles::{IccColorSpace, IccProfile, IccProfileManager, StandardIccProfile};
24pub use devicen_color::{
25 AlternateColorSpace as DeviceNAlternateColorSpace, ColorantDefinition, ColorantType,
26 DeviceNAttributes, DeviceNColorSpace, LinearTransform, SampledFunction, TintTransformFunction,
27};
28pub use form_xobject::{
29 FormTemplates, FormXObject, FormXObjectBuilder, FormXObjectManager,
30 TransparencyGroup as FormTransparencyGroup,
31};
32pub use indexed_color::{BaseColorSpace, ColorLookupTable, IndexedColorManager, IndexedColorSpace};
33pub use lab_color::{LabColor, LabColorSpace};
34pub use path::{LineCap, LineJoin, PathBuilder, PathCommand, WindingRule};
35pub use patterns::{
36 PaintType, PatternGraphicsContext, PatternManager, PatternMatrix, PatternType, TilingPattern,
37 TilingType,
38};
39pub use pdf_image::{ColorSpace, Image, ImageFormat, MaskType};
40pub use separation_color::{
41 AlternateColorSpace, SeparationColor, SeparationColorSpace, SpotColors, TintTransform,
42};
43pub use shadings::{
44 AxialShading, ColorStop, FunctionBasedShading, Point, RadialShading, ShadingDefinition,
45 ShadingManager, ShadingPattern, ShadingType,
46};
47pub use soft_mask::{SoftMask, SoftMaskState, SoftMaskType};
48pub use state::{
49 BlendMode, ExtGState, ExtGStateFont, ExtGStateManager, Halftone, LineDashPattern,
50 RenderingIntent, TransferFunction,
51};
52pub use transparency::TransparencyGroup;
53use transparency::TransparencyGroupState;
54
55use crate::error::Result;
56use crate::text::{ColumnContent, ColumnLayout, Font, FontManager, ListElement, Table};
57use std::collections::{HashMap, HashSet};
58use std::fmt::Write;
59use std::sync::Arc;
60
61#[derive(Clone)]
62pub struct GraphicsContext {
63 operations: String,
64 current_color: Color,
65 stroke_color: Color,
66 line_width: f64,
67 fill_opacity: f64,
68 stroke_opacity: f64,
69 extgstate_manager: ExtGStateManager,
71 pending_extgstate: Option<ExtGState>,
72 current_dash_pattern: Option<LineDashPattern>,
73 current_miter_limit: f64,
74 current_line_cap: LineCap,
75 current_line_join: LineJoin,
76 current_rendering_intent: RenderingIntent,
77 current_flatness: f64,
78 current_smoothness: f64,
79 clipping_region: ClippingRegion,
81 font_manager: Option<Arc<FontManager>>,
83 state_stack: Vec<(Color, Color)>,
85 current_font_name: Option<String>,
86 current_font_size: f64,
87 used_characters: HashSet<char>,
89 glyph_mapping: Option<HashMap<u32, u16>>,
91 transparency_stack: Vec<TransparencyGroupState>,
93}
94
95impl Default for GraphicsContext {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl GraphicsContext {
102 pub fn new() -> Self {
103 Self {
104 operations: String::new(),
105 current_color: Color::black(),
106 stroke_color: Color::black(),
107 line_width: 1.0,
108 fill_opacity: 1.0,
109 stroke_opacity: 1.0,
110 extgstate_manager: ExtGStateManager::new(),
112 pending_extgstate: None,
113 current_dash_pattern: None,
114 current_miter_limit: 10.0,
115 current_line_cap: LineCap::Butt,
116 current_line_join: LineJoin::Miter,
117 current_rendering_intent: RenderingIntent::RelativeColorimetric,
118 current_flatness: 1.0,
119 current_smoothness: 0.0,
120 clipping_region: ClippingRegion::new(),
122 font_manager: None,
124 state_stack: Vec::new(),
125 current_font_name: None,
126 current_font_size: 12.0,
127 used_characters: HashSet::new(),
128 glyph_mapping: None,
129 transparency_stack: Vec::new(),
130 }
131 }
132
133 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
134 writeln!(&mut self.operations, "{x:.2} {y:.2} m")
135 .expect("Writing to string should never fail");
136 self
137 }
138
139 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
140 writeln!(&mut self.operations, "{x:.2} {y:.2} l")
141 .expect("Writing to string should never fail");
142 self
143 }
144
145 pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
146 writeln!(
147 &mut self.operations,
148 "{x1:.2} {y1:.2} {x2:.2} {y2:.2} {x3:.2} {y3:.2} c"
149 )
150 .expect("Writing to string should never fail");
151 self
152 }
153
154 pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
155 writeln!(
156 &mut self.operations,
157 "{x:.2} {y:.2} {width:.2} {height:.2} re"
158 )
159 .expect("Writing to string should never fail");
160 self
161 }
162
163 pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
164 let k = 0.552284749831;
165 let r = radius;
166
167 self.move_to(cx + r, cy);
168 self.curve_to(cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r);
169 self.curve_to(cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy);
170 self.curve_to(cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r);
171 self.curve_to(cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy);
172 self.close_path()
173 }
174
175 pub fn close_path(&mut self) -> &mut Self {
176 self.operations.push_str("h\n");
177 self
178 }
179
180 pub fn stroke(&mut self) -> &mut Self {
181 self.apply_pending_extgstate().unwrap_or_default();
182 self.apply_stroke_color();
183 self.operations.push_str("S\n");
184 self
185 }
186
187 pub fn fill(&mut self) -> &mut Self {
188 self.apply_pending_extgstate().unwrap_or_default();
189 self.apply_fill_color();
190 self.operations.push_str("f\n");
191 self
192 }
193
194 pub fn fill_stroke(&mut self) -> &mut Self {
195 self.apply_pending_extgstate().unwrap_or_default();
196 self.apply_fill_color();
197 self.apply_stroke_color();
198 self.operations.push_str("B\n");
199 self
200 }
201
202 pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
203 self.stroke_color = color;
204 self
205 }
206
207 pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
208 self.current_color = color;
209 self
210 }
211
212 pub fn set_fill_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
214 let cs_name = match &color {
216 CalibratedColor::Gray(_, _) => "CalGray1",
217 CalibratedColor::Rgb(_, _) => "CalRGB1",
218 };
219
220 writeln!(&mut self.operations, "/{} cs", cs_name)
222 .expect("Writing to string should never fail");
223
224 let values = color.values();
226 for value in &values {
227 write!(&mut self.operations, "{:.4} ", value)
228 .expect("Writing to string should never fail");
229 }
230 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
231
232 self
233 }
234
235 pub fn set_stroke_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
237 let cs_name = match &color {
239 CalibratedColor::Gray(_, _) => "CalGray1",
240 CalibratedColor::Rgb(_, _) => "CalRGB1",
241 };
242
243 writeln!(&mut self.operations, "/{} CS", cs_name)
245 .expect("Writing to string should never fail");
246
247 let values = color.values();
249 for value in &values {
250 write!(&mut self.operations, "{:.4} ", value)
251 .expect("Writing to string should never fail");
252 }
253 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
254
255 self
256 }
257
258 pub fn set_fill_color_lab(&mut self, color: LabColor) -> &mut Self {
260 writeln!(&mut self.operations, "/Lab1 cs").expect("Writing to string should never fail");
262
263 let values = color.values();
265 for value in &values {
266 write!(&mut self.operations, "{:.4} ", value)
267 .expect("Writing to string should never fail");
268 }
269 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
270
271 self
272 }
273
274 pub fn set_stroke_color_lab(&mut self, color: LabColor) -> &mut Self {
276 writeln!(&mut self.operations, "/Lab1 CS").expect("Writing to string should never fail");
278
279 let values = color.values();
281 for value in &values {
282 write!(&mut self.operations, "{:.4} ", value)
283 .expect("Writing to string should never fail");
284 }
285 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
286
287 self
288 }
289
290 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
291 self.line_width = width;
292 writeln!(&mut self.operations, "{width:.2} w")
293 .expect("Writing to string should never fail");
294 self
295 }
296
297 pub fn set_line_cap(&mut self, cap: LineCap) -> &mut Self {
298 self.current_line_cap = cap;
299 writeln!(&mut self.operations, "{} J", cap as u8)
300 .expect("Writing to string should never fail");
301 self
302 }
303
304 pub fn set_line_join(&mut self, join: LineJoin) -> &mut Self {
305 self.current_line_join = join;
306 writeln!(&mut self.operations, "{} j", join as u8)
307 .expect("Writing to string should never fail");
308 self
309 }
310
311 pub fn set_opacity(&mut self, opacity: f64) -> &mut Self {
313 let opacity = opacity.clamp(0.0, 1.0);
314 self.fill_opacity = opacity;
315 self.stroke_opacity = opacity;
316
317 if opacity < 1.0 {
319 let mut state = ExtGState::new();
320 state.alpha_fill = Some(opacity);
321 state.alpha_stroke = Some(opacity);
322 self.pending_extgstate = Some(state);
323 }
324
325 self
326 }
327
328 pub fn set_fill_opacity(&mut self, opacity: f64) -> &mut Self {
330 self.fill_opacity = opacity.clamp(0.0, 1.0);
331
332 if opacity < 1.0 {
334 if let Some(ref mut state) = self.pending_extgstate {
335 state.alpha_fill = Some(opacity);
336 } else {
337 let mut state = ExtGState::new();
338 state.alpha_fill = Some(opacity);
339 self.pending_extgstate = Some(state);
340 }
341 }
342
343 self
344 }
345
346 pub fn set_stroke_opacity(&mut self, opacity: f64) -> &mut Self {
348 self.stroke_opacity = opacity.clamp(0.0, 1.0);
349
350 if opacity < 1.0 {
352 if let Some(ref mut state) = self.pending_extgstate {
353 state.alpha_stroke = Some(opacity);
354 } else {
355 let mut state = ExtGState::new();
356 state.alpha_stroke = Some(opacity);
357 self.pending_extgstate = Some(state);
358 }
359 }
360
361 self
362 }
363
364 pub fn save_state(&mut self) -> &mut Self {
365 self.operations.push_str("q\n");
366 self.save_clipping_state();
367 self.state_stack
369 .push((self.current_color, self.stroke_color));
370 self
371 }
372
373 pub fn restore_state(&mut self) -> &mut Self {
374 self.operations.push_str("Q\n");
375 self.restore_clipping_state();
376 if let Some((fill, stroke)) = self.state_stack.pop() {
378 self.current_color = fill;
379 self.stroke_color = stroke;
380 }
381 self
382 }
383
384 pub fn begin_transparency_group(&mut self, group: TransparencyGroup) -> &mut Self {
387 self.save_state();
389
390 writeln!(&mut self.operations, "% Begin Transparency Group")
392 .expect("Writing to string should never fail");
393
394 let mut extgstate = ExtGState::new();
396 extgstate = extgstate.with_blend_mode(group.blend_mode.clone());
397 extgstate.alpha_fill = Some(group.opacity as f64);
398 extgstate.alpha_stroke = Some(group.opacity as f64);
399
400 self.pending_extgstate = Some(extgstate);
402 let _ = self.apply_pending_extgstate();
403
404 let mut group_state = TransparencyGroupState::new(group);
406 group_state.saved_state = self.operations.as_bytes().to_vec();
408 self.transparency_stack.push(group_state);
409
410 self
411 }
412
413 pub fn end_transparency_group(&mut self) -> &mut Self {
415 if let Some(_group_state) = self.transparency_stack.pop() {
416 writeln!(&mut self.operations, "% End Transparency Group")
418 .expect("Writing to string should never fail");
419
420 self.restore_state();
422 }
423 self
424 }
425
426 pub fn in_transparency_group(&self) -> bool {
428 !self.transparency_stack.is_empty()
429 }
430
431 pub fn current_transparency_group(&self) -> Option<&TransparencyGroup> {
433 self.transparency_stack.last().map(|state| &state.group)
434 }
435
436 pub fn translate(&mut self, tx: f64, ty: f64) -> &mut Self {
437 writeln!(&mut self.operations, "1 0 0 1 {tx:.2} {ty:.2} cm")
438 .expect("Writing to string should never fail");
439 self
440 }
441
442 pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
443 writeln!(&mut self.operations, "{sx:.2} 0 0 {sy:.2} 0 0 cm")
444 .expect("Writing to string should never fail");
445 self
446 }
447
448 pub fn rotate(&mut self, angle: f64) -> &mut Self {
449 let cos = angle.cos();
450 let sin = angle.sin();
451 writeln!(
452 &mut self.operations,
453 "{:.6} {:.6} {:.6} {:.6} 0 0 cm",
454 cos, sin, -sin, cos
455 )
456 .expect("Writing to string should never fail");
457 self
458 }
459
460 pub fn transform(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> &mut Self {
461 writeln!(
462 &mut self.operations,
463 "{a:.2} {b:.2} {c:.2} {d:.2} {e:.2} {f:.2} cm"
464 )
465 .expect("Writing to string should never fail");
466 self
467 }
468
469 pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
470 self.rect(x, y, width, height)
471 }
472
473 pub fn draw_image(
474 &mut self,
475 image_name: &str,
476 x: f64,
477 y: f64,
478 width: f64,
479 height: f64,
480 ) -> &mut Self {
481 self.save_state();
483
484 writeln!(
487 &mut self.operations,
488 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
489 )
490 .expect("Writing to string should never fail");
491
492 writeln!(&mut self.operations, "/{image_name} Do")
494 .expect("Writing to string should never fail");
495
496 self.restore_state();
498
499 self
500 }
501
502 pub fn draw_image_with_transparency(
505 &mut self,
506 image_name: &str,
507 x: f64,
508 y: f64,
509 width: f64,
510 height: f64,
511 mask_name: Option<&str>,
512 ) -> &mut Self {
513 self.save_state();
515
516 if let Some(mask) = mask_name {
518 let mut extgstate = ExtGState::new();
520 extgstate.set_soft_mask_name(mask.to_string());
521
522 let gs_name = self
524 .extgstate_manager
525 .add_state(extgstate)
526 .unwrap_or_else(|_| "GS1".to_string());
527 writeln!(&mut self.operations, "/{} gs", gs_name)
528 .expect("Writing to string should never fail");
529 }
530
531 writeln!(
533 &mut self.operations,
534 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
535 )
536 .expect("Writing to string should never fail");
537
538 writeln!(&mut self.operations, "/{image_name} Do")
540 .expect("Writing to string should never fail");
541
542 if mask_name.is_some() {
544 let mut reset_extgstate = ExtGState::new();
546 reset_extgstate.set_soft_mask_none();
547
548 let gs_name = self
549 .extgstate_manager
550 .add_state(reset_extgstate)
551 .unwrap_or_else(|_| "GS2".to_string());
552 writeln!(&mut self.operations, "/{} gs", gs_name)
553 .expect("Writing to string should never fail");
554 }
555
556 self.restore_state();
558
559 self
560 }
561
562 fn apply_stroke_color(&mut self) {
563 match self.stroke_color {
564 Color::Rgb(r, g, b) => {
565 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} RG")
566 .expect("Writing to string should never fail");
567 }
568 Color::Gray(g) => {
569 writeln!(&mut self.operations, "{g:.3} G")
570 .expect("Writing to string should never fail");
571 }
572 Color::Cmyk(c, m, y, k) => {
573 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} K")
574 .expect("Writing to string should never fail");
575 }
576 }
577 }
578
579 fn apply_fill_color(&mut self) {
580 match self.current_color {
581 Color::Rgb(r, g, b) => {
582 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} rg")
583 .expect("Writing to string should never fail");
584 }
585 Color::Gray(g) => {
586 writeln!(&mut self.operations, "{g:.3} g")
587 .expect("Writing to string should never fail");
588 }
589 Color::Cmyk(c, m, y, k) => {
590 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} k")
591 .expect("Writing to string should never fail");
592 }
593 }
594 }
595
596 pub(crate) fn generate_operations(&self) -> Result<Vec<u8>> {
597 Ok(self.operations.as_bytes().to_vec())
598 }
599
600 pub fn uses_transparency(&self) -> bool {
602 self.fill_opacity < 1.0 || self.stroke_opacity < 1.0
603 }
604
605 pub fn generate_graphics_state_dict(&self) -> Option<String> {
607 if !self.uses_transparency() {
608 return None;
609 }
610
611 let mut dict = String::from("<< /Type /ExtGState");
612
613 if self.fill_opacity < 1.0 {
614 write!(&mut dict, " /ca {:.3}", self.fill_opacity)
615 .expect("Writing to string should never fail");
616 }
617
618 if self.stroke_opacity < 1.0 {
619 write!(&mut dict, " /CA {:.3}", self.stroke_opacity)
620 .expect("Writing to string should never fail");
621 }
622
623 dict.push_str(" >>");
624 Some(dict)
625 }
626
627 pub fn fill_color(&self) -> Color {
629 self.current_color
630 }
631
632 pub fn stroke_color(&self) -> Color {
634 self.stroke_color
635 }
636
637 pub fn line_width(&self) -> f64 {
639 self.line_width
640 }
641
642 pub fn fill_opacity(&self) -> f64 {
644 self.fill_opacity
645 }
646
647 pub fn stroke_opacity(&self) -> f64 {
649 self.stroke_opacity
650 }
651
652 pub fn operations(&self) -> &str {
654 &self.operations
655 }
656
657 pub fn get_operations(&self) -> &str {
659 &self.operations
660 }
661
662 pub fn clear(&mut self) {
664 self.operations.clear();
665 }
666
667 pub fn begin_text(&mut self) -> &mut Self {
669 self.operations.push_str("BT\n");
670 self
671 }
672
673 pub fn end_text(&mut self) -> &mut Self {
675 self.operations.push_str("ET\n");
676 self
677 }
678
679 pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
681 writeln!(&mut self.operations, "/{} {} Tf", font.pdf_name(), size)
682 .expect("Writing to string should never fail");
683
684 match &font {
686 Font::Custom(name) => {
687 self.current_font_name = Some(name.clone());
688 self.current_font_size = size;
689 }
690 _ => {
691 self.current_font_name = Some(font.pdf_name());
692 self.current_font_size = size;
693 }
694 }
695
696 self
697 }
698
699 pub fn set_text_position(&mut self, x: f64, y: f64) -> &mut Self {
701 writeln!(&mut self.operations, "{x:.2} {y:.2} Td")
702 .expect("Writing to string should never fail");
703 self
704 }
705
706 pub fn show_text(&mut self, text: &str) -> Result<&mut Self> {
708 self.operations.push('(');
710 for ch in text.chars() {
711 match ch {
712 '(' => self.operations.push_str("\\("),
713 ')' => self.operations.push_str("\\)"),
714 '\\' => self.operations.push_str("\\\\"),
715 '\n' => self.operations.push_str("\\n"),
716 '\r' => self.operations.push_str("\\r"),
717 '\t' => self.operations.push_str("\\t"),
718 _ => self.operations.push(ch),
719 }
720 }
721 self.operations.push_str(") Tj\n");
722 Ok(self)
723 }
724
725 pub fn set_word_spacing(&mut self, spacing: f64) -> &mut Self {
727 writeln!(&mut self.operations, "{spacing:.2} Tw")
728 .expect("Writing to string should never fail");
729 self
730 }
731
732 pub fn set_character_spacing(&mut self, spacing: f64) -> &mut Self {
734 writeln!(&mut self.operations, "{spacing:.2} Tc")
735 .expect("Writing to string should never fail");
736 self
737 }
738
739 pub fn show_justified_text(&mut self, text: &str, target_width: f64) -> Result<&mut Self> {
741 let words: Vec<&str> = text.split_whitespace().collect();
743 if words.len() <= 1 {
744 return self.show_text(text);
746 }
747
748 let text_without_spaces = words.join("");
750 let natural_text_width = self.estimate_text_width_simple(&text_without_spaces);
751 let space_width = self.estimate_text_width_simple(" ");
752 let natural_width = natural_text_width + (space_width * (words.len() - 1) as f64);
753
754 let extra_space_needed = target_width - natural_width;
756 let word_gaps = (words.len() - 1) as f64;
757
758 if word_gaps > 0.0 && extra_space_needed > 0.0 {
759 let extra_word_spacing = extra_space_needed / word_gaps;
760
761 self.set_word_spacing(extra_word_spacing);
763
764 self.show_text(text)?;
766
767 self.set_word_spacing(0.0);
769 } else {
770 self.show_text(text)?;
772 }
773
774 Ok(self)
775 }
776
777 fn estimate_text_width_simple(&self, text: &str) -> f64 {
779 let font_size = self.current_font_size;
782 text.len() as f64 * font_size * 0.6 }
784
785 pub fn render_table(&mut self, table: &Table) -> Result<()> {
787 table.render(self)
788 }
789
790 pub fn render_list(&mut self, list: &ListElement) -> Result<()> {
792 match list {
793 ListElement::Ordered(ordered) => ordered.render(self),
794 ListElement::Unordered(unordered) => unordered.render(self),
795 }
796 }
797
798 pub fn render_column_layout(
800 &mut self,
801 layout: &ColumnLayout,
802 content: &ColumnContent,
803 x: f64,
804 y: f64,
805 height: f64,
806 ) -> Result<()> {
807 layout.render(self, content, x, y, height)
808 }
809
810 pub fn set_line_dash_pattern(&mut self, pattern: LineDashPattern) -> &mut Self {
814 self.current_dash_pattern = Some(pattern.clone());
815 writeln!(&mut self.operations, "{} d", pattern.to_pdf_string())
816 .expect("Writing to string should never fail");
817 self
818 }
819
820 pub fn set_line_solid(&mut self) -> &mut Self {
822 self.current_dash_pattern = None;
823 self.operations.push_str("[] 0 d\n");
824 self
825 }
826
827 pub fn set_miter_limit(&mut self, limit: f64) -> &mut Self {
829 self.current_miter_limit = limit.max(1.0);
830 writeln!(&mut self.operations, "{:.2} M", self.current_miter_limit)
831 .expect("Writing to string should never fail");
832 self
833 }
834
835 pub fn set_rendering_intent(&mut self, intent: RenderingIntent) -> &mut Self {
837 self.current_rendering_intent = intent;
838 writeln!(&mut self.operations, "/{} ri", intent.pdf_name())
839 .expect("Writing to string should never fail");
840 self
841 }
842
843 pub fn set_flatness(&mut self, flatness: f64) -> &mut Self {
845 self.current_flatness = flatness.clamp(0.0, 100.0);
846 writeln!(&mut self.operations, "{:.2} i", self.current_flatness)
847 .expect("Writing to string should never fail");
848 self
849 }
850
851 pub fn apply_extgstate(&mut self, state: ExtGState) -> Result<&mut Self> {
853 let state_name = self.extgstate_manager.add_state(state)?;
854 writeln!(&mut self.operations, "/{state_name} gs")
855 .expect("Writing to string should never fail");
856 Ok(self)
857 }
858
859 #[allow(dead_code)]
861 fn set_pending_extgstate(&mut self, state: ExtGState) {
862 self.pending_extgstate = Some(state);
863 }
864
865 fn apply_pending_extgstate(&mut self) -> Result<()> {
867 if let Some(state) = self.pending_extgstate.take() {
868 let state_name = self.extgstate_manager.add_state(state)?;
869 writeln!(&mut self.operations, "/{state_name} gs")
870 .expect("Writing to string should never fail");
871 }
872 Ok(())
873 }
874
875 pub fn with_extgstate<F>(&mut self, builder: F) -> Result<&mut Self>
877 where
878 F: FnOnce(ExtGState) -> ExtGState,
879 {
880 let state = builder(ExtGState::new());
881 self.apply_extgstate(state)
882 }
883
884 pub fn set_blend_mode(&mut self, mode: BlendMode) -> Result<&mut Self> {
886 let state = ExtGState::new().with_blend_mode(mode);
887 self.apply_extgstate(state)
888 }
889
890 pub fn set_alpha(&mut self, alpha: f64) -> Result<&mut Self> {
892 let state = ExtGState::new().with_alpha(alpha);
893 self.apply_extgstate(state)
894 }
895
896 pub fn set_alpha_stroke(&mut self, alpha: f64) -> Result<&mut Self> {
898 let state = ExtGState::new().with_alpha_stroke(alpha);
899 self.apply_extgstate(state)
900 }
901
902 pub fn set_alpha_fill(&mut self, alpha: f64) -> Result<&mut Self> {
904 let state = ExtGState::new().with_alpha_fill(alpha);
905 self.apply_extgstate(state)
906 }
907
908 pub fn set_overprint_stroke(&mut self, overprint: bool) -> Result<&mut Self> {
910 let state = ExtGState::new().with_overprint_stroke(overprint);
911 self.apply_extgstate(state)
912 }
913
914 pub fn set_overprint_fill(&mut self, overprint: bool) -> Result<&mut Self> {
916 let state = ExtGState::new().with_overprint_fill(overprint);
917 self.apply_extgstate(state)
918 }
919
920 pub fn set_stroke_adjustment(&mut self, adjustment: bool) -> Result<&mut Self> {
922 let state = ExtGState::new().with_stroke_adjustment(adjustment);
923 self.apply_extgstate(state)
924 }
925
926 pub fn set_smoothness(&mut self, smoothness: f64) -> Result<&mut Self> {
928 self.current_smoothness = smoothness.clamp(0.0, 1.0);
929 let state = ExtGState::new().with_smoothness(self.current_smoothness);
930 self.apply_extgstate(state)
931 }
932
933 pub fn line_dash_pattern(&self) -> Option<&LineDashPattern> {
937 self.current_dash_pattern.as_ref()
938 }
939
940 pub fn miter_limit(&self) -> f64 {
942 self.current_miter_limit
943 }
944
945 pub fn line_cap(&self) -> LineCap {
947 self.current_line_cap
948 }
949
950 pub fn line_join(&self) -> LineJoin {
952 self.current_line_join
953 }
954
955 pub fn rendering_intent(&self) -> RenderingIntent {
957 self.current_rendering_intent
958 }
959
960 pub fn flatness(&self) -> f64 {
962 self.current_flatness
963 }
964
965 pub fn smoothness(&self) -> f64 {
967 self.current_smoothness
968 }
969
970 pub fn extgstate_manager(&self) -> &ExtGStateManager {
972 &self.extgstate_manager
973 }
974
975 pub fn extgstate_manager_mut(&mut self) -> &mut ExtGStateManager {
977 &mut self.extgstate_manager
978 }
979
980 pub fn generate_extgstate_resources(&self) -> Result<String> {
982 self.extgstate_manager.to_resource_dictionary()
983 }
984
985 pub fn has_extgstates(&self) -> bool {
987 self.extgstate_manager.count() > 0
988 }
989
990 pub fn add_command(&mut self, command: &str) {
992 self.operations.push_str(command);
993 self.operations.push('\n');
994 }
995
996 pub fn clip(&mut self) -> &mut Self {
998 self.operations.push_str("W\n");
999 self
1000 }
1001
1002 pub fn clip_even_odd(&mut self) -> &mut Self {
1004 self.operations.push_str("W*\n");
1005 self
1006 }
1007
1008 pub fn clip_stroke(&mut self) -> &mut Self {
1010 self.apply_stroke_color();
1011 self.operations.push_str("W S\n");
1012 self
1013 }
1014
1015 pub fn set_clipping_path(&mut self, path: ClippingPath) -> Result<&mut Self> {
1017 let ops = path.to_pdf_operations()?;
1018 self.operations.push_str(&ops);
1019 self.clipping_region.set_clip(path);
1020 Ok(self)
1021 }
1022
1023 pub fn clear_clipping(&mut self) -> &mut Self {
1025 self.clipping_region.clear_clip();
1026 self
1027 }
1028
1029 fn save_clipping_state(&mut self) {
1031 self.clipping_region.save();
1032 }
1033
1034 fn restore_clipping_state(&mut self) {
1036 self.clipping_region.restore();
1037 }
1038
1039 pub fn clip_rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> Result<&mut Self> {
1041 let path = ClippingPath::rect(x, y, width, height);
1042 self.set_clipping_path(path)
1043 }
1044
1045 pub fn clip_circle(&mut self, cx: f64, cy: f64, radius: f64) -> Result<&mut Self> {
1047 let path = ClippingPath::circle(cx, cy, radius);
1048 self.set_clipping_path(path)
1049 }
1050
1051 pub fn clip_ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) -> Result<&mut Self> {
1053 let path = ClippingPath::ellipse(cx, cy, rx, ry);
1054 self.set_clipping_path(path)
1055 }
1056
1057 pub fn has_clipping(&self) -> bool {
1059 self.clipping_region.has_clip()
1060 }
1061
1062 pub fn clipping_path(&self) -> Option<&ClippingPath> {
1064 self.clipping_region.current()
1065 }
1066
1067 pub fn set_font_manager(&mut self, font_manager: Arc<FontManager>) -> &mut Self {
1069 self.font_manager = Some(font_manager);
1070 self
1071 }
1072
1073 pub fn set_custom_font(&mut self, font_name: &str, size: f64) -> &mut Self {
1075 self.current_font_name = Some(font_name.to_string());
1076 self.current_font_size = size;
1077
1078 if let Some(ref font_manager) = self.font_manager {
1080 if let Some(mapping) = font_manager.get_font_glyph_mapping(font_name) {
1081 self.glyph_mapping = Some(mapping);
1082 }
1083 }
1084
1085 self
1086 }
1087
1088 pub fn set_glyph_mapping(&mut self, mapping: HashMap<u32, u16>) -> &mut Self {
1090 self.glyph_mapping = Some(mapping);
1091 self
1092 }
1093
1094 pub fn draw_text(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1096 self.used_characters.extend(text.chars());
1098
1099 let using_custom_font = if let Some(ref font_name) = self.current_font_name {
1102 !matches!(
1104 font_name.as_str(),
1105 "Helvetica"
1106 | "Times"
1107 | "Courier"
1108 | "Symbol"
1109 | "ZapfDingbats"
1110 | "Helvetica-Bold"
1111 | "Helvetica-Oblique"
1112 | "Helvetica-BoldOblique"
1113 | "Times-Roman"
1114 | "Times-Bold"
1115 | "Times-Italic"
1116 | "Times-BoldItalic"
1117 | "Courier-Bold"
1118 | "Courier-Oblique"
1119 | "Courier-BoldOblique"
1120 )
1121 } else {
1122 false
1123 };
1124
1125 let needs_unicode = text.chars().any(|c| c as u32 > 255) || using_custom_font;
1127
1128 if needs_unicode {
1130 self.draw_with_unicode_encoding(text, x, y)
1131 } else {
1132 self.draw_with_simple_encoding(text, x, y)
1133 }
1134 }
1135
1136 fn draw_with_simple_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1138 let has_unicode = text.chars().any(|c| c as u32 > 255);
1140
1141 if has_unicode {
1142 tracing::debug!("Warning: Text contains Unicode characters but using Latin-1 font. Characters will be replaced with '?'");
1144 }
1145
1146 self.operations.push_str("BT\n");
1148
1149 self.apply_fill_color();
1151
1152 if let Some(font_name) = &self.current_font_name {
1154 writeln!(
1155 &mut self.operations,
1156 "/{} {} Tf",
1157 font_name, self.current_font_size
1158 )
1159 .expect("Writing to string should never fail");
1160 } else {
1161 writeln!(
1162 &mut self.operations,
1163 "/Helvetica {} Tf",
1164 self.current_font_size
1165 )
1166 .expect("Writing to string should never fail");
1167 }
1168
1169 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1171 .expect("Writing to string should never fail");
1172
1173 self.operations.push('(');
1176 for ch in text.chars() {
1177 let code = ch as u32;
1178 if code <= 127 {
1179 match ch {
1181 '(' => self.operations.push_str("\\("),
1182 ')' => self.operations.push_str("\\)"),
1183 '\\' => self.operations.push_str("\\\\"),
1184 '\n' => self.operations.push_str("\\n"),
1185 '\r' => self.operations.push_str("\\r"),
1186 '\t' => self.operations.push_str("\\t"),
1187 _ => self.operations.push(ch),
1188 }
1189 } else if code <= 255 {
1190 write!(&mut self.operations, "\\{:03o}", code)
1193 .expect("Writing to string should never fail");
1194 } else {
1195 self.operations.push('?');
1197 }
1198 }
1199 self.operations.push_str(") Tj\n");
1200
1201 self.operations.push_str("ET\n");
1203
1204 Ok(self)
1205 }
1206
1207 fn draw_with_unicode_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1209 self.operations.push_str("BT\n");
1211
1212 self.apply_fill_color();
1214
1215 if let Some(font_name) = &self.current_font_name {
1217 writeln!(
1219 &mut self.operations,
1220 "/{} {} Tf",
1221 font_name, self.current_font_size
1222 )
1223 .expect("Writing to string should never fail");
1224 } else {
1225 writeln!(
1226 &mut self.operations,
1227 "/Helvetica {} Tf",
1228 self.current_font_size
1229 )
1230 .expect("Writing to string should never fail");
1231 }
1232
1233 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1235 .expect("Writing to string should never fail");
1236
1237 self.operations.push('<');
1241
1242 for ch in text.chars() {
1243 let code = ch as u32;
1244
1245 if code <= 0xFFFF {
1248 write!(&mut self.operations, "{:04X}", code)
1250 .expect("Writing to string should never fail");
1251 } else {
1252 write!(&mut self.operations, "FFFD").expect("Writing to string should never fail");
1255 }
1257 }
1258 self.operations.push_str("> Tj\n");
1259
1260 self.operations.push_str("ET\n");
1262
1263 Ok(self)
1264 }
1265
1266 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1268 pub fn draw_text_hex(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1269 self.operations.push_str("BT\n");
1271
1272 self.apply_fill_color();
1274
1275 if let Some(font_name) = &self.current_font_name {
1277 writeln!(
1278 &mut self.operations,
1279 "/{} {} Tf",
1280 font_name, self.current_font_size
1281 )
1282 .expect("Writing to string should never fail");
1283 } else {
1284 writeln!(
1286 &mut self.operations,
1287 "/Helvetica {} Tf",
1288 self.current_font_size
1289 )
1290 .expect("Writing to string should never fail");
1291 }
1292
1293 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1295 .expect("Writing to string should never fail");
1296
1297 self.operations.push('<');
1301 for ch in text.chars() {
1302 if ch as u32 <= 255 {
1303 write!(&mut self.operations, "{:02X}", ch as u8)
1305 .expect("Writing to string should never fail");
1306 } else {
1307 write!(&mut self.operations, "3F").expect("Writing to string should never fail");
1310 }
1312 }
1313 self.operations.push_str("> Tj\n");
1314
1315 self.operations.push_str("ET\n");
1317
1318 Ok(self)
1319 }
1320
1321 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1323 pub fn draw_text_cid(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1324 use crate::fonts::needs_type0_font;
1325
1326 self.operations.push_str("BT\n");
1328
1329 self.apply_fill_color();
1331
1332 if let Some(font_name) = &self.current_font_name {
1334 writeln!(
1335 &mut self.operations,
1336 "/{} {} Tf",
1337 font_name, self.current_font_size
1338 )
1339 .expect("Writing to string should never fail");
1340 } else {
1341 writeln!(
1342 &mut self.operations,
1343 "/Helvetica {} Tf",
1344 self.current_font_size
1345 )
1346 .expect("Writing to string should never fail");
1347 }
1348
1349 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1351 .expect("Writing to string should never fail");
1352
1353 if needs_type0_font(text) {
1355 self.operations.push('<');
1357 for ch in text.chars() {
1358 let code = ch as u32;
1359
1360 if code <= 0xFFFF {
1362 write!(&mut self.operations, "{:04X}", code)
1364 .expect("Writing to string should never fail");
1365 } else if code <= 0x10FFFF {
1366 let code = code - 0x10000;
1368 let high = ((code >> 10) & 0x3FF) + 0xD800;
1369 let low = (code & 0x3FF) + 0xDC00;
1370 write!(&mut self.operations, "{:04X}{:04X}", high, low)
1371 .expect("Writing to string should never fail");
1372 } else {
1373 write!(&mut self.operations, "FFFD")
1375 .expect("Writing to string should never fail");
1376 }
1377 }
1378 self.operations.push_str("> Tj\n");
1379 } else {
1380 self.operations.push('<');
1382 for ch in text.chars() {
1383 if ch as u32 <= 255 {
1384 write!(&mut self.operations, "{:02X}", ch as u8)
1385 .expect("Writing to string should never fail");
1386 } else {
1387 write!(&mut self.operations, "3F")
1388 .expect("Writing to string should never fail");
1389 }
1390 }
1391 self.operations.push_str("> Tj\n");
1392 }
1393
1394 self.operations.push_str("ET\n");
1396 Ok(self)
1397 }
1398
1399 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1401 pub fn draw_text_unicode(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1402 self.operations.push_str("BT\n");
1404
1405 self.apply_fill_color();
1407
1408 if let Some(font_name) = &self.current_font_name {
1410 writeln!(
1411 &mut self.operations,
1412 "/{} {} Tf",
1413 font_name, self.current_font_size
1414 )
1415 .expect("Writing to string should never fail");
1416 } else {
1417 writeln!(
1419 &mut self.operations,
1420 "/Helvetica {} Tf",
1421 self.current_font_size
1422 )
1423 .expect("Writing to string should never fail");
1424 }
1425
1426 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1428 .expect("Writing to string should never fail");
1429
1430 self.operations.push('<');
1432 let mut utf16_buffer = [0u16; 2];
1433 for ch in text.chars() {
1434 let encoded = ch.encode_utf16(&mut utf16_buffer);
1435 for unit in encoded {
1436 write!(&mut self.operations, "{:04X}", unit)
1438 .expect("Writing to string should never fail");
1439 }
1440 }
1441 self.operations.push_str("> Tj\n");
1442
1443 self.operations.push_str("ET\n");
1445
1446 Ok(self)
1447 }
1448
1449 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1451 if self.used_characters.is_empty() {
1452 None
1453 } else {
1454 Some(self.used_characters.clone())
1455 }
1456 }
1457}
1458
1459#[cfg(test)]
1460mod tests {
1461 use super::*;
1462
1463 #[test]
1464 fn test_graphics_context_new() {
1465 let ctx = GraphicsContext::new();
1466 assert_eq!(ctx.fill_color(), Color::black());
1467 assert_eq!(ctx.stroke_color(), Color::black());
1468 assert_eq!(ctx.line_width(), 1.0);
1469 assert_eq!(ctx.fill_opacity(), 1.0);
1470 assert_eq!(ctx.stroke_opacity(), 1.0);
1471 assert!(ctx.operations().is_empty());
1472 }
1473
1474 #[test]
1475 fn test_graphics_context_default() {
1476 let ctx = GraphicsContext::default();
1477 assert_eq!(ctx.fill_color(), Color::black());
1478 assert_eq!(ctx.stroke_color(), Color::black());
1479 assert_eq!(ctx.line_width(), 1.0);
1480 }
1481
1482 #[test]
1483 fn test_move_to() {
1484 let mut ctx = GraphicsContext::new();
1485 ctx.move_to(10.0, 20.0);
1486 assert!(ctx.operations().contains("10.00 20.00 m\n"));
1487 }
1488
1489 #[test]
1490 fn test_line_to() {
1491 let mut ctx = GraphicsContext::new();
1492 ctx.line_to(30.0, 40.0);
1493 assert!(ctx.operations().contains("30.00 40.00 l\n"));
1494 }
1495
1496 #[test]
1497 fn test_curve_to() {
1498 let mut ctx = GraphicsContext::new();
1499 ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
1500 assert!(ctx
1501 .operations()
1502 .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
1503 }
1504
1505 #[test]
1506 fn test_rect() {
1507 let mut ctx = GraphicsContext::new();
1508 ctx.rect(10.0, 20.0, 100.0, 50.0);
1509 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1510 }
1511
1512 #[test]
1513 fn test_rectangle_alias() {
1514 let mut ctx = GraphicsContext::new();
1515 ctx.rectangle(10.0, 20.0, 100.0, 50.0);
1516 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1517 }
1518
1519 #[test]
1520 fn test_circle() {
1521 let mut ctx = GraphicsContext::new();
1522 ctx.circle(50.0, 50.0, 25.0);
1523
1524 let ops = ctx.operations();
1525 assert!(ops.contains("75.00 50.00 m\n"));
1527 assert!(ops.contains(" c\n"));
1529 assert!(ops.contains("h\n"));
1531 }
1532
1533 #[test]
1534 fn test_close_path() {
1535 let mut ctx = GraphicsContext::new();
1536 ctx.close_path();
1537 assert!(ctx.operations().contains("h\n"));
1538 }
1539
1540 #[test]
1541 fn test_stroke() {
1542 let mut ctx = GraphicsContext::new();
1543 ctx.set_stroke_color(Color::red());
1544 ctx.rect(0.0, 0.0, 10.0, 10.0);
1545 ctx.stroke();
1546
1547 let ops = ctx.operations();
1548 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1549 assert!(ops.contains("S\n"));
1550 }
1551
1552 #[test]
1553 fn test_fill() {
1554 let mut ctx = GraphicsContext::new();
1555 ctx.set_fill_color(Color::blue());
1556 ctx.rect(0.0, 0.0, 10.0, 10.0);
1557 ctx.fill();
1558
1559 let ops = ctx.operations();
1560 assert!(ops.contains("0.000 0.000 1.000 rg\n"));
1561 assert!(ops.contains("f\n"));
1562 }
1563
1564 #[test]
1565 fn test_fill_stroke() {
1566 let mut ctx = GraphicsContext::new();
1567 ctx.set_fill_color(Color::green());
1568 ctx.set_stroke_color(Color::red());
1569 ctx.rect(0.0, 0.0, 10.0, 10.0);
1570 ctx.fill_stroke();
1571
1572 let ops = ctx.operations();
1573 assert!(ops.contains("0.000 1.000 0.000 rg\n"));
1574 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1575 assert!(ops.contains("B\n"));
1576 }
1577
1578 #[test]
1579 fn test_set_stroke_color() {
1580 let mut ctx = GraphicsContext::new();
1581 ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
1582 assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
1583 }
1584
1585 #[test]
1586 fn test_set_fill_color() {
1587 let mut ctx = GraphicsContext::new();
1588 ctx.set_fill_color(Color::gray(0.5));
1589 assert_eq!(ctx.fill_color(), Color::Gray(0.5));
1590 }
1591
1592 #[test]
1593 fn test_set_line_width() {
1594 let mut ctx = GraphicsContext::new();
1595 ctx.set_line_width(2.5);
1596 assert_eq!(ctx.line_width(), 2.5);
1597 assert!(ctx.operations().contains("2.50 w\n"));
1598 }
1599
1600 #[test]
1601 fn test_set_line_cap() {
1602 let mut ctx = GraphicsContext::new();
1603 ctx.set_line_cap(LineCap::Round);
1604 assert!(ctx.operations().contains("1 J\n"));
1605
1606 ctx.set_line_cap(LineCap::Butt);
1607 assert!(ctx.operations().contains("0 J\n"));
1608
1609 ctx.set_line_cap(LineCap::Square);
1610 assert!(ctx.operations().contains("2 J\n"));
1611 }
1612
1613 #[test]
1614 fn test_set_line_join() {
1615 let mut ctx = GraphicsContext::new();
1616 ctx.set_line_join(LineJoin::Round);
1617 assert!(ctx.operations().contains("1 j\n"));
1618
1619 ctx.set_line_join(LineJoin::Miter);
1620 assert!(ctx.operations().contains("0 j\n"));
1621
1622 ctx.set_line_join(LineJoin::Bevel);
1623 assert!(ctx.operations().contains("2 j\n"));
1624 }
1625
1626 #[test]
1627 fn test_save_restore_state() {
1628 let mut ctx = GraphicsContext::new();
1629 ctx.save_state();
1630 assert!(ctx.operations().contains("q\n"));
1631
1632 ctx.restore_state();
1633 assert!(ctx.operations().contains("Q\n"));
1634 }
1635
1636 #[test]
1637 fn test_translate() {
1638 let mut ctx = GraphicsContext::new();
1639 ctx.translate(50.0, 100.0);
1640 assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
1641 }
1642
1643 #[test]
1644 fn test_scale() {
1645 let mut ctx = GraphicsContext::new();
1646 ctx.scale(2.0, 3.0);
1647 assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
1648 }
1649
1650 #[test]
1651 fn test_rotate() {
1652 let mut ctx = GraphicsContext::new();
1653 let angle = std::f64::consts::PI / 4.0; ctx.rotate(angle);
1655
1656 let ops = ctx.operations();
1657 assert!(ops.contains(" cm\n"));
1658 assert!(ops.contains("0.707107")); }
1661
1662 #[test]
1663 fn test_transform() {
1664 let mut ctx = GraphicsContext::new();
1665 ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
1666 assert!(ctx
1667 .operations()
1668 .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
1669 }
1670
1671 #[test]
1672 fn test_draw_image() {
1673 let mut ctx = GraphicsContext::new();
1674 ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
1675
1676 let ops = ctx.operations();
1677 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")); }
1682
1683 #[test]
1684 fn test_gray_color_operations() {
1685 let mut ctx = GraphicsContext::new();
1686 ctx.set_stroke_color(Color::gray(0.5));
1687 ctx.set_fill_color(Color::gray(0.7));
1688 ctx.stroke();
1689 ctx.fill();
1690
1691 let ops = ctx.operations();
1692 assert!(ops.contains("0.500 G\n")); assert!(ops.contains("0.700 g\n")); }
1695
1696 #[test]
1697 fn test_cmyk_color_operations() {
1698 let mut ctx = GraphicsContext::new();
1699 ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
1700 ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
1701 ctx.stroke();
1702 ctx.fill();
1703
1704 let ops = ctx.operations();
1705 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")); }
1708
1709 #[test]
1710 fn test_method_chaining() {
1711 let mut ctx = GraphicsContext::new();
1712 ctx.move_to(0.0, 0.0)
1713 .line_to(10.0, 0.0)
1714 .line_to(10.0, 10.0)
1715 .line_to(0.0, 10.0)
1716 .close_path()
1717 .set_fill_color(Color::red())
1718 .fill();
1719
1720 let ops = ctx.operations();
1721 assert!(ops.contains("0.00 0.00 m\n"));
1722 assert!(ops.contains("10.00 0.00 l\n"));
1723 assert!(ops.contains("10.00 10.00 l\n"));
1724 assert!(ops.contains("0.00 10.00 l\n"));
1725 assert!(ops.contains("h\n"));
1726 assert!(ops.contains("f\n"));
1727 }
1728
1729 #[test]
1730 fn test_generate_operations() {
1731 let mut ctx = GraphicsContext::new();
1732 ctx.rect(0.0, 0.0, 10.0, 10.0);
1733
1734 let result = ctx.generate_operations();
1735 assert!(result.is_ok());
1736 let bytes = result.expect("Writing to string should never fail");
1737 let ops_string = String::from_utf8(bytes).expect("Writing to string should never fail");
1738 assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
1739 }
1740
1741 #[test]
1742 fn test_clear_operations() {
1743 let mut ctx = GraphicsContext::new();
1744 ctx.rect(0.0, 0.0, 10.0, 10.0);
1745 assert!(!ctx.operations().is_empty());
1746
1747 ctx.clear();
1748 assert!(ctx.operations().is_empty());
1749 }
1750
1751 #[test]
1752 fn test_complex_path() {
1753 let mut ctx = GraphicsContext::new();
1754 ctx.save_state()
1755 .translate(100.0, 100.0)
1756 .rotate(std::f64::consts::PI / 6.0)
1757 .scale(2.0, 2.0)
1758 .set_line_width(2.0)
1759 .set_stroke_color(Color::blue())
1760 .move_to(0.0, 0.0)
1761 .line_to(50.0, 0.0)
1762 .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
1763 .close_path()
1764 .stroke()
1765 .restore_state();
1766
1767 let ops = ctx.operations();
1768 assert!(ops.contains("q\n"));
1769 assert!(ops.contains("cm\n"));
1770 assert!(ops.contains("2.00 w\n"));
1771 assert!(ops.contains("0.000 0.000 1.000 RG\n"));
1772 assert!(ops.contains("S\n"));
1773 assert!(ops.contains("Q\n"));
1774 }
1775
1776 #[test]
1777 fn test_graphics_context_clone() {
1778 let mut ctx = GraphicsContext::new();
1779 ctx.set_fill_color(Color::red());
1780 ctx.set_stroke_color(Color::blue());
1781 ctx.set_line_width(3.0);
1782 ctx.set_opacity(0.5);
1783 ctx.rect(0.0, 0.0, 10.0, 10.0);
1784
1785 let ctx_clone = ctx.clone();
1786 assert_eq!(ctx_clone.fill_color(), Color::red());
1787 assert_eq!(ctx_clone.stroke_color(), Color::blue());
1788 assert_eq!(ctx_clone.line_width(), 3.0);
1789 assert_eq!(ctx_clone.fill_opacity(), 0.5);
1790 assert_eq!(ctx_clone.stroke_opacity(), 0.5);
1791 assert_eq!(ctx_clone.operations(), ctx.operations());
1792 }
1793
1794 #[test]
1795 fn test_set_opacity() {
1796 let mut ctx = GraphicsContext::new();
1797
1798 ctx.set_opacity(0.5);
1800 assert_eq!(ctx.fill_opacity(), 0.5);
1801 assert_eq!(ctx.stroke_opacity(), 0.5);
1802
1803 ctx.set_opacity(1.5);
1805 assert_eq!(ctx.fill_opacity(), 1.0);
1806 assert_eq!(ctx.stroke_opacity(), 1.0);
1807
1808 ctx.set_opacity(-0.5);
1809 assert_eq!(ctx.fill_opacity(), 0.0);
1810 assert_eq!(ctx.stroke_opacity(), 0.0);
1811 }
1812
1813 #[test]
1814 fn test_set_fill_opacity() {
1815 let mut ctx = GraphicsContext::new();
1816
1817 ctx.set_fill_opacity(0.3);
1818 assert_eq!(ctx.fill_opacity(), 0.3);
1819 assert_eq!(ctx.stroke_opacity(), 1.0); ctx.set_fill_opacity(2.0);
1823 assert_eq!(ctx.fill_opacity(), 1.0);
1824 }
1825
1826 #[test]
1827 fn test_set_stroke_opacity() {
1828 let mut ctx = GraphicsContext::new();
1829
1830 ctx.set_stroke_opacity(0.7);
1831 assert_eq!(ctx.stroke_opacity(), 0.7);
1832 assert_eq!(ctx.fill_opacity(), 1.0); ctx.set_stroke_opacity(-1.0);
1836 assert_eq!(ctx.stroke_opacity(), 0.0);
1837 }
1838
1839 #[test]
1840 fn test_uses_transparency() {
1841 let mut ctx = GraphicsContext::new();
1842
1843 assert!(!ctx.uses_transparency());
1845
1846 ctx.set_fill_opacity(0.5);
1848 assert!(ctx.uses_transparency());
1849
1850 ctx.set_fill_opacity(1.0);
1852 assert!(!ctx.uses_transparency());
1853 ctx.set_stroke_opacity(0.8);
1854 assert!(ctx.uses_transparency());
1855
1856 ctx.set_fill_opacity(0.5);
1858 assert!(ctx.uses_transparency());
1859 }
1860
1861 #[test]
1862 fn test_generate_graphics_state_dict() {
1863 let mut ctx = GraphicsContext::new();
1864
1865 assert_eq!(ctx.generate_graphics_state_dict(), None);
1867
1868 ctx.set_fill_opacity(0.5);
1870 let dict = ctx
1871 .generate_graphics_state_dict()
1872 .expect("Writing to string should never fail");
1873 assert!(dict.contains("/Type /ExtGState"));
1874 assert!(dict.contains("/ca 0.500"));
1875 assert!(!dict.contains("/CA"));
1876
1877 ctx.set_fill_opacity(1.0);
1879 ctx.set_stroke_opacity(0.75);
1880 let dict = ctx
1881 .generate_graphics_state_dict()
1882 .expect("Writing to string should never fail");
1883 assert!(dict.contains("/Type /ExtGState"));
1884 assert!(dict.contains("/CA 0.750"));
1885 assert!(!dict.contains("/ca"));
1886
1887 ctx.set_fill_opacity(0.25);
1889 let dict = ctx
1890 .generate_graphics_state_dict()
1891 .expect("Writing to string should never fail");
1892 assert!(dict.contains("/Type /ExtGState"));
1893 assert!(dict.contains("/ca 0.250"));
1894 assert!(dict.contains("/CA 0.750"));
1895 }
1896
1897 #[test]
1898 fn test_opacity_with_graphics_operations() {
1899 let mut ctx = GraphicsContext::new();
1900
1901 ctx.set_fill_color(Color::red())
1902 .set_opacity(0.5)
1903 .rect(10.0, 10.0, 100.0, 100.0)
1904 .fill();
1905
1906 assert_eq!(ctx.fill_opacity(), 0.5);
1907 assert_eq!(ctx.stroke_opacity(), 0.5);
1908
1909 let ops = ctx.operations();
1910 assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
1911 assert!(ops.contains("1.000 0.000 0.000 rg")); assert!(ops.contains("f")); }
1914
1915 #[test]
1916 fn test_begin_end_text() {
1917 let mut ctx = GraphicsContext::new();
1918 ctx.begin_text();
1919 assert!(ctx.operations().contains("BT\n"));
1920
1921 ctx.end_text();
1922 assert!(ctx.operations().contains("ET\n"));
1923 }
1924
1925 #[test]
1926 fn test_set_font() {
1927 let mut ctx = GraphicsContext::new();
1928 ctx.set_font(Font::Helvetica, 12.0);
1929 assert!(ctx.operations().contains("/Helvetica 12 Tf\n"));
1930
1931 ctx.set_font(Font::TimesBold, 14.5);
1932 assert!(ctx.operations().contains("/Times-Bold 14.5 Tf\n"));
1933 }
1934
1935 #[test]
1936 fn test_set_text_position() {
1937 let mut ctx = GraphicsContext::new();
1938 ctx.set_text_position(100.0, 200.0);
1939 assert!(ctx.operations().contains("100.00 200.00 Td\n"));
1940 }
1941
1942 #[test]
1943 fn test_show_text() {
1944 let mut ctx = GraphicsContext::new();
1945 ctx.show_text("Hello World")
1946 .expect("Writing to string should never fail");
1947 assert!(ctx.operations().contains("(Hello World) Tj\n"));
1948 }
1949
1950 #[test]
1951 fn test_show_text_with_escaping() {
1952 let mut ctx = GraphicsContext::new();
1953 ctx.show_text("Test (parentheses)")
1954 .expect("Writing to string should never fail");
1955 assert!(ctx.operations().contains("(Test \\(parentheses\\)) Tj\n"));
1956
1957 ctx.clear();
1958 ctx.show_text("Back\\slash")
1959 .expect("Writing to string should never fail");
1960 assert!(ctx.operations().contains("(Back\\\\slash) Tj\n"));
1961
1962 ctx.clear();
1963 ctx.show_text("Line\nBreak")
1964 .expect("Writing to string should never fail");
1965 assert!(ctx.operations().contains("(Line\\nBreak) Tj\n"));
1966 }
1967
1968 #[test]
1969 fn test_text_operations_chaining() {
1970 let mut ctx = GraphicsContext::new();
1971 ctx.begin_text()
1972 .set_font(Font::Courier, 10.0)
1973 .set_text_position(50.0, 100.0)
1974 .show_text("Test")
1975 .unwrap()
1976 .end_text();
1977
1978 let ops = ctx.operations();
1979 assert!(ops.contains("BT\n"));
1980 assert!(ops.contains("/Courier 10 Tf\n"));
1981 assert!(ops.contains("50.00 100.00 Td\n"));
1982 assert!(ops.contains("(Test) Tj\n"));
1983 assert!(ops.contains("ET\n"));
1984 }
1985
1986 #[test]
1987 fn test_clip() {
1988 let mut ctx = GraphicsContext::new();
1989 ctx.clip();
1990 assert!(ctx.operations().contains("W\n"));
1991 }
1992
1993 #[test]
1994 fn test_clip_even_odd() {
1995 let mut ctx = GraphicsContext::new();
1996 ctx.clip_even_odd();
1997 assert!(ctx.operations().contains("W*\n"));
1998 }
1999
2000 #[test]
2001 fn test_clipping_with_path() {
2002 let mut ctx = GraphicsContext::new();
2003
2004 ctx.rect(10.0, 10.0, 100.0, 50.0).clip();
2006
2007 let ops = ctx.operations();
2008 assert!(ops.contains("10.00 10.00 100.00 50.00 re\n"));
2009 assert!(ops.contains("W\n"));
2010 }
2011
2012 #[test]
2013 fn test_clipping_even_odd_with_path() {
2014 let mut ctx = GraphicsContext::new();
2015
2016 ctx.move_to(0.0, 0.0)
2018 .line_to(100.0, 0.0)
2019 .line_to(100.0, 100.0)
2020 .line_to(0.0, 100.0)
2021 .close_path()
2022 .clip_even_odd();
2023
2024 let ops = ctx.operations();
2025 assert!(ops.contains("0.00 0.00 m\n"));
2026 assert!(ops.contains("100.00 0.00 l\n"));
2027 assert!(ops.contains("100.00 100.00 l\n"));
2028 assert!(ops.contains("0.00 100.00 l\n"));
2029 assert!(ops.contains("h\n"));
2030 assert!(ops.contains("W*\n"));
2031 }
2032
2033 #[test]
2034 fn test_clipping_chaining() {
2035 let mut ctx = GraphicsContext::new();
2036
2037 ctx.save_state()
2039 .rect(20.0, 20.0, 60.0, 60.0)
2040 .clip()
2041 .set_fill_color(Color::red())
2042 .rect(0.0, 0.0, 100.0, 100.0)
2043 .fill()
2044 .restore_state();
2045
2046 let ops = ctx.operations();
2047 assert!(ops.contains("q\n"));
2048 assert!(ops.contains("20.00 20.00 60.00 60.00 re\n"));
2049 assert!(ops.contains("W\n"));
2050 assert!(ops.contains("1.000 0.000 0.000 rg\n"));
2051 assert!(ops.contains("0.00 0.00 100.00 100.00 re\n"));
2052 assert!(ops.contains("f\n"));
2053 assert!(ops.contains("Q\n"));
2054 }
2055
2056 #[test]
2057 fn test_multiple_clipping_regions() {
2058 let mut ctx = GraphicsContext::new();
2059
2060 ctx.save_state()
2062 .rect(0.0, 0.0, 200.0, 200.0)
2063 .clip()
2064 .save_state()
2065 .circle(100.0, 100.0, 50.0)
2066 .clip_even_odd()
2067 .set_fill_color(Color::blue())
2068 .rect(50.0, 50.0, 100.0, 100.0)
2069 .fill()
2070 .restore_state()
2071 .restore_state();
2072
2073 let ops = ctx.operations();
2074 let q_count = ops.matches("q\n").count();
2076 let q_restore_count = ops.matches("Q\n").count();
2077 assert_eq!(q_count, 2);
2078 assert_eq!(q_restore_count, 2);
2079
2080 assert!(ops.contains("W\n"));
2082 assert!(ops.contains("W*\n"));
2083 }
2084
2085 #[test]
2088 fn test_move_to_and_line_to() {
2089 let mut ctx = GraphicsContext::new();
2090 ctx.move_to(100.0, 200.0).line_to(300.0, 400.0).stroke();
2091
2092 let ops = ctx
2093 .generate_operations()
2094 .expect("Writing to string should never fail");
2095 let ops_str = String::from_utf8_lossy(&ops);
2096 assert!(ops_str.contains("100.00 200.00 m"));
2097 assert!(ops_str.contains("300.00 400.00 l"));
2098 assert!(ops_str.contains("S"));
2099 }
2100
2101 #[test]
2102 fn test_bezier_curve() {
2103 let mut ctx = GraphicsContext::new();
2104 ctx.move_to(0.0, 0.0)
2105 .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0)
2106 .stroke();
2107
2108 let ops = ctx
2109 .generate_operations()
2110 .expect("Writing to string should never fail");
2111 let ops_str = String::from_utf8_lossy(&ops);
2112 assert!(ops_str.contains("0.00 0.00 m"));
2113 assert!(ops_str.contains("10.00 20.00 30.00 40.00 50.00 60.00 c"));
2114 assert!(ops_str.contains("S"));
2115 }
2116
2117 #[test]
2118 fn test_circle_path() {
2119 let mut ctx = GraphicsContext::new();
2120 ctx.circle(100.0, 100.0, 50.0).fill();
2121
2122 let ops = ctx
2123 .generate_operations()
2124 .expect("Writing to string should never fail");
2125 let ops_str = String::from_utf8_lossy(&ops);
2126 assert!(ops_str.contains(" c"));
2128 assert!(ops_str.contains("f"));
2129 }
2130
2131 #[test]
2132 fn test_path_closing() {
2133 let mut ctx = GraphicsContext::new();
2134 ctx.move_to(0.0, 0.0)
2135 .line_to(100.0, 0.0)
2136 .line_to(100.0, 100.0)
2137 .close_path()
2138 .stroke();
2139
2140 let ops = ctx
2141 .generate_operations()
2142 .expect("Writing to string should never fail");
2143 let ops_str = String::from_utf8_lossy(&ops);
2144 assert!(ops_str.contains("h")); assert!(ops_str.contains("S"));
2146 }
2147
2148 #[test]
2149 fn test_fill_and_stroke() {
2150 let mut ctx = GraphicsContext::new();
2151 ctx.rect(10.0, 10.0, 50.0, 50.0).fill_stroke();
2152
2153 let ops = ctx
2154 .generate_operations()
2155 .expect("Writing to string should never fail");
2156 let ops_str = String::from_utf8_lossy(&ops);
2157 assert!(ops_str.contains("10.00 10.00 50.00 50.00 re"));
2158 assert!(ops_str.contains("B")); }
2160
2161 #[test]
2162 fn test_color_settings() {
2163 let mut ctx = GraphicsContext::new();
2164 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2165 .set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
2166 .rect(10.0, 10.0, 50.0, 50.0)
2167 .fill_stroke(); assert_eq!(ctx.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2170 assert_eq!(ctx.stroke_color(), Color::rgb(0.0, 1.0, 0.0));
2171
2172 let ops = ctx
2173 .generate_operations()
2174 .expect("Writing to string should never fail");
2175 let ops_str = String::from_utf8_lossy(&ops);
2176 assert!(ops_str.contains("1.000 0.000 0.000 rg")); assert!(ops_str.contains("0.000 1.000 0.000 RG")); }
2179
2180 #[test]
2181 fn test_line_styles() {
2182 let mut ctx = GraphicsContext::new();
2183 ctx.set_line_width(2.5)
2184 .set_line_cap(LineCap::Round)
2185 .set_line_join(LineJoin::Bevel);
2186
2187 assert_eq!(ctx.line_width(), 2.5);
2188
2189 let ops = ctx
2190 .generate_operations()
2191 .expect("Writing to string should never fail");
2192 let ops_str = String::from_utf8_lossy(&ops);
2193 assert!(ops_str.contains("2.50 w")); assert!(ops_str.contains("1 J")); assert!(ops_str.contains("2 j")); }
2197
2198 #[test]
2199 fn test_opacity_settings() {
2200 let mut ctx = GraphicsContext::new();
2201 ctx.set_opacity(0.5);
2202
2203 assert_eq!(ctx.fill_opacity(), 0.5);
2204 assert_eq!(ctx.stroke_opacity(), 0.5);
2205 assert!(ctx.uses_transparency());
2206
2207 ctx.set_fill_opacity(0.7).set_stroke_opacity(0.3);
2208
2209 assert_eq!(ctx.fill_opacity(), 0.7);
2210 assert_eq!(ctx.stroke_opacity(), 0.3);
2211 }
2212
2213 #[test]
2214 fn test_state_save_restore() {
2215 let mut ctx = GraphicsContext::new();
2216 ctx.save_state()
2217 .set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2218 .restore_state();
2219
2220 let ops = ctx
2221 .generate_operations()
2222 .expect("Writing to string should never fail");
2223 let ops_str = String::from_utf8_lossy(&ops);
2224 assert!(ops_str.contains("q")); assert!(ops_str.contains("Q")); }
2227
2228 #[test]
2229 fn test_transformations() {
2230 let mut ctx = GraphicsContext::new();
2231 ctx.translate(100.0, 200.0).scale(2.0, 3.0).rotate(45.0);
2232
2233 let ops = ctx
2234 .generate_operations()
2235 .expect("Writing to string should never fail");
2236 let ops_str = String::from_utf8_lossy(&ops);
2237 assert!(ops_str.contains("1 0 0 1 100.00 200.00 cm")); assert!(ops_str.contains("2.00 0 0 3.00 0 0 cm")); assert!(ops_str.contains("cm")); }
2241
2242 #[test]
2243 fn test_custom_transform() {
2244 let mut ctx = GraphicsContext::new();
2245 ctx.transform(1.0, 0.5, 0.5, 1.0, 10.0, 20.0);
2246
2247 let ops = ctx
2248 .generate_operations()
2249 .expect("Writing to string should never fail");
2250 let ops_str = String::from_utf8_lossy(&ops);
2251 assert!(ops_str.contains("1.00 0.50 0.50 1.00 10.00 20.00 cm"));
2252 }
2253
2254 #[test]
2255 fn test_rectangle_path() {
2256 let mut ctx = GraphicsContext::new();
2257 ctx.rectangle(25.0, 25.0, 150.0, 100.0).stroke();
2258
2259 let ops = ctx
2260 .generate_operations()
2261 .expect("Writing to string should never fail");
2262 let ops_str = String::from_utf8_lossy(&ops);
2263 assert!(ops_str.contains("25.00 25.00 150.00 100.00 re"));
2264 assert!(ops_str.contains("S"));
2265 }
2266
2267 #[test]
2268 fn test_empty_operations() {
2269 let ctx = GraphicsContext::new();
2270 let ops = ctx
2271 .generate_operations()
2272 .expect("Writing to string should never fail");
2273 assert!(ops.is_empty());
2274 }
2275
2276 #[test]
2277 fn test_complex_path_operations() {
2278 let mut ctx = GraphicsContext::new();
2279 ctx.move_to(50.0, 50.0)
2280 .line_to(100.0, 50.0)
2281 .curve_to(125.0, 50.0, 150.0, 75.0, 150.0, 100.0)
2282 .line_to(150.0, 150.0)
2283 .close_path()
2284 .fill();
2285
2286 let ops = ctx
2287 .generate_operations()
2288 .expect("Writing to string should never fail");
2289 let ops_str = String::from_utf8_lossy(&ops);
2290 assert!(ops_str.contains("50.00 50.00 m"));
2291 assert!(ops_str.contains("100.00 50.00 l"));
2292 assert!(ops_str.contains("125.00 50.00 150.00 75.00 150.00 100.00 c"));
2293 assert!(ops_str.contains("150.00 150.00 l"));
2294 assert!(ops_str.contains("h"));
2295 assert!(ops_str.contains("f"));
2296 }
2297
2298 #[test]
2299 fn test_graphics_state_dict_generation() {
2300 let mut ctx = GraphicsContext::new();
2301
2302 assert!(ctx.generate_graphics_state_dict().is_none());
2304
2305 ctx.set_opacity(0.5);
2307 let dict = ctx.generate_graphics_state_dict();
2308 assert!(dict.is_some());
2309 let dict_str = dict.expect("Writing to string should never fail");
2310 assert!(dict_str.contains("/ca 0.5"));
2311 assert!(dict_str.contains("/CA 0.5"));
2312 }
2313
2314 #[test]
2315 fn test_line_dash_pattern() {
2316 let mut ctx = GraphicsContext::new();
2317 let pattern = LineDashPattern {
2318 array: vec![3.0, 2.0],
2319 phase: 0.0,
2320 };
2321 ctx.set_line_dash_pattern(pattern);
2322
2323 let ops = ctx
2324 .generate_operations()
2325 .expect("Writing to string should never fail");
2326 let ops_str = String::from_utf8_lossy(&ops);
2327 assert!(ops_str.contains("[3.00 2.00] 0.00 d"));
2328 }
2329
2330 #[test]
2331 fn test_miter_limit_setting() {
2332 let mut ctx = GraphicsContext::new();
2333 ctx.set_miter_limit(4.0);
2334
2335 let ops = ctx
2336 .generate_operations()
2337 .expect("Writing to string should never fail");
2338 let ops_str = String::from_utf8_lossy(&ops);
2339 assert!(ops_str.contains("4.00 M"));
2340 }
2341
2342 #[test]
2343 fn test_line_cap_styles() {
2344 let mut ctx = GraphicsContext::new();
2345
2346 ctx.set_line_cap(LineCap::Butt);
2347 let ops = ctx
2348 .generate_operations()
2349 .expect("Writing to string should never fail");
2350 let ops_str = String::from_utf8_lossy(&ops);
2351 assert!(ops_str.contains("0 J"));
2352
2353 let mut ctx = GraphicsContext::new();
2354 ctx.set_line_cap(LineCap::Round);
2355 let ops = ctx
2356 .generate_operations()
2357 .expect("Writing to string should never fail");
2358 let ops_str = String::from_utf8_lossy(&ops);
2359 assert!(ops_str.contains("1 J"));
2360
2361 let mut ctx = GraphicsContext::new();
2362 ctx.set_line_cap(LineCap::Square);
2363 let ops = ctx
2364 .generate_operations()
2365 .expect("Writing to string should never fail");
2366 let ops_str = String::from_utf8_lossy(&ops);
2367 assert!(ops_str.contains("2 J"));
2368 }
2369
2370 #[test]
2371 fn test_transparency_groups() {
2372 let mut ctx = GraphicsContext::new();
2373
2374 let group = TransparencyGroup::new()
2376 .with_isolated(true)
2377 .with_opacity(0.5);
2378
2379 ctx.begin_transparency_group(group);
2380 assert!(ctx.in_transparency_group());
2381
2382 ctx.rect(10.0, 10.0, 100.0, 100.0);
2384 ctx.fill();
2385
2386 ctx.end_transparency_group();
2387 assert!(!ctx.in_transparency_group());
2388
2389 let ops = ctx.operations();
2391 assert!(ops.contains("% Begin Transparency Group"));
2392 assert!(ops.contains("% End Transparency Group"));
2393 }
2394
2395 #[test]
2396 fn test_nested_transparency_groups() {
2397 let mut ctx = GraphicsContext::new();
2398
2399 let group1 = TransparencyGroup::isolated().with_opacity(0.8);
2401 ctx.begin_transparency_group(group1);
2402 assert!(ctx.in_transparency_group());
2403
2404 let group2 = TransparencyGroup::knockout().with_blend_mode(BlendMode::Multiply);
2406 ctx.begin_transparency_group(group2);
2407
2408 ctx.circle(50.0, 50.0, 25.0);
2410 ctx.fill();
2411
2412 ctx.end_transparency_group();
2414 assert!(ctx.in_transparency_group()); ctx.end_transparency_group();
2418 assert!(!ctx.in_transparency_group());
2419 }
2420
2421 #[test]
2422 fn test_line_join_styles() {
2423 let mut ctx = GraphicsContext::new();
2424
2425 ctx.set_line_join(LineJoin::Miter);
2426 let ops = ctx
2427 .generate_operations()
2428 .expect("Writing to string should never fail");
2429 let ops_str = String::from_utf8_lossy(&ops);
2430 assert!(ops_str.contains("0 j"));
2431
2432 let mut ctx = GraphicsContext::new();
2433 ctx.set_line_join(LineJoin::Round);
2434 let ops = ctx
2435 .generate_operations()
2436 .expect("Writing to string should never fail");
2437 let ops_str = String::from_utf8_lossy(&ops);
2438 assert!(ops_str.contains("1 j"));
2439
2440 let mut ctx = GraphicsContext::new();
2441 ctx.set_line_join(LineJoin::Bevel);
2442 let ops = ctx
2443 .generate_operations()
2444 .expect("Writing to string should never fail");
2445 let ops_str = String::from_utf8_lossy(&ops);
2446 assert!(ops_str.contains("2 j"));
2447 }
2448
2449 #[test]
2450 fn test_rendering_intent() {
2451 let mut ctx = GraphicsContext::new();
2452
2453 ctx.set_rendering_intent(RenderingIntent::AbsoluteColorimetric);
2454 assert_eq!(
2455 ctx.rendering_intent(),
2456 RenderingIntent::AbsoluteColorimetric
2457 );
2458
2459 ctx.set_rendering_intent(RenderingIntent::Perceptual);
2460 assert_eq!(ctx.rendering_intent(), RenderingIntent::Perceptual);
2461
2462 ctx.set_rendering_intent(RenderingIntent::Saturation);
2463 assert_eq!(ctx.rendering_intent(), RenderingIntent::Saturation);
2464 }
2465
2466 #[test]
2467 fn test_flatness_tolerance() {
2468 let mut ctx = GraphicsContext::new();
2469
2470 ctx.set_flatness(0.5);
2471 assert_eq!(ctx.flatness(), 0.5);
2472
2473 let ops = ctx
2474 .generate_operations()
2475 .expect("Writing to string should never fail");
2476 let ops_str = String::from_utf8_lossy(&ops);
2477 assert!(ops_str.contains("0.50 i"));
2478 }
2479
2480 #[test]
2481 fn test_smoothness_tolerance() {
2482 let mut ctx = GraphicsContext::new();
2483
2484 let _ = ctx.set_smoothness(0.1);
2485 assert_eq!(ctx.smoothness(), 0.1);
2486 }
2487
2488 #[test]
2489 fn test_bezier_curves() {
2490 let mut ctx = GraphicsContext::new();
2491
2492 ctx.move_to(10.0, 10.0);
2494 ctx.curve_to(20.0, 10.0, 30.0, 20.0, 30.0, 30.0);
2495
2496 let ops = ctx
2497 .generate_operations()
2498 .expect("Writing to string should never fail");
2499 let ops_str = String::from_utf8_lossy(&ops);
2500 assert!(ops_str.contains("10.00 10.00 m"));
2501 assert!(ops_str.contains("c")); }
2503
2504 #[test]
2505 fn test_clipping_path() {
2506 let mut ctx = GraphicsContext::new();
2507
2508 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2509 ctx.clip();
2510
2511 let ops = ctx
2512 .generate_operations()
2513 .expect("Writing to string should never fail");
2514 let ops_str = String::from_utf8_lossy(&ops);
2515 assert!(ops_str.contains("W"));
2516 }
2517
2518 #[test]
2519 fn test_even_odd_clipping() {
2520 let mut ctx = GraphicsContext::new();
2521
2522 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2523 ctx.clip_even_odd();
2524
2525 let ops = ctx
2526 .generate_operations()
2527 .expect("Writing to string should never fail");
2528 let ops_str = String::from_utf8_lossy(&ops);
2529 assert!(ops_str.contains("W*"));
2530 }
2531
2532 #[test]
2533 fn test_color_creation() {
2534 let gray = Color::gray(0.5);
2536 assert_eq!(gray, Color::Gray(0.5));
2537
2538 let rgb = Color::rgb(0.2, 0.4, 0.6);
2539 assert_eq!(rgb, Color::Rgb(0.2, 0.4, 0.6));
2540
2541 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
2542 assert_eq!(cmyk, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
2543
2544 assert_eq!(Color::black(), Color::Gray(0.0));
2546 assert_eq!(Color::white(), Color::Gray(1.0));
2547 assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
2548 }
2549
2550 #[test]
2551 fn test_extended_graphics_state() {
2552 let ctx = GraphicsContext::new();
2553
2554 let _extgstate = ExtGState::new();
2556
2557 assert!(ctx.generate_operations().is_ok());
2559 }
2560
2561 #[test]
2562 fn test_path_construction_methods() {
2563 let mut ctx = GraphicsContext::new();
2564
2565 ctx.move_to(10.0, 10.0);
2567 ctx.line_to(20.0, 20.0);
2568 ctx.curve_to(30.0, 30.0, 40.0, 40.0, 50.0, 50.0);
2569 ctx.rect(60.0, 60.0, 30.0, 30.0);
2570 ctx.circle(100.0, 100.0, 25.0);
2571 ctx.close_path();
2572
2573 let ops = ctx
2574 .generate_operations()
2575 .expect("Writing to string should never fail");
2576 assert!(!ops.is_empty());
2577 }
2578
2579 #[test]
2580 fn test_graphics_context_clone_advanced() {
2581 let mut ctx = GraphicsContext::new();
2582 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
2583 ctx.set_line_width(5.0);
2584
2585 let cloned = ctx.clone();
2586 assert_eq!(cloned.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2587 assert_eq!(cloned.line_width(), 5.0);
2588 }
2589
2590 #[test]
2591 fn test_basic_drawing_operations() {
2592 let mut ctx = GraphicsContext::new();
2593
2594 ctx.move_to(50.0, 50.0);
2596 ctx.line_to(100.0, 100.0);
2597 ctx.stroke();
2598
2599 let ops = ctx
2600 .generate_operations()
2601 .expect("Writing to string should never fail");
2602 let ops_str = String::from_utf8_lossy(&ops);
2603 assert!(ops_str.contains("m")); assert!(ops_str.contains("l")); assert!(ops_str.contains("S")); }
2607
2608 #[test]
2609 fn test_graphics_state_stack() {
2610 let mut ctx = GraphicsContext::new();
2611
2612 ctx.set_fill_color(Color::black());
2614
2615 ctx.save_state();
2617 ctx.set_fill_color(Color::red());
2618 assert_eq!(ctx.fill_color(), Color::red());
2619
2620 ctx.save_state();
2622 ctx.set_fill_color(Color::blue());
2623 assert_eq!(ctx.fill_color(), Color::blue());
2624
2625 ctx.restore_state();
2627 assert_eq!(ctx.fill_color(), Color::red());
2628
2629 ctx.restore_state();
2631 assert_eq!(ctx.fill_color(), Color::black());
2632 }
2633
2634 #[test]
2635 fn test_word_spacing() {
2636 let mut ctx = GraphicsContext::new();
2637 ctx.set_word_spacing(2.5);
2638
2639 let ops = ctx.generate_operations().unwrap();
2640 let ops_str = String::from_utf8_lossy(&ops);
2641 assert!(ops_str.contains("2.50 Tw"));
2642 }
2643
2644 #[test]
2645 fn test_character_spacing() {
2646 let mut ctx = GraphicsContext::new();
2647 ctx.set_character_spacing(1.0);
2648
2649 let ops = ctx.generate_operations().unwrap();
2650 let ops_str = String::from_utf8_lossy(&ops);
2651 assert!(ops_str.contains("1.00 Tc"));
2652 }
2653
2654 #[test]
2655 fn test_justified_text() {
2656 let mut ctx = GraphicsContext::new();
2657 ctx.begin_text();
2658 ctx.set_text_position(100.0, 200.0);
2659 ctx.show_justified_text("Hello world from PDF", 200.0)
2660 .unwrap();
2661 ctx.end_text();
2662
2663 let ops = ctx.generate_operations().unwrap();
2664 let ops_str = String::from_utf8_lossy(&ops);
2665
2666 assert!(ops_str.contains("BT")); assert!(ops_str.contains("ET")); assert!(ops_str.contains("100.00 200.00 Td")); assert!(ops_str.contains("(Hello world from PDF) Tj")); assert!(ops_str.contains("Tw")); }
2675
2676 #[test]
2677 fn test_justified_text_single_word() {
2678 let mut ctx = GraphicsContext::new();
2679 ctx.begin_text();
2680 ctx.show_justified_text("Hello", 200.0).unwrap();
2681 ctx.end_text();
2682
2683 let ops = ctx.generate_operations().unwrap();
2684 let ops_str = String::from_utf8_lossy(&ops);
2685
2686 assert!(ops_str.contains("(Hello) Tj"));
2688 assert_eq!(ops_str.matches("Tw").count(), 0);
2690 }
2691
2692 #[test]
2693 fn test_text_width_estimation() {
2694 let ctx = GraphicsContext::new();
2695 let width = ctx.estimate_text_width_simple("Hello");
2696
2697 assert!(width > 0.0);
2699 assert_eq!(width, 5.0 * 12.0 * 0.6); }
2701
2702 #[test]
2703 fn test_set_alpha_methods() {
2704 let mut ctx = GraphicsContext::new();
2705
2706 assert!(ctx.set_alpha(0.5).is_ok());
2708 assert!(ctx.set_alpha_fill(0.3).is_ok());
2709 assert!(ctx.set_alpha_stroke(0.7).is_ok());
2710
2711 assert!(ctx.set_alpha(1.5).is_ok()); assert!(ctx.set_alpha(-0.2).is_ok()); assert!(ctx.set_alpha_fill(2.0).is_ok()); assert!(ctx.set_alpha_stroke(-1.0).is_ok()); let result = ctx
2719 .set_alpha(0.5)
2720 .and_then(|c| c.set_alpha_fill(0.3))
2721 .and_then(|c| c.set_alpha_stroke(0.7));
2722 assert!(result.is_ok());
2723 }
2724
2725 #[test]
2726 fn test_alpha_methods_generate_extgstate() {
2727 let mut ctx = GraphicsContext::new();
2728
2729 ctx.set_alpha(0.5).unwrap();
2731
2732 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
2734
2735 let ops = ctx.generate_operations().unwrap();
2736 let ops_str = String::from_utf8_lossy(&ops);
2737
2738 assert!(ops_str.contains("/GS")); assert!(ops_str.contains(" gs\n")); ctx.clear();
2744 ctx.set_alpha_fill(0.3).unwrap();
2745 ctx.set_alpha_stroke(0.8).unwrap();
2746 ctx.rect(20.0, 20.0, 60.0, 60.0).fill_stroke();
2747
2748 let ops2 = ctx.generate_operations().unwrap();
2749 let ops_str2 = String::from_utf8_lossy(&ops2);
2750
2751 assert!(ops_str2.contains("/GS")); assert!(ops_str2.contains(" gs\n")); }
2755
2756 #[test]
2757 fn test_add_command() {
2758 let mut ctx = GraphicsContext::new();
2759
2760 ctx.add_command("1 0 0 1 100 200 cm");
2762 let ops = ctx.operations();
2763 assert!(ops.contains("1 0 0 1 100 200 cm\n"));
2764
2765 ctx.clear();
2767 ctx.add_command("q");
2768 assert_eq!(ctx.operations(), "q\n");
2769
2770 ctx.clear();
2772 ctx.add_command("");
2773 assert_eq!(ctx.operations(), "\n");
2774
2775 ctx.clear();
2777 ctx.add_command("Q\n");
2778 assert_eq!(ctx.operations(), "Q\n\n"); ctx.clear();
2782 ctx.add_command("q");
2783 ctx.add_command("1 0 0 1 50 50 cm");
2784 ctx.add_command("Q");
2785 assert_eq!(ctx.operations(), "q\n1 0 0 1 50 50 cm\nQ\n");
2786 }
2787
2788 #[test]
2789 fn test_get_operations() {
2790 let mut ctx = GraphicsContext::new();
2791 ctx.rect(10.0, 10.0, 50.0, 50.0);
2792 let ops1 = ctx.operations();
2793 let ops2 = ctx.get_operations();
2794 assert_eq!(ops1, ops2);
2795 }
2796
2797 #[test]
2798 fn test_set_line_solid() {
2799 let mut ctx = GraphicsContext::new();
2800 ctx.set_line_dash_pattern(LineDashPattern::new(vec![5.0, 3.0], 0.0));
2801 ctx.set_line_solid();
2802 let ops = ctx.operations();
2803 assert!(ops.contains("[] 0 d\n"));
2804 }
2805
2806 #[test]
2807 fn test_set_custom_font() {
2808 let mut ctx = GraphicsContext::new();
2809 ctx.set_custom_font("CustomFont", 14.0);
2810 assert_eq!(ctx.current_font_name, Some("CustomFont".to_string()));
2811 assert_eq!(ctx.current_font_size, 14.0);
2812 }
2813
2814 #[test]
2815 fn test_set_glyph_mapping() {
2816 let mut ctx = GraphicsContext::new();
2817
2818 assert!(ctx.glyph_mapping.is_none());
2820
2821 let mut mapping = HashMap::new();
2823 mapping.insert(65u32, 1u16); mapping.insert(66u32, 2u16); ctx.set_glyph_mapping(mapping.clone());
2826 assert!(ctx.glyph_mapping.is_some());
2827 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 2);
2828 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), Some(&1));
2829 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&66), Some(&2));
2830
2831 ctx.set_glyph_mapping(HashMap::new());
2833 assert!(ctx.glyph_mapping.is_some());
2834 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 0);
2835
2836 let mut new_mapping = HashMap::new();
2838 new_mapping.insert(67u32, 3u16); ctx.set_glyph_mapping(new_mapping);
2840 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 1);
2841 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&67), Some(&3));
2842 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), None); }
2844
2845 #[test]
2846 fn test_draw_text_basic() {
2847 let mut ctx = GraphicsContext::new();
2848 ctx.set_font(Font::Helvetica, 12.0);
2849
2850 let result = ctx.draw_text("Hello", 100.0, 200.0);
2851 assert!(result.is_ok());
2852
2853 let ops = ctx.operations();
2854 assert!(ops.contains("BT\n"));
2856 assert!(ops.contains("ET\n"));
2857
2858 assert!(ops.contains("/Helvetica"));
2860 assert!(ops.contains("12"));
2861 assert!(ops.contains("Tf\n"));
2862
2863 assert!(ops.contains("100"));
2865 assert!(ops.contains("200"));
2866 assert!(ops.contains("Td\n"));
2867
2868 assert!(ops.contains("(Hello)") || ops.contains("<48656c6c6f>")); }
2871
2872 #[test]
2873 fn test_draw_text_with_special_characters() {
2874 let mut ctx = GraphicsContext::new();
2875 ctx.set_font(Font::Helvetica, 12.0);
2876
2877 let result = ctx.draw_text("Test (with) parens", 50.0, 100.0);
2879 assert!(result.is_ok());
2880
2881 let ops = ctx.operations();
2882 assert!(ops.contains("\\(") || ops.contains("\\)") || ops.contains("<"));
2884 }
2886
2887 #[test]
2888 fn test_draw_text_unicode_detection() {
2889 let mut ctx = GraphicsContext::new();
2890 ctx.set_font(Font::Helvetica, 12.0);
2891
2892 ctx.draw_text("ASCII", 0.0, 0.0).unwrap();
2894 let _ops_ascii = ctx.operations();
2895
2896 ctx.clear();
2897
2898 ctx.set_font(Font::Helvetica, 12.0);
2900 ctx.draw_text("中文", 0.0, 0.0).unwrap();
2901 let ops_unicode = ctx.operations();
2902
2903 assert!(ops_unicode.contains("<") && ops_unicode.contains(">"));
2905 }
2906
2907 #[test]
2908 #[allow(deprecated)]
2909 fn test_draw_text_hex_encoding() {
2910 let mut ctx = GraphicsContext::new();
2911 ctx.set_font(Font::Helvetica, 12.0);
2912 let result = ctx.draw_text_hex("Test", 50.0, 100.0);
2913 assert!(result.is_ok());
2914 let ops = ctx.operations();
2915 assert!(ops.contains("<"));
2916 assert!(ops.contains(">"));
2917 }
2918
2919 #[test]
2920 #[allow(deprecated)]
2921 fn test_draw_text_cid() {
2922 let mut ctx = GraphicsContext::new();
2923 ctx.set_custom_font("CustomCIDFont", 12.0);
2924 let result = ctx.draw_text_cid("Test", 50.0, 100.0);
2925 assert!(result.is_ok());
2926 let ops = ctx.operations();
2927 assert!(ops.contains("BT\n"));
2928 assert!(ops.contains("ET\n"));
2929 }
2930
2931 #[test]
2932 #[allow(deprecated)]
2933 fn test_draw_text_unicode() {
2934 let mut ctx = GraphicsContext::new();
2935 ctx.set_custom_font("UnicodeFont", 12.0);
2936 let result = ctx.draw_text_unicode("Test \u{4E2D}\u{6587}", 50.0, 100.0);
2937 assert!(result.is_ok());
2938 let ops = ctx.operations();
2939 assert!(ops.contains("BT\n"));
2940 assert!(ops.contains("ET\n"));
2941 }
2942
2943 #[test]
2944 fn test_begin_end_transparency_group() {
2945 let mut ctx = GraphicsContext::new();
2946
2947 assert!(!ctx.in_transparency_group());
2949 assert!(ctx.current_transparency_group().is_none());
2950
2951 let group = TransparencyGroup::new();
2953 ctx.begin_transparency_group(group);
2954 assert!(ctx.in_transparency_group());
2955 assert!(ctx.current_transparency_group().is_some());
2956
2957 let ops = ctx.operations();
2959 assert!(ops.contains("% Begin Transparency Group"));
2960
2961 ctx.end_transparency_group();
2963 assert!(!ctx.in_transparency_group());
2964 assert!(ctx.current_transparency_group().is_none());
2965
2966 let ops_after = ctx.operations();
2968 assert!(ops_after.contains("% End Transparency Group"));
2969 }
2970
2971 #[test]
2972 fn test_transparency_group_nesting() {
2973 let mut ctx = GraphicsContext::new();
2974
2975 let group1 = TransparencyGroup::new();
2977 let group2 = TransparencyGroup::new();
2978 let group3 = TransparencyGroup::new();
2979
2980 ctx.begin_transparency_group(group1);
2981 assert_eq!(ctx.transparency_stack.len(), 1);
2982
2983 ctx.begin_transparency_group(group2);
2984 assert_eq!(ctx.transparency_stack.len(), 2);
2985
2986 ctx.begin_transparency_group(group3);
2987 assert_eq!(ctx.transparency_stack.len(), 3);
2988
2989 ctx.end_transparency_group();
2991 assert_eq!(ctx.transparency_stack.len(), 2);
2992
2993 ctx.end_transparency_group();
2994 assert_eq!(ctx.transparency_stack.len(), 1);
2995
2996 ctx.end_transparency_group();
2997 assert_eq!(ctx.transparency_stack.len(), 0);
2998 assert!(!ctx.in_transparency_group());
2999 }
3000
3001 #[test]
3002 fn test_transparency_group_without_begin() {
3003 let mut ctx = GraphicsContext::new();
3004
3005 assert!(!ctx.in_transparency_group());
3007 ctx.end_transparency_group();
3008 assert!(!ctx.in_transparency_group());
3009 }
3010
3011 #[test]
3012 fn test_extgstate_manager_access() {
3013 let ctx = GraphicsContext::new();
3014 let manager = ctx.extgstate_manager();
3015 assert_eq!(manager.count(), 0);
3016 }
3017
3018 #[test]
3019 fn test_extgstate_manager_mut_access() {
3020 let mut ctx = GraphicsContext::new();
3021 let manager = ctx.extgstate_manager_mut();
3022 assert_eq!(manager.count(), 0);
3023 }
3024
3025 #[test]
3026 fn test_has_extgstates() {
3027 let mut ctx = GraphicsContext::new();
3028
3029 assert!(!ctx.has_extgstates());
3031 assert_eq!(ctx.extgstate_manager().count(), 0);
3032
3033 ctx.set_alpha(0.5).unwrap();
3035 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3036 let result = ctx.generate_operations().unwrap();
3037
3038 assert!(ctx.has_extgstates());
3039 assert!(ctx.extgstate_manager().count() > 0);
3040
3041 let output = String::from_utf8_lossy(&result);
3043 assert!(output.contains("/GS")); assert!(output.contains(" gs\n")); }
3046
3047 #[test]
3048 fn test_generate_extgstate_resources() {
3049 let mut ctx = GraphicsContext::new();
3050 ctx.set_alpha(0.5).unwrap();
3051 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3052 ctx.generate_operations().unwrap();
3053
3054 let resources = ctx.generate_extgstate_resources();
3055 assert!(resources.is_ok());
3056 }
3057
3058 #[test]
3059 fn test_apply_extgstate() {
3060 let mut ctx = GraphicsContext::new();
3061
3062 let mut state = ExtGState::new();
3064 state.alpha_fill = Some(0.5);
3065 state.alpha_stroke = Some(0.8);
3066 state.blend_mode = Some(BlendMode::Multiply);
3067
3068 let result = ctx.apply_extgstate(state);
3069 assert!(result.is_ok());
3070
3071 assert!(ctx.has_extgstates());
3073 assert_eq!(ctx.extgstate_manager().count(), 1);
3074
3075 let mut state2 = ExtGState::new();
3077 state2.alpha_fill = Some(0.3);
3078 ctx.apply_extgstate(state2).unwrap();
3079
3080 assert_eq!(ctx.extgstate_manager().count(), 2);
3082 }
3083
3084 #[test]
3085 fn test_with_extgstate() {
3086 let mut ctx = GraphicsContext::new();
3087 let result = ctx.with_extgstate(|mut state| {
3088 state.alpha_fill = Some(0.5);
3089 state.alpha_stroke = Some(0.8);
3090 state
3091 });
3092 assert!(result.is_ok());
3093 }
3094
3095 #[test]
3096 fn test_set_blend_mode() {
3097 let mut ctx = GraphicsContext::new();
3098
3099 let result = ctx.set_blend_mode(BlendMode::Multiply);
3101 assert!(result.is_ok());
3102 assert!(ctx.has_extgstates());
3103
3104 ctx.clear();
3106 ctx.set_blend_mode(BlendMode::Screen).unwrap();
3107 ctx.rect(0.0, 0.0, 10.0, 10.0).fill();
3108 let ops = ctx.generate_operations().unwrap();
3109 let output = String::from_utf8_lossy(&ops);
3110
3111 assert!(output.contains("/GS"));
3113 assert!(output.contains(" gs\n"));
3114 }
3115
3116 #[test]
3117 fn test_render_table() {
3118 let mut ctx = GraphicsContext::new();
3119 let table = Table::with_equal_columns(2, 200.0);
3120 let result = ctx.render_table(&table);
3121 assert!(result.is_ok());
3122 }
3123
3124 #[test]
3125 fn test_render_list() {
3126 let mut ctx = GraphicsContext::new();
3127 use crate::text::{OrderedList, OrderedListStyle};
3128 let ordered = OrderedList::new(OrderedListStyle::Decimal);
3129 let list = ListElement::Ordered(ordered);
3130 let result = ctx.render_list(&list);
3131 assert!(result.is_ok());
3132 }
3133
3134 #[test]
3135 fn test_render_column_layout() {
3136 let mut ctx = GraphicsContext::new();
3137 use crate::text::ColumnContent;
3138 let layout = ColumnLayout::new(2, 100.0, 200.0);
3139 let content = ColumnContent::new("Test content");
3140 let result = ctx.render_column_layout(&layout, &content, 50.0, 50.0, 400.0);
3141 assert!(result.is_ok());
3142 }
3143
3144 #[test]
3145 fn test_clip_ellipse() {
3146 let mut ctx = GraphicsContext::new();
3147
3148 assert!(!ctx.has_clipping());
3150 assert!(ctx.clipping_path().is_none());
3151
3152 let result = ctx.clip_ellipse(100.0, 100.0, 50.0, 30.0);
3154 assert!(result.is_ok());
3155 assert!(ctx.has_clipping());
3156 assert!(ctx.clipping_path().is_some());
3157
3158 let ops = ctx.operations();
3160 assert!(ops.contains("W\n") || ops.contains("W*\n")); ctx.clear_clipping();
3164 assert!(!ctx.has_clipping());
3165 }
3166
3167 #[test]
3168 fn test_clipping_path_access() {
3169 let mut ctx = GraphicsContext::new();
3170
3171 assert!(ctx.clipping_path().is_none());
3173
3174 ctx.clip_rect(10.0, 10.0, 50.0, 50.0).unwrap();
3176 assert!(ctx.clipping_path().is_some());
3177
3178 ctx.clip_circle(100.0, 100.0, 25.0).unwrap();
3180 assert!(ctx.clipping_path().is_some());
3181
3182 ctx.save_state();
3184 ctx.clear_clipping();
3185 assert!(!ctx.has_clipping());
3186
3187 ctx.restore_state();
3188 assert!(ctx.has_clipping());
3190 }
3191
3192 #[test]
3195 fn test_edge_case_move_to_negative() {
3196 let mut ctx = GraphicsContext::new();
3197 ctx.move_to(-100.5, -200.25);
3198 assert!(ctx.operations().contains("-100.50 -200.25 m\n"));
3199 }
3200
3201 #[test]
3202 fn test_edge_case_opacity_out_of_range() {
3203 let mut ctx = GraphicsContext::new();
3204
3205 let _ = ctx.set_opacity(2.5);
3207 assert_eq!(ctx.fill_opacity(), 1.0);
3208
3209 let _ = ctx.set_opacity(-0.5);
3211 assert_eq!(ctx.fill_opacity(), 0.0);
3212 }
3213
3214 #[test]
3215 fn test_edge_case_line_width_extremes() {
3216 let mut ctx = GraphicsContext::new();
3217
3218 ctx.set_line_width(0.0);
3219 assert_eq!(ctx.line_width(), 0.0);
3220
3221 ctx.set_line_width(10000.0);
3222 assert_eq!(ctx.line_width(), 10000.0);
3223 }
3224
3225 #[test]
3228 fn test_interaction_transparency_plus_clipping() {
3229 let mut ctx = GraphicsContext::new();
3230
3231 ctx.set_alpha(0.5).unwrap();
3232 ctx.clip_rect(10.0, 10.0, 100.0, 100.0).unwrap();
3233 ctx.rect(20.0, 20.0, 80.0, 80.0).fill();
3234
3235 let ops = ctx.generate_operations().unwrap();
3236 let output = String::from_utf8_lossy(&ops);
3237
3238 assert!(output.contains("W\n") || output.contains("W*\n"));
3240 assert!(output.contains("/GS"));
3241 }
3242
3243 #[test]
3244 fn test_interaction_extgstate_plus_text() {
3245 let mut ctx = GraphicsContext::new();
3246
3247 let mut state = ExtGState::new();
3248 state.alpha_fill = Some(0.7);
3249 ctx.apply_extgstate(state).unwrap();
3250
3251 ctx.set_font(Font::Helvetica, 14.0);
3252 ctx.draw_text("Test", 100.0, 200.0).unwrap();
3253
3254 let ops = ctx.generate_operations().unwrap();
3255 let output = String::from_utf8_lossy(&ops);
3256
3257 assert!(output.contains("/GS"));
3258 assert!(output.contains("BT\n"));
3259 }
3260
3261 #[test]
3262 fn test_interaction_chained_transformations() {
3263 let mut ctx = GraphicsContext::new();
3264
3265 ctx.translate(50.0, 100.0);
3266 ctx.rotate(45.0);
3267 ctx.scale(2.0, 2.0);
3268
3269 let ops = ctx.operations();
3270 assert_eq!(ops.matches("cm\n").count(), 3);
3271 }
3272
3273 #[test]
3276 fn test_e2e_complete_page_with_header() {
3277 use crate::{Document, Page};
3278
3279 let mut doc = Document::new();
3280 let mut page = Page::a4();
3281 let ctx = page.graphics();
3282
3283 ctx.save_state();
3285 let _ = ctx.set_fill_opacity(0.3);
3286 ctx.set_fill_color(Color::rgb(200.0, 200.0, 255.0));
3287 ctx.rect(0.0, 750.0, 595.0, 42.0).fill();
3288 ctx.restore_state();
3289
3290 ctx.save_state();
3292 ctx.clip_rect(50.0, 50.0, 495.0, 692.0).unwrap();
3293 ctx.rect(60.0, 60.0, 100.0, 100.0).fill();
3294 ctx.restore_state();
3295
3296 let ops = ctx.generate_operations().unwrap();
3297 let output = String::from_utf8_lossy(&ops);
3298
3299 assert!(output.contains("q\n"));
3300 assert!(output.contains("Q\n"));
3301 assert!(output.contains("f\n"));
3302
3303 doc.add_page(page);
3304 assert!(doc.to_bytes().unwrap().len() > 0);
3305 }
3306
3307 #[test]
3308 fn test_e2e_watermark_workflow() {
3309 let mut ctx = GraphicsContext::new();
3310
3311 ctx.save_state();
3312 let _ = ctx.set_fill_opacity(0.2);
3313 ctx.translate(300.0, 400.0);
3314 ctx.rotate(45.0);
3315 ctx.set_font(Font::HelveticaBold, 72.0);
3316 ctx.draw_text("DRAFT", 0.0, 0.0).unwrap();
3317 ctx.restore_state();
3318
3319 let ops = ctx.generate_operations().unwrap();
3320 let output = String::from_utf8_lossy(&ops);
3321
3322 assert!(output.contains("q\n")); assert!(output.contains("Q\n")); assert!(output.contains("cm\n")); assert!(output.contains("BT\n")); assert!(output.contains("ET\n")); }
3329}