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 unknown_6: u32,
48 pub bbox: Dimensions,
49 pub render_scale: DisplayScale,
50 pub z_order: i64,
51 pub unknown_15: u32,
52 pub points_id: Option<PointsUuid>,
53 pub line_style: Option<LineStyle>,
54 pub shape_group_id: ShapeGroupUuid,
55 pub points_json: String,
56}
57
58impl Shape {
59 fn from_protobuf(shape: &protobuf::Shape) -> crate::error::Result<Self> {
60 Ok(Self {
61 stroke_id: StrokeUuid::from_str(&shape.stroke_uuid)?,
62 created: convert_timestamp_to_datetime(shape.created)?,
63 modified: convert_timestamp_to_datetime(shape.modified)?,
64 unknown: shape.unknown,
65 stroke_width: shape.stroke_width,
66 unknown_6: shape.unknown_6,
67 bbox: parse_json(&shape.bbox_json)?,
68 render_scale: parse_json(&shape.render_scale_json)?,
69 z_order: shape.z_order,
70 unknown_15: shape.unknown_15,
71 points_id: if shape.points_uuid.is_empty() {
72 None
73 } else {
74 Some(PointsUuid::from_str(&shape.points_uuid)?)
75 },
76 line_style: if shape.line_style_json.is_empty() {
77 None
78 } else {
79 let line_style_container: LineStyleContainer = parse_json(&shape.line_style_json)?;
80 Some(line_style_container.line_style)
81 },
82 shape_group_id: ShapeGroupUuid::from_str(&shape.shape_group_uuid)?,
83 points_json: shape.empty_array_json.clone(),
84 })
85 }
86}
87
88mod json {
89 use serde::Deserialize;
90
91 #[derive(Debug, Clone, Deserialize)]
92 #[allow(dead_code)]
94 #[serde(rename_all = "camelCase")]
95 pub struct DisplayScale {
96 display_scale: f32,
97 max_pressure: f32,
98 #[serde(default)]
99 revised_display_scale: f32,
100 source: u32,
101 }
102
103 #[derive(Debug, Clone, Deserialize)]
104 #[serde(rename_all = "camelCase")]
105 pub struct LineStyleContainer {
106 pub line_style: LineStyle,
107 }
108
109 #[derive(Debug, Clone, Deserialize)]
110 #[serde(rename_all = "camelCase")]
111 pub struct LineStyle {
112 pub phase: f32,
113 pub type_: u8,
114 }
115}
116
117mod protobuf {
118 use prost::Message;
119
120 use crate::error::Result;
121
122 #[derive(Clone, PartialEq, Message)]
123 pub struct ShapeContainer {
124 #[prost(message, repeated, tag = "1")]
125 pub shapes: Vec<Shape>,
126 }
127
128 impl ShapeContainer {
129 pub fn read(mut reader: impl std::io::Read) -> Result<Self> {
130 let mut buf = Vec::new();
131 reader.read_to_end(&mut buf)?;
132 Ok(ShapeContainer::decode(&buf[..])?)
133 }
134 }
135
136 #[derive(Clone, PartialEq, Message)]
137 pub struct Shape {
138 #[prost(string, tag = "1")]
140 pub stroke_uuid: String,
141 #[prost(uint64, tag = "2")]
143 pub created: u64,
144 #[prost(uint64, tag = "3")]
146 pub modified: u64,
147 #[prost(int64, tag = "4")]
149 pub unknown: i64,
150 #[prost(float, tag = "5")]
152 pub stroke_width: f32,
153 #[prost(uint32, tag = "6")]
155 pub unknown_6: u32,
156 #[prost(string, tag = "7")]
158 pub bbox_json: String,
159 #[prost(string, tag = "11")]
161 pub render_scale_json: String,
162 #[prost(int64, tag = "12")]
164 pub z_order: i64,
165 #[prost(uint32, tag = "15")]
167 pub unknown_15: u32,
168 #[prost(string, tag = "16")]
170 pub points_uuid: String,
171 #[prost(string, tag = "17")]
173 pub line_style_json: String,
174 #[prost(string, tag = "18")]
176 pub shape_group_uuid: String,
177 #[prost(string, tag = "21")]
179 pub empty_array_json: String,
180 }
181}