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 if let Some(font_name) = &self.current_font_name {
1151 writeln!(
1152 &mut self.operations,
1153 "/{} {} Tf",
1154 font_name, self.current_font_size
1155 )
1156 .expect("Writing to string should never fail");
1157 } else {
1158 writeln!(
1159 &mut self.operations,
1160 "/Helvetica {} Tf",
1161 self.current_font_size
1162 )
1163 .expect("Writing to string should never fail");
1164 }
1165
1166 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1168 .expect("Writing to string should never fail");
1169
1170 self.operations.push('(');
1173 for ch in text.chars() {
1174 let code = ch as u32;
1175 if code <= 127 {
1176 match ch {
1178 '(' => self.operations.push_str("\\("),
1179 ')' => self.operations.push_str("\\)"),
1180 '\\' => self.operations.push_str("\\\\"),
1181 '\n' => self.operations.push_str("\\n"),
1182 '\r' => self.operations.push_str("\\r"),
1183 '\t' => self.operations.push_str("\\t"),
1184 _ => self.operations.push(ch),
1185 }
1186 } else if code <= 255 {
1187 write!(&mut self.operations, "\\{:03o}", code)
1190 .expect("Writing to string should never fail");
1191 } else {
1192 self.operations.push('?');
1194 }
1195 }
1196 self.operations.push_str(") Tj\n");
1197
1198 self.operations.push_str("ET\n");
1200
1201 Ok(self)
1202 }
1203
1204 fn draw_with_unicode_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1206 self.operations.push_str("BT\n");
1208
1209 if let Some(font_name) = &self.current_font_name {
1211 writeln!(
1213 &mut self.operations,
1214 "/{} {} Tf",
1215 font_name, self.current_font_size
1216 )
1217 .expect("Writing to string should never fail");
1218 } else {
1219 writeln!(
1220 &mut self.operations,
1221 "/Helvetica {} Tf",
1222 self.current_font_size
1223 )
1224 .expect("Writing to string should never fail");
1225 }
1226
1227 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1229 .expect("Writing to string should never fail");
1230
1231 self.operations.push('<');
1235
1236 for ch in text.chars() {
1237 let code = ch as u32;
1238
1239 if code <= 0xFFFF {
1242 write!(&mut self.operations, "{:04X}", code)
1244 .expect("Writing to string should never fail");
1245 } else {
1246 write!(&mut self.operations, "FFFD").expect("Writing to string should never fail");
1249 }
1251 }
1252 self.operations.push_str("> Tj\n");
1253
1254 self.operations.push_str("ET\n");
1256
1257 Ok(self)
1258 }
1259
1260 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1262 pub fn draw_text_hex(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1263 self.operations.push_str("BT\n");
1265
1266 if let Some(font_name) = &self.current_font_name {
1268 writeln!(
1269 &mut self.operations,
1270 "/{} {} Tf",
1271 font_name, self.current_font_size
1272 )
1273 .expect("Writing to string should never fail");
1274 } else {
1275 writeln!(
1277 &mut self.operations,
1278 "/Helvetica {} Tf",
1279 self.current_font_size
1280 )
1281 .expect("Writing to string should never fail");
1282 }
1283
1284 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1286 .expect("Writing to string should never fail");
1287
1288 self.operations.push('<');
1292 for ch in text.chars() {
1293 if ch as u32 <= 255 {
1294 write!(&mut self.operations, "{:02X}", ch as u8)
1296 .expect("Writing to string should never fail");
1297 } else {
1298 write!(&mut self.operations, "3F").expect("Writing to string should never fail");
1301 }
1303 }
1304 self.operations.push_str("> Tj\n");
1305
1306 self.operations.push_str("ET\n");
1308
1309 Ok(self)
1310 }
1311
1312 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1314 pub fn draw_text_cid(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1315 use crate::fonts::needs_type0_font;
1316
1317 self.operations.push_str("BT\n");
1319
1320 if let Some(font_name) = &self.current_font_name {
1322 writeln!(
1323 &mut self.operations,
1324 "/{} {} Tf",
1325 font_name, self.current_font_size
1326 )
1327 .expect("Writing to string should never fail");
1328 } else {
1329 writeln!(
1330 &mut self.operations,
1331 "/Helvetica {} Tf",
1332 self.current_font_size
1333 )
1334 .expect("Writing to string should never fail");
1335 }
1336
1337 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1339 .expect("Writing to string should never fail");
1340
1341 if needs_type0_font(text) {
1343 self.operations.push('<');
1345 for ch in text.chars() {
1346 let code = ch as u32;
1347
1348 if code <= 0xFFFF {
1350 write!(&mut self.operations, "{:04X}", code)
1352 .expect("Writing to string should never fail");
1353 } else if code <= 0x10FFFF {
1354 let code = code - 0x10000;
1356 let high = ((code >> 10) & 0x3FF) + 0xD800;
1357 let low = (code & 0x3FF) + 0xDC00;
1358 write!(&mut self.operations, "{:04X}{:04X}", high, low)
1359 .expect("Writing to string should never fail");
1360 } else {
1361 write!(&mut self.operations, "FFFD")
1363 .expect("Writing to string should never fail");
1364 }
1365 }
1366 self.operations.push_str("> Tj\n");
1367 } else {
1368 self.operations.push('<');
1370 for ch in text.chars() {
1371 if ch as u32 <= 255 {
1372 write!(&mut self.operations, "{:02X}", ch as u8)
1373 .expect("Writing to string should never fail");
1374 } else {
1375 write!(&mut self.operations, "3F")
1376 .expect("Writing to string should never fail");
1377 }
1378 }
1379 self.operations.push_str("> Tj\n");
1380 }
1381
1382 self.operations.push_str("ET\n");
1384 Ok(self)
1385 }
1386
1387 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1389 pub fn draw_text_unicode(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1390 self.operations.push_str("BT\n");
1392
1393 if let Some(font_name) = &self.current_font_name {
1395 writeln!(
1396 &mut self.operations,
1397 "/{} {} Tf",
1398 font_name, self.current_font_size
1399 )
1400 .expect("Writing to string should never fail");
1401 } else {
1402 writeln!(
1404 &mut self.operations,
1405 "/Helvetica {} Tf",
1406 self.current_font_size
1407 )
1408 .expect("Writing to string should never fail");
1409 }
1410
1411 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1413 .expect("Writing to string should never fail");
1414
1415 self.operations.push('<');
1417 let mut utf16_buffer = [0u16; 2];
1418 for ch in text.chars() {
1419 let encoded = ch.encode_utf16(&mut utf16_buffer);
1420 for unit in encoded {
1421 write!(&mut self.operations, "{:04X}", unit)
1423 .expect("Writing to string should never fail");
1424 }
1425 }
1426 self.operations.push_str("> Tj\n");
1427
1428 self.operations.push_str("ET\n");
1430
1431 Ok(self)
1432 }
1433
1434 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1436 if self.used_characters.is_empty() {
1437 None
1438 } else {
1439 Some(self.used_characters.clone())
1440 }
1441 }
1442}
1443
1444#[cfg(test)]
1445mod tests {
1446 use super::*;
1447
1448 #[test]
1449 fn test_graphics_context_new() {
1450 let ctx = GraphicsContext::new();
1451 assert_eq!(ctx.fill_color(), Color::black());
1452 assert_eq!(ctx.stroke_color(), Color::black());
1453 assert_eq!(ctx.line_width(), 1.0);
1454 assert_eq!(ctx.fill_opacity(), 1.0);
1455 assert_eq!(ctx.stroke_opacity(), 1.0);
1456 assert!(ctx.operations().is_empty());
1457 }
1458
1459 #[test]
1460 fn test_graphics_context_default() {
1461 let ctx = GraphicsContext::default();
1462 assert_eq!(ctx.fill_color(), Color::black());
1463 assert_eq!(ctx.stroke_color(), Color::black());
1464 assert_eq!(ctx.line_width(), 1.0);
1465 }
1466
1467 #[test]
1468 fn test_move_to() {
1469 let mut ctx = GraphicsContext::new();
1470 ctx.move_to(10.0, 20.0);
1471 assert!(ctx.operations().contains("10.00 20.00 m\n"));
1472 }
1473
1474 #[test]
1475 fn test_line_to() {
1476 let mut ctx = GraphicsContext::new();
1477 ctx.line_to(30.0, 40.0);
1478 assert!(ctx.operations().contains("30.00 40.00 l\n"));
1479 }
1480
1481 #[test]
1482 fn test_curve_to() {
1483 let mut ctx = GraphicsContext::new();
1484 ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
1485 assert!(ctx
1486 .operations()
1487 .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
1488 }
1489
1490 #[test]
1491 fn test_rect() {
1492 let mut ctx = GraphicsContext::new();
1493 ctx.rect(10.0, 20.0, 100.0, 50.0);
1494 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1495 }
1496
1497 #[test]
1498 fn test_rectangle_alias() {
1499 let mut ctx = GraphicsContext::new();
1500 ctx.rectangle(10.0, 20.0, 100.0, 50.0);
1501 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1502 }
1503
1504 #[test]
1505 fn test_circle() {
1506 let mut ctx = GraphicsContext::new();
1507 ctx.circle(50.0, 50.0, 25.0);
1508
1509 let ops = ctx.operations();
1510 assert!(ops.contains("75.00 50.00 m\n"));
1512 assert!(ops.contains(" c\n"));
1514 assert!(ops.contains("h\n"));
1516 }
1517
1518 #[test]
1519 fn test_close_path() {
1520 let mut ctx = GraphicsContext::new();
1521 ctx.close_path();
1522 assert!(ctx.operations().contains("h\n"));
1523 }
1524
1525 #[test]
1526 fn test_stroke() {
1527 let mut ctx = GraphicsContext::new();
1528 ctx.set_stroke_color(Color::red());
1529 ctx.rect(0.0, 0.0, 10.0, 10.0);
1530 ctx.stroke();
1531
1532 let ops = ctx.operations();
1533 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1534 assert!(ops.contains("S\n"));
1535 }
1536
1537 #[test]
1538 fn test_fill() {
1539 let mut ctx = GraphicsContext::new();
1540 ctx.set_fill_color(Color::blue());
1541 ctx.rect(0.0, 0.0, 10.0, 10.0);
1542 ctx.fill();
1543
1544 let ops = ctx.operations();
1545 assert!(ops.contains("0.000 0.000 1.000 rg\n"));
1546 assert!(ops.contains("f\n"));
1547 }
1548
1549 #[test]
1550 fn test_fill_stroke() {
1551 let mut ctx = GraphicsContext::new();
1552 ctx.set_fill_color(Color::green());
1553 ctx.set_stroke_color(Color::red());
1554 ctx.rect(0.0, 0.0, 10.0, 10.0);
1555 ctx.fill_stroke();
1556
1557 let ops = ctx.operations();
1558 assert!(ops.contains("0.000 1.000 0.000 rg\n"));
1559 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1560 assert!(ops.contains("B\n"));
1561 }
1562
1563 #[test]
1564 fn test_set_stroke_color() {
1565 let mut ctx = GraphicsContext::new();
1566 ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
1567 assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
1568 }
1569
1570 #[test]
1571 fn test_set_fill_color() {
1572 let mut ctx = GraphicsContext::new();
1573 ctx.set_fill_color(Color::gray(0.5));
1574 assert_eq!(ctx.fill_color(), Color::Gray(0.5));
1575 }
1576
1577 #[test]
1578 fn test_set_line_width() {
1579 let mut ctx = GraphicsContext::new();
1580 ctx.set_line_width(2.5);
1581 assert_eq!(ctx.line_width(), 2.5);
1582 assert!(ctx.operations().contains("2.50 w\n"));
1583 }
1584
1585 #[test]
1586 fn test_set_line_cap() {
1587 let mut ctx = GraphicsContext::new();
1588 ctx.set_line_cap(LineCap::Round);
1589 assert!(ctx.operations().contains("1 J\n"));
1590
1591 ctx.set_line_cap(LineCap::Butt);
1592 assert!(ctx.operations().contains("0 J\n"));
1593
1594 ctx.set_line_cap(LineCap::Square);
1595 assert!(ctx.operations().contains("2 J\n"));
1596 }
1597
1598 #[test]
1599 fn test_set_line_join() {
1600 let mut ctx = GraphicsContext::new();
1601 ctx.set_line_join(LineJoin::Round);
1602 assert!(ctx.operations().contains("1 j\n"));
1603
1604 ctx.set_line_join(LineJoin::Miter);
1605 assert!(ctx.operations().contains("0 j\n"));
1606
1607 ctx.set_line_join(LineJoin::Bevel);
1608 assert!(ctx.operations().contains("2 j\n"));
1609 }
1610
1611 #[test]
1612 fn test_save_restore_state() {
1613 let mut ctx = GraphicsContext::new();
1614 ctx.save_state();
1615 assert!(ctx.operations().contains("q\n"));
1616
1617 ctx.restore_state();
1618 assert!(ctx.operations().contains("Q\n"));
1619 }
1620
1621 #[test]
1622 fn test_translate() {
1623 let mut ctx = GraphicsContext::new();
1624 ctx.translate(50.0, 100.0);
1625 assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
1626 }
1627
1628 #[test]
1629 fn test_scale() {
1630 let mut ctx = GraphicsContext::new();
1631 ctx.scale(2.0, 3.0);
1632 assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
1633 }
1634
1635 #[test]
1636 fn test_rotate() {
1637 let mut ctx = GraphicsContext::new();
1638 let angle = std::f64::consts::PI / 4.0; ctx.rotate(angle);
1640
1641 let ops = ctx.operations();
1642 assert!(ops.contains(" cm\n"));
1643 assert!(ops.contains("0.707107")); }
1646
1647 #[test]
1648 fn test_transform() {
1649 let mut ctx = GraphicsContext::new();
1650 ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
1651 assert!(ctx
1652 .operations()
1653 .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
1654 }
1655
1656 #[test]
1657 fn test_draw_image() {
1658 let mut ctx = GraphicsContext::new();
1659 ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
1660
1661 let ops = ctx.operations();
1662 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")); }
1667
1668 #[test]
1669 fn test_gray_color_operations() {
1670 let mut ctx = GraphicsContext::new();
1671 ctx.set_stroke_color(Color::gray(0.5));
1672 ctx.set_fill_color(Color::gray(0.7));
1673 ctx.stroke();
1674 ctx.fill();
1675
1676 let ops = ctx.operations();
1677 assert!(ops.contains("0.500 G\n")); assert!(ops.contains("0.700 g\n")); }
1680
1681 #[test]
1682 fn test_cmyk_color_operations() {
1683 let mut ctx = GraphicsContext::new();
1684 ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
1685 ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
1686 ctx.stroke();
1687 ctx.fill();
1688
1689 let ops = ctx.operations();
1690 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")); }
1693
1694 #[test]
1695 fn test_method_chaining() {
1696 let mut ctx = GraphicsContext::new();
1697 ctx.move_to(0.0, 0.0)
1698 .line_to(10.0, 0.0)
1699 .line_to(10.0, 10.0)
1700 .line_to(0.0, 10.0)
1701 .close_path()
1702 .set_fill_color(Color::red())
1703 .fill();
1704
1705 let ops = ctx.operations();
1706 assert!(ops.contains("0.00 0.00 m\n"));
1707 assert!(ops.contains("10.00 0.00 l\n"));
1708 assert!(ops.contains("10.00 10.00 l\n"));
1709 assert!(ops.contains("0.00 10.00 l\n"));
1710 assert!(ops.contains("h\n"));
1711 assert!(ops.contains("f\n"));
1712 }
1713
1714 #[test]
1715 fn test_generate_operations() {
1716 let mut ctx = GraphicsContext::new();
1717 ctx.rect(0.0, 0.0, 10.0, 10.0);
1718
1719 let result = ctx.generate_operations();
1720 assert!(result.is_ok());
1721 let bytes = result.expect("Writing to string should never fail");
1722 let ops_string = String::from_utf8(bytes).expect("Writing to string should never fail");
1723 assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
1724 }
1725
1726 #[test]
1727 fn test_clear_operations() {
1728 let mut ctx = GraphicsContext::new();
1729 ctx.rect(0.0, 0.0, 10.0, 10.0);
1730 assert!(!ctx.operations().is_empty());
1731
1732 ctx.clear();
1733 assert!(ctx.operations().is_empty());
1734 }
1735
1736 #[test]
1737 fn test_complex_path() {
1738 let mut ctx = GraphicsContext::new();
1739 ctx.save_state()
1740 .translate(100.0, 100.0)
1741 .rotate(std::f64::consts::PI / 6.0)
1742 .scale(2.0, 2.0)
1743 .set_line_width(2.0)
1744 .set_stroke_color(Color::blue())
1745 .move_to(0.0, 0.0)
1746 .line_to(50.0, 0.0)
1747 .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
1748 .close_path()
1749 .stroke()
1750 .restore_state();
1751
1752 let ops = ctx.operations();
1753 assert!(ops.contains("q\n"));
1754 assert!(ops.contains("cm\n"));
1755 assert!(ops.contains("2.00 w\n"));
1756 assert!(ops.contains("0.000 0.000 1.000 RG\n"));
1757 assert!(ops.contains("S\n"));
1758 assert!(ops.contains("Q\n"));
1759 }
1760
1761 #[test]
1762 fn test_graphics_context_clone() {
1763 let mut ctx = GraphicsContext::new();
1764 ctx.set_fill_color(Color::red());
1765 ctx.set_stroke_color(Color::blue());
1766 ctx.set_line_width(3.0);
1767 ctx.set_opacity(0.5);
1768 ctx.rect(0.0, 0.0, 10.0, 10.0);
1769
1770 let ctx_clone = ctx.clone();
1771 assert_eq!(ctx_clone.fill_color(), Color::red());
1772 assert_eq!(ctx_clone.stroke_color(), Color::blue());
1773 assert_eq!(ctx_clone.line_width(), 3.0);
1774 assert_eq!(ctx_clone.fill_opacity(), 0.5);
1775 assert_eq!(ctx_clone.stroke_opacity(), 0.5);
1776 assert_eq!(ctx_clone.operations(), ctx.operations());
1777 }
1778
1779 #[test]
1780 fn test_set_opacity() {
1781 let mut ctx = GraphicsContext::new();
1782
1783 ctx.set_opacity(0.5);
1785 assert_eq!(ctx.fill_opacity(), 0.5);
1786 assert_eq!(ctx.stroke_opacity(), 0.5);
1787
1788 ctx.set_opacity(1.5);
1790 assert_eq!(ctx.fill_opacity(), 1.0);
1791 assert_eq!(ctx.stroke_opacity(), 1.0);
1792
1793 ctx.set_opacity(-0.5);
1794 assert_eq!(ctx.fill_opacity(), 0.0);
1795 assert_eq!(ctx.stroke_opacity(), 0.0);
1796 }
1797
1798 #[test]
1799 fn test_set_fill_opacity() {
1800 let mut ctx = GraphicsContext::new();
1801
1802 ctx.set_fill_opacity(0.3);
1803 assert_eq!(ctx.fill_opacity(), 0.3);
1804 assert_eq!(ctx.stroke_opacity(), 1.0); ctx.set_fill_opacity(2.0);
1808 assert_eq!(ctx.fill_opacity(), 1.0);
1809 }
1810
1811 #[test]
1812 fn test_set_stroke_opacity() {
1813 let mut ctx = GraphicsContext::new();
1814
1815 ctx.set_stroke_opacity(0.7);
1816 assert_eq!(ctx.stroke_opacity(), 0.7);
1817 assert_eq!(ctx.fill_opacity(), 1.0); ctx.set_stroke_opacity(-1.0);
1821 assert_eq!(ctx.stroke_opacity(), 0.0);
1822 }
1823
1824 #[test]
1825 fn test_uses_transparency() {
1826 let mut ctx = GraphicsContext::new();
1827
1828 assert!(!ctx.uses_transparency());
1830
1831 ctx.set_fill_opacity(0.5);
1833 assert!(ctx.uses_transparency());
1834
1835 ctx.set_fill_opacity(1.0);
1837 assert!(!ctx.uses_transparency());
1838 ctx.set_stroke_opacity(0.8);
1839 assert!(ctx.uses_transparency());
1840
1841 ctx.set_fill_opacity(0.5);
1843 assert!(ctx.uses_transparency());
1844 }
1845
1846 #[test]
1847 fn test_generate_graphics_state_dict() {
1848 let mut ctx = GraphicsContext::new();
1849
1850 assert_eq!(ctx.generate_graphics_state_dict(), None);
1852
1853 ctx.set_fill_opacity(0.5);
1855 let dict = ctx
1856 .generate_graphics_state_dict()
1857 .expect("Writing to string should never fail");
1858 assert!(dict.contains("/Type /ExtGState"));
1859 assert!(dict.contains("/ca 0.500"));
1860 assert!(!dict.contains("/CA"));
1861
1862 ctx.set_fill_opacity(1.0);
1864 ctx.set_stroke_opacity(0.75);
1865 let dict = ctx
1866 .generate_graphics_state_dict()
1867 .expect("Writing to string should never fail");
1868 assert!(dict.contains("/Type /ExtGState"));
1869 assert!(dict.contains("/CA 0.750"));
1870 assert!(!dict.contains("/ca"));
1871
1872 ctx.set_fill_opacity(0.25);
1874 let dict = ctx
1875 .generate_graphics_state_dict()
1876 .expect("Writing to string should never fail");
1877 assert!(dict.contains("/Type /ExtGState"));
1878 assert!(dict.contains("/ca 0.250"));
1879 assert!(dict.contains("/CA 0.750"));
1880 }
1881
1882 #[test]
1883 fn test_opacity_with_graphics_operations() {
1884 let mut ctx = GraphicsContext::new();
1885
1886 ctx.set_fill_color(Color::red())
1887 .set_opacity(0.5)
1888 .rect(10.0, 10.0, 100.0, 100.0)
1889 .fill();
1890
1891 assert_eq!(ctx.fill_opacity(), 0.5);
1892 assert_eq!(ctx.stroke_opacity(), 0.5);
1893
1894 let ops = ctx.operations();
1895 assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
1896 assert!(ops.contains("1.000 0.000 0.000 rg")); assert!(ops.contains("f")); }
1899
1900 #[test]
1901 fn test_begin_end_text() {
1902 let mut ctx = GraphicsContext::new();
1903 ctx.begin_text();
1904 assert!(ctx.operations().contains("BT\n"));
1905
1906 ctx.end_text();
1907 assert!(ctx.operations().contains("ET\n"));
1908 }
1909
1910 #[test]
1911 fn test_set_font() {
1912 let mut ctx = GraphicsContext::new();
1913 ctx.set_font(Font::Helvetica, 12.0);
1914 assert!(ctx.operations().contains("/Helvetica 12 Tf\n"));
1915
1916 ctx.set_font(Font::TimesBold, 14.5);
1917 assert!(ctx.operations().contains("/Times-Bold 14.5 Tf\n"));
1918 }
1919
1920 #[test]
1921 fn test_set_text_position() {
1922 let mut ctx = GraphicsContext::new();
1923 ctx.set_text_position(100.0, 200.0);
1924 assert!(ctx.operations().contains("100.00 200.00 Td\n"));
1925 }
1926
1927 #[test]
1928 fn test_show_text() {
1929 let mut ctx = GraphicsContext::new();
1930 ctx.show_text("Hello World")
1931 .expect("Writing to string should never fail");
1932 assert!(ctx.operations().contains("(Hello World) Tj\n"));
1933 }
1934
1935 #[test]
1936 fn test_show_text_with_escaping() {
1937 let mut ctx = GraphicsContext::new();
1938 ctx.show_text("Test (parentheses)")
1939 .expect("Writing to string should never fail");
1940 assert!(ctx.operations().contains("(Test \\(parentheses\\)) Tj\n"));
1941
1942 ctx.clear();
1943 ctx.show_text("Back\\slash")
1944 .expect("Writing to string should never fail");
1945 assert!(ctx.operations().contains("(Back\\\\slash) Tj\n"));
1946
1947 ctx.clear();
1948 ctx.show_text("Line\nBreak")
1949 .expect("Writing to string should never fail");
1950 assert!(ctx.operations().contains("(Line\\nBreak) Tj\n"));
1951 }
1952
1953 #[test]
1954 fn test_text_operations_chaining() {
1955 let mut ctx = GraphicsContext::new();
1956 ctx.begin_text()
1957 .set_font(Font::Courier, 10.0)
1958 .set_text_position(50.0, 100.0)
1959 .show_text("Test")
1960 .unwrap()
1961 .end_text();
1962
1963 let ops = ctx.operations();
1964 assert!(ops.contains("BT\n"));
1965 assert!(ops.contains("/Courier 10 Tf\n"));
1966 assert!(ops.contains("50.00 100.00 Td\n"));
1967 assert!(ops.contains("(Test) Tj\n"));
1968 assert!(ops.contains("ET\n"));
1969 }
1970
1971 #[test]
1972 fn test_clip() {
1973 let mut ctx = GraphicsContext::new();
1974 ctx.clip();
1975 assert!(ctx.operations().contains("W\n"));
1976 }
1977
1978 #[test]
1979 fn test_clip_even_odd() {
1980 let mut ctx = GraphicsContext::new();
1981 ctx.clip_even_odd();
1982 assert!(ctx.operations().contains("W*\n"));
1983 }
1984
1985 #[test]
1986 fn test_clipping_with_path() {
1987 let mut ctx = GraphicsContext::new();
1988
1989 ctx.rect(10.0, 10.0, 100.0, 50.0).clip();
1991
1992 let ops = ctx.operations();
1993 assert!(ops.contains("10.00 10.00 100.00 50.00 re\n"));
1994 assert!(ops.contains("W\n"));
1995 }
1996
1997 #[test]
1998 fn test_clipping_even_odd_with_path() {
1999 let mut ctx = GraphicsContext::new();
2000
2001 ctx.move_to(0.0, 0.0)
2003 .line_to(100.0, 0.0)
2004 .line_to(100.0, 100.0)
2005 .line_to(0.0, 100.0)
2006 .close_path()
2007 .clip_even_odd();
2008
2009 let ops = ctx.operations();
2010 assert!(ops.contains("0.00 0.00 m\n"));
2011 assert!(ops.contains("100.00 0.00 l\n"));
2012 assert!(ops.contains("100.00 100.00 l\n"));
2013 assert!(ops.contains("0.00 100.00 l\n"));
2014 assert!(ops.contains("h\n"));
2015 assert!(ops.contains("W*\n"));
2016 }
2017
2018 #[test]
2019 fn test_clipping_chaining() {
2020 let mut ctx = GraphicsContext::new();
2021
2022 ctx.save_state()
2024 .rect(20.0, 20.0, 60.0, 60.0)
2025 .clip()
2026 .set_fill_color(Color::red())
2027 .rect(0.0, 0.0, 100.0, 100.0)
2028 .fill()
2029 .restore_state();
2030
2031 let ops = ctx.operations();
2032 assert!(ops.contains("q\n"));
2033 assert!(ops.contains("20.00 20.00 60.00 60.00 re\n"));
2034 assert!(ops.contains("W\n"));
2035 assert!(ops.contains("1.000 0.000 0.000 rg\n"));
2036 assert!(ops.contains("0.00 0.00 100.00 100.00 re\n"));
2037 assert!(ops.contains("f\n"));
2038 assert!(ops.contains("Q\n"));
2039 }
2040
2041 #[test]
2042 fn test_multiple_clipping_regions() {
2043 let mut ctx = GraphicsContext::new();
2044
2045 ctx.save_state()
2047 .rect(0.0, 0.0, 200.0, 200.0)
2048 .clip()
2049 .save_state()
2050 .circle(100.0, 100.0, 50.0)
2051 .clip_even_odd()
2052 .set_fill_color(Color::blue())
2053 .rect(50.0, 50.0, 100.0, 100.0)
2054 .fill()
2055 .restore_state()
2056 .restore_state();
2057
2058 let ops = ctx.operations();
2059 let q_count = ops.matches("q\n").count();
2061 let q_restore_count = ops.matches("Q\n").count();
2062 assert_eq!(q_count, 2);
2063 assert_eq!(q_restore_count, 2);
2064
2065 assert!(ops.contains("W\n"));
2067 assert!(ops.contains("W*\n"));
2068 }
2069
2070 #[test]
2073 fn test_move_to_and_line_to() {
2074 let mut ctx = GraphicsContext::new();
2075 ctx.move_to(100.0, 200.0).line_to(300.0, 400.0).stroke();
2076
2077 let ops = ctx
2078 .generate_operations()
2079 .expect("Writing to string should never fail");
2080 let ops_str = String::from_utf8_lossy(&ops);
2081 assert!(ops_str.contains("100.00 200.00 m"));
2082 assert!(ops_str.contains("300.00 400.00 l"));
2083 assert!(ops_str.contains("S"));
2084 }
2085
2086 #[test]
2087 fn test_bezier_curve() {
2088 let mut ctx = GraphicsContext::new();
2089 ctx.move_to(0.0, 0.0)
2090 .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0)
2091 .stroke();
2092
2093 let ops = ctx
2094 .generate_operations()
2095 .expect("Writing to string should never fail");
2096 let ops_str = String::from_utf8_lossy(&ops);
2097 assert!(ops_str.contains("0.00 0.00 m"));
2098 assert!(ops_str.contains("10.00 20.00 30.00 40.00 50.00 60.00 c"));
2099 assert!(ops_str.contains("S"));
2100 }
2101
2102 #[test]
2103 fn test_circle_path() {
2104 let mut ctx = GraphicsContext::new();
2105 ctx.circle(100.0, 100.0, 50.0).fill();
2106
2107 let ops = ctx
2108 .generate_operations()
2109 .expect("Writing to string should never fail");
2110 let ops_str = String::from_utf8_lossy(&ops);
2111 assert!(ops_str.contains(" c"));
2113 assert!(ops_str.contains("f"));
2114 }
2115
2116 #[test]
2117 fn test_path_closing() {
2118 let mut ctx = GraphicsContext::new();
2119 ctx.move_to(0.0, 0.0)
2120 .line_to(100.0, 0.0)
2121 .line_to(100.0, 100.0)
2122 .close_path()
2123 .stroke();
2124
2125 let ops = ctx
2126 .generate_operations()
2127 .expect("Writing to string should never fail");
2128 let ops_str = String::from_utf8_lossy(&ops);
2129 assert!(ops_str.contains("h")); assert!(ops_str.contains("S"));
2131 }
2132
2133 #[test]
2134 fn test_fill_and_stroke() {
2135 let mut ctx = GraphicsContext::new();
2136 ctx.rect(10.0, 10.0, 50.0, 50.0).fill_stroke();
2137
2138 let ops = ctx
2139 .generate_operations()
2140 .expect("Writing to string should never fail");
2141 let ops_str = String::from_utf8_lossy(&ops);
2142 assert!(ops_str.contains("10.00 10.00 50.00 50.00 re"));
2143 assert!(ops_str.contains("B")); }
2145
2146 #[test]
2147 fn test_color_settings() {
2148 let mut ctx = GraphicsContext::new();
2149 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2150 .set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
2151 .rect(10.0, 10.0, 50.0, 50.0)
2152 .fill_stroke(); assert_eq!(ctx.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2155 assert_eq!(ctx.stroke_color(), Color::rgb(0.0, 1.0, 0.0));
2156
2157 let ops = ctx
2158 .generate_operations()
2159 .expect("Writing to string should never fail");
2160 let ops_str = String::from_utf8_lossy(&ops);
2161 assert!(ops_str.contains("1.000 0.000 0.000 rg")); assert!(ops_str.contains("0.000 1.000 0.000 RG")); }
2164
2165 #[test]
2166 fn test_line_styles() {
2167 let mut ctx = GraphicsContext::new();
2168 ctx.set_line_width(2.5)
2169 .set_line_cap(LineCap::Round)
2170 .set_line_join(LineJoin::Bevel);
2171
2172 assert_eq!(ctx.line_width(), 2.5);
2173
2174 let ops = ctx
2175 .generate_operations()
2176 .expect("Writing to string should never fail");
2177 let ops_str = String::from_utf8_lossy(&ops);
2178 assert!(ops_str.contains("2.50 w")); assert!(ops_str.contains("1 J")); assert!(ops_str.contains("2 j")); }
2182
2183 #[test]
2184 fn test_opacity_settings() {
2185 let mut ctx = GraphicsContext::new();
2186 ctx.set_opacity(0.5);
2187
2188 assert_eq!(ctx.fill_opacity(), 0.5);
2189 assert_eq!(ctx.stroke_opacity(), 0.5);
2190 assert!(ctx.uses_transparency());
2191
2192 ctx.set_fill_opacity(0.7).set_stroke_opacity(0.3);
2193
2194 assert_eq!(ctx.fill_opacity(), 0.7);
2195 assert_eq!(ctx.stroke_opacity(), 0.3);
2196 }
2197
2198 #[test]
2199 fn test_state_save_restore() {
2200 let mut ctx = GraphicsContext::new();
2201 ctx.save_state()
2202 .set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2203 .restore_state();
2204
2205 let ops = ctx
2206 .generate_operations()
2207 .expect("Writing to string should never fail");
2208 let ops_str = String::from_utf8_lossy(&ops);
2209 assert!(ops_str.contains("q")); assert!(ops_str.contains("Q")); }
2212
2213 #[test]
2214 fn test_transformations() {
2215 let mut ctx = GraphicsContext::new();
2216 ctx.translate(100.0, 200.0).scale(2.0, 3.0).rotate(45.0);
2217
2218 let ops = ctx
2219 .generate_operations()
2220 .expect("Writing to string should never fail");
2221 let ops_str = String::from_utf8_lossy(&ops);
2222 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")); }
2226
2227 #[test]
2228 fn test_custom_transform() {
2229 let mut ctx = GraphicsContext::new();
2230 ctx.transform(1.0, 0.5, 0.5, 1.0, 10.0, 20.0);
2231
2232 let ops = ctx
2233 .generate_operations()
2234 .expect("Writing to string should never fail");
2235 let ops_str = String::from_utf8_lossy(&ops);
2236 assert!(ops_str.contains("1.00 0.50 0.50 1.00 10.00 20.00 cm"));
2237 }
2238
2239 #[test]
2240 fn test_rectangle_path() {
2241 let mut ctx = GraphicsContext::new();
2242 ctx.rectangle(25.0, 25.0, 150.0, 100.0).stroke();
2243
2244 let ops = ctx
2245 .generate_operations()
2246 .expect("Writing to string should never fail");
2247 let ops_str = String::from_utf8_lossy(&ops);
2248 assert!(ops_str.contains("25.00 25.00 150.00 100.00 re"));
2249 assert!(ops_str.contains("S"));
2250 }
2251
2252 #[test]
2253 fn test_empty_operations() {
2254 let ctx = GraphicsContext::new();
2255 let ops = ctx
2256 .generate_operations()
2257 .expect("Writing to string should never fail");
2258 assert!(ops.is_empty());
2259 }
2260
2261 #[test]
2262 fn test_complex_path_operations() {
2263 let mut ctx = GraphicsContext::new();
2264 ctx.move_to(50.0, 50.0)
2265 .line_to(100.0, 50.0)
2266 .curve_to(125.0, 50.0, 150.0, 75.0, 150.0, 100.0)
2267 .line_to(150.0, 150.0)
2268 .close_path()
2269 .fill();
2270
2271 let ops = ctx
2272 .generate_operations()
2273 .expect("Writing to string should never fail");
2274 let ops_str = String::from_utf8_lossy(&ops);
2275 assert!(ops_str.contains("50.00 50.00 m"));
2276 assert!(ops_str.contains("100.00 50.00 l"));
2277 assert!(ops_str.contains("125.00 50.00 150.00 75.00 150.00 100.00 c"));
2278 assert!(ops_str.contains("150.00 150.00 l"));
2279 assert!(ops_str.contains("h"));
2280 assert!(ops_str.contains("f"));
2281 }
2282
2283 #[test]
2284 fn test_graphics_state_dict_generation() {
2285 let mut ctx = GraphicsContext::new();
2286
2287 assert!(ctx.generate_graphics_state_dict().is_none());
2289
2290 ctx.set_opacity(0.5);
2292 let dict = ctx.generate_graphics_state_dict();
2293 assert!(dict.is_some());
2294 let dict_str = dict.expect("Writing to string should never fail");
2295 assert!(dict_str.contains("/ca 0.5"));
2296 assert!(dict_str.contains("/CA 0.5"));
2297 }
2298
2299 #[test]
2300 fn test_line_dash_pattern() {
2301 let mut ctx = GraphicsContext::new();
2302 let pattern = LineDashPattern {
2303 array: vec![3.0, 2.0],
2304 phase: 0.0,
2305 };
2306 ctx.set_line_dash_pattern(pattern);
2307
2308 let ops = ctx
2309 .generate_operations()
2310 .expect("Writing to string should never fail");
2311 let ops_str = String::from_utf8_lossy(&ops);
2312 assert!(ops_str.contains("[3.00 2.00] 0.00 d"));
2313 }
2314
2315 #[test]
2316 fn test_miter_limit_setting() {
2317 let mut ctx = GraphicsContext::new();
2318 ctx.set_miter_limit(4.0);
2319
2320 let ops = ctx
2321 .generate_operations()
2322 .expect("Writing to string should never fail");
2323 let ops_str = String::from_utf8_lossy(&ops);
2324 assert!(ops_str.contains("4.00 M"));
2325 }
2326
2327 #[test]
2328 fn test_line_cap_styles() {
2329 let mut ctx = GraphicsContext::new();
2330
2331 ctx.set_line_cap(LineCap::Butt);
2332 let ops = ctx
2333 .generate_operations()
2334 .expect("Writing to string should never fail");
2335 let ops_str = String::from_utf8_lossy(&ops);
2336 assert!(ops_str.contains("0 J"));
2337
2338 let mut ctx = GraphicsContext::new();
2339 ctx.set_line_cap(LineCap::Round);
2340 let ops = ctx
2341 .generate_operations()
2342 .expect("Writing to string should never fail");
2343 let ops_str = String::from_utf8_lossy(&ops);
2344 assert!(ops_str.contains("1 J"));
2345
2346 let mut ctx = GraphicsContext::new();
2347 ctx.set_line_cap(LineCap::Square);
2348 let ops = ctx
2349 .generate_operations()
2350 .expect("Writing to string should never fail");
2351 let ops_str = String::from_utf8_lossy(&ops);
2352 assert!(ops_str.contains("2 J"));
2353 }
2354
2355 #[test]
2356 fn test_transparency_groups() {
2357 let mut ctx = GraphicsContext::new();
2358
2359 let group = TransparencyGroup::new()
2361 .with_isolated(true)
2362 .with_opacity(0.5);
2363
2364 ctx.begin_transparency_group(group);
2365 assert!(ctx.in_transparency_group());
2366
2367 ctx.rect(10.0, 10.0, 100.0, 100.0);
2369 ctx.fill();
2370
2371 ctx.end_transparency_group();
2372 assert!(!ctx.in_transparency_group());
2373
2374 let ops = ctx.operations();
2376 assert!(ops.contains("% Begin Transparency Group"));
2377 assert!(ops.contains("% End Transparency Group"));
2378 }
2379
2380 #[test]
2381 fn test_nested_transparency_groups() {
2382 let mut ctx = GraphicsContext::new();
2383
2384 let group1 = TransparencyGroup::isolated().with_opacity(0.8);
2386 ctx.begin_transparency_group(group1);
2387 assert!(ctx.in_transparency_group());
2388
2389 let group2 = TransparencyGroup::knockout().with_blend_mode(BlendMode::Multiply);
2391 ctx.begin_transparency_group(group2);
2392
2393 ctx.circle(50.0, 50.0, 25.0);
2395 ctx.fill();
2396
2397 ctx.end_transparency_group();
2399 assert!(ctx.in_transparency_group()); ctx.end_transparency_group();
2403 assert!(!ctx.in_transparency_group());
2404 }
2405
2406 #[test]
2407 fn test_line_join_styles() {
2408 let mut ctx = GraphicsContext::new();
2409
2410 ctx.set_line_join(LineJoin::Miter);
2411 let ops = ctx
2412 .generate_operations()
2413 .expect("Writing to string should never fail");
2414 let ops_str = String::from_utf8_lossy(&ops);
2415 assert!(ops_str.contains("0 j"));
2416
2417 let mut ctx = GraphicsContext::new();
2418 ctx.set_line_join(LineJoin::Round);
2419 let ops = ctx
2420 .generate_operations()
2421 .expect("Writing to string should never fail");
2422 let ops_str = String::from_utf8_lossy(&ops);
2423 assert!(ops_str.contains("1 j"));
2424
2425 let mut ctx = GraphicsContext::new();
2426 ctx.set_line_join(LineJoin::Bevel);
2427 let ops = ctx
2428 .generate_operations()
2429 .expect("Writing to string should never fail");
2430 let ops_str = String::from_utf8_lossy(&ops);
2431 assert!(ops_str.contains("2 j"));
2432 }
2433
2434 #[test]
2435 fn test_rendering_intent() {
2436 let mut ctx = GraphicsContext::new();
2437
2438 ctx.set_rendering_intent(RenderingIntent::AbsoluteColorimetric);
2439 assert_eq!(
2440 ctx.rendering_intent(),
2441 RenderingIntent::AbsoluteColorimetric
2442 );
2443
2444 ctx.set_rendering_intent(RenderingIntent::Perceptual);
2445 assert_eq!(ctx.rendering_intent(), RenderingIntent::Perceptual);
2446
2447 ctx.set_rendering_intent(RenderingIntent::Saturation);
2448 assert_eq!(ctx.rendering_intent(), RenderingIntent::Saturation);
2449 }
2450
2451 #[test]
2452 fn test_flatness_tolerance() {
2453 let mut ctx = GraphicsContext::new();
2454
2455 ctx.set_flatness(0.5);
2456 assert_eq!(ctx.flatness(), 0.5);
2457
2458 let ops = ctx
2459 .generate_operations()
2460 .expect("Writing to string should never fail");
2461 let ops_str = String::from_utf8_lossy(&ops);
2462 assert!(ops_str.contains("0.50 i"));
2463 }
2464
2465 #[test]
2466 fn test_smoothness_tolerance() {
2467 let mut ctx = GraphicsContext::new();
2468
2469 let _ = ctx.set_smoothness(0.1);
2470 assert_eq!(ctx.smoothness(), 0.1);
2471 }
2472
2473 #[test]
2474 fn test_bezier_curves() {
2475 let mut ctx = GraphicsContext::new();
2476
2477 ctx.move_to(10.0, 10.0);
2479 ctx.curve_to(20.0, 10.0, 30.0, 20.0, 30.0, 30.0);
2480
2481 let ops = ctx
2482 .generate_operations()
2483 .expect("Writing to string should never fail");
2484 let ops_str = String::from_utf8_lossy(&ops);
2485 assert!(ops_str.contains("10.00 10.00 m"));
2486 assert!(ops_str.contains("c")); }
2488
2489 #[test]
2490 fn test_clipping_path() {
2491 let mut ctx = GraphicsContext::new();
2492
2493 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2494 ctx.clip();
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("W"));
2501 }
2502
2503 #[test]
2504 fn test_even_odd_clipping() {
2505 let mut ctx = GraphicsContext::new();
2506
2507 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2508 ctx.clip_even_odd();
2509
2510 let ops = ctx
2511 .generate_operations()
2512 .expect("Writing to string should never fail");
2513 let ops_str = String::from_utf8_lossy(&ops);
2514 assert!(ops_str.contains("W*"));
2515 }
2516
2517 #[test]
2518 fn test_color_creation() {
2519 let gray = Color::gray(0.5);
2521 assert_eq!(gray, Color::Gray(0.5));
2522
2523 let rgb = Color::rgb(0.2, 0.4, 0.6);
2524 assert_eq!(rgb, Color::Rgb(0.2, 0.4, 0.6));
2525
2526 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
2527 assert_eq!(cmyk, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
2528
2529 assert_eq!(Color::black(), Color::Gray(0.0));
2531 assert_eq!(Color::white(), Color::Gray(1.0));
2532 assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
2533 }
2534
2535 #[test]
2536 fn test_extended_graphics_state() {
2537 let ctx = GraphicsContext::new();
2538
2539 let _extgstate = ExtGState::new();
2541
2542 assert!(ctx.generate_operations().is_ok());
2544 }
2545
2546 #[test]
2547 fn test_path_construction_methods() {
2548 let mut ctx = GraphicsContext::new();
2549
2550 ctx.move_to(10.0, 10.0);
2552 ctx.line_to(20.0, 20.0);
2553 ctx.curve_to(30.0, 30.0, 40.0, 40.0, 50.0, 50.0);
2554 ctx.rect(60.0, 60.0, 30.0, 30.0);
2555 ctx.circle(100.0, 100.0, 25.0);
2556 ctx.close_path();
2557
2558 let ops = ctx
2559 .generate_operations()
2560 .expect("Writing to string should never fail");
2561 assert!(!ops.is_empty());
2562 }
2563
2564 #[test]
2565 fn test_graphics_context_clone_advanced() {
2566 let mut ctx = GraphicsContext::new();
2567 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
2568 ctx.set_line_width(5.0);
2569
2570 let cloned = ctx.clone();
2571 assert_eq!(cloned.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2572 assert_eq!(cloned.line_width(), 5.0);
2573 }
2574
2575 #[test]
2576 fn test_basic_drawing_operations() {
2577 let mut ctx = GraphicsContext::new();
2578
2579 ctx.move_to(50.0, 50.0);
2581 ctx.line_to(100.0, 100.0);
2582 ctx.stroke();
2583
2584 let ops = ctx
2585 .generate_operations()
2586 .expect("Writing to string should never fail");
2587 let ops_str = String::from_utf8_lossy(&ops);
2588 assert!(ops_str.contains("m")); assert!(ops_str.contains("l")); assert!(ops_str.contains("S")); }
2592
2593 #[test]
2594 fn test_graphics_state_stack() {
2595 let mut ctx = GraphicsContext::new();
2596
2597 ctx.set_fill_color(Color::black());
2599
2600 ctx.save_state();
2602 ctx.set_fill_color(Color::red());
2603 assert_eq!(ctx.fill_color(), Color::red());
2604
2605 ctx.save_state();
2607 ctx.set_fill_color(Color::blue());
2608 assert_eq!(ctx.fill_color(), Color::blue());
2609
2610 ctx.restore_state();
2612 assert_eq!(ctx.fill_color(), Color::red());
2613
2614 ctx.restore_state();
2616 assert_eq!(ctx.fill_color(), Color::black());
2617 }
2618
2619 #[test]
2620 fn test_word_spacing() {
2621 let mut ctx = GraphicsContext::new();
2622 ctx.set_word_spacing(2.5);
2623
2624 let ops = ctx.generate_operations().unwrap();
2625 let ops_str = String::from_utf8_lossy(&ops);
2626 assert!(ops_str.contains("2.50 Tw"));
2627 }
2628
2629 #[test]
2630 fn test_character_spacing() {
2631 let mut ctx = GraphicsContext::new();
2632 ctx.set_character_spacing(1.0);
2633
2634 let ops = ctx.generate_operations().unwrap();
2635 let ops_str = String::from_utf8_lossy(&ops);
2636 assert!(ops_str.contains("1.00 Tc"));
2637 }
2638
2639 #[test]
2640 fn test_justified_text() {
2641 let mut ctx = GraphicsContext::new();
2642 ctx.begin_text();
2643 ctx.set_text_position(100.0, 200.0);
2644 ctx.show_justified_text("Hello world from PDF", 200.0)
2645 .unwrap();
2646 ctx.end_text();
2647
2648 let ops = ctx.generate_operations().unwrap();
2649 let ops_str = String::from_utf8_lossy(&ops);
2650
2651 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")); }
2660
2661 #[test]
2662 fn test_justified_text_single_word() {
2663 let mut ctx = GraphicsContext::new();
2664 ctx.begin_text();
2665 ctx.show_justified_text("Hello", 200.0).unwrap();
2666 ctx.end_text();
2667
2668 let ops = ctx.generate_operations().unwrap();
2669 let ops_str = String::from_utf8_lossy(&ops);
2670
2671 assert!(ops_str.contains("(Hello) Tj"));
2673 assert_eq!(ops_str.matches("Tw").count(), 0);
2675 }
2676
2677 #[test]
2678 fn test_text_width_estimation() {
2679 let ctx = GraphicsContext::new();
2680 let width = ctx.estimate_text_width_simple("Hello");
2681
2682 assert!(width > 0.0);
2684 assert_eq!(width, 5.0 * 12.0 * 0.6); }
2686
2687 #[test]
2688 fn test_set_alpha_methods() {
2689 let mut ctx = GraphicsContext::new();
2690
2691 assert!(ctx.set_alpha(0.5).is_ok());
2693 assert!(ctx.set_alpha_fill(0.3).is_ok());
2694 assert!(ctx.set_alpha_stroke(0.7).is_ok());
2695
2696 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
2704 .set_alpha(0.5)
2705 .and_then(|c| c.set_alpha_fill(0.3))
2706 .and_then(|c| c.set_alpha_stroke(0.7));
2707 assert!(result.is_ok());
2708 }
2709
2710 #[test]
2711 fn test_alpha_methods_generate_extgstate() {
2712 let mut ctx = GraphicsContext::new();
2713
2714 ctx.set_alpha(0.5).unwrap();
2716
2717 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
2719
2720 let ops = ctx.generate_operations().unwrap();
2721 let ops_str = String::from_utf8_lossy(&ops);
2722
2723 assert!(ops_str.contains("/GS")); assert!(ops_str.contains(" gs\n")); ctx.clear();
2729 ctx.set_alpha_fill(0.3).unwrap();
2730 ctx.set_alpha_stroke(0.8).unwrap();
2731 ctx.rect(20.0, 20.0, 60.0, 60.0).fill_stroke();
2732
2733 let ops2 = ctx.generate_operations().unwrap();
2734 let ops_str2 = String::from_utf8_lossy(&ops2);
2735
2736 assert!(ops_str2.contains("/GS")); assert!(ops_str2.contains(" gs\n")); }
2740
2741 #[test]
2742 fn test_add_command() {
2743 let mut ctx = GraphicsContext::new();
2744
2745 ctx.add_command("1 0 0 1 100 200 cm");
2747 let ops = ctx.operations();
2748 assert!(ops.contains("1 0 0 1 100 200 cm\n"));
2749
2750 ctx.clear();
2752 ctx.add_command("q");
2753 assert_eq!(ctx.operations(), "q\n");
2754
2755 ctx.clear();
2757 ctx.add_command("");
2758 assert_eq!(ctx.operations(), "\n");
2759
2760 ctx.clear();
2762 ctx.add_command("Q\n");
2763 assert_eq!(ctx.operations(), "Q\n\n"); ctx.clear();
2767 ctx.add_command("q");
2768 ctx.add_command("1 0 0 1 50 50 cm");
2769 ctx.add_command("Q");
2770 assert_eq!(ctx.operations(), "q\n1 0 0 1 50 50 cm\nQ\n");
2771 }
2772
2773 #[test]
2774 fn test_get_operations() {
2775 let mut ctx = GraphicsContext::new();
2776 ctx.rect(10.0, 10.0, 50.0, 50.0);
2777 let ops1 = ctx.operations();
2778 let ops2 = ctx.get_operations();
2779 assert_eq!(ops1, ops2);
2780 }
2781
2782 #[test]
2783 fn test_set_line_solid() {
2784 let mut ctx = GraphicsContext::new();
2785 ctx.set_line_dash_pattern(LineDashPattern::new(vec![5.0, 3.0], 0.0));
2786 ctx.set_line_solid();
2787 let ops = ctx.operations();
2788 assert!(ops.contains("[] 0 d\n"));
2789 }
2790
2791 #[test]
2792 fn test_set_custom_font() {
2793 let mut ctx = GraphicsContext::new();
2794 ctx.set_custom_font("CustomFont", 14.0);
2795 assert_eq!(ctx.current_font_name, Some("CustomFont".to_string()));
2796 assert_eq!(ctx.current_font_size, 14.0);
2797 }
2798
2799 #[test]
2800 fn test_set_glyph_mapping() {
2801 let mut ctx = GraphicsContext::new();
2802
2803 assert!(ctx.glyph_mapping.is_none());
2805
2806 let mut mapping = HashMap::new();
2808 mapping.insert(65u32, 1u16); mapping.insert(66u32, 2u16); ctx.set_glyph_mapping(mapping.clone());
2811 assert!(ctx.glyph_mapping.is_some());
2812 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 2);
2813 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), Some(&1));
2814 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&66), Some(&2));
2815
2816 ctx.set_glyph_mapping(HashMap::new());
2818 assert!(ctx.glyph_mapping.is_some());
2819 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 0);
2820
2821 let mut new_mapping = HashMap::new();
2823 new_mapping.insert(67u32, 3u16); ctx.set_glyph_mapping(new_mapping);
2825 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 1);
2826 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&67), Some(&3));
2827 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), None); }
2829
2830 #[test]
2831 fn test_draw_text_basic() {
2832 let mut ctx = GraphicsContext::new();
2833 ctx.set_font(Font::Helvetica, 12.0);
2834
2835 let result = ctx.draw_text("Hello", 100.0, 200.0);
2836 assert!(result.is_ok());
2837
2838 let ops = ctx.operations();
2839 assert!(ops.contains("BT\n"));
2841 assert!(ops.contains("ET\n"));
2842
2843 assert!(ops.contains("/Helvetica"));
2845 assert!(ops.contains("12"));
2846 assert!(ops.contains("Tf\n"));
2847
2848 assert!(ops.contains("100"));
2850 assert!(ops.contains("200"));
2851 assert!(ops.contains("Td\n"));
2852
2853 assert!(ops.contains("(Hello)") || ops.contains("<48656c6c6f>")); }
2856
2857 #[test]
2858 fn test_draw_text_with_special_characters() {
2859 let mut ctx = GraphicsContext::new();
2860 ctx.set_font(Font::Helvetica, 12.0);
2861
2862 let result = ctx.draw_text("Test (with) parens", 50.0, 100.0);
2864 assert!(result.is_ok());
2865
2866 let ops = ctx.operations();
2867 assert!(ops.contains("\\(") || ops.contains("\\)") || ops.contains("<"));
2869 }
2871
2872 #[test]
2873 fn test_draw_text_unicode_detection() {
2874 let mut ctx = GraphicsContext::new();
2875 ctx.set_font(Font::Helvetica, 12.0);
2876
2877 ctx.draw_text("ASCII", 0.0, 0.0).unwrap();
2879 let _ops_ascii = ctx.operations();
2880
2881 ctx.clear();
2882
2883 ctx.set_font(Font::Helvetica, 12.0);
2885 ctx.draw_text("中文", 0.0, 0.0).unwrap();
2886 let ops_unicode = ctx.operations();
2887
2888 assert!(ops_unicode.contains("<") && ops_unicode.contains(">"));
2890 }
2891
2892 #[test]
2893 #[allow(deprecated)]
2894 fn test_draw_text_hex_encoding() {
2895 let mut ctx = GraphicsContext::new();
2896 ctx.set_font(Font::Helvetica, 12.0);
2897 let result = ctx.draw_text_hex("Test", 50.0, 100.0);
2898 assert!(result.is_ok());
2899 let ops = ctx.operations();
2900 assert!(ops.contains("<"));
2901 assert!(ops.contains(">"));
2902 }
2903
2904 #[test]
2905 #[allow(deprecated)]
2906 fn test_draw_text_cid() {
2907 let mut ctx = GraphicsContext::new();
2908 ctx.set_custom_font("CustomCIDFont", 12.0);
2909 let result = ctx.draw_text_cid("Test", 50.0, 100.0);
2910 assert!(result.is_ok());
2911 let ops = ctx.operations();
2912 assert!(ops.contains("BT\n"));
2913 assert!(ops.contains("ET\n"));
2914 }
2915
2916 #[test]
2917 #[allow(deprecated)]
2918 fn test_draw_text_unicode() {
2919 let mut ctx = GraphicsContext::new();
2920 ctx.set_custom_font("UnicodeFont", 12.0);
2921 let result = ctx.draw_text_unicode("Test \u{4E2D}\u{6587}", 50.0, 100.0);
2922 assert!(result.is_ok());
2923 let ops = ctx.operations();
2924 assert!(ops.contains("BT\n"));
2925 assert!(ops.contains("ET\n"));
2926 }
2927
2928 #[test]
2929 fn test_begin_end_transparency_group() {
2930 let mut ctx = GraphicsContext::new();
2931
2932 assert!(!ctx.in_transparency_group());
2934 assert!(ctx.current_transparency_group().is_none());
2935
2936 let group = TransparencyGroup::new();
2938 ctx.begin_transparency_group(group);
2939 assert!(ctx.in_transparency_group());
2940 assert!(ctx.current_transparency_group().is_some());
2941
2942 let ops = ctx.operations();
2944 assert!(ops.contains("% Begin Transparency Group"));
2945
2946 ctx.end_transparency_group();
2948 assert!(!ctx.in_transparency_group());
2949 assert!(ctx.current_transparency_group().is_none());
2950
2951 let ops_after = ctx.operations();
2953 assert!(ops_after.contains("% End Transparency Group"));
2954 }
2955
2956 #[test]
2957 fn test_transparency_group_nesting() {
2958 let mut ctx = GraphicsContext::new();
2959
2960 let group1 = TransparencyGroup::new();
2962 let group2 = TransparencyGroup::new();
2963 let group3 = TransparencyGroup::new();
2964
2965 ctx.begin_transparency_group(group1);
2966 assert_eq!(ctx.transparency_stack.len(), 1);
2967
2968 ctx.begin_transparency_group(group2);
2969 assert_eq!(ctx.transparency_stack.len(), 2);
2970
2971 ctx.begin_transparency_group(group3);
2972 assert_eq!(ctx.transparency_stack.len(), 3);
2973
2974 ctx.end_transparency_group();
2976 assert_eq!(ctx.transparency_stack.len(), 2);
2977
2978 ctx.end_transparency_group();
2979 assert_eq!(ctx.transparency_stack.len(), 1);
2980
2981 ctx.end_transparency_group();
2982 assert_eq!(ctx.transparency_stack.len(), 0);
2983 assert!(!ctx.in_transparency_group());
2984 }
2985
2986 #[test]
2987 fn test_transparency_group_without_begin() {
2988 let mut ctx = GraphicsContext::new();
2989
2990 assert!(!ctx.in_transparency_group());
2992 ctx.end_transparency_group();
2993 assert!(!ctx.in_transparency_group());
2994 }
2995
2996 #[test]
2997 fn test_extgstate_manager_access() {
2998 let ctx = GraphicsContext::new();
2999 let manager = ctx.extgstate_manager();
3000 assert_eq!(manager.count(), 0);
3001 }
3002
3003 #[test]
3004 fn test_extgstate_manager_mut_access() {
3005 let mut ctx = GraphicsContext::new();
3006 let manager = ctx.extgstate_manager_mut();
3007 assert_eq!(manager.count(), 0);
3008 }
3009
3010 #[test]
3011 fn test_has_extgstates() {
3012 let mut ctx = GraphicsContext::new();
3013
3014 assert!(!ctx.has_extgstates());
3016 assert_eq!(ctx.extgstate_manager().count(), 0);
3017
3018 ctx.set_alpha(0.5).unwrap();
3020 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3021 let result = ctx.generate_operations().unwrap();
3022
3023 assert!(ctx.has_extgstates());
3024 assert!(ctx.extgstate_manager().count() > 0);
3025
3026 let output = String::from_utf8_lossy(&result);
3028 assert!(output.contains("/GS")); assert!(output.contains(" gs\n")); }
3031
3032 #[test]
3033 fn test_generate_extgstate_resources() {
3034 let mut ctx = GraphicsContext::new();
3035 ctx.set_alpha(0.5).unwrap();
3036 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3037 ctx.generate_operations().unwrap();
3038
3039 let resources = ctx.generate_extgstate_resources();
3040 assert!(resources.is_ok());
3041 }
3042
3043 #[test]
3044 fn test_apply_extgstate() {
3045 let mut ctx = GraphicsContext::new();
3046
3047 let mut state = ExtGState::new();
3049 state.alpha_fill = Some(0.5);
3050 state.alpha_stroke = Some(0.8);
3051 state.blend_mode = Some(BlendMode::Multiply);
3052
3053 let result = ctx.apply_extgstate(state);
3054 assert!(result.is_ok());
3055
3056 assert!(ctx.has_extgstates());
3058 assert_eq!(ctx.extgstate_manager().count(), 1);
3059
3060 let mut state2 = ExtGState::new();
3062 state2.alpha_fill = Some(0.3);
3063 ctx.apply_extgstate(state2).unwrap();
3064
3065 assert_eq!(ctx.extgstate_manager().count(), 2);
3067 }
3068
3069 #[test]
3070 fn test_with_extgstate() {
3071 let mut ctx = GraphicsContext::new();
3072 let result = ctx.with_extgstate(|mut state| {
3073 state.alpha_fill = Some(0.5);
3074 state.alpha_stroke = Some(0.8);
3075 state
3076 });
3077 assert!(result.is_ok());
3078 }
3079
3080 #[test]
3081 fn test_set_blend_mode() {
3082 let mut ctx = GraphicsContext::new();
3083
3084 let result = ctx.set_blend_mode(BlendMode::Multiply);
3086 assert!(result.is_ok());
3087 assert!(ctx.has_extgstates());
3088
3089 ctx.clear();
3091 ctx.set_blend_mode(BlendMode::Screen).unwrap();
3092 ctx.rect(0.0, 0.0, 10.0, 10.0).fill();
3093 let ops = ctx.generate_operations().unwrap();
3094 let output = String::from_utf8_lossy(&ops);
3095
3096 assert!(output.contains("/GS"));
3098 assert!(output.contains(" gs\n"));
3099 }
3100
3101 #[test]
3102 fn test_render_table() {
3103 let mut ctx = GraphicsContext::new();
3104 let table = Table::with_equal_columns(2, 200.0);
3105 let result = ctx.render_table(&table);
3106 assert!(result.is_ok());
3107 }
3108
3109 #[test]
3110 fn test_render_list() {
3111 let mut ctx = GraphicsContext::new();
3112 use crate::text::{OrderedList, OrderedListStyle};
3113 let ordered = OrderedList::new(OrderedListStyle::Decimal);
3114 let list = ListElement::Ordered(ordered);
3115 let result = ctx.render_list(&list);
3116 assert!(result.is_ok());
3117 }
3118
3119 #[test]
3120 fn test_render_column_layout() {
3121 let mut ctx = GraphicsContext::new();
3122 use crate::text::ColumnContent;
3123 let layout = ColumnLayout::new(2, 100.0, 200.0);
3124 let content = ColumnContent::new("Test content");
3125 let result = ctx.render_column_layout(&layout, &content, 50.0, 50.0, 400.0);
3126 assert!(result.is_ok());
3127 }
3128
3129 #[test]
3130 fn test_clip_ellipse() {
3131 let mut ctx = GraphicsContext::new();
3132
3133 assert!(!ctx.has_clipping());
3135 assert!(ctx.clipping_path().is_none());
3136
3137 let result = ctx.clip_ellipse(100.0, 100.0, 50.0, 30.0);
3139 assert!(result.is_ok());
3140 assert!(ctx.has_clipping());
3141 assert!(ctx.clipping_path().is_some());
3142
3143 let ops = ctx.operations();
3145 assert!(ops.contains("W\n") || ops.contains("W*\n")); ctx.clear_clipping();
3149 assert!(!ctx.has_clipping());
3150 }
3151
3152 #[test]
3153 fn test_clipping_path_access() {
3154 let mut ctx = GraphicsContext::new();
3155
3156 assert!(ctx.clipping_path().is_none());
3158
3159 ctx.clip_rect(10.0, 10.0, 50.0, 50.0).unwrap();
3161 assert!(ctx.clipping_path().is_some());
3162
3163 ctx.clip_circle(100.0, 100.0, 25.0).unwrap();
3165 assert!(ctx.clipping_path().is_some());
3166
3167 ctx.save_state();
3169 ctx.clear_clipping();
3170 assert!(!ctx.has_clipping());
3171
3172 ctx.restore_state();
3173 assert!(ctx.has_clipping());
3175 }
3176
3177 #[test]
3180 fn test_edge_case_move_to_negative() {
3181 let mut ctx = GraphicsContext::new();
3182 ctx.move_to(-100.5, -200.25);
3183 assert!(ctx.operations().contains("-100.50 -200.25 m\n"));
3184 }
3185
3186 #[test]
3187 fn test_edge_case_opacity_out_of_range() {
3188 let mut ctx = GraphicsContext::new();
3189
3190 let _ = ctx.set_opacity(2.5);
3192 assert_eq!(ctx.fill_opacity(), 1.0);
3193
3194 let _ = ctx.set_opacity(-0.5);
3196 assert_eq!(ctx.fill_opacity(), 0.0);
3197 }
3198
3199 #[test]
3200 fn test_edge_case_line_width_extremes() {
3201 let mut ctx = GraphicsContext::new();
3202
3203 ctx.set_line_width(0.0);
3204 assert_eq!(ctx.line_width(), 0.0);
3205
3206 ctx.set_line_width(10000.0);
3207 assert_eq!(ctx.line_width(), 10000.0);
3208 }
3209
3210 #[test]
3213 fn test_interaction_transparency_plus_clipping() {
3214 let mut ctx = GraphicsContext::new();
3215
3216 ctx.set_alpha(0.5).unwrap();
3217 ctx.clip_rect(10.0, 10.0, 100.0, 100.0).unwrap();
3218 ctx.rect(20.0, 20.0, 80.0, 80.0).fill();
3219
3220 let ops = ctx.generate_operations().unwrap();
3221 let output = String::from_utf8_lossy(&ops);
3222
3223 assert!(output.contains("W\n") || output.contains("W*\n"));
3225 assert!(output.contains("/GS"));
3226 }
3227
3228 #[test]
3229 fn test_interaction_extgstate_plus_text() {
3230 let mut ctx = GraphicsContext::new();
3231
3232 let mut state = ExtGState::new();
3233 state.alpha_fill = Some(0.7);
3234 ctx.apply_extgstate(state).unwrap();
3235
3236 ctx.set_font(Font::Helvetica, 14.0);
3237 ctx.draw_text("Test", 100.0, 200.0).unwrap();
3238
3239 let ops = ctx.generate_operations().unwrap();
3240 let output = String::from_utf8_lossy(&ops);
3241
3242 assert!(output.contains("/GS"));
3243 assert!(output.contains("BT\n"));
3244 }
3245
3246 #[test]
3247 fn test_interaction_chained_transformations() {
3248 let mut ctx = GraphicsContext::new();
3249
3250 ctx.translate(50.0, 100.0);
3251 ctx.rotate(45.0);
3252 ctx.scale(2.0, 2.0);
3253
3254 let ops = ctx.operations();
3255 assert_eq!(ops.matches("cm\n").count(), 3);
3256 }
3257
3258 #[test]
3261 fn test_e2e_complete_page_with_header() {
3262 use crate::{Document, Page};
3263
3264 let mut doc = Document::new();
3265 let mut page = Page::a4();
3266 let ctx = page.graphics();
3267
3268 ctx.save_state();
3270 let _ = ctx.set_fill_opacity(0.3);
3271 ctx.set_fill_color(Color::rgb(200.0, 200.0, 255.0));
3272 ctx.rect(0.0, 750.0, 595.0, 42.0).fill();
3273 ctx.restore_state();
3274
3275 ctx.save_state();
3277 ctx.clip_rect(50.0, 50.0, 495.0, 692.0).unwrap();
3278 ctx.rect(60.0, 60.0, 100.0, 100.0).fill();
3279 ctx.restore_state();
3280
3281 let ops = ctx.generate_operations().unwrap();
3282 let output = String::from_utf8_lossy(&ops);
3283
3284 assert!(output.contains("q\n"));
3285 assert!(output.contains("Q\n"));
3286 assert!(output.contains("f\n"));
3287
3288 doc.add_page(page);
3289 assert!(doc.to_bytes().unwrap().len() > 0);
3290 }
3291
3292 #[test]
3293 fn test_e2e_watermark_workflow() {
3294 let mut ctx = GraphicsContext::new();
3295
3296 ctx.save_state();
3297 let _ = ctx.set_fill_opacity(0.2);
3298 ctx.translate(300.0, 400.0);
3299 ctx.rotate(45.0);
3300 ctx.set_font(Font::HelveticaBold, 72.0);
3301 ctx.draw_text("DRAFT", 0.0, 0.0).unwrap();
3302 ctx.restore_state();
3303
3304 let ops = ctx.generate_operations().unwrap();
3305 let output = String::from_utf8_lossy(&ops);
3306
3307 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")); }
3314}