Skip to main content

ofd_core/model/
graphics.rs

1//! 页面图元对象(见 GB/T 33190—2016 第 8~13 章)。
2//!
3//! 基础结构([`crate::model::page`])只关心页/层的骨架,而图层内部真正承载
4//! 可视内容的是一组*页块*(`CT_PageBlock` 的子节点,见表 15):文字对象、
5//! 图形对象、图像对象、复合对象,以及可递归嵌套的页块分组。
6//!
7//! 这些对象是 [`crate::render`] 渲染器的输入。它们均派生自 `CT_GraphicUnit`
8//! (表 23),共享边界、变换矩阵、绘制参数等图元属性;为与 `quick-xml` 的
9//! 属性反序列化良好配合,这里将公共属性直接内联到各对象中,而非使用
10//! `#[serde(flatten)]`。
11
12use serde::{Deserialize, Serialize};
13
14use crate::types::{StArray, StBox, StId, StRefId};
15
16/// 图层 / 页块中的一个可绘制对象(`CT_PageBlock` 的子节点,见表 15)。
17///
18/// 以 XML 元素名区分类型,渲染时按文档顺序(即出现顺序)依次绘制以保证
19/// 正确的叠放次序(z-order)。
20#[derive(Debug, Clone, Deserialize, Serialize)]
21pub enum PageBlock {
22    /// 文字对象(见 11 章、表 45)。
23    #[serde(rename = "TextObject")]
24    Text(TextObject),
25    /// 图形(路径)对象(见 9 章、表 34)。
26    #[serde(rename = "PathObject")]
27    Path(PathObject),
28    /// 图像对象(见 12 章、表 43)。
29    #[serde(rename = "ImageObject")]
30    Image(ImageObject),
31    /// 复合对象(见 13 章、表 49);当前渲染器暂不展开其矢量内容。
32    #[serde(rename = "CompositeObject")]
33    Composite(CompositeObject),
34    /// 页块分组,可递归嵌套(见表 15)。
35    #[serde(rename = "PageBlock")]
36    Block(PageBlockGroup),
37}
38
39/// 可递归嵌套的页块分组(`CT_PageBlock`,见表 15)。
40#[derive(Debug, Clone, Default, Deserialize, Serialize)]
41pub struct PageBlockGroup {
42    /// 分组标识(可选)。
43    #[serde(rename = "@ID")]
44    pub id: Option<StId>,
45    /// 组内的子对象,按出现顺序绘制。
46    #[serde(rename = "$value", default)]
47    pub objects: Vec<PageBlock>,
48}
49
50/// `CT_Color`:颜色(见 8.3、表 27)。
51///
52/// 仅建模渲染所需的核心字段:分量值、颜色空间引用与透明度。渐变、底纹与
53/// 调色板索引等高级着色暂未建模。
54#[derive(Debug, Clone, Default, Deserialize, Serialize)]
55pub struct CtColor {
56    /// 颜色分量值,含义由所引用颜色空间决定(可选;缺省时按黑色处理)。
57    #[serde(rename = "@Value")]
58    pub value: Option<StArray<f64>>,
59    /// 调色板索引(可选)。
60    #[serde(rename = "@Index")]
61    pub index: Option<i64>,
62    /// 引用资源中定义的颜色空间标识(可选;缺省采用文档默认/ RGB)。
63    #[serde(rename = "@ColorSpace")]
64    pub color_space: Option<StRefId>,
65    /// 颜色透明度,取值 0~255(可选,缺省 255 不透明)。
66    #[serde(rename = "@Alpha")]
67    pub alpha: Option<u8>,
68}
69
70/// `CT_Text`:文字对象(见 11 章、表 45)。
71#[derive(Debug, Clone, Default, Deserialize, Serialize)]
72pub struct TextObject {
73    /// 对象标识(可选)。
74    #[serde(rename = "@ID")]
75    pub id: Option<StId>,
76    /// 对象边界,相对页面坐标系(必选)。
77    #[serde(rename = "@Boundary", default)]
78    pub boundary: StBox,
79    /// 对象变换矩阵,6 元素 `[a b c d e f]`(可选)。
80    #[serde(rename = "@CTM")]
81    pub ctm: Option<StArray<f64>>,
82    /// 引用的绘制参数标识(可选)。
83    #[serde(rename = "@DrawParam")]
84    pub draw_param: Option<StRefId>,
85    /// 对象整体透明度 0~255(可选)。
86    #[serde(rename = "@Alpha")]
87    pub alpha: Option<u8>,
88    /// 引用的字型资源标识(必选;缺失时为 `0`,渲染时跳过)。
89    #[serde(rename = "@Font", default)]
90    pub font: StRefId,
91    /// 字号,单位为毫米(必选;缺失时为 `0`,渲染时跳过)。
92    #[serde(rename = "@Size", default)]
93    pub size: f64,
94    /// 是否勾边,默认 `false`(可选)。
95    #[serde(rename = "@Stroke")]
96    pub stroke: Option<bool>,
97    /// 是否填充,默认 `true`(可选)。
98    #[serde(rename = "@Fill")]
99    pub fill: Option<bool>,
100    /// 横向缩放比例,默认 `1.0`(可选)。
101    #[serde(rename = "@HScale")]
102    pub h_scale: Option<f64>,
103    /// 阅读方向:文字排列方向相对 x 轴正向的顺时针角度,取 0/90/180/270,
104    /// 默认 `0`(可选,见 11.3、表 47)。排列由各字 `DeltaX`/`DeltaY` 显式给出,
105    /// 此属性为阅读次序的描述。
106    #[serde(rename = "@ReadDirection")]
107    pub read_direction: Option<i32>,
108    /// 字符方向:单个字形基线相对 x 轴正向的顺时针旋转角,取 0/90/180/270,
109    /// 默认 `0`(可选,见 11.3、表 47)。渲染时每个字形绕其原点旋转该角度。
110    #[serde(rename = "@CharDirection")]
111    pub char_direction: Option<i32>,
112    /// 文字粗细值,取 0~1000 的百级档(100,200,…,900),默认 `400`(可选,见表 45)。
113    #[serde(rename = "@Weight")]
114    pub weight: Option<i32>,
115    /// 是否为斜体样式,默认 `false`(可选,见表 45)。
116    #[serde(rename = "@Italic")]
117    pub italic: Option<bool>,
118    /// 填充颜色(可选)。
119    #[serde(rename = "FillColor")]
120    pub fill_color: Option<CtColor>,
121    /// 勾边颜色(可选)。
122    #[serde(rename = "StrokeColor")]
123    pub stroke_color: Option<CtColor>,
124    /// 字形变换序列:将文字内容中的字符编码映射到具体字形索引(可选,见 11.4、表 48)。
125    ///
126    /// 规范中 `CGTransform` 与 `TextCode` 在 `CT_Text` 内交替出现,每个 `CGTransform`
127    /// 的 `CodePosition` 相对其后 `TextCode` 的字符串起始位置。此处将其汇集为一个序列,
128    /// 渲染时按 `CodePosition` 在所有 `TextCode` 拼接后的字符流中定位;对最常见的
129    /// 单 `TextCode` 文字对象两种解释一致。
130    #[serde(rename = "CGTransform", default)]
131    pub cg_transforms: Vec<CtCgTransform>,
132    /// 文字定位与内容序列(必选,至少一个)。
133    #[serde(rename = "TextCode", default)]
134    pub text_codes: Vec<TextCode>,
135}
136
137/// `CT_CGTransform`:字形变换,描述字符编码到字形索引的对应关系(见 11.4、表 48)。
138///
139/// 涵盖一对一、多对一、一对多、多对多四种关系:自 `code_position` 起的
140/// `code_count` 个字符,整体对应 `glyphs` 给出的 `glyph_count` 个字形索引。
141#[derive(Debug, Clone, Default, Deserialize, Serialize)]
142pub struct CtCgTransform {
143    /// 字符编码在文字内容中的起始位置,从 0 开始(必选)。
144    #[serde(rename = "@CodePosition", default)]
145    pub code_position: i32,
146    /// 参与变换的字符数量,应 ≥1,默认 `1`(可选)。
147    #[serde(rename = "@CodeCount")]
148    pub code_count: Option<i32>,
149    /// 变换得到的字形索引数量,应 ≥1,默认 `1`(可选)。
150    #[serde(rename = "@GlyphCount")]
151    pub glyph_count: Option<i32>,
152    /// 变换后的字形索引列表(必选)。
153    #[serde(rename = "Glyphs")]
154    pub glyphs: Option<StArray<u32>>,
155}
156
157/// `TextCode`:一段文字的定位与字符内容(见表 46)。
158#[derive(Debug, Clone, Default, Deserialize, Serialize)]
159pub struct TextCode {
160    /// 首字符的字形原点横坐标(对象坐标系,毫米)。
161    #[serde(rename = "@X")]
162    pub x: Option<f64>,
163    /// 首字符的字形原点纵坐标(对象坐标系,毫米)。
164    #[serde(rename = "@Y")]
165    pub y: Option<f64>,
166    /// 相邻字符在 X 方向的偏移序列,支持 `g` 游程编码(可选)。
167    #[serde(rename = "@DeltaX")]
168    pub delta_x: Option<String>,
169    /// 相邻字符在 Y 方向的偏移序列,支持 `g` 游程编码(可选)。
170    #[serde(rename = "@DeltaY")]
171    pub delta_y: Option<String>,
172    /// 字符内容。
173    #[serde(rename = "$text")]
174    pub text: Option<String>,
175}
176
177/// `CT_Path`:图形(路径)对象(见 9 章、表 34)。
178#[derive(Debug, Clone, Default, Deserialize, Serialize)]
179pub struct PathObject {
180    /// 对象标识(可选)。
181    #[serde(rename = "@ID")]
182    pub id: Option<StId>,
183    /// 对象边界,相对页面坐标系(必选)。
184    #[serde(rename = "@Boundary", default)]
185    pub boundary: StBox,
186    /// 对象变换矩阵,6 元素 `[a b c d e f]`(可选)。
187    #[serde(rename = "@CTM")]
188    pub ctm: Option<StArray<f64>>,
189    /// 引用的绘制参数标识(可选)。
190    #[serde(rename = "@DrawParam")]
191    pub draw_param: Option<StRefId>,
192    /// 线宽,单位为毫米(可选)。
193    #[serde(rename = "@LineWidth")]
194    pub line_width: Option<f64>,
195    /// 线条端点样式 `Butt`/`Round`/`Square`,默认 `Butt`(可选,见表 34)。
196    #[serde(rename = "@Cap")]
197    pub cap: Option<String>,
198    /// 线条连接样式 `Miter`/`Round`/`Bevel`,默认 `Miter`(可选,见表 34)。
199    #[serde(rename = "@Join")]
200    pub join: Option<String>,
201    /// `Join` 为 `Miter` 时的斜接限制,默认 `4.234`(可选,见表 34)。
202    #[serde(rename = "@MiterLimit")]
203    pub miter_limit: Option<f64>,
204    /// 虚线起始相位,默认 `0`(可选,见表 34)。
205    #[serde(rename = "@DashOffset")]
206    pub dash_offset: Option<f64>,
207    /// 虚线重复样式(实线段/空白段长度交替序列,可选,见表 34)。
208    #[serde(rename = "@DashPattern")]
209    pub dash_pattern: Option<StArray<f64>>,
210    /// 对象整体透明度 0~255(可选)。
211    #[serde(rename = "@Alpha")]
212    pub alpha: Option<u8>,
213    /// 是否勾边,默认 `true`(可选)。
214    #[serde(rename = "@Stroke")]
215    pub stroke: Option<bool>,
216    /// 是否填充,默认 `false`(可选)。
217    #[serde(rename = "@Fill")]
218    pub fill: Option<bool>,
219    /// 填充时的判定规则,`NonZero` 或 `Even-Odd`,默认 `NonZero`(可选)。
220    #[serde(rename = "@Rule")]
221    pub rule: Option<String>,
222    /// 填充颜色(可选)。
223    #[serde(rename = "FillColor")]
224    pub fill_color: Option<CtColor>,
225    /// 勾边颜色(可选)。
226    #[serde(rename = "StrokeColor")]
227    pub stroke_color: Option<CtColor>,
228    /// 路径绘制指令的紧缩表示(必选)。
229    #[serde(rename = "AbbreviatedData")]
230    pub abbreviated_data: Option<String>,
231}
232
233/// `CT_Image`:图像对象(见 12 章、表 43)。
234#[derive(Debug, Clone, Default, Deserialize, Serialize)]
235pub struct ImageObject {
236    /// 对象标识(可选)。
237    #[serde(rename = "@ID")]
238    pub id: Option<StId>,
239    /// 对象边界,相对页面坐标系(必选)。
240    #[serde(rename = "@Boundary", default)]
241    pub boundary: StBox,
242    /// 对象变换矩阵,6 元素 `[a b c d e f]`(可选)。
243    #[serde(rename = "@CTM")]
244    pub ctm: Option<StArray<f64>>,
245    /// 对象整体透明度 0~255(可选)。
246    #[serde(rename = "@Alpha")]
247    pub alpha: Option<u8>,
248    /// 引用的多媒体资源标识(必选)。
249    #[serde(rename = "@ResourceID", default)]
250    pub resource_id: StRefId,
251    /// 图像蒙版资源标识(可选)。
252    #[serde(rename = "@ImageMask")]
253    pub image_mask: Option<StRefId>,
254}
255
256/// `CT_Composite`:复合对象(见 13 章、表 49)。
257///
258/// 复合对象引用资源文件中定义的矢量图像组(`CT_VectorG`)。当前渲染器仅
259/// 保留其边界信息,暂不展开内部矢量内容。
260#[derive(Debug, Clone, Default, Deserialize, Serialize)]
261pub struct CompositeObject {
262    /// 对象标识(可选)。
263    #[serde(rename = "@ID")]
264    pub id: Option<StId>,
265    /// 对象边界,相对页面坐标系(必选)。
266    #[serde(rename = "@Boundary", default)]
267    pub boundary: StBox,
268    /// 对象变换矩阵(可选)。
269    #[serde(rename = "@CTM")]
270    pub ctm: Option<StArray<f64>>,
271    /// 引用的矢量图像组资源标识(必选)。
272    #[serde(rename = "@ResourceID", default)]
273    pub resource_id: StRefId,
274}
275
276/// 解析 `DeltaX` / `DeltaY` 偏移序列。
277///
278/// 序列以空格分隔;其中 `g` 引导一段游程编码:`g n v` 表示数值 `v` 连续
279/// 出现 `n` 次。例如 `"g 3 4.5 10"` 解析为 `[4.5, 4.5, 4.5, 10.0]`。
280pub fn parse_deltas(raw: &str) -> Vec<f64> {
281    let mut out = Vec::new();
282    let mut tokens = raw.split_whitespace();
283    while let Some(tok) = tokens.next() {
284        if tok == "g" || tok == "G" {
285            let count = tokens.next().and_then(|s| s.parse::<usize>().ok());
286            let value = tokens.next().and_then(|s| s.parse::<f64>().ok());
287            if let (Some(count), Some(value)) = (count, value) {
288                out.extend(std::iter::repeat_n(value, count));
289            }
290        } else if let Ok(v) = tok.parse::<f64>() {
291            out.push(v);
292        }
293    }
294    out
295}
296
297/// `CT_VectorG`:矢量图像(复合对象引用的图形内容,见 13 章、表 49、表 50)。
298///
299/// 资源文件中以 `CompositeGraphicUnit` 的形式出现,被页面中的复合对象
300/// ([`CompositeObject`])通过 `ResourceID` 引用。其 `Content` 为一个
301/// `CT_PageBlock`,承载文字、图形、图像乃至嵌套复合对象等绘制内容;这些内容
302/// 在矢量图自身坐标系(原点位于左上角,范围 `Width`×`Height`)中描述,绘制时
303/// 经复合对象的边界与变换矩阵映射到页面。
304#[derive(Debug, Clone, Default, Deserialize, Serialize)]
305pub struct CtVectorG {
306    /// 资源标识(必选)。
307    #[serde(rename = "@ID")]
308    pub id: StId,
309    /// 矢量图自身坐标系的宽度(必选)。
310    #[serde(rename = "@Width", default)]
311    pub width: f64,
312    /// 矢量图自身坐标系的高度(必选)。
313    #[serde(rename = "@Height", default)]
314    pub height: f64,
315    /// 缩略图资源标识(可选)。
316    #[serde(rename = "@Thumbnail")]
317    pub thumbnail: Option<StRefId>,
318    /// 替换图像资源标识(可选)。
319    #[serde(rename = "@Substitution")]
320    pub substitution: Option<StRefId>,
321    /// 矢量内容(`CT_PageBlock`,必选);缺失时无可绘制内容。
322    #[serde(rename = "Content")]
323    pub content: Option<PageBlockGroup>,
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[test]
331    fn parse_delta_runlength() {
332        assert_eq!(parse_deltas("g 3 4.5 10"), vec![4.5, 4.5, 4.5, 10.0]);
333        assert_eq!(parse_deltas("1 2 3"), vec![1.0, 2.0, 3.0]);
334        assert!(parse_deltas("").is_empty());
335    }
336}