1mod 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#[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 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 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 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 #[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#[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 pub const fn code(&self) -> ErrorCode {
761 self.code
762 }
763
764 pub fn json_pointer(&self) -> Option<&str> {
766 self.json_pointer.as_deref()
767 }
768
769 pub fn details(&self) -> Option<&DiagnosticDetails> {
771 self.details.as_deref()
772 }
773
774 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 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
799fn 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
825fn 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 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
928fn 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
955fn 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}