1pub mod calibrated_color;
2pub mod clipping;
3mod color;
4mod color_profiles;
5pub mod devicen_color;
6pub mod extraction;
7pub mod form_xobject;
8mod indexed_color;
9pub mod lab_color;
10mod path;
11mod patterns;
12mod pdf_image;
13mod png_decoder;
14pub mod separation_color;
15mod shadings;
16pub mod soft_mask;
17pub mod state;
18pub mod transparency;
19
20pub use calibrated_color::{CalGrayColorSpace, CalRgbColorSpace, CalibratedColor};
21pub use clipping::{ClippingPath, ClippingRegion};
22pub use color::Color;
23pub use color_profiles::{IccColorSpace, IccProfile, IccProfileManager, StandardIccProfile};
24pub use devicen_color::{
25 AlternateColorSpace as DeviceNAlternateColorSpace, ColorantDefinition, ColorantType,
26 DeviceNAttributes, DeviceNColorSpace, LinearTransform, SampledFunction, TintTransformFunction,
27};
28pub use form_xobject::{
29 FormTemplates, FormXObject, FormXObjectBuilder, FormXObjectManager,
30 TransparencyGroup as FormTransparencyGroup,
31};
32pub use indexed_color::{BaseColorSpace, ColorLookupTable, IndexedColorManager, IndexedColorSpace};
33pub use lab_color::{LabColor, LabColorSpace};
34pub use path::{LineCap, LineJoin, PathBuilder, PathCommand, WindingRule};
35pub use patterns::{
36 PaintType, PatternGraphicsContext, PatternManager, PatternMatrix, PatternType, TilingPattern,
37 TilingType,
38};
39pub use pdf_image::{ColorSpace, Image, ImageFormat, MaskType};
40pub use separation_color::{
41 AlternateColorSpace, SeparationColor, SeparationColorSpace, SpotColors, TintTransform,
42};
43pub use shadings::{
44 AxialShading, ColorStop, FunctionBasedShading, Point, RadialShading, ShadingDefinition,
45 ShadingManager, ShadingPattern, ShadingType,
46};
47pub use soft_mask::{SoftMask, SoftMaskState, SoftMaskType};
48pub use state::{
49 BlendMode, ExtGState, ExtGStateFont, ExtGStateManager, Halftone, LineDashPattern,
50 RenderingIntent, TransferFunction,
51};
52pub use transparency::TransparencyGroup;
53use transparency::TransparencyGroupState;
54
55use crate::error::Result;
56use crate::text::{ColumnContent, ColumnLayout, Font, FontManager, ListElement, Table};
57use std::collections::{HashMap, HashSet};
58use std::fmt::Write;
59use std::sync::Arc;
60
61#[derive(Clone)]
62pub struct GraphicsContext {
63 operations: String,
64 current_color: Color,
65 stroke_color: Color,
66 line_width: f64,
67 fill_opacity: f64,
68 stroke_opacity: f64,
69 extgstate_manager: ExtGStateManager,
71 pending_extgstate: Option<ExtGState>,
72 current_dash_pattern: Option<LineDashPattern>,
73 current_miter_limit: f64,
74 current_line_cap: LineCap,
75 current_line_join: LineJoin,
76 current_rendering_intent: RenderingIntent,
77 current_flatness: f64,
78 current_smoothness: f64,
79 clipping_region: ClippingRegion,
81 font_manager: Option<Arc<FontManager>>,
83 state_stack: Vec<(Color, Color, Option<String>, f64, bool)>,
85 current_font_name: Option<String>,
86 current_font_size: f64,
87 is_custom_font: bool,
89 used_characters: HashSet<char>,
91 glyph_mapping: Option<HashMap<u32, u16>>,
93 transparency_stack: Vec<TransparencyGroupState>,
95}
96
97fn encode_char_as_cid(ch: char, buf: &mut String) {
101 let code = ch as u32;
102 if code <= 0xFFFF {
103 write!(buf, "{:04X}", code).expect("Writing to string should never fail");
104 } else {
105 let adjusted = code - 0x10000;
107 let high = ((adjusted >> 10) & 0x3FF) + 0xD800;
108 let low = (adjusted & 0x3FF) + 0xDC00;
109 write!(buf, "{:04X}{:04X}", high, low).expect("Writing to string should never fail");
110 }
111}
112
113impl Default for GraphicsContext {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl GraphicsContext {
120 pub fn new() -> Self {
121 Self {
122 operations: String::new(),
123 current_color: Color::black(),
124 stroke_color: Color::black(),
125 line_width: 1.0,
126 fill_opacity: 1.0,
127 stroke_opacity: 1.0,
128 extgstate_manager: ExtGStateManager::new(),
130 pending_extgstate: None,
131 current_dash_pattern: None,
132 current_miter_limit: 10.0,
133 current_line_cap: LineCap::Butt,
134 current_line_join: LineJoin::Miter,
135 current_rendering_intent: RenderingIntent::RelativeColorimetric,
136 current_flatness: 1.0,
137 current_smoothness: 0.0,
138 clipping_region: ClippingRegion::new(),
140 font_manager: None,
142 state_stack: Vec::new(),
143 current_font_name: None,
144 current_font_size: 12.0,
145 is_custom_font: false,
146 used_characters: HashSet::new(),
147 glyph_mapping: None,
148 transparency_stack: Vec::new(),
149 }
150 }
151
152 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
153 writeln!(&mut self.operations, "{x:.2} {y:.2} m")
154 .expect("Writing to string should never fail");
155 self
156 }
157
158 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
159 writeln!(&mut self.operations, "{x:.2} {y:.2} l")
160 .expect("Writing to string should never fail");
161 self
162 }
163
164 pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
165 writeln!(
166 &mut self.operations,
167 "{x1:.2} {y1:.2} {x2:.2} {y2:.2} {x3:.2} {y3:.2} c"
168 )
169 .expect("Writing to string should never fail");
170 self
171 }
172
173 pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
174 writeln!(
175 &mut self.operations,
176 "{x:.2} {y:.2} {width:.2} {height:.2} re"
177 )
178 .expect("Writing to string should never fail");
179 self
180 }
181
182 pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
183 let k = 0.552284749831;
184 let r = radius;
185
186 self.move_to(cx + r, cy);
187 self.curve_to(cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r);
188 self.curve_to(cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy);
189 self.curve_to(cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r);
190 self.curve_to(cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy);
191 self.close_path()
192 }
193
194 pub fn close_path(&mut self) -> &mut Self {
195 self.operations.push_str("h\n");
196 self
197 }
198
199 pub fn stroke(&mut self) -> &mut Self {
200 self.apply_pending_extgstate().unwrap_or_default();
201 self.apply_stroke_color();
202 self.operations.push_str("S\n");
203 self
204 }
205
206 pub fn fill(&mut self) -> &mut Self {
207 self.apply_pending_extgstate().unwrap_or_default();
208 self.apply_fill_color();
209 self.operations.push_str("f\n");
210 self
211 }
212
213 pub fn fill_stroke(&mut self) -> &mut Self {
214 self.apply_pending_extgstate().unwrap_or_default();
215 self.apply_fill_color();
216 self.apply_stroke_color();
217 self.operations.push_str("B\n");
218 self
219 }
220
221 pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
222 self.stroke_color = color;
223 self
224 }
225
226 pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
227 self.current_color = color;
228 self
229 }
230
231 pub fn set_fill_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
233 let cs_name = match &color {
235 CalibratedColor::Gray(_, _) => "CalGray1",
236 CalibratedColor::Rgb(_, _) => "CalRGB1",
237 };
238
239 writeln!(&mut self.operations, "/{} cs", cs_name)
241 .expect("Writing to string should never fail");
242
243 let values = color.values();
245 for value in &values {
246 write!(&mut self.operations, "{:.4} ", value)
247 .expect("Writing to string should never fail");
248 }
249 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
250
251 self
252 }
253
254 pub fn set_stroke_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
256 let cs_name = match &color {
258 CalibratedColor::Gray(_, _) => "CalGray1",
259 CalibratedColor::Rgb(_, _) => "CalRGB1",
260 };
261
262 writeln!(&mut self.operations, "/{} CS", cs_name)
264 .expect("Writing to string should never fail");
265
266 let values = color.values();
268 for value in &values {
269 write!(&mut self.operations, "{:.4} ", value)
270 .expect("Writing to string should never fail");
271 }
272 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
273
274 self
275 }
276
277 pub fn set_fill_color_lab(&mut self, color: LabColor) -> &mut Self {
279 writeln!(&mut self.operations, "/Lab1 cs").expect("Writing to string should never fail");
281
282 let values = color.values();
284 for value in &values {
285 write!(&mut self.operations, "{:.4} ", value)
286 .expect("Writing to string should never fail");
287 }
288 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
289
290 self
291 }
292
293 pub fn set_stroke_color_lab(&mut self, color: LabColor) -> &mut Self {
295 writeln!(&mut self.operations, "/Lab1 CS").expect("Writing to string should never fail");
297
298 let values = color.values();
300 for value in &values {
301 write!(&mut self.operations, "{:.4} ", value)
302 .expect("Writing to string should never fail");
303 }
304 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
305
306 self
307 }
308
309 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
310 self.line_width = width;
311 writeln!(&mut self.operations, "{width:.2} w")
312 .expect("Writing to string should never fail");
313 self
314 }
315
316 pub fn set_line_cap(&mut self, cap: LineCap) -> &mut Self {
317 self.current_line_cap = cap;
318 writeln!(&mut self.operations, "{} J", cap as u8)
319 .expect("Writing to string should never fail");
320 self
321 }
322
323 pub fn set_line_join(&mut self, join: LineJoin) -> &mut Self {
324 self.current_line_join = join;
325 writeln!(&mut self.operations, "{} j", join as u8)
326 .expect("Writing to string should never fail");
327 self
328 }
329
330 pub fn set_opacity(&mut self, opacity: f64) -> &mut Self {
332 let opacity = opacity.clamp(0.0, 1.0);
333 self.fill_opacity = opacity;
334 self.stroke_opacity = opacity;
335
336 if opacity < 1.0 {
338 let mut state = ExtGState::new();
339 state.alpha_fill = Some(opacity);
340 state.alpha_stroke = Some(opacity);
341 self.pending_extgstate = Some(state);
342 }
343
344 self
345 }
346
347 pub fn set_fill_opacity(&mut self, opacity: f64) -> &mut Self {
349 self.fill_opacity = opacity.clamp(0.0, 1.0);
350
351 if opacity < 1.0 {
353 if let Some(ref mut state) = self.pending_extgstate {
354 state.alpha_fill = Some(opacity);
355 } else {
356 let mut state = ExtGState::new();
357 state.alpha_fill = Some(opacity);
358 self.pending_extgstate = Some(state);
359 }
360 }
361
362 self
363 }
364
365 pub fn set_stroke_opacity(&mut self, opacity: f64) -> &mut Self {
367 self.stroke_opacity = opacity.clamp(0.0, 1.0);
368
369 if opacity < 1.0 {
371 if let Some(ref mut state) = self.pending_extgstate {
372 state.alpha_stroke = Some(opacity);
373 } else {
374 let mut state = ExtGState::new();
375 state.alpha_stroke = Some(opacity);
376 self.pending_extgstate = Some(state);
377 }
378 }
379
380 self
381 }
382
383 pub fn save_state(&mut self) -> &mut Self {
384 self.operations.push_str("q\n");
385 self.save_clipping_state();
386 self.state_stack.push((
388 self.current_color,
389 self.stroke_color,
390 self.current_font_name.clone(),
391 self.current_font_size,
392 self.is_custom_font,
393 ));
394 self
395 }
396
397 pub fn restore_state(&mut self) -> &mut Self {
398 self.operations.push_str("Q\n");
399 self.restore_clipping_state();
400 if let Some((fill, stroke, font_name, font_size, is_custom)) = self.state_stack.pop() {
402 self.current_color = fill;
403 self.stroke_color = stroke;
404 self.current_font_name = font_name;
405 self.current_font_size = font_size;
406 self.is_custom_font = is_custom;
407 }
408 self
409 }
410
411 pub fn begin_transparency_group(&mut self, group: TransparencyGroup) -> &mut Self {
414 self.save_state();
416
417 writeln!(&mut self.operations, "% Begin Transparency Group")
419 .expect("Writing to string should never fail");
420
421 let mut extgstate = ExtGState::new();
423 extgstate = extgstate.with_blend_mode(group.blend_mode.clone());
424 extgstate.alpha_fill = Some(group.opacity as f64);
425 extgstate.alpha_stroke = Some(group.opacity as f64);
426
427 self.pending_extgstate = Some(extgstate);
429 let _ = self.apply_pending_extgstate();
430
431 let mut group_state = TransparencyGroupState::new(group);
433 group_state.saved_state = self.operations.as_bytes().to_vec();
435 self.transparency_stack.push(group_state);
436
437 self
438 }
439
440 pub fn end_transparency_group(&mut self) -> &mut Self {
442 if let Some(_group_state) = self.transparency_stack.pop() {
443 writeln!(&mut self.operations, "% End Transparency Group")
445 .expect("Writing to string should never fail");
446
447 self.restore_state();
449 }
450 self
451 }
452
453 pub fn in_transparency_group(&self) -> bool {
455 !self.transparency_stack.is_empty()
456 }
457
458 pub fn current_transparency_group(&self) -> Option<&TransparencyGroup> {
460 self.transparency_stack.last().map(|state| &state.group)
461 }
462
463 pub fn translate(&mut self, tx: f64, ty: f64) -> &mut Self {
464 writeln!(&mut self.operations, "1 0 0 1 {tx:.2} {ty:.2} cm")
465 .expect("Writing to string should never fail");
466 self
467 }
468
469 pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
470 writeln!(&mut self.operations, "{sx:.2} 0 0 {sy:.2} 0 0 cm")
471 .expect("Writing to string should never fail");
472 self
473 }
474
475 pub fn rotate(&mut self, angle: f64) -> &mut Self {
476 let cos = angle.cos();
477 let sin = angle.sin();
478 writeln!(
479 &mut self.operations,
480 "{:.6} {:.6} {:.6} {:.6} 0 0 cm",
481 cos, sin, -sin, cos
482 )
483 .expect("Writing to string should never fail");
484 self
485 }
486
487 pub fn transform(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> &mut Self {
488 writeln!(
489 &mut self.operations,
490 "{a:.2} {b:.2} {c:.2} {d:.2} {e:.2} {f:.2} cm"
491 )
492 .expect("Writing to string should never fail");
493 self
494 }
495
496 pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
497 self.rect(x, y, width, height)
498 }
499
500 pub fn draw_image(
501 &mut self,
502 image_name: &str,
503 x: f64,
504 y: f64,
505 width: f64,
506 height: f64,
507 ) -> &mut Self {
508 self.save_state();
510
511 writeln!(
514 &mut self.operations,
515 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
516 )
517 .expect("Writing to string should never fail");
518
519 writeln!(&mut self.operations, "/{image_name} Do")
521 .expect("Writing to string should never fail");
522
523 self.restore_state();
525
526 self
527 }
528
529 pub fn draw_image_with_transparency(
532 &mut self,
533 image_name: &str,
534 x: f64,
535 y: f64,
536 width: f64,
537 height: f64,
538 mask_name: Option<&str>,
539 ) -> &mut Self {
540 self.save_state();
542
543 if let Some(mask) = mask_name {
545 let mut extgstate = ExtGState::new();
547 extgstate.set_soft_mask_name(mask.to_string());
548
549 let gs_name = self
551 .extgstate_manager
552 .add_state(extgstate)
553 .unwrap_or_else(|_| "GS1".to_string());
554 writeln!(&mut self.operations, "/{} gs", gs_name)
555 .expect("Writing to string should never fail");
556 }
557
558 writeln!(
560 &mut self.operations,
561 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
562 )
563 .expect("Writing to string should never fail");
564
565 writeln!(&mut self.operations, "/{image_name} Do")
567 .expect("Writing to string should never fail");
568
569 if mask_name.is_some() {
571 let mut reset_extgstate = ExtGState::new();
573 reset_extgstate.set_soft_mask_none();
574
575 let gs_name = self
576 .extgstate_manager
577 .add_state(reset_extgstate)
578 .unwrap_or_else(|_| "GS2".to_string());
579 writeln!(&mut self.operations, "/{} gs", gs_name)
580 .expect("Writing to string should never fail");
581 }
582
583 self.restore_state();
585
586 self
587 }
588
589 fn apply_stroke_color(&mut self) {
590 match self.stroke_color {
591 Color::Rgb(r, g, b) => {
592 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} RG")
593 .expect("Writing to string should never fail");
594 }
595 Color::Gray(g) => {
596 writeln!(&mut self.operations, "{g:.3} G")
597 .expect("Writing to string should never fail");
598 }
599 Color::Cmyk(c, m, y, k) => {
600 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} K")
601 .expect("Writing to string should never fail");
602 }
603 }
604 }
605
606 fn apply_fill_color(&mut self) {
607 match self.current_color {
608 Color::Rgb(r, g, b) => {
609 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} rg")
610 .expect("Writing to string should never fail");
611 }
612 Color::Gray(g) => {
613 writeln!(&mut self.operations, "{g:.3} g")
614 .expect("Writing to string should never fail");
615 }
616 Color::Cmyk(c, m, y, k) => {
617 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} k")
618 .expect("Writing to string should never fail");
619 }
620 }
621 }
622
623 pub(crate) fn generate_operations(&self) -> Result<Vec<u8>> {
624 Ok(self.operations.as_bytes().to_vec())
625 }
626
627 pub fn uses_transparency(&self) -> bool {
629 self.fill_opacity < 1.0 || self.stroke_opacity < 1.0
630 }
631
632 pub fn generate_graphics_state_dict(&self) -> Option<String> {
634 if !self.uses_transparency() {
635 return None;
636 }
637
638 let mut dict = String::from("<< /Type /ExtGState");
639
640 if self.fill_opacity < 1.0 {
641 write!(&mut dict, " /ca {:.3}", self.fill_opacity)
642 .expect("Writing to string should never fail");
643 }
644
645 if self.stroke_opacity < 1.0 {
646 write!(&mut dict, " /CA {:.3}", self.stroke_opacity)
647 .expect("Writing to string should never fail");
648 }
649
650 dict.push_str(" >>");
651 Some(dict)
652 }
653
654 pub fn fill_color(&self) -> Color {
656 self.current_color
657 }
658
659 pub fn stroke_color(&self) -> Color {
661 self.stroke_color
662 }
663
664 pub fn line_width(&self) -> f64 {
666 self.line_width
667 }
668
669 pub fn fill_opacity(&self) -> f64 {
671 self.fill_opacity
672 }
673
674 pub fn stroke_opacity(&self) -> f64 {
676 self.stroke_opacity
677 }
678
679 pub fn operations(&self) -> &str {
681 &self.operations
682 }
683
684 pub fn get_operations(&self) -> &str {
686 &self.operations
687 }
688
689 pub fn clear(&mut self) {
691 self.operations.clear();
692 }
693
694 pub fn begin_text(&mut self) -> &mut Self {
696 self.operations.push_str("BT\n");
697 self
698 }
699
700 pub fn end_text(&mut self) -> &mut Self {
702 self.operations.push_str("ET\n");
703 self
704 }
705
706 pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
708 writeln!(&mut self.operations, "/{} {} Tf", font.pdf_name(), size)
709 .expect("Writing to string should never fail");
710
711 match &font {
713 Font::Custom(name) => {
714 self.current_font_name = Some(name.clone());
715 self.current_font_size = size;
716 self.is_custom_font = true;
717 }
718 _ => {
719 self.current_font_name = Some(font.pdf_name());
720 self.current_font_size = size;
721 self.is_custom_font = false;
722 }
723 }
724
725 self
726 }
727
728 pub fn set_text_position(&mut self, x: f64, y: f64) -> &mut Self {
730 writeln!(&mut self.operations, "{x:.2} {y:.2} Td")
731 .expect("Writing to string should never fail");
732 self
733 }
734
735 pub fn show_text(&mut self, text: &str) -> Result<&mut Self> {
742 self.used_characters.extend(text.chars());
744
745 if self.is_custom_font {
746 self.operations.push('<');
748 for ch in text.chars() {
749 encode_char_as_cid(ch, &mut self.operations);
750 }
751 self.operations.push_str("> Tj\n");
752 } else {
753 self.operations.push('(');
755 for ch in text.chars() {
756 match ch {
757 '(' => self.operations.push_str("\\("),
758 ')' => self.operations.push_str("\\)"),
759 '\\' => self.operations.push_str("\\\\"),
760 '\n' => self.operations.push_str("\\n"),
761 '\r' => self.operations.push_str("\\r"),
762 '\t' => self.operations.push_str("\\t"),
763 _ => self.operations.push(ch),
764 }
765 }
766 self.operations.push_str(") Tj\n");
767 }
768 Ok(self)
769 }
770
771 pub fn set_word_spacing(&mut self, spacing: f64) -> &mut Self {
773 writeln!(&mut self.operations, "{spacing:.2} Tw")
774 .expect("Writing to string should never fail");
775 self
776 }
777
778 pub fn set_character_spacing(&mut self, spacing: f64) -> &mut Self {
780 writeln!(&mut self.operations, "{spacing:.2} Tc")
781 .expect("Writing to string should never fail");
782 self
783 }
784
785 pub fn show_justified_text(&mut self, text: &str, target_width: f64) -> Result<&mut Self> {
787 let words: Vec<&str> = text.split_whitespace().collect();
789 if words.len() <= 1 {
790 return self.show_text(text);
792 }
793
794 let text_without_spaces = words.join("");
796 let natural_text_width = self.estimate_text_width_simple(&text_without_spaces);
797 let space_width = self.estimate_text_width_simple(" ");
798 let natural_width = natural_text_width + (space_width * (words.len() - 1) as f64);
799
800 let extra_space_needed = target_width - natural_width;
802 let word_gaps = (words.len() - 1) as f64;
803
804 if word_gaps > 0.0 && extra_space_needed > 0.0 {
805 let extra_word_spacing = extra_space_needed / word_gaps;
806
807 self.set_word_spacing(extra_word_spacing);
809
810 self.show_text(text)?;
812
813 self.set_word_spacing(0.0);
815 } else {
816 self.show_text(text)?;
818 }
819
820 Ok(self)
821 }
822
823 fn estimate_text_width_simple(&self, text: &str) -> f64 {
825 let font_size = self.current_font_size;
828 text.len() as f64 * font_size * 0.6 }
830
831 pub fn render_table(&mut self, table: &Table) -> Result<()> {
833 table.render(self)
834 }
835
836 pub fn render_list(&mut self, list: &ListElement) -> Result<()> {
838 match list {
839 ListElement::Ordered(ordered) => ordered.render(self),
840 ListElement::Unordered(unordered) => unordered.render(self),
841 }
842 }
843
844 pub fn render_column_layout(
846 &mut self,
847 layout: &ColumnLayout,
848 content: &ColumnContent,
849 x: f64,
850 y: f64,
851 height: f64,
852 ) -> Result<()> {
853 layout.render(self, content, x, y, height)
854 }
855
856 pub fn set_line_dash_pattern(&mut self, pattern: LineDashPattern) -> &mut Self {
860 self.current_dash_pattern = Some(pattern.clone());
861 writeln!(&mut self.operations, "{} d", pattern.to_pdf_string())
862 .expect("Writing to string should never fail");
863 self
864 }
865
866 pub fn set_line_solid(&mut self) -> &mut Self {
868 self.current_dash_pattern = None;
869 self.operations.push_str("[] 0 d\n");
870 self
871 }
872
873 pub fn set_miter_limit(&mut self, limit: f64) -> &mut Self {
875 self.current_miter_limit = limit.max(1.0);
876 writeln!(&mut self.operations, "{:.2} M", self.current_miter_limit)
877 .expect("Writing to string should never fail");
878 self
879 }
880
881 pub fn set_rendering_intent(&mut self, intent: RenderingIntent) -> &mut Self {
883 self.current_rendering_intent = intent;
884 writeln!(&mut self.operations, "/{} ri", intent.pdf_name())
885 .expect("Writing to string should never fail");
886 self
887 }
888
889 pub fn set_flatness(&mut self, flatness: f64) -> &mut Self {
891 self.current_flatness = flatness.clamp(0.0, 100.0);
892 writeln!(&mut self.operations, "{:.2} i", self.current_flatness)
893 .expect("Writing to string should never fail");
894 self
895 }
896
897 pub fn apply_extgstate(&mut self, state: ExtGState) -> Result<&mut Self> {
899 let state_name = self.extgstate_manager.add_state(state)?;
900 writeln!(&mut self.operations, "/{state_name} gs")
901 .expect("Writing to string should never fail");
902 Ok(self)
903 }
904
905 #[allow(dead_code)]
907 fn set_pending_extgstate(&mut self, state: ExtGState) {
908 self.pending_extgstate = Some(state);
909 }
910
911 fn apply_pending_extgstate(&mut self) -> Result<()> {
913 if let Some(state) = self.pending_extgstate.take() {
914 let state_name = self.extgstate_manager.add_state(state)?;
915 writeln!(&mut self.operations, "/{state_name} gs")
916 .expect("Writing to string should never fail");
917 }
918 Ok(())
919 }
920
921 pub fn with_extgstate<F>(&mut self, builder: F) -> Result<&mut Self>
923 where
924 F: FnOnce(ExtGState) -> ExtGState,
925 {
926 let state = builder(ExtGState::new());
927 self.apply_extgstate(state)
928 }
929
930 pub fn set_blend_mode(&mut self, mode: BlendMode) -> Result<&mut Self> {
932 let state = ExtGState::new().with_blend_mode(mode);
933 self.apply_extgstate(state)
934 }
935
936 pub fn set_alpha(&mut self, alpha: f64) -> Result<&mut Self> {
938 let state = ExtGState::new().with_alpha(alpha);
939 self.apply_extgstate(state)
940 }
941
942 pub fn set_alpha_stroke(&mut self, alpha: f64) -> Result<&mut Self> {
944 let state = ExtGState::new().with_alpha_stroke(alpha);
945 self.apply_extgstate(state)
946 }
947
948 pub fn set_alpha_fill(&mut self, alpha: f64) -> Result<&mut Self> {
950 let state = ExtGState::new().with_alpha_fill(alpha);
951 self.apply_extgstate(state)
952 }
953
954 pub fn set_overprint_stroke(&mut self, overprint: bool) -> Result<&mut Self> {
956 let state = ExtGState::new().with_overprint_stroke(overprint);
957 self.apply_extgstate(state)
958 }
959
960 pub fn set_overprint_fill(&mut self, overprint: bool) -> Result<&mut Self> {
962 let state = ExtGState::new().with_overprint_fill(overprint);
963 self.apply_extgstate(state)
964 }
965
966 pub fn set_stroke_adjustment(&mut self, adjustment: bool) -> Result<&mut Self> {
968 let state = ExtGState::new().with_stroke_adjustment(adjustment);
969 self.apply_extgstate(state)
970 }
971
972 pub fn set_smoothness(&mut self, smoothness: f64) -> Result<&mut Self> {
974 self.current_smoothness = smoothness.clamp(0.0, 1.0);
975 let state = ExtGState::new().with_smoothness(self.current_smoothness);
976 self.apply_extgstate(state)
977 }
978
979 pub fn line_dash_pattern(&self) -> Option<&LineDashPattern> {
983 self.current_dash_pattern.as_ref()
984 }
985
986 pub fn miter_limit(&self) -> f64 {
988 self.current_miter_limit
989 }
990
991 pub fn line_cap(&self) -> LineCap {
993 self.current_line_cap
994 }
995
996 pub fn line_join(&self) -> LineJoin {
998 self.current_line_join
999 }
1000
1001 pub fn rendering_intent(&self) -> RenderingIntent {
1003 self.current_rendering_intent
1004 }
1005
1006 pub fn flatness(&self) -> f64 {
1008 self.current_flatness
1009 }
1010
1011 pub fn smoothness(&self) -> f64 {
1013 self.current_smoothness
1014 }
1015
1016 pub fn extgstate_manager(&self) -> &ExtGStateManager {
1018 &self.extgstate_manager
1019 }
1020
1021 pub fn extgstate_manager_mut(&mut self) -> &mut ExtGStateManager {
1023 &mut self.extgstate_manager
1024 }
1025
1026 pub fn generate_extgstate_resources(&self) -> Result<String> {
1028 self.extgstate_manager.to_resource_dictionary()
1029 }
1030
1031 pub fn has_extgstates(&self) -> bool {
1033 self.extgstate_manager.count() > 0
1034 }
1035
1036 pub fn add_command(&mut self, command: &str) {
1038 self.operations.push_str(command);
1039 self.operations.push('\n');
1040 }
1041
1042 pub fn clip(&mut self) -> &mut Self {
1044 self.operations.push_str("W\n");
1045 self
1046 }
1047
1048 pub fn clip_even_odd(&mut self) -> &mut Self {
1050 self.operations.push_str("W*\n");
1051 self
1052 }
1053
1054 pub fn clip_stroke(&mut self) -> &mut Self {
1056 self.apply_stroke_color();
1057 self.operations.push_str("W S\n");
1058 self
1059 }
1060
1061 pub fn set_clipping_path(&mut self, path: ClippingPath) -> Result<&mut Self> {
1063 let ops = path.to_pdf_operations()?;
1064 self.operations.push_str(&ops);
1065 self.clipping_region.set_clip(path);
1066 Ok(self)
1067 }
1068
1069 pub fn clear_clipping(&mut self) -> &mut Self {
1071 self.clipping_region.clear_clip();
1072 self
1073 }
1074
1075 fn save_clipping_state(&mut self) {
1077 self.clipping_region.save();
1078 }
1079
1080 fn restore_clipping_state(&mut self) {
1082 self.clipping_region.restore();
1083 }
1084
1085 pub fn clip_rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> Result<&mut Self> {
1087 let path = ClippingPath::rect(x, y, width, height);
1088 self.set_clipping_path(path)
1089 }
1090
1091 pub fn clip_circle(&mut self, cx: f64, cy: f64, radius: f64) -> Result<&mut Self> {
1093 let path = ClippingPath::circle(cx, cy, radius);
1094 self.set_clipping_path(path)
1095 }
1096
1097 pub fn clip_ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) -> Result<&mut Self> {
1099 let path = ClippingPath::ellipse(cx, cy, rx, ry);
1100 self.set_clipping_path(path)
1101 }
1102
1103 pub fn has_clipping(&self) -> bool {
1105 self.clipping_region.has_clip()
1106 }
1107
1108 pub fn clipping_path(&self) -> Option<&ClippingPath> {
1110 self.clipping_region.current()
1111 }
1112
1113 pub fn set_font_manager(&mut self, font_manager: Arc<FontManager>) -> &mut Self {
1115 self.font_manager = Some(font_manager);
1116 self
1117 }
1118
1119 pub fn set_custom_font(&mut self, font_name: &str, size: f64) -> &mut Self {
1121 writeln!(&mut self.operations, "/{} {} Tf", font_name, size)
1123 .expect("Writing to string should never fail");
1124
1125 self.current_font_name = Some(font_name.to_string());
1126 self.current_font_size = size;
1127 self.is_custom_font = true;
1128
1129 if let Some(ref font_manager) = self.font_manager {
1131 if let Some(mapping) = font_manager.get_font_glyph_mapping(font_name) {
1132 self.glyph_mapping = Some(mapping);
1133 }
1134 }
1135
1136 self
1137 }
1138
1139 pub fn set_glyph_mapping(&mut self, mapping: HashMap<u32, u16>) -> &mut Self {
1141 self.glyph_mapping = Some(mapping);
1142 self
1143 }
1144
1145 pub fn draw_text(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1147 self.used_characters.extend(text.chars());
1149
1150 let needs_unicode = self.is_custom_font || text.chars().any(|c| c as u32 > 255);
1153
1154 if needs_unicode {
1156 self.draw_with_unicode_encoding(text, x, y)
1157 } else {
1158 self.draw_with_simple_encoding(text, x, y)
1159 }
1160 }
1161
1162 fn draw_with_simple_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1164 let has_unicode = text.chars().any(|c| c as u32 > 255);
1166
1167 if has_unicode {
1168 tracing::debug!("Warning: Text contains Unicode characters but using Latin-1 font. Characters will be replaced with '?'");
1170 }
1171
1172 self.operations.push_str("BT\n");
1174
1175 self.apply_fill_color();
1177
1178 if let Some(font_name) = &self.current_font_name {
1180 writeln!(
1181 &mut self.operations,
1182 "/{} {} Tf",
1183 font_name, self.current_font_size
1184 )
1185 .expect("Writing to string should never fail");
1186 } else {
1187 writeln!(
1188 &mut self.operations,
1189 "/Helvetica {} Tf",
1190 self.current_font_size
1191 )
1192 .expect("Writing to string should never fail");
1193 }
1194
1195 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1197 .expect("Writing to string should never fail");
1198
1199 self.operations.push('(');
1202 for ch in text.chars() {
1203 let code = ch as u32;
1204 if code <= 127 {
1205 match ch {
1207 '(' => self.operations.push_str("\\("),
1208 ')' => self.operations.push_str("\\)"),
1209 '\\' => self.operations.push_str("\\\\"),
1210 '\n' => self.operations.push_str("\\n"),
1211 '\r' => self.operations.push_str("\\r"),
1212 '\t' => self.operations.push_str("\\t"),
1213 _ => self.operations.push(ch),
1214 }
1215 } else if code <= 255 {
1216 write!(&mut self.operations, "\\{:03o}", code)
1219 .expect("Writing to string should never fail");
1220 } else {
1221 self.operations.push('?');
1223 }
1224 }
1225 self.operations.push_str(") Tj\n");
1226
1227 self.operations.push_str("ET\n");
1229
1230 Ok(self)
1231 }
1232
1233 fn draw_with_unicode_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1235 self.operations.push_str("BT\n");
1237
1238 self.apply_fill_color();
1240
1241 if let Some(font_name) = &self.current_font_name {
1243 writeln!(
1245 &mut self.operations,
1246 "/{} {} Tf",
1247 font_name, self.current_font_size
1248 )
1249 .expect("Writing to string should never fail");
1250 } else {
1251 writeln!(
1252 &mut self.operations,
1253 "/Helvetica {} Tf",
1254 self.current_font_size
1255 )
1256 .expect("Writing to string should never fail");
1257 }
1258
1259 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1261 .expect("Writing to string should never fail");
1262
1263 self.operations.push('<');
1266 for ch in text.chars() {
1267 encode_char_as_cid(ch, &mut self.operations);
1268 }
1269 self.operations.push_str("> Tj\n");
1270
1271 self.operations.push_str("ET\n");
1273
1274 Ok(self)
1275 }
1276
1277 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1279 pub fn draw_text_hex(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1280 self.operations.push_str("BT\n");
1282
1283 self.apply_fill_color();
1285
1286 if let Some(font_name) = &self.current_font_name {
1288 writeln!(
1289 &mut self.operations,
1290 "/{} {} Tf",
1291 font_name, self.current_font_size
1292 )
1293 .expect("Writing to string should never fail");
1294 } else {
1295 writeln!(
1297 &mut self.operations,
1298 "/Helvetica {} Tf",
1299 self.current_font_size
1300 )
1301 .expect("Writing to string should never fail");
1302 }
1303
1304 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1306 .expect("Writing to string should never fail");
1307
1308 self.operations.push('<');
1312 for ch in text.chars() {
1313 if ch as u32 <= 255 {
1314 write!(&mut self.operations, "{:02X}", ch as u8)
1316 .expect("Writing to string should never fail");
1317 } else {
1318 write!(&mut self.operations, "3F").expect("Writing to string should never fail");
1321 }
1323 }
1324 self.operations.push_str("> Tj\n");
1325
1326 self.operations.push_str("ET\n");
1328
1329 Ok(self)
1330 }
1331
1332 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1334 pub fn draw_text_cid(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1335 use crate::fonts::needs_type0_font;
1336
1337 self.operations.push_str("BT\n");
1339
1340 self.apply_fill_color();
1342
1343 if let Some(font_name) = &self.current_font_name {
1345 writeln!(
1346 &mut self.operations,
1347 "/{} {} Tf",
1348 font_name, self.current_font_size
1349 )
1350 .expect("Writing to string should never fail");
1351 } else {
1352 writeln!(
1353 &mut self.operations,
1354 "/Helvetica {} Tf",
1355 self.current_font_size
1356 )
1357 .expect("Writing to string should never fail");
1358 }
1359
1360 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1362 .expect("Writing to string should never fail");
1363
1364 if needs_type0_font(text) {
1366 self.operations.push('<');
1368 for ch in text.chars() {
1369 encode_char_as_cid(ch, &mut self.operations);
1370 }
1371 self.operations.push_str("> Tj\n");
1372 } else {
1373 self.operations.push('<');
1375 for ch in text.chars() {
1376 if ch as u32 <= 255 {
1377 write!(&mut self.operations, "{:02X}", ch as u8)
1378 .expect("Writing to string should never fail");
1379 } else {
1380 write!(&mut self.operations, "3F")
1381 .expect("Writing to string should never fail");
1382 }
1383 }
1384 self.operations.push_str("> Tj\n");
1385 }
1386
1387 self.operations.push_str("ET\n");
1389 Ok(self)
1390 }
1391
1392 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1394 pub fn draw_text_unicode(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1395 self.operations.push_str("BT\n");
1397
1398 self.apply_fill_color();
1400
1401 if let Some(font_name) = &self.current_font_name {
1403 writeln!(
1404 &mut self.operations,
1405 "/{} {} Tf",
1406 font_name, self.current_font_size
1407 )
1408 .expect("Writing to string should never fail");
1409 } else {
1410 writeln!(
1412 &mut self.operations,
1413 "/Helvetica {} Tf",
1414 self.current_font_size
1415 )
1416 .expect("Writing to string should never fail");
1417 }
1418
1419 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1421 .expect("Writing to string should never fail");
1422
1423 self.operations.push('<');
1425 let mut utf16_buffer = [0u16; 2];
1426 for ch in text.chars() {
1427 let encoded = ch.encode_utf16(&mut utf16_buffer);
1428 for unit in encoded {
1429 write!(&mut self.operations, "{:04X}", unit)
1431 .expect("Writing to string should never fail");
1432 }
1433 }
1434 self.operations.push_str("> Tj\n");
1435
1436 self.operations.push_str("ET\n");
1438
1439 Ok(self)
1440 }
1441
1442 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1444 if self.used_characters.is_empty() {
1445 None
1446 } else {
1447 Some(self.used_characters.clone())
1448 }
1449 }
1450}
1451
1452#[cfg(test)]
1453mod tests {
1454 use super::*;
1455
1456 #[test]
1457 fn test_graphics_context_new() {
1458 let ctx = GraphicsContext::new();
1459 assert_eq!(ctx.fill_color(), Color::black());
1460 assert_eq!(ctx.stroke_color(), Color::black());
1461 assert_eq!(ctx.line_width(), 1.0);
1462 assert_eq!(ctx.fill_opacity(), 1.0);
1463 assert_eq!(ctx.stroke_opacity(), 1.0);
1464 assert!(ctx.operations().is_empty());
1465 }
1466
1467 #[test]
1468 fn test_graphics_context_default() {
1469 let ctx = GraphicsContext::default();
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 }
1474
1475 #[test]
1476 fn test_move_to() {
1477 let mut ctx = GraphicsContext::new();
1478 ctx.move_to(10.0, 20.0);
1479 assert!(ctx.operations().contains("10.00 20.00 m\n"));
1480 }
1481
1482 #[test]
1483 fn test_line_to() {
1484 let mut ctx = GraphicsContext::new();
1485 ctx.line_to(30.0, 40.0);
1486 assert!(ctx.operations().contains("30.00 40.00 l\n"));
1487 }
1488
1489 #[test]
1490 fn test_curve_to() {
1491 let mut ctx = GraphicsContext::new();
1492 ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
1493 assert!(ctx
1494 .operations()
1495 .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
1496 }
1497
1498 #[test]
1499 fn test_rect() {
1500 let mut ctx = GraphicsContext::new();
1501 ctx.rect(10.0, 20.0, 100.0, 50.0);
1502 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1503 }
1504
1505 #[test]
1506 fn test_rectangle_alias() {
1507 let mut ctx = GraphicsContext::new();
1508 ctx.rectangle(10.0, 20.0, 100.0, 50.0);
1509 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1510 }
1511
1512 #[test]
1513 fn test_circle() {
1514 let mut ctx = GraphicsContext::new();
1515 ctx.circle(50.0, 50.0, 25.0);
1516
1517 let ops = ctx.operations();
1518 assert!(ops.contains("75.00 50.00 m\n"));
1520 assert!(ops.contains(" c\n"));
1522 assert!(ops.contains("h\n"));
1524 }
1525
1526 #[test]
1527 fn test_close_path() {
1528 let mut ctx = GraphicsContext::new();
1529 ctx.close_path();
1530 assert!(ctx.operations().contains("h\n"));
1531 }
1532
1533 #[test]
1534 fn test_stroke() {
1535 let mut ctx = GraphicsContext::new();
1536 ctx.set_stroke_color(Color::red());
1537 ctx.rect(0.0, 0.0, 10.0, 10.0);
1538 ctx.stroke();
1539
1540 let ops = ctx.operations();
1541 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1542 assert!(ops.contains("S\n"));
1543 }
1544
1545 #[test]
1546 fn test_fill() {
1547 let mut ctx = GraphicsContext::new();
1548 ctx.set_fill_color(Color::blue());
1549 ctx.rect(0.0, 0.0, 10.0, 10.0);
1550 ctx.fill();
1551
1552 let ops = ctx.operations();
1553 assert!(ops.contains("0.000 0.000 1.000 rg\n"));
1554 assert!(ops.contains("f\n"));
1555 }
1556
1557 #[test]
1558 fn test_fill_stroke() {
1559 let mut ctx = GraphicsContext::new();
1560 ctx.set_fill_color(Color::green());
1561 ctx.set_stroke_color(Color::red());
1562 ctx.rect(0.0, 0.0, 10.0, 10.0);
1563 ctx.fill_stroke();
1564
1565 let ops = ctx.operations();
1566 assert!(ops.contains("0.000 1.000 0.000 rg\n"));
1567 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1568 assert!(ops.contains("B\n"));
1569 }
1570
1571 #[test]
1572 fn test_set_stroke_color() {
1573 let mut ctx = GraphicsContext::new();
1574 ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
1575 assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
1576 }
1577
1578 #[test]
1579 fn test_set_fill_color() {
1580 let mut ctx = GraphicsContext::new();
1581 ctx.set_fill_color(Color::gray(0.5));
1582 assert_eq!(ctx.fill_color(), Color::Gray(0.5));
1583 }
1584
1585 #[test]
1586 fn test_set_line_width() {
1587 let mut ctx = GraphicsContext::new();
1588 ctx.set_line_width(2.5);
1589 assert_eq!(ctx.line_width(), 2.5);
1590 assert!(ctx.operations().contains("2.50 w\n"));
1591 }
1592
1593 #[test]
1594 fn test_set_line_cap() {
1595 let mut ctx = GraphicsContext::new();
1596 ctx.set_line_cap(LineCap::Round);
1597 assert!(ctx.operations().contains("1 J\n"));
1598
1599 ctx.set_line_cap(LineCap::Butt);
1600 assert!(ctx.operations().contains("0 J\n"));
1601
1602 ctx.set_line_cap(LineCap::Square);
1603 assert!(ctx.operations().contains("2 J\n"));
1604 }
1605
1606 #[test]
1607 fn test_set_line_join() {
1608 let mut ctx = GraphicsContext::new();
1609 ctx.set_line_join(LineJoin::Round);
1610 assert!(ctx.operations().contains("1 j\n"));
1611
1612 ctx.set_line_join(LineJoin::Miter);
1613 assert!(ctx.operations().contains("0 j\n"));
1614
1615 ctx.set_line_join(LineJoin::Bevel);
1616 assert!(ctx.operations().contains("2 j\n"));
1617 }
1618
1619 #[test]
1620 fn test_save_restore_state() {
1621 let mut ctx = GraphicsContext::new();
1622 ctx.save_state();
1623 assert!(ctx.operations().contains("q\n"));
1624
1625 ctx.restore_state();
1626 assert!(ctx.operations().contains("Q\n"));
1627 }
1628
1629 #[test]
1630 fn test_translate() {
1631 let mut ctx = GraphicsContext::new();
1632 ctx.translate(50.0, 100.0);
1633 assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
1634 }
1635
1636 #[test]
1637 fn test_scale() {
1638 let mut ctx = GraphicsContext::new();
1639 ctx.scale(2.0, 3.0);
1640 assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
1641 }
1642
1643 #[test]
1644 fn test_rotate() {
1645 let mut ctx = GraphicsContext::new();
1646 let angle = std::f64::consts::PI / 4.0; ctx.rotate(angle);
1648
1649 let ops = ctx.operations();
1650 assert!(ops.contains(" cm\n"));
1651 assert!(ops.contains("0.707107")); }
1654
1655 #[test]
1656 fn test_transform() {
1657 let mut ctx = GraphicsContext::new();
1658 ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
1659 assert!(ctx
1660 .operations()
1661 .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
1662 }
1663
1664 #[test]
1665 fn test_draw_image() {
1666 let mut ctx = GraphicsContext::new();
1667 ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
1668
1669 let ops = ctx.operations();
1670 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")); }
1675
1676 #[test]
1677 fn test_gray_color_operations() {
1678 let mut ctx = GraphicsContext::new();
1679 ctx.set_stroke_color(Color::gray(0.5));
1680 ctx.set_fill_color(Color::gray(0.7));
1681 ctx.stroke();
1682 ctx.fill();
1683
1684 let ops = ctx.operations();
1685 assert!(ops.contains("0.500 G\n")); assert!(ops.contains("0.700 g\n")); }
1688
1689 #[test]
1690 fn test_cmyk_color_operations() {
1691 let mut ctx = GraphicsContext::new();
1692 ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
1693 ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
1694 ctx.stroke();
1695 ctx.fill();
1696
1697 let ops = ctx.operations();
1698 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")); }
1701
1702 #[test]
1703 fn test_method_chaining() {
1704 let mut ctx = GraphicsContext::new();
1705 ctx.move_to(0.0, 0.0)
1706 .line_to(10.0, 0.0)
1707 .line_to(10.0, 10.0)
1708 .line_to(0.0, 10.0)
1709 .close_path()
1710 .set_fill_color(Color::red())
1711 .fill();
1712
1713 let ops = ctx.operations();
1714 assert!(ops.contains("0.00 0.00 m\n"));
1715 assert!(ops.contains("10.00 0.00 l\n"));
1716 assert!(ops.contains("10.00 10.00 l\n"));
1717 assert!(ops.contains("0.00 10.00 l\n"));
1718 assert!(ops.contains("h\n"));
1719 assert!(ops.contains("f\n"));
1720 }
1721
1722 #[test]
1723 fn test_generate_operations() {
1724 let mut ctx = GraphicsContext::new();
1725 ctx.rect(0.0, 0.0, 10.0, 10.0);
1726
1727 let result = ctx.generate_operations();
1728 assert!(result.is_ok());
1729 let bytes = result.expect("Writing to string should never fail");
1730 let ops_string = String::from_utf8(bytes).expect("Writing to string should never fail");
1731 assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
1732 }
1733
1734 #[test]
1735 fn test_clear_operations() {
1736 let mut ctx = GraphicsContext::new();
1737 ctx.rect(0.0, 0.0, 10.0, 10.0);
1738 assert!(!ctx.operations().is_empty());
1739
1740 ctx.clear();
1741 assert!(ctx.operations().is_empty());
1742 }
1743
1744 #[test]
1745 fn test_complex_path() {
1746 let mut ctx = GraphicsContext::new();
1747 ctx.save_state()
1748 .translate(100.0, 100.0)
1749 .rotate(std::f64::consts::PI / 6.0)
1750 .scale(2.0, 2.0)
1751 .set_line_width(2.0)
1752 .set_stroke_color(Color::blue())
1753 .move_to(0.0, 0.0)
1754 .line_to(50.0, 0.0)
1755 .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
1756 .close_path()
1757 .stroke()
1758 .restore_state();
1759
1760 let ops = ctx.operations();
1761 assert!(ops.contains("q\n"));
1762 assert!(ops.contains("cm\n"));
1763 assert!(ops.contains("2.00 w\n"));
1764 assert!(ops.contains("0.000 0.000 1.000 RG\n"));
1765 assert!(ops.contains("S\n"));
1766 assert!(ops.contains("Q\n"));
1767 }
1768
1769 #[test]
1770 fn test_graphics_context_clone() {
1771 let mut ctx = GraphicsContext::new();
1772 ctx.set_fill_color(Color::red());
1773 ctx.set_stroke_color(Color::blue());
1774 ctx.set_line_width(3.0);
1775 ctx.set_opacity(0.5);
1776 ctx.rect(0.0, 0.0, 10.0, 10.0);
1777
1778 let ctx_clone = ctx.clone();
1779 assert_eq!(ctx_clone.fill_color(), Color::red());
1780 assert_eq!(ctx_clone.stroke_color(), Color::blue());
1781 assert_eq!(ctx_clone.line_width(), 3.0);
1782 assert_eq!(ctx_clone.fill_opacity(), 0.5);
1783 assert_eq!(ctx_clone.stroke_opacity(), 0.5);
1784 assert_eq!(ctx_clone.operations(), ctx.operations());
1785 }
1786
1787 #[test]
1788 fn test_set_opacity() {
1789 let mut ctx = GraphicsContext::new();
1790
1791 ctx.set_opacity(0.5);
1793 assert_eq!(ctx.fill_opacity(), 0.5);
1794 assert_eq!(ctx.stroke_opacity(), 0.5);
1795
1796 ctx.set_opacity(1.5);
1798 assert_eq!(ctx.fill_opacity(), 1.0);
1799 assert_eq!(ctx.stroke_opacity(), 1.0);
1800
1801 ctx.set_opacity(-0.5);
1802 assert_eq!(ctx.fill_opacity(), 0.0);
1803 assert_eq!(ctx.stroke_opacity(), 0.0);
1804 }
1805
1806 #[test]
1807 fn test_set_fill_opacity() {
1808 let mut ctx = GraphicsContext::new();
1809
1810 ctx.set_fill_opacity(0.3);
1811 assert_eq!(ctx.fill_opacity(), 0.3);
1812 assert_eq!(ctx.stroke_opacity(), 1.0); ctx.set_fill_opacity(2.0);
1816 assert_eq!(ctx.fill_opacity(), 1.0);
1817 }
1818
1819 #[test]
1820 fn test_set_stroke_opacity() {
1821 let mut ctx = GraphicsContext::new();
1822
1823 ctx.set_stroke_opacity(0.7);
1824 assert_eq!(ctx.stroke_opacity(), 0.7);
1825 assert_eq!(ctx.fill_opacity(), 1.0); ctx.set_stroke_opacity(-1.0);
1829 assert_eq!(ctx.stroke_opacity(), 0.0);
1830 }
1831
1832 #[test]
1833 fn test_uses_transparency() {
1834 let mut ctx = GraphicsContext::new();
1835
1836 assert!(!ctx.uses_transparency());
1838
1839 ctx.set_fill_opacity(0.5);
1841 assert!(ctx.uses_transparency());
1842
1843 ctx.set_fill_opacity(1.0);
1845 assert!(!ctx.uses_transparency());
1846 ctx.set_stroke_opacity(0.8);
1847 assert!(ctx.uses_transparency());
1848
1849 ctx.set_fill_opacity(0.5);
1851 assert!(ctx.uses_transparency());
1852 }
1853
1854 #[test]
1855 fn test_generate_graphics_state_dict() {
1856 let mut ctx = GraphicsContext::new();
1857
1858 assert_eq!(ctx.generate_graphics_state_dict(), None);
1860
1861 ctx.set_fill_opacity(0.5);
1863 let dict = ctx
1864 .generate_graphics_state_dict()
1865 .expect("Writing to string should never fail");
1866 assert!(dict.contains("/Type /ExtGState"));
1867 assert!(dict.contains("/ca 0.500"));
1868 assert!(!dict.contains("/CA"));
1869
1870 ctx.set_fill_opacity(1.0);
1872 ctx.set_stroke_opacity(0.75);
1873 let dict = ctx
1874 .generate_graphics_state_dict()
1875 .expect("Writing to string should never fail");
1876 assert!(dict.contains("/Type /ExtGState"));
1877 assert!(dict.contains("/CA 0.750"));
1878 assert!(!dict.contains("/ca"));
1879
1880 ctx.set_fill_opacity(0.25);
1882 let dict = ctx
1883 .generate_graphics_state_dict()
1884 .expect("Writing to string should never fail");
1885 assert!(dict.contains("/Type /ExtGState"));
1886 assert!(dict.contains("/ca 0.250"));
1887 assert!(dict.contains("/CA 0.750"));
1888 }
1889
1890 #[test]
1891 fn test_opacity_with_graphics_operations() {
1892 let mut ctx = GraphicsContext::new();
1893
1894 ctx.set_fill_color(Color::red())
1895 .set_opacity(0.5)
1896 .rect(10.0, 10.0, 100.0, 100.0)
1897 .fill();
1898
1899 assert_eq!(ctx.fill_opacity(), 0.5);
1900 assert_eq!(ctx.stroke_opacity(), 0.5);
1901
1902 let ops = ctx.operations();
1903 assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
1904 assert!(ops.contains("1.000 0.000 0.000 rg")); assert!(ops.contains("f")); }
1907
1908 #[test]
1909 fn test_begin_end_text() {
1910 let mut ctx = GraphicsContext::new();
1911 ctx.begin_text();
1912 assert!(ctx.operations().contains("BT\n"));
1913
1914 ctx.end_text();
1915 assert!(ctx.operations().contains("ET\n"));
1916 }
1917
1918 #[test]
1919 fn test_set_font() {
1920 let mut ctx = GraphicsContext::new();
1921 ctx.set_font(Font::Helvetica, 12.0);
1922 assert!(ctx.operations().contains("/Helvetica 12 Tf\n"));
1923
1924 ctx.set_font(Font::TimesBold, 14.5);
1925 assert!(ctx.operations().contains("/Times-Bold 14.5 Tf\n"));
1926 }
1927
1928 #[test]
1929 fn test_set_text_position() {
1930 let mut ctx = GraphicsContext::new();
1931 ctx.set_text_position(100.0, 200.0);
1932 assert!(ctx.operations().contains("100.00 200.00 Td\n"));
1933 }
1934
1935 #[test]
1936 fn test_show_text() {
1937 let mut ctx = GraphicsContext::new();
1938 ctx.show_text("Hello World")
1939 .expect("Writing to string should never fail");
1940 assert!(ctx.operations().contains("(Hello World) Tj\n"));
1941 }
1942
1943 #[test]
1944 fn test_show_text_with_escaping() {
1945 let mut ctx = GraphicsContext::new();
1946 ctx.show_text("Test (parentheses)")
1947 .expect("Writing to string should never fail");
1948 assert!(ctx.operations().contains("(Test \\(parentheses\\)) Tj\n"));
1949
1950 ctx.clear();
1951 ctx.show_text("Back\\slash")
1952 .expect("Writing to string should never fail");
1953 assert!(ctx.operations().contains("(Back\\\\slash) Tj\n"));
1954
1955 ctx.clear();
1956 ctx.show_text("Line\nBreak")
1957 .expect("Writing to string should never fail");
1958 assert!(ctx.operations().contains("(Line\\nBreak) Tj\n"));
1959 }
1960
1961 #[test]
1962 fn test_text_operations_chaining() {
1963 let mut ctx = GraphicsContext::new();
1964 ctx.begin_text()
1965 .set_font(Font::Courier, 10.0)
1966 .set_text_position(50.0, 100.0)
1967 .show_text("Test")
1968 .unwrap()
1969 .end_text();
1970
1971 let ops = ctx.operations();
1972 assert!(ops.contains("BT\n"));
1973 assert!(ops.contains("/Courier 10 Tf\n"));
1974 assert!(ops.contains("50.00 100.00 Td\n"));
1975 assert!(ops.contains("(Test) Tj\n"));
1976 assert!(ops.contains("ET\n"));
1977 }
1978
1979 #[test]
1980 fn test_clip() {
1981 let mut ctx = GraphicsContext::new();
1982 ctx.clip();
1983 assert!(ctx.operations().contains("W\n"));
1984 }
1985
1986 #[test]
1987 fn test_clip_even_odd() {
1988 let mut ctx = GraphicsContext::new();
1989 ctx.clip_even_odd();
1990 assert!(ctx.operations().contains("W*\n"));
1991 }
1992
1993 #[test]
1994 fn test_clipping_with_path() {
1995 let mut ctx = GraphicsContext::new();
1996
1997 ctx.rect(10.0, 10.0, 100.0, 50.0).clip();
1999
2000 let ops = ctx.operations();
2001 assert!(ops.contains("10.00 10.00 100.00 50.00 re\n"));
2002 assert!(ops.contains("W\n"));
2003 }
2004
2005 #[test]
2006 fn test_clipping_even_odd_with_path() {
2007 let mut ctx = GraphicsContext::new();
2008
2009 ctx.move_to(0.0, 0.0)
2011 .line_to(100.0, 0.0)
2012 .line_to(100.0, 100.0)
2013 .line_to(0.0, 100.0)
2014 .close_path()
2015 .clip_even_odd();
2016
2017 let ops = ctx.operations();
2018 assert!(ops.contains("0.00 0.00 m\n"));
2019 assert!(ops.contains("100.00 0.00 l\n"));
2020 assert!(ops.contains("100.00 100.00 l\n"));
2021 assert!(ops.contains("0.00 100.00 l\n"));
2022 assert!(ops.contains("h\n"));
2023 assert!(ops.contains("W*\n"));
2024 }
2025
2026 #[test]
2027 fn test_clipping_chaining() {
2028 let mut ctx = GraphicsContext::new();
2029
2030 ctx.save_state()
2032 .rect(20.0, 20.0, 60.0, 60.0)
2033 .clip()
2034 .set_fill_color(Color::red())
2035 .rect(0.0, 0.0, 100.0, 100.0)
2036 .fill()
2037 .restore_state();
2038
2039 let ops = ctx.operations();
2040 assert!(ops.contains("q\n"));
2041 assert!(ops.contains("20.00 20.00 60.00 60.00 re\n"));
2042 assert!(ops.contains("W\n"));
2043 assert!(ops.contains("1.000 0.000 0.000 rg\n"));
2044 assert!(ops.contains("0.00 0.00 100.00 100.00 re\n"));
2045 assert!(ops.contains("f\n"));
2046 assert!(ops.contains("Q\n"));
2047 }
2048
2049 #[test]
2050 fn test_multiple_clipping_regions() {
2051 let mut ctx = GraphicsContext::new();
2052
2053 ctx.save_state()
2055 .rect(0.0, 0.0, 200.0, 200.0)
2056 .clip()
2057 .save_state()
2058 .circle(100.0, 100.0, 50.0)
2059 .clip_even_odd()
2060 .set_fill_color(Color::blue())
2061 .rect(50.0, 50.0, 100.0, 100.0)
2062 .fill()
2063 .restore_state()
2064 .restore_state();
2065
2066 let ops = ctx.operations();
2067 let q_count = ops.matches("q\n").count();
2069 let q_restore_count = ops.matches("Q\n").count();
2070 assert_eq!(q_count, 2);
2071 assert_eq!(q_restore_count, 2);
2072
2073 assert!(ops.contains("W\n"));
2075 assert!(ops.contains("W*\n"));
2076 }
2077
2078 #[test]
2081 fn test_move_to_and_line_to() {
2082 let mut ctx = GraphicsContext::new();
2083 ctx.move_to(100.0, 200.0).line_to(300.0, 400.0).stroke();
2084
2085 let ops = ctx
2086 .generate_operations()
2087 .expect("Writing to string should never fail");
2088 let ops_str = String::from_utf8_lossy(&ops);
2089 assert!(ops_str.contains("100.00 200.00 m"));
2090 assert!(ops_str.contains("300.00 400.00 l"));
2091 assert!(ops_str.contains("S"));
2092 }
2093
2094 #[test]
2095 fn test_bezier_curve() {
2096 let mut ctx = GraphicsContext::new();
2097 ctx.move_to(0.0, 0.0)
2098 .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0)
2099 .stroke();
2100
2101 let ops = ctx
2102 .generate_operations()
2103 .expect("Writing to string should never fail");
2104 let ops_str = String::from_utf8_lossy(&ops);
2105 assert!(ops_str.contains("0.00 0.00 m"));
2106 assert!(ops_str.contains("10.00 20.00 30.00 40.00 50.00 60.00 c"));
2107 assert!(ops_str.contains("S"));
2108 }
2109
2110 #[test]
2111 fn test_circle_path() {
2112 let mut ctx = GraphicsContext::new();
2113 ctx.circle(100.0, 100.0, 50.0).fill();
2114
2115 let ops = ctx
2116 .generate_operations()
2117 .expect("Writing to string should never fail");
2118 let ops_str = String::from_utf8_lossy(&ops);
2119 assert!(ops_str.contains(" c"));
2121 assert!(ops_str.contains("f"));
2122 }
2123
2124 #[test]
2125 fn test_path_closing() {
2126 let mut ctx = GraphicsContext::new();
2127 ctx.move_to(0.0, 0.0)
2128 .line_to(100.0, 0.0)
2129 .line_to(100.0, 100.0)
2130 .close_path()
2131 .stroke();
2132
2133 let ops = ctx
2134 .generate_operations()
2135 .expect("Writing to string should never fail");
2136 let ops_str = String::from_utf8_lossy(&ops);
2137 assert!(ops_str.contains("h")); assert!(ops_str.contains("S"));
2139 }
2140
2141 #[test]
2142 fn test_fill_and_stroke() {
2143 let mut ctx = GraphicsContext::new();
2144 ctx.rect(10.0, 10.0, 50.0, 50.0).fill_stroke();
2145
2146 let ops = ctx
2147 .generate_operations()
2148 .expect("Writing to string should never fail");
2149 let ops_str = String::from_utf8_lossy(&ops);
2150 assert!(ops_str.contains("10.00 10.00 50.00 50.00 re"));
2151 assert!(ops_str.contains("B")); }
2153
2154 #[test]
2155 fn test_color_settings() {
2156 let mut ctx = GraphicsContext::new();
2157 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2158 .set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
2159 .rect(10.0, 10.0, 50.0, 50.0)
2160 .fill_stroke(); assert_eq!(ctx.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2163 assert_eq!(ctx.stroke_color(), Color::rgb(0.0, 1.0, 0.0));
2164
2165 let ops = ctx
2166 .generate_operations()
2167 .expect("Writing to string should never fail");
2168 let ops_str = String::from_utf8_lossy(&ops);
2169 assert!(ops_str.contains("1.000 0.000 0.000 rg")); assert!(ops_str.contains("0.000 1.000 0.000 RG")); }
2172
2173 #[test]
2174 fn test_line_styles() {
2175 let mut ctx = GraphicsContext::new();
2176 ctx.set_line_width(2.5)
2177 .set_line_cap(LineCap::Round)
2178 .set_line_join(LineJoin::Bevel);
2179
2180 assert_eq!(ctx.line_width(), 2.5);
2181
2182 let ops = ctx
2183 .generate_operations()
2184 .expect("Writing to string should never fail");
2185 let ops_str = String::from_utf8_lossy(&ops);
2186 assert!(ops_str.contains("2.50 w")); assert!(ops_str.contains("1 J")); assert!(ops_str.contains("2 j")); }
2190
2191 #[test]
2192 fn test_opacity_settings() {
2193 let mut ctx = GraphicsContext::new();
2194 ctx.set_opacity(0.5);
2195
2196 assert_eq!(ctx.fill_opacity(), 0.5);
2197 assert_eq!(ctx.stroke_opacity(), 0.5);
2198 assert!(ctx.uses_transparency());
2199
2200 ctx.set_fill_opacity(0.7).set_stroke_opacity(0.3);
2201
2202 assert_eq!(ctx.fill_opacity(), 0.7);
2203 assert_eq!(ctx.stroke_opacity(), 0.3);
2204 }
2205
2206 #[test]
2207 fn test_state_save_restore() {
2208 let mut ctx = GraphicsContext::new();
2209 ctx.save_state()
2210 .set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2211 .restore_state();
2212
2213 let ops = ctx
2214 .generate_operations()
2215 .expect("Writing to string should never fail");
2216 let ops_str = String::from_utf8_lossy(&ops);
2217 assert!(ops_str.contains("q")); assert!(ops_str.contains("Q")); }
2220
2221 #[test]
2222 fn test_transformations() {
2223 let mut ctx = GraphicsContext::new();
2224 ctx.translate(100.0, 200.0).scale(2.0, 3.0).rotate(45.0);
2225
2226 let ops = ctx
2227 .generate_operations()
2228 .expect("Writing to string should never fail");
2229 let ops_str = String::from_utf8_lossy(&ops);
2230 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")); }
2234
2235 #[test]
2236 fn test_custom_transform() {
2237 let mut ctx = GraphicsContext::new();
2238 ctx.transform(1.0, 0.5, 0.5, 1.0, 10.0, 20.0);
2239
2240 let ops = ctx
2241 .generate_operations()
2242 .expect("Writing to string should never fail");
2243 let ops_str = String::from_utf8_lossy(&ops);
2244 assert!(ops_str.contains("1.00 0.50 0.50 1.00 10.00 20.00 cm"));
2245 }
2246
2247 #[test]
2248 fn test_rectangle_path() {
2249 let mut ctx = GraphicsContext::new();
2250 ctx.rectangle(25.0, 25.0, 150.0, 100.0).stroke();
2251
2252 let ops = ctx
2253 .generate_operations()
2254 .expect("Writing to string should never fail");
2255 let ops_str = String::from_utf8_lossy(&ops);
2256 assert!(ops_str.contains("25.00 25.00 150.00 100.00 re"));
2257 assert!(ops_str.contains("S"));
2258 }
2259
2260 #[test]
2261 fn test_empty_operations() {
2262 let ctx = GraphicsContext::new();
2263 let ops = ctx
2264 .generate_operations()
2265 .expect("Writing to string should never fail");
2266 assert!(ops.is_empty());
2267 }
2268
2269 #[test]
2270 fn test_complex_path_operations() {
2271 let mut ctx = GraphicsContext::new();
2272 ctx.move_to(50.0, 50.0)
2273 .line_to(100.0, 50.0)
2274 .curve_to(125.0, 50.0, 150.0, 75.0, 150.0, 100.0)
2275 .line_to(150.0, 150.0)
2276 .close_path()
2277 .fill();
2278
2279 let ops = ctx
2280 .generate_operations()
2281 .expect("Writing to string should never fail");
2282 let ops_str = String::from_utf8_lossy(&ops);
2283 assert!(ops_str.contains("50.00 50.00 m"));
2284 assert!(ops_str.contains("100.00 50.00 l"));
2285 assert!(ops_str.contains("125.00 50.00 150.00 75.00 150.00 100.00 c"));
2286 assert!(ops_str.contains("150.00 150.00 l"));
2287 assert!(ops_str.contains("h"));
2288 assert!(ops_str.contains("f"));
2289 }
2290
2291 #[test]
2292 fn test_graphics_state_dict_generation() {
2293 let mut ctx = GraphicsContext::new();
2294
2295 assert!(ctx.generate_graphics_state_dict().is_none());
2297
2298 ctx.set_opacity(0.5);
2300 let dict = ctx.generate_graphics_state_dict();
2301 assert!(dict.is_some());
2302 let dict_str = dict.expect("Writing to string should never fail");
2303 assert!(dict_str.contains("/ca 0.5"));
2304 assert!(dict_str.contains("/CA 0.5"));
2305 }
2306
2307 #[test]
2308 fn test_line_dash_pattern() {
2309 let mut ctx = GraphicsContext::new();
2310 let pattern = LineDashPattern {
2311 array: vec![3.0, 2.0],
2312 phase: 0.0,
2313 };
2314 ctx.set_line_dash_pattern(pattern);
2315
2316 let ops = ctx
2317 .generate_operations()
2318 .expect("Writing to string should never fail");
2319 let ops_str = String::from_utf8_lossy(&ops);
2320 assert!(ops_str.contains("[3.00 2.00] 0.00 d"));
2321 }
2322
2323 #[test]
2324 fn test_miter_limit_setting() {
2325 let mut ctx = GraphicsContext::new();
2326 ctx.set_miter_limit(4.0);
2327
2328 let ops = ctx
2329 .generate_operations()
2330 .expect("Writing to string should never fail");
2331 let ops_str = String::from_utf8_lossy(&ops);
2332 assert!(ops_str.contains("4.00 M"));
2333 }
2334
2335 #[test]
2336 fn test_line_cap_styles() {
2337 let mut ctx = GraphicsContext::new();
2338
2339 ctx.set_line_cap(LineCap::Butt);
2340 let ops = ctx
2341 .generate_operations()
2342 .expect("Writing to string should never fail");
2343 let ops_str = String::from_utf8_lossy(&ops);
2344 assert!(ops_str.contains("0 J"));
2345
2346 let mut ctx = GraphicsContext::new();
2347 ctx.set_line_cap(LineCap::Round);
2348 let ops = ctx
2349 .generate_operations()
2350 .expect("Writing to string should never fail");
2351 let ops_str = String::from_utf8_lossy(&ops);
2352 assert!(ops_str.contains("1 J"));
2353
2354 let mut ctx = GraphicsContext::new();
2355 ctx.set_line_cap(LineCap::Square);
2356 let ops = ctx
2357 .generate_operations()
2358 .expect("Writing to string should never fail");
2359 let ops_str = String::from_utf8_lossy(&ops);
2360 assert!(ops_str.contains("2 J"));
2361 }
2362
2363 #[test]
2364 fn test_transparency_groups() {
2365 let mut ctx = GraphicsContext::new();
2366
2367 let group = TransparencyGroup::new()
2369 .with_isolated(true)
2370 .with_opacity(0.5);
2371
2372 ctx.begin_transparency_group(group);
2373 assert!(ctx.in_transparency_group());
2374
2375 ctx.rect(10.0, 10.0, 100.0, 100.0);
2377 ctx.fill();
2378
2379 ctx.end_transparency_group();
2380 assert!(!ctx.in_transparency_group());
2381
2382 let ops = ctx.operations();
2384 assert!(ops.contains("% Begin Transparency Group"));
2385 assert!(ops.contains("% End Transparency Group"));
2386 }
2387
2388 #[test]
2389 fn test_nested_transparency_groups() {
2390 let mut ctx = GraphicsContext::new();
2391
2392 let group1 = TransparencyGroup::isolated().with_opacity(0.8);
2394 ctx.begin_transparency_group(group1);
2395 assert!(ctx.in_transparency_group());
2396
2397 let group2 = TransparencyGroup::knockout().with_blend_mode(BlendMode::Multiply);
2399 ctx.begin_transparency_group(group2);
2400
2401 ctx.circle(50.0, 50.0, 25.0);
2403 ctx.fill();
2404
2405 ctx.end_transparency_group();
2407 assert!(ctx.in_transparency_group()); ctx.end_transparency_group();
2411 assert!(!ctx.in_transparency_group());
2412 }
2413
2414 #[test]
2415 fn test_line_join_styles() {
2416 let mut ctx = GraphicsContext::new();
2417
2418 ctx.set_line_join(LineJoin::Miter);
2419 let ops = ctx
2420 .generate_operations()
2421 .expect("Writing to string should never fail");
2422 let ops_str = String::from_utf8_lossy(&ops);
2423 assert!(ops_str.contains("0 j"));
2424
2425 let mut ctx = GraphicsContext::new();
2426 ctx.set_line_join(LineJoin::Round);
2427 let ops = ctx
2428 .generate_operations()
2429 .expect("Writing to string should never fail");
2430 let ops_str = String::from_utf8_lossy(&ops);
2431 assert!(ops_str.contains("1 j"));
2432
2433 let mut ctx = GraphicsContext::new();
2434 ctx.set_line_join(LineJoin::Bevel);
2435 let ops = ctx
2436 .generate_operations()
2437 .expect("Writing to string should never fail");
2438 let ops_str = String::from_utf8_lossy(&ops);
2439 assert!(ops_str.contains("2 j"));
2440 }
2441
2442 #[test]
2443 fn test_rendering_intent() {
2444 let mut ctx = GraphicsContext::new();
2445
2446 ctx.set_rendering_intent(RenderingIntent::AbsoluteColorimetric);
2447 assert_eq!(
2448 ctx.rendering_intent(),
2449 RenderingIntent::AbsoluteColorimetric
2450 );
2451
2452 ctx.set_rendering_intent(RenderingIntent::Perceptual);
2453 assert_eq!(ctx.rendering_intent(), RenderingIntent::Perceptual);
2454
2455 ctx.set_rendering_intent(RenderingIntent::Saturation);
2456 assert_eq!(ctx.rendering_intent(), RenderingIntent::Saturation);
2457 }
2458
2459 #[test]
2460 fn test_flatness_tolerance() {
2461 let mut ctx = GraphicsContext::new();
2462
2463 ctx.set_flatness(0.5);
2464 assert_eq!(ctx.flatness(), 0.5);
2465
2466 let ops = ctx
2467 .generate_operations()
2468 .expect("Writing to string should never fail");
2469 let ops_str = String::from_utf8_lossy(&ops);
2470 assert!(ops_str.contains("0.50 i"));
2471 }
2472
2473 #[test]
2474 fn test_smoothness_tolerance() {
2475 let mut ctx = GraphicsContext::new();
2476
2477 let _ = ctx.set_smoothness(0.1);
2478 assert_eq!(ctx.smoothness(), 0.1);
2479 }
2480
2481 #[test]
2482 fn test_bezier_curves() {
2483 let mut ctx = GraphicsContext::new();
2484
2485 ctx.move_to(10.0, 10.0);
2487 ctx.curve_to(20.0, 10.0, 30.0, 20.0, 30.0, 30.0);
2488
2489 let ops = ctx
2490 .generate_operations()
2491 .expect("Writing to string should never fail");
2492 let ops_str = String::from_utf8_lossy(&ops);
2493 assert!(ops_str.contains("10.00 10.00 m"));
2494 assert!(ops_str.contains("c")); }
2496
2497 #[test]
2498 fn test_clipping_path() {
2499 let mut ctx = GraphicsContext::new();
2500
2501 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2502 ctx.clip();
2503
2504 let ops = ctx
2505 .generate_operations()
2506 .expect("Writing to string should never fail");
2507 let ops_str = String::from_utf8_lossy(&ops);
2508 assert!(ops_str.contains("W"));
2509 }
2510
2511 #[test]
2512 fn test_even_odd_clipping() {
2513 let mut ctx = GraphicsContext::new();
2514
2515 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2516 ctx.clip_even_odd();
2517
2518 let ops = ctx
2519 .generate_operations()
2520 .expect("Writing to string should never fail");
2521 let ops_str = String::from_utf8_lossy(&ops);
2522 assert!(ops_str.contains("W*"));
2523 }
2524
2525 #[test]
2526 fn test_color_creation() {
2527 let gray = Color::gray(0.5);
2529 assert_eq!(gray, Color::Gray(0.5));
2530
2531 let rgb = Color::rgb(0.2, 0.4, 0.6);
2532 assert_eq!(rgb, Color::Rgb(0.2, 0.4, 0.6));
2533
2534 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
2535 assert_eq!(cmyk, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
2536
2537 assert_eq!(Color::black(), Color::Gray(0.0));
2539 assert_eq!(Color::white(), Color::Gray(1.0));
2540 assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
2541 }
2542
2543 #[test]
2544 fn test_extended_graphics_state() {
2545 let ctx = GraphicsContext::new();
2546
2547 let _extgstate = ExtGState::new();
2549
2550 assert!(ctx.generate_operations().is_ok());
2552 }
2553
2554 #[test]
2555 fn test_path_construction_methods() {
2556 let mut ctx = GraphicsContext::new();
2557
2558 ctx.move_to(10.0, 10.0);
2560 ctx.line_to(20.0, 20.0);
2561 ctx.curve_to(30.0, 30.0, 40.0, 40.0, 50.0, 50.0);
2562 ctx.rect(60.0, 60.0, 30.0, 30.0);
2563 ctx.circle(100.0, 100.0, 25.0);
2564 ctx.close_path();
2565
2566 let ops = ctx
2567 .generate_operations()
2568 .expect("Writing to string should never fail");
2569 assert!(!ops.is_empty());
2570 }
2571
2572 #[test]
2573 fn test_graphics_context_clone_advanced() {
2574 let mut ctx = GraphicsContext::new();
2575 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
2576 ctx.set_line_width(5.0);
2577
2578 let cloned = ctx.clone();
2579 assert_eq!(cloned.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2580 assert_eq!(cloned.line_width(), 5.0);
2581 }
2582
2583 #[test]
2584 fn test_basic_drawing_operations() {
2585 let mut ctx = GraphicsContext::new();
2586
2587 ctx.move_to(50.0, 50.0);
2589 ctx.line_to(100.0, 100.0);
2590 ctx.stroke();
2591
2592 let ops = ctx
2593 .generate_operations()
2594 .expect("Writing to string should never fail");
2595 let ops_str = String::from_utf8_lossy(&ops);
2596 assert!(ops_str.contains("m")); assert!(ops_str.contains("l")); assert!(ops_str.contains("S")); }
2600
2601 #[test]
2602 fn test_graphics_state_stack() {
2603 let mut ctx = GraphicsContext::new();
2604
2605 ctx.set_fill_color(Color::black());
2607
2608 ctx.save_state();
2610 ctx.set_fill_color(Color::red());
2611 assert_eq!(ctx.fill_color(), Color::red());
2612
2613 ctx.save_state();
2615 ctx.set_fill_color(Color::blue());
2616 assert_eq!(ctx.fill_color(), Color::blue());
2617
2618 ctx.restore_state();
2620 assert_eq!(ctx.fill_color(), Color::red());
2621
2622 ctx.restore_state();
2624 assert_eq!(ctx.fill_color(), Color::black());
2625 }
2626
2627 #[test]
2628 fn test_word_spacing() {
2629 let mut ctx = GraphicsContext::new();
2630 ctx.set_word_spacing(2.5);
2631
2632 let ops = ctx.generate_operations().unwrap();
2633 let ops_str = String::from_utf8_lossy(&ops);
2634 assert!(ops_str.contains("2.50 Tw"));
2635 }
2636
2637 #[test]
2638 fn test_character_spacing() {
2639 let mut ctx = GraphicsContext::new();
2640 ctx.set_character_spacing(1.0);
2641
2642 let ops = ctx.generate_operations().unwrap();
2643 let ops_str = String::from_utf8_lossy(&ops);
2644 assert!(ops_str.contains("1.00 Tc"));
2645 }
2646
2647 #[test]
2648 fn test_justified_text() {
2649 let mut ctx = GraphicsContext::new();
2650 ctx.begin_text();
2651 ctx.set_text_position(100.0, 200.0);
2652 ctx.show_justified_text("Hello world from PDF", 200.0)
2653 .unwrap();
2654 ctx.end_text();
2655
2656 let ops = ctx.generate_operations().unwrap();
2657 let ops_str = String::from_utf8_lossy(&ops);
2658
2659 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")); }
2668
2669 #[test]
2670 fn test_justified_text_single_word() {
2671 let mut ctx = GraphicsContext::new();
2672 ctx.begin_text();
2673 ctx.show_justified_text("Hello", 200.0).unwrap();
2674 ctx.end_text();
2675
2676 let ops = ctx.generate_operations().unwrap();
2677 let ops_str = String::from_utf8_lossy(&ops);
2678
2679 assert!(ops_str.contains("(Hello) Tj"));
2681 assert_eq!(ops_str.matches("Tw").count(), 0);
2683 }
2684
2685 #[test]
2686 fn test_text_width_estimation() {
2687 let ctx = GraphicsContext::new();
2688 let width = ctx.estimate_text_width_simple("Hello");
2689
2690 assert!(width > 0.0);
2692 assert_eq!(width, 5.0 * 12.0 * 0.6); }
2694
2695 #[test]
2696 fn test_set_alpha_methods() {
2697 let mut ctx = GraphicsContext::new();
2698
2699 assert!(ctx.set_alpha(0.5).is_ok());
2701 assert!(ctx.set_alpha_fill(0.3).is_ok());
2702 assert!(ctx.set_alpha_stroke(0.7).is_ok());
2703
2704 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
2712 .set_alpha(0.5)
2713 .and_then(|c| c.set_alpha_fill(0.3))
2714 .and_then(|c| c.set_alpha_stroke(0.7));
2715 assert!(result.is_ok());
2716 }
2717
2718 #[test]
2719 fn test_alpha_methods_generate_extgstate() {
2720 let mut ctx = GraphicsContext::new();
2721
2722 ctx.set_alpha(0.5).unwrap();
2724
2725 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
2727
2728 let ops = ctx.generate_operations().unwrap();
2729 let ops_str = String::from_utf8_lossy(&ops);
2730
2731 assert!(ops_str.contains("/GS")); assert!(ops_str.contains(" gs\n")); ctx.clear();
2737 ctx.set_alpha_fill(0.3).unwrap();
2738 ctx.set_alpha_stroke(0.8).unwrap();
2739 ctx.rect(20.0, 20.0, 60.0, 60.0).fill_stroke();
2740
2741 let ops2 = ctx.generate_operations().unwrap();
2742 let ops_str2 = String::from_utf8_lossy(&ops2);
2743
2744 assert!(ops_str2.contains("/GS")); assert!(ops_str2.contains(" gs\n")); }
2748
2749 #[test]
2750 fn test_add_command() {
2751 let mut ctx = GraphicsContext::new();
2752
2753 ctx.add_command("1 0 0 1 100 200 cm");
2755 let ops = ctx.operations();
2756 assert!(ops.contains("1 0 0 1 100 200 cm\n"));
2757
2758 ctx.clear();
2760 ctx.add_command("q");
2761 assert_eq!(ctx.operations(), "q\n");
2762
2763 ctx.clear();
2765 ctx.add_command("");
2766 assert_eq!(ctx.operations(), "\n");
2767
2768 ctx.clear();
2770 ctx.add_command("Q\n");
2771 assert_eq!(ctx.operations(), "Q\n\n"); ctx.clear();
2775 ctx.add_command("q");
2776 ctx.add_command("1 0 0 1 50 50 cm");
2777 ctx.add_command("Q");
2778 assert_eq!(ctx.operations(), "q\n1 0 0 1 50 50 cm\nQ\n");
2779 }
2780
2781 #[test]
2782 fn test_get_operations() {
2783 let mut ctx = GraphicsContext::new();
2784 ctx.rect(10.0, 10.0, 50.0, 50.0);
2785 let ops1 = ctx.operations();
2786 let ops2 = ctx.get_operations();
2787 assert_eq!(ops1, ops2);
2788 }
2789
2790 #[test]
2791 fn test_set_line_solid() {
2792 let mut ctx = GraphicsContext::new();
2793 ctx.set_line_dash_pattern(LineDashPattern::new(vec![5.0, 3.0], 0.0));
2794 ctx.set_line_solid();
2795 let ops = ctx.operations();
2796 assert!(ops.contains("[] 0 d\n"));
2797 }
2798
2799 #[test]
2800 fn test_set_custom_font() {
2801 let mut ctx = GraphicsContext::new();
2802 ctx.set_custom_font("CustomFont", 14.0);
2803 assert_eq!(ctx.current_font_name, Some("CustomFont".to_string()));
2804 assert_eq!(ctx.current_font_size, 14.0);
2805 assert!(ctx.is_custom_font);
2806 }
2807
2808 #[test]
2809 fn test_show_text_standard_font_uses_literal_string() {
2810 let mut ctx = GraphicsContext::new();
2811 ctx.set_font(Font::Helvetica, 12.0);
2812 assert!(!ctx.is_custom_font);
2813
2814 ctx.begin_text();
2815 ctx.set_text_position(10.0, 20.0);
2816 ctx.show_text("Hello World").unwrap();
2817 ctx.end_text();
2818
2819 let ops = ctx.operations();
2820 assert!(ops.contains("(Hello World) Tj"));
2821 assert!(!ops.contains("<"));
2822 }
2823
2824 #[test]
2825 fn test_show_text_custom_font_uses_hex_encoding() {
2826 let mut ctx = GraphicsContext::new();
2827 ctx.set_font(Font::Custom("NotoSansCJK".to_string()), 12.0);
2828 assert!(ctx.is_custom_font);
2829
2830 ctx.begin_text();
2831 ctx.set_text_position(10.0, 20.0);
2832 ctx.show_text("你好").unwrap();
2834 ctx.end_text();
2835
2836 let ops = ctx.operations();
2837 assert!(
2839 ops.contains("<4F60597D> Tj"),
2840 "Expected hex encoding for CJK text, got: {}",
2841 ops
2842 );
2843 assert!(!ops.contains("(你好)"));
2844 }
2845
2846 #[test]
2847 fn test_show_text_custom_font_ascii_still_hex() {
2848 let mut ctx = GraphicsContext::new();
2849 ctx.set_font(Font::Custom("MyFont".to_string()), 10.0);
2850
2851 ctx.begin_text();
2852 ctx.set_text_position(0.0, 0.0);
2853 ctx.show_text("AB").unwrap();
2855 ctx.end_text();
2856
2857 let ops = ctx.operations();
2858 assert!(
2860 ops.contains("<00410042> Tj"),
2861 "Expected hex encoding for ASCII in custom font, got: {}",
2862 ops
2863 );
2864 }
2865
2866 #[test]
2867 fn test_show_text_tracks_used_characters() {
2868 let mut ctx = GraphicsContext::new();
2869 ctx.set_font(Font::Custom("CJKFont".to_string()), 12.0);
2870
2871 ctx.begin_text();
2872 ctx.show_text("你好A").unwrap();
2873 ctx.end_text();
2874
2875 assert!(ctx.used_characters.contains(&'你'));
2876 assert!(ctx.used_characters.contains(&'好'));
2877 assert!(ctx.used_characters.contains(&'A'));
2878 }
2879
2880 #[test]
2881 fn test_is_custom_font_toggles_correctly() {
2882 let mut ctx = GraphicsContext::new();
2883 assert!(!ctx.is_custom_font);
2884
2885 ctx.set_font(Font::Custom("CJK".to_string()), 12.0);
2886 assert!(ctx.is_custom_font);
2887
2888 ctx.set_font(Font::Helvetica, 12.0);
2889 assert!(!ctx.is_custom_font);
2890
2891 ctx.set_custom_font("AnotherCJK", 14.0);
2892 assert!(ctx.is_custom_font);
2893
2894 ctx.set_font(Font::CourierBold, 10.0);
2895 assert!(!ctx.is_custom_font);
2896 }
2897
2898 #[test]
2899 fn test_set_glyph_mapping() {
2900 let mut ctx = GraphicsContext::new();
2901
2902 assert!(ctx.glyph_mapping.is_none());
2904
2905 let mut mapping = HashMap::new();
2907 mapping.insert(65u32, 1u16); mapping.insert(66u32, 2u16); ctx.set_glyph_mapping(mapping.clone());
2910 assert!(ctx.glyph_mapping.is_some());
2911 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 2);
2912 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), Some(&1));
2913 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&66), Some(&2));
2914
2915 ctx.set_glyph_mapping(HashMap::new());
2917 assert!(ctx.glyph_mapping.is_some());
2918 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 0);
2919
2920 let mut new_mapping = HashMap::new();
2922 new_mapping.insert(67u32, 3u16); ctx.set_glyph_mapping(new_mapping);
2924 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 1);
2925 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&67), Some(&3));
2926 assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), None); }
2928
2929 #[test]
2930 fn test_draw_text_basic() {
2931 let mut ctx = GraphicsContext::new();
2932 ctx.set_font(Font::Helvetica, 12.0);
2933
2934 let result = ctx.draw_text("Hello", 100.0, 200.0);
2935 assert!(result.is_ok());
2936
2937 let ops = ctx.operations();
2938 assert!(ops.contains("BT\n"));
2940 assert!(ops.contains("ET\n"));
2941
2942 assert!(ops.contains("/Helvetica"));
2944 assert!(ops.contains("12"));
2945 assert!(ops.contains("Tf\n"));
2946
2947 assert!(ops.contains("100"));
2949 assert!(ops.contains("200"));
2950 assert!(ops.contains("Td\n"));
2951
2952 assert!(ops.contains("(Hello)") || ops.contains("<48656c6c6f>")); }
2955
2956 #[test]
2957 fn test_draw_text_with_special_characters() {
2958 let mut ctx = GraphicsContext::new();
2959 ctx.set_font(Font::Helvetica, 12.0);
2960
2961 let result = ctx.draw_text("Test (with) parens", 50.0, 100.0);
2963 assert!(result.is_ok());
2964
2965 let ops = ctx.operations();
2966 assert!(ops.contains("\\(") || ops.contains("\\)") || ops.contains("<"));
2968 }
2970
2971 #[test]
2972 fn test_draw_text_unicode_detection() {
2973 let mut ctx = GraphicsContext::new();
2974 ctx.set_font(Font::Helvetica, 12.0);
2975
2976 ctx.draw_text("ASCII", 0.0, 0.0).unwrap();
2978 let _ops_ascii = ctx.operations();
2979
2980 ctx.clear();
2981
2982 ctx.set_font(Font::Helvetica, 12.0);
2984 ctx.draw_text("中文", 0.0, 0.0).unwrap();
2985 let ops_unicode = ctx.operations();
2986
2987 assert!(ops_unicode.contains("<") && ops_unicode.contains(">"));
2989 }
2990
2991 #[test]
2992 #[allow(deprecated)]
2993 fn test_draw_text_hex_encoding() {
2994 let mut ctx = GraphicsContext::new();
2995 ctx.set_font(Font::Helvetica, 12.0);
2996 let result = ctx.draw_text_hex("Test", 50.0, 100.0);
2997 assert!(result.is_ok());
2998 let ops = ctx.operations();
2999 assert!(ops.contains("<"));
3000 assert!(ops.contains(">"));
3001 }
3002
3003 #[test]
3004 #[allow(deprecated)]
3005 fn test_draw_text_cid() {
3006 let mut ctx = GraphicsContext::new();
3007 ctx.set_custom_font("CustomCIDFont", 12.0);
3008 let result = ctx.draw_text_cid("Test", 50.0, 100.0);
3009 assert!(result.is_ok());
3010 let ops = ctx.operations();
3011 assert!(ops.contains("BT\n"));
3012 assert!(ops.contains("ET\n"));
3013 }
3014
3015 #[test]
3016 #[allow(deprecated)]
3017 fn test_draw_text_unicode() {
3018 let mut ctx = GraphicsContext::new();
3019 ctx.set_custom_font("UnicodeFont", 12.0);
3020 let result = ctx.draw_text_unicode("Test \u{4E2D}\u{6587}", 50.0, 100.0);
3021 assert!(result.is_ok());
3022 let ops = ctx.operations();
3023 assert!(ops.contains("BT\n"));
3024 assert!(ops.contains("ET\n"));
3025 }
3026
3027 #[test]
3028 fn test_begin_end_transparency_group() {
3029 let mut ctx = GraphicsContext::new();
3030
3031 assert!(!ctx.in_transparency_group());
3033 assert!(ctx.current_transparency_group().is_none());
3034
3035 let group = TransparencyGroup::new();
3037 ctx.begin_transparency_group(group);
3038 assert!(ctx.in_transparency_group());
3039 assert!(ctx.current_transparency_group().is_some());
3040
3041 let ops = ctx.operations();
3043 assert!(ops.contains("% Begin Transparency Group"));
3044
3045 ctx.end_transparency_group();
3047 assert!(!ctx.in_transparency_group());
3048 assert!(ctx.current_transparency_group().is_none());
3049
3050 let ops_after = ctx.operations();
3052 assert!(ops_after.contains("% End Transparency Group"));
3053 }
3054
3055 #[test]
3056 fn test_transparency_group_nesting() {
3057 let mut ctx = GraphicsContext::new();
3058
3059 let group1 = TransparencyGroup::new();
3061 let group2 = TransparencyGroup::new();
3062 let group3 = TransparencyGroup::new();
3063
3064 ctx.begin_transparency_group(group1);
3065 assert_eq!(ctx.transparency_stack.len(), 1);
3066
3067 ctx.begin_transparency_group(group2);
3068 assert_eq!(ctx.transparency_stack.len(), 2);
3069
3070 ctx.begin_transparency_group(group3);
3071 assert_eq!(ctx.transparency_stack.len(), 3);
3072
3073 ctx.end_transparency_group();
3075 assert_eq!(ctx.transparency_stack.len(), 2);
3076
3077 ctx.end_transparency_group();
3078 assert_eq!(ctx.transparency_stack.len(), 1);
3079
3080 ctx.end_transparency_group();
3081 assert_eq!(ctx.transparency_stack.len(), 0);
3082 assert!(!ctx.in_transparency_group());
3083 }
3084
3085 #[test]
3086 fn test_transparency_group_without_begin() {
3087 let mut ctx = GraphicsContext::new();
3088
3089 assert!(!ctx.in_transparency_group());
3091 ctx.end_transparency_group();
3092 assert!(!ctx.in_transparency_group());
3093 }
3094
3095 #[test]
3096 fn test_extgstate_manager_access() {
3097 let ctx = GraphicsContext::new();
3098 let manager = ctx.extgstate_manager();
3099 assert_eq!(manager.count(), 0);
3100 }
3101
3102 #[test]
3103 fn test_extgstate_manager_mut_access() {
3104 let mut ctx = GraphicsContext::new();
3105 let manager = ctx.extgstate_manager_mut();
3106 assert_eq!(manager.count(), 0);
3107 }
3108
3109 #[test]
3110 fn test_has_extgstates() {
3111 let mut ctx = GraphicsContext::new();
3112
3113 assert!(!ctx.has_extgstates());
3115 assert_eq!(ctx.extgstate_manager().count(), 0);
3116
3117 ctx.set_alpha(0.5).unwrap();
3119 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3120 let result = ctx.generate_operations().unwrap();
3121
3122 assert!(ctx.has_extgstates());
3123 assert!(ctx.extgstate_manager().count() > 0);
3124
3125 let output = String::from_utf8_lossy(&result);
3127 assert!(output.contains("/GS")); assert!(output.contains(" gs\n")); }
3130
3131 #[test]
3132 fn test_generate_extgstate_resources() {
3133 let mut ctx = GraphicsContext::new();
3134 ctx.set_alpha(0.5).unwrap();
3135 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3136 ctx.generate_operations().unwrap();
3137
3138 let resources = ctx.generate_extgstate_resources();
3139 assert!(resources.is_ok());
3140 }
3141
3142 #[test]
3143 fn test_apply_extgstate() {
3144 let mut ctx = GraphicsContext::new();
3145
3146 let mut state = ExtGState::new();
3148 state.alpha_fill = Some(0.5);
3149 state.alpha_stroke = Some(0.8);
3150 state.blend_mode = Some(BlendMode::Multiply);
3151
3152 let result = ctx.apply_extgstate(state);
3153 assert!(result.is_ok());
3154
3155 assert!(ctx.has_extgstates());
3157 assert_eq!(ctx.extgstate_manager().count(), 1);
3158
3159 let mut state2 = ExtGState::new();
3161 state2.alpha_fill = Some(0.3);
3162 ctx.apply_extgstate(state2).unwrap();
3163
3164 assert_eq!(ctx.extgstate_manager().count(), 2);
3166 }
3167
3168 #[test]
3169 fn test_with_extgstate() {
3170 let mut ctx = GraphicsContext::new();
3171 let result = ctx.with_extgstate(|mut state| {
3172 state.alpha_fill = Some(0.5);
3173 state.alpha_stroke = Some(0.8);
3174 state
3175 });
3176 assert!(result.is_ok());
3177 }
3178
3179 #[test]
3180 fn test_set_blend_mode() {
3181 let mut ctx = GraphicsContext::new();
3182
3183 let result = ctx.set_blend_mode(BlendMode::Multiply);
3185 assert!(result.is_ok());
3186 assert!(ctx.has_extgstates());
3187
3188 ctx.clear();
3190 ctx.set_blend_mode(BlendMode::Screen).unwrap();
3191 ctx.rect(0.0, 0.0, 10.0, 10.0).fill();
3192 let ops = ctx.generate_operations().unwrap();
3193 let output = String::from_utf8_lossy(&ops);
3194
3195 assert!(output.contains("/GS"));
3197 assert!(output.contains(" gs\n"));
3198 }
3199
3200 #[test]
3201 fn test_render_table() {
3202 let mut ctx = GraphicsContext::new();
3203 let table = Table::with_equal_columns(2, 200.0);
3204 let result = ctx.render_table(&table);
3205 assert!(result.is_ok());
3206 }
3207
3208 #[test]
3209 fn test_render_list() {
3210 let mut ctx = GraphicsContext::new();
3211 use crate::text::{OrderedList, OrderedListStyle};
3212 let ordered = OrderedList::new(OrderedListStyle::Decimal);
3213 let list = ListElement::Ordered(ordered);
3214 let result = ctx.render_list(&list);
3215 assert!(result.is_ok());
3216 }
3217
3218 #[test]
3219 fn test_render_column_layout() {
3220 let mut ctx = GraphicsContext::new();
3221 use crate::text::ColumnContent;
3222 let layout = ColumnLayout::new(2, 100.0, 200.0);
3223 let content = ColumnContent::new("Test content");
3224 let result = ctx.render_column_layout(&layout, &content, 50.0, 50.0, 400.0);
3225 assert!(result.is_ok());
3226 }
3227
3228 #[test]
3229 fn test_clip_ellipse() {
3230 let mut ctx = GraphicsContext::new();
3231
3232 assert!(!ctx.has_clipping());
3234 assert!(ctx.clipping_path().is_none());
3235
3236 let result = ctx.clip_ellipse(100.0, 100.0, 50.0, 30.0);
3238 assert!(result.is_ok());
3239 assert!(ctx.has_clipping());
3240 assert!(ctx.clipping_path().is_some());
3241
3242 let ops = ctx.operations();
3244 assert!(ops.contains("W\n") || ops.contains("W*\n")); ctx.clear_clipping();
3248 assert!(!ctx.has_clipping());
3249 }
3250
3251 #[test]
3252 fn test_clipping_path_access() {
3253 let mut ctx = GraphicsContext::new();
3254
3255 assert!(ctx.clipping_path().is_none());
3257
3258 ctx.clip_rect(10.0, 10.0, 50.0, 50.0).unwrap();
3260 assert!(ctx.clipping_path().is_some());
3261
3262 ctx.clip_circle(100.0, 100.0, 25.0).unwrap();
3264 assert!(ctx.clipping_path().is_some());
3265
3266 ctx.save_state();
3268 ctx.clear_clipping();
3269 assert!(!ctx.has_clipping());
3270
3271 ctx.restore_state();
3272 assert!(ctx.has_clipping());
3274 }
3275
3276 #[test]
3279 fn test_edge_case_move_to_negative() {
3280 let mut ctx = GraphicsContext::new();
3281 ctx.move_to(-100.5, -200.25);
3282 assert!(ctx.operations().contains("-100.50 -200.25 m\n"));
3283 }
3284
3285 #[test]
3286 fn test_edge_case_opacity_out_of_range() {
3287 let mut ctx = GraphicsContext::new();
3288
3289 let _ = ctx.set_opacity(2.5);
3291 assert_eq!(ctx.fill_opacity(), 1.0);
3292
3293 let _ = ctx.set_opacity(-0.5);
3295 assert_eq!(ctx.fill_opacity(), 0.0);
3296 }
3297
3298 #[test]
3299 fn test_edge_case_line_width_extremes() {
3300 let mut ctx = GraphicsContext::new();
3301
3302 ctx.set_line_width(0.0);
3303 assert_eq!(ctx.line_width(), 0.0);
3304
3305 ctx.set_line_width(10000.0);
3306 assert_eq!(ctx.line_width(), 10000.0);
3307 }
3308
3309 #[test]
3312 fn test_interaction_transparency_plus_clipping() {
3313 let mut ctx = GraphicsContext::new();
3314
3315 ctx.set_alpha(0.5).unwrap();
3316 ctx.clip_rect(10.0, 10.0, 100.0, 100.0).unwrap();
3317 ctx.rect(20.0, 20.0, 80.0, 80.0).fill();
3318
3319 let ops = ctx.generate_operations().unwrap();
3320 let output = String::from_utf8_lossy(&ops);
3321
3322 assert!(output.contains("W\n") || output.contains("W*\n"));
3324 assert!(output.contains("/GS"));
3325 }
3326
3327 #[test]
3328 fn test_interaction_extgstate_plus_text() {
3329 let mut ctx = GraphicsContext::new();
3330
3331 let mut state = ExtGState::new();
3332 state.alpha_fill = Some(0.7);
3333 ctx.apply_extgstate(state).unwrap();
3334
3335 ctx.set_font(Font::Helvetica, 14.0);
3336 ctx.draw_text("Test", 100.0, 200.0).unwrap();
3337
3338 let ops = ctx.generate_operations().unwrap();
3339 let output = String::from_utf8_lossy(&ops);
3340
3341 assert!(output.contains("/GS"));
3342 assert!(output.contains("BT\n"));
3343 }
3344
3345 #[test]
3346 fn test_interaction_chained_transformations() {
3347 let mut ctx = GraphicsContext::new();
3348
3349 ctx.translate(50.0, 100.0);
3350 ctx.rotate(45.0);
3351 ctx.scale(2.0, 2.0);
3352
3353 let ops = ctx.operations();
3354 assert_eq!(ops.matches("cm\n").count(), 3);
3355 }
3356
3357 #[test]
3360 fn test_e2e_complete_page_with_header() {
3361 use crate::{Document, Page};
3362
3363 let mut doc = Document::new();
3364 let mut page = Page::a4();
3365 let ctx = page.graphics();
3366
3367 ctx.save_state();
3369 let _ = ctx.set_fill_opacity(0.3);
3370 ctx.set_fill_color(Color::rgb(200.0, 200.0, 255.0));
3371 ctx.rect(0.0, 750.0, 595.0, 42.0).fill();
3372 ctx.restore_state();
3373
3374 ctx.save_state();
3376 ctx.clip_rect(50.0, 50.0, 495.0, 692.0).unwrap();
3377 ctx.rect(60.0, 60.0, 100.0, 100.0).fill();
3378 ctx.restore_state();
3379
3380 let ops = ctx.generate_operations().unwrap();
3381 let output = String::from_utf8_lossy(&ops);
3382
3383 assert!(output.contains("q\n"));
3384 assert!(output.contains("Q\n"));
3385 assert!(output.contains("f\n"));
3386
3387 doc.add_page(page);
3388 assert!(doc.to_bytes().unwrap().len() > 0);
3389 }
3390
3391 #[test]
3392 fn test_e2e_watermark_workflow() {
3393 let mut ctx = GraphicsContext::new();
3394
3395 ctx.save_state();
3396 let _ = ctx.set_fill_opacity(0.2);
3397 ctx.translate(300.0, 400.0);
3398 ctx.rotate(45.0);
3399 ctx.set_font(Font::HelveticaBold, 72.0);
3400 ctx.draw_text("DRAFT", 0.0, 0.0).unwrap();
3401 ctx.restore_state();
3402
3403 let ops = ctx.generate_operations().unwrap();
3404 let output = String::from_utf8_lossy(&ops);
3405
3406 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")); }
3413
3414 #[test]
3417 fn test_set_custom_font_emits_tf_operator() {
3418 let mut ctx = GraphicsContext::new();
3419 ctx.set_custom_font("NotoSansCJK", 14.0);
3420
3421 let ops = ctx.operations();
3422 assert!(
3423 ops.contains("/NotoSansCJK 14 Tf"),
3424 "set_custom_font should emit Tf operator, got: {}",
3425 ops
3426 );
3427 }
3428
3429 #[test]
3432 fn test_draw_text_uses_is_custom_font_flag() {
3433 let mut ctx = GraphicsContext::new();
3434 ctx.set_custom_font("Helvetica", 12.0);
3436 ctx.clear(); ctx.draw_text("A", 10.0, 20.0).unwrap();
3439 let ops = ctx.operations();
3440 assert!(
3442 ops.contains("<0041> Tj"),
3443 "draw_text with is_custom_font=true should use hex, got: {}",
3444 ops
3445 );
3446 }
3447
3448 #[test]
3449 fn test_draw_text_standard_font_uses_literal() {
3450 let mut ctx = GraphicsContext::new();
3451 ctx.set_font(Font::Helvetica, 12.0);
3452 ctx.clear();
3453
3454 ctx.draw_text("Hello", 10.0, 20.0).unwrap();
3455 let ops = ctx.operations();
3456 assert!(
3457 ops.contains("(Hello) Tj"),
3458 "draw_text with standard font should use literal, got: {}",
3459 ops
3460 );
3461 }
3462
3463 #[test]
3466 fn test_show_text_smp_character_uses_surrogate_pairs() {
3467 let mut ctx = GraphicsContext::new();
3468 ctx.set_font(Font::Custom("Emoji".to_string()), 12.0);
3469
3470 ctx.begin_text();
3471 ctx.set_text_position(0.0, 0.0);
3472 ctx.show_text("\u{1F600}").unwrap();
3474 ctx.end_text();
3475
3476 let ops = ctx.operations();
3477 assert!(
3478 ops.contains("<D83DDE00> Tj"),
3479 "SMP character should use UTF-16BE surrogate pair, got: {}",
3480 ops
3481 );
3482 assert!(
3483 !ops.contains("FFFD"),
3484 "SMP character must NOT be replaced with FFFD"
3485 );
3486 }
3487
3488 #[test]
3491 fn test_save_restore_preserves_font_state() {
3492 let mut ctx = GraphicsContext::new();
3493 ctx.set_font(Font::Custom("CJK".to_string()), 12.0);
3494 assert!(ctx.is_custom_font);
3495 assert_eq!(ctx.current_font_name, Some("CJK".to_string()));
3496 assert_eq!(ctx.current_font_size, 12.0);
3497
3498 ctx.save_state();
3499 ctx.set_font(Font::Helvetica, 10.0);
3500 assert!(!ctx.is_custom_font);
3501 assert_eq!(ctx.current_font_name, Some("Helvetica".to_string()));
3502
3503 ctx.restore_state();
3504 assert!(
3505 ctx.is_custom_font,
3506 "is_custom_font must be restored after restore_state"
3507 );
3508 assert_eq!(ctx.current_font_name, Some("CJK".to_string()));
3509 assert_eq!(ctx.current_font_size, 12.0);
3510 }
3511
3512 #[test]
3513 fn test_save_restore_mixed_font_encoding() {
3514 let mut ctx = GraphicsContext::new();
3515 ctx.set_font(Font::Custom("CJK".to_string()), 12.0);
3516
3517 ctx.save_state();
3519 ctx.set_font(Font::Helvetica, 10.0);
3520 ctx.begin_text();
3521 ctx.show_text("Hello").unwrap();
3522 ctx.end_text();
3523 ctx.restore_state();
3524
3525 ctx.begin_text();
3527 ctx.show_text("你好").unwrap();
3528 ctx.end_text();
3529
3530 let ops = ctx.operations();
3531 assert!(
3533 ops.contains("<4F60597D> Tj"),
3534 "After restore_state, CJK text should use hex encoding, got: {}",
3535 ops
3536 );
3537 }
3538}