Skip to main content

dotloom_scene/
codec.rs

1//! Compact binary encoding of [`SceneDelta`] (little endian).
2//!
3//! ```text
4//! "DLSC" u16 version u16 flags(preview=1, reset=2, order=4) u64 revision
5//! u32 upserts u32 removals [u32 order]
6//! removals: u64…   order: u64…   upserts: item…
7//! item: u64 id u32 layer u8 flags f64×4 bbox u32 prims prim…
8//! ```
9//!
10//! The decoder is bounded: every count is checked against the remaining input
11//! before allocating, and malformed input returns [`DecodeError`] instead of
12//! panicking.
13
14use dotloom_geometry::{
15    Aabb, Arc, Circle, HAlign, Path, PathEl, Point, PointShape, Polygon, Polyline, Rect, Segment, Shape, Text, VAlign,
16    Vector,
17};
18use thiserror::Error;
19
20use crate::{Primitive, SceneDelta, SceneItem, Stroke};
21
22/// Magic bytes.
23pub const MAGIC: &[u8; 4] = b"DLSC";
24/// Encoding version.
25pub const SCENE_FORMAT_VERSION: u16 = 1;
26
27/// Decoding failure.
28#[derive(Debug, Clone, PartialEq, Eq, Error)]
29pub enum DecodeError {
30    /// Input ended early.
31    #[error("unexpected end of scene data")]
32    Truncated,
33    /// Wrong magic bytes.
34    #[error("not a Dotloom scene delta")]
35    BadMagic,
36    /// Unsupported version.
37    #[error("unsupported scene format version {0}")]
38    Version(u16),
39    /// Unknown tag.
40    #[error("invalid tag {0}")]
41    Tag(u8),
42    /// A count is larger than the remaining input allows.
43    #[error("count {0} exceeds remaining input")]
44    Count(u32),
45    /// Invalid UTF-8 text.
46    #[error("invalid utf-8 text")]
47    Utf8,
48    /// Trailing bytes after the delta.
49    #[error("trailing bytes")]
50    Trailing,
51}
52
53struct W(Vec<u8>);
54
55impl W {
56    fn u8(&mut self, v: u8) {
57        self.0.push(v);
58    }
59    fn u16(&mut self, v: u16) {
60        self.0.extend_from_slice(&v.to_le_bytes());
61    }
62    fn u32(&mut self, v: u32) {
63        self.0.extend_from_slice(&v.to_le_bytes());
64    }
65    fn u64(&mut self, v: u64) {
66        self.0.extend_from_slice(&v.to_le_bytes());
67    }
68    fn f32(&mut self, v: f32) {
69        self.0.extend_from_slice(&v.to_le_bytes());
70    }
71    fn f64(&mut self, v: f64) {
72        self.0.extend_from_slice(&v.to_le_bytes());
73    }
74    fn len(&mut self, n: usize) {
75        self.u32(u32::try_from(n).unwrap_or(u32::MAX));
76    }
77    fn pt(&mut self, p: Point) {
78        self.f64(p.x);
79        self.f64(p.y);
80    }
81    fn str(&mut self, s: &str) {
82        self.len(s.len());
83        self.0.extend_from_slice(s.as_bytes());
84    }
85    fn text(&mut self, t: &Text) {
86        self.pt(t.position);
87        self.f64(t.height);
88        self.f64(t.rotation);
89        self.u8(match t.halign {
90            HAlign::Left => 0,
91            HAlign::Center => 1,
92            HAlign::Right => 2,
93        });
94        self.u8(match t.valign {
95            VAlign::Baseline => 0,
96            VAlign::Middle => 1,
97            VAlign::Top => 2,
98            VAlign::Bottom => 3,
99        });
100        self.str(&t.content);
101    }
102    fn points(&mut self, pts: &[Point]) {
103        self.len(pts.len());
104        for p in pts {
105            self.pt(*p);
106        }
107    }
108    fn shape(&mut self, s: &Shape) {
109        match s {
110            Shape::Point(p) => {
111                self.u8(0);
112                self.pt(p.at);
113            }
114            Shape::Line(l) => {
115                self.u8(1);
116                self.pt(l.a);
117                self.pt(l.b);
118            }
119            Shape::Polyline(p) => {
120                self.u8(2);
121                self.u8(u8::from(p.closed));
122                self.points(&p.points);
123                self.len(p.bulges.len());
124                for b in &p.bulges {
125                    self.f64(*b);
126                }
127            }
128            Shape::Rect(r) => {
129                self.u8(3);
130                self.pt(r.origin);
131                self.f64(r.width);
132                self.f64(r.height);
133            }
134            Shape::Circle(c) => {
135                self.u8(4);
136                self.pt(c.center);
137                self.f64(c.radius);
138            }
139            Shape::Arc(a) => {
140                self.u8(5);
141                self.pt(a.center);
142                self.f64(a.radius);
143                self.f64(a.start);
144                self.f64(a.sweep);
145            }
146            Shape::Path(p) => {
147                self.u8(6);
148                self.len(p.elements.len());
149                for el in &p.elements {
150                    match *el {
151                        PathEl::MoveTo(a) => {
152                            self.u8(0);
153                            self.pt(a);
154                        }
155                        PathEl::LineTo(a) => {
156                            self.u8(1);
157                            self.pt(a);
158                        }
159                        PathEl::QuadTo(a, b) => {
160                            self.u8(2);
161                            self.pt(a);
162                            self.pt(b);
163                        }
164                        PathEl::CubicTo(a, b, c) => {
165                            self.u8(3);
166                            self.pt(a);
167                            self.pt(b);
168                            self.pt(c);
169                        }
170                        PathEl::Close => self.u8(4),
171                    }
172                }
173            }
174            Shape::Polygon(p) => {
175                self.u8(7);
176                self.len(1 + p.holes.len());
177                self.points(&p.outer);
178                for h in &p.holes {
179                    self.points(h);
180                }
181            }
182            Shape::Text(t) => {
183                self.u8(8);
184                self.text(t);
185            }
186        }
187    }
188}
189
190struct R<'a> {
191    b: &'a [u8],
192    i: usize,
193}
194
195impl<'a> R<'a> {
196    fn take(&mut self, n: usize) -> Result<&'a [u8], DecodeError> {
197        let end = self.i.checked_add(n).ok_or(DecodeError::Truncated)?;
198        let s = self.b.get(self.i..end).ok_or(DecodeError::Truncated)?;
199        self.i = end;
200        Ok(s)
201    }
202    fn arr<const N: usize>(&mut self) -> Result<[u8; N], DecodeError> {
203        let s = self.take(N)?;
204        let mut a = [0u8; N];
205        a.copy_from_slice(s);
206        Ok(a)
207    }
208    fn u8(&mut self) -> Result<u8, DecodeError> {
209        Ok(self.arr::<1>()?[0])
210    }
211    fn u16(&mut self) -> Result<u16, DecodeError> {
212        Ok(u16::from_le_bytes(self.arr()?))
213    }
214    fn u32(&mut self) -> Result<u32, DecodeError> {
215        Ok(u32::from_le_bytes(self.arr()?))
216    }
217    fn u64(&mut self) -> Result<u64, DecodeError> {
218        Ok(u64::from_le_bytes(self.arr()?))
219    }
220    fn f32(&mut self) -> Result<f32, DecodeError> {
221        Ok(f32::from_le_bytes(self.arr()?))
222    }
223    fn f64(&mut self) -> Result<f64, DecodeError> {
224        Ok(f64::from_le_bytes(self.arr()?))
225    }
226    fn remaining(&self) -> usize {
227        self.b.len().saturating_sub(self.i)
228    }
229    /// Count whose elements need at least `min_size` bytes each.
230    fn count(&mut self, min_size: usize) -> Result<usize, DecodeError> {
231        let n = self.u32()?;
232        let need = (n as usize).checked_mul(min_size.max(1)).ok_or(DecodeError::Count(n))?;
233        if need > self.remaining() {
234            return Err(DecodeError::Count(n));
235        }
236        Ok(n as usize)
237    }
238    fn pt(&mut self) -> Result<Point, DecodeError> {
239        Ok(Point::new(self.f64()?, self.f64()?))
240    }
241    fn str(&mut self) -> Result<String, DecodeError> {
242        let n = self.count(1)?;
243        let s = self.take(n)?;
244        String::from_utf8(s.to_vec()).map_err(|_| DecodeError::Utf8)
245    }
246    fn text(&mut self) -> Result<Text, DecodeError> {
247        let position = self.pt()?;
248        let height = self.f64()?;
249        let rotation = self.f64()?;
250        let halign = match self.u8()? {
251            0 => HAlign::Left,
252            1 => HAlign::Center,
253            2 => HAlign::Right,
254            t => return Err(DecodeError::Tag(t)),
255        };
256        let valign = match self.u8()? {
257            0 => VAlign::Baseline,
258            1 => VAlign::Middle,
259            2 => VAlign::Top,
260            3 => VAlign::Bottom,
261            t => return Err(DecodeError::Tag(t)),
262        };
263        let content = self.str()?;
264        Ok(Text { position, content, height, rotation, halign, valign })
265    }
266    fn points(&mut self) -> Result<Vec<Point>, DecodeError> {
267        let n = self.count(16)?;
268        (0..n).map(|_| self.pt()).collect()
269    }
270    fn shape(&mut self) -> Result<Shape, DecodeError> {
271        Ok(match self.u8()? {
272            0 => Shape::Point(PointShape { at: self.pt()? }),
273            1 => Shape::Line(Segment::new(self.pt()?, self.pt()?)),
274            2 => {
275                let closed = self.u8()? != 0;
276                let points = self.points()?;
277                let nb = self.count(8)?;
278                let bulges = (0..nb).map(|_| self.f64()).collect::<Result<_, _>>()?;
279                Shape::Polyline(Polyline { points, bulges, closed })
280            }
281            3 => Shape::Rect(Rect { origin: self.pt()?, width: self.f64()?, height: self.f64()? }),
282            4 => Shape::Circle(Circle { center: self.pt()?, radius: self.f64()? }),
283            5 => Shape::Arc(Arc { center: self.pt()?, radius: self.f64()?, start: self.f64()?, sweep: self.f64()? }),
284            6 => {
285                let n = self.count(1)?;
286                let mut elements = Vec::with_capacity(n);
287                for _ in 0..n {
288                    elements.push(match self.u8()? {
289                        0 => PathEl::MoveTo(self.pt()?),
290                        1 => PathEl::LineTo(self.pt()?),
291                        2 => PathEl::QuadTo(self.pt()?, self.pt()?),
292                        3 => PathEl::CubicTo(self.pt()?, self.pt()?, self.pt()?),
293                        4 => PathEl::Close,
294                        t => return Err(DecodeError::Tag(t)),
295                    });
296                }
297                Shape::Path(Path { elements })
298            }
299            7 => {
300                let rings = self.count(4)?;
301                if rings == 0 {
302                    return Err(DecodeError::Count(0));
303                }
304                let outer = self.points()?;
305                let holes = (1..rings).map(|_| self.points()).collect::<Result<_, _>>()?;
306                Shape::Polygon(Polygon { outer, holes })
307            }
308            8 => Shape::Text(self.text()?),
309            t => return Err(DecodeError::Tag(t)),
310        })
311    }
312}
313
314impl SceneDelta {
315    /// Encode to the binary format.
316    #[must_use]
317    pub fn encode(&self) -> Vec<u8> {
318        let mut w = W(Vec::with_capacity(64 + self.upserts.len() * 96));
319        w.0.extend_from_slice(MAGIC);
320        w.u16(SCENE_FORMAT_VERSION);
321        let flags = u16::from(self.preview) | (u16::from(self.reset) << 1) | (u16::from(self.order.is_some()) << 2);
322        w.u16(flags);
323        w.u64(self.revision);
324        w.len(self.upserts.len());
325        w.len(self.removals.len());
326        if let Some(o) = &self.order {
327            w.len(o.len());
328        }
329        for r in &self.removals {
330            w.u64(*r);
331        }
332        if let Some(o) = &self.order {
333            for id in o {
334                w.u64(*id);
335            }
336        }
337        for item in &self.upserts {
338            w.u64(item.id);
339            w.u32(item.layer);
340            w.u8(item.flags);
341            w.pt(item.bbox.min);
342            w.pt(item.bbox.max);
343            w.len(item.prims.len());
344            for p in &item.prims {
345                match p {
346                    Primitive::Shape { shape, stroke, fill } => {
347                        w.u8(0);
348                        w.u8(u8::from(stroke.is_some()) | (u8::from(fill.is_some()) << 1));
349                        if let Some(s) = stroke {
350                            w.u32(s.color);
351                            w.f32(s.width);
352                            w.len(s.dash.len());
353                            for d in &s.dash {
354                                w.f32(*d);
355                            }
356                        }
357                        if let Some(f) = fill {
358                            w.u32(*f);
359                        }
360                        w.shape(shape);
361                    }
362                    Primitive::Text { text, color } => {
363                        w.u8(1);
364                        w.text(text);
365                        w.u32(*color);
366                    }
367                    Primitive::Arrow { tip, direction, size, color } => {
368                        w.u8(2);
369                        w.pt(*tip);
370                        w.f64(direction.x);
371                        w.f64(direction.y);
372                        w.f64(*size);
373                        w.u32(*color);
374                    }
375                }
376            }
377        }
378        w.0
379    }
380
381    /// Decode from the binary format.
382    pub fn decode(bytes: &[u8]) -> Result<Self, DecodeError> {
383        let mut r = R { b: bytes, i: 0 };
384        if r.take(4)? != MAGIC {
385            return Err(DecodeError::BadMagic);
386        }
387        let version = r.u16()?;
388        if version != SCENE_FORMAT_VERSION {
389            return Err(DecodeError::Version(version));
390        }
391        let flags = r.u16()?;
392        let revision = r.u64()?;
393        let n_up = r.count(4)?;
394        let n_rm = r.count(8)?;
395        let n_order = if flags & 4 != 0 { Some(r.count(8)?) } else { None };
396        let removals = (0..n_rm).map(|_| r.u64()).collect::<Result<_, _>>()?;
397        let order = match n_order {
398            Some(n) => Some((0..n).map(|_| r.u64()).collect::<Result<_, _>>()?),
399            None => None,
400        };
401        let mut upserts = Vec::with_capacity(n_up);
402        for _ in 0..n_up {
403            let id = r.u64()?;
404            let layer = r.u32()?;
405            let item_flags = r.u8()?;
406            let bbox = Aabb { min: r.pt()?, max: r.pt()? };
407            let np = r.count(1)?;
408            let mut prims = Vec::with_capacity(np);
409            for _ in 0..np {
410                prims.push(match r.u8()? {
411                    0 => {
412                        let f = r.u8()?;
413                        let stroke = if f & 1 != 0 {
414                            let color = r.u32()?;
415                            let width = r.f32()?;
416                            let nd = r.count(4)?;
417                            let dash = (0..nd).map(|_| r.f32()).collect::<Result<_, _>>()?;
418                            Some(Stroke { color, width, dash })
419                        } else {
420                            None
421                        };
422                        let fill = if f & 2 != 0 { Some(r.u32()?) } else { None };
423                        Primitive::Shape { shape: r.shape()?, stroke, fill }
424                    }
425                    1 => Primitive::Text { text: r.text()?, color: r.u32()? },
426                    2 => Primitive::Arrow {
427                        tip: r.pt()?,
428                        direction: Vector::new(r.f64()?, r.f64()?),
429                        size: r.f64()?,
430                        color: r.u32()?,
431                    },
432                    t => return Err(DecodeError::Tag(t)),
433                });
434            }
435            upserts.push(SceneItem { id, layer, bbox, flags: item_flags, prims });
436        }
437        if r.remaining() != 0 {
438            return Err(DecodeError::Trailing);
439        }
440        Ok(Self { revision, preview: flags & 1 != 0, reset: flags & 2 != 0, upserts, removals, order })
441    }
442}