1use super::ink_drawing_attributes::{InkDrawingAttributes, InkDrawingAttributesKind, PenTipShape};
5use super::ink_point::{InkError, InkPoint};
6use super::ink_stroke::InkStroke;
7use ferroui_base::media::Color;
8use ferroui_base::{Matrix, Point, Size};
9use std::collections::HashSet;
10use std::rc::Rc;
11
12pub const INK_MIME_TYPE: &str = "application/x-ferroui-ink";
13const MAGIC: &[u8; 8] = b"FERROINK";
14const VERSION: u32 = 1;
15const HEADER_SIZE: usize = 20;
16const POINT_SIZE: usize = 40;
17const STROKE_FIXED_SIZE: usize = 136;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct InkPersistenceLimits {
34 pub max_bytes: usize,
35 pub max_strokes: usize,
36 pub max_points_per_stroke: usize,
37 pub max_total_points: usize,
38}
39impl InkPersistenceLimits {
40 pub const DEFAULT: Self = Self {
41 max_bytes: 64 * 1024 * 1024,
42 max_strokes: 100_000,
43 max_points_per_stroke: 65_536,
44 max_total_points: 1_000_000,
45 };
46}
47impl Default for InkPersistenceLimits {
48 fn default() -> Self {
49 Self::DEFAULT
50 }
51}
52
53pub(crate) fn encode(
54 strokes: &[Rc<InkStroke>],
55 limits: InkPersistenceLimits,
56) -> Result<Vec<u8>, InkError> {
57 if strokes.len() > limits.max_strokes || strokes.len() > u32::MAX as usize {
58 return Err(InkError::LimitExceeded);
59 }
60 let mut size = HEADER_SIZE + 4;
61 let mut total_points = 0usize;
62 let mut seen = HashSet::with_capacity(strokes.len());
63 for s in strokes {
64 if !seen.insert(s.id()) {
65 return Err(InkError::DuplicateStrokeId(s.id()));
66 }
67 let n = s.ink_points().len();
68 total_points = total_points.checked_add(n).ok_or(InkError::LimitExceeded)?;
69 size = size
70 .checked_add(4 + STROKE_FIXED_SIZE)
71 .and_then(|v| n.checked_mul(POINT_SIZE).and_then(|n| v.checked_add(n)))
72 .ok_or(InkError::LimitExceeded)?;
73 if n > limits.max_points_per_stroke || total_points > limits.max_total_points {
74 return Err(InkError::LimitExceeded);
75 }
76 }
77 if size > limits.max_bytes || size - HEADER_SIZE > u32::MAX as usize {
78 return Err(InkError::LimitExceeded);
79 }
80 let mut out = Vec::with_capacity(size);
81 out.extend_from_slice(MAGIC);
82 write_u32(&mut out, VERSION);
83 write_u32(&mut out, (size - HEADER_SIZE) as u32);
84 write_u32(&mut out, 0);
85 write_u32(&mut out, strokes.len() as u32);
86 for s in strokes {
87 let a = s.drawing_attributes();
88 write_u32(
89 &mut out,
90 (STROKE_FIXED_SIZE + s.ink_points().len() * POINT_SIZE) as u32,
91 );
92 write_u32(&mut out, s.id());
93 out.push(u8::from(s.selected()));
94 out.push(
95 u8::from(a.draw_as_highlighter)
96 | (u8::from(a.ignore_pressure) << 1)
97 | (u8::from(a.fit_to_curve) << 2)
98 | (u8::from(a.ignore_tilt) << 3),
99 );
100 out.push(a.pen_tip as u8);
101 out.push(a.kind as u8);
102 out.extend_from_slice(&[a.color.a, a.color.r, a.color.g, a.color.b]);
103 write_f64(&mut out, a.size.width);
104 write_f64(&mut out, a.size.height);
105 write_f64(&mut out, a.pencil_opacity);
106 write_matrix(&mut out, a.pen_tip_transform);
107 write_matrix(&mut out, s.point_transform());
108 write_u32(&mut out, s.ink_points().len() as u32);
109 for p in s.ink_points() {
110 write_f64(&mut out, p.position.x);
111 write_f64(&mut out, p.position.y);
112 for value in [p.pressure, p.tilt_x, p.tilt_y, p.twist] {
113 out.extend_from_slice(&value.to_le_bytes());
114 }
115 out.extend_from_slice(&p.timestamp.to_le_bytes());
116 }
117 }
118 debug_assert_eq!(out.len(), size);
119 let checksum = crc32(&out[HEADER_SIZE..]);
120 out[16..20].copy_from_slice(&checksum.to_le_bytes());
121 Ok(out)
122}
123
124pub(crate) fn decode(
125 data: &[u8],
126 limits: InkPersistenceLimits,
127) -> Result<Vec<Rc<InkStroke>>, InkError> {
128 if data.len() > limits.max_bytes {
129 return Err(InkError::LimitExceeded);
130 }
131 let mut reader = Reader::new(data);
132 if reader.take(8)? != MAGIC {
133 return Err(InkError::UnsupportedFormat);
134 }
135 if reader.u32()? != VERSION {
136 return Err(InkError::UnsupportedFormat);
137 }
138 let length = reader.u32()? as usize;
139 let checksum = reader.u32()?;
140 if length != reader.remaining() {
141 return Err(InkError::InvalidData("payload length mismatch"));
142 }
143 if checksum != crc32(&data[HEADER_SIZE..]) {
144 return Err(InkError::InvalidData("checksum mismatch"));
145 }
146 let count = reader.u32()? as usize;
147 if count > limits.max_strokes {
148 return Err(InkError::LimitExceeded);
149 }
150 if count > reader.remaining() / (4 + STROKE_FIXED_SIZE + POINT_SIZE) {
151 return Err(InkError::InvalidData(
152 "stroke count exceeds remaining bytes",
153 ));
154 }
155 let mut result = Vec::with_capacity(count);
156 let mut seen = HashSet::with_capacity(count);
157 let mut total_points = 0usize;
158 for _ in 0..count {
159 let record_len = reader.u32()? as usize;
160 let mut record = Reader::new(reader.take(record_len)?);
161 let id = record.u32()?;
162 if !seen.insert(id) {
163 return Err(InkError::DuplicateStrokeId(id));
164 }
165 let selected = match record.byte()? {
166 0 => false,
167 1 => true,
168 _ => return Err(InkError::InvalidData("invalid selection flag")),
169 };
170 let flags = record.byte()?;
171 if flags & !15 != 0 {
172 return Err(InkError::InvalidData("unknown drawing flags"));
173 }
174 let pen_tip = match record.byte()? {
175 0 => PenTipShape::Circle,
176 1 => PenTipShape::Rectangle,
177 _ => return Err(InkError::InvalidData("unknown pen tip")),
178 };
179 let kind = match record.byte()? {
180 0 => InkDrawingAttributesKind::Default,
181 1 => InkDrawingAttributesKind::Pencil,
182 _ => return Err(InkError::InvalidData("unknown drawing kind")),
183 };
184 let color = record.take(4)?;
185 let color = Color::from_argb(color[0], color[1], color[2], color[3]);
186 let size = Size::new(record.f64()?, record.f64()?);
187 let pencil_opacity = record.f64()?;
188 let pen_tip_transform = record.matrix()?;
189 let transform = record.matrix()?;
190 let count = record.u32()? as usize;
191 total_points = total_points
192 .checked_add(count)
193 .ok_or(InkError::LimitExceeded)?;
194 if count > limits.max_points_per_stroke || total_points > limits.max_total_points {
195 return Err(InkError::LimitExceeded);
196 }
197 if count == 0 || count.checked_mul(POINT_SIZE) != Some(record.remaining()) {
198 return Err(InkError::InvalidData(
199 "point count does not match record length",
200 ));
201 }
202 let mut points = Vec::with_capacity(count);
203 for _ in 0..count {
204 points.push(InkPoint {
205 position: Point::new(record.f64()?, record.f64()?),
206 pressure: record.f32()?,
207 tilt_x: record.f32()?,
208 tilt_y: record.f32()?,
209 twist: record.f32()?,
210 timestamp: record.u64()?,
211 });
212 }
213 let attributes = InkDrawingAttributes {
214 color,
215 size,
216 pen_tip,
217 pen_tip_transform,
218 draw_as_highlighter: flags & 1 != 0,
219 ignore_pressure: flags & 2 != 0,
220 fit_to_curve: flags & 4 != 0,
221 ignore_tilt: flags & 8 != 0,
222 kind,
223 pencil_opacity,
224 };
225 result.push(InkStroke::restore(
226 id, points, attributes, transform, selected,
227 )?);
228 }
229 if reader.remaining() != 0 {
230 return Err(InkError::InvalidData("trailing data"));
231 }
232 Ok(result)
233}
234
235fn write_u32(out: &mut Vec<u8>, value: u32) {
236 out.extend_from_slice(&value.to_le_bytes());
237}
238fn write_f64(out: &mut Vec<u8>, value: f64) {
239 out.extend_from_slice(&value.to_le_bytes());
240}
241fn write_matrix(out: &mut Vec<u8>, value: Matrix) {
242 for v in [
243 value.m11, value.m12, value.m21, value.m22, value.m31, value.m32,
244 ] {
245 write_f64(out, v);
246 }
247}
248struct Reader<'a> {
249 data: &'a [u8],
250 position: usize,
251}
252impl<'a> Reader<'a> {
253 fn new(data: &'a [u8]) -> Self {
254 Self { data, position: 0 }
255 }
256 fn remaining(&self) -> usize {
257 self.data.len() - self.position
258 }
259 fn take(&mut self, length: usize) -> Result<&'a [u8], InkError> {
260 let end = self
261 .position
262 .checked_add(length)
263 .ok_or(InkError::InvalidData("record length overflow"))?;
264 let result = self
265 .data
266 .get(self.position..end)
267 .ok_or(InkError::InvalidData("truncated document"))?;
268 self.position = end;
269 Ok(result)
270 }
271 fn byte(&mut self) -> Result<u8, InkError> {
272 Ok(self.take(1)?[0])
273 }
274 fn u32(&mut self) -> Result<u32, InkError> {
275 let mut b = [0; 4];
276 b.copy_from_slice(self.take(4)?);
277 Ok(u32::from_le_bytes(b))
278 }
279 fn u64(&mut self) -> Result<u64, InkError> {
280 let mut b = [0; 8];
281 b.copy_from_slice(self.take(8)?);
282 Ok(u64::from_le_bytes(b))
283 }
284 fn f32(&mut self) -> Result<f32, InkError> {
285 Ok(f32::from_bits(self.u32()?))
286 }
287 fn f64(&mut self) -> Result<f64, InkError> {
288 Ok(f64::from_bits(self.u64()?))
289 }
290 fn matrix(&mut self) -> Result<Matrix, InkError> {
291 Ok(Matrix::new(
292 self.f64()?,
293 self.f64()?,
294 self.f64()?,
295 self.f64()?,
296 self.f64()?,
297 self.f64()?,
298 ))
299 }
300}
301
302const fn crc_table() -> [u32; 256] {
303 let mut table = [0u32; 256];
304 let mut i = 0;
305 while i < 256 {
306 let mut value = i as u32;
307 let mut j = 0;
308 while j < 8 {
309 value = if value & 1 != 0 {
310 0xedb8_8320 ^ (value >> 1)
311 } else {
312 value >> 1
313 };
314 j += 1;
315 }
316 table[i] = value;
317 i += 1;
318 }
319 table
320}
321const CRC_TABLE: [u32; 256] = crc_table();
322fn crc32(bytes: &[u8]) -> u32 {
323 let mut crc = !0u32;
324 for byte in bytes {
325 crc = CRC_TABLE[((crc ^ u32::from(*byte)) & 255) as usize] ^ (crc >> 8);
326 }
327 !crc
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 fn stroke() -> Rc<InkStroke> {
334 let p = InkPoint {
335 position: Point::new(10.0, -4.0),
336 pressure: 0.75,
337 tilt_x: 30.0,
338 tilt_y: -20.0,
339 twist: 359.0,
340 timestamp: 12345,
341 };
342 let a = InkDrawingAttributes {
343 color: Color::from_argb(128, 255, 80, 20),
344 size: Size::new(7.0, 3.0),
345 kind: InkDrawingAttributesKind::Pencil,
346 pencil_opacity: 0.65,
347 ..Default::default()
348 };
349 let s = InkStroke::new(vec![p], a).unwrap();
350 s.set_selected(true);
351 s.set_point_transform(Matrix::create_translation(4.0, 9.0))
352 .unwrap();
353 s
354 }
355 #[test]
356 fn round_trip_preserves_all_samples_and_attributes() {
357 let s = stroke();
358 let bytes = encode(&[s.clone()], InkPersistenceLimits::DEFAULT).unwrap();
359 let loaded = decode(&bytes, InkPersistenceLimits::DEFAULT).unwrap();
360 let copy = &loaded[0];
361 assert_eq!(copy.id(), s.id());
362 assert_eq!(copy.ink_points(), s.ink_points());
363 assert_eq!(copy.drawing_attributes(), s.drawing_attributes());
364 assert_eq!(copy.point_transform(), s.point_transform());
365 assert!(copy.selected());
366 }
367 #[test]
368 fn every_truncation_is_rejected() {
369 let bytes = encode(&[stroke()], InkPersistenceLimits::DEFAULT).unwrap();
370 for n in 0..bytes.len() {
371 assert!(
372 decode(&bytes[..n], InkPersistenceLimits::DEFAULT).is_err(),
373 "length {n}"
374 );
375 }
376 }
377 #[test]
378 fn corruption_is_detected_and_limits_precede_allocations() {
379 let mut bytes = encode(&[stroke()], InkPersistenceLimits::DEFAULT).unwrap();
380 let last = bytes.len() - 1;
381 bytes[last] ^= 1;
382 assert_eq!(
383 decode(&bytes, InkPersistenceLimits::DEFAULT).unwrap_err(),
384 InkError::InvalidData("checksum mismatch")
385 );
386 assert!(matches!(
387 decode(
388 &bytes,
389 InkPersistenceLimits {
390 max_bytes: 8,
391 ..Default::default()
392 }
393 ),
394 Err(InkError::LimitExceeded)
395 ));
396 }
397 #[test]
398 fn hostile_count_with_valid_checksum_is_rejected() {
399 let mut bytes = encode(&[stroke()], InkPersistenceLimits::DEFAULT).unwrap();
400 bytes[20..24].copy_from_slice(&u32::MAX.to_le_bytes());
401 let crc = crc32(&bytes[HEADER_SIZE..]);
402 bytes[16..20].copy_from_slice(&crc.to_le_bytes());
403 assert_eq!(
404 decode(&bytes, InkPersistenceLimits::DEFAULT).unwrap_err(),
405 InkError::LimitExceeded
406 );
407 }
408 #[test]
409 fn arbitrary_input_never_panics() {
410 for len in 0..256 {
411 let bytes: Vec<_> = (0..len)
412 .map(|i| ((i * 137 + len * 19) % 256) as u8)
413 .collect();
414 let _ = decode(&bytes, InkPersistenceLimits::DEFAULT);
415 }
416 }
417 #[test]
418 fn empty_document_round_trips() {
419 let bytes = encode(&[], InkPersistenceLimits::DEFAULT).unwrap();
420 assert!(decode(&bytes, InkPersistenceLimits::DEFAULT)
421 .unwrap()
422 .is_empty());
423 }
424}