oxidize_pdf/text/
flow.rs

1use crate::text::{Font, measure_text, split_into_words};
2use crate::page::Margins;
3use crate::error::Result;
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    page_height: f64,
24    margins: Margins,
25}
26
27impl TextFlowContext {
28    pub fn new(page_width: f64, page_height: f64, margins: Margins) -> Self {
29        Self {
30            operations: String::new(),
31            current_font: Font::Helvetica,
32            font_size: 12.0,
33            line_height: 1.2,
34            cursor_x: margins.left,
35            cursor_y: page_height - margins.top,
36            alignment: TextAlign::Left,
37            page_width,
38            page_height,
39            margins,
40        }
41    }
42    
43    pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
44        self.current_font = font;
45        self.font_size = size;
46        self
47    }
48    
49    pub fn set_line_height(&mut self, multiplier: f64) -> &mut Self {
50        self.line_height = multiplier;
51        self
52    }
53    
54    pub fn set_alignment(&mut self, alignment: TextAlign) -> &mut Self {
55        self.alignment = alignment;
56        self
57    }
58    
59    pub fn at(&mut self, x: f64, y: f64) -> &mut Self {
60        self.cursor_x = x;
61        self.cursor_y = y;
62        self
63    }
64    
65    pub fn content_width(&self) -> f64 {
66        self.page_width - self.margins.left - self.margins.right
67    }
68    
69    pub fn write_wrapped(&mut self, text: &str) -> Result<&mut Self> {
70        let content_width = self.content_width();
71        
72        // Split text into words
73        let words = split_into_words(text);
74        let mut lines: Vec<Vec<&str>> = Vec::new();
75        let mut current_line: Vec<&str> = Vec::new();
76        let mut current_width = 0.0;
77        
78        // Build lines based on width constraints
79        for word in words {
80            let word_width = measure_text(word, self.current_font, self.font_size);
81            
82            // Check if we need to start a new line
83            if !current_line.is_empty() && current_width + word_width > content_width {
84                lines.push(current_line);
85                current_line = vec![word];
86                current_width = word_width;
87            } else {
88                current_line.push(word);
89                current_width += word_width;
90            }
91        }
92        
93        if !current_line.is_empty() {
94            lines.push(current_line);
95        }
96        
97        // Render each line
98        for (i, line) in lines.iter().enumerate() {
99            let line_text = line.join("");
100            let line_width = measure_text(&line_text, self.current_font, self.font_size);
101            
102            // Calculate x position based on alignment
103            let x = match self.alignment {
104                TextAlign::Left => self.margins.left,
105                TextAlign::Right => self.page_width - self.margins.right - line_width,
106                TextAlign::Center => self.margins.left + (content_width - line_width) / 2.0,
107                TextAlign::Justified => {
108                    if i < lines.len() - 1 && line.len() > 1 {
109                        // We'll handle justification below
110                        self.margins.left
111                    } else {
112                        self.margins.left
113                    }
114                }
115            };
116            
117            // Begin text object
118            self.operations.push_str("BT\n");
119            
120            // Set font
121            write!(&mut self.operations, "/{} {} Tf\n", 
122                   self.current_font.pdf_name(), self.font_size).unwrap();
123            
124            // Set text position
125            write!(&mut self.operations, "{:.2} {:.2} Td\n", 
126                   x, self.cursor_y).unwrap();
127            
128            // Handle justification
129            if self.alignment == TextAlign::Justified && i < lines.len() - 1 && line.len() > 1 {
130                // Calculate extra space to distribute
131                let spaces_count = line.iter().filter(|w| w.trim().is_empty()).count();
132                if spaces_count > 0 {
133                    let extra_space = content_width - line_width;
134                    let space_adjustment = extra_space / spaces_count as f64;
135                    
136                    // Set word spacing
137                    write!(&mut self.operations, "{:.2} Tw\n", space_adjustment).unwrap();
138                }
139            }
140            
141            // Show text
142            self.operations.push('(');
143            for ch in line_text.chars() {
144                match ch {
145                    '(' => self.operations.push_str("\\("),
146                    ')' => self.operations.push_str("\\)"),
147                    '\\' => self.operations.push_str("\\\\"),
148                    '\n' => self.operations.push_str("\\n"),
149                    '\r' => self.operations.push_str("\\r"),
150                    '\t' => self.operations.push_str("\\t"),
151                    _ => self.operations.push(ch),
152                }
153            }
154            self.operations.push_str(") Tj\n");
155            
156            // Reset word spacing if it was set
157            if self.alignment == TextAlign::Justified && i < lines.len() - 1 {
158                self.operations.push_str("0 Tw\n");
159            }
160            
161            // End text object
162            self.operations.push_str("ET\n");
163            
164            // Move cursor down for next line
165            self.cursor_y -= self.font_size * self.line_height;
166        }
167        
168        Ok(self)
169    }
170    
171    pub fn write_paragraph(&mut self, text: &str) -> Result<&mut Self> {
172        self.write_wrapped(text)?;
173        // Add extra space after paragraph
174        self.cursor_y -= self.font_size * self.line_height * 0.5;
175        Ok(self)
176    }
177    
178    pub fn newline(&mut self) -> &mut Self {
179        self.cursor_y -= self.font_size * self.line_height;
180        self.cursor_x = self.margins.left;
181        self
182    }
183    
184    pub fn cursor_position(&self) -> (f64, f64) {
185        (self.cursor_x, self.cursor_y)
186    }
187    
188    pub fn generate_operations(&self) -> Vec<u8> {
189        self.operations.as_bytes().to_vec()
190    }
191}