Skip to main content

easyofd_core/model/
mod.rs

1//! 核心 OFD 数据模型类型。
2//!
3//! 这些类型直接映射到 GB/T 33190-2016 XML 元素。
4
5pub mod bookmark;
6pub mod bookmarks;
7mod content_object;
8pub mod creation_date;
9pub mod creator;
10pub mod custom_data;
11pub mod custom_datas;
12mod image_format;
13mod image_object;
14pub mod ofd_id;
15mod ofd_metadata;
16mod ofd_page;
17pub mod page_size;
18mod path_object;
19pub mod permissions;
20pub mod point;
21pub mod template_page;
22mod text_object;
23pub mod weight;
24
25pub use bookmark::Bookmark;
26pub use bookmarks::Bookmarks;
27pub use content_object::ContentObject;
28pub use creation_date::CreationDate;
29pub use creator::Creator;
30pub use custom_data::CustomData;
31pub use custom_datas::CustomDatas;
32pub use image_format::ImageFormat;
33pub use image_object::ImageObject;
34pub use ofd_id::OfdId;
35pub use ofd_metadata::OfdMetadata;
36pub use ofd_page::OfdPage;
37pub use path_object::PathObject;
38pub use permissions::Permissions;
39pub use point::Point;
40pub use template_page::TemplatePage;
41pub use text_object::TextObject;
42pub use weight::Weight;
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    // ─── OfdMetadata ──────────────────────────────────────────────────────────
49
50    #[test]
51    fn test_ofd_metadata_default() {
52        let meta = OfdMetadata::default();
53        assert_eq!(meta.version, "1.0");
54        assert!(meta.title.is_none());
55        assert!(meta.author.is_none());
56        assert!(meta.creator.is_none());
57        assert!(meta.creation_date.is_none());
58    }
59
60    #[test]
61    fn test_ofd_metadata_clone_debug() {
62        let meta = OfdMetadata {
63            title: Some("t".into()),
64            ..Default::default()
65        };
66        let meta2 = meta.clone();
67        assert_eq!(meta2.title.unwrap(), "t");
68        let dbg = format!("{meta:?}");
69        assert!(dbg.contains("OfdMetadata"));
70    }
71
72    // ─── OfdPage ──────────────────────────────────────────────────────────────
73
74    #[test]
75    fn test_ofd_page_new() {
76        let page = OfdPage::new(210.0, 297.0);
77        assert!((page.width - 210.0).abs() < f64::EPSILON);
78        assert!((page.height - 297.0).abs() < f64::EPSILON);
79        assert!(page.content.is_empty());
80    }
81
82    #[test]
83    fn test_ofd_page_add_text() {
84        let mut page = OfdPage::new(210.0, 297.0);
85        page.add_text(TextObject::new(10.0, 20.0, "hello"));
86        assert_eq!(page.content.len(), 1);
87        assert!(matches!(&page.content[0], ContentObject::Text(_)));
88    }
89
90    #[test]
91    fn test_ofd_page_add_image() {
92        let mut page = OfdPage::new(210.0, 297.0);
93        page.add_image(ImageObject::jpeg(0.0, 0.0, 10.0, 10.0, vec![0xFF]));
94        assert_eq!(page.content.len(), 1);
95        assert!(matches!(&page.content[0], ContentObject::Image(_)));
96    }
97
98    #[test]
99    fn test_ofd_page_add_path() {
100        let mut page = OfdPage::new(210.0, 297.0);
101        page.add_path(PathObject::hline(0.0, 10.0, 100.0));
102        assert_eq!(page.content.len(), 1);
103        assert!(matches!(&page.content[0], ContentObject::Path(_)));
104    }
105
106    #[test]
107    fn test_ofd_page_mixed_content() {
108        let mut page = OfdPage::new(210.0, 297.0);
109        page.add_text(TextObject::new(0.0, 0.0, "t"));
110        page.add_image(ImageObject::png(0.0, 0.0, 1.0, 1.0, vec![0x89]));
111        page.add_path(PathObject::vline(5.0, 0.0, 10.0));
112        assert_eq!(page.content.len(), 3);
113    }
114
115    #[test]
116    fn test_ofd_page_clone_debug() {
117        let page = OfdPage::new(100.0, 200.0);
118        let page2 = page.clone();
119        assert!((page2.width - 100.0).abs() < f64::EPSILON);
120        let dbg = format!("{page:?}");
121        assert!(dbg.contains("OfdPage"));
122    }
123
124    // ─── page_size constants ──────────────────────────────────────────────────
125
126    #[test]
127    fn test_page_size_a4() {
128        assert_eq!(page_size::A4, (210.0, 297.0));
129    }
130
131    #[test]
132    fn test_page_size_a4_landscape() {
133        assert_eq!(page_size::A4_LANDSCAPE, (297.0, 210.0));
134    }
135
136    #[test]
137    fn test_page_size_a3() {
138        assert_eq!(page_size::A3, (297.0, 420.0));
139    }
140
141    #[test]
142    fn test_page_size_letter() {
143        assert_eq!(page_size::LETTER, (215.9, 279.4));
144    }
145
146    // ─── TextObject ───────────────────────────────────────────────────────────
147
148    #[test]
149    fn test_text_object_new() {
150        let t = TextObject::new(10.0, 20.0, "hello");
151        assert!((t.x - 10.0).abs() < f64::EPSILON);
152        assert!((t.y - 20.0).abs() < f64::EPSILON);
153        assert_eq!(t.text, "hello");
154        assert_eq!(t.font, "SimSun");
155        assert!((t.size - 12.0).abs() < f64::EPSILON);
156        assert_eq!(t.weight, 400);
157        assert!(!t.italic);
158        assert_eq!(t.color, 0);
159        assert!(t.width.is_none());
160        assert!(t.height.is_none());
161    }
162
163    #[test]
164    fn test_text_object_from_string() {
165        let s = String::from("owned");
166        let t = TextObject::new(0.0, 0.0, s);
167        assert_eq!(t.text, "owned");
168    }
169
170    #[test]
171    fn test_text_object_builder_font() {
172        let t = TextObject::new(0.0, 0.0, "x").font("SimHei");
173        assert_eq!(t.font, "SimHei");
174    }
175
176    #[test]
177    fn test_text_object_builder_size() {
178        let t = TextObject::new(0.0, 0.0, "x").size(24.0);
179        assert!((t.size - 24.0).abs() < f64::EPSILON);
180    }
181
182    #[test]
183    fn test_text_object_builder_bold() {
184        let t = TextObject::new(0.0, 0.0, "x").bold();
185        assert_eq!(t.weight, 700);
186    }
187
188    #[test]
189    fn test_text_object_builder_italic() {
190        let t = TextObject::new(0.0, 0.0, "x").italic();
191        assert!(t.italic);
192    }
193
194    #[test]
195    fn test_text_object_builder_color() {
196        let t = TextObject::new(0.0, 0.0, "x").color(0xFF_0000);
197        assert_eq!(t.color, 0xFF_0000);
198    }
199
200    #[test]
201    fn test_text_object_builder_chaining() {
202        let t = TextObject::new(1.0, 2.0, "x")
203            .font("Arial")
204            .size(16.0)
205            .bold()
206            .italic()
207            .color(0x00_FF00);
208        assert_eq!(t.font, "Arial");
209        assert!((t.size - 16.0).abs() < f64::EPSILON);
210        assert_eq!(t.weight, 700);
211        assert!(t.italic);
212        assert_eq!(t.color, 0x00_FF00);
213    }
214
215    #[test]
216    fn test_text_object_clone_debug() {
217        let t = TextObject::new(0.0, 0.0, "x");
218        let t2 = t.clone();
219        assert_eq!(t2.text, "x");
220        let dbg = format!("{t:?}");
221        assert!(dbg.contains("TextObject"));
222    }
223
224    // ─── ImageFormat ──────────────────────────────────────────────────────────
225
226    #[test]
227    fn test_image_format_variants() {
228        assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
229        assert_ne!(ImageFormat::Bmp, ImageFormat::Tiff);
230        assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
231    }
232
233    #[test]
234    fn test_image_format_clone_copy_debug() {
235        let f = ImageFormat::Png;
236        let f2 = f;
237        assert_eq!(f2, ImageFormat::Png);
238        let dbg = format!("{f:?}");
239        assert!(dbg.contains("Png"));
240    }
241
242    // ─── ImageObject ──────────────────────────────────────────────────────────
243
244    #[test]
245    fn test_image_object_new() {
246        let img = ImageObject::new(10.0, 20.0, 30.0, 40.0, vec![1, 2], ImageFormat::Png);
247        assert!((img.x - 10.0).abs() < f64::EPSILON);
248        assert!((img.y - 20.0).abs() < f64::EPSILON);
249        assert!((img.width - 30.0).abs() < f64::EPSILON);
250        assert!((img.height - 40.0).abs() < f64::EPSILON);
251        assert_eq!(img.data, vec![1, 2]);
252        assert_eq!(img.format, ImageFormat::Png);
253    }
254
255    #[test]
256    fn test_image_object_jpeg() {
257        let img = ImageObject::jpeg(0.0, 0.0, 10.0, 10.0, vec![0xFF]);
258        assert_eq!(img.format, ImageFormat::Jpeg);
259    }
260
261    #[test]
262    fn test_image_object_png() {
263        let img = ImageObject::png(0.0, 0.0, 10.0, 10.0, vec![0x89]);
264        assert_eq!(img.format, ImageFormat::Png);
265    }
266
267    #[test]
268    fn test_image_object_clone_debug() {
269        let img = ImageObject::jpeg(0.0, 0.0, 1.0, 1.0, vec![0]);
270        let img2 = img.clone();
271        assert_eq!(img2.data, vec![0]);
272        let dbg = format!("{img:?}");
273        assert!(dbg.contains("ImageObject"));
274    }
275
276    // ─── PathObject ───────────────────────────────────────────────────────────
277
278    #[test]
279    fn test_path_object_new() {
280        let p = PathObject::new(5.0, 10.0, "M0 0L10 10");
281        assert!((p.x - 5.0).abs() < f64::EPSILON);
282        assert!((p.y - 10.0).abs() < f64::EPSILON);
283        assert_eq!(p.path_data, "M0 0L10 10");
284        assert_eq!(p.stroke_color, 0);
285        assert!((p.stroke_width - 0.35).abs() < f64::EPSILON);
286        assert!(p.fill_color.is_none());
287    }
288
289    #[test]
290    fn test_path_object_hline() {
291        let p = PathObject::hline(10.0, 20.0, 100.0);
292        assert!((p.x - 10.0).abs() < f64::EPSILON);
293        assert!((p.y - 20.0).abs() < f64::EPSILON);
294        assert!(p.path_data.contains("M10"));
295        assert!(p.path_data.contains("L100"));
296    }
297
298    #[test]
299    fn test_path_object_vline() {
300        let p = PathObject::vline(5.0, 0.0, 50.0);
301        assert!((p.x - 5.0).abs() < f64::EPSILON);
302        assert!(p.path_data.contains("M5"));
303    }
304
305    #[test]
306    fn test_path_object_rect() {
307        let p = PathObject::rect(0.0, 0.0, 100.0, 50.0);
308        assert!(p.path_data.starts_with('M'));
309        assert!(p.path_data.ends_with('Z'));
310        assert!(p.path_data.contains("L100"));
311    }
312
313    #[test]
314    fn test_path_object_builder_stroke_color() {
315        let p = PathObject::new(0.0, 0.0, "M0 0").stroke_color(0xFF_0000);
316        assert_eq!(p.stroke_color, 0xFF_0000);
317    }
318
319    #[test]
320    fn test_path_object_builder_stroke_width() {
321        let p = PathObject::new(0.0, 0.0, "M0 0").stroke_width(1.5);
322        assert!((p.stroke_width - 1.5).abs() < f64::EPSILON);
323    }
324
325    #[test]
326    fn test_path_object_builder_fill_color() {
327        let p = PathObject::new(0.0, 0.0, "M0 0").fill_color(0x00_FF00);
328        assert_eq!(p.fill_color, Some(0x00_FF00));
329    }
330
331    #[test]
332    fn test_path_object_builder_chaining() {
333        let p = PathObject::new(0.0, 0.0, "M0 0")
334            .stroke_color(0xFF)
335            .stroke_width(2.0)
336            .fill_color(0xFF_FF00);
337        assert_eq!(p.stroke_color, 0xFF);
338        assert!((p.stroke_width - 2.0).abs() < f64::EPSILON);
339        assert_eq!(p.fill_color, Some(0xFF_FF00));
340    }
341
342    #[test]
343    fn test_path_object_clone_debug() {
344        let p = PathObject::new(0.0, 0.0, "M0 0");
345        let p2 = p.clone();
346        assert_eq!(p2.path_data, "M0 0");
347        let dbg = format!("{p:?}");
348        assert!(dbg.contains("PathObject"));
349    }
350
351    // ─── ContentObject ────────────────────────────────────────────────────────
352
353    #[test]
354    fn test_content_object_variants_debug() {
355        let text = ContentObject::Text(TextObject::new(0.0, 0.0, "x"));
356        let img = ContentObject::Image(ImageObject::jpeg(0.0, 0.0, 1.0, 1.0, vec![0]));
357        let path = ContentObject::Path(PathObject::new(0.0, 0.0, "M0 0"));
358        assert!(format!("{text:?}").contains("Text"));
359        assert!(format!("{img:?}").contains("Image"));
360        assert!(format!("{path:?}").contains("Path"));
361    }
362
363    // ─── ImageObject::from_file ─────────────────────────────────────────────
364
365    #[test]
366    fn test_image_from_file_png() {
367        let dir = std::env::temp_dir().join("easyofd_img_test");
368        std::fs::create_dir_all(&dir).unwrap();
369        let path = dir.join("test.png");
370        let png: Vec<u8> = vec![0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
371        std::fs::write(&path, &png).unwrap();
372
373        let img = ImageObject::from_file(0.0, 0.0, 10.0, 10.0, &path).unwrap();
374        assert_eq!(img.format, ImageFormat::Png);
375        assert!(!img.data.is_empty());
376        let _ = std::fs::remove_file(&path);
377    }
378
379    #[test]
380    fn test_image_from_file_jpg_by_ext() {
381        let dir = std::env::temp_dir().join("easyofd_img_test2");
382        std::fs::create_dir_all(&dir).unwrap();
383        let path = dir.join("test.jpg");
384        std::fs::write(&path, b"not-real-jpg").unwrap();
385
386        let img = ImageObject::from_file(0.0, 0.0, 10.0, 10.0, &path).unwrap();
387        assert_eq!(img.format, ImageFormat::Jpeg);
388        let _ = std::fs::remove_file(&path);
389    }
390}