Skip to main content

boox_note_parser/
template.rs

1use std::collections::HashMap;
2
3use serde::Deserialize;
4
5use crate::{error::Result, id::PageUuid, json::Dimensions, utils::parse_json};
6
7pub const DEFAULT_TEMPLATE_FILE_NAME: &str = ".template_json";
8pub const TEMPLATE_FILE_SUFFIX: &str = ".template_json";
9
10#[derive(Debug, Clone)]
11pub struct TemplateSet {
12    pub default: Option<TemplateDescriptor>,
13    pub by_page: HashMap<PageUuid, TemplateDescriptor>,
14    pub by_other_id: HashMap<String, TemplateDescriptor>,
15}
16
17impl TemplateSet {
18    pub fn new() -> Self {
19        Self {
20            default: None,
21            by_page: HashMap::new(),
22            by_other_id: HashMap::new(),
23        }
24    }
25
26    pub fn insert_if_absent(&mut self, key: TemplateKey, descriptor: TemplateDescriptor) {
27        match key {
28            TemplateKey::Default => {
29                if self.default.is_none() {
30                    self.default = Some(descriptor);
31                }
32            }
33            TemplateKey::Page(page_id) => {
34                self.by_page.entry(page_id).or_insert(descriptor);
35            }
36            TemplateKey::Other(id) => {
37                self.by_other_id.entry(id).or_insert(descriptor);
38            }
39        }
40    }
41
42    pub fn for_page(&self, page_id: &PageUuid) -> Option<&TemplateDescriptor> {
43        self.by_page.get(page_id)
44    }
45}
46
47impl Default for TemplateSet {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum TemplateKey {
55    Default,
56    Page(PageUuid),
57    Other(String),
58}
59
60pub fn classify_template_file_name(file_name: &str) -> Option<TemplateKey> {
61    if file_name == DEFAULT_TEMPLATE_FILE_NAME {
62        return Some(TemplateKey::Default);
63    }
64
65    let id = file_name.strip_suffix(TEMPLATE_FILE_SUFFIX)?;
66    if id.is_empty() {
67        return None;
68    }
69
70    if let Ok(page_id) = PageUuid::from_str(id) {
71        return Some(TemplateKey::Page(page_id));
72    }
73
74    Some(TemplateKey::Other(id.to_string()))
75}
76
77#[derive(Debug, Clone, Deserialize)]
78pub struct TemplateDescriptor {
79    #[serde(rename = "type")]
80    pub type_: String,
81    pub properties: TemplateProperties,
82}
83
84impl TemplateDescriptor {
85    pub fn read(mut reader: impl std::io::Read) -> Result<Self> {
86        let mut json_string = String::new();
87        reader.read_to_string(&mut json_string)?;
88        parse_json(&json_string)
89    }
90}
91
92#[derive(Debug, Clone, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct TemplateProperties {
95    pub layout_type: String,
96    pub page_margins: PageMargins,
97    pub radius: f32,
98    pub resource_attr: ResourceAttr,
99    pub selection_point_type: String,
100    pub sub_type: String,
101    pub under_content: bool,
102}
103
104#[derive(Debug, Clone, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct PageMargins {
107    pub layout_rect: Dimensions,
108    pub padding_bottom: f32,
109    pub padding_left: f32,
110    pub padding_right: f32,
111    pub padding_top: f32,
112    pub page_padding_rect: Dimensions,
113    pub spacing: f32,
114}
115
116#[derive(Debug, Clone, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct ResourceAttr {
119    pub res_name: String,
120}
121
122#[cfg(test)]
123mod tests {
124    use super::{TemplateDescriptor, TemplateKey, classify_template_file_name};
125    use crate::id::PageUuid;
126
127    #[test]
128    fn classifies_default_template_filename() {
129        let key = classify_template_file_name(".template_json").unwrap();
130        assert_eq!(key, TemplateKey::Default);
131    }
132
133    #[test]
134    fn classifies_page_template_filename() {
135        let key =
136            classify_template_file_name("ba338e220eda49268c7126a02970a160.template_json").unwrap();
137        assert_eq!(
138            key,
139            TemplateKey::Page(PageUuid::from_str("ba338e220eda49268c7126a02970a160").unwrap())
140        );
141    }
142
143    #[test]
144    fn classifies_non_uuid_template_filename_as_other() {
145        let key = classify_template_file_name("custom-background.template_json").unwrap();
146        assert_eq!(key, TemplateKey::Other("custom-background".to_string()));
147    }
148
149    #[test]
150    fn parses_template_descriptor_json() {
151        let json = r#"{"properties":{"layoutType":"LayoutResVector","pageMargins":{"layoutRect":{"bottom":0.0,"empty":true,"left":0.0,"right":0.0,"stability":0,"top":0.0},"paddingBottom":0.0,"paddingLeft":0.0,"paddingRight":0.0,"paddingTop":0.0,"pagePaddingRect":{"bottom":0.0,"empty":true,"left":0.0,"right":0.0,"stability":0,"top":0.0},"spacing":10.0},"radius":0.0,"resourceAttr":{"resName":"com.onyx.android.note:drawable/ic_to_do_list"},"selectionPointType":"SCALE","subType":"Layout","underContent":false},"type":"Feature"}"#;
152        let descriptor = TemplateDescriptor::read(std::io::Cursor::new(json)).unwrap();
153
154        assert_eq!(descriptor.type_, "Feature");
155        assert_eq!(
156            descriptor.properties.resource_attr.res_name,
157            "com.onyx.android.note:drawable/ic_to_do_list"
158        );
159        assert_eq!(descriptor.properties.page_margins.spacing, 10.0);
160    }
161}