Skip to main content

ofd_core/model/
resource.rs

1//! 资源文件(见 GB/T 33190—2016 7.9)。
2//!
3//! 资源是绘制图元时所需数据的集合,其索引信息保存在资源文件中(图 20、表 18)。
4//! 资源分公共资源与页资源:公共资源文件在文档根节点中指定,页资源文件在页对象
5//! 中指定。
6//!
7//! 字型 `CT_Font`(见第 11 章)与多媒体 `CT_MultiMedia`(见表 19)在基础结构中
8//! 较常用,故一并建模;颜色空间、绘制参数、矢量图像等图元细节属于页面描述章节,
9//! 此处暂以分组占位,未建模的内容在反序列化时会被忽略。
10
11use serde::{Deserialize, Serialize};
12
13use crate::model::graphics::{CtColor, CtVectorG};
14use crate::types::{StArray, StId, StLoc};
15
16/// `Res`:资源文件根节点(见表 18)。
17///
18/// 规范中 `Res` 的内容为一个可重复、无序的选择(`xs:choice maxOccurs="unbounded"`),
19/// 即各资源组(如 `MultiMedias`)允许出现多次且可与其他组交错。故这里以保留文档
20/// 顺序的子节点序列 [`children`](Res::children) 建模,并提供 [`Res::fonts`] 等便捷
21/// 方法跨组扁平化遍历其中的资源项。
22#[derive(Debug, Clone, Default, Deserialize, Serialize)]
23pub struct Res {
24    /// 定义此资源文件的通用数据存储路径(必选)。
25    ///
26    /// 资源文件中各数据文件的默认存储位置以此为基准。
27    #[serde(rename = "@BaseLoc")]
28    pub base_loc: StLoc,
29    /// 资源组子节点,按在文档中出现的顺序保留(可重复、可交错)。
30    #[serde(rename = "$value", default)]
31    pub children: Vec<ResChild>,
32}
33
34/// `Res` 的一个资源组子节点(见表 18 的无序选择内容)。
35#[derive(Debug, Clone, Deserialize, Serialize)]
36pub enum ResChild {
37    /// 颜色空间资源组。
38    ColorSpaces(ColorSpaces),
39    /// 绘制参数资源组。
40    DrawParams(DrawParams),
41    /// 字型资源组。
42    Fonts(Fonts),
43    /// 多媒体资源组。
44    MultiMedias(MultiMedias),
45    /// 矢量图像(复合图形单元)资源组。
46    CompositeGraphicUnits(CompositeGraphicUnits),
47}
48
49impl Res {
50    /// 跨所有 `ColorSpaces` 组遍历颜色空间资源。
51    pub fn color_spaces(&self) -> impl Iterator<Item = &CtColorSpace> {
52        self.children
53            .iter()
54            .filter_map(|c| match c {
55                ResChild::ColorSpaces(g) => Some(g),
56                _ => None,
57            })
58            .flat_map(|g| g.color_spaces.iter())
59    }
60
61    /// 跨所有 `DrawParams` 组遍历绘制参数资源。
62    pub fn draw_params(&self) -> impl Iterator<Item = &CtDrawParam> {
63        self.children
64            .iter()
65            .filter_map(|c| match c {
66                ResChild::DrawParams(g) => Some(g),
67                _ => None,
68            })
69            .flat_map(|g| g.draw_params.iter())
70    }
71
72    /// 跨所有 `Fonts` 组遍历字型资源。
73    pub fn fonts(&self) -> impl Iterator<Item = &CtFont> {
74        self.children
75            .iter()
76            .filter_map(|c| match c {
77                ResChild::Fonts(g) => Some(g),
78                _ => None,
79            })
80            .flat_map(|g| g.fonts.iter())
81    }
82
83    /// 跨所有 `MultiMedias` 组遍历多媒体资源。
84    pub fn multi_medias(&self) -> impl Iterator<Item = &CtMultiMedia> {
85        self.children
86            .iter()
87            .filter_map(|c| match c {
88                ResChild::MultiMedias(g) => Some(g),
89                _ => None,
90            })
91            .flat_map(|g| g.multi_medias.iter())
92    }
93
94    /// 跨所有 `CompositeGraphicUnits` 组遍历矢量图像资源。
95    pub fn composite_graphic_units(&self) -> impl Iterator<Item = &CtVectorG> {
96        self.children
97            .iter()
98            .filter_map(|c| match c {
99                ResChild::CompositeGraphicUnits(g) => Some(g),
100                _ => None,
101            })
102            .flat_map(|g| g.units.iter())
103    }
104}
105
106/// 一组矢量图像(复合图形单元)资源的描述(见表 18、表 49)。
107#[derive(Debug, Clone, Default, Deserialize, Serialize)]
108pub struct CompositeGraphicUnits {
109    /// 矢量图像列表,被页面中的复合对象(`CompositeObject`)按 `ResourceID` 引用。
110    #[serde(rename = "CompositeGraphicUnit", default)]
111    pub units: Vec<CtVectorG>,
112}
113
114/// 一组颜色空间资源的描述(见表 18)。
115#[derive(Debug, Clone, Default, Deserialize, Serialize)]
116pub struct ColorSpaces {
117    /// 颜色空间列表。
118    #[serde(rename = "ColorSpace", default)]
119    pub color_spaces: Vec<CtColorSpace>,
120}
121
122/// `CT_ColorSpace`:颜色空间(见 8.3.1、表 28)。
123#[derive(Debug, Clone, Default, Deserialize, Serialize)]
124pub struct CtColorSpace {
125    /// 资源标识(必选)。
126    #[serde(rename = "@ID")]
127    pub id: StId,
128    /// 颜色空间类型:`Gray`/`RGB`/`CMYK`(必选)。
129    #[serde(rename = "@Type")]
130    pub cs_type: String,
131    /// 每个颜色分量的位数,取值 1/2/4/8/16,默认 8(可选)。
132    #[serde(rename = "@BitsPerComponent")]
133    pub bits_per_component: Option<u32>,
134    /// 调色板所在文件(可选)。
135    #[serde(rename = "@Profile")]
136    pub profile: Option<StLoc>,
137}
138
139/// 一组绘制参数资源的描述(见表 18)。
140#[derive(Debug, Clone, Default, Deserialize, Serialize)]
141pub struct DrawParams {
142    /// 绘制参数列表。
143    #[serde(rename = "DrawParam", default)]
144    pub draw_params: Vec<CtDrawParam>,
145}
146
147/// `CT_DrawParam`:绘制参数(见 8.2、表 24)。
148///
149/// 绘制参数可通过 `Relative` 继承另一绘制参数;图元未显式声明的颜色、线宽
150/// 等属性将回退到其引用的绘制参数。
151#[derive(Debug, Clone, Default, Deserialize, Serialize)]
152pub struct CtDrawParam {
153    /// 资源标识(必选)。
154    #[serde(rename = "@ID")]
155    pub id: StId,
156    /// 基础绘制参数标识,本参数在其基础上覆盖(可选)。
157    #[serde(rename = "@Relative")]
158    pub relative: Option<StId>,
159    /// 线宽,单位为毫米,默认 0.353(可选)。
160    #[serde(rename = "@LineWidth")]
161    pub line_width: Option<f64>,
162    /// 线条连接样式(可选)。
163    #[serde(rename = "@Join")]
164    pub join: Option<String>,
165    /// 线条端点样式(可选)。
166    #[serde(rename = "@Cap")]
167    pub cap: Option<String>,
168    /// 斜接限制(可选)。
169    #[serde(rename = "@MiterLimit")]
170    pub miter_limit: Option<f64>,
171    /// 虚线起始相位(可选)。
172    #[serde(rename = "@DashOffset")]
173    pub dash_offset: Option<f64>,
174    /// 虚线重复样式(可选)。
175    #[serde(rename = "@DashPattern")]
176    pub dash_pattern: Option<StArray<f64>>,
177    /// 填充颜色(可选)。
178    #[serde(rename = "FillColor")]
179    pub fill_color: Option<CtColor>,
180    /// 勾边颜色(可选)。
181    #[serde(rename = "StrokeColor")]
182    pub stroke_color: Option<CtColor>,
183}
184
185/// 一组字型资源的描述(见表 18)。
186#[derive(Debug, Clone, Default, Deserialize, Serialize)]
187pub struct Fonts {
188    /// 字型列表。
189    #[serde(rename = "Font", default)]
190    pub fonts: Vec<CtFont>,
191}
192
193/// `CT_Font`:字型资源描述(见第 11 章;在基础类型上扩展 `ID`)。
194#[derive(Debug, Clone, Default, Deserialize, Serialize)]
195pub struct CtFont {
196    /// 资源标识(必选)。
197    #[serde(rename = "@ID")]
198    pub id: StId,
199    /// 字型名(必选)。
200    #[serde(rename = "@FontName")]
201    pub font_name: String,
202    /// 字型族名(可选)。
203    #[serde(rename = "@FamilyName")]
204    pub family_name: Option<String>,
205    /// 字型适用的字符分类(可选)。
206    #[serde(rename = "@Charset")]
207    pub charset: Option<String>,
208    /// 是否为斜体(可选)。
209    #[serde(rename = "@Italic")]
210    pub italic: Option<bool>,
211    /// 是否为粗体(可选)。
212    #[serde(rename = "@Bold")]
213    pub bold: Option<bool>,
214    /// 是否为衬线字体(可选)。
215    #[serde(rename = "@Serif")]
216    pub serif: Option<bool>,
217    /// 是否为等宽字体(可选)。
218    #[serde(rename = "@FixedWidth")]
219    pub fixed_width: Option<bool>,
220    /// 指向内嵌字型文件(可选)。
221    #[serde(rename = "FontFile")]
222    pub font_file: Option<StLoc>,
223}
224
225/// 一组多媒体资源的描述(见表 18)。
226#[derive(Debug, Clone, Default, Deserialize, Serialize)]
227pub struct MultiMedias {
228    /// 多媒体列表。
229    #[serde(rename = "MultiMedia", default)]
230    pub multi_medias: Vec<CtMultiMedia>,
231}
232
233/// `CT_MultiMedia`:多媒体资源描述(见表 19;在基础类型上扩展 `ID`)。
234#[derive(Debug, Clone, Default, Deserialize, Serialize)]
235pub struct CtMultiMedia {
236    /// 资源标识(必选)。
237    #[serde(rename = "@ID")]
238    pub id: StId,
239    /// 多媒体类型:位图图像、视频、音频(必选)。
240    #[serde(rename = "@Type")]
241    pub media_type: String,
242    /// 资源格式,如 `BMP`/`JPEG`/`PNG`/`TIFF`/`AVS` 等(可选)。
243    #[serde(rename = "@Format")]
244    pub format: Option<String>,
245    /// 指向包内的多媒体文件位置(必选)。
246    #[serde(rename = "MediaFile")]
247    pub media_file: StLoc,
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    /// 构造一个含全部资源组类型的 `Res`,使每个迭代器都同时遇到命中分支与
255    /// `_ => None` 跳过分支。
256    fn full_res() -> Res {
257        Res {
258            base_loc: StLoc::from("Res"),
259            children: vec![
260                ResChild::ColorSpaces(ColorSpaces {
261                    color_spaces: vec![CtColorSpace {
262                        id: StId(1),
263                        cs_type: "RGB".into(),
264                        ..Default::default()
265                    }],
266                }),
267                ResChild::DrawParams(DrawParams {
268                    draw_params: vec![CtDrawParam {
269                        id: StId(2),
270                        ..Default::default()
271                    }],
272                }),
273                ResChild::Fonts(Fonts {
274                    fonts: vec![CtFont {
275                        id: StId(3),
276                        font_name: "宋体".into(),
277                        ..Default::default()
278                    }],
279                }),
280                ResChild::MultiMedias(MultiMedias {
281                    multi_medias: vec![CtMultiMedia {
282                        id: StId(4),
283                        media_type: "Image".into(),
284                        media_file: StLoc::from("a.png"),
285                        ..Default::default()
286                    }],
287                }),
288                ResChild::CompositeGraphicUnits(CompositeGraphicUnits {
289                    units: vec![CtVectorG::default()],
290                }),
291            ],
292        }
293    }
294
295    #[test]
296    fn iterators_flatten_each_group() {
297        let res = full_res();
298        assert_eq!(res.color_spaces().count(), 1);
299        assert_eq!(res.draw_params().count(), 1);
300        assert_eq!(res.fonts().count(), 1);
301        assert_eq!(res.multi_medias().count(), 1);
302        assert_eq!(res.composite_graphic_units().count(), 1);
303        // 命中具体项,确认扁平化取到内层元素。
304        assert_eq!(res.fonts().next().unwrap().font_name, "宋体");
305        assert_eq!(res.color_spaces().next().unwrap().cs_type, "RGB");
306    }
307
308    #[test]
309    fn iterators_skip_when_group_absent() {
310        // 仅含字型组:其余迭代器走 `_ => None` 分支返回空。
311        let res = Res {
312            base_loc: StLoc::default(),
313            children: vec![ResChild::Fonts(Fonts::default())],
314        };
315        assert_eq!(res.color_spaces().count(), 0);
316        assert_eq!(res.draw_params().count(), 0);
317        assert_eq!(res.multi_medias().count(), 0);
318        assert_eq!(res.composite_graphic_units().count(), 0);
319    }
320}