Skip to main content

easyofd_core/page_obj/
ct_page_area.rs

1//! CT_PageArea 页面区域结构。
2
3/// 对应 Java: org.ofdrw.core.basicStructure.doc.CT_PageArea
4///
5/// 页面区域结构,描述页面的物理区域、出血区域、裁切区域等。
6/// 对应 GB/T 33190-2016 图 7。
7#[allow(non_camel_case_types)]
8#[derive(Debug, Clone, PartialEq)]
9pub struct CT_PageArea {
10    /// 物理区域,格式 "topLeftX topLeftY width height"(单位 mm)。
11    pub physical_box: Option<String>,
12    /// 出版区域(应用显示区域),格式同上。
13    pub application_box: Option<String>,
14    /// 内容区域(版心),格式同上。
15    pub content_box: Option<String>,
16    /// 出血区域,格式同上。
17    pub bleed_box: Option<String>,
18}
19
20/// 由四个值组成的矩形框。
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct Box {
23    /// 左上角 X 坐标(mm)。
24    pub x: f64,
25    /// 左上角 Y 坐标(mm)。
26    pub y: f64,
27    /// 宽度(mm)。
28    pub width: f64,
29    /// 高度(mm)。
30    pub height: f64,
31}
32
33impl Box {
34    /// 创建新的矩形框。
35    #[must_use]
36    pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
37        Self {
38            x,
39            y,
40            width,
41            height,
42        }
43    }
44
45    /// 转为 OFD ST_Box 格式字符串 "x y width height"。
46    #[must_use]
47    pub fn to_st_box(&self) -> String {
48        format!("{} {} {} {}", self.x, self.y, self.width, self.height)
49    }
50}
51
52impl CT_PageArea {
53    /// 创建空的页面区域。
54    #[must_use]
55    pub fn new() -> Self {
56        Self {
57            physical_box: None,
58            application_box: None,
59            content_box: None,
60            bleed_box: None,
61        }
62    }
63
64    /// 使用物理区域创建页面区域。
65    #[must_use]
66    pub fn with_physical(x: f64, y: f64, width: f64, height: f64) -> Self {
67        Self {
68            physical_box: Some(Box::new(x, y, width, height).to_st_box()),
69            application_box: None,
70            content_box: None,
71            bleed_box: None,
72        }
73    }
74
75    /// 设置物理区域。
76    #[must_use]
77    pub fn physical_box(mut self, x: f64, y: f64, width: f64, height: f64) -> Self {
78        self.physical_box = Some(Box::new(x, y, width, height).to_st_box());
79        self
80    }
81
82    /// 设置出版区域。
83    #[must_use]
84    pub fn application_box(mut self, x: f64, y: f64, width: f64, height: f64) -> Self {
85        self.application_box = Some(Box::new(x, y, width, height).to_st_box());
86        self
87    }
88
89    /// 设置内容区域。
90    #[must_use]
91    pub fn content_box(mut self, x: f64, y: f64, width: f64, height: f64) -> Self {
92        self.content_box = Some(Box::new(x, y, width, height).to_st_box());
93        self
94    }
95
96    /// 设置出血区域。
97    #[must_use]
98    pub fn bleed_box(mut self, x: f64, y: f64, width: f64, height: f64) -> Self {
99        self.bleed_box = Some(Box::new(x, y, width, height).to_st_box());
100        self
101    }
102
103    /// 获取物理区域,若未设置则返回默认值。
104    #[must_use]
105    pub fn get_box(&self) -> String {
106        self.physical_box
107            .clone()
108            .unwrap_or_else(|| "0 0 210 297".to_string())
109    }
110
111    /// 序列化为 OFD XML 字符串。
112    #[must_use]
113    pub fn to_xml_string(&self) -> String {
114        use std::fmt::Write;
115        let mut xml = String::from("<ofd:PageArea");
116        if let Some(ref pb) = self.physical_box {
117            let _ = write!(xml, " PhysicalBox=\"{pb}\"");
118        }
119        if let Some(ref ab) = self.application_box {
120            let _ = write!(xml, " ApplicationBox=\"{ab}\"");
121        }
122        if let Some(ref cb) = self.content_box {
123            let _ = write!(xml, " ContentBox=\"{cb}\"");
124        }
125        if let Some(ref bb) = self.bleed_box {
126            let _ = write!(xml, " BleedBox=\"{bb}\"");
127        }
128        xml.push_str(" />");
129        xml
130    }
131}
132
133impl Default for CT_PageArea {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_ct_page_area_new() {
145        let area = CT_PageArea::new();
146        assert!(area.physical_box.is_none());
147        assert!(area.application_box.is_none());
148        assert!(area.content_box.is_none());
149        assert!(area.bleed_box.is_none());
150    }
151
152    #[test]
153    fn test_ct_page_area_with_physical() {
154        let area = CT_PageArea::with_physical(0.0, 0.0, 210.0, 297.0);
155        assert_eq!(area.physical_box.as_deref(), Some("0 0 210 297"));
156        assert!(area.application_box.is_none());
157    }
158
159    #[test]
160    fn test_ct_page_area_builder_chaining() {
161        let area = CT_PageArea::new()
162            .physical_box(0.0, 0.0, 210.0, 297.0)
163            .application_box(10.0, 10.0, 190.0, 277.0)
164            .content_box(20.0, 20.0, 170.0, 257.0)
165            .bleed_box(-5.0, -5.0, 220.0, 307.0);
166        assert!(area.physical_box.is_some());
167        assert!(area.application_box.is_some());
168        assert!(area.content_box.is_some());
169        assert!(area.bleed_box.is_some());
170    }
171
172    #[test]
173    fn test_ct_page_area_get_box_default() {
174        let area = CT_PageArea::new();
175        assert_eq!(area.get_box(), "0 0 210 297");
176    }
177
178    #[test]
179    fn test_ct_page_area_get_box_set() {
180        let area = CT_PageArea::with_physical(10.0, 20.0, 100.0, 200.0);
181        assert_eq!(area.get_box(), "10 20 100 200");
182    }
183
184    #[test]
185    fn test_ct_page_area_to_xml_basic() {
186        let area = CT_PageArea::with_physical(0.0, 0.0, 210.0, 297.0);
187        let xml = area.to_xml_string();
188        assert!(xml.contains("<ofd:PageArea"));
189        assert!(xml.contains("PhysicalBox=\"0 0 210 297\""));
190        assert!(xml.ends_with(" />"));
191    }
192
193    #[test]
194    fn test_ct_page_area_to_xml_full() {
195        let area = CT_PageArea::new()
196            .physical_box(0.0, 0.0, 210.0, 297.0)
197            .bleed_box(-5.0, -5.0, 220.0, 307.0);
198        let xml = area.to_xml_string();
199        assert!(xml.contains("PhysicalBox"));
200        assert!(xml.contains("BleedBox"));
201    }
202
203    #[test]
204    fn test_ct_page_area_clone_debug() {
205        let area = CT_PageArea::with_physical(0.0, 0.0, 1.0, 1.0);
206        let area2 = area.clone();
207        assert!(area2.physical_box.is_some());
208        assert!(format!("{area:?}").contains("CT_PageArea"));
209    }
210
211    #[test]
212    fn test_box_new_and_to_st_box() {
213        let b = Box::new(1.0, 2.0, 3.0, 4.0);
214        assert_eq!(b.to_st_box(), "1 2 3 4");
215    }
216}