oxidize_pdf/graphics/
mod.rs

1pub mod calibrated_color;
2pub mod clipping;
3mod color;
4mod color_profiles;
5pub mod devicen_color;
6pub mod extraction;
7pub mod form_xobject;
8mod indexed_color;
9pub mod lab_color;
10mod path;
11mod patterns;
12mod pdf_image;
13mod png_decoder;
14pub mod separation_color;
15mod shadings;
16pub mod soft_mask;
17pub mod state;
18pub mod transparency;
19
20pub use calibrated_color::{CalGrayColorSpace, CalRgbColorSpace, CalibratedColor};
21pub use clipping::{ClippingPath, ClippingRegion};
22pub use color::Color;
23pub use color_profiles::{IccColorSpace, IccProfile, IccProfileManager, StandardIccProfile};
24pub use devicen_color::{
25    AlternateColorSpace as DeviceNAlternateColorSpace, ColorantDefinition, ColorantType,
26    DeviceNAttributes, DeviceNColorSpace, LinearTransform, SampledFunction, TintTransformFunction,
27};
28pub use form_xobject::{
29    FormTemplates, FormXObject, FormXObjectBuilder, FormXObjectManager,
30    TransparencyGroup as FormTransparencyGroup,
31};
32pub use indexed_color::{BaseColorSpace, ColorLookupTable, IndexedColorManager, IndexedColorSpace};
33pub use lab_color::{LabColor, LabColorSpace};
34pub use path::{LineCap, LineJoin, PathBuilder, PathCommand, WindingRule};
35pub use patterns::{
36    PaintType, PatternGraphicsContext, PatternManager, PatternMatrix, PatternType, TilingPattern,
37    TilingType,
38};
39pub use pdf_image::{ColorSpace, Image, ImageFormat, MaskType};
40pub use separation_color::{
41    AlternateColorSpace, SeparationColor, SeparationColorSpace, SpotColors, TintTransform,
42};
43pub use shadings::{
44    AxialShading, ColorStop, FunctionBasedShading, Point, RadialShading, ShadingDefinition,
45    ShadingManager, ShadingPattern, ShadingType,
46};
47pub use soft_mask::{SoftMask, SoftMaskState, SoftMaskType};
48pub use state::{
49    BlendMode, ExtGState, ExtGStateFont, ExtGStateManager, Halftone, LineDashPattern,
50    RenderingIntent, TransferFunction,
51};
52pub use transparency::TransparencyGroup;
53use transparency::TransparencyGroupState;
54
55use crate::error::Result;
56use crate::text::{ColumnContent, ColumnLayout, Font, FontManager, ListElement, Table};
57use std::collections::{HashMap, HashSet};
58use std::fmt::Write;
59use std::sync::Arc;
60
61#[derive(Clone)]
62pub struct GraphicsContext {
63    operations: String,
64    current_color: Color,
65    stroke_color: Color,
66    line_width: f64,
67    fill_opacity: f64,
68    stroke_opacity: f64,
69    // Extended Graphics State support
70    extgstate_manager: ExtGStateManager,
71    pending_extgstate: Option<ExtGState>,
72    current_dash_pattern: Option<LineDashPattern>,
73    current_miter_limit: f64,
74    current_line_cap: LineCap,
75    current_line_join: LineJoin,
76    current_rendering_intent: RenderingIntent,
77    current_flatness: f64,
78    current_smoothness: f64,
79    // Clipping support
80    clipping_region: ClippingRegion,
81    // Font management
82    font_manager: Option<Arc<FontManager>>,
83    // State stack for save/restore
84    state_stack: Vec<(Color, Color)>,
85    current_font_name: Option<String>,
86    current_font_size: f64,
87    // Character tracking for font subsetting
88    used_characters: HashSet<char>,
89    // Glyph mapping for Unicode fonts (Unicode code point -> Glyph ID)
90    glyph_mapping: Option<HashMap<u32, u16>>,
91    // Transparency group stack for nested groups
92    transparency_stack: Vec<TransparencyGroupState>,
93}
94
95impl Default for GraphicsContext {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101impl GraphicsContext {
102    pub fn new() -> Self {
103        Self {
104            operations: String::new(),
105            current_color: Color::black(),
106            stroke_color: Color::black(),
107            line_width: 1.0,
108            fill_opacity: 1.0,
109            stroke_opacity: 1.0,
110            // Extended Graphics State defaults
111            extgstate_manager: ExtGStateManager::new(),
112            pending_extgstate: None,
113            current_dash_pattern: None,
114            current_miter_limit: 10.0,
115            current_line_cap: LineCap::Butt,
116            current_line_join: LineJoin::Miter,
117            current_rendering_intent: RenderingIntent::RelativeColorimetric,
118            current_flatness: 1.0,
119            current_smoothness: 0.0,
120            // Clipping defaults
121            clipping_region: ClippingRegion::new(),
122            // Font defaults
123            font_manager: None,
124            state_stack: Vec::new(),
125            current_font_name: None,
126            current_font_size: 12.0,
127            used_characters: HashSet::new(),
128            glyph_mapping: None,
129            transparency_stack: Vec::new(),
130        }
131    }
132
133    pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
134        writeln!(&mut self.operations, "{x:.2} {y:.2} m")
135            .expect("Writing to string should never fail");
136        self
137    }
138
139    pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
140        writeln!(&mut self.operations, "{x:.2} {y:.2} l")
141            .expect("Writing to string should never fail");
142        self
143    }
144
145    pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
146        writeln!(
147            &mut self.operations,
148            "{x1:.2} {y1:.2} {x2:.2} {y2:.2} {x3:.2} {y3:.2} c"
149        )
150        .expect("Writing to string should never fail");
151        self
152    }
153
154    pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
155        writeln!(
156            &mut self.operations,
157            "{x:.2} {y:.2} {width:.2} {height:.2} re"
158        )
159        .expect("Writing to string should never fail");
160        self
161    }
162
163    pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
164        let k = 0.552284749831;
165        let r = radius;
166
167        self.move_to(cx + r, cy);
168        self.curve_to(cx + r, cy + k * r, cx + k * r, cy + r, cx, cy + r);
169        self.curve_to(cx - k * r, cy + r, cx - r, cy + k * r, cx - r, cy);
170        self.curve_to(cx - r, cy - k * r, cx - k * r, cy - r, cx, cy - r);
171        self.curve_to(cx + k * r, cy - r, cx + r, cy - k * r, cx + r, cy);
172        self.close_path()
173    }
174
175    pub fn close_path(&mut self) -> &mut Self {
176        self.operations.push_str("h\n");
177        self
178    }
179
180    pub fn stroke(&mut self) -> &mut Self {
181        self.apply_pending_extgstate().unwrap_or_default();
182        self.apply_stroke_color();
183        self.operations.push_str("S\n");
184        self
185    }
186
187    pub fn fill(&mut self) -> &mut Self {
188        self.apply_pending_extgstate().unwrap_or_default();
189        self.apply_fill_color();
190        self.operations.push_str("f\n");
191        self
192    }
193
194    pub fn fill_stroke(&mut self) -> &mut Self {
195        self.apply_pending_extgstate().unwrap_or_default();
196        self.apply_fill_color();
197        self.apply_stroke_color();
198        self.operations.push_str("B\n");
199        self
200    }
201
202    pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
203        self.stroke_color = color;
204        self
205    }
206
207    pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
208        self.current_color = color;
209        self
210    }
211
212    /// Set fill color using calibrated color space
213    pub fn set_fill_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
214        // Generate a unique color space name
215        let cs_name = match &color {
216            CalibratedColor::Gray(_, _) => "CalGray1",
217            CalibratedColor::Rgb(_, _) => "CalRGB1",
218        };
219
220        // Set the color space (this would need to be registered in the PDF resources)
221        writeln!(&mut self.operations, "/{} cs", cs_name)
222            .expect("Writing to string should never fail");
223
224        // Set color values
225        let values = color.values();
226        for value in &values {
227            write!(&mut self.operations, "{:.4} ", value)
228                .expect("Writing to string should never fail");
229        }
230        writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
231
232        self
233    }
234
235    /// Set stroke color using calibrated color space
236    pub fn set_stroke_color_calibrated(&mut self, color: CalibratedColor) -> &mut Self {
237        // Generate a unique color space name
238        let cs_name = match &color {
239            CalibratedColor::Gray(_, _) => "CalGray1",
240            CalibratedColor::Rgb(_, _) => "CalRGB1",
241        };
242
243        // Set the color space (this would need to be registered in the PDF resources)
244        writeln!(&mut self.operations, "/{} CS", cs_name)
245            .expect("Writing to string should never fail");
246
247        // Set color values
248        let values = color.values();
249        for value in &values {
250            write!(&mut self.operations, "{:.4} ", value)
251                .expect("Writing to string should never fail");
252        }
253        writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
254
255        self
256    }
257
258    /// Set fill color using Lab color space
259    pub fn set_fill_color_lab(&mut self, color: LabColor) -> &mut Self {
260        // Set the color space (this would need to be registered in the PDF resources)
261        writeln!(&mut self.operations, "/Lab1 cs").expect("Writing to string should never fail");
262
263        // Set color values (normalized for PDF)
264        let values = color.values();
265        for value in &values {
266            write!(&mut self.operations, "{:.4} ", value)
267                .expect("Writing to string should never fail");
268        }
269        writeln!(&mut self.operations, "sc").expect("Writing to string should never fail");
270
271        self
272    }
273
274    /// Set stroke color using Lab color space
275    pub fn set_stroke_color_lab(&mut self, color: LabColor) -> &mut Self {
276        // Set the color space (this would need to be registered in the PDF resources)
277        writeln!(&mut self.operations, "/Lab1 CS").expect("Writing to string should never fail");
278
279        // Set color values (normalized for PDF)
280        let values = color.values();
281        for value in &values {
282            write!(&mut self.operations, "{:.4} ", value)
283                .expect("Writing to string should never fail");
284        }
285        writeln!(&mut self.operations, "SC").expect("Writing to string should never fail");
286
287        self
288    }
289
290    pub fn set_line_width(&mut self, width: f64) -> &mut Self {
291        self.line_width = width;
292        writeln!(&mut self.operations, "{width:.2} w")
293            .expect("Writing to string should never fail");
294        self
295    }
296
297    pub fn set_line_cap(&mut self, cap: LineCap) -> &mut Self {
298        self.current_line_cap = cap;
299        writeln!(&mut self.operations, "{} J", cap as u8)
300            .expect("Writing to string should never fail");
301        self
302    }
303
304    pub fn set_line_join(&mut self, join: LineJoin) -> &mut Self {
305        self.current_line_join = join;
306        writeln!(&mut self.operations, "{} j", join as u8)
307            .expect("Writing to string should never fail");
308        self
309    }
310
311    /// Set the opacity for both fill and stroke operations (0.0 to 1.0)
312    pub fn set_opacity(&mut self, opacity: f64) -> &mut Self {
313        let opacity = opacity.clamp(0.0, 1.0);
314        self.fill_opacity = opacity;
315        self.stroke_opacity = opacity;
316
317        // Create pending ExtGState if opacity is not 1.0
318        if opacity < 1.0 {
319            let mut state = ExtGState::new();
320            state.alpha_fill = Some(opacity);
321            state.alpha_stroke = Some(opacity);
322            self.pending_extgstate = Some(state);
323        }
324
325        self
326    }
327
328    /// Set the fill opacity (0.0 to 1.0)
329    pub fn set_fill_opacity(&mut self, opacity: f64) -> &mut Self {
330        self.fill_opacity = opacity.clamp(0.0, 1.0);
331
332        // Update or create pending ExtGState
333        if opacity < 1.0 {
334            if let Some(ref mut state) = self.pending_extgstate {
335                state.alpha_fill = Some(opacity);
336            } else {
337                let mut state = ExtGState::new();
338                state.alpha_fill = Some(opacity);
339                self.pending_extgstate = Some(state);
340            }
341        }
342
343        self
344    }
345
346    /// Set the stroke opacity (0.0 to 1.0)
347    pub fn set_stroke_opacity(&mut self, opacity: f64) -> &mut Self {
348        self.stroke_opacity = opacity.clamp(0.0, 1.0);
349
350        // Update or create pending ExtGState
351        if opacity < 1.0 {
352            if let Some(ref mut state) = self.pending_extgstate {
353                state.alpha_stroke = Some(opacity);
354            } else {
355                let mut state = ExtGState::new();
356                state.alpha_stroke = Some(opacity);
357                self.pending_extgstate = Some(state);
358            }
359        }
360
361        self
362    }
363
364    pub fn save_state(&mut self) -> &mut Self {
365        self.operations.push_str("q\n");
366        self.save_clipping_state();
367        // Save color state
368        self.state_stack
369            .push((self.current_color, self.stroke_color));
370        self
371    }
372
373    pub fn restore_state(&mut self) -> &mut Self {
374        self.operations.push_str("Q\n");
375        self.restore_clipping_state();
376        // Restore color state
377        if let Some((fill, stroke)) = self.state_stack.pop() {
378            self.current_color = fill;
379            self.stroke_color = stroke;
380        }
381        self
382    }
383
384    /// Begin a transparency group
385    /// ISO 32000-1:2008 Section 11.4
386    pub fn begin_transparency_group(&mut self, group: TransparencyGroup) -> &mut Self {
387        // Save current state
388        self.save_state();
389
390        // Mark beginning of transparency group with special comment
391        writeln!(&mut self.operations, "% Begin Transparency Group")
392            .expect("Writing to string should never fail");
393
394        // Apply group settings via ExtGState
395        let mut extgstate = ExtGState::new();
396        extgstate = extgstate.with_blend_mode(group.blend_mode.clone());
397        extgstate.alpha_fill = Some(group.opacity as f64);
398        extgstate.alpha_stroke = Some(group.opacity as f64);
399
400        // Apply the ExtGState
401        self.pending_extgstate = Some(extgstate);
402        let _ = self.apply_pending_extgstate();
403
404        // Create group state and push to stack
405        let mut group_state = TransparencyGroupState::new(group);
406        // Save current operations state
407        group_state.saved_state = self.operations.as_bytes().to_vec();
408        self.transparency_stack.push(group_state);
409
410        self
411    }
412
413    /// End a transparency group
414    pub fn end_transparency_group(&mut self) -> &mut Self {
415        if let Some(_group_state) = self.transparency_stack.pop() {
416            // Mark end of transparency group
417            writeln!(&mut self.operations, "% End Transparency Group")
418                .expect("Writing to string should never fail");
419
420            // Restore state
421            self.restore_state();
422        }
423        self
424    }
425
426    /// Check if we're currently inside a transparency group
427    pub fn in_transparency_group(&self) -> bool {
428        !self.transparency_stack.is_empty()
429    }
430
431    /// Get the current transparency group (if any)
432    pub fn current_transparency_group(&self) -> Option<&TransparencyGroup> {
433        self.transparency_stack.last().map(|state| &state.group)
434    }
435
436    pub fn translate(&mut self, tx: f64, ty: f64) -> &mut Self {
437        writeln!(&mut self.operations, "1 0 0 1 {tx:.2} {ty:.2} cm")
438            .expect("Writing to string should never fail");
439        self
440    }
441
442    pub fn scale(&mut self, sx: f64, sy: f64) -> &mut Self {
443        writeln!(&mut self.operations, "{sx:.2} 0 0 {sy:.2} 0 0 cm")
444            .expect("Writing to string should never fail");
445        self
446    }
447
448    pub fn rotate(&mut self, angle: f64) -> &mut Self {
449        let cos = angle.cos();
450        let sin = angle.sin();
451        writeln!(
452            &mut self.operations,
453            "{:.6} {:.6} {:.6} {:.6} 0 0 cm",
454            cos, sin, -sin, cos
455        )
456        .expect("Writing to string should never fail");
457        self
458    }
459
460    pub fn transform(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> &mut Self {
461        writeln!(
462            &mut self.operations,
463            "{a:.2} {b:.2} {c:.2} {d:.2} {e:.2} {f:.2} cm"
464        )
465        .expect("Writing to string should never fail");
466        self
467    }
468
469    pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
470        self.rect(x, y, width, height)
471    }
472
473    pub fn draw_image(
474        &mut self,
475        image_name: &str,
476        x: f64,
477        y: f64,
478        width: f64,
479        height: f64,
480    ) -> &mut Self {
481        // Save graphics state
482        self.save_state();
483
484        // Set up transformation matrix for image placement
485        // PDF coordinate system has origin at bottom-left, so we need to translate and scale
486        writeln!(
487            &mut self.operations,
488            "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
489        )
490        .expect("Writing to string should never fail");
491
492        // Draw the image XObject
493        writeln!(&mut self.operations, "/{image_name} Do")
494            .expect("Writing to string should never fail");
495
496        // Restore graphics state
497        self.restore_state();
498
499        self
500    }
501
502    /// Draw an image with transparency support (soft mask)
503    /// This method handles images with alpha channels or soft masks
504    pub fn draw_image_with_transparency(
505        &mut self,
506        image_name: &str,
507        x: f64,
508        y: f64,
509        width: f64,
510        height: f64,
511        mask_name: Option<&str>,
512    ) -> &mut Self {
513        // Save graphics state
514        self.save_state();
515
516        // If we have a mask, we need to set up an ExtGState with SMask
517        if let Some(mask) = mask_name {
518            // Create an ExtGState for the soft mask
519            let mut extgstate = ExtGState::new();
520            extgstate.set_soft_mask_name(mask.to_string());
521
522            // Register and apply the ExtGState
523            let gs_name = self
524                .extgstate_manager
525                .add_state(extgstate)
526                .unwrap_or_else(|_| "GS1".to_string());
527            writeln!(&mut self.operations, "/{} gs", gs_name)
528                .expect("Writing to string should never fail");
529        }
530
531        // Set up transformation matrix for image placement
532        writeln!(
533            &mut self.operations,
534            "{width:.2} 0 0 {height:.2} {x:.2} {y:.2} cm"
535        )
536        .expect("Writing to string should never fail");
537
538        // Draw the image XObject
539        writeln!(&mut self.operations, "/{image_name} Do")
540            .expect("Writing to string should never fail");
541
542        // If we had a mask, reset the soft mask to None
543        if mask_name.is_some() {
544            // Create an ExtGState that removes the soft mask
545            let mut reset_extgstate = ExtGState::new();
546            reset_extgstate.set_soft_mask_none();
547
548            let gs_name = self
549                .extgstate_manager
550                .add_state(reset_extgstate)
551                .unwrap_or_else(|_| "GS2".to_string());
552            writeln!(&mut self.operations, "/{} gs", gs_name)
553                .expect("Writing to string should never fail");
554        }
555
556        // Restore graphics state
557        self.restore_state();
558
559        self
560    }
561
562    fn apply_stroke_color(&mut self) {
563        match self.stroke_color {
564            Color::Rgb(r, g, b) => {
565                writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} RG")
566                    .expect("Writing to string should never fail");
567            }
568            Color::Gray(g) => {
569                writeln!(&mut self.operations, "{g:.3} G")
570                    .expect("Writing to string should never fail");
571            }
572            Color::Cmyk(c, m, y, k) => {
573                writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} K")
574                    .expect("Writing to string should never fail");
575            }
576        }
577    }
578
579    fn apply_fill_color(&mut self) {
580        match self.current_color {
581            Color::Rgb(r, g, b) => {
582                writeln!(&mut self.operations, "{r:.3} {g:.3} {b:.3} rg")
583                    .expect("Writing to string should never fail");
584            }
585            Color::Gray(g) => {
586                writeln!(&mut self.operations, "{g:.3} g")
587                    .expect("Writing to string should never fail");
588            }
589            Color::Cmyk(c, m, y, k) => {
590                writeln!(&mut self.operations, "{c:.3} {m:.3} {y:.3} {k:.3} k")
591                    .expect("Writing to string should never fail");
592            }
593        }
594    }
595
596    pub(crate) fn generate_operations(&self) -> Result<Vec<u8>> {
597        Ok(self.operations.as_bytes().to_vec())
598    }
599
600    /// Check if transparency is used (opacity != 1.0)
601    pub fn uses_transparency(&self) -> bool {
602        self.fill_opacity < 1.0 || self.stroke_opacity < 1.0
603    }
604
605    /// Generate the graphics state dictionary for transparency
606    pub fn generate_graphics_state_dict(&self) -> Option<String> {
607        if !self.uses_transparency() {
608            return None;
609        }
610
611        let mut dict = String::from("<< /Type /ExtGState");
612
613        if self.fill_opacity < 1.0 {
614            write!(&mut dict, " /ca {:.3}", self.fill_opacity)
615                .expect("Writing to string should never fail");
616        }
617
618        if self.stroke_opacity < 1.0 {
619            write!(&mut dict, " /CA {:.3}", self.stroke_opacity)
620                .expect("Writing to string should never fail");
621        }
622
623        dict.push_str(" >>");
624        Some(dict)
625    }
626
627    /// Get the current fill color
628    pub fn fill_color(&self) -> Color {
629        self.current_color
630    }
631
632    /// Get the current stroke color
633    pub fn stroke_color(&self) -> Color {
634        self.stroke_color
635    }
636
637    /// Get the current line width
638    pub fn line_width(&self) -> f64 {
639        self.line_width
640    }
641
642    /// Get the current fill opacity
643    pub fn fill_opacity(&self) -> f64 {
644        self.fill_opacity
645    }
646
647    /// Get the current stroke opacity
648    pub fn stroke_opacity(&self) -> f64 {
649        self.stroke_opacity
650    }
651
652    /// Get the operations string
653    pub fn operations(&self) -> &str {
654        &self.operations
655    }
656
657    /// Get the operations string (alias for testing)
658    pub fn get_operations(&self) -> &str {
659        &self.operations
660    }
661
662    /// Clear all operations
663    pub fn clear(&mut self) {
664        self.operations.clear();
665    }
666
667    /// Begin a text object
668    pub fn begin_text(&mut self) -> &mut Self {
669        self.operations.push_str("BT\n");
670        self
671    }
672
673    /// End a text object
674    pub fn end_text(&mut self) -> &mut Self {
675        self.operations.push_str("ET\n");
676        self
677    }
678
679    /// Set font and size
680    pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
681        writeln!(&mut self.operations, "/{} {} Tf", font.pdf_name(), size)
682            .expect("Writing to string should never fail");
683
684        // Track font name and size for Unicode detection and proper font handling
685        match &font {
686            Font::Custom(name) => {
687                self.current_font_name = Some(name.clone());
688                self.current_font_size = size;
689            }
690            _ => {
691                self.current_font_name = Some(font.pdf_name());
692                self.current_font_size = size;
693            }
694        }
695
696        self
697    }
698
699    /// Set text position
700    pub fn set_text_position(&mut self, x: f64, y: f64) -> &mut Self {
701        writeln!(&mut self.operations, "{x:.2} {y:.2} Td")
702            .expect("Writing to string should never fail");
703        self
704    }
705
706    /// Show text
707    pub fn show_text(&mut self, text: &str) -> Result<&mut Self> {
708        // Escape special characters in PDF string
709        self.operations.push('(');
710        for ch in text.chars() {
711            match ch {
712                '(' => self.operations.push_str("\\("),
713                ')' => self.operations.push_str("\\)"),
714                '\\' => self.operations.push_str("\\\\"),
715                '\n' => self.operations.push_str("\\n"),
716                '\r' => self.operations.push_str("\\r"),
717                '\t' => self.operations.push_str("\\t"),
718                _ => self.operations.push(ch),
719            }
720        }
721        self.operations.push_str(") Tj\n");
722        Ok(self)
723    }
724
725    /// Set word spacing for text justification
726    pub fn set_word_spacing(&mut self, spacing: f64) -> &mut Self {
727        writeln!(&mut self.operations, "{spacing:.2} Tw")
728            .expect("Writing to string should never fail");
729        self
730    }
731
732    /// Set character spacing
733    pub fn set_character_spacing(&mut self, spacing: f64) -> &mut Self {
734        writeln!(&mut self.operations, "{spacing:.2} Tc")
735            .expect("Writing to string should never fail");
736        self
737    }
738
739    /// Show justified text with automatic word spacing calculation
740    pub fn show_justified_text(&mut self, text: &str, target_width: f64) -> Result<&mut Self> {
741        // Split text into words
742        let words: Vec<&str> = text.split_whitespace().collect();
743        if words.len() <= 1 {
744            // Can't justify single word or empty text
745            return self.show_text(text);
746        }
747
748        // Calculate natural width of text without extra spacing
749        let text_without_spaces = words.join("");
750        let natural_text_width = self.estimate_text_width_simple(&text_without_spaces);
751        let space_width = self.estimate_text_width_simple(" ");
752        let natural_width = natural_text_width + (space_width * (words.len() - 1) as f64);
753
754        // Calculate extra spacing needed per word gap
755        let extra_space_needed = target_width - natural_width;
756        let word_gaps = (words.len() - 1) as f64;
757
758        if word_gaps > 0.0 && extra_space_needed > 0.0 {
759            let extra_word_spacing = extra_space_needed / word_gaps;
760
761            // Set word spacing
762            self.set_word_spacing(extra_word_spacing);
763
764            // Show text (spaces will be expanded automatically)
765            self.show_text(text)?;
766
767            // Reset word spacing to default
768            self.set_word_spacing(0.0);
769        } else {
770            // Fallback to normal text display
771            self.show_text(text)?;
772        }
773
774        Ok(self)
775    }
776
777    /// Simple text width estimation (placeholder implementation)
778    fn estimate_text_width_simple(&self, text: &str) -> f64 {
779        // This is a simplified estimation. In a full implementation,
780        // you would use actual font metrics.
781        let font_size = self.current_font_size;
782        text.len() as f64 * font_size * 0.6 // Approximate width factor
783    }
784
785    /// Render a table
786    pub fn render_table(&mut self, table: &Table) -> Result<()> {
787        table.render(self)
788    }
789
790    /// Render a list
791    pub fn render_list(&mut self, list: &ListElement) -> Result<()> {
792        match list {
793            ListElement::Ordered(ordered) => ordered.render(self),
794            ListElement::Unordered(unordered) => unordered.render(self),
795        }
796    }
797
798    /// Render column layout
799    pub fn render_column_layout(
800        &mut self,
801        layout: &ColumnLayout,
802        content: &ColumnContent,
803        x: f64,
804        y: f64,
805        height: f64,
806    ) -> Result<()> {
807        layout.render(self, content, x, y, height)
808    }
809
810    // Extended Graphics State methods
811
812    /// Set line dash pattern
813    pub fn set_line_dash_pattern(&mut self, pattern: LineDashPattern) -> &mut Self {
814        self.current_dash_pattern = Some(pattern.clone());
815        writeln!(&mut self.operations, "{} d", pattern.to_pdf_string())
816            .expect("Writing to string should never fail");
817        self
818    }
819
820    /// Set line dash pattern to solid (no dashes)
821    pub fn set_line_solid(&mut self) -> &mut Self {
822        self.current_dash_pattern = None;
823        self.operations.push_str("[] 0 d\n");
824        self
825    }
826
827    /// Set miter limit
828    pub fn set_miter_limit(&mut self, limit: f64) -> &mut Self {
829        self.current_miter_limit = limit.max(1.0);
830        writeln!(&mut self.operations, "{:.2} M", self.current_miter_limit)
831            .expect("Writing to string should never fail");
832        self
833    }
834
835    /// Set rendering intent
836    pub fn set_rendering_intent(&mut self, intent: RenderingIntent) -> &mut Self {
837        self.current_rendering_intent = intent;
838        writeln!(&mut self.operations, "/{} ri", intent.pdf_name())
839            .expect("Writing to string should never fail");
840        self
841    }
842
843    /// Set flatness tolerance
844    pub fn set_flatness(&mut self, flatness: f64) -> &mut Self {
845        self.current_flatness = flatness.clamp(0.0, 100.0);
846        writeln!(&mut self.operations, "{:.2} i", self.current_flatness)
847            .expect("Writing to string should never fail");
848        self
849    }
850
851    /// Apply an ExtGState dictionary immediately
852    pub fn apply_extgstate(&mut self, state: ExtGState) -> Result<&mut Self> {
853        let state_name = self.extgstate_manager.add_state(state)?;
854        writeln!(&mut self.operations, "/{state_name} gs")
855            .expect("Writing to string should never fail");
856        Ok(self)
857    }
858
859    /// Store an ExtGState to be applied before the next drawing operation
860    #[allow(dead_code)]
861    fn set_pending_extgstate(&mut self, state: ExtGState) {
862        self.pending_extgstate = Some(state);
863    }
864
865    /// Apply any pending ExtGState before drawing
866    fn apply_pending_extgstate(&mut self) -> Result<()> {
867        if let Some(state) = self.pending_extgstate.take() {
868            let state_name = self.extgstate_manager.add_state(state)?;
869            writeln!(&mut self.operations, "/{state_name} gs")
870                .expect("Writing to string should never fail");
871        }
872        Ok(())
873    }
874
875    /// Create and apply a custom ExtGState
876    pub fn with_extgstate<F>(&mut self, builder: F) -> Result<&mut Self>
877    where
878        F: FnOnce(ExtGState) -> ExtGState,
879    {
880        let state = builder(ExtGState::new());
881        self.apply_extgstate(state)
882    }
883
884    /// Set blend mode for transparency
885    pub fn set_blend_mode(&mut self, mode: BlendMode) -> Result<&mut Self> {
886        let state = ExtGState::new().with_blend_mode(mode);
887        self.apply_extgstate(state)
888    }
889
890    /// Set alpha for both stroke and fill operations
891    pub fn set_alpha(&mut self, alpha: f64) -> Result<&mut Self> {
892        let state = ExtGState::new().with_alpha(alpha);
893        self.apply_extgstate(state)
894    }
895
896    /// Set alpha for stroke operations only
897    pub fn set_alpha_stroke(&mut self, alpha: f64) -> Result<&mut Self> {
898        let state = ExtGState::new().with_alpha_stroke(alpha);
899        self.apply_extgstate(state)
900    }
901
902    /// Set alpha for fill operations only
903    pub fn set_alpha_fill(&mut self, alpha: f64) -> Result<&mut Self> {
904        let state = ExtGState::new().with_alpha_fill(alpha);
905        self.apply_extgstate(state)
906    }
907
908    /// Set overprint for stroke operations
909    pub fn set_overprint_stroke(&mut self, overprint: bool) -> Result<&mut Self> {
910        let state = ExtGState::new().with_overprint_stroke(overprint);
911        self.apply_extgstate(state)
912    }
913
914    /// Set overprint for fill operations
915    pub fn set_overprint_fill(&mut self, overprint: bool) -> Result<&mut Self> {
916        let state = ExtGState::new().with_overprint_fill(overprint);
917        self.apply_extgstate(state)
918    }
919
920    /// Set stroke adjustment
921    pub fn set_stroke_adjustment(&mut self, adjustment: bool) -> Result<&mut Self> {
922        let state = ExtGState::new().with_stroke_adjustment(adjustment);
923        self.apply_extgstate(state)
924    }
925
926    /// Set smoothness tolerance
927    pub fn set_smoothness(&mut self, smoothness: f64) -> Result<&mut Self> {
928        self.current_smoothness = smoothness.clamp(0.0, 1.0);
929        let state = ExtGState::new().with_smoothness(self.current_smoothness);
930        self.apply_extgstate(state)
931    }
932
933    // Getters for extended graphics state
934
935    /// Get current line dash pattern
936    pub fn line_dash_pattern(&self) -> Option<&LineDashPattern> {
937        self.current_dash_pattern.as_ref()
938    }
939
940    /// Get current miter limit
941    pub fn miter_limit(&self) -> f64 {
942        self.current_miter_limit
943    }
944
945    /// Get current line cap
946    pub fn line_cap(&self) -> LineCap {
947        self.current_line_cap
948    }
949
950    /// Get current line join
951    pub fn line_join(&self) -> LineJoin {
952        self.current_line_join
953    }
954
955    /// Get current rendering intent
956    pub fn rendering_intent(&self) -> RenderingIntent {
957        self.current_rendering_intent
958    }
959
960    /// Get current flatness tolerance
961    pub fn flatness(&self) -> f64 {
962        self.current_flatness
963    }
964
965    /// Get current smoothness tolerance
966    pub fn smoothness(&self) -> f64 {
967        self.current_smoothness
968    }
969
970    /// Get the ExtGState manager (for advanced usage)
971    pub fn extgstate_manager(&self) -> &ExtGStateManager {
972        &self.extgstate_manager
973    }
974
975    /// Get mutable ExtGState manager (for advanced usage)
976    pub fn extgstate_manager_mut(&mut self) -> &mut ExtGStateManager {
977        &mut self.extgstate_manager
978    }
979
980    /// Generate ExtGState resource dictionary for PDF
981    pub fn generate_extgstate_resources(&self) -> Result<String> {
982        self.extgstate_manager.to_resource_dictionary()
983    }
984
985    /// Check if any extended graphics states are defined
986    pub fn has_extgstates(&self) -> bool {
987        self.extgstate_manager.count() > 0
988    }
989
990    /// Add a command to the operations
991    pub fn add_command(&mut self, command: &str) {
992        self.operations.push_str(command);
993        self.operations.push('\n');
994    }
995
996    /// Create clipping path from current path using non-zero winding rule
997    pub fn clip(&mut self) -> &mut Self {
998        self.operations.push_str("W\n");
999        self
1000    }
1001
1002    /// Create clipping path from current path using even-odd rule
1003    pub fn clip_even_odd(&mut self) -> &mut Self {
1004        self.operations.push_str("W*\n");
1005        self
1006    }
1007
1008    /// Create clipping path and stroke it
1009    pub fn clip_stroke(&mut self) -> &mut Self {
1010        self.apply_stroke_color();
1011        self.operations.push_str("W S\n");
1012        self
1013    }
1014
1015    /// Set a custom clipping path
1016    pub fn set_clipping_path(&mut self, path: ClippingPath) -> Result<&mut Self> {
1017        let ops = path.to_pdf_operations()?;
1018        self.operations.push_str(&ops);
1019        self.clipping_region.set_clip(path);
1020        Ok(self)
1021    }
1022
1023    /// Clear the current clipping path
1024    pub fn clear_clipping(&mut self) -> &mut Self {
1025        self.clipping_region.clear_clip();
1026        self
1027    }
1028
1029    /// Save the current clipping state (called automatically by save_state)
1030    fn save_clipping_state(&mut self) {
1031        self.clipping_region.save();
1032    }
1033
1034    /// Restore the previous clipping state (called automatically by restore_state)
1035    fn restore_clipping_state(&mut self) {
1036        self.clipping_region.restore();
1037    }
1038
1039    /// Create a rectangular clipping region
1040    pub fn clip_rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> Result<&mut Self> {
1041        let path = ClippingPath::rect(x, y, width, height);
1042        self.set_clipping_path(path)
1043    }
1044
1045    /// Create a circular clipping region
1046    pub fn clip_circle(&mut self, cx: f64, cy: f64, radius: f64) -> Result<&mut Self> {
1047        let path = ClippingPath::circle(cx, cy, radius);
1048        self.set_clipping_path(path)
1049    }
1050
1051    /// Create an elliptical clipping region
1052    pub fn clip_ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) -> Result<&mut Self> {
1053        let path = ClippingPath::ellipse(cx, cy, rx, ry);
1054        self.set_clipping_path(path)
1055    }
1056
1057    /// Check if a clipping path is active
1058    pub fn has_clipping(&self) -> bool {
1059        self.clipping_region.has_clip()
1060    }
1061
1062    /// Get the current clipping path
1063    pub fn clipping_path(&self) -> Option<&ClippingPath> {
1064        self.clipping_region.current()
1065    }
1066
1067    /// Set the font manager for custom fonts
1068    pub fn set_font_manager(&mut self, font_manager: Arc<FontManager>) -> &mut Self {
1069        self.font_manager = Some(font_manager);
1070        self
1071    }
1072
1073    /// Set the current font to a custom font
1074    pub fn set_custom_font(&mut self, font_name: &str, size: f64) -> &mut Self {
1075        self.current_font_name = Some(font_name.to_string());
1076        self.current_font_size = size;
1077
1078        // Try to get the glyph mapping from the font manager
1079        if let Some(ref font_manager) = self.font_manager {
1080            if let Some(mapping) = font_manager.get_font_glyph_mapping(font_name) {
1081                self.glyph_mapping = Some(mapping);
1082            }
1083        }
1084
1085        self
1086    }
1087
1088    /// Set the glyph mapping for Unicode fonts (Unicode -> GlyphID)
1089    pub fn set_glyph_mapping(&mut self, mapping: HashMap<u32, u16>) -> &mut Self {
1090        self.glyph_mapping = Some(mapping);
1091        self
1092    }
1093
1094    /// Draw text at the specified position with automatic encoding detection
1095    pub fn draw_text(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1096        // Track used characters for font subsetting
1097        self.used_characters.extend(text.chars());
1098
1099        // Check if we're using a custom font (which will be Type0/Unicode)
1100        // Custom fonts are those that are not the standard PDF fonts (Helvetica, Times, etc.)
1101        let using_custom_font = if let Some(ref font_name) = self.current_font_name {
1102            // If font name doesn't start with standard PDF font names, it's custom
1103            !matches!(
1104                font_name.as_str(),
1105                "Helvetica"
1106                    | "Times"
1107                    | "Courier"
1108                    | "Symbol"
1109                    | "ZapfDingbats"
1110                    | "Helvetica-Bold"
1111                    | "Helvetica-Oblique"
1112                    | "Helvetica-BoldOblique"
1113                    | "Times-Roman"
1114                    | "Times-Bold"
1115                    | "Times-Italic"
1116                    | "Times-BoldItalic"
1117                    | "Courier-Bold"
1118                    | "Courier-Oblique"
1119                    | "Courier-BoldOblique"
1120            )
1121        } else {
1122            false
1123        };
1124
1125        // Detect if text needs Unicode encoding
1126        let needs_unicode = text.chars().any(|c| c as u32 > 255) || using_custom_font;
1127
1128        // Use appropriate encoding based on content and font type
1129        if needs_unicode {
1130            self.draw_with_unicode_encoding(text, x, y)
1131        } else {
1132            self.draw_with_simple_encoding(text, x, y)
1133        }
1134    }
1135
1136    /// Internal: Draw text with simple encoding (WinAnsiEncoding for standard fonts)
1137    fn draw_with_simple_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1138        // Check if text contains characters outside Latin-1
1139        let has_unicode = text.chars().any(|c| c as u32 > 255);
1140
1141        if has_unicode {
1142            // Warning: Text contains Unicode characters but no Unicode font is set
1143            tracing::debug!("Warning: Text contains Unicode characters but using Latin-1 font. Characters will be replaced with '?'");
1144        }
1145
1146        // Begin text object
1147        self.operations.push_str("BT\n");
1148
1149        // Set font if available
1150        if let Some(font_name) = &self.current_font_name {
1151            writeln!(
1152                &mut self.operations,
1153                "/{} {} Tf",
1154                font_name, self.current_font_size
1155            )
1156            .expect("Writing to string should never fail");
1157        } else {
1158            writeln!(
1159                &mut self.operations,
1160                "/Helvetica {} Tf",
1161                self.current_font_size
1162            )
1163            .expect("Writing to string should never fail");
1164        }
1165
1166        // Set text position
1167        writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1168            .expect("Writing to string should never fail");
1169
1170        // Use parentheses encoding for Latin-1 text (standard PDF fonts use WinAnsiEncoding)
1171        // This allows proper rendering of accented characters
1172        self.operations.push('(');
1173        for ch in text.chars() {
1174            let code = ch as u32;
1175            if code <= 127 {
1176                // ASCII characters - handle special characters that need escaping
1177                match ch {
1178                    '(' => self.operations.push_str("\\("),
1179                    ')' => self.operations.push_str("\\)"),
1180                    '\\' => self.operations.push_str("\\\\"),
1181                    '\n' => self.operations.push_str("\\n"),
1182                    '\r' => self.operations.push_str("\\r"),
1183                    '\t' => self.operations.push_str("\\t"),
1184                    _ => self.operations.push(ch),
1185                }
1186            } else if code <= 255 {
1187                // Latin-1 characters (128-255)
1188                // For WinAnsiEncoding, we can use octal notation for high-bit characters
1189                write!(&mut self.operations, "\\{:03o}", code)
1190                    .expect("Writing to string should never fail");
1191            } else {
1192                // Characters outside Latin-1 - replace with '?'
1193                self.operations.push('?');
1194            }
1195        }
1196        self.operations.push_str(") Tj\n");
1197
1198        // End text object
1199        self.operations.push_str("ET\n");
1200
1201        Ok(self)
1202    }
1203
1204    /// Internal: Draw text with Unicode encoding (Type0/CID)
1205    fn draw_with_unicode_encoding(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1206        // Begin text object
1207        self.operations.push_str("BT\n");
1208
1209        // Set font - ensure it's a Type0 font for Unicode
1210        if let Some(font_name) = &self.current_font_name {
1211            // The font should be converted to Type0 by FontManager if needed
1212            writeln!(
1213                &mut self.operations,
1214                "/{} {} Tf",
1215                font_name, self.current_font_size
1216            )
1217            .expect("Writing to string should never fail");
1218        } else {
1219            writeln!(
1220                &mut self.operations,
1221                "/Helvetica {} Tf",
1222                self.current_font_size
1223            )
1224            .expect("Writing to string should never fail");
1225        }
1226
1227        // Set text position
1228        writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1229            .expect("Writing to string should never fail");
1230
1231        // IMPORTANT: For Type0 fonts with Identity-H encoding, we write CIDs (Character IDs),
1232        // NOT GlyphIDs. The CIDToGIDMap in the font handles the CID -> GlyphID conversion.
1233        // In our case, we use Unicode code points as CIDs.
1234        self.operations.push('<');
1235
1236        for ch in text.chars() {
1237            let code = ch as u32;
1238
1239            // For Type0 fonts with Identity-H encoding, write the Unicode code point as CID
1240            // The CIDToGIDMap will handle the conversion to the actual glyph ID
1241            if code <= 0xFFFF {
1242                // Write the Unicode code point as a 2-byte hex value (CID)
1243                write!(&mut self.operations, "{:04X}", code)
1244                    .expect("Writing to string should never fail");
1245            } else {
1246                // Characters outside BMP - use replacement character
1247                // Most PDF viewers don't handle supplementary planes well
1248                write!(&mut self.operations, "FFFD").expect("Writing to string should never fail");
1249                // Unicode replacement character
1250            }
1251        }
1252        self.operations.push_str("> Tj\n");
1253
1254        // End text object
1255        self.operations.push_str("ET\n");
1256
1257        Ok(self)
1258    }
1259
1260    /// Legacy: Draw text with hex encoding (kept for compatibility)
1261    #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1262    pub fn draw_text_hex(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1263        // Begin text object
1264        self.operations.push_str("BT\n");
1265
1266        // Set font if available
1267        if let Some(font_name) = &self.current_font_name {
1268            writeln!(
1269                &mut self.operations,
1270                "/{} {} Tf",
1271                font_name, self.current_font_size
1272            )
1273            .expect("Writing to string should never fail");
1274        } else {
1275            // Fallback to Helvetica if no font is set
1276            writeln!(
1277                &mut self.operations,
1278                "/Helvetica {} Tf",
1279                self.current_font_size
1280            )
1281            .expect("Writing to string should never fail");
1282        }
1283
1284        // Set text position
1285        writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1286            .expect("Writing to string should never fail");
1287
1288        // Encode text as hex string
1289        // For TrueType fonts with Identity-H encoding, we need UTF-16BE
1290        // But we'll use single-byte encoding for now to fix spacing
1291        self.operations.push('<');
1292        for ch in text.chars() {
1293            if ch as u32 <= 255 {
1294                // For characters in the Latin-1 range, use single byte
1295                write!(&mut self.operations, "{:02X}", ch as u8)
1296                    .expect("Writing to string should never fail");
1297            } else {
1298                // For characters outside Latin-1, we need proper glyph mapping
1299                // For now, use a placeholder
1300                write!(&mut self.operations, "3F").expect("Writing to string should never fail");
1301                // '?' character
1302            }
1303        }
1304        self.operations.push_str("> Tj\n");
1305
1306        // End text object
1307        self.operations.push_str("ET\n");
1308
1309        Ok(self)
1310    }
1311
1312    /// Legacy: Draw text with Type0 font encoding (kept for compatibility)
1313    #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1314    pub fn draw_text_cid(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1315        use crate::fonts::needs_type0_font;
1316
1317        // Begin text object
1318        self.operations.push_str("BT\n");
1319
1320        // Set font if available
1321        if let Some(font_name) = &self.current_font_name {
1322            writeln!(
1323                &mut self.operations,
1324                "/{} {} Tf",
1325                font_name, self.current_font_size
1326            )
1327            .expect("Writing to string should never fail");
1328        } else {
1329            writeln!(
1330                &mut self.operations,
1331                "/Helvetica {} Tf",
1332                self.current_font_size
1333            )
1334            .expect("Writing to string should never fail");
1335        }
1336
1337        // Set text position
1338        writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1339            .expect("Writing to string should never fail");
1340
1341        // Check if text needs Type0 encoding
1342        if needs_type0_font(text) {
1343            // Use 2-byte hex encoding for CIDs with identity mapping
1344            self.operations.push('<');
1345            for ch in text.chars() {
1346                let code = ch as u32;
1347
1348                // Handle all Unicode characters
1349                if code <= 0xFFFF {
1350                    // Direct identity mapping for BMP characters
1351                    write!(&mut self.operations, "{:04X}", code)
1352                        .expect("Writing to string should never fail");
1353                } else if code <= 0x10FFFF {
1354                    // For characters outside BMP - use surrogate pairs
1355                    let code = code - 0x10000;
1356                    let high = ((code >> 10) & 0x3FF) + 0xD800;
1357                    let low = (code & 0x3FF) + 0xDC00;
1358                    write!(&mut self.operations, "{:04X}{:04X}", high, low)
1359                        .expect("Writing to string should never fail");
1360                } else {
1361                    // Invalid Unicode - use replacement character
1362                    write!(&mut self.operations, "FFFD")
1363                        .expect("Writing to string should never fail");
1364                }
1365            }
1366            self.operations.push_str("> Tj\n");
1367        } else {
1368            // Use regular single-byte encoding for Latin-1
1369            self.operations.push('<');
1370            for ch in text.chars() {
1371                if ch as u32 <= 255 {
1372                    write!(&mut self.operations, "{:02X}", ch as u8)
1373                        .expect("Writing to string should never fail");
1374                } else {
1375                    write!(&mut self.operations, "3F")
1376                        .expect("Writing to string should never fail");
1377                }
1378            }
1379            self.operations.push_str("> Tj\n");
1380        }
1381
1382        // End text object
1383        self.operations.push_str("ET\n");
1384        Ok(self)
1385    }
1386
1387    /// Legacy: Draw text with UTF-16BE encoding (kept for compatibility)
1388    #[deprecated(note = "Use draw_text() which automatically detects encoding")]
1389    pub fn draw_text_unicode(&mut self, text: &str, x: f64, y: f64) -> Result<&mut Self> {
1390        // Begin text object
1391        self.operations.push_str("BT\n");
1392
1393        // Set font if available
1394        if let Some(font_name) = &self.current_font_name {
1395            writeln!(
1396                &mut self.operations,
1397                "/{} {} Tf",
1398                font_name, self.current_font_size
1399            )
1400            .expect("Writing to string should never fail");
1401        } else {
1402            // Fallback to Helvetica if no font is set
1403            writeln!(
1404                &mut self.operations,
1405                "/Helvetica {} Tf",
1406                self.current_font_size
1407            )
1408            .expect("Writing to string should never fail");
1409        }
1410
1411        // Set text position
1412        writeln!(&mut self.operations, "{:.2} {:.2} Td", x, y)
1413            .expect("Writing to string should never fail");
1414
1415        // Encode text as UTF-16BE hex string
1416        self.operations.push('<');
1417        let mut utf16_buffer = [0u16; 2];
1418        for ch in text.chars() {
1419            let encoded = ch.encode_utf16(&mut utf16_buffer);
1420            for unit in encoded {
1421                // Write UTF-16BE (big-endian)
1422                write!(&mut self.operations, "{:04X}", unit)
1423                    .expect("Writing to string should never fail");
1424            }
1425        }
1426        self.operations.push_str("> Tj\n");
1427
1428        // End text object
1429        self.operations.push_str("ET\n");
1430
1431        Ok(self)
1432    }
1433
1434    /// Get the characters used in this graphics context
1435    pub(crate) fn get_used_characters(&self) -> Option<HashSet<char>> {
1436        if self.used_characters.is_empty() {
1437            None
1438        } else {
1439            Some(self.used_characters.clone())
1440        }
1441    }
1442}
1443
1444#[cfg(test)]
1445mod tests {
1446    use super::*;
1447
1448    #[test]
1449    fn test_graphics_context_new() {
1450        let ctx = GraphicsContext::new();
1451        assert_eq!(ctx.fill_color(), Color::black());
1452        assert_eq!(ctx.stroke_color(), Color::black());
1453        assert_eq!(ctx.line_width(), 1.0);
1454        assert_eq!(ctx.fill_opacity(), 1.0);
1455        assert_eq!(ctx.stroke_opacity(), 1.0);
1456        assert!(ctx.operations().is_empty());
1457    }
1458
1459    #[test]
1460    fn test_graphics_context_default() {
1461        let ctx = GraphicsContext::default();
1462        assert_eq!(ctx.fill_color(), Color::black());
1463        assert_eq!(ctx.stroke_color(), Color::black());
1464        assert_eq!(ctx.line_width(), 1.0);
1465    }
1466
1467    #[test]
1468    fn test_move_to() {
1469        let mut ctx = GraphicsContext::new();
1470        ctx.move_to(10.0, 20.0);
1471        assert!(ctx.operations().contains("10.00 20.00 m\n"));
1472    }
1473
1474    #[test]
1475    fn test_line_to() {
1476        let mut ctx = GraphicsContext::new();
1477        ctx.line_to(30.0, 40.0);
1478        assert!(ctx.operations().contains("30.00 40.00 l\n"));
1479    }
1480
1481    #[test]
1482    fn test_curve_to() {
1483        let mut ctx = GraphicsContext::new();
1484        ctx.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
1485        assert!(ctx
1486            .operations()
1487            .contains("10.00 20.00 30.00 40.00 50.00 60.00 c\n"));
1488    }
1489
1490    #[test]
1491    fn test_rect() {
1492        let mut ctx = GraphicsContext::new();
1493        ctx.rect(10.0, 20.0, 100.0, 50.0);
1494        assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1495    }
1496
1497    #[test]
1498    fn test_rectangle_alias() {
1499        let mut ctx = GraphicsContext::new();
1500        ctx.rectangle(10.0, 20.0, 100.0, 50.0);
1501        assert!(ctx.operations().contains("10.00 20.00 100.00 50.00 re\n"));
1502    }
1503
1504    #[test]
1505    fn test_circle() {
1506        let mut ctx = GraphicsContext::new();
1507        ctx.circle(50.0, 50.0, 25.0);
1508
1509        let ops = ctx.operations();
1510        // Check that it starts with move to radius point
1511        assert!(ops.contains("75.00 50.00 m\n"));
1512        // Check that it contains curve operations
1513        assert!(ops.contains(" c\n"));
1514        // Check that it closes the path
1515        assert!(ops.contains("h\n"));
1516    }
1517
1518    #[test]
1519    fn test_close_path() {
1520        let mut ctx = GraphicsContext::new();
1521        ctx.close_path();
1522        assert!(ctx.operations().contains("h\n"));
1523    }
1524
1525    #[test]
1526    fn test_stroke() {
1527        let mut ctx = GraphicsContext::new();
1528        ctx.set_stroke_color(Color::red());
1529        ctx.rect(0.0, 0.0, 10.0, 10.0);
1530        ctx.stroke();
1531
1532        let ops = ctx.operations();
1533        assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1534        assert!(ops.contains("S\n"));
1535    }
1536
1537    #[test]
1538    fn test_fill() {
1539        let mut ctx = GraphicsContext::new();
1540        ctx.set_fill_color(Color::blue());
1541        ctx.rect(0.0, 0.0, 10.0, 10.0);
1542        ctx.fill();
1543
1544        let ops = ctx.operations();
1545        assert!(ops.contains("0.000 0.000 1.000 rg\n"));
1546        assert!(ops.contains("f\n"));
1547    }
1548
1549    #[test]
1550    fn test_fill_stroke() {
1551        let mut ctx = GraphicsContext::new();
1552        ctx.set_fill_color(Color::green());
1553        ctx.set_stroke_color(Color::red());
1554        ctx.rect(0.0, 0.0, 10.0, 10.0);
1555        ctx.fill_stroke();
1556
1557        let ops = ctx.operations();
1558        assert!(ops.contains("0.000 1.000 0.000 rg\n"));
1559        assert!(ops.contains("1.000 0.000 0.000 RG\n"));
1560        assert!(ops.contains("B\n"));
1561    }
1562
1563    #[test]
1564    fn test_set_stroke_color() {
1565        let mut ctx = GraphicsContext::new();
1566        ctx.set_stroke_color(Color::rgb(0.5, 0.6, 0.7));
1567        assert_eq!(ctx.stroke_color(), Color::Rgb(0.5, 0.6, 0.7));
1568    }
1569
1570    #[test]
1571    fn test_set_fill_color() {
1572        let mut ctx = GraphicsContext::new();
1573        ctx.set_fill_color(Color::gray(0.5));
1574        assert_eq!(ctx.fill_color(), Color::Gray(0.5));
1575    }
1576
1577    #[test]
1578    fn test_set_line_width() {
1579        let mut ctx = GraphicsContext::new();
1580        ctx.set_line_width(2.5);
1581        assert_eq!(ctx.line_width(), 2.5);
1582        assert!(ctx.operations().contains("2.50 w\n"));
1583    }
1584
1585    #[test]
1586    fn test_set_line_cap() {
1587        let mut ctx = GraphicsContext::new();
1588        ctx.set_line_cap(LineCap::Round);
1589        assert!(ctx.operations().contains("1 J\n"));
1590
1591        ctx.set_line_cap(LineCap::Butt);
1592        assert!(ctx.operations().contains("0 J\n"));
1593
1594        ctx.set_line_cap(LineCap::Square);
1595        assert!(ctx.operations().contains("2 J\n"));
1596    }
1597
1598    #[test]
1599    fn test_set_line_join() {
1600        let mut ctx = GraphicsContext::new();
1601        ctx.set_line_join(LineJoin::Round);
1602        assert!(ctx.operations().contains("1 j\n"));
1603
1604        ctx.set_line_join(LineJoin::Miter);
1605        assert!(ctx.operations().contains("0 j\n"));
1606
1607        ctx.set_line_join(LineJoin::Bevel);
1608        assert!(ctx.operations().contains("2 j\n"));
1609    }
1610
1611    #[test]
1612    fn test_save_restore_state() {
1613        let mut ctx = GraphicsContext::new();
1614        ctx.save_state();
1615        assert!(ctx.operations().contains("q\n"));
1616
1617        ctx.restore_state();
1618        assert!(ctx.operations().contains("Q\n"));
1619    }
1620
1621    #[test]
1622    fn test_translate() {
1623        let mut ctx = GraphicsContext::new();
1624        ctx.translate(50.0, 100.0);
1625        assert!(ctx.operations().contains("1 0 0 1 50.00 100.00 cm\n"));
1626    }
1627
1628    #[test]
1629    fn test_scale() {
1630        let mut ctx = GraphicsContext::new();
1631        ctx.scale(2.0, 3.0);
1632        assert!(ctx.operations().contains("2.00 0 0 3.00 0 0 cm\n"));
1633    }
1634
1635    #[test]
1636    fn test_rotate() {
1637        let mut ctx = GraphicsContext::new();
1638        let angle = std::f64::consts::PI / 4.0; // 45 degrees
1639        ctx.rotate(angle);
1640
1641        let ops = ctx.operations();
1642        assert!(ops.contains(" cm\n"));
1643        // Should contain cos and sin values
1644        assert!(ops.contains("0.707107")); // Approximate cos(45°)
1645    }
1646
1647    #[test]
1648    fn test_transform() {
1649        let mut ctx = GraphicsContext::new();
1650        ctx.transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
1651        assert!(ctx
1652            .operations()
1653            .contains("1.00 2.00 3.00 4.00 5.00 6.00 cm\n"));
1654    }
1655
1656    #[test]
1657    fn test_draw_image() {
1658        let mut ctx = GraphicsContext::new();
1659        ctx.draw_image("Image1", 10.0, 20.0, 100.0, 150.0);
1660
1661        let ops = ctx.operations();
1662        assert!(ops.contains("q\n")); // Save state
1663        assert!(ops.contains("100.00 0 0 150.00 10.00 20.00 cm\n")); // Transform
1664        assert!(ops.contains("/Image1 Do\n")); // Draw image
1665        assert!(ops.contains("Q\n")); // Restore state
1666    }
1667
1668    #[test]
1669    fn test_gray_color_operations() {
1670        let mut ctx = GraphicsContext::new();
1671        ctx.set_stroke_color(Color::gray(0.5));
1672        ctx.set_fill_color(Color::gray(0.7));
1673        ctx.stroke();
1674        ctx.fill();
1675
1676        let ops = ctx.operations();
1677        assert!(ops.contains("0.500 G\n")); // Stroke gray
1678        assert!(ops.contains("0.700 g\n")); // Fill gray
1679    }
1680
1681    #[test]
1682    fn test_cmyk_color_operations() {
1683        let mut ctx = GraphicsContext::new();
1684        ctx.set_stroke_color(Color::cmyk(0.1, 0.2, 0.3, 0.4));
1685        ctx.set_fill_color(Color::cmyk(0.5, 0.6, 0.7, 0.8));
1686        ctx.stroke();
1687        ctx.fill();
1688
1689        let ops = ctx.operations();
1690        assert!(ops.contains("0.100 0.200 0.300 0.400 K\n")); // Stroke CMYK
1691        assert!(ops.contains("0.500 0.600 0.700 0.800 k\n")); // Fill CMYK
1692    }
1693
1694    #[test]
1695    fn test_method_chaining() {
1696        let mut ctx = GraphicsContext::new();
1697        ctx.move_to(0.0, 0.0)
1698            .line_to(10.0, 0.0)
1699            .line_to(10.0, 10.0)
1700            .line_to(0.0, 10.0)
1701            .close_path()
1702            .set_fill_color(Color::red())
1703            .fill();
1704
1705        let ops = ctx.operations();
1706        assert!(ops.contains("0.00 0.00 m\n"));
1707        assert!(ops.contains("10.00 0.00 l\n"));
1708        assert!(ops.contains("10.00 10.00 l\n"));
1709        assert!(ops.contains("0.00 10.00 l\n"));
1710        assert!(ops.contains("h\n"));
1711        assert!(ops.contains("f\n"));
1712    }
1713
1714    #[test]
1715    fn test_generate_operations() {
1716        let mut ctx = GraphicsContext::new();
1717        ctx.rect(0.0, 0.0, 10.0, 10.0);
1718
1719        let result = ctx.generate_operations();
1720        assert!(result.is_ok());
1721        let bytes = result.expect("Writing to string should never fail");
1722        let ops_string = String::from_utf8(bytes).expect("Writing to string should never fail");
1723        assert!(ops_string.contains("0.00 0.00 10.00 10.00 re"));
1724    }
1725
1726    #[test]
1727    fn test_clear_operations() {
1728        let mut ctx = GraphicsContext::new();
1729        ctx.rect(0.0, 0.0, 10.0, 10.0);
1730        assert!(!ctx.operations().is_empty());
1731
1732        ctx.clear();
1733        assert!(ctx.operations().is_empty());
1734    }
1735
1736    #[test]
1737    fn test_complex_path() {
1738        let mut ctx = GraphicsContext::new();
1739        ctx.save_state()
1740            .translate(100.0, 100.0)
1741            .rotate(std::f64::consts::PI / 6.0)
1742            .scale(2.0, 2.0)
1743            .set_line_width(2.0)
1744            .set_stroke_color(Color::blue())
1745            .move_to(0.0, 0.0)
1746            .line_to(50.0, 0.0)
1747            .curve_to(50.0, 25.0, 25.0, 50.0, 0.0, 50.0)
1748            .close_path()
1749            .stroke()
1750            .restore_state();
1751
1752        let ops = ctx.operations();
1753        assert!(ops.contains("q\n"));
1754        assert!(ops.contains("cm\n"));
1755        assert!(ops.contains("2.00 w\n"));
1756        assert!(ops.contains("0.000 0.000 1.000 RG\n"));
1757        assert!(ops.contains("S\n"));
1758        assert!(ops.contains("Q\n"));
1759    }
1760
1761    #[test]
1762    fn test_graphics_context_clone() {
1763        let mut ctx = GraphicsContext::new();
1764        ctx.set_fill_color(Color::red());
1765        ctx.set_stroke_color(Color::blue());
1766        ctx.set_line_width(3.0);
1767        ctx.set_opacity(0.5);
1768        ctx.rect(0.0, 0.0, 10.0, 10.0);
1769
1770        let ctx_clone = ctx.clone();
1771        assert_eq!(ctx_clone.fill_color(), Color::red());
1772        assert_eq!(ctx_clone.stroke_color(), Color::blue());
1773        assert_eq!(ctx_clone.line_width(), 3.0);
1774        assert_eq!(ctx_clone.fill_opacity(), 0.5);
1775        assert_eq!(ctx_clone.stroke_opacity(), 0.5);
1776        assert_eq!(ctx_clone.operations(), ctx.operations());
1777    }
1778
1779    #[test]
1780    fn test_set_opacity() {
1781        let mut ctx = GraphicsContext::new();
1782
1783        // Test setting opacity
1784        ctx.set_opacity(0.5);
1785        assert_eq!(ctx.fill_opacity(), 0.5);
1786        assert_eq!(ctx.stroke_opacity(), 0.5);
1787
1788        // Test clamping to valid range
1789        ctx.set_opacity(1.5);
1790        assert_eq!(ctx.fill_opacity(), 1.0);
1791        assert_eq!(ctx.stroke_opacity(), 1.0);
1792
1793        ctx.set_opacity(-0.5);
1794        assert_eq!(ctx.fill_opacity(), 0.0);
1795        assert_eq!(ctx.stroke_opacity(), 0.0);
1796    }
1797
1798    #[test]
1799    fn test_set_fill_opacity() {
1800        let mut ctx = GraphicsContext::new();
1801
1802        ctx.set_fill_opacity(0.3);
1803        assert_eq!(ctx.fill_opacity(), 0.3);
1804        assert_eq!(ctx.stroke_opacity(), 1.0); // Should not affect stroke
1805
1806        // Test clamping
1807        ctx.set_fill_opacity(2.0);
1808        assert_eq!(ctx.fill_opacity(), 1.0);
1809    }
1810
1811    #[test]
1812    fn test_set_stroke_opacity() {
1813        let mut ctx = GraphicsContext::new();
1814
1815        ctx.set_stroke_opacity(0.7);
1816        assert_eq!(ctx.stroke_opacity(), 0.7);
1817        assert_eq!(ctx.fill_opacity(), 1.0); // Should not affect fill
1818
1819        // Test clamping
1820        ctx.set_stroke_opacity(-1.0);
1821        assert_eq!(ctx.stroke_opacity(), 0.0);
1822    }
1823
1824    #[test]
1825    fn test_uses_transparency() {
1826        let mut ctx = GraphicsContext::new();
1827
1828        // Initially no transparency
1829        assert!(!ctx.uses_transparency());
1830
1831        // With fill transparency
1832        ctx.set_fill_opacity(0.5);
1833        assert!(ctx.uses_transparency());
1834
1835        // Reset and test stroke transparency
1836        ctx.set_fill_opacity(1.0);
1837        assert!(!ctx.uses_transparency());
1838        ctx.set_stroke_opacity(0.8);
1839        assert!(ctx.uses_transparency());
1840
1841        // Both transparent
1842        ctx.set_fill_opacity(0.5);
1843        assert!(ctx.uses_transparency());
1844    }
1845
1846    #[test]
1847    fn test_generate_graphics_state_dict() {
1848        let mut ctx = GraphicsContext::new();
1849
1850        // No transparency
1851        assert_eq!(ctx.generate_graphics_state_dict(), None);
1852
1853        // Fill opacity only
1854        ctx.set_fill_opacity(0.5);
1855        let dict = ctx
1856            .generate_graphics_state_dict()
1857            .expect("Writing to string should never fail");
1858        assert!(dict.contains("/Type /ExtGState"));
1859        assert!(dict.contains("/ca 0.500"));
1860        assert!(!dict.contains("/CA"));
1861
1862        // Stroke opacity only
1863        ctx.set_fill_opacity(1.0);
1864        ctx.set_stroke_opacity(0.75);
1865        let dict = ctx
1866            .generate_graphics_state_dict()
1867            .expect("Writing to string should never fail");
1868        assert!(dict.contains("/Type /ExtGState"));
1869        assert!(dict.contains("/CA 0.750"));
1870        assert!(!dict.contains("/ca"));
1871
1872        // Both opacities
1873        ctx.set_fill_opacity(0.25);
1874        let dict = ctx
1875            .generate_graphics_state_dict()
1876            .expect("Writing to string should never fail");
1877        assert!(dict.contains("/Type /ExtGState"));
1878        assert!(dict.contains("/ca 0.250"));
1879        assert!(dict.contains("/CA 0.750"));
1880    }
1881
1882    #[test]
1883    fn test_opacity_with_graphics_operations() {
1884        let mut ctx = GraphicsContext::new();
1885
1886        ctx.set_fill_color(Color::red())
1887            .set_opacity(0.5)
1888            .rect(10.0, 10.0, 100.0, 100.0)
1889            .fill();
1890
1891        assert_eq!(ctx.fill_opacity(), 0.5);
1892        assert_eq!(ctx.stroke_opacity(), 0.5);
1893
1894        let ops = ctx.operations();
1895        assert!(ops.contains("10.00 10.00 100.00 100.00 re"));
1896        assert!(ops.contains("1.000 0.000 0.000 rg")); // Red color
1897        assert!(ops.contains("f")); // Fill
1898    }
1899
1900    #[test]
1901    fn test_begin_end_text() {
1902        let mut ctx = GraphicsContext::new();
1903        ctx.begin_text();
1904        assert!(ctx.operations().contains("BT\n"));
1905
1906        ctx.end_text();
1907        assert!(ctx.operations().contains("ET\n"));
1908    }
1909
1910    #[test]
1911    fn test_set_font() {
1912        let mut ctx = GraphicsContext::new();
1913        ctx.set_font(Font::Helvetica, 12.0);
1914        assert!(ctx.operations().contains("/Helvetica 12 Tf\n"));
1915
1916        ctx.set_font(Font::TimesBold, 14.5);
1917        assert!(ctx.operations().contains("/Times-Bold 14.5 Tf\n"));
1918    }
1919
1920    #[test]
1921    fn test_set_text_position() {
1922        let mut ctx = GraphicsContext::new();
1923        ctx.set_text_position(100.0, 200.0);
1924        assert!(ctx.operations().contains("100.00 200.00 Td\n"));
1925    }
1926
1927    #[test]
1928    fn test_show_text() {
1929        let mut ctx = GraphicsContext::new();
1930        ctx.show_text("Hello World")
1931            .expect("Writing to string should never fail");
1932        assert!(ctx.operations().contains("(Hello World) Tj\n"));
1933    }
1934
1935    #[test]
1936    fn test_show_text_with_escaping() {
1937        let mut ctx = GraphicsContext::new();
1938        ctx.show_text("Test (parentheses)")
1939            .expect("Writing to string should never fail");
1940        assert!(ctx.operations().contains("(Test \\(parentheses\\)) Tj\n"));
1941
1942        ctx.clear();
1943        ctx.show_text("Back\\slash")
1944            .expect("Writing to string should never fail");
1945        assert!(ctx.operations().contains("(Back\\\\slash) Tj\n"));
1946
1947        ctx.clear();
1948        ctx.show_text("Line\nBreak")
1949            .expect("Writing to string should never fail");
1950        assert!(ctx.operations().contains("(Line\\nBreak) Tj\n"));
1951    }
1952
1953    #[test]
1954    fn test_text_operations_chaining() {
1955        let mut ctx = GraphicsContext::new();
1956        ctx.begin_text()
1957            .set_font(Font::Courier, 10.0)
1958            .set_text_position(50.0, 100.0)
1959            .show_text("Test")
1960            .unwrap()
1961            .end_text();
1962
1963        let ops = ctx.operations();
1964        assert!(ops.contains("BT\n"));
1965        assert!(ops.contains("/Courier 10 Tf\n"));
1966        assert!(ops.contains("50.00 100.00 Td\n"));
1967        assert!(ops.contains("(Test) Tj\n"));
1968        assert!(ops.contains("ET\n"));
1969    }
1970
1971    #[test]
1972    fn test_clip() {
1973        let mut ctx = GraphicsContext::new();
1974        ctx.clip();
1975        assert!(ctx.operations().contains("W\n"));
1976    }
1977
1978    #[test]
1979    fn test_clip_even_odd() {
1980        let mut ctx = GraphicsContext::new();
1981        ctx.clip_even_odd();
1982        assert!(ctx.operations().contains("W*\n"));
1983    }
1984
1985    #[test]
1986    fn test_clipping_with_path() {
1987        let mut ctx = GraphicsContext::new();
1988
1989        // Create a rectangular clipping path
1990        ctx.rect(10.0, 10.0, 100.0, 50.0).clip();
1991
1992        let ops = ctx.operations();
1993        assert!(ops.contains("10.00 10.00 100.00 50.00 re\n"));
1994        assert!(ops.contains("W\n"));
1995    }
1996
1997    #[test]
1998    fn test_clipping_even_odd_with_path() {
1999        let mut ctx = GraphicsContext::new();
2000
2001        // Create a complex path and clip with even-odd rule
2002        ctx.move_to(0.0, 0.0)
2003            .line_to(100.0, 0.0)
2004            .line_to(100.0, 100.0)
2005            .line_to(0.0, 100.0)
2006            .close_path()
2007            .clip_even_odd();
2008
2009        let ops = ctx.operations();
2010        assert!(ops.contains("0.00 0.00 m\n"));
2011        assert!(ops.contains("100.00 0.00 l\n"));
2012        assert!(ops.contains("100.00 100.00 l\n"));
2013        assert!(ops.contains("0.00 100.00 l\n"));
2014        assert!(ops.contains("h\n"));
2015        assert!(ops.contains("W*\n"));
2016    }
2017
2018    #[test]
2019    fn test_clipping_chaining() {
2020        let mut ctx = GraphicsContext::new();
2021
2022        // Test method chaining with clipping
2023        ctx.save_state()
2024            .rect(20.0, 20.0, 60.0, 60.0)
2025            .clip()
2026            .set_fill_color(Color::red())
2027            .rect(0.0, 0.0, 100.0, 100.0)
2028            .fill()
2029            .restore_state();
2030
2031        let ops = ctx.operations();
2032        assert!(ops.contains("q\n"));
2033        assert!(ops.contains("20.00 20.00 60.00 60.00 re\n"));
2034        assert!(ops.contains("W\n"));
2035        assert!(ops.contains("1.000 0.000 0.000 rg\n"));
2036        assert!(ops.contains("0.00 0.00 100.00 100.00 re\n"));
2037        assert!(ops.contains("f\n"));
2038        assert!(ops.contains("Q\n"));
2039    }
2040
2041    #[test]
2042    fn test_multiple_clipping_regions() {
2043        let mut ctx = GraphicsContext::new();
2044
2045        // Test nested clipping regions
2046        ctx.save_state()
2047            .rect(0.0, 0.0, 200.0, 200.0)
2048            .clip()
2049            .save_state()
2050            .circle(100.0, 100.0, 50.0)
2051            .clip_even_odd()
2052            .set_fill_color(Color::blue())
2053            .rect(50.0, 50.0, 100.0, 100.0)
2054            .fill()
2055            .restore_state()
2056            .restore_state();
2057
2058        let ops = ctx.operations();
2059        // Check for nested save/restore states
2060        let q_count = ops.matches("q\n").count();
2061        let q_restore_count = ops.matches("Q\n").count();
2062        assert_eq!(q_count, 2);
2063        assert_eq!(q_restore_count, 2);
2064
2065        // Check for both clipping operations
2066        assert!(ops.contains("W\n"));
2067        assert!(ops.contains("W*\n"));
2068    }
2069
2070    // ============= Additional Critical Method Tests =============
2071
2072    #[test]
2073    fn test_move_to_and_line_to() {
2074        let mut ctx = GraphicsContext::new();
2075        ctx.move_to(100.0, 200.0).line_to(300.0, 400.0).stroke();
2076
2077        let ops = ctx
2078            .generate_operations()
2079            .expect("Writing to string should never fail");
2080        let ops_str = String::from_utf8_lossy(&ops);
2081        assert!(ops_str.contains("100.00 200.00 m"));
2082        assert!(ops_str.contains("300.00 400.00 l"));
2083        assert!(ops_str.contains("S"));
2084    }
2085
2086    #[test]
2087    fn test_bezier_curve() {
2088        let mut ctx = GraphicsContext::new();
2089        ctx.move_to(0.0, 0.0)
2090            .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0)
2091            .stroke();
2092
2093        let ops = ctx
2094            .generate_operations()
2095            .expect("Writing to string should never fail");
2096        let ops_str = String::from_utf8_lossy(&ops);
2097        assert!(ops_str.contains("0.00 0.00 m"));
2098        assert!(ops_str.contains("10.00 20.00 30.00 40.00 50.00 60.00 c"));
2099        assert!(ops_str.contains("S"));
2100    }
2101
2102    #[test]
2103    fn test_circle_path() {
2104        let mut ctx = GraphicsContext::new();
2105        ctx.circle(100.0, 100.0, 50.0).fill();
2106
2107        let ops = ctx
2108            .generate_operations()
2109            .expect("Writing to string should never fail");
2110        let ops_str = String::from_utf8_lossy(&ops);
2111        // Circle should use bezier curves (c operator)
2112        assert!(ops_str.contains(" c"));
2113        assert!(ops_str.contains("f"));
2114    }
2115
2116    #[test]
2117    fn test_path_closing() {
2118        let mut ctx = GraphicsContext::new();
2119        ctx.move_to(0.0, 0.0)
2120            .line_to(100.0, 0.0)
2121            .line_to(100.0, 100.0)
2122            .close_path()
2123            .stroke();
2124
2125        let ops = ctx
2126            .generate_operations()
2127            .expect("Writing to string should never fail");
2128        let ops_str = String::from_utf8_lossy(&ops);
2129        assert!(ops_str.contains("h")); // close path operator
2130        assert!(ops_str.contains("S"));
2131    }
2132
2133    #[test]
2134    fn test_fill_and_stroke() {
2135        let mut ctx = GraphicsContext::new();
2136        ctx.rect(10.0, 10.0, 50.0, 50.0).fill_stroke();
2137
2138        let ops = ctx
2139            .generate_operations()
2140            .expect("Writing to string should never fail");
2141        let ops_str = String::from_utf8_lossy(&ops);
2142        assert!(ops_str.contains("10.00 10.00 50.00 50.00 re"));
2143        assert!(ops_str.contains("B")); // fill and stroke operator
2144    }
2145
2146    #[test]
2147    fn test_color_settings() {
2148        let mut ctx = GraphicsContext::new();
2149        ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2150            .set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
2151            .rect(10.0, 10.0, 50.0, 50.0)
2152            .fill_stroke(); // This will write the colors
2153
2154        assert_eq!(ctx.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2155        assert_eq!(ctx.stroke_color(), Color::rgb(0.0, 1.0, 0.0));
2156
2157        let ops = ctx
2158            .generate_operations()
2159            .expect("Writing to string should never fail");
2160        let ops_str = String::from_utf8_lossy(&ops);
2161        assert!(ops_str.contains("1.000 0.000 0.000 rg")); // red fill
2162        assert!(ops_str.contains("0.000 1.000 0.000 RG")); // green stroke
2163    }
2164
2165    #[test]
2166    fn test_line_styles() {
2167        let mut ctx = GraphicsContext::new();
2168        ctx.set_line_width(2.5)
2169            .set_line_cap(LineCap::Round)
2170            .set_line_join(LineJoin::Bevel);
2171
2172        assert_eq!(ctx.line_width(), 2.5);
2173
2174        let ops = ctx
2175            .generate_operations()
2176            .expect("Writing to string should never fail");
2177        let ops_str = String::from_utf8_lossy(&ops);
2178        assert!(ops_str.contains("2.50 w")); // line width
2179        assert!(ops_str.contains("1 J")); // round line cap
2180        assert!(ops_str.contains("2 j")); // bevel line join
2181    }
2182
2183    #[test]
2184    fn test_opacity_settings() {
2185        let mut ctx = GraphicsContext::new();
2186        ctx.set_opacity(0.5);
2187
2188        assert_eq!(ctx.fill_opacity(), 0.5);
2189        assert_eq!(ctx.stroke_opacity(), 0.5);
2190        assert!(ctx.uses_transparency());
2191
2192        ctx.set_fill_opacity(0.7).set_stroke_opacity(0.3);
2193
2194        assert_eq!(ctx.fill_opacity(), 0.7);
2195        assert_eq!(ctx.stroke_opacity(), 0.3);
2196    }
2197
2198    #[test]
2199    fn test_state_save_restore() {
2200        let mut ctx = GraphicsContext::new();
2201        ctx.save_state()
2202            .set_fill_color(Color::rgb(1.0, 0.0, 0.0))
2203            .restore_state();
2204
2205        let ops = ctx
2206            .generate_operations()
2207            .expect("Writing to string should never fail");
2208        let ops_str = String::from_utf8_lossy(&ops);
2209        assert!(ops_str.contains("q")); // save state
2210        assert!(ops_str.contains("Q")); // restore state
2211    }
2212
2213    #[test]
2214    fn test_transformations() {
2215        let mut ctx = GraphicsContext::new();
2216        ctx.translate(100.0, 200.0).scale(2.0, 3.0).rotate(45.0);
2217
2218        let ops = ctx
2219            .generate_operations()
2220            .expect("Writing to string should never fail");
2221        let ops_str = String::from_utf8_lossy(&ops);
2222        assert!(ops_str.contains("1 0 0 1 100.00 200.00 cm")); // translate
2223        assert!(ops_str.contains("2.00 0 0 3.00 0 0 cm")); // scale
2224        assert!(ops_str.contains("cm")); // rotate matrix
2225    }
2226
2227    #[test]
2228    fn test_custom_transform() {
2229        let mut ctx = GraphicsContext::new();
2230        ctx.transform(1.0, 0.5, 0.5, 1.0, 10.0, 20.0);
2231
2232        let ops = ctx
2233            .generate_operations()
2234            .expect("Writing to string should never fail");
2235        let ops_str = String::from_utf8_lossy(&ops);
2236        assert!(ops_str.contains("1.00 0.50 0.50 1.00 10.00 20.00 cm"));
2237    }
2238
2239    #[test]
2240    fn test_rectangle_path() {
2241        let mut ctx = GraphicsContext::new();
2242        ctx.rectangle(25.0, 25.0, 150.0, 100.0).stroke();
2243
2244        let ops = ctx
2245            .generate_operations()
2246            .expect("Writing to string should never fail");
2247        let ops_str = String::from_utf8_lossy(&ops);
2248        assert!(ops_str.contains("25.00 25.00 150.00 100.00 re"));
2249        assert!(ops_str.contains("S"));
2250    }
2251
2252    #[test]
2253    fn test_empty_operations() {
2254        let ctx = GraphicsContext::new();
2255        let ops = ctx
2256            .generate_operations()
2257            .expect("Writing to string should never fail");
2258        assert!(ops.is_empty());
2259    }
2260
2261    #[test]
2262    fn test_complex_path_operations() {
2263        let mut ctx = GraphicsContext::new();
2264        ctx.move_to(50.0, 50.0)
2265            .line_to(100.0, 50.0)
2266            .curve_to(125.0, 50.0, 150.0, 75.0, 150.0, 100.0)
2267            .line_to(150.0, 150.0)
2268            .close_path()
2269            .fill();
2270
2271        let ops = ctx
2272            .generate_operations()
2273            .expect("Writing to string should never fail");
2274        let ops_str = String::from_utf8_lossy(&ops);
2275        assert!(ops_str.contains("50.00 50.00 m"));
2276        assert!(ops_str.contains("100.00 50.00 l"));
2277        assert!(ops_str.contains("125.00 50.00 150.00 75.00 150.00 100.00 c"));
2278        assert!(ops_str.contains("150.00 150.00 l"));
2279        assert!(ops_str.contains("h"));
2280        assert!(ops_str.contains("f"));
2281    }
2282
2283    #[test]
2284    fn test_graphics_state_dict_generation() {
2285        let mut ctx = GraphicsContext::new();
2286
2287        // Without transparency, should return None
2288        assert!(ctx.generate_graphics_state_dict().is_none());
2289
2290        // With transparency, should generate dict
2291        ctx.set_opacity(0.5);
2292        let dict = ctx.generate_graphics_state_dict();
2293        assert!(dict.is_some());
2294        let dict_str = dict.expect("Writing to string should never fail");
2295        assert!(dict_str.contains("/ca 0.5"));
2296        assert!(dict_str.contains("/CA 0.5"));
2297    }
2298
2299    #[test]
2300    fn test_line_dash_pattern() {
2301        let mut ctx = GraphicsContext::new();
2302        let pattern = LineDashPattern {
2303            array: vec![3.0, 2.0],
2304            phase: 0.0,
2305        };
2306        ctx.set_line_dash_pattern(pattern);
2307
2308        let ops = ctx
2309            .generate_operations()
2310            .expect("Writing to string should never fail");
2311        let ops_str = String::from_utf8_lossy(&ops);
2312        assert!(ops_str.contains("[3.00 2.00] 0.00 d"));
2313    }
2314
2315    #[test]
2316    fn test_miter_limit_setting() {
2317        let mut ctx = GraphicsContext::new();
2318        ctx.set_miter_limit(4.0);
2319
2320        let ops = ctx
2321            .generate_operations()
2322            .expect("Writing to string should never fail");
2323        let ops_str = String::from_utf8_lossy(&ops);
2324        assert!(ops_str.contains("4.00 M"));
2325    }
2326
2327    #[test]
2328    fn test_line_cap_styles() {
2329        let mut ctx = GraphicsContext::new();
2330
2331        ctx.set_line_cap(LineCap::Butt);
2332        let ops = ctx
2333            .generate_operations()
2334            .expect("Writing to string should never fail");
2335        let ops_str = String::from_utf8_lossy(&ops);
2336        assert!(ops_str.contains("0 J"));
2337
2338        let mut ctx = GraphicsContext::new();
2339        ctx.set_line_cap(LineCap::Round);
2340        let ops = ctx
2341            .generate_operations()
2342            .expect("Writing to string should never fail");
2343        let ops_str = String::from_utf8_lossy(&ops);
2344        assert!(ops_str.contains("1 J"));
2345
2346        let mut ctx = GraphicsContext::new();
2347        ctx.set_line_cap(LineCap::Square);
2348        let ops = ctx
2349            .generate_operations()
2350            .expect("Writing to string should never fail");
2351        let ops_str = String::from_utf8_lossy(&ops);
2352        assert!(ops_str.contains("2 J"));
2353    }
2354
2355    #[test]
2356    fn test_transparency_groups() {
2357        let mut ctx = GraphicsContext::new();
2358
2359        // Test basic transparency group
2360        let group = TransparencyGroup::new()
2361            .with_isolated(true)
2362            .with_opacity(0.5);
2363
2364        ctx.begin_transparency_group(group);
2365        assert!(ctx.in_transparency_group());
2366
2367        // Draw something in the group
2368        ctx.rect(10.0, 10.0, 100.0, 100.0);
2369        ctx.fill();
2370
2371        ctx.end_transparency_group();
2372        assert!(!ctx.in_transparency_group());
2373
2374        // Check that operations contain transparency markers
2375        let ops = ctx.operations();
2376        assert!(ops.contains("% Begin Transparency Group"));
2377        assert!(ops.contains("% End Transparency Group"));
2378    }
2379
2380    #[test]
2381    fn test_nested_transparency_groups() {
2382        let mut ctx = GraphicsContext::new();
2383
2384        // First group
2385        let group1 = TransparencyGroup::isolated().with_opacity(0.8);
2386        ctx.begin_transparency_group(group1);
2387        assert!(ctx.in_transparency_group());
2388
2389        // Nested group
2390        let group2 = TransparencyGroup::knockout().with_blend_mode(BlendMode::Multiply);
2391        ctx.begin_transparency_group(group2);
2392
2393        // Draw in nested group
2394        ctx.circle(50.0, 50.0, 25.0);
2395        ctx.fill();
2396
2397        // End nested group
2398        ctx.end_transparency_group();
2399        assert!(ctx.in_transparency_group()); // Still in first group
2400
2401        // End first group
2402        ctx.end_transparency_group();
2403        assert!(!ctx.in_transparency_group());
2404    }
2405
2406    #[test]
2407    fn test_line_join_styles() {
2408        let mut ctx = GraphicsContext::new();
2409
2410        ctx.set_line_join(LineJoin::Miter);
2411        let ops = ctx
2412            .generate_operations()
2413            .expect("Writing to string should never fail");
2414        let ops_str = String::from_utf8_lossy(&ops);
2415        assert!(ops_str.contains("0 j"));
2416
2417        let mut ctx = GraphicsContext::new();
2418        ctx.set_line_join(LineJoin::Round);
2419        let ops = ctx
2420            .generate_operations()
2421            .expect("Writing to string should never fail");
2422        let ops_str = String::from_utf8_lossy(&ops);
2423        assert!(ops_str.contains("1 j"));
2424
2425        let mut ctx = GraphicsContext::new();
2426        ctx.set_line_join(LineJoin::Bevel);
2427        let ops = ctx
2428            .generate_operations()
2429            .expect("Writing to string should never fail");
2430        let ops_str = String::from_utf8_lossy(&ops);
2431        assert!(ops_str.contains("2 j"));
2432    }
2433
2434    #[test]
2435    fn test_rendering_intent() {
2436        let mut ctx = GraphicsContext::new();
2437
2438        ctx.set_rendering_intent(RenderingIntent::AbsoluteColorimetric);
2439        assert_eq!(
2440            ctx.rendering_intent(),
2441            RenderingIntent::AbsoluteColorimetric
2442        );
2443
2444        ctx.set_rendering_intent(RenderingIntent::Perceptual);
2445        assert_eq!(ctx.rendering_intent(), RenderingIntent::Perceptual);
2446
2447        ctx.set_rendering_intent(RenderingIntent::Saturation);
2448        assert_eq!(ctx.rendering_intent(), RenderingIntent::Saturation);
2449    }
2450
2451    #[test]
2452    fn test_flatness_tolerance() {
2453        let mut ctx = GraphicsContext::new();
2454
2455        ctx.set_flatness(0.5);
2456        assert_eq!(ctx.flatness(), 0.5);
2457
2458        let ops = ctx
2459            .generate_operations()
2460            .expect("Writing to string should never fail");
2461        let ops_str = String::from_utf8_lossy(&ops);
2462        assert!(ops_str.contains("0.50 i"));
2463    }
2464
2465    #[test]
2466    fn test_smoothness_tolerance() {
2467        let mut ctx = GraphicsContext::new();
2468
2469        let _ = ctx.set_smoothness(0.1);
2470        assert_eq!(ctx.smoothness(), 0.1);
2471    }
2472
2473    #[test]
2474    fn test_bezier_curves() {
2475        let mut ctx = GraphicsContext::new();
2476
2477        // Cubic Bezier
2478        ctx.move_to(10.0, 10.0);
2479        ctx.curve_to(20.0, 10.0, 30.0, 20.0, 30.0, 30.0);
2480
2481        let ops = ctx
2482            .generate_operations()
2483            .expect("Writing to string should never fail");
2484        let ops_str = String::from_utf8_lossy(&ops);
2485        assert!(ops_str.contains("10.00 10.00 m"));
2486        assert!(ops_str.contains("c")); // cubic curve
2487    }
2488
2489    #[test]
2490    fn test_clipping_path() {
2491        let mut ctx = GraphicsContext::new();
2492
2493        ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2494        ctx.clip();
2495
2496        let ops = ctx
2497            .generate_operations()
2498            .expect("Writing to string should never fail");
2499        let ops_str = String::from_utf8_lossy(&ops);
2500        assert!(ops_str.contains("W"));
2501    }
2502
2503    #[test]
2504    fn test_even_odd_clipping() {
2505        let mut ctx = GraphicsContext::new();
2506
2507        ctx.rectangle(10.0, 10.0, 100.0, 100.0);
2508        ctx.clip_even_odd();
2509
2510        let ops = ctx
2511            .generate_operations()
2512            .expect("Writing to string should never fail");
2513        let ops_str = String::from_utf8_lossy(&ops);
2514        assert!(ops_str.contains("W*"));
2515    }
2516
2517    #[test]
2518    fn test_color_creation() {
2519        // Test color creation methods
2520        let gray = Color::gray(0.5);
2521        assert_eq!(gray, Color::Gray(0.5));
2522
2523        let rgb = Color::rgb(0.2, 0.4, 0.6);
2524        assert_eq!(rgb, Color::Rgb(0.2, 0.4, 0.6));
2525
2526        let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
2527        assert_eq!(cmyk, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
2528
2529        // Test predefined colors
2530        assert_eq!(Color::black(), Color::Gray(0.0));
2531        assert_eq!(Color::white(), Color::Gray(1.0));
2532        assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
2533    }
2534
2535    #[test]
2536    fn test_extended_graphics_state() {
2537        let ctx = GraphicsContext::new();
2538
2539        // Test that we can create and use an extended graphics state
2540        let _extgstate = ExtGState::new();
2541
2542        // We should be able to create the state without errors
2543        assert!(ctx.generate_operations().is_ok());
2544    }
2545
2546    #[test]
2547    fn test_path_construction_methods() {
2548        let mut ctx = GraphicsContext::new();
2549
2550        // Test basic path construction methods that exist
2551        ctx.move_to(10.0, 10.0);
2552        ctx.line_to(20.0, 20.0);
2553        ctx.curve_to(30.0, 30.0, 40.0, 40.0, 50.0, 50.0);
2554        ctx.rect(60.0, 60.0, 30.0, 30.0);
2555        ctx.circle(100.0, 100.0, 25.0);
2556        ctx.close_path();
2557
2558        let ops = ctx
2559            .generate_operations()
2560            .expect("Writing to string should never fail");
2561        assert!(!ops.is_empty());
2562    }
2563
2564    #[test]
2565    fn test_graphics_context_clone_advanced() {
2566        let mut ctx = GraphicsContext::new();
2567        ctx.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
2568        ctx.set_line_width(5.0);
2569
2570        let cloned = ctx.clone();
2571        assert_eq!(cloned.fill_color(), Color::rgb(1.0, 0.0, 0.0));
2572        assert_eq!(cloned.line_width(), 5.0);
2573    }
2574
2575    #[test]
2576    fn test_basic_drawing_operations() {
2577        let mut ctx = GraphicsContext::new();
2578
2579        // Test that we can at least create a basic drawing
2580        ctx.move_to(50.0, 50.0);
2581        ctx.line_to(100.0, 100.0);
2582        ctx.stroke();
2583
2584        let ops = ctx
2585            .generate_operations()
2586            .expect("Writing to string should never fail");
2587        let ops_str = String::from_utf8_lossy(&ops);
2588        assert!(ops_str.contains("m")); // move
2589        assert!(ops_str.contains("l")); // line
2590        assert!(ops_str.contains("S")); // stroke
2591    }
2592
2593    #[test]
2594    fn test_graphics_state_stack() {
2595        let mut ctx = GraphicsContext::new();
2596
2597        // Initial state
2598        ctx.set_fill_color(Color::black());
2599
2600        // Save and change
2601        ctx.save_state();
2602        ctx.set_fill_color(Color::red());
2603        assert_eq!(ctx.fill_color(), Color::red());
2604
2605        // Save again and change
2606        ctx.save_state();
2607        ctx.set_fill_color(Color::blue());
2608        assert_eq!(ctx.fill_color(), Color::blue());
2609
2610        // Restore once
2611        ctx.restore_state();
2612        assert_eq!(ctx.fill_color(), Color::red());
2613
2614        // Restore again
2615        ctx.restore_state();
2616        assert_eq!(ctx.fill_color(), Color::black());
2617    }
2618
2619    #[test]
2620    fn test_word_spacing() {
2621        let mut ctx = GraphicsContext::new();
2622        ctx.set_word_spacing(2.5);
2623
2624        let ops = ctx.generate_operations().unwrap();
2625        let ops_str = String::from_utf8_lossy(&ops);
2626        assert!(ops_str.contains("2.50 Tw"));
2627    }
2628
2629    #[test]
2630    fn test_character_spacing() {
2631        let mut ctx = GraphicsContext::new();
2632        ctx.set_character_spacing(1.0);
2633
2634        let ops = ctx.generate_operations().unwrap();
2635        let ops_str = String::from_utf8_lossy(&ops);
2636        assert!(ops_str.contains("1.00 Tc"));
2637    }
2638
2639    #[test]
2640    fn test_justified_text() {
2641        let mut ctx = GraphicsContext::new();
2642        ctx.begin_text();
2643        ctx.set_text_position(100.0, 200.0);
2644        ctx.show_justified_text("Hello world from PDF", 200.0)
2645            .unwrap();
2646        ctx.end_text();
2647
2648        let ops = ctx.generate_operations().unwrap();
2649        let ops_str = String::from_utf8_lossy(&ops);
2650
2651        // Should contain text operations
2652        assert!(ops_str.contains("BT")); // Begin text
2653        assert!(ops_str.contains("ET")); // End text
2654        assert!(ops_str.contains("100.00 200.00 Td")); // Text position
2655        assert!(ops_str.contains("(Hello world from PDF) Tj")); // Show text
2656
2657        // Should contain word spacing operations
2658        assert!(ops_str.contains("Tw")); // Word spacing
2659    }
2660
2661    #[test]
2662    fn test_justified_text_single_word() {
2663        let mut ctx = GraphicsContext::new();
2664        ctx.begin_text();
2665        ctx.show_justified_text("Hello", 200.0).unwrap();
2666        ctx.end_text();
2667
2668        let ops = ctx.generate_operations().unwrap();
2669        let ops_str = String::from_utf8_lossy(&ops);
2670
2671        // Single word should just use normal text display
2672        assert!(ops_str.contains("(Hello) Tj"));
2673        // Should not contain word spacing since there's only one word
2674        assert_eq!(ops_str.matches("Tw").count(), 0);
2675    }
2676
2677    #[test]
2678    fn test_text_width_estimation() {
2679        let ctx = GraphicsContext::new();
2680        let width = ctx.estimate_text_width_simple("Hello");
2681
2682        // Should return reasonable estimation based on font size and character count
2683        assert!(width > 0.0);
2684        assert_eq!(width, 5.0 * 12.0 * 0.6); // 5 chars * 12pt font * 0.6 factor
2685    }
2686
2687    #[test]
2688    fn test_set_alpha_methods() {
2689        let mut ctx = GraphicsContext::new();
2690
2691        // Test that set_alpha methods don't panic and return correctly
2692        assert!(ctx.set_alpha(0.5).is_ok());
2693        assert!(ctx.set_alpha_fill(0.3).is_ok());
2694        assert!(ctx.set_alpha_stroke(0.7).is_ok());
2695
2696        // Test edge cases - should handle clamping in ExtGState
2697        assert!(ctx.set_alpha(1.5).is_ok()); // Should not panic
2698        assert!(ctx.set_alpha(-0.2).is_ok()); // Should not panic
2699        assert!(ctx.set_alpha_fill(2.0).is_ok()); // Should not panic
2700        assert!(ctx.set_alpha_stroke(-1.0).is_ok()); // Should not panic
2701
2702        // Test that methods return self for chaining
2703        let result = ctx
2704            .set_alpha(0.5)
2705            .and_then(|c| c.set_alpha_fill(0.3))
2706            .and_then(|c| c.set_alpha_stroke(0.7));
2707        assert!(result.is_ok());
2708    }
2709
2710    #[test]
2711    fn test_alpha_methods_generate_extgstate() {
2712        let mut ctx = GraphicsContext::new();
2713
2714        // Set some transparency
2715        ctx.set_alpha(0.5).unwrap();
2716
2717        // Draw something to trigger ExtGState generation
2718        ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
2719
2720        let ops = ctx.generate_operations().unwrap();
2721        let ops_str = String::from_utf8_lossy(&ops);
2722
2723        // Should contain ExtGState reference
2724        assert!(ops_str.contains("/GS")); // ExtGState name
2725        assert!(ops_str.contains(" gs\n")); // ExtGState operator
2726
2727        // Test separate alpha settings
2728        ctx.clear();
2729        ctx.set_alpha_fill(0.3).unwrap();
2730        ctx.set_alpha_stroke(0.8).unwrap();
2731        ctx.rect(20.0, 20.0, 60.0, 60.0).fill_stroke();
2732
2733        let ops2 = ctx.generate_operations().unwrap();
2734        let ops_str2 = String::from_utf8_lossy(&ops2);
2735
2736        // Should contain multiple ExtGState references
2737        assert!(ops_str2.contains("/GS")); // ExtGState names
2738        assert!(ops_str2.contains(" gs\n")); // ExtGState operators
2739    }
2740
2741    #[test]
2742    fn test_add_command() {
2743        let mut ctx = GraphicsContext::new();
2744
2745        // Test normal command
2746        ctx.add_command("1 0 0 1 100 200 cm");
2747        let ops = ctx.operations();
2748        assert!(ops.contains("1 0 0 1 100 200 cm\n"));
2749
2750        // Test that newline is always added
2751        ctx.clear();
2752        ctx.add_command("q");
2753        assert_eq!(ctx.operations(), "q\n");
2754
2755        // Test empty string
2756        ctx.clear();
2757        ctx.add_command("");
2758        assert_eq!(ctx.operations(), "\n");
2759
2760        // Test command with existing newline
2761        ctx.clear();
2762        ctx.add_command("Q\n");
2763        assert_eq!(ctx.operations(), "Q\n\n"); // Double newline
2764
2765        // Test multiple commands
2766        ctx.clear();
2767        ctx.add_command("q");
2768        ctx.add_command("1 0 0 1 50 50 cm");
2769        ctx.add_command("Q");
2770        assert_eq!(ctx.operations(), "q\n1 0 0 1 50 50 cm\nQ\n");
2771    }
2772
2773    #[test]
2774    fn test_get_operations() {
2775        let mut ctx = GraphicsContext::new();
2776        ctx.rect(10.0, 10.0, 50.0, 50.0);
2777        let ops1 = ctx.operations();
2778        let ops2 = ctx.get_operations();
2779        assert_eq!(ops1, ops2);
2780    }
2781
2782    #[test]
2783    fn test_set_line_solid() {
2784        let mut ctx = GraphicsContext::new();
2785        ctx.set_line_dash_pattern(LineDashPattern::new(vec![5.0, 3.0], 0.0));
2786        ctx.set_line_solid();
2787        let ops = ctx.operations();
2788        assert!(ops.contains("[] 0 d\n"));
2789    }
2790
2791    #[test]
2792    fn test_set_custom_font() {
2793        let mut ctx = GraphicsContext::new();
2794        ctx.set_custom_font("CustomFont", 14.0);
2795        assert_eq!(ctx.current_font_name, Some("CustomFont".to_string()));
2796        assert_eq!(ctx.current_font_size, 14.0);
2797    }
2798
2799    #[test]
2800    fn test_set_glyph_mapping() {
2801        let mut ctx = GraphicsContext::new();
2802
2803        // Test initial state
2804        assert!(ctx.glyph_mapping.is_none());
2805
2806        // Test normal mapping
2807        let mut mapping = HashMap::new();
2808        mapping.insert(65u32, 1u16); // 'A' -> glyph 1
2809        mapping.insert(66u32, 2u16); // 'B' -> glyph 2
2810        ctx.set_glyph_mapping(mapping.clone());
2811        assert!(ctx.glyph_mapping.is_some());
2812        assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 2);
2813        assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), Some(&1));
2814        assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&66), Some(&2));
2815
2816        // Test empty mapping
2817        ctx.set_glyph_mapping(HashMap::new());
2818        assert!(ctx.glyph_mapping.is_some());
2819        assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 0);
2820
2821        // Test overwrite existing mapping
2822        let mut new_mapping = HashMap::new();
2823        new_mapping.insert(67u32, 3u16); // 'C' -> glyph 3
2824        ctx.set_glyph_mapping(new_mapping);
2825        assert_eq!(ctx.glyph_mapping.as_ref().unwrap().len(), 1);
2826        assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&67), Some(&3));
2827        assert_eq!(ctx.glyph_mapping.as_ref().unwrap().get(&65), None); // Old mapping gone
2828    }
2829
2830    #[test]
2831    fn test_draw_text_basic() {
2832        let mut ctx = GraphicsContext::new();
2833        ctx.set_font(Font::Helvetica, 12.0);
2834
2835        let result = ctx.draw_text("Hello", 100.0, 200.0);
2836        assert!(result.is_ok());
2837
2838        let ops = ctx.operations();
2839        // Verify text block
2840        assert!(ops.contains("BT\n"));
2841        assert!(ops.contains("ET\n"));
2842
2843        // Verify font is set
2844        assert!(ops.contains("/Helvetica"));
2845        assert!(ops.contains("12"));
2846        assert!(ops.contains("Tf\n"));
2847
2848        // Verify positioning
2849        assert!(ops.contains("100"));
2850        assert!(ops.contains("200"));
2851        assert!(ops.contains("Td\n"));
2852
2853        // Verify text content
2854        assert!(ops.contains("(Hello)") || ops.contains("<48656c6c6f>")); // Text or hex
2855    }
2856
2857    #[test]
2858    fn test_draw_text_with_special_characters() {
2859        let mut ctx = GraphicsContext::new();
2860        ctx.set_font(Font::Helvetica, 12.0);
2861
2862        // Test with parentheses (must be escaped in PDF)
2863        let result = ctx.draw_text("Test (with) parens", 50.0, 100.0);
2864        assert!(result.is_ok());
2865
2866        let ops = ctx.operations();
2867        // Should escape parentheses
2868        assert!(ops.contains("\\(") || ops.contains("\\)") || ops.contains("<"));
2869        // Either escaped or hex
2870    }
2871
2872    #[test]
2873    fn test_draw_text_unicode_detection() {
2874        let mut ctx = GraphicsContext::new();
2875        ctx.set_font(Font::Helvetica, 12.0);
2876
2877        // ASCII text should use simple encoding
2878        ctx.draw_text("ASCII", 0.0, 0.0).unwrap();
2879        let _ops_ascii = ctx.operations();
2880
2881        ctx.clear();
2882
2883        // Unicode text should trigger different encoding
2884        ctx.set_font(Font::Helvetica, 12.0);
2885        ctx.draw_text("中文", 0.0, 0.0).unwrap();
2886        let ops_unicode = ctx.operations();
2887
2888        // Unicode should produce hex encoding
2889        assert!(ops_unicode.contains("<") && ops_unicode.contains(">"));
2890    }
2891
2892    #[test]
2893    #[allow(deprecated)]
2894    fn test_draw_text_hex_encoding() {
2895        let mut ctx = GraphicsContext::new();
2896        ctx.set_font(Font::Helvetica, 12.0);
2897        let result = ctx.draw_text_hex("Test", 50.0, 100.0);
2898        assert!(result.is_ok());
2899        let ops = ctx.operations();
2900        assert!(ops.contains("<"));
2901        assert!(ops.contains(">"));
2902    }
2903
2904    #[test]
2905    #[allow(deprecated)]
2906    fn test_draw_text_cid() {
2907        let mut ctx = GraphicsContext::new();
2908        ctx.set_custom_font("CustomCIDFont", 12.0);
2909        let result = ctx.draw_text_cid("Test", 50.0, 100.0);
2910        assert!(result.is_ok());
2911        let ops = ctx.operations();
2912        assert!(ops.contains("BT\n"));
2913        assert!(ops.contains("ET\n"));
2914    }
2915
2916    #[test]
2917    #[allow(deprecated)]
2918    fn test_draw_text_unicode() {
2919        let mut ctx = GraphicsContext::new();
2920        ctx.set_custom_font("UnicodeFont", 12.0);
2921        let result = ctx.draw_text_unicode("Test \u{4E2D}\u{6587}", 50.0, 100.0);
2922        assert!(result.is_ok());
2923        let ops = ctx.operations();
2924        assert!(ops.contains("BT\n"));
2925        assert!(ops.contains("ET\n"));
2926    }
2927
2928    #[test]
2929    fn test_begin_end_transparency_group() {
2930        let mut ctx = GraphicsContext::new();
2931
2932        // Initial state - no transparency group
2933        assert!(!ctx.in_transparency_group());
2934        assert!(ctx.current_transparency_group().is_none());
2935
2936        // Begin transparency group
2937        let group = TransparencyGroup::new();
2938        ctx.begin_transparency_group(group);
2939        assert!(ctx.in_transparency_group());
2940        assert!(ctx.current_transparency_group().is_some());
2941
2942        // Verify operations contain transparency marker
2943        let ops = ctx.operations();
2944        assert!(ops.contains("% Begin Transparency Group"));
2945
2946        // End transparency group
2947        ctx.end_transparency_group();
2948        assert!(!ctx.in_transparency_group());
2949        assert!(ctx.current_transparency_group().is_none());
2950
2951        // Verify end marker
2952        let ops_after = ctx.operations();
2953        assert!(ops_after.contains("% End Transparency Group"));
2954    }
2955
2956    #[test]
2957    fn test_transparency_group_nesting() {
2958        let mut ctx = GraphicsContext::new();
2959
2960        // Nest 3 levels
2961        let group1 = TransparencyGroup::new();
2962        let group2 = TransparencyGroup::new();
2963        let group3 = TransparencyGroup::new();
2964
2965        ctx.begin_transparency_group(group1);
2966        assert_eq!(ctx.transparency_stack.len(), 1);
2967
2968        ctx.begin_transparency_group(group2);
2969        assert_eq!(ctx.transparency_stack.len(), 2);
2970
2971        ctx.begin_transparency_group(group3);
2972        assert_eq!(ctx.transparency_stack.len(), 3);
2973
2974        // End all
2975        ctx.end_transparency_group();
2976        assert_eq!(ctx.transparency_stack.len(), 2);
2977
2978        ctx.end_transparency_group();
2979        assert_eq!(ctx.transparency_stack.len(), 1);
2980
2981        ctx.end_transparency_group();
2982        assert_eq!(ctx.transparency_stack.len(), 0);
2983        assert!(!ctx.in_transparency_group());
2984    }
2985
2986    #[test]
2987    fn test_transparency_group_without_begin() {
2988        let mut ctx = GraphicsContext::new();
2989
2990        // Try to end without begin - should not panic, just be no-op
2991        assert!(!ctx.in_transparency_group());
2992        ctx.end_transparency_group();
2993        assert!(!ctx.in_transparency_group());
2994    }
2995
2996    #[test]
2997    fn test_extgstate_manager_access() {
2998        let ctx = GraphicsContext::new();
2999        let manager = ctx.extgstate_manager();
3000        assert_eq!(manager.count(), 0);
3001    }
3002
3003    #[test]
3004    fn test_extgstate_manager_mut_access() {
3005        let mut ctx = GraphicsContext::new();
3006        let manager = ctx.extgstate_manager_mut();
3007        assert_eq!(manager.count(), 0);
3008    }
3009
3010    #[test]
3011    fn test_has_extgstates() {
3012        let mut ctx = GraphicsContext::new();
3013
3014        // Initially no extgstates
3015        assert!(!ctx.has_extgstates());
3016        assert_eq!(ctx.extgstate_manager().count(), 0);
3017
3018        // Adding transparency creates extgstate
3019        ctx.set_alpha(0.5).unwrap();
3020        ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3021        let result = ctx.generate_operations().unwrap();
3022
3023        assert!(ctx.has_extgstates());
3024        assert!(ctx.extgstate_manager().count() > 0);
3025
3026        // Verify extgstate is in PDF output
3027        let output = String::from_utf8_lossy(&result);
3028        assert!(output.contains("/GS")); // ExtGState reference
3029        assert!(output.contains(" gs\n")); // ExtGState operator
3030    }
3031
3032    #[test]
3033    fn test_generate_extgstate_resources() {
3034        let mut ctx = GraphicsContext::new();
3035        ctx.set_alpha(0.5).unwrap();
3036        ctx.rect(10.0, 10.0, 50.0, 50.0).fill();
3037        ctx.generate_operations().unwrap();
3038
3039        let resources = ctx.generate_extgstate_resources();
3040        assert!(resources.is_ok());
3041    }
3042
3043    #[test]
3044    fn test_apply_extgstate() {
3045        let mut ctx = GraphicsContext::new();
3046
3047        // Create ExtGState with specific values
3048        let mut state = ExtGState::new();
3049        state.alpha_fill = Some(0.5);
3050        state.alpha_stroke = Some(0.8);
3051        state.blend_mode = Some(BlendMode::Multiply);
3052
3053        let result = ctx.apply_extgstate(state);
3054        assert!(result.is_ok());
3055
3056        // Verify ExtGState was registered
3057        assert!(ctx.has_extgstates());
3058        assert_eq!(ctx.extgstate_manager().count(), 1);
3059
3060        // Apply different ExtGState
3061        let mut state2 = ExtGState::new();
3062        state2.alpha_fill = Some(0.3);
3063        ctx.apply_extgstate(state2).unwrap();
3064
3065        // Should have 2 different extgstates
3066        assert_eq!(ctx.extgstate_manager().count(), 2);
3067    }
3068
3069    #[test]
3070    fn test_with_extgstate() {
3071        let mut ctx = GraphicsContext::new();
3072        let result = ctx.with_extgstate(|mut state| {
3073            state.alpha_fill = Some(0.5);
3074            state.alpha_stroke = Some(0.8);
3075            state
3076        });
3077        assert!(result.is_ok());
3078    }
3079
3080    #[test]
3081    fn test_set_blend_mode() {
3082        let mut ctx = GraphicsContext::new();
3083
3084        // Test different blend modes
3085        let result = ctx.set_blend_mode(BlendMode::Multiply);
3086        assert!(result.is_ok());
3087        assert!(ctx.has_extgstates());
3088
3089        // Test that different blend modes create different extgstates
3090        ctx.clear();
3091        ctx.set_blend_mode(BlendMode::Screen).unwrap();
3092        ctx.rect(0.0, 0.0, 10.0, 10.0).fill();
3093        let ops = ctx.generate_operations().unwrap();
3094        let output = String::from_utf8_lossy(&ops);
3095
3096        // Should contain extgstate reference
3097        assert!(output.contains("/GS"));
3098        assert!(output.contains(" gs\n"));
3099    }
3100
3101    #[test]
3102    fn test_render_table() {
3103        let mut ctx = GraphicsContext::new();
3104        let table = Table::with_equal_columns(2, 200.0);
3105        let result = ctx.render_table(&table);
3106        assert!(result.is_ok());
3107    }
3108
3109    #[test]
3110    fn test_render_list() {
3111        let mut ctx = GraphicsContext::new();
3112        use crate::text::{OrderedList, OrderedListStyle};
3113        let ordered = OrderedList::new(OrderedListStyle::Decimal);
3114        let list = ListElement::Ordered(ordered);
3115        let result = ctx.render_list(&list);
3116        assert!(result.is_ok());
3117    }
3118
3119    #[test]
3120    fn test_render_column_layout() {
3121        let mut ctx = GraphicsContext::new();
3122        use crate::text::ColumnContent;
3123        let layout = ColumnLayout::new(2, 100.0, 200.0);
3124        let content = ColumnContent::new("Test content");
3125        let result = ctx.render_column_layout(&layout, &content, 50.0, 50.0, 400.0);
3126        assert!(result.is_ok());
3127    }
3128
3129    #[test]
3130    fn test_clip_ellipse() {
3131        let mut ctx = GraphicsContext::new();
3132
3133        // No clipping initially
3134        assert!(!ctx.has_clipping());
3135        assert!(ctx.clipping_path().is_none());
3136
3137        // Apply ellipse clipping
3138        let result = ctx.clip_ellipse(100.0, 100.0, 50.0, 30.0);
3139        assert!(result.is_ok());
3140        assert!(ctx.has_clipping());
3141        assert!(ctx.clipping_path().is_some());
3142
3143        // Verify clipping operations in PDF
3144        let ops = ctx.operations();
3145        assert!(ops.contains("W\n") || ops.contains("W*\n")); // Clipping operator
3146
3147        // Clear clipping
3148        ctx.clear_clipping();
3149        assert!(!ctx.has_clipping());
3150    }
3151
3152    #[test]
3153    fn test_clipping_path_access() {
3154        let mut ctx = GraphicsContext::new();
3155
3156        // No clipping initially
3157        assert!(ctx.clipping_path().is_none());
3158
3159        // Apply rect clipping
3160        ctx.clip_rect(10.0, 10.0, 50.0, 50.0).unwrap();
3161        assert!(ctx.clipping_path().is_some());
3162
3163        // Apply different clipping - should replace
3164        ctx.clip_circle(100.0, 100.0, 25.0).unwrap();
3165        assert!(ctx.clipping_path().is_some());
3166
3167        // Save/restore should preserve clipping
3168        ctx.save_state();
3169        ctx.clear_clipping();
3170        assert!(!ctx.has_clipping());
3171
3172        ctx.restore_state();
3173        // After restore, clipping should be back
3174        assert!(ctx.has_clipping());
3175    }
3176
3177    // ====== QUALITY TESTS: EDGE CASES ======
3178
3179    #[test]
3180    fn test_edge_case_move_to_negative() {
3181        let mut ctx = GraphicsContext::new();
3182        ctx.move_to(-100.5, -200.25);
3183        assert!(ctx.operations().contains("-100.50 -200.25 m\n"));
3184    }
3185
3186    #[test]
3187    fn test_edge_case_opacity_out_of_range() {
3188        let mut ctx = GraphicsContext::new();
3189
3190        // Above 1.0 - should clamp
3191        let _ = ctx.set_opacity(2.5);
3192        assert_eq!(ctx.fill_opacity(), 1.0);
3193
3194        // Below 0.0 - should clamp
3195        let _ = ctx.set_opacity(-0.5);
3196        assert_eq!(ctx.fill_opacity(), 0.0);
3197    }
3198
3199    #[test]
3200    fn test_edge_case_line_width_extremes() {
3201        let mut ctx = GraphicsContext::new();
3202
3203        ctx.set_line_width(0.0);
3204        assert_eq!(ctx.line_width(), 0.0);
3205
3206        ctx.set_line_width(10000.0);
3207        assert_eq!(ctx.line_width(), 10000.0);
3208    }
3209
3210    // ====== QUALITY TESTS: FEATURE INTERACTIONS ======
3211
3212    #[test]
3213    fn test_interaction_transparency_plus_clipping() {
3214        let mut ctx = GraphicsContext::new();
3215
3216        ctx.set_alpha(0.5).unwrap();
3217        ctx.clip_rect(10.0, 10.0, 100.0, 100.0).unwrap();
3218        ctx.rect(20.0, 20.0, 80.0, 80.0).fill();
3219
3220        let ops = ctx.generate_operations().unwrap();
3221        let output = String::from_utf8_lossy(&ops);
3222
3223        // Both features should be in PDF
3224        assert!(output.contains("W\n") || output.contains("W*\n"));
3225        assert!(output.contains("/GS"));
3226    }
3227
3228    #[test]
3229    fn test_interaction_extgstate_plus_text() {
3230        let mut ctx = GraphicsContext::new();
3231
3232        let mut state = ExtGState::new();
3233        state.alpha_fill = Some(0.7);
3234        ctx.apply_extgstate(state).unwrap();
3235
3236        ctx.set_font(Font::Helvetica, 14.0);
3237        ctx.draw_text("Test", 100.0, 200.0).unwrap();
3238
3239        let ops = ctx.generate_operations().unwrap();
3240        let output = String::from_utf8_lossy(&ops);
3241
3242        assert!(output.contains("/GS"));
3243        assert!(output.contains("BT\n"));
3244    }
3245
3246    #[test]
3247    fn test_interaction_chained_transformations() {
3248        let mut ctx = GraphicsContext::new();
3249
3250        ctx.translate(50.0, 100.0);
3251        ctx.rotate(45.0);
3252        ctx.scale(2.0, 2.0);
3253
3254        let ops = ctx.operations();
3255        assert_eq!(ops.matches("cm\n").count(), 3);
3256    }
3257
3258    // ====== QUALITY TESTS: END-TO-END ======
3259
3260    #[test]
3261    fn test_e2e_complete_page_with_header() {
3262        use crate::{Document, Page};
3263
3264        let mut doc = Document::new();
3265        let mut page = Page::a4();
3266        let ctx = page.graphics();
3267
3268        // Header
3269        ctx.save_state();
3270        let _ = ctx.set_fill_opacity(0.3);
3271        ctx.set_fill_color(Color::rgb(200.0, 200.0, 255.0));
3272        ctx.rect(0.0, 750.0, 595.0, 42.0).fill();
3273        ctx.restore_state();
3274
3275        // Content
3276        ctx.save_state();
3277        ctx.clip_rect(50.0, 50.0, 495.0, 692.0).unwrap();
3278        ctx.rect(60.0, 60.0, 100.0, 100.0).fill();
3279        ctx.restore_state();
3280
3281        let ops = ctx.generate_operations().unwrap();
3282        let output = String::from_utf8_lossy(&ops);
3283
3284        assert!(output.contains("q\n"));
3285        assert!(output.contains("Q\n"));
3286        assert!(output.contains("f\n"));
3287
3288        doc.add_page(page);
3289        assert!(doc.to_bytes().unwrap().len() > 0);
3290    }
3291
3292    #[test]
3293    fn test_e2e_watermark_workflow() {
3294        let mut ctx = GraphicsContext::new();
3295
3296        ctx.save_state();
3297        let _ = ctx.set_fill_opacity(0.2);
3298        ctx.translate(300.0, 400.0);
3299        ctx.rotate(45.0);
3300        ctx.set_font(Font::HelveticaBold, 72.0);
3301        ctx.draw_text("DRAFT", 0.0, 0.0).unwrap();
3302        ctx.restore_state();
3303
3304        let ops = ctx.generate_operations().unwrap();
3305        let output = String::from_utf8_lossy(&ops);
3306
3307        // Verify watermark structure
3308        assert!(output.contains("q\n")); // save state
3309        assert!(output.contains("Q\n")); // restore state
3310        assert!(output.contains("cm\n")); // transformations
3311        assert!(output.contains("BT\n")); // text begin
3312        assert!(output.contains("ET\n")); // text end
3313    }
3314}