xberg_native_pdf/elements/
mod.rs1mod image;
34mod path;
35mod table;
36mod text;
37
38pub use image::{ColorSpace, ImageContent, ImageFormat};
39pub(crate) use path::path_operations_bbox;
40pub use path::{LineCap, LineJoin, PathContent, PathOperation};
41pub use table::{
42 TableCellAlign, TableCellContent, TableCellVAlign, TableContent, TableContentStyle, TableDetectionInfo,
43 TableRowContent, TableSource,
44};
45pub use text::{FontSpec, FontStyle, TextContent, TextStyle};
46
47use crate::geometry::Rect;
48
49#[derive(Debug, Clone)]
54pub enum ContentElement {
55 Text(TextContent),
57 Image(ImageContent),
59 Path(PathContent),
61 Structure(StructureElement),
63 Table(TableContent),
65}
66
67impl ContentElement {
68 pub fn bbox(&self) -> Rect {
70 match self {
71 ContentElement::Text(t) => t.bbox,
72 ContentElement::Image(i) => i.bbox,
73 ContentElement::Path(p) => p.bbox,
74 ContentElement::Structure(s) => s.bbox,
75 ContentElement::Table(t) => t.bbox,
76 }
77 }
78
79 pub fn reading_order(&self) -> Option<usize> {
81 match self {
82 ContentElement::Text(t) => t.reading_order,
83 ContentElement::Image(i) => i.reading_order,
84 ContentElement::Path(p) => p.reading_order,
85 ContentElement::Structure(s) => s.reading_order,
86 ContentElement::Table(t) => t.reading_order,
87 }
88 }
89
90 pub fn is_text(&self) -> bool {
92 matches!(self, ContentElement::Text(_))
93 }
94
95 pub fn is_image(&self) -> bool {
97 matches!(self, ContentElement::Image(_))
98 }
99
100 pub fn is_path(&self) -> bool {
102 matches!(self, ContentElement::Path(_))
103 }
104
105 pub fn is_table(&self) -> bool {
107 matches!(self, ContentElement::Table(_))
108 }
109
110 pub fn as_text(&self) -> Option<&TextContent> {
112 match self {
113 ContentElement::Text(t) => Some(t),
114 _ => None,
115 }
116 }
117
118 pub fn as_image(&self) -> Option<&ImageContent> {
120 match self {
121 ContentElement::Image(i) => Some(i),
122 _ => None,
123 }
124 }
125
126 pub fn as_path(&self) -> Option<&PathContent> {
128 match self {
129 ContentElement::Path(p) => Some(p),
130 _ => None,
131 }
132 }
133
134 pub fn as_table(&self) -> Option<&TableContent> {
136 match self {
137 ContentElement::Table(t) => Some(t),
138 _ => None,
139 }
140 }
141}
142
143#[derive(Debug, Clone)]
148pub struct StructureElement {
149 pub structure_type: String,
151 pub bbox: Rect,
153 pub children: Vec<ContentElement>,
155 pub reading_order: Option<usize>,
157 pub alt_text: Option<String>,
159 pub language: Option<String>,
161}
162
163impl Default for StructureElement {
164 fn default() -> Self {
165 Self {
166 structure_type: String::new(),
167 bbox: Rect::new(0.0, 0.0, 0.0, 0.0),
168 children: Vec::new(),
169 reading_order: None,
170 alt_text: None,
171 language: None,
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_content_element_bbox() {
182 let text = TextContent {
183 artifact_type: None,
184 text: "Test".to_string(),
185 bbox: Rect::new(10.0, 20.0, 50.0, 12.0),
186 font: FontSpec::default(),
187 style: TextStyle::default(),
188 reading_order: Some(0),
189 origin: None,
190 rotation_degrees: None,
191 matrix: None,
192 };
193
194 let element = ContentElement::Text(text);
195 let bbox = element.bbox();
196
197 assert_eq!(bbox.x, 10.0);
198 assert_eq!(bbox.y, 20.0);
199 assert_eq!(bbox.width, 50.0);
200 assert_eq!(bbox.height, 12.0);
201 }
202
203 #[test]
204 fn test_content_element_type_checks() {
205 let text = ContentElement::Text(TextContent {
206 artifact_type: None,
207 text: "Test".to_string(),
208 bbox: Rect::new(0.0, 0.0, 10.0, 10.0),
209 font: FontSpec::default(),
210 style: TextStyle::default(),
211 reading_order: None,
212 origin: None,
213 rotation_degrees: None,
214 matrix: None,
215 });
216
217 assert!(text.is_text());
218 assert!(!text.is_image());
219 assert!(!text.is_path());
220 assert!(text.as_text().is_some());
221 assert!(text.as_image().is_none());
222 }
223
224 #[test]
225 fn test_reading_order() {
226 let text = ContentElement::Text(TextContent {
227 artifact_type: None,
228 text: "First".to_string(),
229 bbox: Rect::new(0.0, 0.0, 10.0, 10.0),
230 font: FontSpec::default(),
231 style: TextStyle::default(),
232 reading_order: Some(5),
233 origin: None,
234 rotation_degrees: None,
235 matrix: None,
236 });
237
238 assert_eq!(text.reading_order(), Some(5));
239 }
240}