boox_note_parser/
points.rs1use std::collections::HashMap;
2
3use byteorder::{BE, ReadBytesExt};
4use raqote::{DrawOptions, DrawTarget, PathBuilder, Source, StrokeStyle};
5
6use crate::{
7 error::{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_str = str::from_utf8(&buffer).map_err(|e| Error::UuidInvalidUtf8(e))?;
29 let page_id = PageUuid::from_str(page_id_str.trim())?;
30
31 buffer.fill(0);
33 reader.read_exact(&mut buffer)?;
34 let points_id_str = str::from_utf8(&buffer).map_err(|e| Error::UuidInvalidUtf8(e))?;
35 let points_id = PointsUuid::from_str(points_id_str)?;
36
37 Ok(Self {
38 version,
39 page_id,
40 points_id,
41 })
42 }
43}
44
45#[derive(Debug, Clone, PartialEq)]
46pub struct PointsTableEntry {
47 pub stroke_id: StrokeUuid,
48 pub start_addr: u32,
50 pub point_count: u32,
52 pub flag: u8,
54}
55
56impl PointsTableEntry {
57 pub fn read(reader: impl std::io::Read + std::io::Seek) -> Result<Self> {
58 let mut reader = reader;
59
60 let mut buffer = [0; 36];
61 reader.read_exact(&mut buffer)?;
62 let stroke_uuid_str = str::from_utf8(&buffer).map_err(|e| Error::UuidInvalidUtf8(e))?;
63 let stroke_uuid = StrokeUuid::from_str(stroke_uuid_str)?;
64
65 let start_addr = reader.read_u32::<BE>()?;
66 let packed = reader.read_u32::<BE>()?;
67
68 let point_count = (packed >> 4) & 0x0FFFFFFF; let flag = (packed & 0xF) as u8; Ok(Self {
72 stroke_id: stroke_uuid,
73 start_addr,
74 point_count,
75 flag,
76 })
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
81pub struct Point {
82 pub timestamp_rel: u32,
83 pub x: f32,
84 pub y: f32,
85 pub tilt_x: i8,
86 pub tilt_y: i8,
87 pub pressure: u16,
88}
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct Stroke {
92 pub points: Vec<Point>,
93}
94
95impl Stroke {
96 pub fn read(
97 reader: impl std::io::Read + std::io::Seek,
98 entry: &PointsTableEntry,
99 ) -> Result<Self> {
100 let mut reader = reader;
101
102 reader.seek(std::io::SeekFrom::Start(entry.start_addr as u64))?;
103
104 let mut points = Vec::with_capacity(entry.point_count as usize);
105 for _ in 0..entry.point_count {
106 let timestamp_rel = reader.read_u32::<BE>()?;
107 let x = reader.read_f32::<BE>()?;
108 let y = reader.read_f32::<BE>()?;
109 let tilt_x = reader.read_i8()?;
110 let tilt_y = reader.read_i8()?;
111 let pressure = reader.read_u16::<BE>()?;
112
113 points.push(Point {
114 timestamp_rel,
115 x,
116 y,
117 tilt_x,
118 tilt_y,
119 pressure,
120 });
121 }
122
123 Ok(Self { points })
124 }
125
126 pub fn render(
127 &self,
128 draw_target: &mut DrawTarget,
129 draw_options: &DrawOptions,
130 stroke_style: &StrokeStyle,
131 ) -> Result<()> {
132 if self.points.is_empty() {
133 log::warn!("No points to draw for stroke");
134 return Ok(());
135 }
136
137 let mut path = PathBuilder::new();
138 let mut first_point = true;
139
140 for point in &self.points {
141 if first_point {
142 path.move_to(point.x, point.y);
143 first_point = false;
144 } else {
145 path.line_to(point.x, point.y);
146 }
147 }
148
149 draw_target.stroke(
150 &path.finish(),
151 &Source::Solid(raqote::Color::new(255, 0, 0, 0).into()),
152 stroke_style,
153 draw_options,
154 );
155
156 Ok(())
157 }
158}
159
160#[derive(Debug, Clone, PartialEq)]
161pub struct PointsFile {
162 header: Header,
163 points: HashMap<StrokeUuid, Stroke>,
164}
165
166impl PointsFile {
167 pub fn read(mut reader: impl std::io::Read + std::io::Seek) -> Result<Self> {
168 let header = Header::read(&mut reader)?;
169
170 let points_table_end = reader.seek(std::io::SeekFrom::End(-4))?;
171 let points_table_start = reader.read_u32::<BE>()?;
172
173 let mut points_table = Vec::new();
174
175 reader.seek(std::io::SeekFrom::Start(points_table_start as u64))?;
176 while reader.stream_position()? < points_table_end {
177 let entry = PointsTableEntry::read(&mut reader)?;
178 points_table.push(entry);
179 }
180
181 let mut points = HashMap::new();
182 for entry in points_table {
183 let stroke = Stroke::read(&mut reader, &entry)?;
184 points.insert(entry.stroke_id, stroke);
185 }
186
187 Ok(Self { header, points })
188 }
189
190 pub fn header(&self) -> &Header {
191 &self.header
192 }
193
194 pub fn get_stroke(&self, stroke_id: &StrokeUuid) -> Option<&Stroke> {
195 self.points.get(stroke_id)
196 }
197}