boox_note_parser/
template.rs1use 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}