1pub mod calibrated_color;
2pub mod clipping;
3mod color;
4mod color_profiles;
5pub mod devicen_color;
6pub mod form_xobject;
7mod indexed_color;
8pub mod lab_color;
9mod path;
10mod patterns;
11mod pdf_image;
12mod png_decoder;
13pub mod separation_color;
14mod shadings;
15pub mod soft_mask;
16pub mod state;
17pub mod transparency;
18
19pub use calibrated_color::{CalGrayColorSpace, CalRgbColorSpace, CalibratedColor};
20pub use clipping::{ClippingPath, ClippingRegion};
21pub use color::Color;
22pub use color_profiles::{IccColorSpace, IccProfile, IccProfileManager, StandardIccProfile};
23pub use devicen_color::{
24 AlternateColorSpace as DeviceNAlternateColorSpace, ColorantDefinition, ColorantType,
25 DeviceNAttributes, DeviceNColorSpace, LinearTransform, SampledFunction, TintTransformFunction,
26};
27pub use form_xobject::{
28 FormTemplates, FormXObject, FormXObjectBuilder, FormXObjectManager,
29 TransparencyGroup as FormTransparencyGroup,
30};
31pub use indexed_color::{BaseColorSpace, ColorLookupTable, IndexedColorManager, IndexedColorSpace};
32pub use lab_color::{LabColor, LabColorSpace};
33pub use path::{LineCap, LineJoin, PathBuilder, PathCommand, WindingRule};
34pub use patterns::{
35 PaintType, PatternGraphicsContext, PatternManager, PatternMatrix, PatternType, TilingPattern,
36 TilingType,
37};
38pub use pdf_image::{ColorSpace, Image, ImageFormat, MaskType};
39pub use separation_color::{
40 AlternateColorSpace, SeparationColor, SeparationColorSpace, SpotColors, TintTransform,
41};
42pub use shadings::{
43 AxialShading, ColorStop, FunctionBasedShading, Point, RadialShading, ShadingDefinition,
44 ShadingManager, ShadingPattern, ShadingType,
45};
46pub use soft_mask::{SoftMask, SoftMaskState, SoftMaskType};
47pub use state::{
48 BlendMode, ExtGState, ExtGStateFont, ExtGStateManager, Halftone, LineDashPattern,
49 RenderingIntent, TransferFunction,
50};
51pub use transparency::TransparencyGroup;
52use transparency::TransparencyGroupState;
53
54use crate::error::Result;
55use crate::text::{ColumnContent, ColumnLayout, Font, FontManager, ListElement, Table};
56use std::collections::{HashMap, HashSet};
57use std::fmt::Write;
58use std::sync::Arc;
59
60#[derive(Clone)]
61pub struct GraphicsContext {
62 operations: String,
63 current_color: Color,
64 stroke_color: Color,
65 line_width: f64,
66 fill_opacity: f64,
67 stroke_opacity: f64,
68 extgstate_manager: ExtGStateManager,
70 pending_extgstate: Option<ExtGState>,
71 current_dash_pattern: Option<LineDashPattern>,
72 current_miter_limit: f64,
73 current_line_cap: LineCap,
74 current_line_join: LineJoin,
75 current_rendering_intent: RenderingIntent,
76 current_flatness: f64,
77 current_smoothness: f64,
78 clipping_region: ClippingRegion,
80 font_manager: Option<Arc<FontManager>>,
82 state_stack: Vec<(Color, Color)>,
84 current_font_name: Option<String>,
85 current_font_size: f64,
86 used_characters: HashSet<char>,
88 glyph_mapping: Option<HashMap<u32, u16>>,
90 transparency_stack: Vec<TransparencyGroupState>,
92}
93
94impl Default for GraphicsContext {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl GraphicsContext {
101 pub fn new() -> Self {
102 Self {
103 operations: String::new(),
104 current_color: Color::black(),
105 stroke_color: Color::black(),
106 line_width: 1.0,
107 fill_opacity: 1.0,
108 stroke_opacity: 1.0,
109 extgstate_manager: ExtGStateManager::new(),
111 pending_extgstate: None,
112 current_dash_pattern: None,
113 current_miter_limit: 10.0,
114 current_line_cap: LineCap::Butt,
115 current_line_join: LineJoin::Miter,
116 current_rendering_intent: RenderingIntent::RelativeColorimetric,
117 current_flatness: 1.0,
118 current_smoothness: 0.0,
119 clipping_region: ClippingRegion::new(),
121 font_manager: None,
123 state_stack: Vec::new(),
124 current_font_name: None,
125 current_font_size: 12.0,
126 used_characters: HashSet::new(),
127 glyph_mapping: None,
128 transparency_stack: Vec::new(),
129 }
130 }
131
132 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
133 writeln!(&mut self.operations, "{x:.2} {y:.2} m")
134 .expect("Writing to string should never fail");
135 self
136 }
137
138 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
139 writeln!(&mut self.operations, "{x:.2} {y:.2} l")
140 .expect("Writing to string should never fail");
141 self
142 }
143
144 pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
145 writeln!(
146 &mut self.operations,
147 "{x1:.2} {y1:.2} {x2:.2} {y2:.2} {x3:.2} {y3:.2} c"
148 )
149 .expect("Writing to string should never fail");
150 self
151 }
152
153 pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
154 writeln!(
155 &mut self.operations,
156 "{x:.2} {y:.2} {width:.2} {height:.2} re"
157 )
158 .expect("Writing to string should never fail");
159 self
160 }
161
162 pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
163 let k = 0.552284749831;
164 let r = radius;
165
166 self.move_to(cx + r, cy);
167 self.curve_to(cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r);
168 self.curve_to(cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy);
169 self.curve_to(cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r);
170 self.curve_to(cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy);
171 self.close_path()
172 }
173
174 pub fn close_path(&mut self) -> &mut Self {
175 self.operations.push_str("h\n");
176 self
177 }
178
179 pub fn stroke(&mut self) -> &mut Self {
180 self.apply_pending_extgstate().unwrap_or_default();
181 self.apply_stroke_color();
182 self.operations.push_str("S\n");
183 self
184 }
185
186 pub fn fill(&mut self) -> &mut Self {
187 self.apply_pending_extgstate().unwrap_or_default();
188 self.apply_fill_color();
189 self.operations.push_str("f\n");
190 self
191 }
192
193 pub fn fill_stroke(&mut self) -> &mut Self {
194 self.apply_pending_extgstate().unwrap_or_default();
195 self.apply_fill_color();
196 self.apply_stroke_color();
197 self.operations.push_str("B\n");
198 self
199 }
200
201 pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
202 self.stroke_color = color;
203 self
204 }
205
206 pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
207 self.current_color = color;
208 self
209 }
210
211 pub fn set_fill_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
213 let cs_name = match &color {
215 CalibratedColor::Gray(_, _) => "CalGray1",
216 CalibratedColor::Rgb(_, _) => "CalRGB1",
217 };
218
219 writeln!(&mut self.operations, "/{} cs", cs_name)
221 .expect("Writing to string should never fail");
222
223 let values = color.values();
225 for value in &values {
226 write!(&mut self.operations, "{:.4} ", value)
227 .expect("Writing to string should never fail");
228 }
229 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
230
231 self
232 }
233
234 pub fn set_stroke_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
236 let cs_name = match &color {
238 CalibratedColor::Gray(_, _) => "CalGray1",
239 CalibratedColor::Rgb(_, _) => "CalRGB1",
240 };
241
242 writeln!(&mut self.operations, "/{} CS", cs_name)
244 .expect("Writing to string should never fail");
245
246 let values = color.values();
248 for value in &values {
249 write!(&mut self.operations, "{:.4} ", value)
250 .expect("Writing to string should never fail");
251 }
252 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
253
254 self
255 }
256
257 pub fn set_fill_color_lab(&mut self, color: LabColor) -> &mut Self {
259 writeln!(&mut self.operations, "/Lab1 cs").expect("Writing to string should never fail");
261
262 let values = color.values();
264 for value in &values {
265 write!(&mut self.operations, "{:.4} ", value)
266 .expect("Writing to string should never fail");
267 }
268 writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
269
270 self
271 }
272
273 pub fn set_stroke_color_lab(&mut self, color: LabColor) -> &mut Self {
275 writeln!(&mut self.operations, "/Lab1 CS").expect("Writing to string should never fail");
277
278 let values = color.values();
280 for value in &values {
281 write!(&mut self.operations, "{:.4} ", value)
282 .expect("Writing to string should never fail");
283 }
284 writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
285
286 self
287 }
288
289 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
290 self.line_width = width;
291 writeln!(&mut self.operations, "{width:.2} w")
292 .expect("Writing to string should never fail");
293 self
294 }
295
296 pub fn set_line_cap(&mut self, cap: LineCap) -> &mut Self {
297 self.current_line_cap = cap;
298 writeln!(&mut self.operations, "{} J", cap as u8)
299 .expect("Writing to string should never fail");
300 self
301 }
302
303 pub fn set_line_join(&mut self, join: LineJoin) -> &mut Self {
304 self.current_line_join = join;
305 writeln!(&mut self.operations, "{} j", join as u8)
306 .expect("Writing to string should never fail");
307 self
308 }
309
310 pub fn set_opacity(&mut self, opacity: f64) -> &mut Self {
312 let opacity = opacity.clamp(0.0, 1.0);
313 self.fill_opacity = opacity;
314 self.stroke_opacity = opacity;
315
316 if opacity < 1.0 {
318 let mut state = ExtGState::new();
319 state.alpha_fill = Some(opacity);
320 state.alpha_stroke = Some(opacity);
321 self.pending_extgstate = Some(state);
322 }
323
324 self
325 }
326
327 pub fn set_fill_opacity(&mut self, opacity: f64) -> &mut Self {
329 self.fill_opacity = opacity.clamp(0.0, 1.0);
330
331 if opacity < 1.0 {
333 if let Some(ref mut state) = self.pending_extgstate {
334 state.alpha_fill = Some(opacity);
335 } else {
336 let mut state = ExtGState::new();
337 state.alpha_fill = Some(opacity);
338 self.pending_extgstate = Some(state);
339 }
340 }
341
342 self
343 }
344
345 pub fn set_stroke_opacity(&mut self, opacity: f64) -> &mut Self {
347 self.stroke_opacity = opacity.clamp(0.0, 1.0);
348
349 if opacity < 1.0 {
351 if let Some(ref mut state) = self.pending_extgstate {
352 state.alpha_stroke = Some(opacity);
353 } else {
354 let mut state = ExtGState::new();
355 state.alpha_stroke = Some(opacity);
356 self.pending_extgstate = Some(state);
357 }
358 }
359
360 self
361 }
362
363 pub fn save_state(&mut self) -> &mut Self {
364 self.operations.push_str("q\n");
365 self.save_clipping_state();
366 self.state_stack
368 .push((self.current_color, self.stroke_color));
369 self
370 }
371
372 pub fn restore_state(&mut self) -> &mut Self {
373 self.operations.push_str("Q\n");
374 self.restore_clipping_state();
375 if let Some((fill, stroke)) = self.state_stack.pop() {
377 self.current_color = fill;
378 self.stroke_color = stroke;
379 }
380 self
381 }
382
383 pub fn begin_transparency_group(&mut self, group: TransparencyGroup) -> &mut Self {
386 self.save_state();
388
389 writeln!(&mut self.operations, "% Begin Transparency Group")
391 .expect("Writing to string should never fail");
392
393 let mut extgstate = ExtGState::new();
395 extgstate = extgstate.with_blend_mode(group.blend_mode.clone());
396 extgstate.alpha_fill = Some(group.opacity as f64);
397 extgstate.alpha_stroke = Some(group.opacity as f64);
398
399 self.pending_extgstate = Some(extgstate);
401 let _ = self.apply_pending_extgstate();
402
403 let mut group_state = TransparencyGroupState::new(group);
405 group_state.saved_state = self.operations.as_bytes().to_vec();
407 self.transparency_stack.push(group_state);
408
409 self
410 }
411
412 pub fn end_transparency_group(&mut self) -> &mut Self {
414 if let Some(_group_state) = self.transparency_stack.pop() {
415 writeln!(&mut self.operations, "% End Transparency Group")
417 .expect("Writing to string should never fail");
418
419 self.restore_state();
421 }
422 self
423 }
424
425 pub fn in_transparency_group(&self) -> bool {
427 !self.transparency_stack.is_empty()
428 }
429
430 pub fn current_transparency_group(&self) -> Option<&TransparencyGroup> {
432 self.transparency_stack.last().map(|state| &state.group)
433 }
434
435 pub fn translate(&mut self, tx: f64, ty: f64) -> &mut Self {
436 writeln!(&mut self.operations, "1 0 0 1 {tx:.2} {ty:.2} cm")
437 .expect("Writing to string should never fail");
438 self
439 }
440
441 pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
442 writeln!(&mut self.operations, "{sx:.2} 0 0 {sy:.2} 0 0 cm")
443 .expect("Writing to string should never fail");
444 self
445 }
446
447 pub fn rotate(&mut self, angle: f64) -> &mut Self {
448 let cos = angle.cos();
449 let sin = angle.sin();
450 writeln!(
451 &mut self.operations,
452 "{:.6} {:.6} {:.6} {:.6} 0 0 cm",
453 cos, sin, -sin, cos
454 )
455 .expect("Writing to string should never fail");
456 self
457 }
458
459 pub fn transform(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> &mut Self {
460 writeln!(
461 &mut self.operations,
462 "{a:.2} {b:.2} {c:.2} {d:.2} {e:.2} {f:.2} cm"
463 )
464 .expect("Writing to string should never fail");
465 self
466 }
467
468 pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
469 self.rect(x, y, width, height)
470 }
471
472 pub fn draw_image(
473 &mut self,
474 image_name: &str,
475 x: f64,
476 y: f64,
477 width: f64,
478 height: f64,
479 ) -> &mut Self {
480 self.save_state();
482
483 writeln!(
486 &mut self.operations,
487 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
488 )
489 .expect("Writing to string should never fail");
490
491 writeln!(&mut self.operations, "/{image_name} Do")
493 .expect("Writing to string should never fail");
494
495 self.restore_state();
497
498 self
499 }
500
501 pub fn draw_image_with_transparency(
504 &mut self,
505 image_name: &str,
506 x: f64,
507 y: f64,
508 width: f64,
509 height: f64,
510 mask_name: Option<&str>,
511 ) -> &mut Self {
512 self.save_state();
514
515 if let Some(mask) = mask_name {
517 let mut extgstate = ExtGState::new();
519 extgstate.set_soft_mask_name(mask.to_string());
520
521 let gs_name = self
523 .extgstate_manager
524 .add_state(extgstate)
525 .unwrap_or_else(|_| "GS1".to_string());
526 writeln!(&mut self.operations, "/{} gs", gs_name)
527 .expect("Writing to string should never fail");
528 }
529
530 writeln!(
532 &mut self.operations,
533 "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
534 )
535 .expect("Writing to string should never fail");
536
537 writeln!(&mut self.operations, "/{image_name} Do")
539 .expect("Writing to string should never fail");
540
541 if mask_name.is_some() {
543 let mut reset_extgstate = ExtGState::new();
545 reset_extgstate.set_soft_mask_none();
546
547 let gs_name = self
548 .extgstate_manager
549 .add_state(reset_extgstate)
550 .unwrap_or_else(|_| "GS2".to_string());
551 writeln!(&mut self.operations, "/{} gs", gs_name)
552 .expect("Writing to string should never fail");
553 }
554
555 self.restore_state();
557
558 self
559 }
560
561 fn apply_stroke_color(&mut self) {
562 match self.stroke_color {
563 Color::Rgb(r, g, b) => {
564 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} RG")
565 .expect("Writing to string should never fail");
566 }
567 Color::Gray(g) => {
568 writeln!(&mut self.operations, "{g:.3} G")
569 .expect("Writing to string should never fail");
570 }
571 Color::Cmyk(c, m, y, k) => {
572 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} K")
573 .expect("Writing to string should never fail");
574 }
575 }
576 }
577
578 fn apply_fill_color(&mut self) {
579 match self.current_color {
580 Color::Rgb(r, g, b) => {
581 writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} rg")
582 .expect("Writing to string should never fail");
583 }
584 Color::Gray(g) => {
585 writeln!(&mut self.operations, "{g:.3} g")
586 .expect("Writing to string should never fail");
587 }
588 Color::Cmyk(c, m, y, k) => {
589 writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} k")
590 .expect("Writing to string should never fail");
591 }
592 }
593 }
594
595 pub(crate) fn generate_operations(&self) -> Result<Vec<u8>> {
596 Ok(self.operations.as_bytes().to_vec())
597 }
598
599 pub fn uses_transparency(&self) -> bool {
601 self.fill_opacity < 1.0 || self.stroke_opacity < 1.0
602 }
603
604 pub fn generate_graphics_state_dict(&self) -> Option<String> {
606 if !self.uses_transparency() {
607 return None;
608 }
609
610 let mut dict = String::from("<< /Type /ExtGState");
611
612 if self.fill_opacity < 1.0 {
613 write!(&mut dict, " /ca {:.3}", self.fill_opacity)
614 .expect("Writing to string should never fail");
615 }
616
617 if self.stroke_opacity < 1.0 {
618 write!(&mut dict, " /CA {:.3}", self.stroke_opacity)
619 .expect("Writing to string should never fail");
620 }
621
622 dict.push_str(" >>");
623 Some(dict)
624 }
625
626 pub fn fill_color(&self) -> Color {
628 self.current_color
629 }
630
631 pub fn stroke_color(&self) -> Color {
633 self.stroke_color
634 }
635
636 pub fn line_width(&self) -> f64 {
638 self.line_width
639 }
640
641 pub fn fill_opacity(&self) -> f64 {
643 self.fill_opacity
644 }
645
646 pub fn stroke_opacity(&self) -> f64 {
648 self.stroke_opacity
649 }
650
651 pub fn operations(&self) -> &str {
653 &self.operations
654 }
655
656 pub fn get_operations(&self) -> &str {
658 &self.operations
659 }
660
661 pub fn clear(&mut self) {
663 self.operations.clear();
664 }
665
666 pub fn begin_text(&mut self) -> &mut Self {
668 self.operations.push_str("BT\n");
669 self
670 }
671
672 pub fn end_text(&mut self) -> &mut Self {
674 self.operations.push_str("ET\n");
675 self
676 }
677
678 pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
680 writeln!(&mut self.operations, "/{} {} Tf", font.pdf_name(), size)
681 .expect("Writing to string should never fail");
682
683 match &font {
685 Font::Custom(name) => {
686 self.current_font_name = Some(name.clone());
687 self.current_font_size = size;
688 }
689 _ => {
690 self.current_font_name = Some(font.pdf_name());
691 self.current_font_size = size;
692 }
693 }
694
695 self
696 }
697
698 pub fn set_text_position(&mut self, x: f64, y: f64) -> &mut Self {
700 writeln!(&mut self.operations, "{x:.2} {y:.2} Td")
701 .expect("Writing to string should never fail");
702 self
703 }
704
705 pub fn show_text(&mut self, text: &str) -> Result<&mut Self> {
707 self.operations.push('(');
709 for ch in text.chars() {
710 match ch {
711 '(' => self.operations.push_str("\\("),
712 ')' => self.operations.push_str("\\)"),
713 '\\' => self.operations.push_str("\\\\"),
714 '\n' => self.operations.push_str("\\n"),
715 '\r' => self.operations.push_str("\\r"),
716 '\t' => self.operations.push_str("\\t"),
717 _ => self.operations.push(ch),
718 }
719 }
720 self.operations.push_str(") Tj\n");
721 Ok(self)
722 }
723
724 pub fn set_word_spacing(&mut self, spacing: f64) -> &mut Self {
726 writeln!(&mut self.operations, "{spacing:.2} Tw")
727 .expect("Writing to string should never fail");
728 self
729 }
730
731 pub fn set_character_spacing(&mut self, spacing: f64) -> &mut Self {
733 writeln!(&mut self.operations, "{spacing:.2} Tc")
734 .expect("Writing to string should never fail");
735 self
736 }
737
738 pub fn show_justified_text(&mut self, text: &str, target_width: f64) -> Result<&mut Self> {
740 let words: Vec<&str> = text.split_whitespace().collect();
742 if words.len() <= 1 {
743 return self.show_text(text);
745 }
746
747 let text_without_spaces = words.join("");
749 let natural_text_width = self.estimate_text_width_simple(&text_without_spaces);
750 let space_width = self.estimate_text_width_simple(" ");
751 let natural_width = natural_text_width + (space_width * (words.len() - 1) as f64);
752
753 let extra_space_needed = target_width - natural_width;
755 let word_gaps = (words.len() - 1) as f64;
756
757 if word_gaps > 0.0 && extra_space_needed > 0.0 {
758 let extra_word_spacing = extra_space_needed / word_gaps;
759
760 self.set_word_spacing(extra_word_spacing);
762
763 self.show_text(text)?;
765
766 self.set_word_spacing(0.0);
768 } else {
769 self.show_text(text)?;
771 }
772
773 Ok(self)
774 }
775
776 fn estimate_text_width_simple(&self, text: &str) -> f64 {
778 let font_size = self.current_font_size;
781 text.len() as f64 * font_size * 0.6 }
783
784 pub fn render_table(&mut self, table: &Table) -> Result<()> {
786 table.render(self)
787 }
788
789 pub fn render_list(&mut self, list: &ListElement) -> Result<()> {
791 match list {
792 ListElement::Ordered(ordered) => ordered.render(self),
793 ListElement::Unordered(unordered) => unordered.render(self),
794 }
795 }
796
797 pub fn render_column_layout(
799 &mut self,
800 layout: &ColumnLayout,
801 content: &ColumnContent,
802 x: f64,
803 y: f64,
804 height: f64,
805 ) -> Result<()> {
806 layout.render(self, content, x, y, height)
807 }
808
809 pub fn set_line_dash_pattern(&mut self, pattern: LineDashPattern) -> &mut Self {
813 self.current_dash_pattern = Some(pattern.clone());
814 writeln!(&mut self.operations, "{} d", pattern.to_pdf_string())
815 .expect("Writing to string should never fail");
816 self
817 }
818
819 pub fn set_line_solid(&mut self) -> &mut Self {
821 self.current_dash_pattern = None;
822 self.operations.push_str("[] 0 d\n");
823 self
824 }
825
826 pub fn set_miter_limit(&mut self, limit: f64) -> &mut Self {
828 self.current_miter_limit = limit.max(1.0);
829 writeln!(&mut self.operations, "{:.2} M", self.current_miter_limit)
830 .expect("Writing to string should never fail");
831 self
832 }
833
834 pub fn set_rendering_intent(&mut self, intent: RenderingIntent) -> &mut Self {
836 self.current_rendering_intent = intent;
837 writeln!(&mut self.operations, "/{} ri", intent.pdf_name())
838 .expect("Writing to string should never fail");
839 self
840 }
841
842 pub fn set_flatness(&mut self, flatness: f64) -> &mut Self {
844 self.current_flatness = flatness.clamp(0.0, 100.0);
845 writeln!(&mut self.operations, "{:.2} i", self.current_flatness)
846 .expect("Writing to string should never fail");
847 self
848 }
849
850 pub fn apply_extgstate(&mut self, state: ExtGState) -> Result<&mut Self> {
852 let state_name = self.extgstate_manager.add_state(state)?;
853 writeln!(&mut self.operations, "/{state_name} gs")
854 .expect("Writing to string should never fail");
855 Ok(self)
856 }
857
858 fn set_pending_extgstate(&mut self, state: ExtGState) {
860 self.pending_extgstate = Some(state);
861 }
862
863 fn apply_pending_extgstate(&mut self) -> Result<()> {
865 if let Some(state) = self.pending_extgstate.take() {
866 let state_name = self.extgstate_manager.add_state(state)?;
867 writeln!(&mut self.operations, "/{state_name} gs")
868 .expect("Writing to string should never fail");
869 }
870 Ok(())
871 }
872
873 pub fn with_extgstate<F>(&mut self, builder: F) -> Result<&mut Self>
875 where
876 F: FnOnce(ExtGState) -> ExtGState,
877 {
878 let state = builder(ExtGState::new());
879 self.apply_extgstate(state)
880 }
881
882 pub fn set_blend_mode(&mut self, mode: BlendMode) -> Result<&mut Self> {
884 let state = ExtGState::new().with_blend_mode(mode);
885 self.apply_extgstate(state)
886 }
887
888 pub fn set_alpha(&mut self, alpha: f64) -> Result<&mut Self> {
890 let state = ExtGState::new().with_alpha(alpha);
891 self.set_pending_extgstate(state);
892 Ok(self)
893 }
894
895 pub fn set_alpha_stroke(&mut self, alpha: f64) -> Result<&mut Self> {
897 let state = ExtGState::new().with_alpha_stroke(alpha);
898 self.set_pending_extgstate(state);
899 Ok(self)
900 }
901
902 pub fn set_alpha_fill(&mut self, alpha: f64) -> Result<&mut Self> {
904 let state = ExtGState::new().with_alpha_fill(alpha);
905 self.set_pending_extgstate(state);
906 Ok(self)
907 }
908
909 pub fn set_overprint_stroke(&mut self, overprint: bool) -> Result<&mut Self> {
911 let state = ExtGState::new().with_overprint_stroke(overprint);
912 self.apply_extgstate(state)
913 }
914
915 pub fn set_overprint_fill(&mut self, overprint: bool) -> Result<&mut Self> {
917 let state = ExtGState::new().with_overprint_fill(overprint);
918 self.apply_extgstate(state)
919 }
920
921 pub fn set_stroke_adjustment(&mut self, adjustment: bool) -> Result<&mut Self> {
923 let state = ExtGState::new().with_stroke_adjustment(adjustment);
924 self.apply_extgstate(state)
925 }
926
927 pub fn set_smoothness(&mut self, smoothness: f64) -> Result<&mut Self> {
929 self.current_smoothness = smoothness.clamp(0.0, 1.0);
930 let state = ExtGState::new().with_smoothness(self.current_smoothness);
931 self.apply_extgstate(state)
932 }
933
934 pub fn line_dash_pattern(&self) -> Option<&LineDashPattern> {
938 self.current_dash_pattern.as_ref()
939 }
940
941 pub fn miter_limit(&self) -> f64 {
943 self.current_miter_limit
944 }
945
946 pub fn line_cap(&self) -> LineCap {
948 self.current_line_cap
949 }
950
951 pub fn line_join(&self) -> LineJoin {
953 self.current_line_join
954 }
955
956 pub fn rendering_intent(&self) -> RenderingIntent {
958 self.current_rendering_intent
959 }
960
961 pub fn flatness(&self) -> f64 {
963 self.current_flatness
964 }
965
966 pub fn smoothness(&self) -> f64 {
968 self.current_smoothness
969 }
970
971 pub fn extgstate_manager(&self) -> &ExtGStateManager {
973 &self.extgstate_manager
974 }
975
976 pub fn extgstate_manager_mut(&mut self) -> &mut ExtGStateManager {
978 &mut self.extgstate_manager
979 }
980
981 pub fn generate_extgstate_resources(&self) -> Result<String> {
983 self.extgstate_manager.to_resource_dictionary()
984 }
985
986 pub fn has_extgstates(&self) -> bool {
988 self.extgstate_manager.count() > 0
989 }
990
991 pub fn add_command(&mut self, command: &str) {
993 self.operations.push_str(command);
994 self.operations.push('\n');
995 }
996
997 pub fn clip(&mut self) -> &mut Self {
999 self.operations.push_str("W\n");
1000 self
1001 }
1002
1003 pub fn clip_even_odd(&mut self) -> &mut Self {
1005 self.operations.push_str("W*\n");
1006 self
1007 }
1008
1009 pub fn clip_stroke(&mut self) -> &mut Self {
1011 self.apply_stroke_color();
1012 self.operations.push_str("W S\n");
1013 self
1014 }
1015
1016 pub fn set_clipping_path(&mut self, path: ClippingPath) -> Result<&mut Self> {
1018 let ops = path.to_pdf_operations()?;
1019 self.operations.push_str(&ops);
1020 self.clipping_region.set_clip(path);
1021 Ok(self)
1022 }
1023
1024 pub fn clear_clipping(&mut self) -> &mut Self {
1026 self.clipping_region.clear_clip();
1027 self
1028 }
1029
1030 fn save_clipping_state(&mut self) {
1032 self.clipping_region.save();
1033 }
1034
1035 fn restore_clipping_state(&mut self) {
1037 self.clipping_region.restore();
1038 }
1039
1040 pub fn clip_rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> Result<&mut Self> {
1042 let path = ClippingPath::rect(x, y, width, height);
1043 self.set_clipping_path(path)
1044 }
1045
1046 pub fn clip_circle(&mut self, cx: f64, cy: f64, radius: f64) -> Result<&mut Self> {
1048 let path = ClippingPath::circle(cx, cy, radius);
1049 self.set_clipping_path(path)
1050 }
1051
1052 pub fn clip_ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) -> Result<&mut Self> {
1054 let path = ClippingPath::ellipse(cx, cy, rx, ry);
1055 self.set_clipping_path(path)
1056 }
1057
1058 pub fn has_clipping(&self) -> bool {
1060 self.clipping_region.has_clip()
1061 }
1062
1063 pub fn clipping_path(&self) -> Option<&ClippingPath> {
1065 self.clipping_region.current()
1066 }
1067
1068 pub fn set_font_manager(&mut self, font_manager: Arc<FontManager>) -> &mut Self {
1070 self.font_manager = Some(font_manager);
1071 self
1072 }
1073
1074 pub fn set_custom_font(&mut self, font_name: &str, size: f64) -> &mut Self {
1076 self.current_font_name = Some(font_name.to_string());
1077 self.current_font_size = size;
1078
1079 if let Some(ref font_manager) = self.font_manager {
1081 if let Some(mapping) = font_manager.get_font_glyph_mapping(font_name) {
1082 self.glyph_mapping = Some(mapping);
1083 }
1084 }
1085
1086 self
1087 }
1088
1089 pub fn set_glyph_mapping(&mut self, mapping: HashMap<u32, u16>) -> &mut Self {
1091 self.glyph_mapping = Some(mapping);
1092 self
1093 }
1094
1095 pub fn draw_text(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1097 self.used_characters.extend(text.chars());
1099
1100 let using_custom_font = if let Some(ref font_name) = self.current_font_name {
1103 !matches!(
1105 font_name.as_str(),
1106 "Helvetica"
1107 | "Times"
1108 | "Courier"
1109 | "Symbol"
1110 | "ZapfDingbats"
1111 | "Helvetica-Bold"
1112 | "Helvetica-Oblique"
1113 | "Helvetica-BoldOblique"
1114 | "Times-Roman"
1115 | "Times-Bold"
1116 | "Times-Italic"
1117 | "Times-BoldItalic"
1118 | "Courier-Bold"
1119 | "Courier-Oblique"
1120 | "Courier-BoldOblique"
1121 )
1122 } else {
1123 false
1124 };
1125
1126 let needs_unicode = text.chars().any(|c| c as u32 > 255) || using_custom_font;
1128
1129 if needs_unicode {
1131 self.draw_with_unicode_encoding(text, x, y)
1132 } else {
1133 self.draw_with_simple_encoding(text, x, y)
1134 }
1135 }
1136
1137 fn draw_with_simple_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1139 let has_unicode = text.chars().any(|c| c as u32 > 255);
1141
1142 if has_unicode {
1143 eprintln!("Warning: Text contains Unicode characters but using Latin-1 font. Characters will be replaced with '?'");
1145 }
1146
1147 self.operations.push_str("BT\n");
1149
1150 if let Some(font_name) = &self.current_font_name {
1152 writeln!(
1153 &mut self.operations,
1154 "/{} {} Tf",
1155 font_name, self.current_font_size
1156 )
1157 .expect("Writing to string should never fail");
1158 } else {
1159 writeln!(
1160 &mut self.operations,
1161 "/Helvetica {} Tf",
1162 self.current_font_size
1163 )
1164 .expect("Writing to string should never fail");
1165 }
1166
1167 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1169 .expect("Writing to string should never fail");
1170
1171 self.operations.push('(');
1174 for ch in text.chars() {
1175 let code = ch as u32;
1176 if code <= 127 {
1177 match ch {
1179 '(' => self.operations.push_str("\\("),
1180 ')' => self.operations.push_str("\\)"),
1181 '\\' => self.operations.push_str("\\\\"),
1182 '\n' => self.operations.push_str("\\n"),
1183 '\r' => self.operations.push_str("\\r"),
1184 '\t' => self.operations.push_str("\\t"),
1185 _ => self.operations.push(ch),
1186 }
1187 } else if code <= 255 {
1188 write!(&mut self.operations, "\\{:03o}", code)
1191 .expect("Writing to string should never fail");
1192 } else {
1193 self.operations.push('?');
1195 }
1196 }
1197 self.operations.push_str(") Tj\n");
1198
1199 self.operations.push_str("ET\n");
1201
1202 Ok(self)
1203 }
1204
1205 fn draw_with_unicode_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1207 self.operations.push_str("BT\n");
1209
1210 if let Some(font_name) = &self.current_font_name {
1212 writeln!(
1214 &mut self.operations,
1215 "/{} {} Tf",
1216 font_name, self.current_font_size
1217 )
1218 .expect("Writing to string should never fail");
1219 } else {
1220 writeln!(
1221 &mut self.operations,
1222 "/Helvetica {} Tf",
1223 self.current_font_size
1224 )
1225 .expect("Writing to string should never fail");
1226 }
1227
1228 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1230 .expect("Writing to string should never fail");
1231
1232 self.operations.push('<');
1236
1237 for ch in text.chars() {
1238 let code = ch as u32;
1239
1240 if code <= 0xFFFF {
1243 write!(&mut self.operations, "{:04X}", code)
1245 .expect("Writing to string should never fail");
1246 } else {
1247 write!(&mut self.operations, "FFFD").expect("Writing to string should never fail");
1250 }
1252 }
1253 self.operations.push_str("> Tj\n");
1254
1255 self.operations.push_str("ET\n");
1257
1258 Ok(self)
1259 }
1260
1261 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1263 pub fn draw_text_hex(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1264 self.operations.push_str("BT\n");
1266
1267 if let Some(font_name) = &self.current_font_name {
1269 writeln!(
1270 &mut self.operations,
1271 "/{} {} Tf",
1272 font_name, self.current_font_size
1273 )
1274 .expect("Writing to string should never fail");
1275 } else {
1276 writeln!(
1278 &mut self.operations,
1279 "/Helvetica {} Tf",
1280 self.current_font_size
1281 )
1282 .expect("Writing to string should never fail");
1283 }
1284
1285 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1287 .expect("Writing to string should never fail");
1288
1289 self.operations.push('<');
1293 for ch in text.chars() {
1294 if ch as u32 <= 255 {
1295 write!(&mut self.operations, "{:02X}", ch as u8)
1297 .expect("Writing to string should never fail");
1298 } else {
1299 write!(&mut self.operations, "3F").expect("Writing to string should never fail");
1302 }
1304 }
1305 self.operations.push_str("> Tj\n");
1306
1307 self.operations.push_str("ET\n");
1309
1310 Ok(self)
1311 }
1312
1313 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1315 pub fn draw_text_cid(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1316 use crate::fonts::needs_type0_font;
1317
1318 self.operations.push_str("BT\n");
1320
1321 if let Some(font_name) = &self.current_font_name {
1323 writeln!(
1324 &mut self.operations,
1325 "/{} {} Tf",
1326 font_name, self.current_font_size
1327 )
1328 .expect("Writing to string should never fail");
1329 } else {
1330 writeln!(
1331 &mut self.operations,
1332 "/Helvetica {} Tf",
1333 self.current_font_size
1334 )
1335 .expect("Writing to string should never fail");
1336 }
1337
1338 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1340 .expect("Writing to string should never fail");
1341
1342 if needs_type0_font(text) {
1344 self.operations.push('<');
1346 for ch in text.chars() {
1347 let code = ch as u32;
1348
1349 if code <= 0xFFFF {
1351 write!(&mut self.operations, "{:04X}", code)
1353 .expect("Writing to string should never fail");
1354 } else if code <= 0x10FFFF {
1355 let code = code - 0x10000;
1357 let high = ((code >> 10) & 0x3FF) + 0xD800;
1358 let low = (code & 0x3FF) + 0xDC00;
1359 write!(&mut self.operations, "{:04X}{:04X}", high, low)
1360 .expect("Writing to string should never fail");
1361 } else {
1362 write!(&mut self.operations, "FFFD")
1364 .expect("Writing to string should never fail");
1365 }
1366 }
1367 self.operations.push_str("> Tj\n");
1368 } else {
1369 self.operations.push('<');
1371 for ch in text.chars() {
1372 if ch as u32 <= 255 {
1373 write!(&mut self.operations, "{:02X}", ch as u8)
1374 .expect("Writing to string should never fail");
1375 } else {
1376 write!(&mut self.operations, "3F")
1377 .expect("Writing to string should never fail");
1378 }
1379 }
1380 self.operations.push_str("> Tj\n");
1381 }
1382
1383 self.operations.push_str("ET\n");
1385 Ok(self)
1386 }
1387
1388 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1390 pub fn draw_text_unicode(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1391 self.operations.push_str("BT\n");
1393
1394 if let Some(font_name) = &self.current_font_name {
1396 writeln!(
1397 &mut self.operations,
1398 "/{} {} Tf",
1399 font_name, self.current_font_size
1400 )
1401 .expect("Writing to string should never fail");
1402 } else {
1403 writeln!(
1405 &mut self.operations,
1406 "/Helvetica {} Tf",
1407 self.current_font_size
1408 )
1409 .expect("Writing to string should never fail");
1410 }
1411
1412 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1414 .expect("Writing to string should never fail");
1415
1416 self.operations.push('<');
1418 let mut utf16_buffer = [0u16; 2];
1419 for ch in text.chars() {
1420 let encoded = ch.encode_utf16(&mut utf16_buffer);
1421 for unit in encoded {
1422 write!(&mut self.operations, "{:04X}", unit)
1424 .expect("Writing to string should never fail");
1425 }
1426 }
1427 self.operations.push_str("> Tj\n");
1428
1429 self.operations.push_str("ET\n");
1431
1432 Ok(self)
1433 }
1434
1435 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1437 if self.used_characters.is_empty() {
1438 None
1439 } else {
1440 Some(self.used_characters.clone())
1441 }
1442 }
1443}
1444
1445#[cfg(test)]
1446mod tests {
1447 use super::*;
1448
1449 #[test]
1450 fn test_graphics_context_new() {
1451 let ctx = GraphicsContext::new();
1452 assert_eq!(ctx.fill_color(), Color::black());
1453 assert_eq!(ctx.stroke_color(), Color::black());
1454 assert_eq!(ctx.line_width(), 1.0);
1455 assert_eq!(ctx.fill_opacity(), 1.0);
1456 assert_eq!(ctx.stroke_opacity(), 1.0);
1457 assert!(ctx.operations().is_empty());
1458 }
1459
1460 #[test]
1461 fn test_graphics_context_default() {
1462 let ctx = GraphicsContext::default();
1463 assert_eq!(ctx.fill_color(), Color::black());
1464 assert_eq!(ctx.stroke_color(), Color::black());
1465 assert_eq!(ctx.line_width(), 1.0);
1466 }
1467
1468 #[test]
1469 fn test_move_to() {
1470 let mut ctx = GraphicsContext::new();
1471 ctx.move_to(10.0, 20.0);
1472 assert!(ctx.operations().contains("10.00 20.00 m\n"));
1473 }
1474
1475 #[test]
1476 fn test_line_to() {
1477 let mut ctx = GraphicsContext::new();
1478 ctx.line_to(30.0, 40.0);
1479 assert!(ctx.operations().contains("30.00 40.00 l\n"));
1480 }
1481
1482 #[test]
1483 fn test_curve_to() {
1484 let mut ctx = GraphicsContext::new();
1485 ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
1486 assert!(ctx
1487 .operations()
1488 .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
1489 }
1490
1491 #[test]
1492 fn test_rect() {
1493 let mut ctx = GraphicsContext::new();
1494 ctx.rect(10.0, 20.0, 100.0, 50.0);
1495 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1496 }
1497
1498 #[test]
1499 fn test_rectangle_alias() {
1500 let mut ctx = GraphicsContext::new();
1501 ctx.rectangle(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_circle() {
1507 let mut ctx = GraphicsContext::new();
1508 ctx.circle(50.0, 50.0, 25.0);
1509
1510 let ops = ctx.operations();
1511 assert!(ops.contains("75.00 50.00 m\n"));
1513 assert!(ops.contains(" c\n"));
1515 assert!(ops.contains("h\n"));
1517 }
1518
1519 #[test]
1520 fn test_close_path() {
1521 let mut ctx = GraphicsContext::new();
1522 ctx.close_path();
1523 assert!(ctx.operations().contains("h\n"));
1524 }
1525
1526 #[test]
1527 fn test_stroke() {
1528 let mut ctx = GraphicsContext::new();
1529 ctx.set_stroke_color(Color::red());
1530 ctx.rect(0.0, 0.0, 10.0, 10.0);
1531 ctx.stroke();
1532
1533 let ops = ctx.operations();
1534 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1535 assert!(ops.contains("S\n"));
1536 }
1537
1538 #[test]
1539 fn test_fill() {
1540 let mut ctx = GraphicsContext::new();
1541 ctx.set_fill_color(Color::blue());
1542 ctx.rect(0.0, 0.0, 10.0, 10.0);
1543 ctx.fill();
1544
1545 let ops = ctx.operations();
1546 assert!(ops.contains("0.000 0.000 1.000 rg\n"));
1547 assert!(ops.contains("f\n"));
1548 }
1549
1550 #[test]
1551 fn test_fill_stroke() {
1552 let mut ctx = GraphicsContext::new();
1553 ctx.set_fill_color(Color::green());
1554 ctx.set_stroke_color(Color::red());
1555 ctx.rect(0.0, 0.0, 10.0, 10.0);
1556 ctx.fill_stroke();
1557
1558 let ops = ctx.operations();
1559 assert!(ops.contains("0.000 1.000 0.000 rg\n"));
1560 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1561 assert!(ops.contains("B\n"));
1562 }
1563
1564 #[test]
1565 fn test_set_stroke_color() {
1566 let mut ctx = GraphicsContext::new();
1567 ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
1568 assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
1569 }
1570
1571 #[test]
1572 fn test_set_fill_color() {
1573 let mut ctx = GraphicsContext::new();
1574 ctx.set_fill_color(Color::gray(0.5));
1575 assert_eq!(ctx.fill_color(), Color::Gray(0.5));
1576 }
1577
1578 #[test]
1579 fn test_set_line_width() {
1580 let mut ctx = GraphicsContext::new();
1581 ctx.set_line_width(2.5);
1582 assert_eq!(ctx.line_width(), 2.5);
1583 assert!(ctx.operations().contains("2.50 w\n"));
1584 }
1585
1586 #[test]
1587 fn test_set_line_cap() {
1588 let mut ctx = GraphicsContext::new();
1589 ctx.set_line_cap(LineCap::Round);
1590 assert!(ctx.operations().contains("1 J\n"));
1591
1592 ctx.set_line_cap(LineCap::Butt);
1593 assert!(ctx.operations().contains("0 J\n"));
1594
1595 ctx.set_line_cap(LineCap::Square);
1596 assert!(ctx.operations().contains("2 J\n"));
1597 }
1598
1599 #[test]
1600 fn test_set_line_join() {
1601 let mut ctx = GraphicsContext::new();
1602 ctx.set_line_join(LineJoin::Round);
1603 assert!(ctx.operations().contains("1 j\n"));
1604
1605 ctx.set_line_join(LineJoin::Miter);
1606 assert!(ctx.operations().contains("0 j\n"));
1607
1608 ctx.set_line_join(LineJoin::Bevel);
1609 assert!(ctx.operations().contains("2 j\n"));
1610 }
1611
1612 #[test]
1613 fn test_save_restore_state() {
1614 let mut ctx = GraphicsContext::new();
1615 ctx.save_state();
1616 assert!(ctx.operations().contains("q\n"));
1617
1618 ctx.restore_state();
1619 assert!(ctx.operations().contains("Q\n"));
1620 }
1621
1622 #[test]
1623 fn test_translate() {
1624 let mut ctx = GraphicsContext::new();
1625 ctx.translate(50.0, 100.0);
1626 assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
1627 }
1628
1629 #[test]
1630 fn test_scale() {
1631 let mut ctx = GraphicsContext::new();
1632 ctx.scale(2.0, 3.0);
1633 assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
1634 }
1635
1636 #[test]
1637 fn test_rotate() {
1638 let mut ctx = GraphicsContext::new();
1639 let angle = std::f64::consts::PI / 4.0; ctx.rotate(angle);
1641
1642 let ops = ctx.operations();
1643 assert!(ops.contains(" cm\n"));
1644 assert!(ops.contains("0.707107")); }
1647
1648 #[test]
1649 fn test_transform() {
1650 let mut ctx = GraphicsContext::new();
1651 ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
1652 assert!(ctx
1653 .operations()
1654 .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
1655 }
1656
1657 #[test]
1658 fn test_draw_image() {
1659 let mut ctx = GraphicsContext::new();
1660 ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
1661
1662 let ops = ctx.operations();
1663 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")); }
1668
1669 #[test]
1670 fn test_gray_color_operations() {
1671 let mut ctx = GraphicsContext::new();
1672 ctx.set_stroke_color(Color::gray(0.5));
1673 ctx.set_fill_color(Color::gray(0.7));
1674 ctx.stroke();
1675 ctx.fill();
1676
1677 let ops = ctx.operations();
1678 assert!(ops.contains("0.500 G\n")); assert!(ops.contains("0.700 g\n")); }
1681
1682 #[test]
1683 fn test_cmyk_color_operations() {
1684 let mut ctx = GraphicsContext::new();
1685 ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
1686 ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
1687 ctx.stroke();
1688 ctx.fill();
1689
1690 let ops = ctx.operations();
1691 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")); }
1694
1695 #[test]
1696 fn test_method_chaining() {
1697 let mut ctx = GraphicsContext::new();
1698 ctx.move_to(0.0, 0.0)
1699 .line_to(10.0, 0.0)
1700 .line_to(10.0, 10.0)
1701 .line_to(0.0, 10.0)
1702 .close_path()
1703 .set_fill_color(Color::red())
1704 .fill();
1705
1706 let ops = ctx.operations();
1707 assert!(ops.contains("0.00 0.00 m\n"));
1708 assert!(ops.contains("10.00 0.00 l\n"));
1709 assert!(ops.contains("10.00 10.00 l\n"));
1710 assert!(ops.contains("0.00 10.00 l\n"));
1711 assert!(ops.contains("h\n"));
1712 assert!(ops.contains("f\n"));
1713 }
1714
1715 #[test]
1716 fn test_generate_operations() {
1717 let mut ctx = GraphicsContext::new();
1718 ctx.rect(0.0, 0.0, 10.0, 10.0);
1719
1720 let result = ctx.generate_operations();
1721 assert!(result.is_ok());
1722 let bytes = result.expect("Writing to string should never fail");
1723 let ops_string = String::from_utf8(bytes).expect("Writing to string should never fail");
1724 assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
1725 }
1726
1727 #[test]
1728 fn test_clear_operations() {
1729 let mut ctx = GraphicsContext::new();
1730 ctx.rect(0.0, 0.0, 10.0, 10.0);
1731 assert!(!ctx.operations().is_empty());
1732
1733 ctx.clear();
1734 assert!(ctx.operations().is_empty());
1735 }
1736
1737 #[test]
1738 fn test_complex_path() {
1739 let mut ctx = GraphicsContext::new();
1740 ctx.save_state()
1741 .translate(100.0, 100.0)
1742 .rotate(std::f64::consts::PI / 6.0)
1743 .scale(2.0, 2.0)
1744 .set_line_width(2.0)
1745 .set_stroke_color(Color::blue())
1746 .move_to(0.0, 0.0)
1747 .line_to(50.0, 0.0)
1748 .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
1749 .close_path()
1750 .stroke()
1751 .restore_state();
1752
1753 let ops = ctx.operations();
1754 assert!(ops.contains("q\n"));
1755 assert!(ops.contains("cm\n"));
1756 assert!(ops.contains("2.00 w\n"));
1757 assert!(ops.contains("0.000 0.000 1.000 RG\n"));
1758 assert!(ops.contains("S\n"));
1759 assert!(ops.contains("Q\n"));
1760 }
1761
1762 #[test]
1763 fn test_graphics_context_clone() {
1764 let mut ctx = GraphicsContext::new();
1765 ctx.set_fill_color(Color::red());
1766 ctx.set_stroke_color(Color::blue());
1767 ctx.set_line_width(3.0);
1768 ctx.set_opacity(0.5);
1769 ctx.rect(0.0, 0.0, 10.0, 10.0);
1770
1771 let ctx_clone = ctx.clone();
1772 assert_eq!(ctx_clone.fill_color(), Color::red());
1773 assert_eq!(ctx_clone.stroke_color(), Color::blue());
1774 assert_eq!(ctx_clone.line_width(), 3.0);
1775 assert_eq!(ctx_clone.fill_opacity(), 0.5);
1776 assert_eq!(ctx_clone.stroke_opacity(), 0.5);
1777 assert_eq!(ctx_clone.operations(), ctx.operations());
1778 }
1779
1780 #[test]
1781 fn test_set_opacity() {
1782 let mut ctx = GraphicsContext::new();
1783
1784 ctx.set_opacity(0.5);
1786 assert_eq!(ctx.fill_opacity(), 0.5);
1787 assert_eq!(ctx.stroke_opacity(), 0.5);
1788
1789 ctx.set_opacity(1.5);
1791 assert_eq!(ctx.fill_opacity(), 1.0);
1792 assert_eq!(ctx.stroke_opacity(), 1.0);
1793
1794 ctx.set_opacity(-0.5);
1795 assert_eq!(ctx.fill_opacity(), 0.0);
1796 assert_eq!(ctx.stroke_opacity(), 0.0);
1797 }
1798
1799 #[test]
1800 fn test_set_fill_opacity() {
1801 let mut ctx = GraphicsContext::new();
1802
1803 ctx.set_fill_opacity(0.3);
1804 assert_eq!(ctx.fill_opacity(), 0.3);
1805 assert_eq!(ctx.stroke_opacity(), 1.0); ctx.set_fill_opacity(2.0);
1809 assert_eq!(ctx.fill_opacity(), 1.0);
1810 }
1811
1812 #[test]
1813 fn test_set_stroke_opacity() {
1814 let mut ctx = GraphicsContext::new();
1815
1816 ctx.set_stroke_opacity(0.7);
1817 assert_eq!(ctx.stroke_opacity(), 0.7);
1818 assert_eq!(ctx.fill_opacity(), 1.0); ctx.set_stroke_opacity(-1.0);
1822 assert_eq!(ctx.stroke_opacity(), 0.0);
1823 }
1824
1825 #[test]
1826 fn test_uses_transparency() {
1827 let mut ctx = GraphicsContext::new();
1828
1829 assert!(!ctx.uses_transparency());
1831
1832 ctx.set_fill_opacity(0.5);
1834 assert!(ctx.uses_transparency());
1835
1836 ctx.set_fill_opacity(1.0);
1838 assert!(!ctx.uses_transparency());
1839 ctx.set_stroke_opacity(0.8);
1840 assert!(ctx.uses_transparency());
1841
1842 ctx.set_fill_opacity(0.5);
1844 assert!(ctx.uses_transparency());
1845 }
1846
1847 #[test]
1848 fn test_generate_graphics_state_dict() {
1849 let mut ctx = GraphicsContext::new();
1850
1851 assert_eq!(ctx.generate_graphics_state_dict(), None);
1853
1854 ctx.set_fill_opacity(0.5);
1856 let dict = ctx
1857 .generate_graphics_state_dict()
1858 .expect("Writing to string should never fail");
1859 assert!(dict.contains("/Type /ExtGState"));
1860 assert!(dict.contains("/ca 0.500"));
1861 assert!(!dict.contains("/CA"));
1862
1863 ctx.set_fill_opacity(1.0);
1865 ctx.set_stroke_opacity(0.75);
1866 let dict = ctx
1867 .generate_graphics_state_dict()
1868 .expect("Writing to string should never fail");
1869 assert!(dict.contains("/Type /ExtGState"));
1870 assert!(dict.contains("/CA 0.750"));
1871 assert!(!dict.contains("/ca"));
1872
1873 ctx.set_fill_opacity(0.25);
1875 let dict = ctx
1876 .generate_graphics_state_dict()
1877 .expect("Writing to string should never fail");
1878 assert!(dict.contains("/Type /ExtGState"));
1879 assert!(dict.contains("/ca 0.250"));
1880 assert!(dict.contains("/CA 0.750"));
1881 }
1882
1883 #[test]
1884 fn test_opacity_with_graphics_operations() {
1885 let mut ctx = GraphicsContext::new();
1886
1887 ctx.set_fill_color(Color::red())
1888 .set_opacity(0.5)
1889 .rect(10.0, 10.0, 100.0, 100.0)
1890 .fill();
1891
1892 assert_eq!(ctx.fill_opacity(), 0.5);
1893 assert_eq!(ctx.stroke_opacity(), 0.5);
1894
1895 let ops = ctx.operations();
1896 assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
1897 assert!(ops.contains("1.000 0.000 0.000 rg")); assert!(ops.contains("f")); }
1900
1901 #[test]
1902 fn test_begin_end_text() {
1903 let mut ctx = GraphicsContext::new();
1904 ctx.begin_text();
1905 assert!(ctx.operations().contains("BT\n"));
1906
1907 ctx.end_text();
1908 assert!(ctx.operations().contains("ET\n"));
1909 }
1910
1911 #[test]
1912 fn test_set_font() {
1913 let mut ctx = GraphicsContext::new();
1914 ctx.set_font(Font::Helvetica, 12.0);
1915 assert!(ctx.operations().contains("/Helvetica 12 Tf\n"));
1916
1917 ctx.set_font(Font::TimesBold, 14.5);
1918 assert!(ctx.operations().contains("/Times-Bold 14.5 Tf\n"));
1919 }
1920
1921 #[test]
1922 fn test_set_text_position() {
1923 let mut ctx = GraphicsContext::new();
1924 ctx.set_text_position(100.0, 200.0);
1925 assert!(ctx.operations().contains("100.00 200.00 Td\n"));
1926 }
1927
1928 #[test]
1929 fn test_show_text() {
1930 let mut ctx = GraphicsContext::new();
1931 ctx.show_text("Hello World")
1932 .expect("Writing to string should never fail");
1933 assert!(ctx.operations().contains("(Hello World) Tj\n"));
1934 }
1935
1936 #[test]
1937 fn test_show_text_with_escaping() {
1938 let mut ctx = GraphicsContext::new();
1939 ctx.show_text("Test (parentheses)")
1940 .expect("Writing to string should never fail");
1941 assert!(ctx.operations().contains("(Test \\(parentheses\\)) Tj\n"));
1942
1943 ctx.clear();
1944 ctx.show_text("Back\\slash")
1945 .expect("Writing to string should never fail");
1946 assert!(ctx.operations().contains("(Back\\\\slash) Tj\n"));
1947
1948 ctx.clear();
1949 ctx.show_text("Line\nBreak")
1950 .expect("Writing to string should never fail");
1951 assert!(ctx.operations().contains("(Line\\nBreak) Tj\n"));
1952 }
1953
1954 #[test]
1955 fn test_text_operations_chaining() {
1956 let mut ctx = GraphicsContext::new();
1957 ctx.begin_text()
1958 .set_font(Font::Courier, 10.0)
1959 .set_text_position(50.0, 100.0)
1960 .show_text("Test")
1961 .unwrap()
1962 .end_text();
1963
1964 let ops = ctx.operations();
1965 assert!(ops.contains("BT\n"));
1966 assert!(ops.contains("/Courier 10 Tf\n"));
1967 assert!(ops.contains("50.00 100.00 Td\n"));
1968 assert!(ops.contains("(Test) Tj\n"));
1969 assert!(ops.contains("ET\n"));
1970 }
1971
1972 #[test]
1973 fn test_clip() {
1974 let mut ctx = GraphicsContext::new();
1975 ctx.clip();
1976 assert!(ctx.operations().contains("W\n"));
1977 }
1978
1979 #[test]
1980 fn test_clip_even_odd() {
1981 let mut ctx = GraphicsContext::new();
1982 ctx.clip_even_odd();
1983 assert!(ctx.operations().contains("W*\n"));
1984 }
1985
1986 #[test]
1987 fn test_clipping_with_path() {
1988 let mut ctx = GraphicsContext::new();
1989
1990 ctx.rect(10.0, 10.0, 100.0, 50.0).clip();
1992
1993 let ops = ctx.operations();
1994 assert!(ops.contains("10.00 10.00 100.00 50.00 re\n"));
1995 assert!(ops.contains("W\n"));
1996 }
1997
1998 #[test]
1999 fn test_clipping_even_odd_with_path() {
2000 let mut ctx = GraphicsContext::new();
2001
2002 ctx.move_to(0.0, 0.0)
2004 .line_to(100.0, 0.0)
2005 .line_to(100.0, 100.0)
2006 .line_to(0.0, 100.0)
2007 .close_path()
2008 .clip_even_odd();
2009
2010 let ops = ctx.operations();
2011 assert!(ops.contains("0.00 0.00 m\n"));
2012 assert!(ops.contains("100.00 0.00 l\n"));
2013 assert!(ops.contains("100.00 100.00 l\n"));
2014 assert!(ops.contains("0.00 100.00 l\n"));
2015 assert!(ops.contains("h\n"));
2016 assert!(ops.contains("W*\n"));
2017 }
2018
2019 #[test]
2020 fn test_clipping_chaining() {
2021 let mut ctx = GraphicsContext::new();
2022
2023 ctx.save_state()
2025 .rect(20.0, 20.0, 60.0, 60.0)
2026 .clip()
2027 .set_fill_color(Color::red())
2028 .rect(0.0, 0.0, 100.0, 100.0)
2029 .fill()
2030 .restore_state();
2031
2032 let ops = ctx.operations();
2033 assert!(ops.contains("q\n"));
2034 assert!(ops.contains("20.00 20.00 60.00 60.00 re\n"));
2035 assert!(ops.contains("W\n"));
2036 assert!(ops.contains("1.000 0.000 0.000 rg\n"));
2037 assert!(ops.contains("0.00 0.00 100.00 100.00 re\n"));
2038 assert!(ops.contains("f\n"));
2039 assert!(ops.contains("Q\n"));
2040 }
2041
2042 #[test]
2043 fn test_multiple_clipping_regions() {
2044 let mut ctx = GraphicsContext::new();
2045
2046 ctx.save_state()
2048 .rect(0.0, 0.0, 200.0, 200.0)
2049 .clip()
2050 .save_state()
2051 .circle(100.0, 100.0, 50.0)
2052 .clip_even_odd()
2053 .set_fill_color(Color::blue())
2054 .rect(50.0, 50.0, 100.0, 100.0)
2055 .fill()
2056 .restore_state()
2057 .restore_state();
2058
2059 let ops = ctx.operations();
2060 let q_count = ops.matches("q\n").count();
2062 let q_restore_count = ops.matches("Q\n").count();
2063 assert_eq!(q_count, 2);
2064 assert_eq!(q_restore_count, 2);
2065
2066 assert!(ops.contains("W\n"));
2068 assert!(ops.contains("W*\n"));
2069 }
2070
2071 #[test]
2074 fn test_move_to_and_line_to() {
2075 let mut ctx = GraphicsContext::new();
2076 ctx.move_to(100.0, 200.0).line_to(300.0, 400.0).stroke();
2077
2078 let ops = ctx
2079 .generate_operations()
2080 .expect("Writing to string should never fail");
2081 let ops_str = String::from_utf8_lossy(&ops);
2082 assert!(ops_str.contains("100.00 200.00 m"));
2083 assert!(ops_str.contains("300.00 400.00 l"));
2084 assert!(ops_str.contains("S"));
2085 }
2086
2087 #[test]
2088 fn test_bezier_curve() {
2089 let mut ctx = GraphicsContext::new();
2090 ctx.move_to(0.0, 0.0)
2091 .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0)
2092 .stroke();
2093
2094 let ops = ctx
2095 .generate_operations()
2096 .expect("Writing to string should never fail");
2097 let ops_str = String::from_utf8_lossy(&ops);
2098 assert!(ops_str.contains("0.00 0.00 m"));
2099 assert!(ops_str.contains("10.00 20.00 30.00 40.00 50.00 60.00 c"));
2100 assert!(ops_str.contains("S"));
2101 }
2102
2103 #[test]
2104 fn test_circle_path() {
2105 let mut ctx = GraphicsContext::new();
2106 ctx.circle(100.0, 100.0, 50.0).fill();
2107
2108 let ops = ctx
2109 .generate_operations()
2110 .expect("Writing to string should never fail");
2111 let ops_str = String::from_utf8_lossy(&ops);
2112 assert!(ops_str.contains(" c"));
2114 assert!(ops_str.contains("f"));
2115 }
2116
2117 #[test]
2118 fn test_path_closing() {
2119 let mut ctx = GraphicsContext::new();
2120 ctx.move_to(0.0, 0.0)
2121 .line_to(100.0, 0.0)
2122 .line_to(100.0, 100.0)
2123 .close_path()
2124 .stroke();
2125
2126 let ops = ctx
2127 .generate_operations()
2128 .expect("Writing to string should never fail");
2129 let ops_str = String::from_utf8_lossy(&ops);
2130 assert!(ops_str.contains("h")); assert!(ops_str.contains("S"));
2132 }
2133
2134 #[test]
2135 fn test_fill_and_stroke() {
2136 let mut ctx = GraphicsContext::new();
2137 ctx.rect(10.0, 10.0, 50.0, 50.0).fill_stroke();
2138
2139 let ops = ctx
2140 .generate_operations()
2141 .expect("Writing to string should never fail");
2142 let ops_str = String::from_utf8_lossy(&ops);
2143 assert!(ops_str.contains("10.00 10.00 50.00 50.00 re"));
2144 assert!(ops_str.contains("B")); }
2146
2147 #[test]
2148 fn test_color_settings() {
2149 let mut ctx = GraphicsContext::new();
2150 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2151 .set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
2152 .rect(10.0, 10.0, 50.0, 50.0)
2153 .fill_stroke(); assert_eq!(ctx.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2156 assert_eq!(ctx.stroke_color(), Color::rgb(0.0, 1.0, 0.0));
2157
2158 let ops = ctx
2159 .generate_operations()
2160 .expect("Writing to string should never fail");
2161 let ops_str = String::from_utf8_lossy(&ops);
2162 assert!(ops_str.contains("1.000 0.000 0.000 rg")); assert!(ops_str.contains("0.000 1.000 0.000 RG")); }
2165
2166 #[test]
2167 fn test_line_styles() {
2168 let mut ctx = GraphicsContext::new();
2169 ctx.set_line_width(2.5)
2170 .set_line_cap(LineCap::Round)
2171 .set_line_join(LineJoin::Bevel);
2172
2173 assert_eq!(ctx.line_width(), 2.5);
2174
2175 let ops = ctx
2176 .generate_operations()
2177 .expect("Writing to string should never fail");
2178 let ops_str = String::from_utf8_lossy(&ops);
2179 assert!(ops_str.contains("2.50 w")); assert!(ops_str.contains("1 J")); assert!(ops_str.contains("2 j")); }
2183
2184 #[test]
2185 fn test_opacity_settings() {
2186 let mut ctx = GraphicsContext::new();
2187 ctx.set_opacity(0.5);
2188
2189 assert_eq!(ctx.fill_opacity(), 0.5);
2190 assert_eq!(ctx.stroke_opacity(), 0.5);
2191 assert!(ctx.uses_transparency());
2192
2193 ctx.set_fill_opacity(0.7).set_stroke_opacity(0.3);
2194
2195 assert_eq!(ctx.fill_opacity(), 0.7);
2196 assert_eq!(ctx.stroke_opacity(), 0.3);
2197 }
2198
2199 #[test]
2200 fn test_state_save_restore() {
2201 let mut ctx = GraphicsContext::new();
2202 ctx.save_state()
2203 .set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2204 .restore_state();
2205
2206 let ops = ctx
2207 .generate_operations()
2208 .expect("Writing to string should never fail");
2209 let ops_str = String::from_utf8_lossy(&ops);
2210 assert!(ops_str.contains("q")); assert!(ops_str.contains("Q")); }
2213
2214 #[test]
2215 fn test_transformations() {
2216 let mut ctx = GraphicsContext::new();
2217 ctx.translate(100.0, 200.0).scale(2.0, 3.0).rotate(45.0);
2218
2219 let ops = ctx
2220 .generate_operations()
2221 .expect("Writing to string should never fail");
2222 let ops_str = String::from_utf8_lossy(&ops);
2223 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")); }
2227
2228 #[test]
2229 fn test_custom_transform() {
2230 let mut ctx = GraphicsContext::new();
2231 ctx.transform(1.0, 0.5, 0.5, 1.0, 10.0, 20.0);
2232
2233 let ops = ctx
2234 .generate_operations()
2235 .expect("Writing to string should never fail");
2236 let ops_str = String::from_utf8_lossy(&ops);
2237 assert!(ops_str.contains("1.00 0.50 0.50 1.00 10.00 20.00 cm"));
2238 }
2239
2240 #[test]
2241 fn test_rectangle_path() {
2242 let mut ctx = GraphicsContext::new();
2243 ctx.rectangle(25.0, 25.0, 150.0, 100.0).stroke();
2244
2245 let ops = ctx
2246 .generate_operations()
2247 .expect("Writing to string should never fail");
2248 let ops_str = String::from_utf8_lossy(&ops);
2249 assert!(ops_str.contains("25.00 25.00 150.00 100.00 re"));
2250 assert!(ops_str.contains("S"));
2251 }
2252
2253 #[test]
2254 fn test_empty_operations() {
2255 let ctx = GraphicsContext::new();
2256 let ops = ctx
2257 .generate_operations()
2258 .expect("Writing to string should never fail");
2259 assert!(ops.is_empty());
2260 }
2261
2262 #[test]
2263 fn test_complex_path_operations() {
2264 let mut ctx = GraphicsContext::new();
2265 ctx.move_to(50.0, 50.0)
2266 .line_to(100.0, 50.0)
2267 .curve_to(125.0, 50.0, 150.0, 75.0, 150.0, 100.0)
2268 .line_to(150.0, 150.0)
2269 .close_path()
2270 .fill();
2271
2272 let ops = ctx
2273 .generate_operations()
2274 .expect("Writing to string should never fail");
2275 let ops_str = String::from_utf8_lossy(&ops);
2276 assert!(ops_str.contains("50.00 50.00 m"));
2277 assert!(ops_str.contains("100.00 50.00 l"));
2278 assert!(ops_str.contains("125.00 50.00 150.00 75.00 150.00 100.00 c"));
2279 assert!(ops_str.contains("150.00 150.00 l"));
2280 assert!(ops_str.contains("h"));
2281 assert!(ops_str.contains("f"));
2282 }
2283
2284 #[test]
2285 fn test_graphics_state_dict_generation() {
2286 let mut ctx = GraphicsContext::new();
2287
2288 assert!(ctx.generate_graphics_state_dict().is_none());
2290
2291 ctx.set_opacity(0.5);
2293 let dict = ctx.generate_graphics_state_dict();
2294 assert!(dict.is_some());
2295 let dict_str = dict.expect("Writing to string should never fail");
2296 assert!(dict_str.contains("/ca 0.5"));
2297 assert!(dict_str.contains("/CA 0.5"));
2298 }
2299
2300 #[test]
2301 fn test_line_dash_pattern() {
2302 let mut ctx = GraphicsContext::new();
2303 let pattern = LineDashPattern {
2304 array: vec![3.0, 2.0],
2305 phase: 0.0,
2306 };
2307 ctx.set_line_dash_pattern(pattern);
2308
2309 let ops = ctx
2310 .generate_operations()
2311 .expect("Writing to string should never fail");
2312 let ops_str = String::from_utf8_lossy(&ops);
2313 assert!(ops_str.contains("[3.00 2.00] 0.00 d"));
2314 }
2315
2316 #[test]
2317 fn test_miter_limit_setting() {
2318 let mut ctx = GraphicsContext::new();
2319 ctx.set_miter_limit(4.0);
2320
2321 let ops = ctx
2322 .generate_operations()
2323 .expect("Writing to string should never fail");
2324 let ops_str = String::from_utf8_lossy(&ops);
2325 assert!(ops_str.contains("4.00 M"));
2326 }
2327
2328 #[test]
2329 fn test_line_cap_styles() {
2330 let mut ctx = GraphicsContext::new();
2331
2332 ctx.set_line_cap(LineCap::Butt);
2333 let ops = ctx
2334 .generate_operations()
2335 .expect("Writing to string should never fail");
2336 let ops_str = String::from_utf8_lossy(&ops);
2337 assert!(ops_str.contains("0 J"));
2338
2339 let mut ctx = GraphicsContext::new();
2340 ctx.set_line_cap(LineCap::Round);
2341 let ops = ctx
2342 .generate_operations()
2343 .expect("Writing to string should never fail");
2344 let ops_str = String::from_utf8_lossy(&ops);
2345 assert!(ops_str.contains("1 J"));
2346
2347 let mut ctx = GraphicsContext::new();
2348 ctx.set_line_cap(LineCap::Square);
2349 let ops = ctx
2350 .generate_operations()
2351 .expect("Writing to string should never fail");
2352 let ops_str = String::from_utf8_lossy(&ops);
2353 assert!(ops_str.contains("2 J"));
2354 }
2355
2356 #[test]
2357 fn test_transparency_groups() {
2358 let mut ctx = GraphicsContext::new();
2359
2360 let group = TransparencyGroup::new()
2362 .with_isolated(true)
2363 .with_opacity(0.5);
2364
2365 ctx.begin_transparency_group(group);
2366 assert!(ctx.in_transparency_group());
2367
2368 ctx.rect(10.0, 10.0, 100.0, 100.0);
2370 ctx.fill();
2371
2372 ctx.end_transparency_group();
2373 assert!(!ctx.in_transparency_group());
2374
2375 let ops = ctx.operations();
2377 assert!(ops.contains("% Begin Transparency Group"));
2378 assert!(ops.contains("% End Transparency Group"));
2379 }
2380
2381 #[test]
2382 fn test_nested_transparency_groups() {
2383 let mut ctx = GraphicsContext::new();
2384
2385 let group1 = TransparencyGroup::isolated().with_opacity(0.8);
2387 ctx.begin_transparency_group(group1);
2388 assert!(ctx.in_transparency_group());
2389
2390 let group2 = TransparencyGroup::knockout().with_blend_mode(BlendMode::Multiply);
2392 ctx.begin_transparency_group(group2);
2393
2394 ctx.circle(50.0, 50.0, 25.0);
2396 ctx.fill();
2397
2398 ctx.end_transparency_group();
2400 assert!(ctx.in_transparency_group()); ctx.end_transparency_group();
2404 assert!(!ctx.in_transparency_group());
2405 }
2406
2407 #[test]
2408 fn test_line_join_styles() {
2409 let mut ctx = GraphicsContext::new();
2410
2411 ctx.set_line_join(LineJoin::Miter);
2412 let ops = ctx
2413 .generate_operations()
2414 .expect("Writing to string should never fail");
2415 let ops_str = String::from_utf8_lossy(&ops);
2416 assert!(ops_str.contains("0 j"));
2417
2418 let mut ctx = GraphicsContext::new();
2419 ctx.set_line_join(LineJoin::Round);
2420 let ops = ctx
2421 .generate_operations()
2422 .expect("Writing to string should never fail");
2423 let ops_str = String::from_utf8_lossy(&ops);
2424 assert!(ops_str.contains("1 j"));
2425
2426 let mut ctx = GraphicsContext::new();
2427 ctx.set_line_join(LineJoin::Bevel);
2428 let ops = ctx
2429 .generate_operations()
2430 .expect("Writing to string should never fail");
2431 let ops_str = String::from_utf8_lossy(&ops);
2432 assert!(ops_str.contains("2 j"));
2433 }
2434
2435 #[test]
2436 fn test_rendering_intent() {
2437 let mut ctx = GraphicsContext::new();
2438
2439 ctx.set_rendering_intent(RenderingIntent::AbsoluteColorimetric);
2440 assert_eq!(
2441 ctx.rendering_intent(),
2442 RenderingIntent::AbsoluteColorimetric
2443 );
2444
2445 ctx.set_rendering_intent(RenderingIntent::Perceptual);
2446 assert_eq!(ctx.rendering_intent(), RenderingIntent::Perceptual);
2447
2448 ctx.set_rendering_intent(RenderingIntent::Saturation);
2449 assert_eq!(ctx.rendering_intent(), RenderingIntent::Saturation);
2450 }
2451
2452 #[test]
2453 fn test_flatness_tolerance() {
2454 let mut ctx = GraphicsContext::new();
2455
2456 ctx.set_flatness(0.5);
2457 assert_eq!(ctx.flatness(), 0.5);
2458
2459 let ops = ctx
2460 .generate_operations()
2461 .expect("Writing to string should never fail");
2462 let ops_str = String::from_utf8_lossy(&ops);
2463 assert!(ops_str.contains("0.50 i"));
2464 }
2465
2466 #[test]
2467 fn test_smoothness_tolerance() {
2468 let mut ctx = GraphicsContext::new();
2469
2470 let _ = ctx.set_smoothness(0.1);
2471 assert_eq!(ctx.smoothness(), 0.1);
2472 }
2473
2474 #[test]
2475 fn test_bezier_curves() {
2476 let mut ctx = GraphicsContext::new();
2477
2478 ctx.move_to(10.0, 10.0);
2480 ctx.curve_to(20.0, 10.0, 30.0, 20.0, 30.0, 30.0);
2481
2482 let ops = ctx
2483 .generate_operations()
2484 .expect("Writing to string should never fail");
2485 let ops_str = String::from_utf8_lossy(&ops);
2486 assert!(ops_str.contains("10.00 10.00 m"));
2487 assert!(ops_str.contains("c")); }
2489
2490 #[test]
2491 fn test_clipping_path() {
2492 let mut ctx = GraphicsContext::new();
2493
2494 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2495 ctx.clip();
2496
2497 let ops = ctx
2498 .generate_operations()
2499 .expect("Writing to string should never fail");
2500 let ops_str = String::from_utf8_lossy(&ops);
2501 assert!(ops_str.contains("W"));
2502 }
2503
2504 #[test]
2505 fn test_even_odd_clipping() {
2506 let mut ctx = GraphicsContext::new();
2507
2508 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2509 ctx.clip_even_odd();
2510
2511 let ops = ctx
2512 .generate_operations()
2513 .expect("Writing to string should never fail");
2514 let ops_str = String::from_utf8_lossy(&ops);
2515 assert!(ops_str.contains("W*"));
2516 }
2517
2518 #[test]
2519 fn test_color_creation() {
2520 let gray = Color::gray(0.5);
2522 assert_eq!(gray, Color::Gray(0.5));
2523
2524 let rgb = Color::rgb(0.2, 0.4, 0.6);
2525 assert_eq!(rgb, Color::Rgb(0.2, 0.4, 0.6));
2526
2527 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
2528 assert_eq!(cmyk, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
2529
2530 assert_eq!(Color::black(), Color::Gray(0.0));
2532 assert_eq!(Color::white(), Color::Gray(1.0));
2533 assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
2534 }
2535
2536 #[test]
2537 fn test_extended_graphics_state() {
2538 let ctx = GraphicsContext::new();
2539
2540 let _extgstate = ExtGState::new();
2542
2543 assert!(ctx.generate_operations().is_ok());
2545 }
2546
2547 #[test]
2548 fn test_path_construction_methods() {
2549 let mut ctx = GraphicsContext::new();
2550
2551 ctx.move_to(10.0, 10.0);
2553 ctx.line_to(20.0, 20.0);
2554 ctx.curve_to(30.0, 30.0, 40.0, 40.0, 50.0, 50.0);
2555 ctx.rect(60.0, 60.0, 30.0, 30.0);
2556 ctx.circle(100.0, 100.0, 25.0);
2557 ctx.close_path();
2558
2559 let ops = ctx
2560 .generate_operations()
2561 .expect("Writing to string should never fail");
2562 assert!(!ops.is_empty());
2563 }
2564
2565 #[test]
2566 fn test_graphics_context_clone_advanced() {
2567 let mut ctx = GraphicsContext::new();
2568 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
2569 ctx.set_line_width(5.0);
2570
2571 let cloned = ctx.clone();
2572 assert_eq!(cloned.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2573 assert_eq!(cloned.line_width(), 5.0);
2574 }
2575
2576 #[test]
2577 fn test_basic_drawing_operations() {
2578 let mut ctx = GraphicsContext::new();
2579
2580 ctx.move_to(50.0, 50.0);
2582 ctx.line_to(100.0, 100.0);
2583 ctx.stroke();
2584
2585 let ops = ctx
2586 .generate_operations()
2587 .expect("Writing to string should never fail");
2588 let ops_str = String::from_utf8_lossy(&ops);
2589 assert!(ops_str.contains("m")); assert!(ops_str.contains("l")); assert!(ops_str.contains("S")); }
2593
2594 #[test]
2595 fn test_graphics_state_stack() {
2596 let mut ctx = GraphicsContext::new();
2597
2598 ctx.set_fill_color(Color::black());
2600
2601 ctx.save_state();
2603 ctx.set_fill_color(Color::red());
2604 assert_eq!(ctx.fill_color(), Color::red());
2605
2606 ctx.save_state();
2608 ctx.set_fill_color(Color::blue());
2609 assert_eq!(ctx.fill_color(), Color::blue());
2610
2611 ctx.restore_state();
2613 assert_eq!(ctx.fill_color(), Color::red());
2614
2615 ctx.restore_state();
2617 assert_eq!(ctx.fill_color(), Color::black());
2618 }
2619
2620 #[test]
2621 fn test_word_spacing() {
2622 let mut ctx = GraphicsContext::new();
2623 ctx.set_word_spacing(2.5);
2624
2625 let ops = ctx.generate_operations().unwrap();
2626 let ops_str = String::from_utf8_lossy(&ops);
2627 assert!(ops_str.contains("2.50 Tw"));
2628 }
2629
2630 #[test]
2631 fn test_character_spacing() {
2632 let mut ctx = GraphicsContext::new();
2633 ctx.set_character_spacing(1.0);
2634
2635 let ops = ctx.generate_operations().unwrap();
2636 let ops_str = String::from_utf8_lossy(&ops);
2637 assert!(ops_str.contains("1.00 Tc"));
2638 }
2639
2640 #[test]
2641 fn test_justified_text() {
2642 let mut ctx = GraphicsContext::new();
2643 ctx.begin_text();
2644 ctx.set_text_position(100.0, 200.0);
2645 ctx.show_justified_text("Hello world from PDF", 200.0)
2646 .unwrap();
2647 ctx.end_text();
2648
2649 let ops = ctx.generate_operations().unwrap();
2650 let ops_str = String::from_utf8_lossy(&ops);
2651
2652 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")); }
2661
2662 #[test]
2663 fn test_justified_text_single_word() {
2664 let mut ctx = GraphicsContext::new();
2665 ctx.begin_text();
2666 ctx.show_justified_text("Hello", 200.0).unwrap();
2667 ctx.end_text();
2668
2669 let ops = ctx.generate_operations().unwrap();
2670 let ops_str = String::from_utf8_lossy(&ops);
2671
2672 assert!(ops_str.contains("(Hello) Tj"));
2674 assert_eq!(ops_str.matches("Tw").count(), 0);
2676 }
2677
2678 #[test]
2679 fn test_text_width_estimation() {
2680 let ctx = GraphicsContext::new();
2681 let width = ctx.estimate_text_width_simple("Hello");
2682
2683 assert!(width > 0.0);
2685 assert_eq!(width, 5.0 * 12.0 * 0.6); }
2687}