1use std::collections::HashMap;
2
3use byteorder::{BE, ReadBytesExt};
4use raqote::{DrawOptions, DrawTarget, PathBuilder, Source, StrokeStyle};
5
6use crate::{
7 error::Result,
8 id::{PageUuid, PointsUuid, StrokeUuid},
9};
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct Header {
13 pub version: u32,
14 pub page_id: PageUuid,
15 pub points_id: PointsUuid,
16}
17
18impl Header {
19 pub fn read(reader: impl std::io::Read + std::io::Seek) -> Result<Self> {
20 let mut reader = reader;
21 reader.seek(std::io::SeekFrom::Start(0))?;
22
23 let version = reader.read_u32::<BE>()?;
24
25 let mut buffer = [0; 36];
26
27 reader.read_exact(&mut buffer)?;
28 let page_id = PageUuid::from_byte_str(&buffer)?;
29
30 buffer.fill(0);
32 reader.read_exact(&mut buffer)?;
33 let points_id = PointsUuid::from_byte_str(&buffer)?;
34
35 Ok(Self {
36 version,
37 page_id,
38 points_id,
39 })
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
44pub struct PointsTableEntry {
45 pub stroke_id: StrokeUuid,
46 pub start_addr: u32,
48 pub point_count: u32,
50 pub flag: u8,
52}
53
54impl PointsTableEntry {
55 pub fn read(reader: impl std::io::Read + std::io::Seek) -> Result<Self> {
56 let mut reader = reader;
57
58 let mut buffer = [0; 36];
59 reader.read_exact(&mut buffer)?;
60 let stroke_uuid = StrokeUuid::from_byte_str(&buffer)?;
61
62 let start_addr = reader.read_u32::<BE>()?;
63 let packed = reader.read_u32::<BE>()?;
64
65 let point_count = (packed >> 4) & 0x0FFFFFFF; let flag = (packed & 0xF) as u8; Ok(Self {
69 stroke_id: stroke_uuid,
70 start_addr,
71 point_count,
72 flag,
73 })
74 }
75}
76
77#[derive(Debug, Clone, PartialEq)]
78pub struct Point {
79 pub timestamp_rel: u32,
80 pub x: f32,
81 pub y: f32,
82 pub tilt_x: i8,
83 pub tilt_y: i8,
84 pub pressure: u16,
85}
86
87#[derive(Debug, Clone, PartialEq)]
88pub struct Stroke {
89 pub points: Vec<Point>,
90}
91
92impl Stroke {
93 pub fn read(
94 reader: impl std::io::Read + std::io::Seek,
95 entry: &PointsTableEntry,
96 ) -> Result<Self> {
97 let mut reader = reader;
98
99 reader.seek(std::io::SeekFrom::Start(entry.start_addr as u64))?;
100
101 let mut points = Vec::with_capacity(entry.point_count as usize);
102 for _ in 0..entry.point_count {
103 let timestamp_rel = reader.read_u32::<BE>()?;
104 let x = reader.read_f32::<BE>()?;
105 let y = reader.read_f32::<BE>()?;
106 let tilt_x = reader.read_i8()?;
107 let tilt_y = reader.read_i8()?;
108 let pressure = reader.read_u16::<BE>()?;
109
110 points.push(Point {
111 timestamp_rel,
112 x,
113 y,
114 tilt_x,
115 tilt_y,
116 pressure,
117 });
118 }
119
120 Ok(Self { points })
121 }
122
123 pub fn render(
124 &self,
125 draw_target: &mut DrawTarget,
126 draw_options: &DrawOptions,
127 stroke_style: &StrokeStyle,
128 ) -> Result<()> {
129 self.render_with_color(
130 draw_target,
131 draw_options,
132 stroke_style,
133 raqote::Color::new(255, 0, 0, 0),
134 )
135 }
136
137 pub fn render_with_color(
138 &self,
139 draw_target: &mut DrawTarget,
140 draw_options: &DrawOptions,
141 stroke_style: &StrokeStyle,
142 color: raqote::Color,
143 ) -> Result<()> {
144 if self.points.is_empty() {
145 log::warn!("No points to draw for stroke");
146 return Ok(());
147 }
148
149 let mut path = PathBuilder::new();
150 let mut first_point = true;
151
152 for point in &self.points {
153 if first_point {
154 path.move_to(point.x, point.y);
155 first_point = false;
156 } else {
157 path.line_to(point.x, point.y);
158 }
159 }
160
161 draw_target.stroke(
162 &path.finish(),
163 &Source::Solid(color.into()),
164 stroke_style,
165 draw_options,
166 );
167
168 Ok(())
169 }
170}
171
172#[derive(Debug, Clone, PartialEq)]
173pub struct PointsFile {
174 header: Header,
175 points: HashMap<StrokeUuid, Stroke>,
176}
177
178impl PointsFile {
179 pub fn read(mut reader: impl std::io::Read + std::io::Seek) -> Result<Self> {
180 let header = Header::read(&mut reader)?;
181
182 let points_table_end = reader.seek(std::io::SeekFrom::End(-4))?;
183 let points_table_start = reader.read_u32::<BE>()?;
184
185 let mut points_table = Vec::new();
186
187 reader.seek(std::io::SeekFrom::Start(points_table_start as u64))?;
188 while reader.stream_position()? < points_table_end {
189 let entry = PointsTableEntry::read(&mut reader)?;
190 points_table.push(entry);
191 }
192
193 let mut points = HashMap::new();
194 for entry in points_table {
195 let stroke = Stroke::read(&mut reader, &entry)?;
196 points.insert(entry.stroke_id, stroke);
197 }
198
199 Ok(Self { header, points })
200 }
201
202 pub fn header(&self) -> &Header {
203 &self.header
204 }
205
206 pub fn get_stroke(&self, stroke_id: &StrokeUuid) -> Option<&Stroke> {
207 self.points.get(stroke_id)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use byteorder::{BE, WriteBytesExt};
214
215 use super::{Header, PointsTableEntry};
216 use crate::id::{PageUuid, PointsUuid, StrokeUuid};
217
218 #[test]
219 fn header_parses_padded_uuid_fields() {
220 let mut bytes = Vec::new();
221 bytes.write_u32::<BE>(1).unwrap();
222 bytes.extend_from_slice(b"ba338e220eda49268c7126a02970a160 ");
223 bytes.extend_from_slice(b"e858c829-d2f3-4994-b9fb-95bcc8003fa3");
224
225 let header = Header::read(std::io::Cursor::new(bytes)).unwrap();
226
227 assert_eq!(header.version, 1);
228 assert_eq!(
229 header.page_id,
230 PageUuid::from_str("ba338e220eda49268c7126a02970a160").unwrap()
231 );
232 assert_eq!(
233 header.points_id,
234 PointsUuid::from_str("e858c829-d2f3-4994-b9fb-95bcc8003fa3").unwrap()
235 );
236 }
237
238 #[test]
239 fn points_table_entry_parses_padded_stroke_id() {
240 let mut bytes = Vec::new();
241 bytes.extend_from_slice(b"92c1ab734ec14f70907adc11dcb0806d ");
242 bytes.write_u32::<BE>(64).unwrap();
243 bytes.write_u32::<BE>((3 << 4) | 5).unwrap();
244
245 let entry = PointsTableEntry::read(std::io::Cursor::new(bytes)).unwrap();
246
247 assert_eq!(
248 entry.stroke_id,
249 StrokeUuid::from_str("92c1ab73-4ec1-4f70-907a-dc11dcb0806d").unwrap()
250 );
251 assert_eq!(entry.start_addr, 64);
252 assert_eq!(entry.point_count, 3);
253 assert_eq!(entry.flag, 5);
254 }
255}