boox_note_parser/
shape.rs

1use zip::ZipArchive;
2
3use crate::{
4    id::{PointsUuid, ShapeGroupUuid, StrokeUuid},
5    json::Dimensions,
6    shape::json::{DisplayScale, LineStyle, LineStyleContainer},
7    utils::{convert_timestamp_to_datetime, parse_json},
8};
9
10#[derive(Debug, Clone)]
11pub struct ShapeGroup {
12    shapes: Vec<Shape>,
13}
14
15impl ShapeGroup {
16    pub fn read(mut reader: impl std::io::Read) -> crate::error::Result<Self> {
17        let mut buf = Vec::new();
18        reader.read_to_end(&mut buf)?;
19
20        let reader = std::io::Cursor::new(buf);
21
22        let mut archive = ZipArchive::new(reader)?;
23
24        let mut reader = archive.by_index(0)?;
25
26        let container = protobuf::ShapeContainer::read(&mut reader)?;
27        let shapes = container
28            .shapes
29            .iter()
30            .map(Shape::from_protobuf)
31            .collect::<crate::error::Result<_>>()?;
32        Ok(Self { shapes })
33    }
34    
35    pub fn shapes(&self) -> &[Shape] {
36        &self.shapes
37    }
38}
39
40#[derive(Debug, Clone)]
41pub struct Shape {
42    pub stroke_id: StrokeUuid,
43    pub created: chrono::DateTime<chrono::Utc>,
44    pub modified: chrono::DateTime<chrono::Utc>,
45    pub unknown: i64,
46    pub stroke_width: f32,
47    pub bbox: Dimensions,
48    pub render_scale: DisplayScale,
49    pub z_order: i64,
50    pub points_id: Option<PointsUuid>,
51    pub line_style: Option<LineStyle>,
52    pub shape_group_id: ShapeGroupUuid,
53    pub points_json: String,
54}
55
56impl Shape {
57    fn from_protobuf(shape: &protobuf::Shape) -> crate::error::Result<Self> {
58        Ok(Self {
59            stroke_id: StrokeUuid::from_str(&shape.stroke_uuid)?,
60            created: convert_timestamp_to_datetime(shape.created)?,
61            modified: convert_timestamp_to_datetime(shape.modified)?,
62            unknown: shape.unknown,
63            stroke_width: shape.stroke_width,
64            bbox: parse_json(&shape.bbox_json)?,
65            render_scale: parse_json(&shape.render_scale_json)?,
66            z_order: shape.z_order,
67            points_id: if shape.points_uuid.is_empty() {
68                None
69            } else {
70                Some(PointsUuid::from_str(&shape.points_uuid)?)
71            },
72            line_style: if shape.line_style_json.is_empty() {
73                None
74            } else {
75                let line_style_container: LineStyleContainer = parse_json(&shape.line_style_json)?;
76                Some(line_style_container.line_style)
77            },
78            shape_group_id: ShapeGroupUuid::from_str(&shape.shape_group_uuid)?,
79            points_json: shape.empty_array_json.clone(),
80        })
81    }
82}
83
84mod json {
85    use serde::Deserialize;
86
87    #[derive(Debug, Clone, Deserialize)]
88    #[serde(rename_all = "camelCase")]
89    pub struct DisplayScale {
90        display_scale: f32,
91        max_pressure: f32,
92        revised_display_scale: f32,
93        source: u32,
94    }
95
96    #[derive(Debug, Clone, Deserialize)]
97    #[serde(rename_all = "camelCase")]
98    pub struct LineStyleContainer {
99        pub line_style: LineStyle,
100    }
101
102    #[derive(Debug, Clone, Deserialize)]
103    #[serde(rename_all = "camelCase")]
104    pub struct LineStyle {
105        pub phase: f32,
106        pub type_: u8,
107    }
108}
109
110mod protobuf {
111    use prost::Message;
112
113    use crate::error::Result;
114
115    #[derive(Clone, PartialEq, Message)]
116    pub struct ShapeContainer {
117        #[prost(message, repeated, tag = "1")]
118        pub shapes: Vec<Shape>,
119    }
120
121    impl ShapeContainer {
122        pub fn read(mut reader: impl std::io::Read) -> Result<Self> {
123            let mut buf = Vec::new();
124            reader.read_to_end(&mut buf)?;
125            Ok(ShapeContainer::decode(&buf[..])?)
126        }
127    }
128
129    #[derive(Clone, PartialEq, Message)]
130    pub struct Shape {
131        // Confirmed
132        #[prost(string, tag = "1")]
133        pub stroke_uuid: String,
134        // Confirmed
135        #[prost(uint64, tag = "2")]
136        pub created: u64,
137        // Confirmed
138        #[prost(uint64, tag = "3")]
139        pub modified: u64,
140        // Uncertain
141        #[prost(sint64, tag = "4")]
142        pub unknown: i64,
143        // Uncertain
144        #[prost(float, tag = "5")]
145        pub stroke_width: f32,
146        // Confirmed
147        #[prost(string, tag = "7")]
148        pub bbox_json: String,
149        // Confirmed
150        #[prost(string, tag = "11")]
151        pub render_scale_json: String,
152        // Uncertain
153        #[prost(sint64, tag = "12")]
154        pub z_order: i64,
155        // Confirmed
156        #[prost(string, tag = "16")]
157        pub points_uuid: String,
158        // Confirmed
159        #[prost(string, tag = "17")]
160        pub line_style_json: String,
161        // Confirmed
162        #[prost(string, tag = "18")]
163        pub shape_group_uuid: String,
164        // Uncertain
165        #[prost(string, tag = "21")]
166        pub empty_array_json: String,
167    }
168}