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)]
64struct GraphicsState {
65 fill_color: Color,
66 stroke_color: Color,
67 font_name: Option<Arc<str>>,
68 font_size: f64,
69 is_custom_font: bool,
70}
71
72#[derive(Clone)]
73pub struct GraphicsContext {
74 operations: String,
75 current_color: Color,
76 stroke_color: Color,
77 line_width: f64,
78 fill_opacity: f64,
79 stroke_opacity: f64,
80 extgstate_manager: ExtGStateManager,
82 pending_extgstate: Option<ExtGState>,
83 current_dash_pattern: Option<LineDashPattern>,
84 current_miter_limit: f64,
85 current_line_cap: LineCap,
86 current_line_join: LineJoin,
87 current_rendering_intent: RenderingIntent,
88 current_flatness: f64,
89 current_smoothness: f64,
90 clipping_region: ClippingRegion,
92 font_manager: Option<Arc<FontManager>>,
94 state_stack: Vec<GraphicsState>,
96 current_font_name: Option<Arc<str>>,
97 current_font_size: f64,
98 is_custom_font: bool,
100 used_characters: HashSet<char>,
102 glyph_mapping: Option<HashMap<u32, u16>>,
104 transparency_stack: Vec<TransparencyGroupState>,
106}
107
108fn encode_char_as_cid(ch: char, buf: &mut String) {
112 let code = ch as u32;
113 if code <= 0xFFFF {
114 write!(buf, "{:04X}", code).expect("Writing to string should never fail");
115 } else {
116 let adjusted = code - 0x10000;
118 let high = ((adjusted >> 10) & 0x3FF) + 0xD800;
119 let low = (adjusted & 0x3FF) + 0xDC00;
120 write!(buf, "{:04X}{:04X}", high, low).expect("Writing to string should never fail");
121 }
122}
123
124impl Default for GraphicsContext {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130impl GraphicsContext {
131 pub fn new() -> Self {
132 Self {
133 operations: String::new(),
134 current_color: Color::black(),
135 stroke_color: Color::black(),
136 line_width: 1.0,
137 fill_opacity: 1.0,
138 stroke_opacity: 1.0,
139 extgstate_manager: ExtGStateManager::new(),
141 pending_extgstate: None,
142 current_dash_pattern: None,
143 current_miter_limit: 10.0,
144 current_line_cap: LineCap::Butt,
145 current_line_join: LineJoin::Miter,
146 current_rendering_intent: RenderingIntent::RelativeColorimetric,
147 current_flatness: 1.0,
148 current_smoothness: 0.0,
149 clipping_region: ClippingRegion::new(),
151 font_manager: None,
153 state_stack: Vec::new(),
154 current_font_name: None,
155 current_font_size: 12.0,
156 is_custom_font: false,
157 used_characters: HashSet::new(),
158 glyph_mapping: None,
159 transparency_stack: Vec::new(),
160 }
161 }
162
163 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
164 writeln!(&mut self.operations, "{x:.2} {y:.2} m")
165 .expect("Writing to string should never fail");
166 self
167 }
168
169 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
170 writeln!(&mut self.operations, "{x:.2} {y:.2} l")
171 .expect("Writing to string should never fail");
172 self
173 }
174
175 pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
176 writeln!(
177 &mut self.operations,
178 "{x1:.2} {y1:.2} {x2:.2} {y2:.2} {x3:.2} {y3:.2} c"
179 )
180 .expect("Writing to string should never fail");
181 self
182 }
183
184 pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
185 writeln!(
186 &mut self.operations,
187 "{x:.2} {y:.2} {width:.2} {height:.2} re"
188 )
189 .expect("Writing to string should never fail");
190 self
191 }
192
193 pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
194 let k = 0.552284749831;
195 let r = radius;
196
197 self.move_to(cx + r, cy);
198 self.curve_to(cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r);
199 self.curve_to(cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy);
200 self.curve_to(cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r);
201 self.curve_to(cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy);
202 self.close_path()
203 }
204
205 pub fn close_path(&mut self) -> &mut Self {
206 self.operations.push_str("h\n");
207 self
208 }
209
210 pub fn stroke(&mut self) -> &mut Self {
211 self.apply_pending_extgstate().unwrap_or_default();
212 self.apply_stroke_color();
213 self.operations.push_str("S\n");
214 self
215 }
216
217 pub fn fill(&mut self) -> &mut Self {
218 self.apply_pending_extgstate().unwrap_or_default();
219 self.apply_fill_color();
220 self.operations.push_str("f\n");
221 self
222 }
223
224 pub fn fill_stroke(&mut self) -> &mut Self {
225 self.apply_pending_extgstate().unwrap_or_default();
226 self.apply_fill_color();
227 self.apply_stroke_color();
228 self.operations.push_str("B\n");
229 self
230 }
231
232 pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
233 self.stroke_color = color;
234 self
235 }
236
237 pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
238 self.current_color = color;
239 self
240 }
241
242 pub fn set_fill_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
244 let cs_name = match &color {
246 CalibratedColor::Gray(_, _) => "CalGray1",
247 CalibratedColor::Rgb(_, _) => "CalRGB1",
248 };
249
250 writeln!(&mut self.operations, "/{} cs", cs_name)
252 .expect("Writing to string should never fail");
253
254 let values = color.values();
256 for value in &values {
257 write!(&mut self.operations, "{:.4} ", value)
258 .expect("Writing to string should never fail");
259 }
260 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
261
262 self
263 }
264
265 pub fn set_stroke_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
267 let cs_name = match &color {
269 CalibratedColor::Gray(_, _) => "CalGray1",
270 CalibratedColor::Rgb(_, _) => "CalRGB1",
271 };
272
273 writeln!(&mut self.operations, "/{} CS", cs_name)
275 .expect("Writing to string should never fail");
276
277 let values = color.values();
279 for value in &values {
280 write!(&mut self.operations, "{:.4} ", value)
281 .expect("Writing to string should never fail");
282 }
283 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
284
285 self
286 }
287
288 pub fn set_fill_color_lab(&mut self, color: LabColor) -> &mut Self {
290 writeln!(&mut self.operations, "/Lab1 cs").expect("Writing to string should never fail");
292
293 let values = color.values();
295 for value in &values {
296 write!(&mut self.operations, "{:.4} ", value)
297 .expect("Writing to string should never fail");
298 }
299 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
300
301 self
302 }
303
304 pub fn set_stroke_color_lab(&mut self, color: LabColor) -> &mut Self {
306 writeln!(&mut self.operations, "/Lab1 CS").expect("Writing to string should never fail");
308
309 let values = color.values();
311 for value in &values {
312 write!(&mut self.operations, "{:.4} ", value)
313 .expect("Writing to string should never fail");
314 }
315 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
316
317 self
318 }
319
320 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
321 self.line_width = width;
322 writeln!(&mut self.operations, "{width:.2} w")
323 .expect("Writing to string should never fail");
324 self
325 }
326
327 pub fn set_line_cap(&mut self, cap: LineCap) -> &mut Self {
328 self.current_line_cap = cap;
329 writeln!(&mut self.operations, "{} J", cap as u8)
330 .expect("Writing to string should never fail");
331 self
332 }
333
334 pub fn set_line_join(&mut self, join: LineJoin) -> &mut Self {
335 self.current_line_join = join;
336 writeln!(&mut self.operations, "{} j", join as u8)
337 .expect("Writing to string should never fail");
338 self
339 }
340
341 pub fn set_opacity(&mut self, opacity: f64) -> &mut Self {
343 let opacity = opacity.clamp(0.0, 1.0);
344 self.fill_opacity = opacity;
345 self.stroke_opacity = opacity;
346
347 if opacity < 1.0 {
349 let mut state = ExtGState::new();
350 state.alpha_fill = Some(opacity);
351 state.alpha_stroke = Some(opacity);
352 self.pending_extgstate = Some(state);
353 }
354
355 self
356 }
357
358 pub fn set_fill_opacity(&mut self, opacity: f64) -> &mut Self {
360 self.fill_opacity = opacity.clamp(0.0, 1.0);
361
362 if opacity < 1.0 {
364 if let Some(ref mut state) = self.pending_extgstate {
365 state.alpha_fill = Some(opacity);
366 } else {
367 let mut state = ExtGState::new();
368 state.alpha_fill = Some(opacity);
369 self.pending_extgstate = Some(state);
370 }
371 }
372
373 self
374 }
375
376 pub fn set_stroke_opacity(&mut self, opacity: f64) -> &mut Self {
378 self.stroke_opacity = opacity.clamp(0.0, 1.0);
379
380 if opacity < 1.0 {
382 if let Some(ref mut state) = self.pending_extgstate {
383 state.alpha_stroke = Some(opacity);
384 } else {
385 let mut state = ExtGState::new();
386 state.alpha_stroke = Some(opacity);
387 self.pending_extgstate = Some(state);
388 }
389 }
390
391 self
392 }
393
394 pub fn save_state(&mut self) -> &mut Self {
395 self.operations.push_str("q\n");
396 self.save_clipping_state();
397 self.state_stack.push(GraphicsState {
399 fill_color: self.current_color,
400 stroke_color: self.stroke_color,
401 font_name: self.current_font_name.clone(),
402 font_size: self.current_font_size,
403 is_custom_font: self.is_custom_font,
404 });
405 self
406 }
407
408 pub fn restore_state(&mut self) -> &mut Self {
409 self.operations.push_str("Q\n");
410 self.restore_clipping_state();
411 if let Some(state) = self.state_stack.pop() {
413 self.current_color = state.fill_color;
414 self.stroke_color = state.stroke_color;
415 self.current_font_name = state.font_name;
416 self.current_font_size = state.font_size;
417 self.is_custom_font = state.is_custom_font;
418 }
419 self
420 }
421
422 pub fn begin_transparency_group(&mut self, group: TransparencyGroup) -> &mut Self {
425 self.save_state();
427
428 writeln!(&mut self.operations, "% Begin Transparency Group")
430 .expect("Writing to string should never fail");
431
432 let mut extgstate = ExtGState::new();
434 extgstate = extgstate.with_blend_mode(group.blend_mode.clone());
435 extgstate.alpha_fill = Some(group.opacity as f64);
436 extgstate.alpha_stroke = Some(group.opacity as f64);
437
438 self.pending_extgstate = Some(extgstate);
440 let _ = self.apply_pending_extgstate();
441
442 let mut group_state = TransparencyGroupState::new(group);
444 group_state.saved_state = self.operations.as_bytes().to_vec();
446 self.transparency_stack.push(group_state);
447
448 self
449 }
450
451 pub fn end_transparency_group(&mut self) -> &mut Self {
453 if let Some(_group_state) = self.transparency_stack.pop() {
454 writeln!(&mut self.operations, "% End Transparency Group")
456 .expect("Writing to string should never fail");
457
458 self.restore_state();
460 }
461 self
462 }
463
464 pub fn in_transparency_group(&self) -> bool {
466 !self.transparency_stack.is_empty()
467 }
468
469 pub fn current_transparency_group(&self) -> Option<&TransparencyGroup> {
471 self.transparency_stack.last().map(|state| &state.group)
472 }
473
474 pub fn translate(&mut self, tx: f64, ty: f64) -> &mut Self {
475 writeln!(&mut self.operations, "1 0 0 1 {tx:.2} {ty:.2} cm")
476 .expect("Writing to string should never fail");
477 self
478 }
479
480 pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
481 writeln!(&mut self.operations, "{sx:.2} 0 0 {sy:.2} 0 0 cm")
482 .expect("Writing to string should never fail");
483 self
484 }
485
486 pub fn rotate(&mut self, angle: f64) -> &mut Self {
487 let cos = angle.cos();
488 let sin = angle.sin();
489 writeln!(
490 &mut self.operations,
491 "{:.6} {:.6} {:.6} {:.6} 0 0 cm",
492 cos, sin, -sin, cos
493 )
494 .expect("Writing to string should never fail");
495 self
496 }
497
498 pub fn transform(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> &mut Self {
499 writeln!(
500 &mut self.operations,
501 "{a:.2} {b:.2} {c:.2} {d:.2} {e:.2} {f:.2} cm"
502 )
503 .expect("Writing to string should never fail");
504 self
505 }
506
507 pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
508 self.rect(x, y, width, height)
509 }
510
511 pub fn draw_image(
512 &mut self,
513 image_name: &str,
514 x: f64,
515 y: f64,
516 width: f64,
517 height: f64,
518 ) -> &mut Self {
519 self.save_state();
521
522 writeln!(
525 &mut self.operations,
526 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
527 )
528 .expect("Writing to string should never fail");
529
530 writeln!(&mut self.operations, "/{image_name} Do")
532 .expect("Writing to string should never fail");
533
534 self.restore_state();
536
537 self
538 }
539
540 pub fn draw_image_with_transparency(
543 &mut self,
544 image_name: &str,
545 x: f64,
546 y: f64,
547 width: f64,
548 height: f64,
549 mask_name: Option<&str>,
550 ) -> &mut Self {
551 self.save_state();
553
554 if let Some(mask) = mask_name {
556 let mut extgstate = ExtGState::new();
558 extgstate.set_soft_mask_name(mask.to_string());
559
560 let gs_name = self
562 .extgstate_manager
563 .add_state(extgstate)
564 .unwrap_or_else(|_| "GS1".to_string());
565 writeln!(&mut self.operations, "/{} gs", gs_name)
566 .expect("Writing to string should never fail");
567 }
568
569 writeln!(
571 &mut self.operations,
572 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
573 )
574 .expect("Writing to string should never fail");
575
576 writeln!(&mut self.operations, "/{image_name} Do")
578 .expect("Writing to string should never fail");
579
580 if mask_name.is_some() {
582 let mut reset_extgstate = ExtGState::new();
584 reset_extgstate.set_soft_mask_none();
585
586 let gs_name = self
587 .extgstate_manager
588 .add_state(reset_extgstate)
589 .unwrap_or_else(|_| "GS2".to_string());
590 writeln!(&mut self.operations, "/{} gs", gs_name)
591 .expect("Writing to string should never fail");
592 }
593
594 self.restore_state();
596
597 self
598 }
599
600 fn apply_stroke_color(&mut self) {
601 match self.stroke_color {
602 Color::Rgb(r, g, b) => {
603 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} RG")
604 .expect("Writing to string should never fail");
605 }
606 Color::Gray(g) => {
607 writeln!(&mut self.operations, "{g:.3} G")
608 .expect("Writing to string should never fail");
609 }
610 Color::Cmyk(c, m, y, k) => {
611 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} K")
612 .expect("Writing to string should never fail");
613 }
614 }
615 }
616
617 fn apply_fill_color(&mut self) {
618 match self.current_color {
619 Color::Rgb(r, g, b) => {
620 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} rg")
621 .expect("Writing to string should never fail");
622 }
623 Color::Gray(g) => {
624 writeln!(&mut self.operations, "{g:.3} g")
625 .expect("Writing to string should never fail");
626 }
627 Color::Cmyk(c, m, y, k) => {
628 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} k")
629 .expect("Writing to string should never fail");
630 }
631 }
632 }
633
634 pub(crate) fn generate_operations(&self) -> Result<Vec<u8>> {
635 Ok(self.operations.as_bytes().to_vec())
636 }
637
638 pub fn uses_transparency(&self) -> bool {
640 self.fill_opacity < 1.0 || self.stroke_opacity < 1.0
641 }
642
643 pub fn generate_graphics_state_dict(&self) -> Option<String> {
645 if !self.uses_transparency() {
646 return None;
647 }
648
649 let mut dict = String::from("<< /Type /ExtGState");
650
651 if self.fill_opacity < 1.0 {
652 write!(&mut dict, " /ca {:.3}", self.fill_opacity)
653 .expect("Writing to string should never fail");
654 }
655
656 if self.stroke_opacity < 1.0 {
657 write!(&mut dict, " /CA {:.3}", self.stroke_opacity)
658 .expect("Writing to string should never fail");
659 }
660
661 dict.push_str(" >>");
662 Some(dict)
663 }
664
665 pub fn fill_color(&self) -> Color {
667 self.current_color
668 }
669
670 pub fn stroke_color(&self) -> Color {
672 self.stroke_color
673 }
674
675 pub fn line_width(&self) -> f64 {
677 self.line_width
678 }
679
680 pub fn fill_opacity(&self) -> f64 {
682 self.fill_opacity
683 }
684
685 pub fn stroke_opacity(&self) -> f64 {
687 self.stroke_opacity
688 }
689
690 pub fn operations(&self) -> &str {
692 &self.operations
693 }
694
695 pub fn get_operations(&self) -> &str {
697 &self.operations
698 }
699
700 pub fn clear(&mut self) {
702 self.operations.clear();
703 }
704
705 pub fn begin_text(&mut self) -> &mut Self {
707 self.operations.push_str("BT\n");
708 self
709 }
710
711 pub fn end_text(&mut self) -> &mut Self {
713 self.operations.push_str("ET\n");
714 self
715 }
716
717 pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
719 writeln!(&mut self.operations, "/{} {} Tf", font.pdf_name(), size)
720 .expect("Writing to string should never fail");
721
722 match &font {
724 Font::Custom(name) => {
725 self.current_font_name = Some(Arc::from(name.as_str()));
726 self.current_font_size = size;
727 self.is_custom_font = true;
728 }
729 _ => {
730 self.current_font_name = Some(Arc::from(font.pdf_name().as_str()));
731 self.current_font_size = size;
732 self.is_custom_font = false;
733 }
734 }
735
736 self
737 }
738
739 pub fn set_text_position(&mut self, x: f64, y: f64) -> &mut Self {
741 writeln!(&mut self.operations, "{x:.2} {y:.2} Td")
742 .expect("Writing to string should never fail");
743 self
744 }
745
746 pub fn show_text(&mut self, text: &str) -> Result<&mut Self> {
753 self.used_characters.extend(text.chars());
755
756 if self.is_custom_font {
757 self.operations.push('<');
759 for ch in text.chars() {
760 encode_char_as_cid(ch, &mut self.operations);
761 }
762 self.operations.push_str("> Tj\n");
763 } else {
764 self.operations.push('(');
766 for ch in text.chars() {
767 match ch {
768 '(' => self.operations.push_str("\\("),
769 ')' => self.operations.push_str("\\)"),
770 '\\' => self.operations.push_str("\\\\"),
771 '\n' => self.operations.push_str("\\n"),
772 '\r' => self.operations.push_str("\\r"),
773 '\t' => self.operations.push_str("\\t"),
774 _ => self.operations.push(ch),
775 }
776 }
777 self.operations.push_str(") Tj\n");
778 }
779 Ok(self)
780 }
781
782 pub fn set_word_spacing(&mut self, spacing: f64) -> &mut Self {
784 writeln!(&mut self.operations, "{spacing:.2} Tw")
785 .expect("Writing to string should never fail");
786 self
787 }
788
789 pub fn set_character_spacing(&mut self, spacing: f64) -> &mut Self {
791 writeln!(&mut self.operations, "{spacing:.2} Tc")
792 .expect("Writing to string should never fail");
793 self
794 }
795
796 pub fn show_justified_text(&mut self, text: &str, target_width: f64) -> Result<&mut Self> {
798 let words: Vec<&str> = text.split_whitespace().collect();
800 if words.len() <= 1 {
801 return self.show_text(text);
803 }
804
805 let text_without_spaces = words.join("");
807 let natural_text_width = self.estimate_text_width_simple(&text_without_spaces);
808 let space_width = self.estimate_text_width_simple(" ");
809 let natural_width = natural_text_width + (space_width * (words.len() - 1) as f64);
810
811 let extra_space_needed = target_width - natural_width;
813 let word_gaps = (words.len() - 1) as f64;
814
815 if word_gaps > 0.0 && extra_space_needed > 0.0 {
816 let extra_word_spacing = extra_space_needed / word_gaps;
817
818 self.set_word_spacing(extra_word_spacing);
820
821 self.show_text(text)?;
823
824 self.set_word_spacing(0.0);
826 } else {
827 self.show_text(text)?;
829 }
830
831 Ok(self)
832 }
833
834 fn estimate_text_width_simple(&self, text: &str) -> f64 {
836 let font_size = self.current_font_size;
839 text.len() as f64 * font_size * 0.6 }
841
842 pub fn render_table(&mut self, table: &Table) -> Result<()> {
844 table.render(self)
845 }
846
847 pub fn render_list(&mut self, list: &ListElement) -> Result<()> {
849 match list {
850 ListElement::Ordered(ordered) => ordered.render(self),
851 ListElement::Unordered(unordered) => unordered.render(self),
852 }
853 }
854
855 pub fn render_column_layout(
857 &mut self,
858 layout: &ColumnLayout,
859 content: &ColumnContent,
860 x: f64,
861 y: f64,
862 height: f64,
863 ) -> Result<()> {
864 layout.render(self, content, x, y, height)
865 }
866
867 pub fn set_line_dash_pattern(&mut self, pattern: LineDashPattern) -> &mut Self {
871 self.current_dash_pattern = Some(pattern.clone());
872 writeln!(&mut self.operations, "{} d", pattern.to_pdf_string())
873 .expect("Writing to string should never fail");
874 self
875 }
876
877 pub fn set_line_solid(&mut self) -> &mut Self {
879 self.current_dash_pattern = None;
880 self.operations.push_str("[] 0 d\n");
881 self
882 }
883
884 pub fn set_miter_limit(&mut self, limit: f64) -> &mut Self {
886 self.current_miter_limit = limit.max(1.0);
887 writeln!(&mut self.operations, "{:.2} M", self.current_miter_limit)
888 .expect("Writing to string should never fail");
889 self
890 }
891
892 pub fn set_rendering_intent(&mut self, intent: RenderingIntent) -> &mut Self {
894 self.current_rendering_intent = intent;
895 writeln!(&mut self.operations, "/{} ri", intent.pdf_name())
896 .expect("Writing to string should never fail");
897 self
898 }
899
900 pub fn set_flatness(&mut self, flatness: f64) -> &mut Self {
902 self.current_flatness = flatness.clamp(0.0, 100.0);
903 writeln!(&mut self.operations, "{:.2} i", self.current_flatness)
904 .expect("Writing to string should never fail");
905 self
906 }
907
908 pub fn apply_extgstate(&mut self, state: ExtGState) -> Result<&mut Self> {
910 let state_name = self.extgstate_manager.add_state(state)?;
911 writeln!(&mut self.operations, "/{state_name} gs")
912 .expect("Writing to string should never fail");
913 Ok(self)
914 }
915
916 #[allow(dead_code)]
918 fn set_pending_extgstate(&mut self, state: ExtGState) {
919 self.pending_extgstate = Some(state);
920 }
921
922 fn apply_pending_extgstate(&mut self) -> Result<()> {
924 if let Some(state) = self.pending_extgstate.take() {
925 let state_name = self.extgstate_manager.add_state(state)?;
926 writeln!(&mut self.operations, "/{state_name} gs")
927 .expect("Writing to string should never fail");
928 }
929 Ok(())
930 }
931
932 pub fn with_extgstate<F>(&mut self, builder: F) -> Result<&mut Self>
934 where
935 F: FnOnce(ExtGState) -> ExtGState,
936 {
937 let state = builder(ExtGState::new());
938 self.apply_extgstate(state)
939 }
940
941 pub fn set_blend_mode(&mut self, mode: BlendMode) -> Result<&mut Self> {
943 let state = ExtGState::new().with_blend_mode(mode);
944 self.apply_extgstate(state)
945 }
946
947 pub fn set_alpha(&mut self, alpha: f64) -> Result<&mut Self> {
949 let state = ExtGState::new().with_alpha(alpha);
950 self.apply_extgstate(state)
951 }
952
953 pub fn set_alpha_stroke(&mut self, alpha: f64) -> Result<&mut Self> {
955 let state = ExtGState::new().with_alpha_stroke(alpha);
956 self.apply_extgstate(state)
957 }
958
959 pub fn set_alpha_fill(&mut self, alpha: f64) -> Result<&mut Self> {
961 let state = ExtGState::new().with_alpha_fill(alpha);
962 self.apply_extgstate(state)
963 }
964
965 pub fn set_overprint_stroke(&mut self, overprint: bool) -> Result<&mut Self> {
967 let state = ExtGState::new().with_overprint_stroke(overprint);
968 self.apply_extgstate(state)
969 }
970
971 pub fn set_overprint_fill(&mut self, overprint: bool) -> Result<&mut Self> {
973 let state = ExtGState::new().with_overprint_fill(overprint);
974 self.apply_extgstate(state)
975 }
976
977 pub fn set_stroke_adjustment(&mut self, adjustment: bool) -> Result<&mut Self> {
979 let state = ExtGState::new().with_stroke_adjustment(adjustment);
980 self.apply_extgstate(state)
981 }
982
983 pub fn set_smoothness(&mut self, smoothness: f64) -> Result<&mut Self> {
985 self.current_smoothness = smoothness.clamp(0.0, 1.0);
986 let state = ExtGState::new().with_smoothness(self.current_smoothness);
987 self.apply_extgstate(state)
988 }
989
990 pub fn line_dash_pattern(&self) -> Option<&LineDashPattern> {
994 self.current_dash_pattern.as_ref()
995 }
996
997 pub fn miter_limit(&self) -> f64 {
999 self.current_miter_limit
1000 }
1001
1002 pub fn line_cap(&self) -> LineCap {
1004 self.current_line_cap
1005 }
1006
1007 pub fn line_join(&self) -> LineJoin {
1009 self.current_line_join
1010 }
1011
1012 pub fn rendering_intent(&self) -> RenderingIntent {
1014 self.current_rendering_intent
1015 }
1016
1017 pub fn flatness(&self) -> f64 {
1019 self.current_flatness
1020 }
1021
1022 pub fn smoothness(&self) -> f64 {
1024 self.current_smoothness
1025 }
1026
1027 pub fn extgstate_manager(&self) -> &ExtGStateManager {
1029 &self.extgstate_manager
1030 }
1031
1032 pub fn extgstate_manager_mut(&mut self) -> &mut ExtGStateManager {
1034 &mut self.extgstate_manager
1035 }
1036
1037 pub fn generate_extgstate_resources(&self) -> Result<String> {
1039 self.extgstate_manager.to_resource_dictionary()
1040 }
1041
1042 pub fn has_extgstates(&self) -> bool {
1044 self.extgstate_manager.count() > 0
1045 }
1046
1047 pub fn add_command(&mut self, command: &str) {
1049 self.operations.push_str(command);
1050 self.operations.push('\n');
1051 }
1052
1053 pub fn clip(&mut self) -> &mut Self {
1055 self.operations.push_str("W\n");
1056 self
1057 }
1058
1059 pub fn clip_even_odd(&mut self) -> &mut Self {
1061 self.operations.push_str("W*\n");
1062 self
1063 }
1064
1065 pub fn clip_stroke(&mut self) -> &mut Self {
1067 self.apply_stroke_color();
1068 self.operations.push_str("W S\n");
1069 self
1070 }
1071
1072 pub fn set_clipping_path(&mut self, path: ClippingPath) -> Result<&mut Self> {
1074 let ops = path.to_pdf_operations()?;
1075 self.operations.push_str(&ops);
1076 self.clipping_region.set_clip(path);
1077 Ok(self)
1078 }
1079
1080 pub fn clear_clipping(&mut self) -> &mut Self {
1082 self.clipping_region.clear_clip();
1083 self
1084 }
1085
1086 fn save_clipping_state(&mut self) {
1088 self.clipping_region.save();
1089 }
1090
1091 fn restore_clipping_state(&mut self) {
1093 self.clipping_region.restore();
1094 }
1095
1096 pub fn clip_rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> Result<&mut Self> {
1098 let path = ClippingPath::rect(x, y, width, height);
1099 self.set_clipping_path(path)
1100 }
1101
1102 pub fn clip_circle(&mut self, cx: f64, cy: f64, radius: f64) -> Result<&mut Self> {
1104 let path = ClippingPath::circle(cx, cy, radius);
1105 self.set_clipping_path(path)
1106 }
1107
1108 pub fn clip_ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) -> Result<&mut Self> {
1110 let path = ClippingPath::ellipse(cx, cy, rx, ry);
1111 self.set_clipping_path(path)
1112 }
1113
1114 pub fn has_clipping(&self) -> bool {
1116 self.clipping_region.has_clip()
1117 }
1118
1119 pub fn clipping_path(&self) -> Option<&ClippingPath> {
1121 self.clipping_region.current()
1122 }
1123
1124 pub fn set_font_manager(&mut self, font_manager: Arc<FontManager>) -> &mut Self {
1126 self.font_manager = Some(font_manager);
1127 self
1128 }
1129
1130 pub fn set_custom_font(&mut self, font_name: &str, size: f64) -> &mut Self {
1132 writeln!(&mut self.operations, "/{} {} Tf", font_name, size)
1134 .expect("Writing to string should never fail");
1135
1136 self.current_font_name = Some(Arc::from(font_name));
1137 self.current_font_size = size;
1138 self.is_custom_font = true;
1139
1140 if let Some(ref font_manager) = self.font_manager {
1142 if let Some(mapping) = font_manager.get_font_glyph_mapping(font_name) {
1143 self.glyph_mapping = Some(mapping);
1144 }
1145 }
1146
1147 self
1148 }
1149
1150 pub fn set_glyph_mapping(&mut self, mapping: HashMap<u32, u16>) -> &mut Self {
1152 self.glyph_mapping = Some(mapping);
1153 self
1154 }
1155
1156 pub fn draw_text(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1158 self.used_characters.extend(text.chars());
1160
1161 let needs_unicode = self.is_custom_font || text.chars().any(|c| c as u32 > 255);
1164
1165 if needs_unicode {
1167 self.draw_with_unicode_encoding(text, x, y)
1168 } else {
1169 self.draw_with_simple_encoding(text, x, y)
1170 }
1171 }
1172
1173 fn draw_with_simple_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1175 let has_unicode = text.chars().any(|c| c as u32 > 255);
1177
1178 if has_unicode {
1179 tracing::debug!("Warning: Text contains Unicode characters but using Latin-1 font. Characters will be replaced with '?'");
1181 }
1182
1183 self.operations.push_str("BT\n");
1185
1186 self.apply_fill_color();
1188
1189 if let Some(font_name) = &self.current_font_name {
1191 writeln!(
1192 &mut self.operations,
1193 "/{} {} Tf",
1194 font_name, self.current_font_size
1195 )
1196 .expect("Writing to string should never fail");
1197 } else {
1198 writeln!(
1199 &mut self.operations,
1200 "/Helvetica {} Tf",
1201 self.current_font_size
1202 )
1203 .expect("Writing to string should never fail");
1204 }
1205
1206 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1208 .expect("Writing to string should never fail");
1209
1210 self.operations.push('(');
1213 for ch in text.chars() {
1214 let code = ch as u32;
1215 if code <= 127 {
1216 match ch {
1218 '(' => self.operations.push_str("\\("),
1219 ')' => self.operations.push_str("\\)"),
1220 '\\' => self.operations.push_str("\\\\"),
1221 '\n' => self.operations.push_str("\\n"),
1222 '\r' => self.operations.push_str("\\r"),
1223 '\t' => self.operations.push_str("\\t"),
1224 _ => self.operations.push(ch),
1225 }
1226 } else if code <= 255 {
1227 write!(&mut self.operations, "\\{:03o}", code)
1230 .expect("Writing to string should never fail");
1231 } else {
1232 self.operations.push('?');
1234 }
1235 }
1236 self.operations.push_str(") Tj\n");
1237
1238 self.operations.push_str("ET\n");
1240
1241 Ok(self)
1242 }
1243
1244 fn draw_with_unicode_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1246 self.operations.push_str("BT\n");
1248
1249 self.apply_fill_color();
1251
1252 if let Some(font_name) = &self.current_font_name {
1254 writeln!(
1256 &mut self.operations,
1257 "/{} {} Tf",
1258 font_name, self.current_font_size
1259 )
1260 .expect("Writing to string should never fail");
1261 } else {
1262 writeln!(
1263 &mut self.operations,
1264 "/Helvetica {} Tf",
1265 self.current_font_size
1266 )
1267 .expect("Writing to string should never fail");
1268 }
1269
1270 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1272 .expect("Writing to string should never fail");
1273
1274 self.operations.push('<');
1277 for ch in text.chars() {
1278 encode_char_as_cid(ch, &mut self.operations);
1279 }
1280 self.operations.push_str("> Tj\n");
1281
1282 self.operations.push_str("ET\n");
1284
1285 Ok(self)
1286 }
1287
1288 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1290 pub fn draw_text_hex(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1291 self.operations.push_str("BT\n");
1293
1294 self.apply_fill_color();
1296
1297 if let Some(font_name) = &self.current_font_name {
1299 writeln!(
1300 &mut self.operations,
1301 "/{} {} Tf",
1302 font_name, self.current_font_size
1303 )
1304 .expect("Writing to string should never fail");
1305 } else {
1306 writeln!(
1308 &mut self.operations,
1309 "/Helvetica {} Tf",
1310 self.current_font_size
1311 )
1312 .expect("Writing to string should never fail");
1313 }
1314
1315 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1317 .expect("Writing to string should never fail");
1318
1319 self.operations.push('<');
1323 for ch in text.chars() {
1324 if ch as u32 <= 255 {
1325 write!(&mut self.operations, "{:02X}", ch as u8)
1327 .expect("Writing to string should never fail");
1328 } else {
1329 write!(&mut self.operations, "3F").expect("Writing to string should never fail");
1332 }
1334 }
1335 self.operations.push_str("> Tj\n");
1336
1337 self.operations.push_str("ET\n");
1339
1340 Ok(self)
1341 }
1342
1343 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1345 pub fn draw_text_cid(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1346 use crate::fonts::needs_type0_font;
1347
1348 self.operations.push_str("BT\n");
1350
1351 self.apply_fill_color();
1353
1354 if let Some(font_name) = &self.current_font_name {
1356 writeln!(
1357 &mut self.operations,
1358 "/{} {} Tf",
1359 font_name, self.current_font_size
1360 )
1361 .expect("Writing to string should never fail");
1362 } else {
1363 writeln!(
1364 &mut self.operations,
1365 "/Helvetica {} Tf",
1366 self.current_font_size
1367 )
1368 .expect("Writing to string should never fail");
1369 }
1370
1371 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1373 .expect("Writing to string should never fail");
1374
1375 if needs_type0_font(text) {
1377 self.operations.push('<');
1379 for ch in text.chars() {
1380 encode_char_as_cid(ch, &mut self.operations);
1381 }
1382 self.operations.push_str("> Tj\n");
1383 } else {
1384 self.operations.push('<');
1386 for ch in text.chars() {
1387 if ch as u32 <= 255 {
1388 write!(&mut self.operations, "{:02X}", ch as u8)
1389 .expect("Writing to string should never fail");
1390 } else {
1391 write!(&mut self.operations, "3F")
1392 .expect("Writing to string should never fail");
1393 }
1394 }
1395 self.operations.push_str("> Tj\n");
1396 }
1397
1398 self.operations.push_str("ET\n");
1400 Ok(self)
1401 }
1402
1403 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1405 pub fn draw_text_unicode(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1406 self.operations.push_str("BT\n");
1408
1409 self.apply_fill_color();
1411
1412 if let Some(font_name) = &self.current_font_name {
1414 writeln!(
1415 &mut self.operations,
1416 "/{} {} Tf",
1417 font_name, self.current_font_size
1418 )
1419 .expect("Writing to string should never fail");
1420 } else {
1421 writeln!(
1423 &mut self.operations,
1424 "/Helvetica {} Tf",
1425 self.current_font_size
1426 )
1427 .expect("Writing to string should never fail");
1428 }
1429
1430 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1432 .expect("Writing to string should never fail");
1433
1434 self.operations.push('<');
1436 let mut utf16_buffer = [0u16; 2];
1437 for ch in text.chars() {
1438 let encoded = ch.encode_utf16(&mut utf16_buffer);
1439 for unit in encoded {
1440 write!(&mut self.operations, "{:04X}", unit)
1442 .expect("Writing to string should never fail");
1443 }
1444 }
1445 self.operations.push_str("> Tj\n");
1446
1447 self.operations.push_str("ET\n");
1449
1450 Ok(self)
1451 }
1452
1453 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1455 if self.used_characters.is_empty() {
1456 None
1457 } else {
1458 Some(self.used_characters.clone())
1459 }
1460 }
1461}
1462
1463#[cfg(test)]
1464mod tests {
1465 use super::*;
1466
1467 #[test]
1468 fn test_graphics_context_new() {
1469 let ctx = GraphicsContext::new();
1470 assert_eq!(ctx.fill_color(), Color::black());
1471 assert_eq!(ctx.stroke_color(), Color::black());
1472 assert_eq!(ctx.line_width(), 1.0);
1473 assert_eq!(ctx.fill_opacity(), 1.0);
1474 assert_eq!(ctx.stroke_opacity(), 1.0);
1475 assert!(ctx.operations().is_empty());
1476 }
1477
1478 #[test]
1479 fn test_graphics_context_default() {
1480 let ctx = GraphicsContext::default();
1481 assert_eq!(ctx.fill_color(), Color::black());
1482 assert_eq!(ctx.stroke_color(), Color::black());
1483 assert_eq!(ctx.line_width(), 1.0);
1484 }
1485
1486 #[test]
1487 fn test_move_to() {
1488 let mut ctx = GraphicsContext::new();
1489 ctx.move_to(10.0, 20.0);
1490 assert!(ctx.operations().contains("10.00 20.00 m\n"));
1491 }
1492
1493 #[test]
1494 fn test_line_to() {
1495 let mut ctx = GraphicsContext::new();
1496 ctx.line_to(30.0, 40.0);
1497 assert!(ctx.operations().contains("30.00 40.00 l\n"));
1498 }
1499
1500 #[test]
1501 fn test_curve_to() {
1502 let mut ctx = GraphicsContext::new();
1503 ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
1504 assert!(ctx
1505 .operations()
1506 .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
1507 }
1508
1509 #[test]
1510 fn test_rect() {
1511 let mut ctx = GraphicsContext::new();
1512 ctx.rect(10.0, 20.0, 100.0, 50.0);
1513 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1514 }
1515
1516 #[test]
1517 fn test_rectangle_alias() {
1518 let mut ctx = GraphicsContext::new();
1519 ctx.rectangle(10.0, 20.0, 100.0, 50.0);
1520 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1521 }
1522
1523 #[test]
1524 fn test_circle() {
1525 let mut ctx = GraphicsContext::new();
1526 ctx.circle(50.0, 50.0, 25.0);
1527
1528 let ops = ctx.operations();
1529 assert!(ops.contains("75.00 50.00 m\n"));
1531 assert!(ops.contains(" c\n"));
1533 assert!(ops.contains("h\n"));
1535 }
1536
1537 #[test]
1538 fn test_close_path() {
1539 let mut ctx = GraphicsContext::new();
1540 ctx.close_path();
1541 assert!(ctx.operations().contains("h\n"));
1542 }
1543
1544 #[test]
1545 fn test_stroke() {
1546 let mut ctx = GraphicsContext::new();
1547 ctx.set_stroke_color(Color::red());
1548 ctx.rect(0.0, 0.0, 10.0, 10.0);
1549 ctx.stroke();
1550
1551 let ops = ctx.operations();
1552 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1553 assert!(ops.contains("S\n"));
1554 }
1555
1556 #[test]
1557 fn test_fill() {
1558 let mut ctx = GraphicsContext::new();
1559 ctx.set_fill_color(Color::blue());
1560 ctx.rect(0.0, 0.0, 10.0, 10.0);
1561 ctx.fill();
1562
1563 let ops = ctx.operations();
1564 assert!(ops.contains("0.000 0.000 1.000 rg\n"));
1565 assert!(ops.contains("f\n"));
1566 }
1567
1568 #[test]
1569 fn test_fill_stroke() {
1570 let mut ctx = GraphicsContext::new();
1571 ctx.set_fill_color(Color::green());
1572 ctx.set_stroke_color(Color::red());
1573 ctx.rect(0.0, 0.0, 10.0, 10.0);
1574 ctx.fill_stroke();
1575
1576 let ops = ctx.operations();
1577 assert!(ops.contains("0.000 1.000 0.000 rg\n"));
1578 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1579 assert!(ops.contains("B\n"));
1580 }
1581
1582 #[test]
1583 fn test_set_stroke_color() {
1584 let mut ctx = GraphicsContext::new();
1585 ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
1586 assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
1587 }
1588
1589 #[test]
1590 fn test_set_fill_color() {
1591 let mut ctx = GraphicsContext::new();
1592 ctx.set_fill_color(Color::gray(0.5));
1593 assert_eq!(ctx.fill_color(), Color::Gray(0.5));
1594 }
1595
1596 #[test]
1597 fn test_set_line_width() {
1598 let mut ctx = GraphicsContext::new();
1599 ctx.set_line_width(2.5);
1600 assert_eq!(ctx.line_width(), 2.5);
1601 assert!(ctx.operations().contains("2.50 w\n"));
1602 }
1603
1604 #[test]
1605 fn test_set_line_cap() {
1606 let mut ctx = GraphicsContext::new();
1607 ctx.set_line_cap(LineCap::Round);
1608 assert!(ctx.operations().contains("1 J\n"));
1609
1610 ctx.set_line_cap(LineCap::Butt);
1611 assert!(ctx.operations().contains("0 J\n"));
1612
1613 ctx.set_line_cap(LineCap::Square);
1614 assert!(ctx.operations().contains("2 J\n"));
1615 }
1616
1617 #[test]
1618 fn test_set_line_join() {
1619 let mut ctx = GraphicsContext::new();
1620 ctx.set_line_join(LineJoin::Round);
1621 assert!(ctx.operations().contains("1 j\n"));
1622
1623 ctx.set_line_join(LineJoin::Miter);
1624 assert!(ctx.operations().contains("0 j\n"));
1625
1626 ctx.set_line_join(LineJoin::Bevel);
1627 assert!(ctx.operations().contains("2 j\n"));
1628 }
1629
1630 #[test]
1631 fn test_save_restore_state() {
1632 let mut ctx = GraphicsContext::new();
1633 ctx.save_state();
1634 assert!(ctx.operations().contains("q\n"));
1635
1636 ctx.restore_state();
1637 assert!(ctx.operations().contains("Q\n"));
1638 }
1639
1640 #[test]
1641 fn test_translate() {
1642 let mut ctx = GraphicsContext::new();
1643 ctx.translate(50.0, 100.0);
1644 assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
1645 }
1646
1647 #[test]
1648 fn test_scale() {
1649 let mut ctx = GraphicsContext::new();
1650 ctx.scale(2.0, 3.0);
1651 assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
1652 }
1653
1654 #[test]
1655 fn test_rotate() {
1656 let mut ctx = GraphicsContext::new();
1657 let angle = std::f64::consts::PI / 4.0; ctx.rotate(angle);
1659
1660 let ops = ctx.operations();
1661 assert!(ops.contains(" cm\n"));
1662 assert!(ops.contains("0.707107")); }
1665
1666 #[test]
1667 fn test_transform() {
1668 let mut ctx = GraphicsContext::new();
1669 ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
1670 assert!(ctx
1671 .operations()
1672 .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
1673 }
1674
1675 #[test]
1676 fn test_draw_image() {
1677 let mut ctx = GraphicsContext::new();
1678 ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
1679
1680 let ops = ctx.operations();
1681 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")); }
1686
1687 #[test]
1688 fn test_gray_color_operations() {
1689 let mut ctx = GraphicsContext::new();
1690 ctx.set_stroke_color(Color::gray(0.5));
1691 ctx.set_fill_color(Color::gray(0.7));
1692 ctx.stroke();
1693 ctx.fill();
1694
1695 let ops = ctx.operations();
1696 assert!(ops.contains("0.500 G\n")); assert!(ops.contains("0.700 g\n")); }
1699
1700 #[test]
1701 fn test_cmyk_color_operations() {
1702 let mut ctx = GraphicsContext::new();
1703 ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
1704 ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
1705 ctx.stroke();
1706 ctx.fill();
1707
1708 let ops = ctx.operations();
1709 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")); }
1712
1713 #[test]
1714 fn test_method_chaining() {
1715 let mut ctx = GraphicsContext::new();
1716 ctx.move_to(0.0, 0.0)
1717 .line_to(10.0, 0.0)
1718 .line_to(10.0, 10.0)
1719 .line_to(0.0, 10.0)
1720 .close_path()
1721 .set_fill_color(Color::red())
1722 .fill();
1723
1724 let ops = ctx.operations();
1725 assert!(ops.contains("0.00 0.00 m\n"));
1726 assert!(ops.contains("10.00 0.00 l\n"));
1727 assert!(ops.contains("10.00 10.00 l\n"));
1728 assert!(ops.contains("0.00 10.00 l\n"));
1729 assert!(ops.contains("h\n"));
1730 assert!(ops.contains("f\n"));
1731 }
1732
1733 #[test]
1734 fn test_generate_operations() {
1735 let mut ctx = GraphicsContext::new();
1736 ctx.rect(0.0, 0.0, 10.0, 10.0);
1737
1738 let result = ctx.generate_operations();
1739 assert!(result.is_ok());
1740 let bytes = result.expect("Writing to string should never fail");
1741 let ops_string = String::from_utf8(bytes).expect("Writing to string should never fail");
1742 assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
1743 }
1744
1745 #[test]
1746 fn test_clear_operations() {
1747 let mut ctx = GraphicsContext::new();
1748 ctx.rect(0.0, 0.0, 10.0, 10.0);
1749 assert!(!ctx.operations().is_empty());
1750
1751 ctx.clear();
1752 assert!(ctx.operations().is_empty());
1753 }
1754
1755 #[test]
1756 fn test_complex_path() {
1757 let mut ctx = GraphicsContext::new();
1758 ctx.save_state()
1759 .translate(100.0, 100.0)
1760 .rotate(std::f64::consts::PI / 6.0)
1761 .scale(2.0, 2.0)
1762 .set_line_width(2.0)
1763 .set_stroke_color(Color::blue())
1764 .move_to(0.0, 0.0)
1765 .line_to(50.0, 0.0)
1766 .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
1767 .close_path()
1768 .stroke()
1769 .restore_state();
1770
1771 let ops = ctx.operations();
1772 assert!(ops.contains("q\n"));
1773 assert!(ops.contains("cm\n"));
1774 assert!(ops.contains("2.00 w\n"));
1775 assert!(ops.contains("0.000 0.000 1.000 RG\n"));
1776 assert!(ops.contains("S\n"));
1777 assert!(ops.contains("Q\n"));
1778 }
1779
1780 #[test]
1781 fn test_graphics_context_clone() {
1782 let mut ctx = GraphicsContext::new();
1783 ctx.set_fill_color(Color::red());
1784 ctx.set_stroke_color(Color::blue());
1785 ctx.set_line_width(3.0);
1786 ctx.set_opacity(0.5);
1787 ctx.rect(0.0, 0.0, 10.0, 10.0);
1788
1789 let ctx_clone = ctx.clone();
1790 assert_eq!(ctx_clone.fill_color(), Color::red());
1791 assert_eq!(ctx_clone.stroke_color(), Color::blue());
1792 assert_eq!(ctx_clone.line_width(), 3.0);
1793 assert_eq!(ctx_clone.fill_opacity(), 0.5);
1794 assert_eq!(ctx_clone.stroke_opacity(), 0.5);
1795 assert_eq!(ctx_clone.operations(), ctx.operations());
1796 }
1797
1798 #[test]
1799 fn test_set_opacity() {
1800 let mut ctx = GraphicsContext::new();
1801
1802 ctx.set_opacity(0.5);
1804 assert_eq!(ctx.fill_opacity(), 0.5);
1805 assert_eq!(ctx.stroke_opacity(), 0.5);
1806
1807 ctx.set_opacity(1.5);
1809 assert_eq!(ctx.fill_opacity(), 1.0);
1810 assert_eq!(ctx.stroke_opacity(), 1.0);
1811
1812 ctx.set_opacity(-0.5);
1813 assert_eq!(ctx.fill_opacity(), 0.0);
1814 assert_eq!(ctx.stroke_opacity(), 0.0);
1815 }
1816
1817 #[test]
1818 fn test_set_fill_opacity() {
1819 let mut ctx = GraphicsContext::new();
1820
1821 ctx.set_fill_opacity(0.3);
1822 assert_eq!(ctx.fill_opacity(), 0.3);
1823 assert_eq!(ctx.stroke_opacity(), 1.0); ctx.set_fill_opacity(2.0);
1827 assert_eq!(ctx.fill_opacity(), 1.0);
1828 }
1829
1830 #[test]
1831 fn test_set_stroke_opacity() {
1832 let mut ctx = GraphicsContext::new();
1833
1834 ctx.set_stroke_opacity(0.7);
1835 assert_eq!(ctx.stroke_opacity(), 0.7);
1836 assert_eq!(ctx.fill_opacity(), 1.0); ctx.set_stroke_opacity(-1.0);
1840 assert_eq!(ctx.stroke_opacity(), 0.0);
1841 }
1842
1843 #[test]
1844 fn test_uses_transparency() {
1845 let mut ctx = GraphicsContext::new();
1846
1847 assert!(!ctx.uses_transparency());
1849
1850 ctx.set_fill_opacity(0.5);
1852 assert!(ctx.uses_transparency());
1853
1854 ctx.set_fill_opacity(1.0);
1856 assert!(!ctx.uses_transparency());
1857 ctx.set_stroke_opacity(0.8);
1858 assert!(ctx.uses_transparency());
1859
1860 ctx.set_fill_opacity(0.5);
1862 assert!(ctx.uses_transparency());
1863 }
1864
1865 #[test]
1866 fn test_generate_graphics_state_dict() {
1867 let mut ctx = GraphicsContext::new();
1868
1869 assert_eq!(ctx.generate_graphics_state_dict(), None);
1871
1872 ctx.set_fill_opacity(0.5);
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.500"));
1879 assert!(!dict.contains("/CA"));
1880
1881 ctx.set_fill_opacity(1.0);
1883 ctx.set_stroke_opacity(0.75);
1884 let dict = ctx
1885 .generate_graphics_state_dict()
1886 .expect("Writing to string should never fail");
1887 assert!(dict.contains("/Type /ExtGState"));
1888 assert!(dict.contains("/CA 0.750"));
1889 assert!(!dict.contains("/ca"));
1890
1891 ctx.set_fill_opacity(0.25);
1893 let dict = ctx
1894 .generate_graphics_state_dict()
1895 .expect("Writing to string should never fail");
1896 assert!(dict.contains("/Type /ExtGState"));
1897 assert!(dict.contains("/ca 0.250"));
1898 assert!(dict.contains("/CA 0.750"));
1899 }
1900
1901 #[test]
1902 fn test_opacity_with_graphics_operations() {
1903 let mut ctx = GraphicsContext::new();
1904
1905 ctx.set_fill_color(Color::red())
1906 .set_opacity(0.5)
1907 .rect(10.0, 10.0, 100.0, 100.0)
1908 .fill();
1909
1910 assert_eq!(ctx.fill_opacity(), 0.5);
1911 assert_eq!(ctx.stroke_opacity(), 0.5);
1912
1913 let ops = ctx.operations();
1914 assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
1915 assert!(ops.contains("1.000 0.000 0.000 rg")); assert!(ops.contains("f")); }
1918
1919 #[test]
1920 fn test_begin_end_text() {
1921 let mut ctx = GraphicsContext::new();
1922 ctx.begin_text();
1923 assert!(ctx.operations().contains("BT\n"));
1924
1925 ctx.end_text();
1926 assert!(ctx.operations().contains("ET\n"));
1927 }
1928
1929 #[test]
1930 fn test_set_font() {
1931 let mut ctx = GraphicsContext::new();
1932 ctx.set_font(Font::Helvetica, 12.0);
1933 assert!(ctx.operations().contains("/Helvetica 12 Tf\n"));
1934
1935 ctx.set_font(Font::TimesBold, 14.5);
1936 assert!(ctx.operations().contains("/Times-Bold 14.5 Tf\n"));
1937 }
1938
1939 #[test]
1940 fn test_set_text_position() {
1941 let mut ctx = GraphicsContext::new();
1942 ctx.set_text_position(100.0, 200.0);
1943 assert!(ctx.operations().contains("100.00 200.00 Td\n"));
1944 }
1945
1946 #[test]
1947 fn test_show_text() {
1948 let mut ctx = GraphicsContext::new();
1949 ctx.show_text("Hello World")
1950 .expect("Writing to string should never fail");
1951 assert!(ctx.operations().contains("(Hello World) Tj\n"));
1952 }
1953
1954 #[test]
1955 fn test_show_text_with_escaping() {
1956 let mut ctx = GraphicsContext::new();
1957 ctx.show_text("Test (parentheses)")
1958 .expect("Writing to string should never fail");
1959 assert!(ctx.operations().contains("(Test \\(parentheses\\)) Tj\n"));
1960
1961 ctx.clear();
1962 ctx.show_text("Back\\slash")
1963 .expect("Writing to string should never fail");
1964 assert!(ctx.operations().contains("(Back\\\\slash) Tj\n"));
1965
1966 ctx.clear();
1967 ctx.show_text("Line\nBreak")
1968 .expect("Writing to string should never fail");
1969 assert!(ctx.operations().contains("(Line\\nBreak) Tj\n"));
1970 }
1971
1972 #[test]
1973 fn test_text_operations_chaining() {
1974 let mut ctx = GraphicsContext::new();
1975 ctx.begin_text()
1976 .set_font(Font::Courier, 10.0)
1977 .set_text_position(50.0, 100.0)
1978 .show_text("Test")
1979 .unwrap()
1980 .end_text();
1981
1982 let ops = ctx.operations();
1983 assert!(ops.contains("BT\n"));
1984 assert!(ops.contains("/Courier 10 Tf\n"));
1985 assert!(ops.contains("50.00 100.00 Td\n"));
1986 assert!(ops.contains("(Test) Tj\n"));
1987 assert!(ops.contains("ET\n"));
1988 }
1989
1990 #[test]
1991 fn test_clip() {
1992 let mut ctx = GraphicsContext::new();
1993 ctx.clip();
1994 assert!(ctx.operations().contains("W\n"));
1995 }
1996
1997 #[test]
1998 fn test_clip_even_odd() {
1999 let mut ctx = GraphicsContext::new();
2000 ctx.clip_even_odd();
2001 assert!(ctx.operations().contains("W*\n"));
2002 }
2003
2004 #[test]
2005 fn test_clipping_with_path() {
2006 let mut ctx = GraphicsContext::new();
2007
2008 ctx.rect(10.0, 10.0, 100.0, 50.0).clip();
2010
2011 let ops = ctx.operations();
2012 assert!(ops.contains("10.00 10.00 100.00 50.00 re\n"));
2013 assert!(ops.contains("W\n"));
2014 }
2015
2016 #[test]
2017 fn test_clipping_even_odd_with_path() {
2018 let mut ctx = GraphicsContext::new();
2019
2020 ctx.move_to(0.0, 0.0)
2022 .line_to(100.0, 0.0)
2023 .line_to(100.0, 100.0)
2024 .line_to(0.0, 100.0)
2025 .close_path()
2026 .clip_even_odd();
2027
2028 let ops = ctx.operations();
2029 assert!(ops.contains("0.00 0.00 m\n"));
2030 assert!(ops.contains("100.00 0.00 l\n"));
2031 assert!(ops.contains("100.00 100.00 l\n"));
2032 assert!(ops.contains("0.00 100.00 l\n"));
2033 assert!(ops.contains("h\n"));
2034 assert!(ops.contains("W*\n"));
2035 }
2036
2037 #[test]
2038 fn test_clipping_chaining() {
2039 let mut ctx = GraphicsContext::new();
2040
2041 ctx.save_state()
2043 .rect(20.0, 20.0, 60.0, 60.0)
2044 .clip()
2045 .set_fill_color(Color::red())
2046 .rect(0.0, 0.0, 100.0, 100.0)
2047 .fill()
2048 .restore_state();
2049
2050 let ops = ctx.operations();
2051 assert!(ops.contains("q\n"));
2052 assert!(ops.contains("20.00 20.00 60.00 60.00 re\n"));
2053 assert!(ops.contains("W\n"));
2054 assert!(ops.contains("1.000 0.000 0.000 rg\n"));
2055 assert!(ops.contains("0.00 0.00 100.00 100.00 re\n"));
2056 assert!(ops.contains("f\n"));
2057 assert!(ops.contains("Q\n"));
2058 }
2059
2060 #[test]
2061 fn test_multiple_clipping_regions() {
2062 let mut ctx = GraphicsContext::new();
2063
2064 ctx.save_state()
2066 .rect(0.0, 0.0, 200.0, 200.0)
2067 .clip()
2068 .save_state()
2069 .circle(100.0, 100.0, 50.0)
2070 .clip_even_odd()
2071 .set_fill_color(Color::blue())
2072 .rect(50.0, 50.0, 100.0, 100.0)
2073 .fill()
2074 .restore_state()
2075 .restore_state();
2076
2077 let ops = ctx.operations();
2078 let q_count = ops.matches("q\n").count();
2080 let q_restore_count = ops.matches("Q\n").count();
2081 assert_eq!(q_count, 2);
2082 assert_eq!(q_restore_count, 2);
2083
2084 assert!(ops.contains("W\n"));
2086 assert!(ops.contains("W*\n"));
2087 }
2088
2089 #[test]
2092 fn test_move_to_and_line_to() {
2093 let mut ctx = GraphicsContext::new();
2094 ctx.move_to(100.0, 200.0).line_to(300.0, 400.0).stroke();
2095
2096 let ops = ctx
2097 .generate_operations()
2098 .expect("Writing to string should never fail");
2099 let ops_str = String::from_utf8_lossy(&ops);
2100 assert!(ops_str.contains("100.00 200.00 m"));
2101 assert!(ops_str.contains("300.00 400.00 l"));
2102 assert!(ops_str.contains("S"));
2103 }
2104
2105 #[test]
2106 fn test_bezier_curve() {
2107 let mut ctx = GraphicsContext::new();
2108 ctx.move_to(0.0, 0.0)
2109 .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0)
2110 .stroke();
2111
2112 let ops = ctx
2113 .generate_operations()
2114 .expect("Writing to string should never fail");
2115 let ops_str = String::from_utf8_lossy(&ops);
2116 assert!(ops_str.contains("0.00 0.00 m"));
2117 assert!(ops_str.contains("10.00 20.00 30.00 40.00 50.00 60.00 c"));
2118 assert!(ops_str.contains("S"));
2119 }
2120
2121 #[test]
2122 fn test_circle_path() {
2123 let mut ctx = GraphicsContext::new();
2124 ctx.circle(100.0, 100.0, 50.0).fill();
2125
2126 let ops = ctx
2127 .generate_operations()
2128 .expect("Writing to string should never fail");
2129 let ops_str = String::from_utf8_lossy(&ops);
2130 assert!(ops_str.contains(" c"));
2132 assert!(ops_str.contains("f"));
2133 }
2134
2135 #[test]
2136 fn test_path_closing() {
2137 let mut ctx = GraphicsContext::new();
2138 ctx.move_to(0.0, 0.0)
2139 .line_to(100.0, 0.0)
2140 .line_to(100.0, 100.0)
2141 .close_path()
2142 .stroke();
2143
2144 let ops = ctx
2145 .generate_operations()
2146 .expect("Writing to string should never fail");
2147 let ops_str = String::from_utf8_lossy(&ops);
2148 assert!(ops_str.contains("h")); assert!(ops_str.contains("S"));
2150 }
2151
2152 #[test]
2153 fn test_fill_and_stroke() {
2154 let mut ctx = GraphicsContext::new();
2155 ctx.rect(10.0, 10.0, 50.0, 50.0).fill_stroke();
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("10.00 10.00 50.00 50.00 re"));
2162 assert!(ops_str.contains("B")); }
2164
2165 #[test]
2166 fn test_color_settings() {
2167 let mut ctx = GraphicsContext::new();
2168 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2169 .set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
2170 .rect(10.0, 10.0, 50.0, 50.0)
2171 .fill_stroke(); assert_eq!(ctx.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2174 assert_eq!(ctx.stroke_color(), Color::rgb(0.0, 1.0, 0.0));
2175
2176 let ops = ctx
2177 .generate_operations()
2178 .expect("Writing to string should never fail");
2179 let ops_str = String::from_utf8_lossy(&ops);
2180 assert!(ops_str.contains("1.000 0.000 0.000 rg")); assert!(ops_str.contains("0.000 1.000 0.000 RG")); }
2183
2184 #[test]
2185 fn test_line_styles() {
2186 let mut ctx = GraphicsContext::new();
2187 ctx.set_line_width(2.5)
2188 .set_line_cap(LineCap::Round)
2189 .set_line_join(LineJoin::Bevel);
2190
2191 assert_eq!(ctx.line_width(), 2.5);
2192
2193 let ops = ctx
2194 .generate_operations()
2195 .expect("Writing to string should never fail");
2196 let ops_str = String::from_utf8_lossy(&ops);
2197 assert!(ops_str.contains("2.50 w")); assert!(ops_str.contains("1 J")); assert!(ops_str.contains("2 j")); }
2201
2202 #[test]
2203 fn test_opacity_settings() {
2204 let mut ctx = GraphicsContext::new();
2205 ctx.set_opacity(0.5);
2206
2207 assert_eq!(ctx.fill_opacity(), 0.5);
2208 assert_eq!(ctx.stroke_opacity(), 0.5);
2209 assert!(ctx.uses_transparency());
2210
2211 ctx.set_fill_opacity(0.7).set_stroke_opacity(0.3);
2212
2213 assert_eq!(ctx.fill_opacity(), 0.7);
2214 assert_eq!(ctx.stroke_opacity(), 0.3);
2215 }
2216
2217 #[test]
2218 fn test_state_save_restore() {
2219 let mut ctx = GraphicsContext::new();
2220 ctx.save_state()
2221 .set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2222 .restore_state();
2223
2224 let ops = ctx
2225 .generate_operations()
2226 .expect("Writing to string should never fail");
2227 let ops_str = String::from_utf8_lossy(&ops);
2228 assert!(ops_str.contains("q")); assert!(ops_str.contains("Q")); }
2231
2232 #[test]
2233 fn test_transformations() {
2234 let mut ctx = GraphicsContext::new();
2235 ctx.translate(100.0, 200.0).scale(2.0, 3.0).rotate(45.0);
2236
2237 let ops = ctx
2238 .generate_operations()
2239 .expect("Writing to string should never fail");
2240 let ops_str = String::from_utf8_lossy(&ops);
2241 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")); }
2245
2246 #[test]
2247 fn test_custom_transform() {
2248 let mut ctx = GraphicsContext::new();
2249 ctx.transform(1.0, 0.5, 0.5, 1.0, 10.0, 20.0);
2250
2251 let ops = ctx
2252 .generate_operations()
2253 .expect("Writing to string should never fail");
2254 let ops_str = String::from_utf8_lossy(&ops);
2255 assert!(ops_str.contains("1.00 0.50 0.50 1.00 10.00 20.00 cm"));
2256 }
2257
2258 #[test]
2259 fn test_rectangle_path() {
2260 let mut ctx = GraphicsContext::new();
2261 ctx.rectangle(25.0, 25.0, 150.0, 100.0).stroke();
2262
2263 let ops = ctx
2264 .generate_operations()
2265 .expect("Writing to string should never fail");
2266 let ops_str = String::from_utf8_lossy(&ops);
2267 assert!(ops_str.contains("25.00 25.00 150.00 100.00 re"));
2268 assert!(ops_str.contains("S"));
2269 }
2270
2271 #[test]
2272 fn test_empty_operations() {
2273 let ctx = GraphicsContext::new();
2274 let ops = ctx
2275 .generate_operations()
2276 .expect("Writing to string should never fail");
2277 assert!(ops.is_empty());
2278 }
2279
2280 #[test]
2281 fn test_complex_path_operations() {
2282 let mut ctx = GraphicsContext::new();
2283 ctx.move_to(50.0, 50.0)
2284 .line_to(100.0, 50.0)
2285 .curve_to(125.0, 50.0, 150.0, 75.0, 150.0, 100.0)
2286 .line_to(150.0, 150.0)
2287 .close_path()
2288 .fill();
2289
2290 let ops = ctx
2291 .generate_operations()
2292 .expect("Writing to string should never fail");
2293 let ops_str = String::from_utf8_lossy(&ops);
2294 assert!(ops_str.contains("50.00 50.00 m"));
2295 assert!(ops_str.contains("100.00 50.00 l"));
2296 assert!(ops_str.contains("125.00 50.00 150.00 75.00 150.00 100.00 c"));
2297 assert!(ops_str.contains("150.00 150.00 l"));
2298 assert!(ops_str.contains("h"));
2299 assert!(ops_str.contains("f"));
2300 }
2301
2302 #[test]
2303 fn test_graphics_state_dict_generation() {
2304 let mut ctx = GraphicsContext::new();
2305
2306 assert!(ctx.generate_graphics_state_dict().is_none());
2308
2309 ctx.set_opacity(0.5);
2311 let dict = ctx.generate_graphics_state_dict();
2312 assert!(dict.is_some());
2313 let dict_str = dict.expect("Writing to string should never fail");
2314 assert!(dict_str.contains("/ca 0.5"));
2315 assert!(dict_str.contains("/CA 0.5"));
2316 }
2317
2318 #[test]
2319 fn test_line_dash_pattern() {
2320 let mut ctx = GraphicsContext::new();
2321 let pattern = LineDashPattern {
2322 array: vec![3.0, 2.0],
2323 phase: 0.0,
2324 };
2325 ctx.set_line_dash_pattern(pattern);
2326
2327 let ops = ctx
2328 .generate_operations()
2329 .expect("Writing to string should never fail");
2330 let ops_str = String::from_utf8_lossy(&ops);
2331 assert!(ops_str.contains("[3.00 2.00] 0.00 d"));
2332 }
2333
2334 #[test]
2335 fn test_miter_limit_setting() {
2336 let mut ctx = GraphicsContext::new();
2337 ctx.set_miter_limit(4.0);
2338
2339 let ops = ctx
2340 .generate_operations()
2341 .expect("Writing to string should never fail");
2342 let ops_str = String::from_utf8_lossy(&ops);
2343 assert!(ops_str.contains("4.00 M"));
2344 }
2345
2346 #[test]
2347 fn test_line_cap_styles() {
2348 let mut ctx = GraphicsContext::new();
2349
2350 ctx.set_line_cap(LineCap::Butt);
2351 let ops = ctx
2352 .generate_operations()
2353 .expect("Writing to string should never fail");
2354 let ops_str = String::from_utf8_lossy(&ops);
2355 assert!(ops_str.contains("0 J"));
2356
2357 let mut ctx = GraphicsContext::new();
2358 ctx.set_line_cap(LineCap::Round);
2359 let ops = ctx
2360 .generate_operations()
2361 .expect("Writing to string should never fail");
2362 let ops_str = String::from_utf8_lossy(&ops);
2363 assert!(ops_str.contains("1 J"));
2364
2365 let mut ctx = GraphicsContext::new();
2366 ctx.set_line_cap(LineCap::Square);
2367 let ops = ctx
2368 .generate_operations()
2369 .expect("Writing to string should never fail");
2370 let ops_str = String::from_utf8_lossy(&ops);
2371 assert!(ops_str.contains("2 J"));
2372 }
2373
2374 #[test]
2375 fn test_transparency_groups() {
2376 let mut ctx = GraphicsContext::new();
2377
2378 let group = TransparencyGroup::new()
2380 .with_isolated(true)
2381 .with_opacity(0.5);
2382
2383 ctx.begin_transparency_group(group);
2384 assert!(ctx.in_transparency_group());
2385
2386 ctx.rect(10.0, 10.0, 100.0, 100.0);
2388 ctx.fill();
2389
2390 ctx.end_transparency_group();
2391 assert!(!ctx.in_transparency_group());
2392
2393 let ops = ctx.operations();
2395 assert!(ops.contains("% Begin Transparency Group"));
2396 assert!(ops.contains("% End Transparency Group"));
2397 }
2398
2399 #[test]
2400 fn test_nested_transparency_groups() {
2401 let mut ctx = GraphicsContext::new();
2402
2403 let group1 = TransparencyGroup::isolated().with_opacity(0.8);
2405 ctx.begin_transparency_group(group1);
2406 assert!(ctx.in_transparency_group());
2407
2408 let group2 = TransparencyGroup::knockout().with_blend_mode(BlendMode::Multiply);
2410 ctx.begin_transparency_group(group2);
2411
2412 ctx.circle(50.0, 50.0, 25.0);
2414 ctx.fill();
2415
2416 ctx.end_transparency_group();
2418 assert!(ctx.in_transparency_group()); ctx.end_transparency_group();
2422 assert!(!ctx.in_transparency_group());
2423 }
2424
2425 #[test]
2426 fn test_line_join_styles() {
2427 let mut ctx = GraphicsContext::new();
2428
2429 ctx.set_line_join(LineJoin::Miter);
2430 let ops = ctx
2431 .generate_operations()
2432 .expect("Writing to string should never fail");
2433 let ops_str = String::from_utf8_lossy(&ops);
2434 assert!(ops_str.contains("0 j"));
2435
2436 let mut ctx = GraphicsContext::new();
2437 ctx.set_line_join(LineJoin::Round);
2438 let ops = ctx
2439 .generate_operations()
2440 .expect("Writing to string should never fail");
2441 let ops_str = String::from_utf8_lossy(&ops);
2442 assert!(ops_str.contains("1 j"));
2443
2444 let mut ctx = GraphicsContext::new();
2445 ctx.set_line_join(LineJoin::Bevel);
2446 let ops = ctx
2447 .generate_operations()
2448 .expect("Writing to string should never fail");
2449 let ops_str = String::from_utf8_lossy(&ops);
2450 assert!(ops_str.contains("2 j"));
2451 }
2452
2453 #[test]
2454 fn test_rendering_intent() {
2455 let mut ctx = GraphicsContext::new();
2456
2457 ctx.set_rendering_intent(RenderingIntent::AbsoluteColorimetric);
2458 assert_eq!(
2459 ctx.rendering_intent(),
2460 RenderingIntent::AbsoluteColorimetric
2461 );
2462
2463 ctx.set_rendering_intent(RenderingIntent::Perceptual);
2464 assert_eq!(ctx.rendering_intent(), RenderingIntent::Perceptual);
2465
2466 ctx.set_rendering_intent(RenderingIntent::Saturation);
2467 assert_eq!(ctx.rendering_intent(), RenderingIntent::Saturation);
2468 }
2469
2470 #[test]
2471 fn test_flatness_tolerance() {
2472 let mut ctx = GraphicsContext::new();
2473
2474 ctx.set_flatness(0.5);
2475 assert_eq!(ctx.flatness(), 0.5);
2476
2477 let ops = ctx
2478 .generate_operations()
2479 .expect("Writing to string should never fail");
2480 let ops_str = String::from_utf8_lossy(&ops);
2481 assert!(ops_str.contains("0.50 i"));
2482 }
2483
2484 #[test]
2485 fn test_smoothness_tolerance() {
2486 let mut ctx = GraphicsContext::new();
2487
2488 let _ = ctx.set_smoothness(0.1);
2489 assert_eq!(ctx.smoothness(), 0.1);
2490 }
2491
2492 #[test]
2493 fn test_bezier_curves() {
2494 let mut ctx = GraphicsContext::new();
2495
2496 ctx.move_to(10.0, 10.0);
2498 ctx.curve_to(20.0, 10.0, 30.0, 20.0, 30.0, 30.0);
2499
2500 let ops = ctx
2501 .generate_operations()
2502 .expect("Writing to string should never fail");
2503 let ops_str = String::from_utf8_lossy(&ops);
2504 assert!(ops_str.contains("10.00 10.00 m"));
2505 assert!(ops_str.contains("c")); }
2507
2508 #[test]
2509 fn test_clipping_path() {
2510 let mut ctx = GraphicsContext::new();
2511
2512 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2513 ctx.clip();
2514
2515 let ops = ctx
2516 .generate_operations()
2517 .expect("Writing to string should never fail");
2518 let ops_str = String::from_utf8_lossy(&ops);
2519 assert!(ops_str.contains("W"));
2520 }
2521
2522 #[test]
2523 fn test_even_odd_clipping() {
2524 let mut ctx = GraphicsContext::new();
2525
2526 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2527 ctx.clip_even_odd();
2528
2529 let ops = ctx
2530 .generate_operations()
2531 .expect("Writing to string should never fail");
2532 let ops_str = String::from_utf8_lossy(&ops);
2533 assert!(ops_str.contains("W*"));
2534 }
2535
2536 #[test]
2537 fn test_color_creation() {
2538 let gray = Color::gray(0.5);
2540 assert_eq!(gray, Color::Gray(0.5));
2541
2542 let rgb = Color::rgb(0.2, 0.4, 0.6);
2543 assert_eq!(rgb, Color::Rgb(0.2, 0.4, 0.6));
2544
2545 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
2546 assert_eq!(cmyk, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
2547
2548 assert_eq!(Color::black(), Color::Gray(0.0));
2550 assert_eq!(Color::white(), Color::Gray(1.0));
2551 assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
2552 }
2553
2554 #[test]
2555 fn test_extended_graphics_state() {
2556 let ctx = GraphicsContext::new();
2557
2558 let _extgstate = ExtGState::new();
2560
2561 assert!(ctx.generate_operations().is_ok());
2563 }
2564
2565 #[test]
2566 fn test_path_construction_methods() {
2567 let mut ctx = GraphicsContext::new();
2568
2569 ctx.move_to(10.0, 10.0);
2571 ctx.line_to(20.0, 20.0);
2572 ctx.curve_to(30.0, 30.0, 40.0, 40.0, 50.0, 50.0);
2573 ctx.rect(60.0, 60.0, 30.0, 30.0);
2574 ctx.circle(100.0, 100.0, 25.0);
2575 ctx.close_path();
2576
2577 let ops = ctx
2578 .generate_operations()
2579 .expect("Writing to string should never fail");
2580 assert!(!ops.is_empty());
2581 }
2582
2583 #[test]
2584 fn test_graphics_context_clone_advanced() {
2585 let mut ctx = GraphicsContext::new();
2586 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
2587 ctx.set_line_width(5.0);
2588
2589 let cloned = ctx.clone();
2590 assert_eq!(cloned.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2591 assert_eq!(cloned.line_width(), 5.0);
2592 }
2593
2594 #[test]
2595 fn test_basic_drawing_operations() {
2596 let mut ctx = GraphicsContext::new();
2597
2598 ctx.move_to(50.0, 50.0);
2600 ctx.line_to(100.0, 100.0);
2601 ctx.stroke();
2602
2603 let ops = ctx
2604 .generate_operations()
2605 .expect("Writing to string should never fail");
2606 let ops_str = String::from_utf8_lossy(&ops);
2607 assert!(ops_str.contains("m")); assert!(ops_str.contains("l")); assert!(ops_str.contains("S")); }
2611
2612 #[test]
2613 fn test_graphics_state_stack() {
2614 let mut ctx = GraphicsContext::new();
2615
2616 ctx.set_fill_color(Color::black());
2618
2619 ctx.save_state();
2621 ctx.set_fill_color(Color::red());
2622 assert_eq!(ctx.fill_color(), Color::red());
2623
2624 ctx.save_state();
2626 ctx.set_fill_color(Color::blue());
2627 assert_eq!(ctx.fill_color(), Color::blue());
2628
2629 ctx.restore_state();
2631 assert_eq!(ctx.fill_color(), Color::red());
2632
2633 ctx.restore_state();
2635 assert_eq!(ctx.fill_color(), Color::black());
2636 }
2637
2638 #[test]
2639 fn test_word_spacing() {
2640 let mut ctx = GraphicsContext::new();
2641 ctx.set_word_spacing(2.5);
2642
2643 let ops = ctx.generate_operations().unwrap();
2644 let ops_str = String::from_utf8_lossy(&ops);
2645 assert!(ops_str.contains("2.50 Tw"));
2646 }
2647
2648 #[test]
2649 fn test_character_spacing() {
2650 let mut ctx = GraphicsContext::new();
2651 ctx.set_character_spacing(1.0);
2652
2653 let ops = ctx.generate_operations().unwrap();
2654 let ops_str = String::from_utf8_lossy(&ops);
2655 assert!(ops_str.contains("1.00 Tc"));
2656 }
2657
2658 #[test]
2659 fn test_justified_text() {
2660 let mut ctx = GraphicsContext::new();
2661 ctx.begin_text();
2662 ctx.set_text_position(100.0, 200.0);
2663 ctx.show_justified_text("Hello world from PDF", 200.0)
2664 .unwrap();
2665 ctx.end_text();
2666
2667 let ops = ctx.generate_operations().unwrap();
2668 let ops_str = String::from_utf8_lossy(&ops);
2669
2670 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")); }
2679
2680 #[test]
2681 fn test_justified_text_single_word() {
2682 let mut ctx = GraphicsContext::new();
2683 ctx.begin_text();
2684 ctx.show_justified_text("Hello", 200.0).unwrap();
2685 ctx.end_text();
2686
2687 let ops = ctx.generate_operations().unwrap();
2688 let ops_str = String::from_utf8_lossy(&ops);
2689
2690 assert!(ops_str.contains("(Hello) Tj"));
2692 assert_eq!(ops_str.matches("Tw").count(), 0);
2694 }
2695
2696 #[test]
2697 fn test_text_width_estimation() {
2698 let ctx = GraphicsContext::new();
2699 let width = ctx.estimate_text_width_simple("Hello");
2700
2701 assert!(width > 0.0);
2703 assert_eq!(width, 5.0 * 12.0 * 0.6); }
2705
2706 #[test]
2707 fn test_set_alpha_methods() {
2708 let mut ctx = GraphicsContext::new();
2709
2710 assert!(ctx.set_alpha(0.5).is_ok());
2712 assert!(ctx.set_alpha_fill(0.3).is_ok());
2713 assert!(ctx.set_alpha_stroke(0.7).is_ok());
2714
2715 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
2723 .set_alpha(0.5)
2724 .and_then(|c| c.set_alpha_fill(0.3))
2725 .and_then(|c| c.set_alpha_stroke(0.7));
2726 assert!(result.is_ok());
2727 }
2728
2729 #[test]
2730 fn test_alpha_methods_generate_extgstate() {
2731 let mut ctx = GraphicsContext::new();
2732
2733 ctx.set_alpha(0.5).unwrap();
2735
2736 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
2738
2739 let ops = ctx.generate_operations().unwrap();
2740 let ops_str = String::from_utf8_lossy(&ops);
2741
2742 assert!(ops_str.contains("/GS")); assert!(ops_str.contains(" gs\n")); ctx.clear();
2748 ctx.set_alpha_fill(0.3).unwrap();
2749 ctx.set_alpha_stroke(0.8).unwrap();
2750 ctx.rect(20.0, 20.0, 60.0, 60.0).fill_stroke();
2751
2752 let ops2 = ctx.generate_operations().unwrap();
2753 let ops_str2 = String::from_utf8_lossy(&ops2);
2754
2755 assert!(ops_str2.contains("/GS")); assert!(ops_str2.contains(" gs\n")); }
2759
2760 #[test]
2761 fn test_add_command() {
2762 let mut ctx = GraphicsContext::new();
2763
2764 ctx.add_command("1 0 0 1 100 200 cm");
2766 let ops = ctx.operations();
2767 assert!(ops.contains("1 0 0 1 100 200 cm\n"));
2768
2769 ctx.clear();
2771 ctx.add_command("q");
2772 assert_eq!(ctx.operations(), "q\n");
2773
2774 ctx.clear();
2776 ctx.add_command("");
2777 assert_eq!(ctx.operations(), "\n");
2778
2779 ctx.clear();
2781 ctx.add_command("Q\n");
2782 assert_eq!(ctx.operations(), "Q\n\n"); ctx.clear();
2786 ctx.add_command("q");
2787 ctx.add_command("1 0 0 1 50 50 cm");
2788 ctx.add_command("Q");
2789 assert_eq!(ctx.operations(), "q\n1 0 0 1 50 50 cm\nQ\n");
2790 }
2791
2792 #[test]
2793 fn test_get_operations() {
2794 let mut ctx = GraphicsContext::new();
2795 ctx.rect(10.0, 10.0, 50.0, 50.0);
2796 let ops1 = ctx.operations();
2797 let ops2 = ctx.get_operations();
2798 assert_eq!(ops1, ops2);
2799 }
2800
2801 #[test]
2802 fn test_set_line_solid() {
2803 let mut ctx = GraphicsContext::new();
2804 ctx.set_line_dash_pattern(LineDashPattern::new(vec![5.0, 3.0], 0.0));
2805 ctx.set_line_solid();
2806 let ops = ctx.operations();
2807 assert!(ops.contains("[] 0 d\n"));
2808 }
2809
2810 #[test]
2811 fn test_set_custom_font() {
2812 let mut ctx = GraphicsContext::new();
2813 ctx.set_custom_font("CustomFont", 14.0);
2814 assert_eq!(ctx.current_font_name.as_deref(), Some("CustomFont"));
2815 assert_eq!(ctx.current_font_size, 14.0);
2816 assert!(ctx.is_custom_font);
2817 }
2818
2819 #[test]
2820 fn test_show_text_standard_font_uses_literal_string() {
2821 let mut ctx = GraphicsContext::new();
2822 ctx.set_font(Font::Helvetica, 12.0);
2823 assert!(!ctx.is_custom_font);
2824
2825 ctx.begin_text();
2826 ctx.set_text_position(10.0, 20.0);
2827 ctx.show_text("Hello World").unwrap();
2828 ctx.end_text();
2829
2830 let ops = ctx.operations();
2831 assert!(ops.contains("(Hello World) Tj"));
2832 assert!(!ops.contains("<"));
2833 }
2834
2835 #[test]
2836 fn test_show_text_custom_font_uses_hex_encoding() {
2837 let mut ctx = GraphicsContext::new();
2838 ctx.set_font(Font::Custom("NotoSansCJK".to_string()), 12.0);
2839 assert!(ctx.is_custom_font);
2840
2841 ctx.begin_text();
2842 ctx.set_text_position(10.0, 20.0);
2843 ctx.show_text("你好").unwrap();
2845 ctx.end_text();
2846
2847 let ops = ctx.operations();
2848 assert!(
2850 ops.contains("<4F60597D> Tj"),
2851 "Expected hex encoding for CJK text, got: {}",
2852 ops
2853 );
2854 assert!(!ops.contains("(你好)"));
2855 }
2856
2857 #[test]
2858 fn test_show_text_custom_font_ascii_still_hex() {
2859 let mut ctx = GraphicsContext::new();
2860 ctx.set_font(Font::Custom("MyFont".to_string()), 10.0);
2861
2862 ctx.begin_text();
2863 ctx.set_text_position(0.0, 0.0);
2864 ctx.show_text("AB").unwrap();
2866 ctx.end_text();
2867
2868 let ops = ctx.operations();
2869 assert!(
2871 ops.contains("<00410042> Tj"),
2872 "Expected hex encoding for ASCII in custom font, got: {}",
2873 ops
2874 );
2875 }
2876
2877 #[test]
2878 fn test_show_text_tracks_used_characters() {
2879 let mut ctx = GraphicsContext::new();
2880 ctx.set_font(Font::Custom("CJKFont".to_string()), 12.0);
2881
2882 ctx.begin_text();
2883 ctx.show_text("你好A").unwrap();
2884 ctx.end_text();
2885
2886 assert!(ctx.used_characters.contains(&'你'));
2887 assert!(ctx.used_characters.contains(&'好'));
2888 assert!(ctx.used_characters.contains(&'A'));
2889 }
2890
2891 #[test]
2892 fn test_is_custom_font_toggles_correctly() {
2893 let mut ctx = GraphicsContext::new();
2894 assert!(!ctx.is_custom_font);
2895
2896 ctx.set_font(Font::Custom("CJK".to_string()), 12.0);
2897 assert!(ctx.is_custom_font);
2898
2899 ctx.set_font(Font::Helvetica, 12.0);
2900 assert!(!ctx.is_custom_font);
2901
2902 ctx.set_custom_font("AnotherCJK", 14.0);
2903 assert!(ctx.is_custom_font);
2904
2905 ctx.set_font(Font::CourierBold, 10.0);
2906 assert!(!ctx.is_custom_font);
2907 }
2908
2909 #[test]
2910 fn test_set_glyph_mapping() {
2911 let mut ctx = GraphicsContext::new();
2912
2913 assert!(ctx.glyph_mapping.is_none());
2915
2916 let mut mapping = HashMap::new();
2918 mapping.insert(65u32, 1u16); mapping.insert(66u32, 2u16); ctx.set_glyph_mapping(mapping.clone());
2921 assert!(ctx.glyph_mapping.is_some());
2922 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 2);
2923 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), Some(&1));
2924 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&66), Some(&2));
2925
2926 ctx.set_glyph_mapping(HashMap::new());
2928 assert!(ctx.glyph_mapping.is_some());
2929 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 0);
2930
2931 let mut new_mapping = HashMap::new();
2933 new_mapping.insert(67u32, 3u16); ctx.set_glyph_mapping(new_mapping);
2935 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 1);
2936 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&67), Some(&3));
2937 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), None); }
2939
2940 #[test]
2941 fn test_draw_text_basic() {
2942 let mut ctx = GraphicsContext::new();
2943 ctx.set_font(Font::Helvetica, 12.0);
2944
2945 let result = ctx.draw_text("Hello", 100.0, 200.0);
2946 assert!(result.is_ok());
2947
2948 let ops = ctx.operations();
2949 assert!(ops.contains("BT\n"));
2951 assert!(ops.contains("ET\n"));
2952
2953 assert!(ops.contains("/Helvetica"));
2955 assert!(ops.contains("12"));
2956 assert!(ops.contains("Tf\n"));
2957
2958 assert!(ops.contains("100"));
2960 assert!(ops.contains("200"));
2961 assert!(ops.contains("Td\n"));
2962
2963 assert!(ops.contains("(Hello)") || ops.contains("<48656c6c6f>")); }
2966
2967 #[test]
2968 fn test_draw_text_with_special_characters() {
2969 let mut ctx = GraphicsContext::new();
2970 ctx.set_font(Font::Helvetica, 12.0);
2971
2972 let result = ctx.draw_text("Test (with) parens", 50.0, 100.0);
2974 assert!(result.is_ok());
2975
2976 let ops = ctx.operations();
2977 assert!(ops.contains("\\(") || ops.contains("\\)") || ops.contains("<"));
2979 }
2981
2982 #[test]
2983 fn test_draw_text_unicode_detection() {
2984 let mut ctx = GraphicsContext::new();
2985 ctx.set_font(Font::Helvetica, 12.0);
2986
2987 ctx.draw_text("ASCII", 0.0, 0.0).unwrap();
2989 let _ops_ascii = ctx.operations();
2990
2991 ctx.clear();
2992
2993 ctx.set_font(Font::Helvetica, 12.0);
2995 ctx.draw_text("中文", 0.0, 0.0).unwrap();
2996 let ops_unicode = ctx.operations();
2997
2998 assert!(ops_unicode.contains("<") && ops_unicode.contains(">"));
3000 }
3001
3002 #[test]
3003 #[allow(deprecated)]
3004 fn test_draw_text_hex_encoding() {
3005 let mut ctx = GraphicsContext::new();
3006 ctx.set_font(Font::Helvetica, 12.0);
3007 let result = ctx.draw_text_hex("Test", 50.0, 100.0);
3008 assert!(result.is_ok());
3009 let ops = ctx.operations();
3010 assert!(ops.contains("<"));
3011 assert!(ops.contains(">"));
3012 }
3013
3014 #[test]
3015 #[allow(deprecated)]
3016 fn test_draw_text_cid() {
3017 let mut ctx = GraphicsContext::new();
3018 ctx.set_custom_font("CustomCIDFont", 12.0);
3019 let result = ctx.draw_text_cid("Test", 50.0, 100.0);
3020 assert!(result.is_ok());
3021 let ops = ctx.operations();
3022 assert!(ops.contains("BT\n"));
3023 assert!(ops.contains("ET\n"));
3024 }
3025
3026 #[test]
3027 #[allow(deprecated)]
3028 fn test_draw_text_unicode() {
3029 let mut ctx = GraphicsContext::new();
3030 ctx.set_custom_font("UnicodeFont", 12.0);
3031 let result = ctx.draw_text_unicode("Test \u{4E2D}\u{6587}", 50.0, 100.0);
3032 assert!(result.is_ok());
3033 let ops = ctx.operations();
3034 assert!(ops.contains("BT\n"));
3035 assert!(ops.contains("ET\n"));
3036 }
3037
3038 #[test]
3039 fn test_begin_end_transparency_group() {
3040 let mut ctx = GraphicsContext::new();
3041
3042 assert!(!ctx.in_transparency_group());
3044 assert!(ctx.current_transparency_group().is_none());
3045
3046 let group = TransparencyGroup::new();
3048 ctx.begin_transparency_group(group);
3049 assert!(ctx.in_transparency_group());
3050 assert!(ctx.current_transparency_group().is_some());
3051
3052 let ops = ctx.operations();
3054 assert!(ops.contains("% Begin Transparency Group"));
3055
3056 ctx.end_transparency_group();
3058 assert!(!ctx.in_transparency_group());
3059 assert!(ctx.current_transparency_group().is_none());
3060
3061 let ops_after = ctx.operations();
3063 assert!(ops_after.contains("% End Transparency Group"));
3064 }
3065
3066 #[test]
3067 fn test_transparency_group_nesting() {
3068 let mut ctx = GraphicsContext::new();
3069
3070 let group1 = TransparencyGroup::new();
3072 let group2 = TransparencyGroup::new();
3073 let group3 = TransparencyGroup::new();
3074
3075 ctx.begin_transparency_group(group1);
3076 assert_eq!(ctx.transparency_stack.len(), 1);
3077
3078 ctx.begin_transparency_group(group2);
3079 assert_eq!(ctx.transparency_stack.len(), 2);
3080
3081 ctx.begin_transparency_group(group3);
3082 assert_eq!(ctx.transparency_stack.len(), 3);
3083
3084 ctx.end_transparency_group();
3086 assert_eq!(ctx.transparency_stack.len(), 2);
3087
3088 ctx.end_transparency_group();
3089 assert_eq!(ctx.transparency_stack.len(), 1);
3090
3091 ctx.end_transparency_group();
3092 assert_eq!(ctx.transparency_stack.len(), 0);
3093 assert!(!ctx.in_transparency_group());
3094 }
3095
3096 #[test]
3097 fn test_transparency_group_without_begin() {
3098 let mut ctx = GraphicsContext::new();
3099
3100 assert!(!ctx.in_transparency_group());
3102 ctx.end_transparency_group();
3103 assert!(!ctx.in_transparency_group());
3104 }
3105
3106 #[test]
3107 fn test_extgstate_manager_access() {
3108 let ctx = GraphicsContext::new();
3109 let manager = ctx.extgstate_manager();
3110 assert_eq!(manager.count(), 0);
3111 }
3112
3113 #[test]
3114 fn test_extgstate_manager_mut_access() {
3115 let mut ctx = GraphicsContext::new();
3116 let manager = ctx.extgstate_manager_mut();
3117 assert_eq!(manager.count(), 0);
3118 }
3119
3120 #[test]
3121 fn test_has_extgstates() {
3122 let mut ctx = GraphicsContext::new();
3123
3124 assert!(!ctx.has_extgstates());
3126 assert_eq!(ctx.extgstate_manager().count(), 0);
3127
3128 ctx.set_alpha(0.5).unwrap();
3130 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3131 let result = ctx.generate_operations().unwrap();
3132
3133 assert!(ctx.has_extgstates());
3134 assert!(ctx.extgstate_manager().count() > 0);
3135
3136 let output = String::from_utf8_lossy(&result);
3138 assert!(output.contains("/GS")); assert!(output.contains(" gs\n")); }
3141
3142 #[test]
3143 fn test_generate_extgstate_resources() {
3144 let mut ctx = GraphicsContext::new();
3145 ctx.set_alpha(0.5).unwrap();
3146 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3147 ctx.generate_operations().unwrap();
3148
3149 let resources = ctx.generate_extgstate_resources();
3150 assert!(resources.is_ok());
3151 }
3152
3153 #[test]
3154 fn test_apply_extgstate() {
3155 let mut ctx = GraphicsContext::new();
3156
3157 let mut state = ExtGState::new();
3159 state.alpha_fill = Some(0.5);
3160 state.alpha_stroke = Some(0.8);
3161 state.blend_mode = Some(BlendMode::Multiply);
3162
3163 let result = ctx.apply_extgstate(state);
3164 assert!(result.is_ok());
3165
3166 assert!(ctx.has_extgstates());
3168 assert_eq!(ctx.extgstate_manager().count(), 1);
3169
3170 let mut state2 = ExtGState::new();
3172 state2.alpha_fill = Some(0.3);
3173 ctx.apply_extgstate(state2).unwrap();
3174
3175 assert_eq!(ctx.extgstate_manager().count(), 2);
3177 }
3178
3179 #[test]
3180 fn test_with_extgstate() {
3181 let mut ctx = GraphicsContext::new();
3182 let result = ctx.with_extgstate(|mut state| {
3183 state.alpha_fill = Some(0.5);
3184 state.alpha_stroke = Some(0.8);
3185 state
3186 });
3187 assert!(result.is_ok());
3188 }
3189
3190 #[test]
3191 fn test_set_blend_mode() {
3192 let mut ctx = GraphicsContext::new();
3193
3194 let result = ctx.set_blend_mode(BlendMode::Multiply);
3196 assert!(result.is_ok());
3197 assert!(ctx.has_extgstates());
3198
3199 ctx.clear();
3201 ctx.set_blend_mode(BlendMode::Screen).unwrap();
3202 ctx.rect(0.0, 0.0, 10.0, 10.0).fill();
3203 let ops = ctx.generate_operations().unwrap();
3204 let output = String::from_utf8_lossy(&ops);
3205
3206 assert!(output.contains("/GS"));
3208 assert!(output.contains(" gs\n"));
3209 }
3210
3211 #[test]
3212 fn test_render_table() {
3213 let mut ctx = GraphicsContext::new();
3214 let table = Table::with_equal_columns(2, 200.0);
3215 let result = ctx.render_table(&table);
3216 assert!(result.is_ok());
3217 }
3218
3219 #[test]
3220 fn test_render_list() {
3221 let mut ctx = GraphicsContext::new();
3222 use crate::text::{OrderedList, OrderedListStyle};
3223 let ordered = OrderedList::new(OrderedListStyle::Decimal);
3224 let list = ListElement::Ordered(ordered);
3225 let result = ctx.render_list(&list);
3226 assert!(result.is_ok());
3227 }
3228
3229 #[test]
3230 fn test_render_column_layout() {
3231 let mut ctx = GraphicsContext::new();
3232 use crate::text::ColumnContent;
3233 let layout = ColumnLayout::new(2, 100.0, 200.0);
3234 let content = ColumnContent::new("Test content");
3235 let result = ctx.render_column_layout(&layout, &content, 50.0, 50.0, 400.0);
3236 assert!(result.is_ok());
3237 }
3238
3239 #[test]
3240 fn test_clip_ellipse() {
3241 let mut ctx = GraphicsContext::new();
3242
3243 assert!(!ctx.has_clipping());
3245 assert!(ctx.clipping_path().is_none());
3246
3247 let result = ctx.clip_ellipse(100.0, 100.0, 50.0, 30.0);
3249 assert!(result.is_ok());
3250 assert!(ctx.has_clipping());
3251 assert!(ctx.clipping_path().is_some());
3252
3253 let ops = ctx.operations();
3255 assert!(ops.contains("W\n") || ops.contains("W*\n")); ctx.clear_clipping();
3259 assert!(!ctx.has_clipping());
3260 }
3261
3262 #[test]
3263 fn test_clipping_path_access() {
3264 let mut ctx = GraphicsContext::new();
3265
3266 assert!(ctx.clipping_path().is_none());
3268
3269 ctx.clip_rect(10.0, 10.0, 50.0, 50.0).unwrap();
3271 assert!(ctx.clipping_path().is_some());
3272
3273 ctx.clip_circle(100.0, 100.0, 25.0).unwrap();
3275 assert!(ctx.clipping_path().is_some());
3276
3277 ctx.save_state();
3279 ctx.clear_clipping();
3280 assert!(!ctx.has_clipping());
3281
3282 ctx.restore_state();
3283 assert!(ctx.has_clipping());
3285 }
3286
3287 #[test]
3290 fn test_edge_case_move_to_negative() {
3291 let mut ctx = GraphicsContext::new();
3292 ctx.move_to(-100.5, -200.25);
3293 assert!(ctx.operations().contains("-100.50 -200.25 m\n"));
3294 }
3295
3296 #[test]
3297 fn test_edge_case_opacity_out_of_range() {
3298 let mut ctx = GraphicsContext::new();
3299
3300 let _ = ctx.set_opacity(2.5);
3302 assert_eq!(ctx.fill_opacity(), 1.0);
3303
3304 let _ = ctx.set_opacity(-0.5);
3306 assert_eq!(ctx.fill_opacity(), 0.0);
3307 }
3308
3309 #[test]
3310 fn test_edge_case_line_width_extremes() {
3311 let mut ctx = GraphicsContext::new();
3312
3313 ctx.set_line_width(0.0);
3314 assert_eq!(ctx.line_width(), 0.0);
3315
3316 ctx.set_line_width(10000.0);
3317 assert_eq!(ctx.line_width(), 10000.0);
3318 }
3319
3320 #[test]
3323 fn test_interaction_transparency_plus_clipping() {
3324 let mut ctx = GraphicsContext::new();
3325
3326 ctx.set_alpha(0.5).unwrap();
3327 ctx.clip_rect(10.0, 10.0, 100.0, 100.0).unwrap();
3328 ctx.rect(20.0, 20.0, 80.0, 80.0).fill();
3329
3330 let ops = ctx.generate_operations().unwrap();
3331 let output = String::from_utf8_lossy(&ops);
3332
3333 assert!(output.contains("W\n") || output.contains("W*\n"));
3335 assert!(output.contains("/GS"));
3336 }
3337
3338 #[test]
3339 fn test_interaction_extgstate_plus_text() {
3340 let mut ctx = GraphicsContext::new();
3341
3342 let mut state = ExtGState::new();
3343 state.alpha_fill = Some(0.7);
3344 ctx.apply_extgstate(state).unwrap();
3345
3346 ctx.set_font(Font::Helvetica, 14.0);
3347 ctx.draw_text("Test", 100.0, 200.0).unwrap();
3348
3349 let ops = ctx.generate_operations().unwrap();
3350 let output = String::from_utf8_lossy(&ops);
3351
3352 assert!(output.contains("/GS"));
3353 assert!(output.contains("BT\n"));
3354 }
3355
3356 #[test]
3357 fn test_interaction_chained_transformations() {
3358 let mut ctx = GraphicsContext::new();
3359
3360 ctx.translate(50.0, 100.0);
3361 ctx.rotate(45.0);
3362 ctx.scale(2.0, 2.0);
3363
3364 let ops = ctx.operations();
3365 assert_eq!(ops.matches("cm\n").count(), 3);
3366 }
3367
3368 #[test]
3371 fn test_e2e_complete_page_with_header() {
3372 use crate::{Document, Page};
3373
3374 let mut doc = Document::new();
3375 let mut page = Page::a4();
3376 let ctx = page.graphics();
3377
3378 ctx.save_state();
3380 let _ = ctx.set_fill_opacity(0.3);
3381 ctx.set_fill_color(Color::rgb(200.0, 200.0, 255.0));
3382 ctx.rect(0.0, 750.0, 595.0, 42.0).fill();
3383 ctx.restore_state();
3384
3385 ctx.save_state();
3387 ctx.clip_rect(50.0, 50.0, 495.0, 692.0).unwrap();
3388 ctx.rect(60.0, 60.0, 100.0, 100.0).fill();
3389 ctx.restore_state();
3390
3391 let ops = ctx.generate_operations().unwrap();
3392 let output = String::from_utf8_lossy(&ops);
3393
3394 assert!(output.contains("q\n"));
3395 assert!(output.contains("Q\n"));
3396 assert!(output.contains("f\n"));
3397
3398 doc.add_page(page);
3399 assert!(doc.to_bytes().unwrap().len() > 0);
3400 }
3401
3402 #[test]
3403 fn test_e2e_watermark_workflow() {
3404 let mut ctx = GraphicsContext::new();
3405
3406 ctx.save_state();
3407 let _ = ctx.set_fill_opacity(0.2);
3408 ctx.translate(300.0, 400.0);
3409 ctx.rotate(45.0);
3410 ctx.set_font(Font::HelveticaBold, 72.0);
3411 ctx.draw_text("DRAFT", 0.0, 0.0).unwrap();
3412 ctx.restore_state();
3413
3414 let ops = ctx.generate_operations().unwrap();
3415 let output = String::from_utf8_lossy(&ops);
3416
3417 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")); }
3424
3425 #[test]
3428 fn test_set_custom_font_emits_tf_operator() {
3429 let mut ctx = GraphicsContext::new();
3430 ctx.set_custom_font("NotoSansCJK", 14.0);
3431
3432 let ops = ctx.operations();
3433 assert!(
3434 ops.contains("/NotoSansCJK 14 Tf"),
3435 "set_custom_font should emit Tf operator, got: {}",
3436 ops
3437 );
3438 }
3439
3440 #[test]
3443 fn test_draw_text_uses_is_custom_font_flag() {
3444 let mut ctx = GraphicsContext::new();
3445 ctx.set_custom_font("Helvetica", 12.0);
3447 ctx.clear(); ctx.draw_text("A", 10.0, 20.0).unwrap();
3450 let ops = ctx.operations();
3451 assert!(
3453 ops.contains("<0041> Tj"),
3454 "draw_text with is_custom_font=true should use hex, got: {}",
3455 ops
3456 );
3457 }
3458
3459 #[test]
3460 fn test_draw_text_standard_font_uses_literal() {
3461 let mut ctx = GraphicsContext::new();
3462 ctx.set_font(Font::Helvetica, 12.0);
3463 ctx.clear();
3464
3465 ctx.draw_text("Hello", 10.0, 20.0).unwrap();
3466 let ops = ctx.operations();
3467 assert!(
3468 ops.contains("(Hello) Tj"),
3469 "draw_text with standard font should use literal, got: {}",
3470 ops
3471 );
3472 }
3473
3474 #[test]
3477 fn test_show_text_smp_character_uses_surrogate_pairs() {
3478 let mut ctx = GraphicsContext::new();
3479 ctx.set_font(Font::Custom("Emoji".to_string()), 12.0);
3480
3481 ctx.begin_text();
3482 ctx.set_text_position(0.0, 0.0);
3483 ctx.show_text("\u{1F600}").unwrap();
3485 ctx.end_text();
3486
3487 let ops = ctx.operations();
3488 assert!(
3489 ops.contains("<D83DDE00> Tj"),
3490 "SMP character should use UTF-16BE surrogate pair, got: {}",
3491 ops
3492 );
3493 assert!(
3494 !ops.contains("FFFD"),
3495 "SMP character must NOT be replaced with FFFD"
3496 );
3497 }
3498
3499 #[test]
3502 fn test_save_restore_preserves_font_state() {
3503 let mut ctx = GraphicsContext::new();
3504 ctx.set_font(Font::Custom("CJK".to_string()), 12.0);
3505 assert!(ctx.is_custom_font);
3506 assert_eq!(ctx.current_font_name.as_deref(), Some("CJK"));
3507 assert_eq!(ctx.current_font_size, 12.0);
3508
3509 ctx.save_state();
3510 ctx.set_font(Font::Helvetica, 10.0);
3511 assert!(!ctx.is_custom_font);
3512 assert_eq!(ctx.current_font_name.as_deref(), Some("Helvetica"));
3513
3514 ctx.restore_state();
3515 assert!(
3516 ctx.is_custom_font,
3517 "is_custom_font must be restored after restore_state"
3518 );
3519 assert_eq!(ctx.current_font_name.as_deref(), Some("CJK"));
3520 assert_eq!(ctx.current_font_size, 12.0);
3521 }
3522
3523 #[test]
3524 fn test_save_restore_mixed_font_encoding() {
3525 let mut ctx = GraphicsContext::new();
3526 ctx.set_font(Font::Custom("CJK".to_string()), 12.0);
3527
3528 ctx.save_state();
3530 ctx.set_font(Font::Helvetica, 10.0);
3531 ctx.begin_text();
3532 ctx.show_text("Hello").unwrap();
3533 ctx.end_text();
3534 ctx.restore_state();
3535
3536 ctx.begin_text();
3538 ctx.show_text("你好").unwrap();
3539 ctx.end_text();
3540
3541 let ops = ctx.operations();
3542 assert!(
3544 ops.contains("<4F60597D> Tj"),
3545 "After restore_state, CJK text should use hex encoding, got: {}",
3546 ops
3547 );
3548 }
3549
3550 #[test]
3551 fn test_graphics_state_arc_str_save_restore() {
3552 let mut ctx = GraphicsContext::new();
3555
3556 ctx.set_font(Font::Custom("TestFont".to_string()), 14.0);
3558 assert_eq!(ctx.current_font_name.as_deref(), Some("TestFont"));
3559 assert!(ctx.is_custom_font);
3560
3561 ctx.save_state();
3563 ctx.set_font(Font::Custom("Other".to_string()), 10.0);
3564 assert_eq!(ctx.current_font_name.as_deref(), Some("Other"));
3565
3566 ctx.restore_state();
3568 assert_eq!(
3569 ctx.current_font_name.as_deref(),
3570 Some("TestFont"),
3571 "Font name must be restored to TestFont after restore_state"
3572 );
3573 assert_eq!(ctx.current_font_size, 14.0);
3574 assert!(
3575 ctx.is_custom_font,
3576 "is_custom_font must be restored to true"
3577 );
3578
3579 if let Some(ref arc) = ctx.current_font_name {
3581 let cloned = arc.clone();
3582 assert_eq!(arc.as_ref(), cloned.as_ref());
3583 assert!(Arc::ptr_eq(arc, &cloned));
3585 }
3586 }
3587}