Skip to main content

easyofd_core/page_obj/
ct_page_block.rs

1//! CT_PageBlock 页面块容器。
2
3/// 对应 Java: org.ofdrw.core.basicStructure.pageObj.layer.block.CT_PageBlock
4///
5/// 页面块容器,可以嵌套。用于组织页面内容,支持包含文本对象、
6/// 图像对象、路径对象以及嵌套的页面块。
7/// 对应 GB/T 33190-2016 第 7.7 节图 17 表 16。
8#[allow(non_camel_case_types)]
9#[derive(Debug, Clone)]
10pub struct CT_PageBlock {
11    /// 嵌套的页面块列表。
12    pub page_blocks: Vec<CT_PageBlock>,
13    /// 文本对象列表。
14    pub text_objects: Vec<PageBlockTextObject>,
15    /// 路径对象列表。
16    pub path_objects: Vec<PageBlockPathObject>,
17    /// 图像对象列表。
18    pub image_objects: Vec<PageBlockImageObject>,
19}
20
21/// 页面块中的文本对象(简化表示)。
22#[derive(Debug, Clone)]
23pub struct PageBlockTextObject {
24    /// 对象 ID。
25    pub id: u32,
26    /// 边界框 "x y width height"。
27    pub boundary: String,
28    /// 文本内容。
29    pub content: String,
30    /// 字号(pt)。
31    pub font_size: f64,
32}
33
34/// 页面块中的路径对象(简化表示)。
35#[derive(Debug, Clone)]
36pub struct PageBlockPathObject {
37    /// 对象 ID。
38    pub id: u32,
39    /// 边界框 "x y width height"。
40    pub boundary: String,
41    /// 缩略路径数据。
42    pub abbreviated_data: String,
43}
44
45/// 页面块中的图像对象(简化表示)。
46#[derive(Debug, Clone)]
47pub struct PageBlockImageObject {
48    /// 对象 ID。
49    pub id: u32,
50    /// 边界框 "x y width height"。
51    pub boundary: String,
52    /// 图像资源引用 ID。
53    pub resource_id: u32,
54}
55
56impl CT_PageBlock {
57    /// 创建空的页面块。
58    #[must_use]
59    pub fn new() -> Self {
60        Self {
61            page_blocks: Vec::new(),
62            text_objects: Vec::new(),
63            path_objects: Vec::new(),
64            image_objects: Vec::new(),
65        }
66    }
67
68    /// 添加嵌套页面块。
69    pub fn add_page_block(&mut self, block: CT_PageBlock) {
70        self.page_blocks.push(block);
71    }
72
73    /// 添加文本对象。
74    pub fn add_text_object(&mut self, obj: PageBlockTextObject) {
75        self.text_objects.push(obj);
76    }
77
78    /// 添加路径对象。
79    pub fn add_path_object(&mut self, obj: PageBlockPathObject) {
80        self.path_objects.push(obj);
81    }
82
83    /// 添加图像对象。
84    pub fn add_image_object(&mut self, obj: PageBlockImageObject) {
85        self.image_objects.push(obj);
86    }
87
88    /// 获取所有嵌套页面块。
89    #[must_use]
90    pub fn get_page_blocks(&self) -> &[CT_PageBlock] {
91        &self.page_blocks
92    }
93
94    /// 子元素总数(递归统计)。
95    #[must_use]
96    pub fn total_count(&self) -> usize {
97        let direct = self.text_objects.len() + self.path_objects.len() + self.image_objects.len();
98        let nested: usize = self.page_blocks.iter().map(|b| b.total_count()).sum();
99        direct + nested
100    }
101
102    /// 序列化为 OFD XML 字符串。
103    #[must_use]
104    pub fn to_xml_string(&self) -> String {
105        use std::fmt::Write;
106        let mut xml = String::from("<ofd:PageBlock>\n");
107        for text_obj in &self.text_objects {
108            let _ = writeln!(
109                xml,
110                "  <ofd:TextObject ID=\"{}\" Boundary=\"{}\">{}</ofd:TextObject>",
111                text_obj.id, text_obj.boundary, text_obj.content
112            );
113        }
114        for path_obj in &self.path_objects {
115            let _ = writeln!(
116                xml,
117                "  <ofd:PathObject ID=\"{}\" Boundary=\"{}\">\
118                 <ofd:AbbreviatedData>{}</ofd:AbbreviatedData>\
119                 </ofd:PathObject>",
120                path_obj.id, path_obj.boundary, path_obj.abbreviated_data
121            );
122        }
123        for img_obj in &self.image_objects {
124            let _ = writeln!(
125                xml,
126                "  <ofd:ImageObject ID=\"{}\" Boundary=\"{}\" ResourceID=\"{}\" />",
127                img_obj.id, img_obj.boundary, img_obj.resource_id
128            );
129        }
130        for block in &self.page_blocks {
131            xml.push_str(&block.to_xml_string());
132        }
133        xml.push_str("</ofd:PageBlock>\n");
134        xml
135    }
136}
137
138impl Default for CT_PageBlock {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144impl PageBlockTextObject {
145    /// 创建文本对象。
146    #[must_use]
147    pub fn new(id: u32, boundary: impl Into<String>, content: impl Into<String>) -> Self {
148        Self {
149            id,
150            boundary: boundary.into(),
151            content: content.into(),
152            font_size: 12.0,
153        }
154    }
155
156    /// 设置字号。
157    #[must_use]
158    pub fn font_size(mut self, size: f64) -> Self {
159        self.font_size = size;
160        self
161    }
162}
163
164impl PageBlockPathObject {
165    /// 创建路径对象。
166    #[must_use]
167    pub fn new(id: u32, boundary: impl Into<String>, data: impl Into<String>) -> Self {
168        Self {
169            id,
170            boundary: boundary.into(),
171            abbreviated_data: data.into(),
172        }
173    }
174}
175
176impl PageBlockImageObject {
177    /// 创建图像对象。
178    #[must_use]
179    pub fn new(id: u32, boundary: impl Into<String>, resource_id: u32) -> Self {
180        Self {
181            id,
182            boundary: boundary.into(),
183            resource_id,
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_ct_page_block_new() {
194        let block = CT_PageBlock::new();
195        assert!(block.page_blocks.is_empty());
196        assert!(block.text_objects.is_empty());
197        assert!(block.path_objects.is_empty());
198        assert!(block.image_objects.is_empty());
199    }
200
201    #[test]
202    fn test_ct_page_block_add_text() {
203        let mut block = CT_PageBlock::new();
204        block.add_text_object(PageBlockTextObject::new(1, "0 0 100 20", "hello"));
205        assert_eq!(block.text_objects.len(), 1);
206        assert_eq!(block.total_count(), 1);
207    }
208
209    #[test]
210    fn test_ct_page_block_add_path() {
211        let mut block = CT_PageBlock::new();
212        block.add_path_object(PageBlockPathObject::new(2, "0 0 50 50", "M0 0L10 10"));
213        assert_eq!(block.path_objects.len(), 1);
214    }
215
216    #[test]
217    fn test_ct_page_block_add_image() {
218        let mut block = CT_PageBlock::new();
219        block.add_image_object(PageBlockImageObject::new(3, "0 0 100 100", 10));
220        assert_eq!(block.image_objects.len(), 1);
221    }
222
223    #[test]
224    fn test_ct_page_block_nested() {
225        let inner = CT_PageBlock::new();
226        let mut outer = CT_PageBlock::new();
227        outer.add_page_block(inner);
228        assert_eq!(outer.get_page_blocks().len(), 1);
229    }
230
231    #[test]
232    fn test_ct_page_block_total_count_recursive() {
233        let mut inner = CT_PageBlock::new();
234        inner.add_text_object(PageBlockTextObject::new(1, "0 0 10 10", "x"));
235        inner.add_path_object(PageBlockPathObject::new(2, "0 0 10 10", "M0 0"));
236        let mut outer = CT_PageBlock::new();
237        outer.add_text_object(PageBlockTextObject::new(3, "0 0 10 10", "y"));
238        outer.add_page_block(inner);
239        assert_eq!(outer.total_count(), 3);
240    }
241
242    #[test]
243    fn test_ct_page_block_to_xml_basic() {
244        let block = CT_PageBlock::new();
245        let xml = block.to_xml_string();
246        assert!(xml.contains("<ofd:PageBlock>"));
247        assert!(xml.contains("</ofd:PageBlock>"));
248    }
249
250    #[test]
251    fn test_ct_page_block_to_xml_with_objects() {
252        let mut block = CT_PageBlock::new();
253        block.add_text_object(PageBlockTextObject::new(1, "10 20 50 15", "test").font_size(14.0));
254        block.add_image_object(PageBlockImageObject::new(2, "0 0 100 100", 5));
255        let xml = block.to_xml_string();
256        assert!(xml.contains("ofd:TextObject"));
257        assert!(xml.contains("test"));
258        assert!(xml.contains("ofd:ImageObject"));
259        assert!(xml.contains("ResourceID=\"5\""));
260    }
261
262    #[test]
263    fn test_ct_page_block_clone_debug() {
264        let block = CT_PageBlock::new();
265        let block2 = block.clone();
266        assert!(block2.text_objects.is_empty());
267        assert!(format!("{block:?}").contains("CT_PageBlock"));
268    }
269
270    #[test]
271    fn test_page_block_text_object_builder() {
272        let obj = PageBlockTextObject::new(1, "0 0 50 20", "hello").font_size(18.0);
273        assert_eq!(obj.id, 1);
274        assert_eq!(obj.content, "hello");
275        assert!((obj.font_size - 18.0).abs() < f64::EPSILON);
276    }
277}