onenote_parser/onenote/
ink.rs1use crate::errors::{ErrorKind, Result};
2use crate::fsshttpb::data::exguid::ExGuid;
3use crate::one::property_set::{
4 ink_container, ink_data_node, ink_stroke_node, stroke_properties_node,
5};
6use crate::onestore::object_space::ObjectSpace;
7
8#[derive(Clone, Debug)]
10pub struct Ink {
11 pub(crate) ink_strokes: Vec<InkStroke>,
12 pub(crate) bounding_box: Option<InkBoundingBox>,
13
14 pub(crate) offset_horizontal: Option<f32>,
15 pub(crate) offset_vertical: Option<f32>,
16}
17
18impl Ink {
19 pub fn ink_strokes(&self) -> &[InkStroke] {
21 &self.ink_strokes
22 }
23
24 pub fn bounding_box(&self) -> Option<InkBoundingBox> {
26 self.bounding_box
27 }
28
29 pub fn offset_horizontal(&self) -> Option<f32> {
35 self.offset_horizontal
36 }
37
38 pub fn offset_vertical(&self) -> Option<f32> {
44 self.offset_vertical
45 }
46}
47
48#[derive(Clone, Debug)]
50pub struct InkStroke {
51 pub(crate) path: Vec<InkPoint>,
52 pub(crate) pen_tip: Option<u8>,
53 pub(crate) transparency: Option<u8>,
54 pub(crate) height: f32,
55 pub(crate) width: f32,
56 pub(crate) color: Option<u32>,
57}
58
59impl InkStroke {
60 pub fn path(&self) -> &[InkPoint] {
62 &self.path
63 }
64
65 pub fn pen_tip(&self) -> Option<u8> {
69 self.pen_tip
70 }
71
72 pub fn transparency(&self) -> Option<u8> {
76 self.transparency
77 }
78
79 pub fn height(&self) -> f32 {
81 self.height
82 }
83
84 pub fn width(&self) -> f32 {
88 self.width
89 }
90
91 pub fn color(&self) -> Option<u32> {
95 self.color
96 }
97}
98
99#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
101pub struct InkPoint {
102 pub(crate) x: f32,
103 pub(crate) y: f32,
104}
105
106impl InkPoint {
107 pub fn x(&self) -> f32 {
109 self.x
110 }
111
112 pub fn y(&self) -> f32 {
114 self.y
115 }
116}
117
118#[derive(Clone, Copy, Debug)]
120pub struct InkBoundingBox {
121 pub(crate) x: f32,
122 pub(crate) y: f32,
123 pub(crate) height: f32,
124 pub(crate) width: f32,
125}
126
127impl InkBoundingBox {
128 pub fn x(&self) -> f32 {
132 self.x
133 }
134
135 pub fn y(&self) -> f32 {
139 self.y
140 }
141
142 pub fn height(&self) -> f32 {
146 self.height
147 }
148
149 pub fn width(&self) -> f32 {
153 self.width
154 }
155
156 pub fn scale(&self, factor: f32) -> InkBoundingBox {
158 InkBoundingBox {
159 x: self.x * factor,
160 y: self.y * factor,
161 height: self.height * factor,
162 width: self.width * factor,
163 }
164 }
165}
166
167pub(crate) fn parse_ink(ink_container_id: ExGuid, space: &ObjectSpace) -> Result<Ink> {
168 let container_object = space
169 .get_object(ink_container_id)
170 .ok_or_else(|| ErrorKind::MalformedOneNoteData("ink container is missing".into()))?;
171 let container = ink_container::parse(container_object)?;
172
173 let ink_data_id = match container.ink_data {
174 Some(id) => id,
175 None => {
176 return Ok(Ink {
177 ink_strokes: vec![],
178 bounding_box: None,
179 offset_horizontal: container.offset_from_parent_horiz,
180 offset_vertical: container.offset_from_parent_vert,
181 });
182 }
183 };
184
185 let (ink_strokes, bounding_box) = parse_ink_data(
186 ink_data_id,
187 space,
188 container.ink_scaling_x,
189 container.ink_scaling_y,
190 )?;
191
192 Ok(Ink {
193 ink_strokes,
194 bounding_box,
195 offset_horizontal: container.offset_from_parent_horiz,
196 offset_vertical: container.offset_from_parent_vert,
197 })
198}
199
200pub(crate) fn parse_ink_data(
201 ink_data_id: ExGuid,
202 space: &ObjectSpace,
203 scale_x: Option<f32>,
204 scale_y: Option<f32>,
205) -> Result<(Vec<InkStroke>, Option<InkBoundingBox>)> {
206 let ink_data_object = space
207 .get_object(ink_data_id)
208 .ok_or_else(|| ErrorKind::MalformedOneNoteData("ink data node is missing".into()))?;
209 let ink_data = ink_data_node::parse(ink_data_object)?;
210
211 let strokes = ink_data
212 .strokes
213 .iter()
214 .copied()
215 .map(|ink_stroke_id| parse_ink_stroke(ink_stroke_id, space, scale_x, scale_y))
216 .collect::<Result<_>>()?;
217
218 let scale_x = scale_x.unwrap_or(1.0);
219 let scale_y = scale_y.unwrap_or(1.0);
220
221 let bounding_box = ink_data
222 .bounding_box
223 .map(|[x_min, y_min, x_max, y_max]| InkBoundingBox {
224 x: x_min as f32 * scale_x,
225 y: y_min as f32 * scale_y,
226 width: (x_max as f32 - x_min as f32) * scale_x,
227 height: (y_max as f32 - y_min as f32) * scale_y,
228 });
229
230 Ok((strokes, bounding_box))
231}
232
233fn parse_ink_stroke(
234 ink_stroke_id: ExGuid,
235 space: &ObjectSpace,
236 scale_x: Option<f32>,
237 scale_y: Option<f32>,
238) -> Result<InkStroke> {
239 let object = space
240 .get_object(ink_stroke_id)
241 .ok_or_else(|| ErrorKind::MalformedOneNoteData("ink stroke node is missing".into()))?;
242 let data = ink_stroke_node::parse(object)?;
243
244 let props_object = space.get_object(data.properties).ok_or_else(|| {
245 ErrorKind::MalformedOneNoteData("ink stroke properties node is missing".into())
246 })?;
247 let props = stroke_properties_node::parse(props_object)?;
248
249 let path = parse_ink_path(data.path, &props, scale_x, scale_y)?;
250
251 Ok(InkStroke {
252 path,
253 pen_tip: props.pen_tip,
254 transparency: props.transparency,
255 height: props.ink_height,
256 width: props.ink_width,
257 color: props.color,
258 })
259}
260
261fn parse_ink_path(
262 data: Vec<i64>,
263 props: &stroke_properties_node::Data,
264 scale_x: Option<f32>,
265 scale_y: Option<f32>,
266) -> Result<Vec<InkPoint>> {
267 let idx_x = props
269 .dimensions
270 .iter()
271 .position(|d| d.id == guid!("598a6a8f-52c0-4ba0-93af-af357411a561"))
272 .ok_or_else(|| {
273 ErrorKind::MalformedOneNoteData("ink stroke properties has no x dimension".into())
274 })?;
275 let idx_y = props
276 .dimensions
277 .iter()
278 .position(|d| d.id == guid!("b53f9f75-04e0-4498-a7ee-c30dbb5a9011"))
279 .ok_or_else(|| {
280 ErrorKind::MalformedOneNoteData("ink stroke properties has no y dimension".into())
281 })?;
282
283 let dimension_offset = data.len() / props.dimensions.len();
285
286 let start_x = dimension_offset * idx_x;
287 let start_y = dimension_offset * idx_y;
288
289 let x = &data[start_x..start_x + dimension_offset];
290 let y = &data[start_y..start_y + dimension_offset];
291
292 let scale_x = scale_x.unwrap_or(1.0);
293 let scale_y = scale_y.unwrap_or(1.0);
294
295 let path = x
296 .iter()
297 .copied()
298 .zip(y.iter().copied())
299 .map(|(x, y)| InkPoint {
300 x: scale_x * x as f32,
301 y: scale_y * y as f32,
302 })
303 .collect();
304
305 Ok(path)
306}