Skip to main content

easyofd_reader/
resource_manage.rs

1//! 资源管理器,按 ID 访问文档中出现的资源对象。
2//!
3//! 对应 Java: org.ofdrw.reader.ResourceManage
4//!
5//! Java 版通过解析 PublicRes.xml 和 DocumentRes.xml 加载字体、颜色空间、
6//! 绘制参数、多媒体等资源到内存映射表。Rust 版复用 parser 已有的资源解析
7//! 结果,提供按 ID 查找的能力。
8
9use std::collections::HashMap;
10
11/// 资源类型标识。
12///
13/// 对应 Java: `org.ofdrw.core.basicStructure.res.MediaType`(部分)
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum ResourceType {
16    /// 字体资源。
17    Font,
18    /// 图像资源。
19    Image,
20    /// 矢量图形资源。
21    VectorGraphic,
22    /// 颜色空间资源。
23    ColorSpace,
24    /// 绘制参数资源。
25    DrawParam,
26    /// 其他类型。
27    Other,
28}
29
30/// 资源条目,描述文档中一个被引用的资源。
31///
32/// 对应 Java: `org.ofdrw.core.basicStructure.res.CT_MultiMedia`(简化版)
33#[derive(Debug, Clone)]
34pub struct ResourceItem {
35    /// 资源 ID(文档内唯一)。
36    pub id: String,
37    /// 资源类型。
38    pub resource_type: ResourceType,
39    /// 资源文件在容器内的路径(相对于文档目录)。
40    pub file_path: Option<String>,
41    /// 资源文件格式(如 "PNG"、"JPEG"、"TTF")。
42    pub format: Option<String>,
43}
44
45impl ResourceItem {
46    /// 创建新的资源条目。
47    #[must_use]
48    pub fn new(id: impl Into<String>, resource_type: ResourceType) -> Self {
49        Self {
50            id: id.into(),
51            resource_type,
52            file_path: None,
53            format: None,
54        }
55    }
56
57    /// 设置资源文件路径。
58    #[must_use]
59    pub fn with_file_path(mut self, path: impl Into<String>) -> Self {
60        self.file_path = Some(path.into());
61        self
62    }
63
64    /// 设置资源格式。
65    #[must_use]
66    pub fn with_format(mut self, format: impl Into<String>) -> Self {
67        self.format = Some(format.into());
68        self
69    }
70}
71
72/// 资源管理器,维护文档中所有资源的 ID 索引。
73///
74/// 对应 Java: `org.ofdrw.reader.ResourceManage`
75///
76/// 提供按 ID 随机访问资源的能力。资源来源于 DocumentRes.xml 和
77/// PublicRes.xml 中声明的资源序列。
78#[derive(Debug, Clone, Default)]
79pub struct ResourceManage {
80    /// 所有资源的 ID 索引表。
81    resources: HashMap<String, ResourceItem>,
82    /// 按类型分组的 ID 集合(便于批量查询)。
83    by_type: HashMap<ResourceType, Vec<String>>,
84}
85
86impl ResourceManage {
87    /// 创建空的资源管理器。
88    #[must_use]
89    pub fn new() -> Self {
90        Self::default()
91    }
92
93    /// 从资源条目列表构建资源管理器。
94    #[must_use]
95    pub fn from_items(items: Vec<ResourceItem>) -> Self {
96        let mut mgr = Self::new();
97        for item in items {
98            mgr.insert(item);
99        }
100        mgr
101    }
102
103    /// 插入一个资源条目。如果 ID 已存在则覆盖。
104    pub fn insert(&mut self, item: ResourceItem) {
105        let id = item.id.clone();
106        let resource_type = item.resource_type;
107        self.by_type
108            .entry(resource_type)
109            .or_default()
110            .push(id.clone());
111        self.resources.insert(id, item);
112    }
113
114    /// 按 ID 获取资源条目。
115    ///
116    /// 对应 Java: `ResourceManage.get(String id)`
117    #[must_use]
118    pub fn get(&self, id: &str) -> Option<&ResourceItem> {
119        self.resources.get(id)
120    }
121
122    /// 获取所有资源条目。
123    #[must_use]
124    pub fn all(&self) -> &HashMap<String, ResourceItem> {
125        &self.resources
126    }
127
128    /// 按类型获取资源 ID 列表。
129    #[must_use]
130    pub fn ids_by_type(&self, resource_type: ResourceType) -> &[String] {
131        self.by_type.get(&resource_type).map_or(&[], Vec::as_slice)
132    }
133
134    /// 获取指定类型的全部资源条目。
135    #[must_use]
136    pub fn items_by_type(&self, resource_type: ResourceType) -> Vec<&ResourceItem> {
137        self.ids_by_type(resource_type)
138            .iter()
139            .filter_map(|id| self.resources.get(id))
140            .collect()
141    }
142
143    /// 资源总数。
144    #[must_use]
145    pub fn len(&self) -> usize {
146        self.resources.len()
147    }
148
149    /// 资源管理器是否为空。
150    #[must_use]
151    pub fn is_empty(&self) -> bool {
152        self.resources.is_empty()
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_empty_manager() {
162        let mgr = ResourceManage::new();
163        assert!(mgr.is_empty());
164        assert_eq!(mgr.len(), 0);
165        assert!(mgr.get("nonexistent").is_none());
166    }
167
168    #[test]
169    fn test_insert_and_get() {
170        let mut mgr = ResourceManage::new();
171        mgr.insert(
172            ResourceItem::new("1", ResourceType::Image)
173                .with_file_path("Res/image.png")
174                .with_format("PNG"),
175        );
176        assert_eq!(mgr.len(), 1);
177        let item = mgr.get("1").unwrap();
178        assert_eq!(item.resource_type, ResourceType::Image);
179        assert_eq!(item.file_path.as_deref(), Some("Res/image.png"));
180    }
181
182    #[test]
183    fn test_from_items() {
184        let items = vec![
185            ResourceItem::new("1", ResourceType::Font),
186            ResourceItem::new("2", ResourceType::Image),
187            ResourceItem::new("3", ResourceType::Font),
188        ];
189        let mgr = ResourceManage::from_items(items);
190        assert_eq!(mgr.len(), 3);
191        assert_eq!(mgr.ids_by_type(ResourceType::Font).len(), 2);
192        assert_eq!(mgr.ids_by_type(ResourceType::Image).len(), 1);
193    }
194
195    #[test]
196    fn test_items_by_type() {
197        let mut mgr = ResourceManage::new();
198        mgr.insert(ResourceItem::new("10", ResourceType::ColorSpace));
199        mgr.insert(ResourceItem::new("20", ResourceType::DrawParam));
200        mgr.insert(ResourceItem::new("30", ResourceType::ColorSpace));
201        let cs_items = mgr.items_by_type(ResourceType::ColorSpace);
202        assert_eq!(cs_items.len(), 2);
203    }
204
205    #[test]
206    fn test_insert_overwrite() {
207        let mut mgr = ResourceManage::new();
208        mgr.insert(ResourceItem::new("1", ResourceType::Image));
209        mgr.insert(ResourceItem::new("1", ResourceType::Font));
210        assert_eq!(mgr.len(), 1);
211        assert_eq!(mgr.get("1").unwrap().resource_type, ResourceType::Font);
212    }
213}