oxidize_pdf/text/
flow.rs

1use crate::error::Result;
2use crate::page::Margins;
3use crate::text::{measure_text, split_into_words, Font};
4use std::fmt::Write;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum TextAlign {
8    Left,
9    Right,
10    Center,
11    Justified,
12}
13
14pub struct TextFlowContext {
15    operations: String,
16    current_font: Font,
17    font_size: f64,
18    line_height: f64,
19    cursor_x: f64,
20    cursor_y: f64,
21    alignment: TextAlign,
22    page_width: f64,
23    #[allow(dead_code)]
24    page_height: f64,
25    margins: Margins,
26}
27
28impl TextFlowContext {
29    pub fn new(page_width: f64, page_height: f64, margins: Margins) -> Self {
30        Self {
31            operations: String::new(),
32            current_font: Font::Helvetica,
33            font_size: 12.0,
34            line_height: 1.2,
35            cursor_x: margins.left,
36            cursor_y: page_height - margins.top,
37            alignment: TextAlign::Left,
38            page_width,
39            page_height,
40            margins,
41        }
42    }
43
44    pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
45        self.current_font = font;
46        self.font_size = size;
47        self
48    }
49
50    pub fn set_line_height(&mut self, multiplier: f64) -> &mut Self {
51        self.line_height = multiplier;
52        self
53    }
54
55    pub fn set_alignment(&mut self, alignment: TextAlign) -> &mut Self {
56        self.alignment = alignment;
57        self
58    }
59
60    pub fn at(&mut self, x: f64, y: f64) -> &mut Self {
61        self.cursor_x = x;
62        self.cursor_y = y;
63        self
64    }
65
66    pub fn content_width(&self) -> f64 {
67        self.page_width - self.margins.left - self.margins.right
68    }
69
70    pub fn write_wrapped(&mut self, text: &str) -> Result<&mut Self> {
71        let content_width = self.content_width();
72
73        // Split text into words
74        let words = split_into_words(text);
75        let mut lines: Vec<Vec<&str>> = Vec::new();
76        let mut current_line: Vec<&str> = Vec::new();
77        let mut current_width = 0.0;
78
79        // Build lines based on width constraints
80        for word in words {
81            let word_width = measure_text(word, self.current_font.clone(), self.font_size);
82
83            // Check if we need to start a new line
84            if !current_line.is_empty() && current_width + word_width > content_width {
85                lines.push(current_line);
86                current_line = vec![word];
87                current_width = word_width;
88            } else {
89                current_line.push(word);
90                current_width += word_width;
91            }
92        }
93
94        if !current_line.is_empty() {
95            lines.push(current_line);
96        }
97
98        // Render each line
99        for (i, line) in lines.iter().enumerate() {
100            let line_text = line.join("");
101            let line_width = measure_text(&line_text, self.current_font.clone(), self.font_size);
102
103            // Calculate x position based on alignment
104            let x = match self.alignment {
105                TextAlign::Left => self.margins.left,
106                TextAlign::Right => self.page_width - self.margins.right - line_width,
107                TextAlign::Center => self.margins.left + (content_width - line_width) / 2.0,
108                TextAlign::Justified => {
109                    if i < lines.len() - 1 && line.len() > 1 {
110                        // We'll handle justification below
111                        self.margins.left
112                    } else {
113                        self.margins.left
114                    }
115                }
116            };
117
118            // Begin text object
119            self.operations.push_str("BT\n");
120
121            // Set font
122            writeln!(
123                &mut self.operations,
124                "/{} {} Tf",
125                self.current_font.pdf_name(),
126                self.font_size
127            )
128            .unwrap();
129
130            // Set text position
131            writeln!(&mut self.operations, "{:.2} {:.2} Td", x, self.cursor_y).unwrap();
132
133            // Handle justification
134            if self.alignment == TextAlign::Justified && i < lines.len() - 1 && line.len() > 1 {
135                // Calculate extra space to distribute
136                let spaces_count = line.iter().filter(|w| w.trim().is_empty()).count();
137                if spaces_count > 0 {
138                    let extra_space = content_width - line_width;
139                    let space_adjustment = extra_space / spaces_count as f64;
140
141                    // Set word spacing
142                    writeln!(&mut self.operations, "{space_adjustment:.2} Tw").unwrap();
143                }
144            }
145
146            // Show text
147            self.operations.push('(');
148            for ch in line_text.chars() {
149                match ch {
150                    '(' => self.operations.push_str("\\("),
151                    ')' => self.operations.push_str("\\)"),
152                    '\\' => self.operations.push_str("\\\\"),
153                    '\n' => self.operations.push_str("\\n"),
154                    '\r' => self.operations.push_str("\\r"),
155                    '\t' => self.operations.push_str("\\t"),
156                    _ => self.operations.push(ch),
157                }
158            }
159            self.operations.push_str(") Tj\n");
160
161            // Reset word spacing if it was set
162            if self.alignment == TextAlign::Justified && i < lines.len() - 1 {
163                self.operations.push_str("0 Tw\n");
164            }
165
166            // End text object
167            self.operations.push_str("ET\n");
168
169            // Move cursor down for next line
170            self.cursor_y -= self.font_size * self.line_height;
171        }
172
173        Ok(self)
174    }
175
176    pub fn write_paragraph(&mut self, text: &str) -> Result<&mut Self> {
177        self.write_wrapped(text)?;
178        // Add extra space after paragraph
179        self.cursor_y -= self.font_size * self.line_height * 0.5;
180        Ok(self)
181    }
182
183    pub fn newline(&mut self) -> &mut Self {
184        self.cursor_y -= self.font_size * self.line_height;
185        self.cursor_x = self.margins.left;
186        self
187    }
188
189    pub fn cursor_position(&self) -> (f64, f64) {
190        (self.cursor_x, self.cursor_y)
191    }
192
193    pub fn generate_operations(&self) -> Vec<u8> {
194        self.operations.as_bytes().to_vec()
195    }
196
197    /// Get the current alignment
198    pub fn alignment(&self) -> TextAlign {
199        self.alignment
200    }
201
202    /// Get the page dimensions
203    pub fn page_dimensions(&self) -> (f64, f64) {
204        (self.page_width, self.page_height)
205    }
206
207    /// Get the margins
208    pub fn margins(&self) -> &Margins {
209        &self.margins
210    }
211
212    /// Get current line height multiplier
213    pub fn line_height(&self) -> f64 {
214        self.line_height
215    }
216
217    /// Get the operations string
218    pub fn operations(&self) -> &str {
219        &self.operations
220    }
221
222    /// Clear all operations
223    pub fn clear(&mut self) {
224        self.operations.clear();
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231    use crate::page::Margins;
232
233    fn create_test_margins() -> Margins {
234        Margins {
235            left: 50.0,
236            right: 50.0,
237            top: 50.0,
238            bottom: 50.0,
239        }
240    }
241
242    #[test]
243    fn test_text_flow_context_new() {
244        let margins = create_test_margins();
245        let context = TextFlowContext::new(400.0, 600.0, margins.clone());
246
247        assert_eq!(context.current_font, Font::Helvetica);
248        assert_eq!(context.font_size, 12.0);
249        assert_eq!(context.line_height, 1.2);
250        assert_eq!(context.alignment, TextAlign::Left);
251        assert_eq!(context.page_width, 400.0);
252        assert_eq!(context.page_height, 600.0);
253        assert_eq!(context.cursor_x, 50.0); // margins.left
254        assert_eq!(context.cursor_y, 550.0); // page_height - margins.top
255    }
256
257    #[test]
258    fn test_set_font() {
259        let margins = create_test_margins();
260        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
261
262        context.set_font(Font::TimesBold, 16.0);
263        assert_eq!(context.current_font, Font::TimesBold);
264        assert_eq!(context.font_size, 16.0);
265    }
266
267    #[test]
268    fn test_set_line_height() {
269        let margins = create_test_margins();
270        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
271
272        context.set_line_height(1.5);
273        assert_eq!(context.line_height(), 1.5);
274    }
275
276    #[test]
277    fn test_set_alignment() {
278        let margins = create_test_margins();
279        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
280
281        context.set_alignment(TextAlign::Center);
282        assert_eq!(context.alignment(), TextAlign::Center);
283    }
284
285    #[test]
286    fn test_at_position() {
287        let margins = create_test_margins();
288        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
289
290        context.at(100.0, 200.0);
291        let (x, y) = context.cursor_position();
292        assert_eq!(x, 100.0);
293        assert_eq!(y, 200.0);
294    }
295
296    #[test]
297    fn test_content_width() {
298        let margins = create_test_margins();
299        let context = TextFlowContext::new(400.0, 600.0, margins.clone());
300
301        let content_width = context.content_width();
302        assert_eq!(content_width, 300.0); // 400 - 50 - 50
303    }
304
305    #[test]
306    fn test_text_align_variants() {
307        assert_eq!(TextAlign::Left, TextAlign::Left);
308        assert_eq!(TextAlign::Right, TextAlign::Right);
309        assert_eq!(TextAlign::Center, TextAlign::Center);
310        assert_eq!(TextAlign::Justified, TextAlign::Justified);
311
312        assert_ne!(TextAlign::Left, TextAlign::Right);
313    }
314
315    #[test]
316    fn test_write_wrapped_simple() {
317        let margins = create_test_margins();
318        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
319
320        context.write_wrapped("Hello World").unwrap();
321
322        let ops = context.operations();
323        assert!(ops.contains("BT\n"));
324        assert!(ops.contains("ET\n"));
325        assert!(ops.contains("/Helvetica 12 Tf"));
326        assert!(ops.contains("(Hello World) Tj"));
327    }
328
329    #[test]
330    fn test_write_paragraph() {
331        let margins = create_test_margins();
332        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
333
334        let initial_y = context.cursor_y;
335        context.write_paragraph("Test paragraph").unwrap();
336
337        // Y position should have moved down more than just line height
338        assert!(context.cursor_y < initial_y);
339    }
340
341    #[test]
342    fn test_newline() {
343        let margins = create_test_margins();
344        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
345
346        let initial_y = context.cursor_y;
347        context.newline();
348
349        assert_eq!(context.cursor_x, margins.left);
350        assert!(context.cursor_y < initial_y);
351        assert_eq!(
352            context.cursor_y,
353            initial_y - context.font_size * context.line_height
354        );
355    }
356
357    #[test]
358    fn test_cursor_position() {
359        let margins = create_test_margins();
360        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
361
362        context.at(75.0, 125.0);
363        let (x, y) = context.cursor_position();
364        assert_eq!(x, 75.0);
365        assert_eq!(y, 125.0);
366    }
367
368    #[test]
369    fn test_generate_operations() {
370        let margins = create_test_margins();
371        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
372
373        context.write_wrapped("Test").unwrap();
374        let ops_bytes = context.generate_operations();
375        let ops_string = String::from_utf8(ops_bytes).unwrap();
376
377        assert_eq!(ops_string, context.operations());
378    }
379
380    #[test]
381    fn test_clear_operations() {
382        let margins = create_test_margins();
383        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
384
385        context.write_wrapped("Test").unwrap();
386        assert!(!context.operations().is_empty());
387
388        context.clear();
389        assert!(context.operations().is_empty());
390    }
391
392    #[test]
393    fn test_page_dimensions() {
394        let margins = create_test_margins();
395        let context = TextFlowContext::new(400.0, 600.0, margins.clone());
396
397        let (width, height) = context.page_dimensions();
398        assert_eq!(width, 400.0);
399        assert_eq!(height, 600.0);
400    }
401
402    #[test]
403    fn test_margins_access() {
404        let margins = create_test_margins();
405        let context = TextFlowContext::new(400.0, 600.0, margins.clone());
406
407        let ctx_margins = context.margins();
408        assert_eq!(ctx_margins.left, 50.0);
409        assert_eq!(ctx_margins.right, 50.0);
410        assert_eq!(ctx_margins.top, 50.0);
411        assert_eq!(ctx_margins.bottom, 50.0);
412    }
413
414    #[test]
415    fn test_method_chaining() {
416        let margins = create_test_margins();
417        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
418
419        context
420            .set_font(Font::Courier, 10.0)
421            .set_line_height(1.5)
422            .set_alignment(TextAlign::Center)
423            .at(100.0, 200.0);
424
425        assert_eq!(context.current_font, Font::Courier);
426        assert_eq!(context.font_size, 10.0);
427        assert_eq!(context.line_height(), 1.5);
428        assert_eq!(context.alignment(), TextAlign::Center);
429        let (x, y) = context.cursor_position();
430        assert_eq!(x, 100.0);
431        assert_eq!(y, 200.0);
432    }
433
434    #[test]
435    fn test_text_align_debug() {
436        let align = TextAlign::Center;
437        let debug_str = format!("{:?}", align);
438        assert_eq!(debug_str, "Center");
439    }
440
441    #[test]
442    fn test_text_align_clone() {
443        let align1 = TextAlign::Justified;
444        let align2 = align1;
445        assert_eq!(align1, align2);
446    }
447
448    #[test]
449    fn test_text_align_copy() {
450        let align1 = TextAlign::Right;
451        let align2 = align1; // Copy semantics
452        assert_eq!(align1, align2);
453
454        // Both variables should still be usable
455        assert_eq!(align1, TextAlign::Right);
456        assert_eq!(align2, TextAlign::Right);
457    }
458
459    #[test]
460    fn test_write_wrapped_with_alignment_right() {
461        let margins = create_test_margins();
462        let mut context = TextFlowContext::new(400.0, 600.0, margins);
463
464        context.set_alignment(TextAlign::Right);
465        context.write_wrapped("Right aligned text").unwrap();
466
467        let ops = context.operations();
468        assert!(ops.contains("BT\n"));
469        assert!(ops.contains("ET\n"));
470        // Right alignment should position text differently
471        assert!(ops.contains("Td"));
472    }
473
474    #[test]
475    fn test_write_wrapped_with_alignment_center() {
476        let margins = create_test_margins();
477        let mut context = TextFlowContext::new(400.0, 600.0, margins);
478
479        context.set_alignment(TextAlign::Center);
480        context.write_wrapped("Centered text").unwrap();
481
482        let ops = context.operations();
483        assert!(ops.contains("BT\n"));
484        assert!(ops.contains("(Centered text) Tj"));
485    }
486
487    #[test]
488    fn test_write_wrapped_with_alignment_justified() {
489        let margins = create_test_margins();
490        let mut context = TextFlowContext::new(400.0, 600.0, margins);
491
492        context.set_alignment(TextAlign::Justified);
493        // Long text that will wrap and justify
494        context.write_wrapped("This is a longer text that should wrap across multiple lines to test justification").unwrap();
495
496        let ops = context.operations();
497        assert!(ops.contains("BT\n"));
498        // Justified text may have word spacing adjustments
499        assert!(ops.contains("Tw") || ops.contains("0 Tw"));
500    }
501
502    #[test]
503    fn test_write_wrapped_empty_text() {
504        let margins = create_test_margins();
505        let mut context = TextFlowContext::new(400.0, 600.0, margins);
506
507        context.write_wrapped("").unwrap();
508
509        // Empty text should not generate operations
510        assert!(context.operations().is_empty());
511    }
512
513    #[test]
514    fn test_write_wrapped_whitespace_only() {
515        let margins = create_test_margins();
516        let mut context = TextFlowContext::new(400.0, 600.0, margins);
517
518        context.write_wrapped("   ").unwrap();
519
520        let ops = context.operations();
521        // Should handle whitespace-only text
522        assert!(ops.contains("BT\n") || ops.is_empty());
523    }
524
525    #[test]
526    fn test_write_wrapped_special_characters() {
527        let margins = create_test_margins();
528        let mut context = TextFlowContext::new(400.0, 600.0, margins);
529
530        context
531            .write_wrapped("Text with (parentheses) and \\backslash\\")
532            .unwrap();
533
534        let ops = context.operations();
535        // Special characters should be escaped
536        assert!(ops.contains("\\(parentheses\\)"));
537        assert!(ops.contains("\\\\backslash\\\\"));
538    }
539
540    #[test]
541    fn test_write_wrapped_newlines_tabs() {
542        let margins = create_test_margins();
543        let mut context = TextFlowContext::new(400.0, 600.0, margins);
544
545        context.write_wrapped("Line1\nLine2\tTabbed").unwrap();
546
547        let ops = context.operations();
548        // Newlines and tabs should be escaped
549        assert!(ops.contains("\\n") || ops.contains("\\t"));
550    }
551
552    #[test]
553    fn test_write_wrapped_very_long_word() {
554        let margins = create_test_margins();
555        let mut context = TextFlowContext::new(200.0, 600.0, margins); // Narrow page
556
557        let long_word = "a".repeat(100);
558        context.write_wrapped(&long_word).unwrap();
559
560        let ops = context.operations();
561        assert!(ops.contains("BT\n"));
562        assert!(ops.contains(&long_word));
563    }
564
565    #[test]
566    fn test_write_wrapped_cursor_movement() {
567        let margins = create_test_margins();
568        let mut context = TextFlowContext::new(400.0, 600.0, margins);
569
570        let initial_y = context.cursor_y;
571
572        context.write_wrapped("Line 1").unwrap();
573        let y_after_line1 = context.cursor_y;
574
575        context.write_wrapped("Line 2").unwrap();
576        let y_after_line2 = context.cursor_y;
577
578        // Cursor should move down after each line
579        assert!(y_after_line1 < initial_y);
580        assert!(y_after_line2 < y_after_line1);
581    }
582
583    #[test]
584    fn test_write_paragraph_spacing() {
585        let margins = create_test_margins();
586        let mut context = TextFlowContext::new(400.0, 600.0, margins);
587
588        let initial_y = context.cursor_y;
589        context.write_paragraph("Paragraph 1").unwrap();
590        let y_after_p1 = context.cursor_y;
591
592        context.write_paragraph("Paragraph 2").unwrap();
593        let y_after_p2 = context.cursor_y;
594
595        // Paragraphs should have extra spacing
596        let spacing1 = initial_y - y_after_p1;
597        let spacing2 = y_after_p1 - y_after_p2;
598
599        assert!(spacing1 > 0.0);
600        assert!(spacing2 > 0.0);
601    }
602
603    #[test]
604    fn test_multiple_newlines() {
605        let margins = create_test_margins();
606        let mut context = TextFlowContext::new(400.0, 600.0, margins);
607
608        let initial_y = context.cursor_y;
609
610        context.newline();
611        let y1 = context.cursor_y;
612
613        context.newline();
614        let y2 = context.cursor_y;
615
616        context.newline();
617        let y3 = context.cursor_y;
618
619        // Each newline should move cursor down by same amount
620        let spacing1 = initial_y - y1;
621        let spacing2 = y1 - y2;
622        let spacing3 = y2 - y3;
623
624        // Use approximate equality for floating point comparisons
625        assert!((spacing1 - spacing2).abs() < 1e-10);
626        assert!((spacing2 - spacing3).abs() < 1e-10);
627        assert!((spacing1 - context.font_size * context.line_height).abs() < 1e-10);
628    }
629
630    #[test]
631    fn test_content_width_different_margins() {
632        let margins = Margins {
633            left: 30.0,
634            right: 70.0,
635            top: 40.0,
636            bottom: 60.0,
637        };
638        let context = TextFlowContext::new(500.0, 700.0, margins);
639
640        let content_width = context.content_width();
641        assert_eq!(content_width, 400.0); // 500 - 30 - 70
642    }
643
644    #[test]
645    fn test_custom_line_height() {
646        let margins = create_test_margins();
647        let mut context = TextFlowContext::new(400.0, 600.0, margins);
648
649        context.set_line_height(2.0);
650
651        let initial_y = context.cursor_y;
652        context.newline();
653        let y_after = context.cursor_y;
654
655        let spacing = initial_y - y_after;
656        assert_eq!(spacing, context.font_size * 2.0); // line_height = 2.0
657    }
658
659    #[test]
660    fn test_different_fonts() {
661        let margins = create_test_margins();
662        let mut context = TextFlowContext::new(400.0, 600.0, margins);
663
664        let fonts = vec![
665            Font::Helvetica,
666            Font::HelveticaBold,
667            Font::TimesRoman,
668            Font::TimesBold,
669            Font::Courier,
670            Font::CourierBold,
671        ];
672
673        for font in fonts {
674            context.clear();
675            let font_name = font.pdf_name();
676            context.set_font(font, 14.0);
677            context.write_wrapped("Test text").unwrap();
678
679            let ops = context.operations();
680            assert!(ops.contains(&format!("/{} 14 Tf", font_name)));
681        }
682    }
683
684    #[test]
685    fn test_font_size_variations() {
686        let margins = create_test_margins();
687        let mut context = TextFlowContext::new(400.0, 600.0, margins);
688
689        let sizes = vec![8.0, 10.0, 12.0, 14.0, 16.0, 24.0, 36.0];
690
691        for size in sizes {
692            context.clear();
693            context.set_font(Font::Helvetica, size);
694            context.write_wrapped("Test").unwrap();
695
696            let ops = context.operations();
697            assert!(ops.contains(&format!("/Helvetica {} Tf", size)));
698        }
699    }
700
701    #[test]
702    fn test_at_position_edge_cases() {
703        let margins = create_test_margins();
704        let mut context = TextFlowContext::new(400.0, 600.0, margins);
705
706        // Test zero position
707        context.at(0.0, 0.0);
708        assert_eq!(context.cursor_position(), (0.0, 0.0));
709
710        // Test negative position
711        context.at(-10.0, -20.0);
712        assert_eq!(context.cursor_position(), (-10.0, -20.0));
713
714        // Test large position
715        context.at(10000.0, 20000.0);
716        assert_eq!(context.cursor_position(), (10000.0, 20000.0));
717    }
718
719    #[test]
720    fn test_write_wrapped_with_narrow_content() {
721        let margins = Margins {
722            left: 190.0,
723            right: 190.0,
724            top: 50.0,
725            bottom: 50.0,
726        };
727        let mut context = TextFlowContext::new(400.0, 600.0, margins);
728
729        // Content width is only 20.0 units
730        context
731            .write_wrapped("This text should wrap a lot")
732            .unwrap();
733
734        let ops = context.operations();
735        // Should have multiple text objects for wrapped lines
736        let bt_count = ops.matches("BT\n").count();
737        assert!(bt_count > 1);
738    }
739
740    #[test]
741    fn test_justified_text_single_word_line() {
742        let margins = create_test_margins();
743        let mut context = TextFlowContext::new(400.0, 600.0, margins);
744
745        context.set_alignment(TextAlign::Justified);
746        context.write_wrapped("SingleWord").unwrap();
747
748        let ops = context.operations();
749        // Single word lines should not have word spacing
750        assert!(!ops.contains(" Tw") || ops.contains("0 Tw"));
751    }
752
753    #[test]
754    fn test_justified_text_last_line() {
755        let margins = create_test_margins();
756        let mut context = TextFlowContext::new(400.0, 600.0, margins);
757
758        context.set_alignment(TextAlign::Justified);
759        // Text that will create multiple lines
760        context.write_wrapped("This is a test of justified text alignment where the last line should not be justified").unwrap();
761
762        let ops = context.operations();
763        // Should reset word spacing (0 Tw) for last line
764        assert!(ops.contains("0 Tw"));
765    }
766
767    #[test]
768    fn test_generate_operations_encoding() {
769        let margins = create_test_margins();
770        let mut context = TextFlowContext::new(400.0, 600.0, margins);
771
772        context.write_wrapped("UTF-8 Text: Ñ").unwrap();
773
774        let ops_bytes = context.generate_operations();
775        let ops_string = String::from_utf8(ops_bytes.clone()).unwrap();
776
777        assert_eq!(ops_bytes, context.operations().as_bytes());
778        assert_eq!(ops_string, context.operations());
779    }
780
781    #[test]
782    fn test_clear_resets_operations_only() {
783        let margins = create_test_margins();
784        let mut context = TextFlowContext::new(400.0, 600.0, margins);
785
786        context.set_font(Font::TimesBold, 18.0);
787        context.set_alignment(TextAlign::Right);
788        context.at(100.0, 200.0);
789        context.write_wrapped("Text").unwrap();
790
791        context.clear();
792
793        // Operations should be cleared
794        assert!(context.operations().is_empty());
795
796        // But other settings should remain
797        assert_eq!(context.current_font, Font::TimesBold);
798        assert_eq!(context.font_size, 18.0);
799        assert_eq!(context.alignment(), TextAlign::Right);
800        // Cursor position should reflect where we are after writing text (moved down by line height)
801        let (x, y) = context.cursor_position();
802        assert_eq!(x, 100.0); // X position should be unchanged
803        assert!(y < 200.0); // Y position should have moved down after writing text
804    }
805
806    #[test]
807    fn test_long_text_wrapping() {
808        let margins = create_test_margins();
809        let mut context = TextFlowContext::new(400.0, 600.0, margins);
810
811        let long_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
812                        Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \
813                        Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.";
814
815        context.write_wrapped(long_text).unwrap();
816
817        let ops = context.operations();
818        // Should have multiple lines
819        let tj_count = ops.matches(") Tj").count();
820        assert!(tj_count > 1);
821    }
822
823    #[test]
824    fn test_empty_operations_initially() {
825        let margins = create_test_margins();
826        let context = TextFlowContext::new(400.0, 600.0, margins);
827
828        assert!(context.operations().is_empty());
829        assert_eq!(context.generate_operations().len(), 0);
830    }
831
832    #[test]
833    fn test_write_paragraph_empty() {
834        let margins = create_test_margins();
835        let mut context = TextFlowContext::new(400.0, 600.0, margins);
836
837        let initial_y = context.cursor_y;
838        context.write_paragraph("").unwrap();
839
840        // Empty paragraph should still add spacing
841        assert!(context.cursor_y < initial_y);
842    }
843
844    #[test]
845    fn test_extreme_line_height() {
846        let margins = create_test_margins();
847        let mut context = TextFlowContext::new(400.0, 600.0, margins);
848
849        // Very small line height
850        context.set_line_height(0.1);
851        let initial_y = context.cursor_y;
852        context.newline();
853        assert_eq!(context.cursor_y, initial_y - context.font_size * 0.1);
854
855        // Very large line height
856        context.set_line_height(10.0);
857        let initial_y2 = context.cursor_y;
858        context.newline();
859        assert_eq!(context.cursor_y, initial_y2 - context.font_size * 10.0);
860    }
861
862    #[test]
863    fn test_zero_content_width() {
864        let margins = Margins {
865            left: 200.0,
866            right: 200.0,
867            top: 50.0,
868            bottom: 50.0,
869        };
870        let context = TextFlowContext::new(400.0, 600.0, margins);
871
872        assert_eq!(context.content_width(), 0.0);
873    }
874
875    #[test]
876    fn test_cursor_x_reset_on_newline() {
877        let margins = create_test_margins();
878        let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
879
880        context.at(250.0, 300.0); // Move cursor to custom position
881        context.newline();
882
883        // X should reset to left margin
884        assert_eq!(context.cursor_x, margins.left);
885        // Y should decrease by line height
886        assert_eq!(
887            context.cursor_y,
888            300.0 - context.font_size * context.line_height
889        );
890    }
891}