Skip to main content

excalidraw_render/
scene.rs

1//! Scene documents: admission of the version-2 envelope and the typed
2//! `*Source` accessors the rest of the pipeline reads through. `json` owns the
3//! bounded, lossless parser that produces the preserved tree, the RFC 6901
4//! pointer builders and the canonical writer; this file adds the envelope
5//! check, the public `SceneDocument` and `SceneParseError`, and the original-
6//! JSON visitor the ledger charges from.
7
8mod json;
9
10#[cfg(test)]
11pub(crate) use json::JsonKind;
12pub(crate) use json::{
13    finite_json_number, member_value, pointer_child, pointer_index, JsonMember, JsonValue,
14};
15use json::{
16    json_bool, json_i32, json_string, json_u64, visit_canonical_json, write_canonical_json,
17    ParserLimit,
18};
19
20use std::collections::TryReserveError;
21use std::error::Error;
22use std::fmt;
23use std::sync::Arc;
24
25use crate::{DiagnosticDetails, ErrorCode, ErrorDescriptor, Measurement};
26
27/// An admitted Excalidraw scene document.
28#[derive(Clone)]
29pub struct SceneDocument {
30    _root: Arc<JsonValue>,
31    source_bytes: u64,
32    #[cfg(test)]
33    source_sha256: [u8; 32],
34}
35
36impl SceneDocument {
37    /// Parses and admits a scene document from UTF-8 JSON bytes.
38    ///
39    /// # Errors
40    ///
41    /// Returns a [`SceneParseError`] when the bytes are not UTF-8
42    /// (`scene.invalid-utf8`) or JSON (`scene.invalid-json`), when the
43    /// document is not a version-2 Excalidraw scene envelope
44    /// (`scene.invalid-envelope`), when an object repeats a key
45    /// (`scene.duplicate-object-key`), or when it crosses one of the fixed
46    /// parser ceilings: 64 MiB of input (`scene.input-too-large`), nesting
47    /// depth, entry count, string, key or number-token size, or parser
48    /// memory (the other `scene.*` codes). These ceilings are independent of
49    /// [`ResourceLimits`](crate::ResourceLimits).
50    pub fn parse(bytes: &[u8]) -> Result<Self, SceneParseError> {
51        let root = json::parse(bytes).map_err(|error| {
52            if matches!(
53                error.code,
54                ErrorCode::SCENE_INVALID_UTF8 | ErrorCode::SCENE_INVALID_JSON
55            ) {
56                recognized_export(bytes).unwrap_or(error)
57            } else {
58                error
59            }
60        })?;
61        validate_envelope(&root)?;
62
63        Ok(Self {
64            _root: Arc::new(root),
65            source_bytes: u64::try_from(bytes.len()).unwrap_or(u64::MAX),
66            #[cfg(test)]
67            source_sha256: crate::resource_accounting::sha256_digest(bytes),
68        })
69    }
70
71    /// Discovers live ordinary and magic frame occurrences in source order.
72    pub fn frames(&self) -> crate::FrameRefs<'_> {
73        crate::FrameRefs::new(self)
74    }
75
76    pub(crate) fn same_identity(&self, other: &Self) -> bool {
77        Arc::ptr_eq(&self._root, &other._root)
78    }
79
80    /// Hashes the identity [`same_identity`](Self::same_identity) compares.
81    pub(crate) fn hash_identity<H: std::hash::Hasher>(&self, state: &mut H) {
82        std::ptr::hash(Arc::as_ptr(&self._root), state);
83    }
84
85    pub(crate) fn canonicalized_original_bytes(&self) -> Result<Vec<u8>, TryReserveError> {
86        let mut bytes = Vec::new();
87        write_canonical_json(&self._root, &mut bytes)?;
88        Ok(bytes)
89    }
90
91    pub(crate) fn visit_canonicalized_original(&self, visitor: &mut impl FnMut(&[u8])) {
92        visit_canonical_json(&self._root, visitor);
93    }
94
95    pub(crate) fn element_occurrence_count(&self) -> usize {
96        let JsonValue::Object(root) = &*self._root else {
97            return 0;
98        };
99        let Some(JsonValue::Array(elements)) = member_value(root, "elements") else {
100            return 0;
101        };
102        elements.len()
103    }
104
105    pub(crate) fn element_value(&self, index: usize) -> Option<&JsonValue> {
106        let JsonValue::Object(root) = &*self._root else {
107            return None;
108        };
109        let Some(JsonValue::Array(elements)) = member_value(root, "elements") else {
110            return None;
111        };
112        elements.get(index)
113    }
114
115    pub(crate) fn root_members(&self) -> &[JsonMember] {
116        match &*self._root {
117            JsonValue::Object(members) => members,
118            _ => &[],
119        }
120    }
121
122    pub(crate) fn app_state_members(&self) -> &[JsonMember] {
123        let Some(JsonValue::Object(members)) = member_value(self.root_members(), "appState") else {
124            return &[];
125        };
126        members
127    }
128
129    pub(crate) fn app_state_member_position(&self, key: &str) -> Option<usize> {
130        self.app_state_members()
131            .iter()
132            .position(|member| &*member.key == key)
133    }
134
135    pub(crate) fn view_background_color(&self) -> SourceField<&str> {
136        source_field(self.app_state_members(), "viewBackgroundColor", json_string)
137    }
138
139    pub(crate) fn source_owner_order(&self, root_member: &str, child_index: usize) -> u64 {
140        let root_position = match &*self._root {
141            JsonValue::Object(members) => members
142                .iter()
143                .position(|member| &*member.key == root_member)
144                .unwrap_or(usize::MAX),
145            _ => usize::MAX,
146        };
147        u64::try_from(root_position)
148            .unwrap_or(u64::MAX)
149            .saturating_mul(u64::from(u32::MAX) + 1)
150            .saturating_add(u64::try_from(child_index).unwrap_or(u64::MAX))
151    }
152
153    pub(crate) fn element_at(&self, index: usize) -> Option<ElementSource<'_>> {
154        let JsonValue::Object(root) = &*self._root else {
155            return None;
156        };
157        let JsonValue::Array(elements) = member_value(root, "elements")? else {
158            return None;
159        };
160        let value = elements.get(index)?;
161        let JsonValue::Object(element) = value else {
162            return None;
163        };
164        let kind = match json_string(member_value(element, "type")?)? {
165            "rectangle" => ElementKind::Rectangle,
166            "diamond" => ElementKind::Diamond,
167            "ellipse" => ElementKind::Ellipse,
168            "text" => ElementKind::Text,
169            "line" => ElementKind::Line,
170            "draw" => ElementKind::LegacyDraw,
171            "arrow" => ElementKind::Arrow,
172            "freedraw" => ElementKind::Freedraw,
173            "image" => ElementKind::Image,
174            "frame" => ElementKind::Frame,
175            "magicframe" => ElementKind::MagicFrame,
176            "iframe" => ElementKind::Iframe,
177            "embeddable" => ElementKind::Embeddable,
178            _ => return None,
179        };
180        Some(ElementSource {
181            element: value,
182            kind,
183            id: source_field(element, "id", json_string),
184            index: source_nullable_string(element, "index"),
185            x: source_field(element, "x", finite_json_number),
186            y: source_field(element, "y", finite_json_number),
187            width: source_field(element, "width", finite_json_number),
188            height: source_field(element, "height", finite_json_number),
189            angle: source_field(element, "angle", finite_json_number),
190            stroke_color: source_field(element, "strokeColor", json_string),
191            background_color: source_field(element, "backgroundColor", json_string),
192            fill_style: source_field(element, "fillStyle", json_string),
193            stroke_width: source_field(element, "strokeWidth", finite_json_number),
194            stroke_style: source_field(element, "strokeStyle", json_string),
195            roundness: source_roundness(element, "roundness"),
196            stroke_sharpness: source_field(element, "strokeSharpness", json_string),
197            roughness: source_field(element, "roughness", finite_json_number),
198            opacity: source_field(element, "opacity", finite_json_number),
199            seed: source_field(element, "seed", json_i32),
200            is_deleted: source_field(element, "isDeleted", json_bool),
201            frame_id: source_nullable_string(element, "frameId"),
202            link: source_nullable_string(element, "link"),
203            updated_is_missing: member_value(element, "updated").is_none(),
204            points: source_point_array(element, "points"),
205            pressures: source_number_array(element, "pressures"),
206            simulate_pressure: source_field(element, "simulatePressure", json_bool),
207            start_binding: source_binding(element, "startBinding"),
208            end_binding: source_binding(element, "endBinding"),
209            start_arrowhead: source_nullable_string(element, "startArrowhead"),
210            end_arrowhead: source_nullable_string(element, "endArrowhead"),
211            elbowed: source_field(element, "elbowed", json_bool),
212            file_id: source_nullable_string(element, "fileId"),
213            image_status: source_field(element, "status", json_string),
214            image_scale: source_number_array(element, "scale"),
215            image_crop: source_image_crop(element, "crop"),
216            name: source_nullable_string(element, "name"),
217            font_size: source_field(element, "fontSize", finite_json_number),
218            font_family: source_field(element, "fontFamily", json_i32),
219            font: source_field(element, "font", json_string),
220            text: source_field(element, "text", json_string),
221            original_text: source_field(element, "originalText", json_string),
222            container_id: source_nullable_string(element, "containerId"),
223            text_align: source_field(element, "textAlign", json_string),
224            vertical_align: source_field(element, "verticalAlign", json_string),
225            line_height: source_field(element, "lineHeight", finite_json_number),
226        })
227    }
228
229    pub(crate) fn frame_rendering(&self) -> SourceField<FrameRenderingSource<'_>> {
230        let JsonValue::Object(root) = &*self._root else {
231            return SourceField::Invalid;
232        };
233        let Some(JsonValue::Object(app_state)) = member_value(root, "appState") else {
234            return SourceField::Missing;
235        };
236        source_field(app_state, "frameRendering", |value| {
237            let JsonValue::Object(members) = value else {
238                return None;
239            };
240            Some(FrameRenderingSource { members })
241        })
242    }
243
244    pub(crate) fn file_record_count(&self) -> usize {
245        let JsonValue::Object(root) = &*self._root else {
246            return 0;
247        };
248        let Some(JsonValue::Object(files)) = member_value(root, "files") else {
249            return 0;
250        };
251        files.len()
252    }
253
254    pub(crate) fn file_record_at(&self, index: usize) -> Option<FileRecordSource<'_>> {
255        let JsonValue::Object(root) = &*self._root else {
256            return None;
257        };
258        let JsonValue::Object(files) = member_value(root, "files")? else {
259            return None;
260        };
261        let member = files.get(index)?;
262        Some(FileRecordSource {
263            key: &member.key,
264            value: &member.value,
265        })
266    }
267
268    pub(crate) fn element_is_silent(&self, index: usize) -> bool {
269        let JsonValue::Object(root) = &*self._root else {
270            return false;
271        };
272        let Some(JsonValue::Array(elements)) = member_value(root, "elements") else {
273            return false;
274        };
275        let Some(JsonValue::Object(element)) = elements.get(index) else {
276            return false;
277        };
278        matches!(
279            member_value(element, "isDeleted"),
280            Some(JsonValue::Bool(true))
281        )
282    }
283
284    pub(crate) const fn source_bytes(&self) -> u64 {
285        self.source_bytes
286    }
287
288    #[cfg(test)]
289    pub(crate) const fn source_sha256(&self) -> &[u8; 32] {
290        &self.source_sha256
291    }
292
293    /// The JSON kind at an RFC 6901 pointer into the admitted document, or
294    /// `None` when the pointer names nothing.
295    #[cfg(test)]
296    pub(crate) fn json_kind_at(&self, pointer: &str) -> Option<JsonKind> {
297        let mut value: &JsonValue = &self._root;
298        if pointer.is_empty() {
299            return Some(value.kind());
300        }
301        for segment in pointer.strip_prefix('/')?.split('/') {
302            let segment = segment.replace("~1", "/").replace("~0", "~");
303            value = match value {
304                JsonValue::Object(members) => member_value(members, &segment)?,
305                JsonValue::Array(values) => values.get(segment.parse::<usize>().ok()?)?,
306                JsonValue::Null
307                | JsonValue::Bool(_)
308                | JsonValue::Number(_)
309                | JsonValue::String(_) => {
310                    return None;
311                }
312            };
313        }
314        Some(value.kind())
315    }
316
317    pub(crate) fn visit_original_json_entries<E>(
318        &self,
319        mut visit: impl FnMut(OriginalJsonEntry<'_>) -> Result<(), E>,
320    ) -> Result<(), OriginalJsonVisitError<E>> {
321        visit_json_value(&self._root, "", false, &mut visit)
322    }
323}
324
325#[derive(Clone, Copy, Debug, Eq, PartialEq)]
326pub(crate) enum ElementKind {
327    Rectangle,
328    Diamond,
329    Ellipse,
330    Text,
331    Line,
332    LegacyDraw,
333    Arrow,
334    Freedraw,
335    Image,
336    Frame,
337    MagicFrame,
338    Iframe,
339    Embeddable,
340}
341
342impl ElementKind {
343    pub(crate) const fn scene_surface_kind(self) -> &'static str {
344        match self {
345            Self::Rectangle => "rectangle",
346            Self::Diamond => "diamond",
347            Self::Ellipse => "ellipse",
348            Self::Text => "text",
349            Self::Line => "line",
350            Self::LegacyDraw => "draw",
351            Self::Arrow => "arrow",
352            Self::Freedraw => "freedraw",
353            Self::Image => "image",
354            Self::Frame => "frame",
355            Self::MagicFrame => "magicframe",
356            Self::Iframe => "iframe",
357            Self::Embeddable => "embeddable",
358        }
359    }
360}
361
362pub(crate) struct ElementSource<'a> {
363    element: &'a JsonValue,
364    pub(crate) kind: ElementKind,
365    pub(crate) id: SourceField<&'a str>,
366    pub(crate) index: SourceField<Option<&'a str>>,
367    pub(crate) x: SourceField<f64>,
368    pub(crate) y: SourceField<f64>,
369    pub(crate) width: SourceField<f64>,
370    pub(crate) height: SourceField<f64>,
371    pub(crate) angle: SourceField<f64>,
372    pub(crate) stroke_color: SourceField<&'a str>,
373    pub(crate) background_color: SourceField<&'a str>,
374    pub(crate) fill_style: SourceField<&'a str>,
375    pub(crate) stroke_width: SourceField<f64>,
376    pub(crate) stroke_style: SourceField<&'a str>,
377    pub(crate) roundness: SourceField<Option<RoundnessSource<'a>>>,
378    pub(crate) stroke_sharpness: SourceField<&'a str>,
379    pub(crate) roughness: SourceField<f64>,
380    pub(crate) opacity: SourceField<f64>,
381    pub(crate) seed: SourceField<i32>,
382    pub(crate) is_deleted: SourceField<bool>,
383    pub(crate) frame_id: SourceField<Option<&'a str>>,
384    pub(crate) link: SourceField<Option<&'a str>>,
385    pub(crate) updated_is_missing: bool,
386    pub(crate) points: SourceField<PointArraySource<'a>>,
387    pub(crate) pressures: SourceField<NumberArraySource<'a>>,
388    pub(crate) simulate_pressure: SourceField<bool>,
389    pub(crate) start_binding: SourceField<Option<BindingSource<'a>>>,
390    pub(crate) end_binding: SourceField<Option<BindingSource<'a>>>,
391    pub(crate) start_arrowhead: SourceField<Option<&'a str>>,
392    pub(crate) end_arrowhead: SourceField<Option<&'a str>>,
393    pub(crate) elbowed: SourceField<bool>,
394    pub(crate) file_id: SourceField<Option<&'a str>>,
395    pub(crate) image_status: SourceField<&'a str>,
396    pub(crate) image_scale: SourceField<NumberArraySource<'a>>,
397    pub(crate) image_crop: SourceField<Option<ImageCropSource<'a>>>,
398    pub(crate) name: SourceField<Option<&'a str>>,
399    pub(crate) font_size: SourceField<f64>,
400    pub(crate) font_family: SourceField<i32>,
401    pub(crate) font: SourceField<&'a str>,
402    pub(crate) text: SourceField<&'a str>,
403    pub(crate) original_text: SourceField<&'a str>,
404    pub(crate) container_id: SourceField<Option<&'a str>>,
405    pub(crate) text_align: SourceField<&'a str>,
406    pub(crate) vertical_align: SourceField<&'a str>,
407    pub(crate) line_height: SourceField<f64>,
408}
409
410impl ElementSource<'_> {
411    pub(crate) fn canonical_element_bytes(&self) -> Result<Vec<u8>, TryReserveError> {
412        let mut bytes = Vec::new();
413        write_canonical_json(self.element, &mut bytes)?;
414        Ok(bytes)
415    }
416
417    pub(crate) fn member_position(&self, key: &str) -> Option<usize> {
418        let JsonValue::Object(members) = self.element else {
419            return None;
420        };
421        members.iter().position(|member| &*member.key == key)
422    }
423}
424
425#[derive(Clone, Copy)]
426pub(crate) struct PointArraySource<'a> {
427    values: &'a [JsonValue],
428}
429
430impl PointArraySource<'_> {
431    pub(crate) fn len(&self) -> usize {
432        self.values.len()
433    }
434
435    pub(crate) fn point(&self, index: usize) -> SourceField<[f64; 2]> {
436        let Some(value) = self.values.get(index) else {
437            return SourceField::Missing;
438        };
439        parse_point(value).map_or(SourceField::Invalid, SourceField::Value)
440    }
441
442    pub(crate) fn invalid_component(&self, index: usize) -> Option<usize> {
443        let JsonValue::Array(values) = self.values.get(index)? else {
444            return None;
445        };
446        if values.len() < 2 {
447            return Some(values.len());
448        }
449        (0..2).find(|component| finite_json_number(&values[*component]).is_none())
450    }
451
452    pub(crate) fn first_invalid_point(&self) -> Option<(usize, Option<usize>)> {
453        (0..self.values.len()).find_map(|index| {
454            matches!(self.point(index), SourceField::Invalid)
455                .then(|| (index, self.invalid_component(index)))
456        })
457    }
458
459    pub(crate) fn component_count(&self, index: usize) -> Option<usize> {
460        let JsonValue::Array(values) = self.values.get(index)? else {
461            return None;
462        };
463        Some(values.len())
464    }
465}
466
467#[derive(Clone, Copy)]
468pub(crate) struct NumberArraySource<'a> {
469    values: &'a [JsonValue],
470}
471
472impl NumberArraySource<'_> {
473    pub(crate) fn len(&self) -> usize {
474        self.values.len()
475    }
476
477    pub(crate) fn number(&self, index: usize) -> SourceField<f64> {
478        let Some(value) = self.values.get(index) else {
479            return SourceField::Missing;
480        };
481        finite_json_number(value).map_or(SourceField::Invalid, SourceField::Value)
482    }
483}
484
485#[derive(Clone, Copy)]
486pub(crate) struct BindingSource<'a> {
487    members: &'a [JsonMember],
488}
489
490impl BindingSource<'_> {
491    pub(crate) fn element_id(&self) -> SourceField<&str> {
492        source_field(self.members, "elementId", json_string)
493    }
494
495    pub(crate) fn focus(&self) -> SourceField<f64> {
496        match member_value(self.members, "focus") {
497            None | Some(JsonValue::Null | JsonValue::Bool(false)) => SourceField::Missing,
498            Some(JsonValue::String(value)) if value.is_empty() => SourceField::Missing,
499            Some(value) => {
500                finite_json_number(value).map_or(SourceField::Invalid, SourceField::Value)
501            }
502        }
503    }
504
505    pub(crate) fn gap(&self) -> SourceField<f64> {
506        source_field(self.members, "gap", finite_json_number)
507    }
508
509    pub(crate) fn fixed_point(&self) -> SourceField<[f64; 2]> {
510        match member_value(self.members, "fixedPoint") {
511            None | Some(JsonValue::Null) => SourceField::Missing,
512            Some(JsonValue::Array(values)) if values.len() != 2 => SourceField::Invalid,
513            Some(value) => parse_point(value).map_or(SourceField::Invalid, SourceField::Value),
514        }
515    }
516
517    pub(crate) fn fixed_point_invalid_component(&self) -> Option<usize> {
518        let JsonValue::Array(values) = member_value(self.members, "fixedPoint")? else {
519            return None;
520        };
521        if values.len() < 2 {
522            return Some(values.len());
523        }
524        (0..2).find(|component| finite_json_number(&values[*component]).is_none())
525    }
526
527    pub(crate) fn member_position(&self, key: &str) -> Option<usize> {
528        self.members.iter().position(|member| &*member.key == key)
529    }
530
531    pub(crate) fn members(&self) -> &[JsonMember] {
532        self.members
533    }
534}
535
536#[derive(Clone, Copy)]
537pub(crate) struct ImageCropSource<'a> {
538    members: &'a [JsonMember],
539}
540
541#[derive(Clone, Copy)]
542pub(crate) struct RoundnessSource<'a> {
543    members: &'a [JsonMember],
544}
545
546impl RoundnessSource<'_> {
547    pub(crate) fn kind(&self) -> SourceField<i32> {
548        source_field(self.members, "type", json_i32)
549    }
550
551    pub(crate) fn value(&self) -> SourceField<f64> {
552        source_field(self.members, "value", finite_json_number)
553    }
554
555    pub(crate) fn member_position(&self, key: &str) -> Option<usize> {
556        self.members.iter().position(|member| &*member.key == key)
557    }
558
559    pub(crate) fn members(&self) -> &[JsonMember] {
560        self.members
561    }
562}
563
564impl ImageCropSource<'_> {
565    pub(crate) fn len(&self) -> usize {
566        self.members.len()
567    }
568
569    pub(crate) fn number(&self, key: &str) -> SourceField<f64> {
570        source_field(self.members, key, finite_json_number)
571    }
572
573    pub(crate) fn member_position(&self, key: &str) -> Option<usize> {
574        self.members.iter().position(|member| &*member.key == key)
575    }
576
577    pub(crate) fn members(&self) -> &[JsonMember] {
578        self.members
579    }
580}
581
582#[derive(Clone, Copy)]
583pub(crate) struct FrameRenderingSource<'a> {
584    members: &'a [JsonMember],
585}
586
587impl FrameRenderingSource<'_> {
588    pub(crate) fn boolean(&self, key: &str) -> SourceField<bool> {
589        source_field(self.members, key, json_bool)
590    }
591
592    pub(crate) fn member_position(&self, key: &str) -> Option<usize> {
593        self.members.iter().position(|member| &*member.key == key)
594    }
595
596    pub(crate) fn members(&self) -> &[JsonMember] {
597        self.members
598    }
599}
600
601pub(crate) struct FileRecordSource<'a> {
602    key: &'a str,
603    value: &'a JsonValue,
604}
605
606impl<'a> FileRecordSource<'a> {
607    pub(crate) fn key(&self) -> &'a str {
608        self.key
609    }
610
611    pub(crate) fn is_record(&self) -> bool {
612        matches!(self.value, JsonValue::Object(_))
613    }
614
615    pub(crate) fn members(&self) -> Option<&[JsonMember]> {
616        let JsonValue::Object(members) = self.value else {
617            return None;
618        };
619        Some(members)
620    }
621
622    pub(crate) fn id(&self) -> SourceField<&str> {
623        let JsonValue::Object(members) = self.value else {
624            return SourceField::Invalid;
625        };
626        source_field(members, "id", json_string)
627    }
628
629    pub(crate) fn created(&self) -> SourceField<u64> {
630        let JsonValue::Object(members) = self.value else {
631            return SourceField::Invalid;
632        };
633        source_field(members, "created", json_u64)
634    }
635
636    pub(crate) fn data_url(&self) -> SourceField<&str> {
637        let JsonValue::Object(members) = self.value else {
638            return SourceField::Invalid;
639        };
640        source_field(members, "dataURL", json_string)
641    }
642
643    pub(crate) fn mime_type(&self) -> SourceField<&str> {
644        let JsonValue::Object(members) = self.value else {
645            return SourceField::Invalid;
646        };
647        source_field(members, "mimeType", json_string)
648    }
649
650    pub(crate) fn member_position(&self, key: &str) -> Option<usize> {
651        let JsonValue::Object(members) = self.value else {
652            return None;
653        };
654        members.iter().position(|member| &*member.key == key)
655    }
656}
657
658#[derive(Clone, Copy)]
659pub(crate) enum SourceField<T> {
660    Missing,
661    Value(T),
662    Invalid,
663}
664
665pub(crate) enum OriginalJsonVisitError<E> {
666    Visitor(E),
667    MemoryUnavailable { json_pointer: String },
668}
669
670#[derive(Clone, Copy)]
671pub(crate) struct OriginalJsonEntry<'a> {
672    pub(crate) kind: OriginalJsonEntryKind,
673    pub(crate) json_pointer: &'a str,
674    pub(crate) is_element_occurrence: bool,
675}
676
677#[derive(Clone, Copy)]
678pub(crate) enum OriginalJsonEntryKind {
679    Node,
680    Member,
681}
682
683impl fmt::Debug for SceneDocument {
684    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
685        formatter
686            .debug_struct("SceneDocument")
687            .finish_non_exhaustive()
688    }
689}
690
691/// Indicates that scene-document bytes could not be admitted.
692///
693/// [`Error::source`] is always `None`: JSON decoder failures are flattened
694/// into the stable code and message.
695#[derive(Clone, Debug)]
696pub struct SceneParseError {
697    code: ErrorCode,
698    message: String,
699    json_pointer: Option<String>,
700    details: Option<Box<DiagnosticDetails>>,
701}
702
703impl SceneParseError {
704    fn new(
705        code: ErrorCode,
706        message: impl Into<String>,
707        json_pointer: Option<String>,
708        details: Option<DiagnosticDetails>,
709    ) -> Self {
710        Self {
711            code,
712            message: message.into(),
713            json_pointer,
714            details: details.map(Box::new),
715        }
716    }
717
718    fn invalid_json(json_pointer: Option<String>) -> Self {
719        Self::new(
720            ErrorCode::SCENE_INVALID_JSON,
721            "scene document is not valid JSON",
722            json_pointer,
723            None,
724        )
725    }
726
727    fn invalid_envelope(json_pointer: &str, message: impl Into<String>) -> Self {
728        Self::new(
729            ErrorCode::SCENE_INVALID_ENVELOPE,
730            message,
731            Some(json_pointer.to_owned()),
732            None,
733        )
734    }
735
736    fn unsupported_input(json_pointer: Option<&str>, message: &str) -> Self {
737        Self::new(
738            ErrorCode::SCENE_UNSUPPORTED_INPUT,
739            message,
740            json_pointer.map(str::to_owned),
741            None,
742        )
743    }
744
745    fn limit(limit: ParserLimit, measurement: Measurement, json_pointer: Option<String>) -> Self {
746        Self::new(
747            limit.code,
748            limit.message,
749            json_pointer,
750            Some(DiagnosticDetails::ParseLimit {
751                field: limit.field,
752                configured: limit.configured,
753                value_kind: limit.value_kind,
754                measurement,
755            }),
756        )
757    }
758
759    /// Returns the stable machine-readable error code.
760    pub const fn code(&self) -> ErrorCode {
761        self.code
762    }
763
764    /// Returns the narrowest reliable scene-document JSON pointer.
765    pub fn json_pointer(&self) -> Option<&str> {
766        self.json_pointer.as_deref()
767    }
768
769    /// Returns typed details required by this failure's stable code.
770    pub fn details(&self) -> Option<&DiagnosticDetails> {
771        self.details.as_deref()
772    }
773
774    /// Returns this failure's stable serializable descriptor.
775    pub fn descriptor(&self) -> ErrorDescriptor {
776        ErrorDescriptor::scene_parse(
777            self.code,
778            self.to_string(),
779            self.json_pointer.clone(),
780            self.details.as_deref().cloned(),
781        )
782    }
783}
784
785impl fmt::Display for SceneParseError {
786    /// The failure's message; a parser-limit crossing appends the measured
787    /// value, the limit field and its fixed ceiling.
788    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
789        formatter.write_str(&self.message)?;
790        if let Some(crossing) = self.details().and_then(DiagnosticDetails::limit_crossing) {
791            write!(formatter, ": {crossing}")?;
792        }
793        Ok(())
794    }
795}
796
797impl Error for SceneParseError {}
798
799/// The export a document that is neither UTF-8 nor JSON recognizably is
800/// (spec section 4.1): a PNG, SVG or PDF image is `scene.unsupported-input`
801/// rather than a malformed scene. `None` for anything else.
802fn recognized_export(bytes: &[u8]) -> Option<SceneParseError> {
803    let text = bytes
804        .strip_prefix("\u{feff}".as_bytes())
805        .unwrap_or(bytes)
806        .trim_ascii_start();
807    let message = if bytes.starts_with(b"\x89PNG\r\n\x1a\n") {
808        "this is a PNG image, not a scene document; open an image exported with an \
809         embedded scene in Excalidraw and save it as an .excalidraw file"
810    } else if text.starts_with(b"<svg")
811        || text.starts_with(b"<?xml")
812        || text.starts_with(b"<!DOCTYPE svg")
813    {
814        "this is an SVG image, not a scene document; open an image exported with an \
815         embedded scene in Excalidraw and save it as an .excalidraw file"
816    } else if bytes.starts_with(b"%PDF-") {
817        "this is a PDF document, not a scene document; render the .excalidraw file it \
818         was exported from"
819    } else {
820        return None;
821    };
822    Some(SceneParseError::unsupported_input(None, message))
823}
824
825/// Admits the version-2 envelope (spec section 4.1). Every rejection is
826/// `scene.invalid-envelope` at the offending or expected member, except the
827/// library and clipboard payloads most often mistaken for scene documents,
828/// which are `scene.unsupported-input` at `/type`; the message says what is
829/// wrong.
830fn validate_envelope(root: &JsonValue) -> Result<(), SceneParseError> {
831    let JsonValue::Object(members) = root else {
832        return Err(SceneParseError::invalid_envelope(
833            "",
834            format!(
835                "scene document must be a JSON object, not {}",
836                json_kind_phrase(root)
837            ),
838        ));
839    };
840
841    match member_value(members, "type") {
842        Some(JsonValue::String(value)) if &**value == "excalidraw" => {}
843        Some(JsonValue::String(value)) if &**value == "excalidrawlib" => {
844            return Err(SceneParseError::unsupported_input(
845                Some("/type"),
846                "this is an Excalidraw library (type \"excalidrawlib\"), not a scene document; \
847                 render a scene saved as an .excalidraw file instead",
848            ));
849        }
850        Some(JsonValue::String(value)) if &**value == "excalidraw/clipboard" => {
851            return Err(SceneParseError::unsupported_input(
852                Some("/type"),
853                "this is an Excalidraw clipboard payload (type \"excalidraw/clipboard\"), \
854                 not a scene document; paste it into Excalidraw and save the scene instead",
855            ));
856        }
857        Some(JsonValue::String(value)) => {
858            return Err(SceneParseError::invalid_envelope(
859                "/type",
860                format!("scene document type must be \"excalidraw\", not {value:?}"),
861            ));
862        }
863        Some(other) => {
864            return Err(SceneParseError::invalid_envelope(
865                "/type",
866                format!(
867                    "scene document type must be the string \"excalidraw\", not {}",
868                    json_kind_phrase(other)
869                ),
870            ));
871        }
872        None => {
873            return Err(SceneParseError::invalid_envelope(
874                "/type",
875                "scene document has no \"type\" member; expected \"excalidraw\"",
876            ));
877        }
878    }
879    match member_value(members, "version") {
880        Some(JsonValue::Number(value)) if &**value == "2" => {}
881        Some(JsonValue::Number(value)) => {
882            return Err(SceneParseError::invalid_envelope(
883                "/version",
884                format!("scene envelope version {value} is not supported; only version 2 is"),
885            ));
886        }
887        Some(other) => {
888            return Err(SceneParseError::invalid_envelope(
889                "/version",
890                format!(
891                    "scene envelope version must be the number 2, not {}",
892                    json_kind_phrase(other)
893                ),
894            ));
895        }
896        None => {
897            return Err(SceneParseError::invalid_envelope(
898                "/version",
899                "scene document has no \"version\" member; expected envelope version 2",
900            ));
901        }
902    }
903    require_container(
904        members,
905        "elements",
906        "/elements",
907        "an array",
908        true,
909        |value| matches!(value, JsonValue::Array(_)),
910    )?;
911    // Excalidraw's load path restores an absent `appState` to its defaults
912    // and an absent `files` to an empty map, and older excalidraw.com exports
913    // omit `files`; either may be absent, but not another kind.
914    require_container(
915        members,
916        "appState",
917        "/appState",
918        "an object",
919        false,
920        |value| matches!(value, JsonValue::Object(_)),
921    )?;
922    require_container(members, "files", "/files", "an object", false, |value| {
923        matches!(value, JsonValue::Object(_))
924    })?;
925    Ok(())
926}
927
928/// Requires the root member `key`, when present or `required`, to be of the
929/// expected container kind, which `expected` names for the message.
930fn require_container(
931    members: &[JsonMember],
932    key: &str,
933    pointer: &str,
934    expected: &str,
935    required: bool,
936    accepts: impl FnOnce(&JsonValue) -> bool,
937) -> Result<(), SceneParseError> {
938    match member_value(members, key) {
939        Some(value) if accepts(value) => Ok(()),
940        Some(value) => Err(SceneParseError::invalid_envelope(
941            pointer,
942            format!(
943                "scene document {key:?} must be {expected}, not {}",
944                json_kind_phrase(value)
945            ),
946        )),
947        None if !required => Ok(()),
948        None => Err(SceneParseError::invalid_envelope(
949            pointer,
950            format!("scene document has no {key:?} member; expected {expected}"),
951        )),
952    }
953}
954
955/// The JSON kind of `value` as a message phrase: `an array`, `null`.
956fn json_kind_phrase(value: &JsonValue) -> &'static str {
957    match value {
958        JsonValue::Null => "null",
959        JsonValue::Bool(_) => "a boolean",
960        JsonValue::Number(_) => "a number",
961        JsonValue::String(_) => "a string",
962        JsonValue::Array(_) => "an array",
963        JsonValue::Object(_) => "an object",
964    }
965}
966
967fn source_field<'a, T>(
968    members: &'a [JsonMember],
969    key: &str,
970    parse: impl FnOnce(&'a JsonValue) -> Option<T>,
971) -> SourceField<T> {
972    match member_value(members, key) {
973        None => SourceField::Missing,
974        Some(value) => parse(value).map_or(SourceField::Invalid, SourceField::Value),
975    }
976}
977
978fn source_roundness<'a>(
979    members: &'a [JsonMember],
980    key: &str,
981) -> SourceField<Option<RoundnessSource<'a>>> {
982    source_field(members, key, |value| match value {
983        JsonValue::Null => Some(None),
984        JsonValue::Object(members) => Some(Some(RoundnessSource { members })),
985        _ => None,
986    })
987}
988
989fn source_nullable_string<'a>(
990    members: &'a [JsonMember],
991    key: &str,
992) -> SourceField<Option<&'a str>> {
993    source_field(members, key, |value| match value {
994        JsonValue::Null => Some(None),
995        JsonValue::String(value) => Some(Some(value.as_ref())),
996        _ => None,
997    })
998}
999
1000fn source_point_array<'a>(
1001    members: &'a [JsonMember],
1002    key: &str,
1003) -> SourceField<PointArraySource<'a>> {
1004    source_field(members, key, |value| {
1005        let JsonValue::Array(values) = value else {
1006            return None;
1007        };
1008        Some(PointArraySource { values })
1009    })
1010}
1011
1012fn source_number_array<'a>(
1013    members: &'a [JsonMember],
1014    key: &str,
1015) -> SourceField<NumberArraySource<'a>> {
1016    source_field(members, key, |value| {
1017        let JsonValue::Array(values) = value else {
1018            return None;
1019        };
1020        Some(NumberArraySource { values })
1021    })
1022}
1023
1024fn source_binding<'a>(
1025    members: &'a [JsonMember],
1026    key: &str,
1027) -> SourceField<Option<BindingSource<'a>>> {
1028    source_field(members, key, |value| match value {
1029        JsonValue::Null => Some(None),
1030        JsonValue::Object(members) => Some(Some(BindingSource { members })),
1031        _ => None,
1032    })
1033}
1034
1035fn source_image_crop<'a>(
1036    members: &'a [JsonMember],
1037    key: &str,
1038) -> SourceField<Option<ImageCropSource<'a>>> {
1039    source_field(members, key, |value| match value {
1040        JsonValue::Null => Some(None),
1041        JsonValue::Object(members) => Some(Some(ImageCropSource { members })),
1042        _ => None,
1043    })
1044}
1045
1046fn parse_point(value: &JsonValue) -> Option<[f64; 2]> {
1047    let JsonValue::Array(values) = value else {
1048        return None;
1049    };
1050    if values.len() < 2 {
1051        return None;
1052    }
1053    Some([
1054        finite_json_number(&values[0])?,
1055        finite_json_number(&values[1])?,
1056    ])
1057}
1058
1059fn visit_json_value<E>(
1060    value: &JsonValue,
1061    json_pointer: &str,
1062    is_element_occurrence: bool,
1063    visit: &mut impl FnMut(OriginalJsonEntry<'_>) -> Result<(), E>,
1064) -> Result<(), OriginalJsonVisitError<E>> {
1065    let mut root_pointer = String::new();
1066    if root_pointer.try_reserve_exact(json_pointer.len()).is_err() {
1067        return Err(OriginalJsonVisitError::MemoryUnavailable {
1068            json_pointer: String::new(),
1069        });
1070    }
1071    root_pointer.push_str(json_pointer);
1072
1073    let mut pending = Vec::new();
1074    if pending.try_reserve_exact(1).is_err() {
1075        return Err(OriginalJsonVisitError::MemoryUnavailable {
1076            json_pointer: root_pointer,
1077        });
1078    }
1079    pending.push(OriginalJsonVisit::Node {
1080        value,
1081        json_pointer: root_pointer,
1082        is_element_occurrence,
1083    });
1084
1085    while let Some(entry) = pending.pop() {
1086        match entry {
1087            OriginalJsonVisit::Member {
1088                value,
1089                json_pointer,
1090            } => {
1091                visit(OriginalJsonEntry {
1092                    kind: OriginalJsonEntryKind::Member,
1093                    json_pointer: &json_pointer,
1094                    is_element_occurrence: false,
1095                })
1096                .map_err(OriginalJsonVisitError::Visitor)?;
1097                if pending.try_reserve(1).is_err() {
1098                    return Err(OriginalJsonVisitError::MemoryUnavailable { json_pointer });
1099                }
1100                pending.push(OriginalJsonVisit::Node {
1101                    value,
1102                    json_pointer,
1103                    is_element_occurrence: false,
1104                });
1105            }
1106            OriginalJsonVisit::Node {
1107                value,
1108                json_pointer,
1109                is_element_occurrence,
1110            } => {
1111                visit(OriginalJsonEntry {
1112                    kind: OriginalJsonEntryKind::Node,
1113                    json_pointer: &json_pointer,
1114                    is_element_occurrence,
1115                })
1116                .map_err(OriginalJsonVisitError::Visitor)?;
1117
1118                match value {
1119                    JsonValue::Array(values) => {
1120                        if pending.try_reserve(values.len()).is_err() {
1121                            return Err(OriginalJsonVisitError::MemoryUnavailable { json_pointer });
1122                        }
1123                        let elements = json_pointer == "/elements";
1124                        for (index, value) in values.iter().enumerate().rev() {
1125                            let child_pointer = match pointer_index(&json_pointer, index) {
1126                                Ok(pointer) => pointer,
1127                                Err(_) => {
1128                                    return Err(OriginalJsonVisitError::MemoryUnavailable {
1129                                        json_pointer,
1130                                    });
1131                                }
1132                            };
1133                            pending.push(OriginalJsonVisit::Node {
1134                                value,
1135                                json_pointer: child_pointer,
1136                                is_element_occurrence: elements,
1137                            });
1138                        }
1139                    }
1140                    JsonValue::Object(members) => {
1141                        if pending.try_reserve(members.len()).is_err() {
1142                            return Err(OriginalJsonVisitError::MemoryUnavailable { json_pointer });
1143                        }
1144                        for member in members.iter().rev() {
1145                            let child_pointer = match pointer_child(&json_pointer, &member.key) {
1146                                Ok(pointer) => pointer,
1147                                Err(_) => {
1148                                    return Err(OriginalJsonVisitError::MemoryUnavailable {
1149                                        json_pointer,
1150                                    });
1151                                }
1152                            };
1153                            pending.push(OriginalJsonVisit::Member {
1154                                value: &member.value,
1155                                json_pointer: child_pointer,
1156                            });
1157                        }
1158                    }
1159                    JsonValue::Null
1160                    | JsonValue::Bool(_)
1161                    | JsonValue::Number(_)
1162                    | JsonValue::String(_) => {}
1163                }
1164            }
1165        }
1166    }
1167    Ok(())
1168}
1169
1170enum OriginalJsonVisit<'a> {
1171    Node {
1172        value: &'a JsonValue,
1173        json_pointer: String,
1174        is_element_occurrence: bool,
1175    },
1176    Member {
1177        value: &'a JsonValue,
1178        json_pointer: String,
1179    },
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184    use super::SceneDocument;
1185
1186    #[test]
1187    fn canonicalized_original_scene_document_uses_normative_json_spelling() {
1188        let source = br#"
1189        {
1190            "\u0074ype": "excalidraw",
1191            "version": 2,
1192            "elements": [
1193                null,
1194                true,
1195                false,
1196                {
1197                    "id": "deleted",
1198                    "isDeleted": true,
1199                    "unknown": "\/\u00e9\u2028\u2029\uD83D\uDE00",
1200                    "controls": "\u0000\b\t\n\f\r\u001F\"\\\/"
1201                }
1202            ],
1203            "appState": {"zoom": {"value": 1E+00}, "opaque": -0},
1204            "files": {
1205                "unused": {"mimeType": "image/png", "dataURL": "future"},
1206                "broken": false
1207            },
1208            "futureRoot": [3.00, 1e-2]
1209        }
1210        "#;
1211        let expected = concat!(
1212            r#"{"type":"excalidraw","version":2,"elements":[null,true,false,{"id":"deleted","isDeleted":true,"unknown":"/"#,
1213            "\u{00e9}\u{2028}\u{2029}\u{1f600}",
1214            r#"","controls":"\u0000\b\t\n\f\r\u001f\"\\/"}],"appState":{"zoom":{"value":1E+00},"opaque":-0},"files":{"unused":{"mimeType":"image/png","dataURL":"future"},"broken":false},"futureRoot":[3.00,1e-2]}"#,
1215        );
1216
1217        let scene = SceneDocument::parse(source).expect("preservation vector is admitted");
1218
1219        assert_eq!(
1220            scene
1221                .canonicalized_original_bytes()
1222                .expect("canonical scene serialization succeeds"),
1223            expected.as_bytes()
1224        );
1225    }
1226
1227    #[test]
1228    fn canonicalized_original_scene_document_discards_whitespace_and_escape_spelling() {
1229        let escaped = br#" {
1230            "\u0074ype": "excalidraw",
1231            "version": 2,
1232            "elements": [{"label": "\u0061\/\u00e9"}],
1233            "appState": {},
1234            "files": {}
1235        } "#;
1236        let direct = concat!(
1237            r#"{"type":"excalidraw","version":2,"elements":[{"label":"a/"#,
1238            "\u{00e9}",
1239            r#""}],"appState":{},"files":{}}"#,
1240        );
1241
1242        let escaped = SceneDocument::parse(escaped).expect("escaped vector is admitted");
1243        let direct_scene =
1244            SceneDocument::parse(direct.as_bytes()).expect("direct vector is admitted");
1245
1246        assert_eq!(
1247            escaped
1248                .canonicalized_original_bytes()
1249                .expect("escaped scene serialization succeeds"),
1250            direct_scene
1251                .canonicalized_original_bytes()
1252                .expect("direct scene serialization succeeds")
1253        );
1254        assert_eq!(
1255            escaped
1256                .canonicalized_original_bytes()
1257                .expect("escaped scene serialization succeeds"),
1258            direct.as_bytes()
1259        );
1260    }
1261
1262    #[test]
1263    fn canonicalized_original_scene_document_preserves_order_and_number_tokens() {
1264        let member_order_a = br#"{"type":"excalidraw","version":2,"elements":[],"appState":{"future":{"first":1,"second":2}},"files":{}}"#;
1265        let member_order_b = br#"{"type":"excalidraw","version":2,"elements":[],"appState":{"future":{"second":2,"first":1}},"files":{}}"#;
1266        let array_order_a =
1267            br#"{"type":"excalidraw","version":2,"elements":[1,2],"appState":{},"files":{}}"#;
1268        let array_order_b =
1269            br#"{"type":"excalidraw","version":2,"elements":[2,1],"appState":{},"files":{}}"#;
1270        let number_tokens = br#"{"type":"excalidraw","version":2,"elements":[0,-0,1,1.0,1e0,1E+00,10.00e-01],"appState":{},"files":{}}"#;
1271
1272        let canonical = |source: &[u8]| {
1273            SceneDocument::parse(source)
1274                .expect("distinction vector is admitted")
1275                .canonicalized_original_bytes()
1276                .expect("distinction vector serialization succeeds")
1277        };
1278
1279        assert_ne!(canonical(member_order_a), canonical(member_order_b));
1280        assert_eq!(canonical(member_order_a), member_order_a);
1281        assert_eq!(canonical(member_order_b), member_order_b);
1282        assert_ne!(canonical(array_order_a), canonical(array_order_b));
1283        assert_eq!(canonical(array_order_a), array_order_a);
1284        assert_eq!(canonical(array_order_b), array_order_b);
1285        assert_eq!(canonical(number_tokens), number_tokens);
1286    }
1287}