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 #[allow(dead_code)]
860 fn set_pending_extgstate(&mut self, state: ExtGState) {
861 self.pending_extgstate = Some(state);
862 }
863
864 fn apply_pending_extgstate(&mut self) -> Result<()> {
866 if let Some(state) = self.pending_extgstate.take() {
867 let state_name = self.extgstate_manager.add_state(state)?;
868 writeln!(&mut self.operations, "/{state_name} gs")
869 .expect("Writing to string should never fail");
870 }
871 Ok(())
872 }
873
874 pub fn with_extgstate<F>(&mut self, builder: F) -> Result<&mut Self>
876 where
877 F: FnOnce(ExtGState) -> ExtGState,
878 {
879 let state = builder(ExtGState::new());
880 self.apply_extgstate(state)
881 }
882
883 pub fn set_blend_mode(&mut self, mode: BlendMode) -> Result<&mut Self> {
885 let state = ExtGState::new().with_blend_mode(mode);
886 self.apply_extgstate(state)
887 }
888
889 pub fn set_alpha(&mut self, alpha: f64) -> Result<&mut Self> {
891 let state = ExtGState::new().with_alpha(alpha);
892 self.apply_extgstate(state)
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.apply_extgstate(state)
899 }
900
901 pub fn set_alpha_fill(&mut self, alpha: f64) -> Result<&mut Self> {
903 let state = ExtGState::new().with_alpha_fill(alpha);
904 self.apply_extgstate(state)
905 }
906
907 pub fn set_overprint_stroke(&mut self, overprint: bool) -> Result<&mut Self> {
909 let state = ExtGState::new().with_overprint_stroke(overprint);
910 self.apply_extgstate(state)
911 }
912
913 pub fn set_overprint_fill(&mut self, overprint: bool) -> Result<&mut Self> {
915 let state = ExtGState::new().with_overprint_fill(overprint);
916 self.apply_extgstate(state)
917 }
918
919 pub fn set_stroke_adjustment(&mut self, adjustment: bool) -> Result<&mut Self> {
921 let state = ExtGState::new().with_stroke_adjustment(adjustment);
922 self.apply_extgstate(state)
923 }
924
925 pub fn set_smoothness(&mut self, smoothness: f64) -> Result<&mut Self> {
927 self.current_smoothness = smoothness.clamp(0.0, 1.0);
928 let state = ExtGState::new().with_smoothness(self.current_smoothness);
929 self.apply_extgstate(state)
930 }
931
932 pub fn line_dash_pattern(&self) -> Option<&LineDashPattern> {
936 self.current_dash_pattern.as_ref()
937 }
938
939 pub fn miter_limit(&self) -> f64 {
941 self.current_miter_limit
942 }
943
944 pub fn line_cap(&self) -> LineCap {
946 self.current_line_cap
947 }
948
949 pub fn line_join(&self) -> LineJoin {
951 self.current_line_join
952 }
953
954 pub fn rendering_intent(&self) -> RenderingIntent {
956 self.current_rendering_intent
957 }
958
959 pub fn flatness(&self) -> f64 {
961 self.current_flatness
962 }
963
964 pub fn smoothness(&self) -> f64 {
966 self.current_smoothness
967 }
968
969 pub fn extgstate_manager(&self) -> &ExtGStateManager {
971 &self.extgstate_manager
972 }
973
974 pub fn extgstate_manager_mut(&mut self) -> &mut ExtGStateManager {
976 &mut self.extgstate_manager
977 }
978
979 pub fn generate_extgstate_resources(&self) -> Result<String> {
981 self.extgstate_manager.to_resource_dictionary()
982 }
983
984 pub fn has_extgstates(&self) -> bool {
986 self.extgstate_manager.count() > 0
987 }
988
989 pub fn add_command(&mut self, command: &str) {
991 self.operations.push_str(command);
992 self.operations.push('\n');
993 }
994
995 pub fn clip(&mut self) -> &mut Self {
997 self.operations.push_str("W\n");
998 self
999 }
1000
1001 pub fn clip_even_odd(&mut self) -> &mut Self {
1003 self.operations.push_str("W*\n");
1004 self
1005 }
1006
1007 pub fn clip_stroke(&mut self) -> &mut Self {
1009 self.apply_stroke_color();
1010 self.operations.push_str("W S\n");
1011 self
1012 }
1013
1014 pub fn set_clipping_path(&mut self, path: ClippingPath) -> Result<&mut Self> {
1016 let ops = path.to_pdf_operations()?;
1017 self.operations.push_str(&ops);
1018 self.clipping_region.set_clip(path);
1019 Ok(self)
1020 }
1021
1022 pub fn clear_clipping(&mut self) -> &mut Self {
1024 self.clipping_region.clear_clip();
1025 self
1026 }
1027
1028 fn save_clipping_state(&mut self) {
1030 self.clipping_region.save();
1031 }
1032
1033 fn restore_clipping_state(&mut self) {
1035 self.clipping_region.restore();
1036 }
1037
1038 pub fn clip_rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> Result<&mut Self> {
1040 let path = ClippingPath::rect(x, y, width, height);
1041 self.set_clipping_path(path)
1042 }
1043
1044 pub fn clip_circle(&mut self, cx: f64, cy: f64, radius: f64) -> Result<&mut Self> {
1046 let path = ClippingPath::circle(cx, cy, radius);
1047 self.set_clipping_path(path)
1048 }
1049
1050 pub fn clip_ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) -> Result<&mut Self> {
1052 let path = ClippingPath::ellipse(cx, cy, rx, ry);
1053 self.set_clipping_path(path)
1054 }
1055
1056 pub fn has_clipping(&self) -> bool {
1058 self.clipping_region.has_clip()
1059 }
1060
1061 pub fn clipping_path(&self) -> Option<&ClippingPath> {
1063 self.clipping_region.current()
1064 }
1065
1066 pub fn set_font_manager(&mut self, font_manager: Arc<FontManager>) -> &mut Self {
1068 self.font_manager = Some(font_manager);
1069 self
1070 }
1071
1072 pub fn set_custom_font(&mut self, font_name: &str, size: f64) -> &mut Self {
1074 self.current_font_name = Some(font_name.to_string());
1075 self.current_font_size = size;
1076
1077 if let Some(ref font_manager) = self.font_manager {
1079 if let Some(mapping) = font_manager.get_font_glyph_mapping(font_name) {
1080 self.glyph_mapping = Some(mapping);
1081 }
1082 }
1083
1084 self
1085 }
1086
1087 pub fn set_glyph_mapping(&mut self, mapping: HashMap<u32, u16>) -> &mut Self {
1089 self.glyph_mapping = Some(mapping);
1090 self
1091 }
1092
1093 pub fn draw_text(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1095 self.used_characters.extend(text.chars());
1097
1098 let using_custom_font = if let Some(ref font_name) = self.current_font_name {
1101 !matches!(
1103 font_name.as_str(),
1104 "Helvetica"
1105 | "Times"
1106 | "Courier"
1107 | "Symbol"
1108 | "ZapfDingbats"
1109 | "Helvetica-Bold"
1110 | "Helvetica-Oblique"
1111 | "Helvetica-BoldOblique"
1112 | "Times-Roman"
1113 | "Times-Bold"
1114 | "Times-Italic"
1115 | "Times-BoldItalic"
1116 | "Courier-Bold"
1117 | "Courier-Oblique"
1118 | "Courier-BoldOblique"
1119 )
1120 } else {
1121 false
1122 };
1123
1124 let needs_unicode = text.chars().any(|c| c as u32 > 255) || using_custom_font;
1126
1127 if needs_unicode {
1129 self.draw_with_unicode_encoding(text, x, y)
1130 } else {
1131 self.draw_with_simple_encoding(text, x, y)
1132 }
1133 }
1134
1135 fn draw_with_simple_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1137 let has_unicode = text.chars().any(|c| c as u32 > 255);
1139
1140 if has_unicode {
1141 eprintln!("Warning: Text contains Unicode characters but using Latin-1 font. Characters will be replaced with '?'");
1143 }
1144
1145 self.operations.push_str("BT\n");
1147
1148 if let Some(font_name) = &self.current_font_name {
1150 writeln!(
1151 &mut self.operations,
1152 "/{} {} Tf",
1153 font_name, self.current_font_size
1154 )
1155 .expect("Writing to string should never fail");
1156 } else {
1157 writeln!(
1158 &mut self.operations,
1159 "/Helvetica {} Tf",
1160 self.current_font_size
1161 )
1162 .expect("Writing to string should never fail");
1163 }
1164
1165 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1167 .expect("Writing to string should never fail");
1168
1169 self.operations.push('(');
1172 for ch in text.chars() {
1173 let code = ch as u32;
1174 if code <= 127 {
1175 match ch {
1177 '(' => self.operations.push_str("\\("),
1178 ')' => self.operations.push_str("\\)"),
1179 '\\' => self.operations.push_str("\\\\"),
1180 '\n' => self.operations.push_str("\\n"),
1181 '\r' => self.operations.push_str("\\r"),
1182 '\t' => self.operations.push_str("\\t"),
1183 _ => self.operations.push(ch),
1184 }
1185 } else if code <= 255 {
1186 write!(&mut self.operations, "\\{:03o}", code)
1189 .expect("Writing to string should never fail");
1190 } else {
1191 self.operations.push('?');
1193 }
1194 }
1195 self.operations.push_str(") Tj\n");
1196
1197 self.operations.push_str("ET\n");
1199
1200 Ok(self)
1201 }
1202
1203 fn draw_with_unicode_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1205 self.operations.push_str("BT\n");
1207
1208 if let Some(font_name) = &self.current_font_name {
1210 writeln!(
1212 &mut self.operations,
1213 "/{} {} Tf",
1214 font_name, self.current_font_size
1215 )
1216 .expect("Writing to string should never fail");
1217 } else {
1218 writeln!(
1219 &mut self.operations,
1220 "/Helvetica {} Tf",
1221 self.current_font_size
1222 )
1223 .expect("Writing to string should never fail");
1224 }
1225
1226 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1228 .expect("Writing to string should never fail");
1229
1230 self.operations.push('<');
1234
1235 for ch in text.chars() {
1236 let code = ch as u32;
1237
1238 if code <= 0xFFFF {
1241 write!(&mut self.operations, "{:04X}", code)
1243 .expect("Writing to string should never fail");
1244 } else {
1245 write!(&mut self.operations, "FFFD").expect("Writing to string should never fail");
1248 }
1250 }
1251 self.operations.push_str("> Tj\n");
1252
1253 self.operations.push_str("ET\n");
1255
1256 Ok(self)
1257 }
1258
1259 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1261 pub fn draw_text_hex(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1262 self.operations.push_str("BT\n");
1264
1265 if let Some(font_name) = &self.current_font_name {
1267 writeln!(
1268 &mut self.operations,
1269 "/{} {} Tf",
1270 font_name, self.current_font_size
1271 )
1272 .expect("Writing to string should never fail");
1273 } else {
1274 writeln!(
1276 &mut self.operations,
1277 "/Helvetica {} Tf",
1278 self.current_font_size
1279 )
1280 .expect("Writing to string should never fail");
1281 }
1282
1283 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1285 .expect("Writing to string should never fail");
1286
1287 self.operations.push('<');
1291 for ch in text.chars() {
1292 if ch as u32 <= 255 {
1293 write!(&mut self.operations, "{:02X}", ch as u8)
1295 .expect("Writing to string should never fail");
1296 } else {
1297 write!(&mut self.operations, "3F").expect("Writing to string should never fail");
1300 }
1302 }
1303 self.operations.push_str("> Tj\n");
1304
1305 self.operations.push_str("ET\n");
1307
1308 Ok(self)
1309 }
1310
1311 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1313 pub fn draw_text_cid(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1314 use crate::fonts::needs_type0_font;
1315
1316 self.operations.push_str("BT\n");
1318
1319 if let Some(font_name) = &self.current_font_name {
1321 writeln!(
1322 &mut self.operations,
1323 "/{} {} Tf",
1324 font_name, self.current_font_size
1325 )
1326 .expect("Writing to string should never fail");
1327 } else {
1328 writeln!(
1329 &mut self.operations,
1330 "/Helvetica {} Tf",
1331 self.current_font_size
1332 )
1333 .expect("Writing to string should never fail");
1334 }
1335
1336 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1338 .expect("Writing to string should never fail");
1339
1340 if needs_type0_font(text) {
1342 self.operations.push('<');
1344 for ch in text.chars() {
1345 let code = ch as u32;
1346
1347 if code <= 0xFFFF {
1349 write!(&mut self.operations, "{:04X}", code)
1351 .expect("Writing to string should never fail");
1352 } else if code <= 0x10FFFF {
1353 let code = code - 0x10000;
1355 let high = ((code >> 10) & 0x3FF) + 0xD800;
1356 let low = (code & 0x3FF) + 0xDC00;
1357 write!(&mut self.operations, "{:04X}{:04X}", high, low)
1358 .expect("Writing to string should never fail");
1359 } else {
1360 write!(&mut self.operations, "FFFD")
1362 .expect("Writing to string should never fail");
1363 }
1364 }
1365 self.operations.push_str("> Tj\n");
1366 } else {
1367 self.operations.push('<');
1369 for ch in text.chars() {
1370 if ch as u32 <= 255 {
1371 write!(&mut self.operations, "{:02X}", ch as u8)
1372 .expect("Writing to string should never fail");
1373 } else {
1374 write!(&mut self.operations, "3F")
1375 .expect("Writing to string should never fail");
1376 }
1377 }
1378 self.operations.push_str("> Tj\n");
1379 }
1380
1381 self.operations.push_str("ET\n");
1383 Ok(self)
1384 }
1385
1386 #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1388 pub fn draw_text_unicode(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1389 self.operations.push_str("BT\n");
1391
1392 if let Some(font_name) = &self.current_font_name {
1394 writeln!(
1395 &mut self.operations,
1396 "/{} {} Tf",
1397 font_name, self.current_font_size
1398 )
1399 .expect("Writing to string should never fail");
1400 } else {
1401 writeln!(
1403 &mut self.operations,
1404 "/Helvetica {} Tf",
1405 self.current_font_size
1406 )
1407 .expect("Writing to string should never fail");
1408 }
1409
1410 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1412 .expect("Writing to string should never fail");
1413
1414 self.operations.push('<');
1416 let mut utf16_buffer = [0u16; 2];
1417 for ch in text.chars() {
1418 let encoded = ch.encode_utf16(&mut utf16_buffer);
1419 for unit in encoded {
1420 write!(&mut self.operations, "{:04X}", unit)
1422 .expect("Writing to string should never fail");
1423 }
1424 }
1425 self.operations.push_str("> Tj\n");
1426
1427 self.operations.push_str("ET\n");
1429
1430 Ok(self)
1431 }
1432
1433 pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1435 if self.used_characters.is_empty() {
1436 None
1437 } else {
1438 Some(self.used_characters.clone())
1439 }
1440 }
1441}
1442
1443#[cfg(test)]
1444mod tests {
1445 use super::*;
1446
1447 #[test]
1448 fn test_graphics_context_new() {
1449 let ctx = GraphicsContext::new();
1450 assert_eq!(ctx.fill_color(), Color::black());
1451 assert_eq!(ctx.stroke_color(), Color::black());
1452 assert_eq!(ctx.line_width(), 1.0);
1453 assert_eq!(ctx.fill_opacity(), 1.0);
1454 assert_eq!(ctx.stroke_opacity(), 1.0);
1455 assert!(ctx.operations().is_empty());
1456 }
1457
1458 #[test]
1459 fn test_graphics_context_default() {
1460 let ctx = GraphicsContext::default();
1461 assert_eq!(ctx.fill_color(), Color::black());
1462 assert_eq!(ctx.stroke_color(), Color::black());
1463 assert_eq!(ctx.line_width(), 1.0);
1464 }
1465
1466 #[test]
1467 fn test_move_to() {
1468 let mut ctx = GraphicsContext::new();
1469 ctx.move_to(10.0, 20.0);
1470 assert!(ctx.operations().contains("10.00 20.00 m\n"));
1471 }
1472
1473 #[test]
1474 fn test_line_to() {
1475 let mut ctx = GraphicsContext::new();
1476 ctx.line_to(30.0, 40.0);
1477 assert!(ctx.operations().contains("30.00 40.00 l\n"));
1478 }
1479
1480 #[test]
1481 fn test_curve_to() {
1482 let mut ctx = GraphicsContext::new();
1483 ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
1484 assert!(ctx
1485 .operations()
1486 .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
1487 }
1488
1489 #[test]
1490 fn test_rect() {
1491 let mut ctx = GraphicsContext::new();
1492 ctx.rect(10.0, 20.0, 100.0, 50.0);
1493 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1494 }
1495
1496 #[test]
1497 fn test_rectangle_alias() {
1498 let mut ctx = GraphicsContext::new();
1499 ctx.rectangle(10.0, 20.0, 100.0, 50.0);
1500 assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1501 }
1502
1503 #[test]
1504 fn test_circle() {
1505 let mut ctx = GraphicsContext::new();
1506 ctx.circle(50.0, 50.0, 25.0);
1507
1508 let ops = ctx.operations();
1509 assert!(ops.contains("75.00 50.00 m\n"));
1511 assert!(ops.contains(" c\n"));
1513 assert!(ops.contains("h\n"));
1515 }
1516
1517 #[test]
1518 fn test_close_path() {
1519 let mut ctx = GraphicsContext::new();
1520 ctx.close_path();
1521 assert!(ctx.operations().contains("h\n"));
1522 }
1523
1524 #[test]
1525 fn test_stroke() {
1526 let mut ctx = GraphicsContext::new();
1527 ctx.set_stroke_color(Color::red());
1528 ctx.rect(0.0, 0.0, 10.0, 10.0);
1529 ctx.stroke();
1530
1531 let ops = ctx.operations();
1532 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1533 assert!(ops.contains("S\n"));
1534 }
1535
1536 #[test]
1537 fn test_fill() {
1538 let mut ctx = GraphicsContext::new();
1539 ctx.set_fill_color(Color::blue());
1540 ctx.rect(0.0, 0.0, 10.0, 10.0);
1541 ctx.fill();
1542
1543 let ops = ctx.operations();
1544 assert!(ops.contains("0.000 0.000 1.000 rg\n"));
1545 assert!(ops.contains("f\n"));
1546 }
1547
1548 #[test]
1549 fn test_fill_stroke() {
1550 let mut ctx = GraphicsContext::new();
1551 ctx.set_fill_color(Color::green());
1552 ctx.set_stroke_color(Color::red());
1553 ctx.rect(0.0, 0.0, 10.0, 10.0);
1554 ctx.fill_stroke();
1555
1556 let ops = ctx.operations();
1557 assert!(ops.contains("0.000 1.000 0.000 rg\n"));
1558 assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1559 assert!(ops.contains("B\n"));
1560 }
1561
1562 #[test]
1563 fn test_set_stroke_color() {
1564 let mut ctx = GraphicsContext::new();
1565 ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
1566 assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
1567 }
1568
1569 #[test]
1570 fn test_set_fill_color() {
1571 let mut ctx = GraphicsContext::new();
1572 ctx.set_fill_color(Color::gray(0.5));
1573 assert_eq!(ctx.fill_color(), Color::Gray(0.5));
1574 }
1575
1576 #[test]
1577 fn test_set_line_width() {
1578 let mut ctx = GraphicsContext::new();
1579 ctx.set_line_width(2.5);
1580 assert_eq!(ctx.line_width(), 2.5);
1581 assert!(ctx.operations().contains("2.50 w\n"));
1582 }
1583
1584 #[test]
1585 fn test_set_line_cap() {
1586 let mut ctx = GraphicsContext::new();
1587 ctx.set_line_cap(LineCap::Round);
1588 assert!(ctx.operations().contains("1 J\n"));
1589
1590 ctx.set_line_cap(LineCap::Butt);
1591 assert!(ctx.operations().contains("0 J\n"));
1592
1593 ctx.set_line_cap(LineCap::Square);
1594 assert!(ctx.operations().contains("2 J\n"));
1595 }
1596
1597 #[test]
1598 fn test_set_line_join() {
1599 let mut ctx = GraphicsContext::new();
1600 ctx.set_line_join(LineJoin::Round);
1601 assert!(ctx.operations().contains("1 j\n"));
1602
1603 ctx.set_line_join(LineJoin::Miter);
1604 assert!(ctx.operations().contains("0 j\n"));
1605
1606 ctx.set_line_join(LineJoin::Bevel);
1607 assert!(ctx.operations().contains("2 j\n"));
1608 }
1609
1610 #[test]
1611 fn test_save_restore_state() {
1612 let mut ctx = GraphicsContext::new();
1613 ctx.save_state();
1614 assert!(ctx.operations().contains("q\n"));
1615
1616 ctx.restore_state();
1617 assert!(ctx.operations().contains("Q\n"));
1618 }
1619
1620 #[test]
1621 fn test_translate() {
1622 let mut ctx = GraphicsContext::new();
1623 ctx.translate(50.0, 100.0);
1624 assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
1625 }
1626
1627 #[test]
1628 fn test_scale() {
1629 let mut ctx = GraphicsContext::new();
1630 ctx.scale(2.0, 3.0);
1631 assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
1632 }
1633
1634 #[test]
1635 fn test_rotate() {
1636 let mut ctx = GraphicsContext::new();
1637 let angle = std::f64::consts::PI / 4.0; ctx.rotate(angle);
1639
1640 let ops = ctx.operations();
1641 assert!(ops.contains(" cm\n"));
1642 assert!(ops.contains("0.707107")); }
1645
1646 #[test]
1647 fn test_transform() {
1648 let mut ctx = GraphicsContext::new();
1649 ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
1650 assert!(ctx
1651 .operations()
1652 .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
1653 }
1654
1655 #[test]
1656 fn test_draw_image() {
1657 let mut ctx = GraphicsContext::new();
1658 ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
1659
1660 let ops = ctx.operations();
1661 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")); }
1666
1667 #[test]
1668 fn test_gray_color_operations() {
1669 let mut ctx = GraphicsContext::new();
1670 ctx.set_stroke_color(Color::gray(0.5));
1671 ctx.set_fill_color(Color::gray(0.7));
1672 ctx.stroke();
1673 ctx.fill();
1674
1675 let ops = ctx.operations();
1676 assert!(ops.contains("0.500 G\n")); assert!(ops.contains("0.700 g\n")); }
1679
1680 #[test]
1681 fn test_cmyk_color_operations() {
1682 let mut ctx = GraphicsContext::new();
1683 ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
1684 ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
1685 ctx.stroke();
1686 ctx.fill();
1687
1688 let ops = ctx.operations();
1689 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")); }
1692
1693 #[test]
1694 fn test_method_chaining() {
1695 let mut ctx = GraphicsContext::new();
1696 ctx.move_to(0.0, 0.0)
1697 .line_to(10.0, 0.0)
1698 .line_to(10.0, 10.0)
1699 .line_to(0.0, 10.0)
1700 .close_path()
1701 .set_fill_color(Color::red())
1702 .fill();
1703
1704 let ops = ctx.operations();
1705 assert!(ops.contains("0.00 0.00 m\n"));
1706 assert!(ops.contains("10.00 0.00 l\n"));
1707 assert!(ops.contains("10.00 10.00 l\n"));
1708 assert!(ops.contains("0.00 10.00 l\n"));
1709 assert!(ops.contains("h\n"));
1710 assert!(ops.contains("f\n"));
1711 }
1712
1713 #[test]
1714 fn test_generate_operations() {
1715 let mut ctx = GraphicsContext::new();
1716 ctx.rect(0.0, 0.0, 10.0, 10.0);
1717
1718 let result = ctx.generate_operations();
1719 assert!(result.is_ok());
1720 let bytes = result.expect("Writing to string should never fail");
1721 let ops_string = String::from_utf8(bytes).expect("Writing to string should never fail");
1722 assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
1723 }
1724
1725 #[test]
1726 fn test_clear_operations() {
1727 let mut ctx = GraphicsContext::new();
1728 ctx.rect(0.0, 0.0, 10.0, 10.0);
1729 assert!(!ctx.operations().is_empty());
1730
1731 ctx.clear();
1732 assert!(ctx.operations().is_empty());
1733 }
1734
1735 #[test]
1736 fn test_complex_path() {
1737 let mut ctx = GraphicsContext::new();
1738 ctx.save_state()
1739 .translate(100.0, 100.0)
1740 .rotate(std::f64::consts::PI / 6.0)
1741 .scale(2.0, 2.0)
1742 .set_line_width(2.0)
1743 .set_stroke_color(Color::blue())
1744 .move_to(0.0, 0.0)
1745 .line_to(50.0, 0.0)
1746 .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
1747 .close_path()
1748 .stroke()
1749 .restore_state();
1750
1751 let ops = ctx.operations();
1752 assert!(ops.contains("q\n"));
1753 assert!(ops.contains("cm\n"));
1754 assert!(ops.contains("2.00 w\n"));
1755 assert!(ops.contains("0.000 0.000 1.000 RG\n"));
1756 assert!(ops.contains("S\n"));
1757 assert!(ops.contains("Q\n"));
1758 }
1759
1760 #[test]
1761 fn test_graphics_context_clone() {
1762 let mut ctx = GraphicsContext::new();
1763 ctx.set_fill_color(Color::red());
1764 ctx.set_stroke_color(Color::blue());
1765 ctx.set_line_width(3.0);
1766 ctx.set_opacity(0.5);
1767 ctx.rect(0.0, 0.0, 10.0, 10.0);
1768
1769 let ctx_clone = ctx.clone();
1770 assert_eq!(ctx_clone.fill_color(), Color::red());
1771 assert_eq!(ctx_clone.stroke_color(), Color::blue());
1772 assert_eq!(ctx_clone.line_width(), 3.0);
1773 assert_eq!(ctx_clone.fill_opacity(), 0.5);
1774 assert_eq!(ctx_clone.stroke_opacity(), 0.5);
1775 assert_eq!(ctx_clone.operations(), ctx.operations());
1776 }
1777
1778 #[test]
1779 fn test_set_opacity() {
1780 let mut ctx = GraphicsContext::new();
1781
1782 ctx.set_opacity(0.5);
1784 assert_eq!(ctx.fill_opacity(), 0.5);
1785 assert_eq!(ctx.stroke_opacity(), 0.5);
1786
1787 ctx.set_opacity(1.5);
1789 assert_eq!(ctx.fill_opacity(), 1.0);
1790 assert_eq!(ctx.stroke_opacity(), 1.0);
1791
1792 ctx.set_opacity(-0.5);
1793 assert_eq!(ctx.fill_opacity(), 0.0);
1794 assert_eq!(ctx.stroke_opacity(), 0.0);
1795 }
1796
1797 #[test]
1798 fn test_set_fill_opacity() {
1799 let mut ctx = GraphicsContext::new();
1800
1801 ctx.set_fill_opacity(0.3);
1802 assert_eq!(ctx.fill_opacity(), 0.3);
1803 assert_eq!(ctx.stroke_opacity(), 1.0); ctx.set_fill_opacity(2.0);
1807 assert_eq!(ctx.fill_opacity(), 1.0);
1808 }
1809
1810 #[test]
1811 fn test_set_stroke_opacity() {
1812 let mut ctx = GraphicsContext::new();
1813
1814 ctx.set_stroke_opacity(0.7);
1815 assert_eq!(ctx.stroke_opacity(), 0.7);
1816 assert_eq!(ctx.fill_opacity(), 1.0); ctx.set_stroke_opacity(-1.0);
1820 assert_eq!(ctx.stroke_opacity(), 0.0);
1821 }
1822
1823 #[test]
1824 fn test_uses_transparency() {
1825 let mut ctx = GraphicsContext::new();
1826
1827 assert!(!ctx.uses_transparency());
1829
1830 ctx.set_fill_opacity(0.5);
1832 assert!(ctx.uses_transparency());
1833
1834 ctx.set_fill_opacity(1.0);
1836 assert!(!ctx.uses_transparency());
1837 ctx.set_stroke_opacity(0.8);
1838 assert!(ctx.uses_transparency());
1839
1840 ctx.set_fill_opacity(0.5);
1842 assert!(ctx.uses_transparency());
1843 }
1844
1845 #[test]
1846 fn test_generate_graphics_state_dict() {
1847 let mut ctx = GraphicsContext::new();
1848
1849 assert_eq!(ctx.generate_graphics_state_dict(), None);
1851
1852 ctx.set_fill_opacity(0.5);
1854 let dict = ctx
1855 .generate_graphics_state_dict()
1856 .expect("Writing to string should never fail");
1857 assert!(dict.contains("/Type /ExtGState"));
1858 assert!(dict.contains("/ca 0.500"));
1859 assert!(!dict.contains("/CA"));
1860
1861 ctx.set_fill_opacity(1.0);
1863 ctx.set_stroke_opacity(0.75);
1864 let dict = ctx
1865 .generate_graphics_state_dict()
1866 .expect("Writing to string should never fail");
1867 assert!(dict.contains("/Type /ExtGState"));
1868 assert!(dict.contains("/CA 0.750"));
1869 assert!(!dict.contains("/ca"));
1870
1871 ctx.set_fill_opacity(0.25);
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.250"));
1878 assert!(dict.contains("/CA 0.750"));
1879 }
1880
1881 #[test]
1882 fn test_opacity_with_graphics_operations() {
1883 let mut ctx = GraphicsContext::new();
1884
1885 ctx.set_fill_color(Color::red())
1886 .set_opacity(0.5)
1887 .rect(10.0, 10.0, 100.0, 100.0)
1888 .fill();
1889
1890 assert_eq!(ctx.fill_opacity(), 0.5);
1891 assert_eq!(ctx.stroke_opacity(), 0.5);
1892
1893 let ops = ctx.operations();
1894 assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
1895 assert!(ops.contains("1.000 0.000 0.000 rg")); assert!(ops.contains("f")); }
1898
1899 #[test]
1900 fn test_begin_end_text() {
1901 let mut ctx = GraphicsContext::new();
1902 ctx.begin_text();
1903 assert!(ctx.operations().contains("BT\n"));
1904
1905 ctx.end_text();
1906 assert!(ctx.operations().contains("ET\n"));
1907 }
1908
1909 #[test]
1910 fn test_set_font() {
1911 let mut ctx = GraphicsContext::new();
1912 ctx.set_font(Font::Helvetica, 12.0);
1913 assert!(ctx.operations().contains("/Helvetica 12 Tf\n"));
1914
1915 ctx.set_font(Font::TimesBold, 14.5);
1916 assert!(ctx.operations().contains("/Times-Bold 14.5 Tf\n"));
1917 }
1918
1919 #[test]
1920 fn test_set_text_position() {
1921 let mut ctx = GraphicsContext::new();
1922 ctx.set_text_position(100.0, 200.0);
1923 assert!(ctx.operations().contains("100.00 200.00 Td\n"));
1924 }
1925
1926 #[test]
1927 fn test_show_text() {
1928 let mut ctx = GraphicsContext::new();
1929 ctx.show_text("Hello World")
1930 .expect("Writing to string should never fail");
1931 assert!(ctx.operations().contains("(Hello World) Tj\n"));
1932 }
1933
1934 #[test]
1935 fn test_show_text_with_escaping() {
1936 let mut ctx = GraphicsContext::new();
1937 ctx.show_text("Test (parentheses)")
1938 .expect("Writing to string should never fail");
1939 assert!(ctx.operations().contains("(Test \\(parentheses\\)) Tj\n"));
1940
1941 ctx.clear();
1942 ctx.show_text("Back\\slash")
1943 .expect("Writing to string should never fail");
1944 assert!(ctx.operations().contains("(Back\\\\slash) Tj\n"));
1945
1946 ctx.clear();
1947 ctx.show_text("Line\nBreak")
1948 .expect("Writing to string should never fail");
1949 assert!(ctx.operations().contains("(Line\\nBreak) Tj\n"));
1950 }
1951
1952 #[test]
1953 fn test_text_operations_chaining() {
1954 let mut ctx = GraphicsContext::new();
1955 ctx.begin_text()
1956 .set_font(Font::Courier, 10.0)
1957 .set_text_position(50.0, 100.0)
1958 .show_text("Test")
1959 .unwrap()
1960 .end_text();
1961
1962 let ops = ctx.operations();
1963 assert!(ops.contains("BT\n"));
1964 assert!(ops.contains("/Courier 10 Tf\n"));
1965 assert!(ops.contains("50.00 100.00 Td\n"));
1966 assert!(ops.contains("(Test) Tj\n"));
1967 assert!(ops.contains("ET\n"));
1968 }
1969
1970 #[test]
1971 fn test_clip() {
1972 let mut ctx = GraphicsContext::new();
1973 ctx.clip();
1974 assert!(ctx.operations().contains("W\n"));
1975 }
1976
1977 #[test]
1978 fn test_clip_even_odd() {
1979 let mut ctx = GraphicsContext::new();
1980 ctx.clip_even_odd();
1981 assert!(ctx.operations().contains("W*\n"));
1982 }
1983
1984 #[test]
1985 fn test_clipping_with_path() {
1986 let mut ctx = GraphicsContext::new();
1987
1988 ctx.rect(10.0, 10.0, 100.0, 50.0).clip();
1990
1991 let ops = ctx.operations();
1992 assert!(ops.contains("10.00 10.00 100.00 50.00 re\n"));
1993 assert!(ops.contains("W\n"));
1994 }
1995
1996 #[test]
1997 fn test_clipping_even_odd_with_path() {
1998 let mut ctx = GraphicsContext::new();
1999
2000 ctx.move_to(0.0, 0.0)
2002 .line_to(100.0, 0.0)
2003 .line_to(100.0, 100.0)
2004 .line_to(0.0, 100.0)
2005 .close_path()
2006 .clip_even_odd();
2007
2008 let ops = ctx.operations();
2009 assert!(ops.contains("0.00 0.00 m\n"));
2010 assert!(ops.contains("100.00 0.00 l\n"));
2011 assert!(ops.contains("100.00 100.00 l\n"));
2012 assert!(ops.contains("0.00 100.00 l\n"));
2013 assert!(ops.contains("h\n"));
2014 assert!(ops.contains("W*\n"));
2015 }
2016
2017 #[test]
2018 fn test_clipping_chaining() {
2019 let mut ctx = GraphicsContext::new();
2020
2021 ctx.save_state()
2023 .rect(20.0, 20.0, 60.0, 60.0)
2024 .clip()
2025 .set_fill_color(Color::red())
2026 .rect(0.0, 0.0, 100.0, 100.0)
2027 .fill()
2028 .restore_state();
2029
2030 let ops = ctx.operations();
2031 assert!(ops.contains("q\n"));
2032 assert!(ops.contains("20.00 20.00 60.00 60.00 re\n"));
2033 assert!(ops.contains("W\n"));
2034 assert!(ops.contains("1.000 0.000 0.000 rg\n"));
2035 assert!(ops.contains("0.00 0.00 100.00 100.00 re\n"));
2036 assert!(ops.contains("f\n"));
2037 assert!(ops.contains("Q\n"));
2038 }
2039
2040 #[test]
2041 fn test_multiple_clipping_regions() {
2042 let mut ctx = GraphicsContext::new();
2043
2044 ctx.save_state()
2046 .rect(0.0, 0.0, 200.0, 200.0)
2047 .clip()
2048 .save_state()
2049 .circle(100.0, 100.0, 50.0)
2050 .clip_even_odd()
2051 .set_fill_color(Color::blue())
2052 .rect(50.0, 50.0, 100.0, 100.0)
2053 .fill()
2054 .restore_state()
2055 .restore_state();
2056
2057 let ops = ctx.operations();
2058 let q_count = ops.matches("q\n").count();
2060 let q_restore_count = ops.matches("Q\n").count();
2061 assert_eq!(q_count, 2);
2062 assert_eq!(q_restore_count, 2);
2063
2064 assert!(ops.contains("W\n"));
2066 assert!(ops.contains("W*\n"));
2067 }
2068
2069 #[test]
2072 fn test_move_to_and_line_to() {
2073 let mut ctx = GraphicsContext::new();
2074 ctx.move_to(100.0, 200.0).line_to(300.0, 400.0).stroke();
2075
2076 let ops = ctx
2077 .generate_operations()
2078 .expect("Writing to string should never fail");
2079 let ops_str = String::from_utf8_lossy(&ops);
2080 assert!(ops_str.contains("100.00 200.00 m"));
2081 assert!(ops_str.contains("300.00 400.00 l"));
2082 assert!(ops_str.contains("S"));
2083 }
2084
2085 #[test]
2086 fn test_bezier_curve() {
2087 let mut ctx = GraphicsContext::new();
2088 ctx.move_to(0.0, 0.0)
2089 .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0)
2090 .stroke();
2091
2092 let ops = ctx
2093 .generate_operations()
2094 .expect("Writing to string should never fail");
2095 let ops_str = String::from_utf8_lossy(&ops);
2096 assert!(ops_str.contains("0.00 0.00 m"));
2097 assert!(ops_str.contains("10.00 20.00 30.00 40.00 50.00 60.00 c"));
2098 assert!(ops_str.contains("S"));
2099 }
2100
2101 #[test]
2102 fn test_circle_path() {
2103 let mut ctx = GraphicsContext::new();
2104 ctx.circle(100.0, 100.0, 50.0).fill();
2105
2106 let ops = ctx
2107 .generate_operations()
2108 .expect("Writing to string should never fail");
2109 let ops_str = String::from_utf8_lossy(&ops);
2110 assert!(ops_str.contains(" c"));
2112 assert!(ops_str.contains("f"));
2113 }
2114
2115 #[test]
2116 fn test_path_closing() {
2117 let mut ctx = GraphicsContext::new();
2118 ctx.move_to(0.0, 0.0)
2119 .line_to(100.0, 0.0)
2120 .line_to(100.0, 100.0)
2121 .close_path()
2122 .stroke();
2123
2124 let ops = ctx
2125 .generate_operations()
2126 .expect("Writing to string should never fail");
2127 let ops_str = String::from_utf8_lossy(&ops);
2128 assert!(ops_str.contains("h")); assert!(ops_str.contains("S"));
2130 }
2131
2132 #[test]
2133 fn test_fill_and_stroke() {
2134 let mut ctx = GraphicsContext::new();
2135 ctx.rect(10.0, 10.0, 50.0, 50.0).fill_stroke();
2136
2137 let ops = ctx
2138 .generate_operations()
2139 .expect("Writing to string should never fail");
2140 let ops_str = String::from_utf8_lossy(&ops);
2141 assert!(ops_str.contains("10.00 10.00 50.00 50.00 re"));
2142 assert!(ops_str.contains("B")); }
2144
2145 #[test]
2146 fn test_color_settings() {
2147 let mut ctx = GraphicsContext::new();
2148 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2149 .set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
2150 .rect(10.0, 10.0, 50.0, 50.0)
2151 .fill_stroke(); assert_eq!(ctx.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2154 assert_eq!(ctx.stroke_color(), Color::rgb(0.0, 1.0, 0.0));
2155
2156 let ops = ctx
2157 .generate_operations()
2158 .expect("Writing to string should never fail");
2159 let ops_str = String::from_utf8_lossy(&ops);
2160 assert!(ops_str.contains("1.000 0.000 0.000 rg")); assert!(ops_str.contains("0.000 1.000 0.000 RG")); }
2163
2164 #[test]
2165 fn test_line_styles() {
2166 let mut ctx = GraphicsContext::new();
2167 ctx.set_line_width(2.5)
2168 .set_line_cap(LineCap::Round)
2169 .set_line_join(LineJoin::Bevel);
2170
2171 assert_eq!(ctx.line_width(), 2.5);
2172
2173 let ops = ctx
2174 .generate_operations()
2175 .expect("Writing to string should never fail");
2176 let ops_str = String::from_utf8_lossy(&ops);
2177 assert!(ops_str.contains("2.50 w")); assert!(ops_str.contains("1 J")); assert!(ops_str.contains("2 j")); }
2181
2182 #[test]
2183 fn test_opacity_settings() {
2184 let mut ctx = GraphicsContext::new();
2185 ctx.set_opacity(0.5);
2186
2187 assert_eq!(ctx.fill_opacity(), 0.5);
2188 assert_eq!(ctx.stroke_opacity(), 0.5);
2189 assert!(ctx.uses_transparency());
2190
2191 ctx.set_fill_opacity(0.7).set_stroke_opacity(0.3);
2192
2193 assert_eq!(ctx.fill_opacity(), 0.7);
2194 assert_eq!(ctx.stroke_opacity(), 0.3);
2195 }
2196
2197 #[test]
2198 fn test_state_save_restore() {
2199 let mut ctx = GraphicsContext::new();
2200 ctx.save_state()
2201 .set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2202 .restore_state();
2203
2204 let ops = ctx
2205 .generate_operations()
2206 .expect("Writing to string should never fail");
2207 let ops_str = String::from_utf8_lossy(&ops);
2208 assert!(ops_str.contains("q")); assert!(ops_str.contains("Q")); }
2211
2212 #[test]
2213 fn test_transformations() {
2214 let mut ctx = GraphicsContext::new();
2215 ctx.translate(100.0, 200.0).scale(2.0, 3.0).rotate(45.0);
2216
2217 let ops = ctx
2218 .generate_operations()
2219 .expect("Writing to string should never fail");
2220 let ops_str = String::from_utf8_lossy(&ops);
2221 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")); }
2225
2226 #[test]
2227 fn test_custom_transform() {
2228 let mut ctx = GraphicsContext::new();
2229 ctx.transform(1.0, 0.5, 0.5, 1.0, 10.0, 20.0);
2230
2231 let ops = ctx
2232 .generate_operations()
2233 .expect("Writing to string should never fail");
2234 let ops_str = String::from_utf8_lossy(&ops);
2235 assert!(ops_str.contains("1.00 0.50 0.50 1.00 10.00 20.00 cm"));
2236 }
2237
2238 #[test]
2239 fn test_rectangle_path() {
2240 let mut ctx = GraphicsContext::new();
2241 ctx.rectangle(25.0, 25.0, 150.0, 100.0).stroke();
2242
2243 let ops = ctx
2244 .generate_operations()
2245 .expect("Writing to string should never fail");
2246 let ops_str = String::from_utf8_lossy(&ops);
2247 assert!(ops_str.contains("25.00 25.00 150.00 100.00 re"));
2248 assert!(ops_str.contains("S"));
2249 }
2250
2251 #[test]
2252 fn test_empty_operations() {
2253 let ctx = GraphicsContext::new();
2254 let ops = ctx
2255 .generate_operations()
2256 .expect("Writing to string should never fail");
2257 assert!(ops.is_empty());
2258 }
2259
2260 #[test]
2261 fn test_complex_path_operations() {
2262 let mut ctx = GraphicsContext::new();
2263 ctx.move_to(50.0, 50.0)
2264 .line_to(100.0, 50.0)
2265 .curve_to(125.0, 50.0, 150.0, 75.0, 150.0, 100.0)
2266 .line_to(150.0, 150.0)
2267 .close_path()
2268 .fill();
2269
2270 let ops = ctx
2271 .generate_operations()
2272 .expect("Writing to string should never fail");
2273 let ops_str = String::from_utf8_lossy(&ops);
2274 assert!(ops_str.contains("50.00 50.00 m"));
2275 assert!(ops_str.contains("100.00 50.00 l"));
2276 assert!(ops_str.contains("125.00 50.00 150.00 75.00 150.00 100.00 c"));
2277 assert!(ops_str.contains("150.00 150.00 l"));
2278 assert!(ops_str.contains("h"));
2279 assert!(ops_str.contains("f"));
2280 }
2281
2282 #[test]
2283 fn test_graphics_state_dict_generation() {
2284 let mut ctx = GraphicsContext::new();
2285
2286 assert!(ctx.generate_graphics_state_dict().is_none());
2288
2289 ctx.set_opacity(0.5);
2291 let dict = ctx.generate_graphics_state_dict();
2292 assert!(dict.is_some());
2293 let dict_str = dict.expect("Writing to string should never fail");
2294 assert!(dict_str.contains("/ca 0.5"));
2295 assert!(dict_str.contains("/CA 0.5"));
2296 }
2297
2298 #[test]
2299 fn test_line_dash_pattern() {
2300 let mut ctx = GraphicsContext::new();
2301 let pattern = LineDashPattern {
2302 array: vec![3.0, 2.0],
2303 phase: 0.0,
2304 };
2305 ctx.set_line_dash_pattern(pattern);
2306
2307 let ops = ctx
2308 .generate_operations()
2309 .expect("Writing to string should never fail");
2310 let ops_str = String::from_utf8_lossy(&ops);
2311 assert!(ops_str.contains("[3.00 2.00] 0.00 d"));
2312 }
2313
2314 #[test]
2315 fn test_miter_limit_setting() {
2316 let mut ctx = GraphicsContext::new();
2317 ctx.set_miter_limit(4.0);
2318
2319 let ops = ctx
2320 .generate_operations()
2321 .expect("Writing to string should never fail");
2322 let ops_str = String::from_utf8_lossy(&ops);
2323 assert!(ops_str.contains("4.00 M"));
2324 }
2325
2326 #[test]
2327 fn test_line_cap_styles() {
2328 let mut ctx = GraphicsContext::new();
2329
2330 ctx.set_line_cap(LineCap::Butt);
2331 let ops = ctx
2332 .generate_operations()
2333 .expect("Writing to string should never fail");
2334 let ops_str = String::from_utf8_lossy(&ops);
2335 assert!(ops_str.contains("0 J"));
2336
2337 let mut ctx = GraphicsContext::new();
2338 ctx.set_line_cap(LineCap::Round);
2339 let ops = ctx
2340 .generate_operations()
2341 .expect("Writing to string should never fail");
2342 let ops_str = String::from_utf8_lossy(&ops);
2343 assert!(ops_str.contains("1 J"));
2344
2345 let mut ctx = GraphicsContext::new();
2346 ctx.set_line_cap(LineCap::Square);
2347 let ops = ctx
2348 .generate_operations()
2349 .expect("Writing to string should never fail");
2350 let ops_str = String::from_utf8_lossy(&ops);
2351 assert!(ops_str.contains("2 J"));
2352 }
2353
2354 #[test]
2355 fn test_transparency_groups() {
2356 let mut ctx = GraphicsContext::new();
2357
2358 let group = TransparencyGroup::new()
2360 .with_isolated(true)
2361 .with_opacity(0.5);
2362
2363 ctx.begin_transparency_group(group);
2364 assert!(ctx.in_transparency_group());
2365
2366 ctx.rect(10.0, 10.0, 100.0, 100.0);
2368 ctx.fill();
2369
2370 ctx.end_transparency_group();
2371 assert!(!ctx.in_transparency_group());
2372
2373 let ops = ctx.operations();
2375 assert!(ops.contains("% Begin Transparency Group"));
2376 assert!(ops.contains("% End Transparency Group"));
2377 }
2378
2379 #[test]
2380 fn test_nested_transparency_groups() {
2381 let mut ctx = GraphicsContext::new();
2382
2383 let group1 = TransparencyGroup::isolated().with_opacity(0.8);
2385 ctx.begin_transparency_group(group1);
2386 assert!(ctx.in_transparency_group());
2387
2388 let group2 = TransparencyGroup::knockout().with_blend_mode(BlendMode::Multiply);
2390 ctx.begin_transparency_group(group2);
2391
2392 ctx.circle(50.0, 50.0, 25.0);
2394 ctx.fill();
2395
2396 ctx.end_transparency_group();
2398 assert!(ctx.in_transparency_group()); ctx.end_transparency_group();
2402 assert!(!ctx.in_transparency_group());
2403 }
2404
2405 #[test]
2406 fn test_line_join_styles() {
2407 let mut ctx = GraphicsContext::new();
2408
2409 ctx.set_line_join(LineJoin::Miter);
2410 let ops = ctx
2411 .generate_operations()
2412 .expect("Writing to string should never fail");
2413 let ops_str = String::from_utf8_lossy(&ops);
2414 assert!(ops_str.contains("0 j"));
2415
2416 let mut ctx = GraphicsContext::new();
2417 ctx.set_line_join(LineJoin::Round);
2418 let ops = ctx
2419 .generate_operations()
2420 .expect("Writing to string should never fail");
2421 let ops_str = String::from_utf8_lossy(&ops);
2422 assert!(ops_str.contains("1 j"));
2423
2424 let mut ctx = GraphicsContext::new();
2425 ctx.set_line_join(LineJoin::Bevel);
2426 let ops = ctx
2427 .generate_operations()
2428 .expect("Writing to string should never fail");
2429 let ops_str = String::from_utf8_lossy(&ops);
2430 assert!(ops_str.contains("2 j"));
2431 }
2432
2433 #[test]
2434 fn test_rendering_intent() {
2435 let mut ctx = GraphicsContext::new();
2436
2437 ctx.set_rendering_intent(RenderingIntent::AbsoluteColorimetric);
2438 assert_eq!(
2439 ctx.rendering_intent(),
2440 RenderingIntent::AbsoluteColorimetric
2441 );
2442
2443 ctx.set_rendering_intent(RenderingIntent::Perceptual);
2444 assert_eq!(ctx.rendering_intent(), RenderingIntent::Perceptual);
2445
2446 ctx.set_rendering_intent(RenderingIntent::Saturation);
2447 assert_eq!(ctx.rendering_intent(), RenderingIntent::Saturation);
2448 }
2449
2450 #[test]
2451 fn test_flatness_tolerance() {
2452 let mut ctx = GraphicsContext::new();
2453
2454 ctx.set_flatness(0.5);
2455 assert_eq!(ctx.flatness(), 0.5);
2456
2457 let ops = ctx
2458 .generate_operations()
2459 .expect("Writing to string should never fail");
2460 let ops_str = String::from_utf8_lossy(&ops);
2461 assert!(ops_str.contains("0.50 i"));
2462 }
2463
2464 #[test]
2465 fn test_smoothness_tolerance() {
2466 let mut ctx = GraphicsContext::new();
2467
2468 let _ = ctx.set_smoothness(0.1);
2469 assert_eq!(ctx.smoothness(), 0.1);
2470 }
2471
2472 #[test]
2473 fn test_bezier_curves() {
2474 let mut ctx = GraphicsContext::new();
2475
2476 ctx.move_to(10.0, 10.0);
2478 ctx.curve_to(20.0, 10.0, 30.0, 20.0, 30.0, 30.0);
2479
2480 let ops = ctx
2481 .generate_operations()
2482 .expect("Writing to string should never fail");
2483 let ops_str = String::from_utf8_lossy(&ops);
2484 assert!(ops_str.contains("10.00 10.00 m"));
2485 assert!(ops_str.contains("c")); }
2487
2488 #[test]
2489 fn test_clipping_path() {
2490 let mut ctx = GraphicsContext::new();
2491
2492 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2493 ctx.clip();
2494
2495 let ops = ctx
2496 .generate_operations()
2497 .expect("Writing to string should never fail");
2498 let ops_str = String::from_utf8_lossy(&ops);
2499 assert!(ops_str.contains("W"));
2500 }
2501
2502 #[test]
2503 fn test_even_odd_clipping() {
2504 let mut ctx = GraphicsContext::new();
2505
2506 ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2507 ctx.clip_even_odd();
2508
2509 let ops = ctx
2510 .generate_operations()
2511 .expect("Writing to string should never fail");
2512 let ops_str = String::from_utf8_lossy(&ops);
2513 assert!(ops_str.contains("W*"));
2514 }
2515
2516 #[test]
2517 fn test_color_creation() {
2518 let gray = Color::gray(0.5);
2520 assert_eq!(gray, Color::Gray(0.5));
2521
2522 let rgb = Color::rgb(0.2, 0.4, 0.6);
2523 assert_eq!(rgb, Color::Rgb(0.2, 0.4, 0.6));
2524
2525 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
2526 assert_eq!(cmyk, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
2527
2528 assert_eq!(Color::black(), Color::Gray(0.0));
2530 assert_eq!(Color::white(), Color::Gray(1.0));
2531 assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
2532 }
2533
2534 #[test]
2535 fn test_extended_graphics_state() {
2536 let ctx = GraphicsContext::new();
2537
2538 let _extgstate = ExtGState::new();
2540
2541 assert!(ctx.generate_operations().is_ok());
2543 }
2544
2545 #[test]
2546 fn test_path_construction_methods() {
2547 let mut ctx = GraphicsContext::new();
2548
2549 ctx.move_to(10.0, 10.0);
2551 ctx.line_to(20.0, 20.0);
2552 ctx.curve_to(30.0, 30.0, 40.0, 40.0, 50.0, 50.0);
2553 ctx.rect(60.0, 60.0, 30.0, 30.0);
2554 ctx.circle(100.0, 100.0, 25.0);
2555 ctx.close_path();
2556
2557 let ops = ctx
2558 .generate_operations()
2559 .expect("Writing to string should never fail");
2560 assert!(!ops.is_empty());
2561 }
2562
2563 #[test]
2564 fn test_graphics_context_clone_advanced() {
2565 let mut ctx = GraphicsContext::new();
2566 ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
2567 ctx.set_line_width(5.0);
2568
2569 let cloned = ctx.clone();
2570 assert_eq!(cloned.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2571 assert_eq!(cloned.line_width(), 5.0);
2572 }
2573
2574 #[test]
2575 fn test_basic_drawing_operations() {
2576 let mut ctx = GraphicsContext::new();
2577
2578 ctx.move_to(50.0, 50.0);
2580 ctx.line_to(100.0, 100.0);
2581 ctx.stroke();
2582
2583 let ops = ctx
2584 .generate_operations()
2585 .expect("Writing to string should never fail");
2586 let ops_str = String::from_utf8_lossy(&ops);
2587 assert!(ops_str.contains("m")); assert!(ops_str.contains("l")); assert!(ops_str.contains("S")); }
2591
2592 #[test]
2593 fn test_graphics_state_stack() {
2594 let mut ctx = GraphicsContext::new();
2595
2596 ctx.set_fill_color(Color::black());
2598
2599 ctx.save_state();
2601 ctx.set_fill_color(Color::red());
2602 assert_eq!(ctx.fill_color(), Color::red());
2603
2604 ctx.save_state();
2606 ctx.set_fill_color(Color::blue());
2607 assert_eq!(ctx.fill_color(), Color::blue());
2608
2609 ctx.restore_state();
2611 assert_eq!(ctx.fill_color(), Color::red());
2612
2613 ctx.restore_state();
2615 assert_eq!(ctx.fill_color(), Color::black());
2616 }
2617
2618 #[test]
2619 fn test_word_spacing() {
2620 let mut ctx = GraphicsContext::new();
2621 ctx.set_word_spacing(2.5);
2622
2623 let ops = ctx.generate_operations().unwrap();
2624 let ops_str = String::from_utf8_lossy(&ops);
2625 assert!(ops_str.contains("2.50 Tw"));
2626 }
2627
2628 #[test]
2629 fn test_character_spacing() {
2630 let mut ctx = GraphicsContext::new();
2631 ctx.set_character_spacing(1.0);
2632
2633 let ops = ctx.generate_operations().unwrap();
2634 let ops_str = String::from_utf8_lossy(&ops);
2635 assert!(ops_str.contains("1.00 Tc"));
2636 }
2637
2638 #[test]
2639 fn test_justified_text() {
2640 let mut ctx = GraphicsContext::new();
2641 ctx.begin_text();
2642 ctx.set_text_position(100.0, 200.0);
2643 ctx.show_justified_text("Hello world from PDF", 200.0)
2644 .unwrap();
2645 ctx.end_text();
2646
2647 let ops = ctx.generate_operations().unwrap();
2648 let ops_str = String::from_utf8_lossy(&ops);
2649
2650 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")); }
2659
2660 #[test]
2661 fn test_justified_text_single_word() {
2662 let mut ctx = GraphicsContext::new();
2663 ctx.begin_text();
2664 ctx.show_justified_text("Hello", 200.0).unwrap();
2665 ctx.end_text();
2666
2667 let ops = ctx.generate_operations().unwrap();
2668 let ops_str = String::from_utf8_lossy(&ops);
2669
2670 assert!(ops_str.contains("(Hello) Tj"));
2672 assert_eq!(ops_str.matches("Tw").count(), 0);
2674 }
2675
2676 #[test]
2677 fn test_text_width_estimation() {
2678 let ctx = GraphicsContext::new();
2679 let width = ctx.estimate_text_width_simple("Hello");
2680
2681 assert!(width > 0.0);
2683 assert_eq!(width, 5.0 * 12.0 * 0.6); }
2685
2686 #[test]
2687 fn test_set_alpha_methods() {
2688 let mut ctx = GraphicsContext::new();
2689
2690 assert!(ctx.set_alpha(0.5).is_ok());
2692 assert!(ctx.set_alpha_fill(0.3).is_ok());
2693 assert!(ctx.set_alpha_stroke(0.7).is_ok());
2694
2695 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
2703 .set_alpha(0.5)
2704 .and_then(|c| c.set_alpha_fill(0.3))
2705 .and_then(|c| c.set_alpha_stroke(0.7));
2706 assert!(result.is_ok());
2707 }
2708
2709 #[test]
2710 fn test_alpha_methods_generate_extgstate() {
2711 let mut ctx = GraphicsContext::new();
2712
2713 ctx.set_alpha(0.5).unwrap();
2715
2716 ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
2718
2719 let ops = ctx.generate_operations().unwrap();
2720 let ops_str = String::from_utf8_lossy(&ops);
2721
2722 assert!(ops_str.contains("/GS")); assert!(ops_str.contains(" gs\n")); ctx.clear();
2728 ctx.set_alpha_fill(0.3).unwrap();
2729 ctx.set_alpha_stroke(0.8).unwrap();
2730 ctx.rect(20.0, 20.0, 60.0, 60.0).fill_stroke();
2731
2732 let ops2 = ctx.generate_operations().unwrap();
2733 let ops_str2 = String::from_utf8_lossy(&ops2);
2734
2735 assert!(ops_str2.contains("/GS")); assert!(ops_str2.contains(" gs\n")); }
2739}