| 1 | //! Ink drawings and handwriting: strokes as page-coordinate polylines in points, decoded from |
| 2 | //! the stroke packets OneNote stores (ISF multi-byte first differences in HIMETRIC). |
| 3 | |
| 4 | use super::Error; |
| 5 | use crate::{ |
| 6 | ExGuid, |
| 7 | document::{Kind, Layout, Revision}, |
| 8 | }; |
| 9 | |
| 10 | /// Points from a HIMETRIC coordinate, the one conversion every stroke coordinate goes through. |
| 11 | fn points(himetric: i64, scale: f32) -> f32 { |
| 12 | himetric as f32 * scale * 72.0 / 2540.0 |
| 13 | } |
| 14 | |
| 15 | /// Rounds a page point to the HIMETRIC grid OneNote stores, so a stroke written from it reads |
| 16 | /// back equal. |
| 17 | pub fn snap(value: f32) -> f32 { |
| 18 | points((value * 2540.0 / 72.0).round() as i64, 1.0) |
| 19 | } |
| 20 | const DIMENSION_X: [u8; 16] = [ |
| 21 | 0x8f, 0x6a, 0x8a, 0x59, 0xc0, 0x52, 0xa0, 0x4b, 0x93, 0xaf, 0xaf, 0x35, 0x74, 0x11, 0xa5, 0x61, |
| 22 | ]; |
| 23 | const DIMENSION_Y: [u8; 16] = [ |
| 24 | 0x75, 0x9f, 0x3f, 0xb5, 0xe0, 0x04, 0x98, 0x44, 0xa7, 0xee, 0xc3, 0x0d, 0xbb, 0x5a, 0x90, 0x11, |
| 25 | ]; |
| 26 | /// The highest pressure level new strokes store, as a 1024-level pen reports it. |
| 27 | const PRESSURE_LEVELS: f32 = 1023.0; |
| 28 | |
| 29 | /// Rounds a pressure to the level a stroke stores, so a stroke written from it reads back |
| 30 | /// equal. |
| 31 | pub fn level(pressure: f32) -> f32 { |
| 32 | (pressure.clamp(0.0, 1.0) * PRESSURE_LEVELS).round() / PRESSURE_LEVELS |
| 33 | } |
| 34 | |
| 35 | #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] |
| 36 | pub struct Ink { |
| 37 | pub id: ExGuid, |
| 38 | /// A drawing's `x` and `y` offset every stroke point, as OneNote moves ink. |
| 39 | pub layout: Layout, |
| 40 | pub strokes: Vec<InkStroke>, |
| 41 | /// Nested ink containers, as newer OneNote versions group handwriting. |
| 42 | pub groups: Vec<Ink>, |
| 43 | /// The shape drawn, for a drawing made from Draw's Insert Shapes. |
| 44 | #[serde(default)] |
| 45 | pub shape: Option<InkShape>, |
| 46 | } |
| 47 | |
| 48 | /// The shapes of Draw's Insert Shapes that Snowbound draws. |
| 49 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 50 | pub enum ShapeKind { |
| 51 | Line, |
| 52 | Arrow, |
| 53 | Rectangle, |
| 54 | Ellipse, |
| 55 | } |
| 56 | |
| 57 | impl Ink { |
| 58 | /// A shape dragged from `from` to `to` in page points, drawn with `pen`'s width and |
| 59 | /// colour as OneNote 2010 draws it (`corpus/ink-tools`): a rectangle clockwise from its |
| 60 | /// top left corner, an ellipse in 50 steps clockwise from its right end, and an arrow's |
| 61 | /// head as a second stroke of two barbs at atan(1/2) to the line, 8 points plus twice |
| 62 | /// the pen's width long. The result reads back unchanged once stored. |
| 63 | pub fn drawn( |
| 64 | kind: ShapeKind, |
| 65 | from: [f32; 2], |
| 66 | to: [f32; 2], |
| 67 | pen: &InkStroke, |
| 68 | ) -> Result<Self, Error> { |
| 69 | let [x0, y0] = [from[0].min(to[0]), from[1].min(to[1])]; |
| 70 | let [x1, y1] = [from[0].max(to[0]), from[1].max(to[1])]; |
| 71 | let (shape, paths) = match kind { |
| 72 | ShapeKind::Line | ShapeKind::Arrow => { |
| 73 | let mut paths = vec![vec![from, to]]; |
| 74 | let [dx, dy] = [to[0] - from[0], to[1] - from[1]]; |
| 75 | if kind == ShapeKind::Arrow && dx.hypot(dy) > 0.0 { |
| 76 | let back = (-dy).atan2(-dx); |
| 77 | let length = 8.0 + 2.0 * pen.width; |
| 78 | let barb = |turn: f32| { |
| 79 | let angle = back + turn; |
| 80 | [to[0] + length * angle.cos(), to[1] + length * angle.sin()] |
| 81 | }; |
| 82 | let spread = 0.5f32.atan(); |
| 83 | paths.push(vec![barb(spread), to, barb(-spread)]); |
| 84 | } |
| 85 | (InkShape::Line([from, to]), paths) |
| 86 | } |
| 87 | ShapeKind::Rectangle | ShapeKind::Ellipse => { |
| 88 | let [w, h] = [x1 - x0, y1 - y0]; |
| 89 | let transform = [w, 0.0, 0.0, h, x0, y0]; |
| 90 | let (anchors, path): (&[[f32; 2]], Vec<[f32; 2]>) = if kind == ShapeKind::Rectangle |
| 91 | { |
| 92 | ( |
| 93 | &[ |
| 94 | [0.0, 0.0], |
| 95 | [0.5, 0.0], |
| 96 | [1.0, 0.0], |
| 97 | [1.0, 0.5], |
| 98 | [1.0, 1.0], |
| 99 | [0.5, 1.0], |
| 100 | [0.0, 1.0], |
| 101 | [0.0, 0.5], |
| 102 | ], |
| 103 | vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]], |
| 104 | ) |
| 105 | } else { |
| 106 | let [cx, cy, rx, ry] = [x0 + w / 2.0, y0 + h / 2.0, w / 2.0, h / 2.0]; |
| 107 | ( |
| 108 | &[[0.5, 0.0], [1.0, 0.5], [0.5, 1.0], [0.0, 0.5]], |
| 109 | (0..=50) |
| 110 | .map(|step| { |
| 111 | let angle = step as f32 * std::f32::consts::TAU / 50.0; |
| 112 | [cx + rx * angle.cos(), cy + ry * angle.sin()] |
| 113 | }) |
| 114 | .collect(), |
| 115 | ) |
| 116 | }; |
| 117 | ( |
| 118 | InkShape::Closed { |
| 119 | transform, |
| 120 | anchors: anchors.to_vec(), |
| 121 | }, |
| 122 | vec![path], |
| 123 | ) |
| 124 | } |
| 125 | }; |
| 126 | let mut strokes = Vec::new(); |
| 127 | for path in paths { |
| 128 | strokes.push(InkStroke { |
| 129 | id: super::text::new_id()?, |
| 130 | points: path.into_iter().map(|p| p.map(snap)).collect(), |
| 131 | pressure: Vec::new(), |
| 132 | ..pen.clone() |
| 133 | }); |
| 134 | } |
| 135 | Ok(Self { |
| 136 | id: super::text::new_id()?, |
| 137 | layout: Layout::default(), |
| 138 | strokes, |
| 139 | groups: Vec::new(), |
| 140 | shape: Some(shape.snapped()), |
| 141 | }) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | /// What OneNote keeps beside a drawn shape's strokes to edit it by (`corpus/ink-tools`), in |
| 146 | /// page points before the drawing's offset. |
| 147 | #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] |
| 148 | pub enum InkShape { |
| 149 | /// A line or arrow from one end to the other. |
| 150 | Line([[f32; 2]; 2]), |
| 151 | /// A closed shape: `transform` (`[a, b, c, d, x, y]`) takes its anchors, in the unit |
| 152 | /// square, onto the page. |
| 153 | Closed { |
| 154 | transform: [f32; 6], |
| 155 | anchors: Vec<[f32; 2]>, |
| 156 | }, |
| 157 | } |
| 158 | |
| 159 | #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] |
| 160 | pub struct InkStroke { |
| 161 | pub id: ExGuid, |
| 162 | /// Page coordinates in points, before the drawing's offset. |
| 163 | pub points: Vec<[f32; 2]>, |
| 164 | /// Pen width and height in points. |
| 165 | pub width: f32, |
| 166 | pub height: f32, |
| 167 | /// COLORREF; absent means the window text colour. |
| 168 | pub color: Option<u32>, |
| 169 | /// 0 is opaque, 255 fully transparent. |
| 170 | pub transparency: Option<u8>, |
| 171 | /// 0 is a round (ball) tip, 1 a rectangle. |
| 172 | pub pen_tip: Option<u8>, |
| 173 | /// The ISF raster operation: 9 (MaskPen) for a highlighter, whose colour multiplies |
| 174 | /// what lies beneath. |
| 175 | #[serde(default)] |
| 176 | pub raster_operation: Option<u8>, |
| 177 | /// The pen's pressure at each point, from none at 0 to full at 1, for a stroke whose |
| 178 | /// width follows it; empty where the pen ignores pressure, as a mouse's does. |
| 179 | #[serde(default)] |
| 180 | pub pressure: Vec<f32>, |
| 181 | } |
| 182 | |
| 183 | impl Ink { |
| 184 | pub(crate) fn read( |
| 185 | revision: &Revision<'_>, |
| 186 | id: ExGuid, |
| 187 | node: &crate::document::Element<'_>, |
| 188 | ) -> Result<Self, Error> { |
| 189 | let invalid = |message| Error { offset: 0, message }; |
| 190 | let Kind::Ink { |
| 191 | data, |
| 192 | scale_x, |
| 193 | scale_y, |
| 194 | shape_kind, |
| 195 | line, |
| 196 | anchors, |
| 197 | } = &node.kind |
| 198 | else { |
| 199 | unreachable!() |
| 200 | }; |
| 201 | let scale = [scale_x.unwrap_or(1.0), scale_y.unwrap_or(1.0)]; |
| 202 | let mut strokes = Vec::new(); |
| 203 | if let Some(data) = data { |
| 204 | let data = revision |
| 205 | .nodes |
| 206 | .get(data) |
| 207 | .ok_or_else(|| invalid("Missing ink data"))?; |
| 208 | let Kind::InkData { strokes: ids, .. } = &data.kind else { |
| 209 | return Err(invalid("Ink data has the wrong type")); |
| 210 | }; |
| 211 | for stroke_id in ids { |
| 212 | let stroke = revision |
| 213 | .nodes |
| 214 | .get(stroke_id) |
| 215 | .ok_or_else(|| invalid("Missing ink stroke"))?; |
| 216 | strokes.push(InkStroke::read(revision, *stroke_id, stroke, scale)?); |
| 217 | } |
| 218 | } |
| 219 | let mut groups = Vec::new(); |
| 220 | for child in &node.content { |
| 221 | let element = revision |
| 222 | .nodes |
| 223 | .get(child) |
| 224 | .ok_or_else(|| invalid("Missing nested ink"))?; |
| 225 | if matches!(element.kind, Kind::Ink { .. }) { |
| 226 | groups.push(Ink::read(revision, *child, element)?); |
| 227 | } |
| 228 | } |
| 229 | Ok(Self { |
| 230 | id, |
| 231 | layout: node.layout.clone(), |
| 232 | strokes, |
| 233 | groups, |
| 234 | shape: InkShape::read(*shape_kind, line.as_deref(), anchors.as_deref()), |
| 235 | }) |
| 236 | } |
| 237 | |
| 238 | /// The extent of every stroke point, as `[left, top, width, height]` in points. |
| 239 | pub fn bounds(&self) -> Option<[f32; 4]> { |
| 240 | let mut extent: Option<[f32; 4]> = None; |
| 241 | for point in self |
| 242 | .strokes |
| 243 | .iter() |
| 244 | .flat_map(|stroke| stroke.points.iter()) |
| 245 | .copied() |
| 246 | .chain( |
| 247 | self.groups |
| 248 | .iter() |
| 249 | .filter_map(Ink::bounds) |
| 250 | .flat_map(|[x, y, w, h]| [[x, y], [x + w, y + h]]), |
| 251 | ) |
| 252 | { |
| 253 | extent = Some(match extent { |
| 254 | None => [point[0], point[1], point[0], point[1]], |
| 255 | Some([x0, y0, x1, y1]) => [ |
| 256 | x0.min(point[0]), |
| 257 | y0.min(point[1]), |
| 258 | x1.max(point[0]), |
| 259 | y1.max(point[1]), |
| 260 | ], |
| 261 | }); |
| 262 | } |
| 263 | extent.map(|[x0, y0, x1, y1]| [x0, y0, x1 - x0, y1 - y0]) |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | impl InkStroke { |
| 268 | fn read( |
| 269 | revision: &Revision<'_>, |
| 270 | id: ExGuid, |
| 271 | node: &crate::document::Element<'_>, |
| 272 | scale: [f32; 2], |
| 273 | ) -> Result<Self, Error> { |
| 274 | let invalid = |message| Error { offset: 0, message }; |
| 275 | let Kind::InkStroke { path, style, .. } = &node.kind else { |
| 276 | return Err(invalid("Ink stroke has the wrong type")); |
| 277 | }; |
| 278 | let style = style |
| 279 | .and_then(|style| revision.nodes.get(&style)) |
| 280 | .ok_or_else(|| invalid("Missing ink stroke style"))?; |
| 281 | let Kind::InkStyle { |
| 282 | dimensions, |
| 283 | width, |
| 284 | height, |
| 285 | color, |
| 286 | transparency, |
| 287 | pen_tip, |
| 288 | raster_operation, |
| 289 | ignore_pressure, |
| 290 | .. |
| 291 | } = &style.kind |
| 292 | else { |
| 293 | return Err(invalid("Ink stroke style has the wrong type")); |
| 294 | }; |
| 295 | if dimensions.len() % 32 != 0 || dimensions.is_empty() { |
| 296 | return Err(invalid("Ink dimension table has an invalid length")); |
| 297 | } |
| 298 | let entries: Vec<&[u8]> = dimensions.chunks_exact(32).collect(); |
| 299 | let axis = |guid: &[u8]| entries.iter().position(|entry| entry[..16] == *guid); |
| 300 | let (Some(x), Some(y)) = (axis(&DIMENSION_X), axis(&DIMENSION_Y)) else { |
| 301 | return Err(invalid("Ink dimension table lacks X and Y")); |
| 302 | }; |
| 303 | let values = multi_byte(path).ok_or_else(|| invalid("Ink stroke packet is malformed"))?; |
| 304 | if values.len() % entries.len() != 0 { |
| 305 | return Err(invalid("Ink stroke packet does not cover its dimensions")); |
| 306 | } |
| 307 | let per_dimension = values.len() / entries.len(); |
| 308 | let coordinates = |dimension: usize, factor: f32| { |
| 309 | let mut position = 0i64; |
| 310 | values[dimension * per_dimension..(dimension + 1) * per_dimension] |
| 311 | .iter() |
| 312 | .map(move |delta| { |
| 313 | position += delta; |
| 314 | points(position, factor) |
| 315 | }) |
| 316 | }; |
| 317 | let points = coordinates(x, scale[0]) |
| 318 | .zip(coordinates(y, scale[1])) |
| 319 | .map(|(x, y)| [x, y]) |
| 320 | .collect(); |
| 321 | let mut pressure = Vec::new(); |
| 322 | if let Some(dimension) = axis(&PRESSURE_DIMENSION[..16]) |
| 323 | && *ignore_pressure != Some(true) |
| 324 | { |
| 325 | // In f32, as `level` divides, so a level written reads back equal. |
| 326 | let [lower, upper] = [16, 20].map(|at| { |
| 327 | i32::from_le_bytes(entries[dimension][at..at + 4].try_into().unwrap()) as f32 |
| 328 | }); |
| 329 | if upper > lower { |
| 330 | let mut level = 0i64; |
| 331 | pressure = values[dimension * per_dimension..(dimension + 1) * per_dimension] |
| 332 | .iter() |
| 333 | .map(|delta| { |
| 334 | level += delta; |
| 335 | (level as f32 - lower) / (upper - lower) |
| 336 | }) |
| 337 | .collect(); |
| 338 | } |
| 339 | } |
| 340 | Ok(Self { |
| 341 | id, |
| 342 | points, |
| 343 | width: width.unwrap_or(0.0) * 72.0 / 2540.0, |
| 344 | height: height.unwrap_or(0.0) * 72.0 / 2540.0, |
| 345 | color: *color, |
| 346 | transparency: *transparency, |
| 347 | pen_tip: *pen_tip, |
| 348 | raster_operation: *raster_operation, |
| 349 | pressure, |
| 350 | }) |
| 351 | } |
| 352 | |
| 353 | /// How many times its pen's size the stroke draws at point `index`, as OneNote 2010 |
| 354 | /// draws pressure (`corpus/ink-pressure`): a quarter at none, all of it at half and 1.75 |
| 355 | /// times at full. |
| 356 | pub fn thickness(&self, index: usize) -> f32 { |
| 357 | self.pressure |
| 358 | .get(index) |
| 359 | .map_or(1.0, |pressure| 0.25 + 1.5 * pressure) |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | impl InkShape { |
| 364 | /// Any geometry this does not recognise reads as no shape: the strokes still draw it. |
| 365 | fn read(kind: Option<u8>, line: Option<&[u8]>, anchors: Option<&[u8]>) -> Option<Self> { |
| 366 | let floats = |bytes: &[u8]| -> Vec<f32> { |
| 367 | bytes |
| 368 | .chunks_exact(4) |
| 369 | .map(|b| f32::from_le_bytes(b.try_into().unwrap())) |
| 370 | .collect() |
| 371 | }; |
| 372 | match (kind?, line, anchors) { |
| 373 | (11, Some(line), _) if line.len() == 16 => { |
| 374 | let v = floats(line); |
| 375 | Some(Self::Line( |
| 376 | [[v[0], v[1]], [v[2], v[3]]].map(|end| end.map(|v| v * 36.0)), |
| 377 | )) |
| 378 | .filter(Self::finite) |
| 379 | } |
| 380 | (12, _, Some(bytes)) if bytes.len() >= 24 && (bytes.len() - 24) % 8 == 0 => { |
| 381 | let v = floats(bytes); |
| 382 | Some(Self::Closed { |
| 383 | transform: std::array::from_fn(|i| v[i] * 36.0), |
| 384 | anchors: v[6..].chunks_exact(2).map(|p| [p[0], p[1]]).collect(), |
| 385 | }) |
| 386 | .filter(Self::finite) |
| 387 | } |
| 388 | _ => None, |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | fn finite(&self) -> bool { |
| 393 | match self { |
| 394 | Self::Line(ends) => ends.iter().flatten().all(|v| v.is_finite()), |
| 395 | Self::Closed { transform, anchors } => transform |
| 396 | .iter() |
| 397 | .chain(anchors.iter().flatten()) |
| 398 | .all(|v| v.is_finite()), |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | /// The kind, property and bytes OneNote stores the shape as. |
| 403 | pub(crate) fn stored(&self) -> (u8, u32, Vec<u8>) { |
| 404 | let half = |v: &f32| (v / 36.0).to_le_bytes(); |
| 405 | match self { |
| 406 | Self::Line(ends) => ( |
| 407 | 11, |
| 408 | 0x1c001dac, |
| 409 | ends.iter().flatten().flat_map(half).collect(), |
| 410 | ), |
| 411 | Self::Closed { transform, anchors } => ( |
| 412 | 12, |
| 413 | 0x1c001daa, |
| 414 | transform |
| 415 | .iter() |
| 416 | .flat_map(half) |
| 417 | .chain(anchors.iter().flatten().flat_map(|v| v.to_le_bytes())) |
| 418 | .collect(), |
| 419 | ), |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | /// This shape as it reads back once stored. |
| 424 | pub fn snapped(&self) -> Self { |
| 425 | let half = |v: f32| (v / 36.0) * 36.0; |
| 426 | match self { |
| 427 | Self::Line(ends) => Self::Line(ends.map(|end| end.map(half))), |
| 428 | Self::Closed { transform, anchors } => Self::Closed { |
| 429 | transform: transform.map(half), |
| 430 | anchors: anchors.clone(), |
| 431 | }, |
| 432 | } |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | /// ISF multi-byte decoding: a count, then that many 7-bit little-endian varints whose low bit |
| 437 | /// is the sign. |
| 438 | fn multi_byte(bytes: &[u8]) -> Option<Vec<i64>> { |
| 439 | let mut cursor = 0; |
| 440 | let mut next = || { |
| 441 | let mut value = 0u64; |
| 442 | let mut shift = 0; |
| 443 | loop { |
| 444 | let byte = *bytes.get(cursor)?; |
| 445 | cursor += 1; |
| 446 | if shift >= 64 { |
| 447 | return None; |
| 448 | } |
| 449 | value |= u64::from(byte & 0x7f) << shift; |
| 450 | shift += 7; |
| 451 | if byte & 0x80 == 0 { |
| 452 | return Some(value); |
| 453 | } |
| 454 | } |
| 455 | }; |
| 456 | let count = next()? >> 1; |
| 457 | let mut values = Vec::with_capacity(usize::try_from(count).ok()?.min(1 << 20)); |
| 458 | for _ in 0..count { |
| 459 | let raw = next()?; |
| 460 | let magnitude = i64::try_from(raw >> 1).ok()?; |
| 461 | values.push(if raw & 1 == 1 { -magnitude } else { magnitude }); |
| 462 | } |
| 463 | (cursor == bytes.len()).then_some(values) |
| 464 | } |
| 465 | |
| 466 | impl InkStroke { |
| 467 | /// The stroke packet OneNote stores: X, Y and any pressure as ISF multi-byte first |
| 468 | /// differences, of the points rounded to HIMETRIC and the pressure's levels. |
| 469 | pub(crate) fn packet(&self) -> Vec<u8> { |
| 470 | let differences = |values: Vec<i64>| { |
| 471 | let mut previous = 0i64; |
| 472 | values.into_iter().map(move |value| { |
| 473 | let delta = value - previous; |
| 474 | previous = value; |
| 475 | delta |
| 476 | }) |
| 477 | }; |
| 478 | let axis = |index: usize| { |
| 479 | differences( |
| 480 | self.points |
| 481 | .iter() |
| 482 | .map(|point| (point[index] * 2540.0 / 72.0).round() as i64) |
| 483 | .collect(), |
| 484 | ) |
| 485 | }; |
| 486 | let pressure = differences( |
| 487 | self.pressure |
| 488 | .iter() |
| 489 | .map(|pressure| (pressure.clamp(0.0, 1.0) * PRESSURE_LEVELS).round() as i64) |
| 490 | .collect(), |
| 491 | ); |
| 492 | let values: Vec<i64> = axis(0).chain(axis(1)).chain(pressure).collect(); |
| 493 | let mut out = Vec::new(); |
| 494 | let mut push = |raw: u64| { |
| 495 | let mut raw = raw; |
| 496 | loop { |
| 497 | let byte = (raw & 0x7f) as u8; |
| 498 | raw >>= 7; |
| 499 | if raw == 0 { |
| 500 | out.push(byte); |
| 501 | break; |
| 502 | } |
| 503 | out.push(byte | 0x80); |
| 504 | } |
| 505 | }; |
| 506 | push((values.len() as u64) << 1); |
| 507 | for value in values { |
| 508 | push(((value.unsigned_abs()) << 1) | u64::from(value < 0)); |
| 509 | } |
| 510 | out |
| 511 | } |
| 512 | } |
| 513 | |
| 514 | /// The dimension table OneNote 2010 writes for mouse ink: X and Y in HIMETRIC with the limits |
| 515 | /// and resolution of the authoring screen. |
| 516 | pub(crate) const DIMENSIONS: [u8; 64] = [ |
| 517 | 0x8f, 0x6a, 0x8a, 0x59, 0xc0, 0x52, 0xa0, 0x4b, 0x93, 0xaf, 0xaf, 0x35, 0x74, 0x11, 0xa5, 0x61, |
| 518 | 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0xe2, 0xe2, 0x41, |
| 519 | 0x75, 0x9f, 0x3f, 0xb5, 0xe0, 0x04, 0x98, 0x44, 0xa7, 0xee, 0xc3, 0x0d, 0xbb, 0x5a, 0x90, 0x11, |
| 520 | 0x00, 0x00, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x8b, 0xc5, 0xe2, 0x41, |
| 521 | ]; |
| 522 | |
| 523 | /// The entry a pressure-sensitive pen adds to `DIMENSIONS`: NormalPressure from 0 to 1023, of |
| 524 | /// no unit and resolution 1, as OneNote keeps it (`corpus/ink-pressure`). |
| 525 | pub(crate) const PRESSURE_DIMENSION: [u8; 32] = [ |
| 526 | 0x2d, 0x50, 0x07, 0x73, 0xf4, 0xf9, 0x18, 0x4e, 0xb3, 0xf2, 0x2c, 0xe1, 0xb1, 0xa3, 0x61, 0x0c, |
| 527 | 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, |
| 528 | ]; |
| 529 | |
| 530 | /// The dimension table OneNote 2010 writes for a drawn shape's pen: X and Y in HIMETRIC over |
| 531 | /// the whole 32-bit range, at a resolution of 1000. |
| 532 | pub(crate) const SHAPE_DIMENSIONS: [u8; 64] = [ |
| 533 | 0x8f, 0x6a, 0x8a, 0x59, 0xc0, 0x52, 0xa0, 0x4b, 0x93, 0xaf, 0xaf, 0x35, 0x74, 0x11, 0xa5, 0x61, |
| 534 | 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x7f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x44, |
| 535 | 0x75, 0x9f, 0x3f, 0xb5, 0xe0, 0x04, 0x98, 0x44, 0xa7, 0xee, 0xc3, 0x0d, 0xbb, 0x5a, 0x90, 0x11, |
| 536 | 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0x7f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x44, |
| 537 | ]; |
| 538 | |
| 539 | #[cfg(test)] |
| 540 | mod tests { |
| 541 | use super::*; |
| 542 | |
| 543 | #[test] |
| 544 | fn packets_round_trip_through_the_multi_byte_coding() { |
| 545 | let stroke = InkStroke { |
| 546 | id: ExGuid::default(), |
| 547 | points: vec![[100.0, 50.0], [100.5, 49.0], [99.0, 49.0]], |
| 548 | width: 1.0, |
| 549 | height: 1.0, |
| 550 | color: None, |
| 551 | transparency: None, |
| 552 | pen_tip: None, |
| 553 | raster_operation: None, |
| 554 | pressure: Vec::new(), |
| 555 | }; |
| 556 | let values = multi_byte(&stroke.packet()).unwrap(); |
| 557 | assert_eq!(values.len(), 6); |
| 558 | assert_eq!(&values[..3], &[3528, 17, -52]); |
| 559 | assert_eq!(&values[3..], &[1764, -35, 0]); |
| 560 | } |
| 561 | } |