1use std::{collections::HashMap, io::Read};
2
3use raqote::{DrawOptions, DrawTarget, Source, StrokeStyle};
4
5use crate::{
6 error::{Error, Result},
7 id::{NoteUuid, PageModelUuid, PageUuid, PointsUuid, ShapeGroupUuid, VirtualPageUuid},
8 note_tree::{NoteMetadata, NoteTree},
9 page_model::{PageModel, PageModelGroup},
10 shape::ShapeGroup,
11 utils::convert_timestamp_to_datetime,
12 virtual_doc::VirtualDoc,
13 virtual_page::VirtualPage,
14};
15
16mod container;
17mod json;
18mod note_tree;
19mod page_model;
20mod utils;
21mod virtual_doc;
22
23pub mod error;
24pub mod id;
25pub mod points;
26pub mod shape;
27pub mod virtual_page;
28
29pub struct NoteFile<R: std::io::Read + std::io::Seek> {
30 container: container::Container<R>,
31 note_tree: NoteTree,
32}
33
34impl<R: std::io::Read + std::io::Seek> NoteFile<R> {
35 pub fn read(reader: R) -> Result<Self> {
36 let mut container = container::Container::open(reader).expect("Failed to open container");
37
38 let note_tree = if *container.container_type() == container::ContainerType::MultiNote {
39 container.get_file_relative("note_tree", |reader| NoteTree::read(reader))?
40 } else {
41 container.get_file_relative(
42 &format!("{}/note/pb/note_info", container.root_path()),
43 |reader| NoteTree::read(reader),
44 )?
45 };
46
47 Ok(Self {
48 container,
49 note_tree,
50 })
51 }
52
53 pub fn list_notes(&self) -> HashMap<NoteUuid, String> {
54 self.note_tree
55 .notes
56 .iter()
57 .map(|(id, metadata)| (*id, metadata.name.clone()))
58 .collect()
59 }
60
61 pub fn get_note(&self, note_id: &NoteUuid) -> Option<Note<R>> {
62 self.note_tree
63 .notes
64 .get(note_id)
65 .map(|metadata| Note::new(self.container.clone(), metadata.clone()))
66 }
67}
68
69impl<R: std::io::Read + std::io::Seek> std::fmt::Debug for NoteFile<R> {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct("NoteFile")
72 .field("container_type", &self.container.container_type())
73 .field("note_tree", &self.note_tree)
74 .finish()
75 }
76}
77
78pub struct Note<R: std::io::Read + std::io::Seek> {
79 container: container::Container<R>,
80 metadata: NoteMetadata,
81 virtual_doc: Option<VirtualDoc>,
82 virtual_pages: Option<HashMap<VirtualPageUuid, VirtualPage>>,
83 page_models: Option<HashMap<PageModelUuid, PageModelGroup>>,
84}
85
86impl<R: std::io::Read + std::io::Seek> Note<R> {
87 fn new(container: container::Container<R>, metadata: NoteMetadata) -> Self {
88 Self {
89 container,
90 metadata,
91 virtual_doc: None,
92 virtual_pages: None,
93 page_models: None,
94 }
95 }
96
97 pub fn name(&self) -> &str {
98 &self.metadata.name
99 }
100
101 pub fn active_pages(&self) -> &[PageUuid] {
102 &self.metadata.active_pages
103 }
104
105 pub fn reserved_pages(&self) -> &[PageUuid] {
106 &self.metadata.reserved_pages
107 }
108
109 pub fn detached_pages(&self) -> &[PageUuid] {
110 &self.metadata.detached_pages
111 }
112
113 pub fn created(&self) -> chrono::DateTime<chrono::Utc> {
114 self.metadata.created
115 }
116
117 pub fn modified(&self) -> chrono::DateTime<chrono::Utc> {
118 self.metadata.modified
119 }
120
121 pub fn flag(&self) -> u32 {
122 self.metadata.flag
123 }
124
125 pub fn pen_width(&self) -> f32 {
126 self.metadata.pen_width
127 }
128
129 pub fn scale_factor(&self) -> f32 {
130 self.metadata.scale_factor
131 }
132
133 pub fn fill_color(&self) -> &u32 {
134 &self.metadata.fill_color
135 }
136
137 pub fn pen_type(&self) -> &u32 {
138 &self.metadata.pen_type
139 }
140
141 pub fn pen_settings_fill_color(&self) -> &u32 {
142 &self.metadata.pen_settings.fill_color
143 }
144
145 pub fn pen_settings_graphics_shape_color(&self) -> &u32 {
146 &self.metadata.pen_settings.graphics_shape_color
147 }
148
149 pub fn get_page(&mut self, page_id: &PageUuid) -> Option<Page<R>> {
150 let virtual_page = {
151 let virtual_pages = self
152 .virtual_pages()
153 .inspect_err(|_| {
154 log::error!("Failed to get virtual pages for page ID: {}", page_id);
155 })
156 .ok()?;
157
158 virtual_pages
159 .values()
160 .find(|vp| &vp.page_id == page_id)
161 .cloned()
162 };
163
164 let page_model = {
165 let page_models = self
166 .page_models()
167 .inspect_err(|_| {
168 log::error!("Failed to get page models for page ID: {}", page_id);
169 })
170 .ok()?;
171
172 page_models
173 .values()
174 .find_map(|pm| pm.page_models.iter().find(|p| p.page_id == *page_id))?
175 .clone()
176 };
177
178 Some(Page::new(
179 self.container.clone(),
180 page_id.clone(),
181 self.metadata.note_id.clone(),
182 virtual_page,
183 page_model,
184 ))
185 }
186
187 pub fn virtual_doc(&mut self) -> Result<&VirtualDoc> {
188 if self.virtual_doc.is_none() {
189 let note_id = self.metadata.note_id.to_simple_string();
190 let virtual_doc = self.container.get_file_relative(
191 &format!("{}/virtual/doc/pb/{}", note_id, note_id),
192 |reader| VirtualDoc::read(reader),
193 )?;
194 self.virtual_doc = Some(virtual_doc);
195 }
196 Ok(self.virtual_doc.as_ref().unwrap())
197 }
198
199 pub fn virtual_pages(&mut self) -> Result<&HashMap<VirtualPageUuid, VirtualPage>> {
200 if self.virtual_pages.is_none() {
201 let note_id = self.metadata.note_id.to_simple_string();
202
203 let mut virtual_pages = HashMap::new();
204
205 for virtual_page_path in self
206 .container
207 .list_directory(&format!("{}/virtual/page/pb", note_id))
208 {
209 let virtual_page_id =
210 VirtualPageUuid::from_str(&virtual_page_path.rsplit('/').next().unwrap())?;
211 let virtual_page = self
212 .container
213 .get_file_absolute(&virtual_page_path, |reader| VirtualPage::read(reader))?;
214 virtual_pages.insert(virtual_page_id, virtual_page);
215 }
216 self.virtual_pages = Some(virtual_pages);
217 }
218 Ok(self.virtual_pages.as_ref().unwrap())
219 }
220
221 pub fn page_models(&mut self) -> Result<&HashMap<PageModelUuid, PageModelGroup>> {
222 if self.page_models.is_none() {
223 let note_id = self.metadata.note_id.to_simple_string();
224
225 let mut page_models = HashMap::new();
226
227 for page_model_path in self
228 .container
229 .list_directory(&format!("{}/pageModel/pb", note_id))
230 {
231 let page_model_id =
232 PageModelUuid::from_str(&page_model_path.rsplit('/').next().unwrap())?;
233 let page_model = self
234 .container
235 .get_file_absolute(&page_model_path, |reader| PageModelGroup::read(reader))?;
236 page_models.insert(page_model_id, page_model);
237 }
238 self.page_models = Some(page_models);
239 }
240 Ok(self.page_models.as_ref().unwrap())
241 }
242}
243
244impl<R: std::io::Read + std::io::Seek> std::fmt::Debug for Note<R> {
245 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246 f.debug_struct("Note")
247 .field("metadata", &self.metadata)
248 .finish()
249 }
250}
251
252pub struct Page<R: std::io::Read + std::io::Seek> {
253 container: container::Container<R>,
254 note_id: NoteUuid,
255 page_id: PageUuid,
256 virtual_page: Option<VirtualPage>,
257 page_model: PageModel,
258 shape_groups: Option<HashMap<ShapeGroupUuid, ShapeGroup>>,
259 points_files: Option<HashMap<PointsUuid, Vec<points::PointsFile>>>,
260}
261
262impl<R: std::io::Read + std::io::Seek> Page<R> {
263 fn new(
264 container: container::Container<R>,
265 page_id: PageUuid,
266 note_id: NoteUuid,
267 virtual_page: Option<VirtualPage>,
268 page_model: PageModel,
269 ) -> Self {
270 Self {
271 container,
272 page_id,
273 note_id,
274 virtual_page,
275 page_model,
276 shape_groups: None,
277 points_files: None,
278 }
279 }
280
281 pub fn virtual_page(&self) -> &Option<VirtualPage> {
282 &self.virtual_page
283 }
284
285 pub fn page_model(&self) -> &PageModel {
286 &self.page_model
287 }
288
289 pub fn shape_groups(&mut self) -> Result<&HashMap<ShapeGroupUuid, ShapeGroup>> {
290 if self.shape_groups.is_none() {
291 let note_id = self.note_id.to_simple_string();
292 let page_id = self.page_id.to_simple_string();
293
294 let mut shape_groups = HashMap::new();
295
296 for shape_group_path in self
297 .container
298 .list_directory(&format!("{}/shape/{}#", note_id, page_id))
299 {
300 let path_tail = shape_group_path.rsplit('/').next().unwrap();
301 let parts = path_tail.split('#').collect::<Vec<_>>();
302 let shape_group_id = ShapeGroupUuid::from_str(parts[1])?;
303 let _timestamp = convert_timestamp_to_datetime(
304 parts[2].replace(".zip", "").parse::<u64>().map_err(|e| {
305 Error::InvalidTimestampFormat(format!("Failed to parse timestamp: {}", e))
306 })?,
307 );
308 let shape_group = self
309 .container
310 .get_file_absolute(&shape_group_path, |reader| ShapeGroup::read(reader))?;
311 shape_groups.insert(shape_group_id, shape_group);
312 }
313 self.shape_groups = Some(shape_groups);
314 }
315 Ok(self.shape_groups.as_ref().unwrap())
316 }
317
318 pub fn points_files(&mut self) -> Result<&HashMap<PointsUuid, Vec<points::PointsFile>>> {
319 if self.points_files.is_none() {
320 let note_id = self.note_id.to_simple_string();
321 let page_id = self.page_id.to_simple_string();
322
323 let mut points_files = HashMap::new();
324
325 for stroke_path in self
326 .container
327 .list_directory(&format!("{}/point/{}/{}#", note_id, page_id, page_id))
328 {
329 let path_tail = stroke_path.rsplit('/').next().unwrap();
330 let parts = path_tail.split('#').collect::<Vec<_>>();
331 let shape_id = PointsUuid::from_str(parts[1])?;
332
333 let file_data = self
334 .container
335 .get_file_absolute(&stroke_path, |mut reader| {
336 let mut buffer = Vec::new();
337 reader.read_to_end(&mut buffer).map_err(Error::Io)?;
338 Ok(buffer)
339 })?;
340
341 let buffer_cursor = std::io::Cursor::new(file_data);
342 let points_file = points::PointsFile::read(buffer_cursor)?;
343
344 points_files
345 .entry(shape_id)
346 .or_insert_with(Vec::new)
347 .push(points_file);
348 }
349 self.points_files = Some(points_files);
350 }
351 Ok(self.points_files.as_ref().unwrap())
352 }
353
354 pub fn render(&mut self) -> Result<DrawTarget> {
355 let page_id = self.page_id.to_hyphenated_string();
356 let width = self.page_model.dimensions.right - self.page_model.dimensions.left;
357 let height = self.page_model.dimensions.bottom - self.page_model.dimensions.top;
358 let mut draw_target = DrawTarget::new(width as i32, height as i32);
359 let draw_options = DrawOptions::new();
360
361 draw_target.fill_rect(
362 0.0,
363 0.0,
364 width,
365 height,
366 &Source::Solid(raqote::Color::new(255, 255, 255, 255).into()),
367 &DrawOptions::new(),
368 );
369
370 let shape_groups = {
372 let sg = self.shape_groups().inspect_err(|_| {
373 log::error!("Failed to get shape groups for page ID: {}", page_id)
374 })?;
375 sg.clone()
376 };
377
378 let points_files_vec = {
379 let pf = self.points_files().inspect_err(|_| {
380 log::error!("Failed to get points files for page ID: {}", page_id)
381 })?;
382 pf.values().flatten().collect::<Vec<_>>()
383 };
384
385 for (shape_group_id, shape_group) in &shape_groups {
386 let mut shapes = shape_group.shapes().to_vec();
387 shapes.sort_by(|a, b| a.z_order.cmp(&b.z_order));
388
389 for shape in shapes {
390 if let Some(points_id) = shape.points_id {
391 if let Some(points) = points_files_vec
392 .iter()
393 .find(|pf| pf.header().points_id == points_id)
394 {
395 points
396 .get_stroke(&shape.stroke_id)
397 .ok_or_else(|| {
398 log::error!("Failed to get stroke for shape");
399 Error::StrokeNotFound
400 })
401 .and_then(|stroke| {
402 log::debug!("Rendering stroke for shape");
403 log::debug!(
404 "Shape Group ID: {}, Stroke ID: {}",
405 shape_group_id.to_hyphenated_string(),
406 shape.stroke_id.to_hyphenated_string()
407 );
408 log::debug!("Shape: {:#x?}", shape);
409 stroke.render(
410 &mut draw_target,
411 &draw_options,
412 &StrokeStyle::default(),
413 )
414 })?;
415 } else {
416 log::warn!(
417 "No points files found for shape group: {}",
418 shape_group_id.to_hyphenated_string()
419 );
420 }
421 }
422 }
423 }
424
425 Ok(draw_target)
426 }
427}
428
429impl<R: std::io::Read + std::io::Seek> std::fmt::Debug for Page<R> {
430 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431 f.debug_struct("Page")
432 .field("virtual_page", &self.virtual_page)
433 .field("page_model", &self.page_model)
434 .finish()
435 }
436}