Skip to main content

excalidraw_render/
frame.rs

1use std::error::Error;
2use std::fmt;
3
4use crate::scene::{ElementKind, SourceField};
5use crate::{
6    CompatibilityDiagnostic, ErrorCode, ErrorDescriptor, FrameRenderRequest, RenderError,
7    RenderOutcome, SceneDocument,
8};
9
10/// Kind of live frame occurrence discovered in a scene document.
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12#[non_exhaustive]
13pub enum FrameKind {
14    /// An ordinary Excalidraw frame.
15    Frame,
16    /// An Excalidraw magic frame.
17    MagicFrame,
18}
19
20impl FrameKind {
21    /// Returns the stable Excalidraw element-kind spelling.
22    pub const fn as_str(self) -> &'static str {
23        match self {
24            Self::Frame => "frame",
25            Self::MagicFrame => "magicframe",
26        }
27    }
28}
29
30impl fmt::Display for FrameKind {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        formatter.write_str(self.as_str())
33    }
34}
35
36/// Borrowed identity and display metadata for one live frame occurrence.
37#[derive(Clone, Copy)]
38pub struct FrameRef<'scene> {
39    pub(crate) scene: &'scene SceneDocument,
40    pub(crate) source_index: usize,
41    ordinal: u32,
42    kind: FrameKind,
43    id: Option<&'scene str>,
44    name: Option<&'scene str>,
45}
46
47impl FrameRef<'_> {
48    /// Returns the one-based source-order position among live frame occurrences.
49    pub const fn ordinal(self) -> u32 {
50        self.ordinal
51    }
52
53    /// Returns whether this is an ordinary or magic frame.
54    pub const fn kind(self) -> FrameKind {
55        self.kind
56    }
57
58    pub(crate) fn belongs_to(self, scene: &SceneDocument) -> bool {
59        self.scene.same_identity(scene)
60    }
61}
62
63impl<'scene> FrameRef<'scene> {
64    /// Returns the source element ID when it is a non-empty string.
65    ///
66    /// Excalidraw gives every element a unique ID, but a hand-edited or
67    /// damaged scene can omit or repeat one; the renderer repairs such IDs
68    /// when it renders, and the [ordinal](Self::ordinal) always identifies
69    /// the occurrence.
70    pub const fn id(self) -> Option<&'scene str> {
71        self.id
72    }
73
74    /// Returns the source display name when it was a string.
75    pub const fn name(self) -> Option<&'scene str> {
76        self.name
77    }
78}
79
80/// Two references are equal when they name the same frame occurrence of the
81/// same admitted scene. A clone of a [`SceneDocument`] is the same scene; a
82/// second parse of the same bytes is not.
83impl PartialEq for FrameRef<'_> {
84    fn eq(&self, other: &Self) -> bool {
85        self.belongs_to(other.scene) && self.source_index == other.source_index
86    }
87}
88
89impl Eq for FrameRef<'_> {}
90
91impl std::hash::Hash for FrameRef<'_> {
92    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
93        self.scene.hash_identity(state);
94        self.source_index.hash(state);
95    }
96}
97
98impl fmt::Debug for FrameRef<'_> {
99    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
100        formatter
101            .debug_struct("FrameRef")
102            .field("ordinal", &self.ordinal)
103            .field("kind", &self.kind)
104            .field("id", &self.id)
105            .field("name", &self.name)
106            .finish_non_exhaustive()
107    }
108}
109
110/// Iterator over live ordinary and magic frame occurrences.
111#[derive(Clone, Debug)]
112pub struct FrameRefs<'scene> {
113    scene: &'scene SceneDocument,
114    source_index: usize,
115    ordinal: u32,
116}
117
118impl<'scene> FrameRefs<'scene> {
119    pub(crate) const fn new(scene: &'scene SceneDocument) -> Self {
120        Self {
121            scene,
122            source_index: 0,
123            ordinal: 0,
124        }
125    }
126}
127
128impl<'scene> Iterator for FrameRefs<'scene> {
129    type Item = FrameRef<'scene>;
130
131    fn next(&mut self) -> Option<Self::Item> {
132        while self.source_index < self.scene.element_occurrence_count() {
133            let source_index = self.source_index;
134            self.source_index += 1;
135            if self.scene.element_is_silent(source_index) {
136                continue;
137            }
138            let Some(source) = self.scene.element_at(source_index) else {
139                continue;
140            };
141            let kind = match source.kind {
142                ElementKind::Frame => FrameKind::Frame,
143                ElementKind::MagicFrame => FrameKind::MagicFrame,
144                _ => continue,
145            };
146            self.ordinal = self.ordinal.saturating_add(1);
147            let id = match source.id {
148                SourceField::Value(id) if !id.is_empty() => Some(id),
149                SourceField::Value(_) | SourceField::Missing | SourceField::Invalid => None,
150            };
151            let name = match source.name {
152                SourceField::Value(name) => name,
153                SourceField::Missing | SourceField::Invalid => None,
154            };
155            return Some(FrameRef {
156                scene: self.scene,
157                source_index,
158                ordinal: self.ordinal,
159                kind,
160                id,
161                name,
162            });
163        }
164        None
165    }
166}
167
168/// Stable category of a lazy frame-batch construction failure.
169#[derive(Clone, Copy, Debug, Eq, PartialEq)]
170#[non_exhaustive]
171pub enum FrameBatchErrorKind {
172    /// A frame reference belongs to another scene document.
173    ForeignTarget,
174    /// The same frame occurrence was requested more than once.
175    DuplicateTarget,
176    /// Shared target-neutral preparation could not be completed.
177    SharedPreparationFailed,
178}
179
180/// Failure to construct a lazy multi-frame render operation.
181///
182/// [`Error::source`] is always `None`: a shared-preparation failure is
183/// flattened into its kind and subject.
184#[derive(Clone, Debug)]
185pub struct FrameBatchError {
186    kind: FrameBatchErrorKind,
187    ordinal: Option<u32>,
188    preparation_subject: Option<&'static str>,
189}
190
191impl FrameBatchError {
192    pub(crate) const fn new(kind: FrameBatchErrorKind, ordinal: u32) -> Self {
193        Self {
194            kind,
195            ordinal: Some(ordinal),
196            preparation_subject: None,
197        }
198    }
199
200    pub(crate) const fn preparation(error: &crate::render::FramePreparationError) -> Self {
201        Self::from_preparation_subject(error.subject())
202    }
203
204    const fn from_preparation_subject(subject: &'static str) -> Self {
205        Self {
206            kind: FrameBatchErrorKind::SharedPreparationFailed,
207            ordinal: None,
208            preparation_subject: Some(subject),
209        }
210    }
211
212    /// Returns the stable batch-construction failure category.
213    pub const fn kind(&self) -> FrameBatchErrorKind {
214        self.kind
215    }
216
217    /// Returns the implicated frame ordinal for a target-selection failure.
218    pub const fn ordinal(&self) -> Option<u32> {
219        self.ordinal
220    }
221
222    /// Returns the stable machine-readable error code.
223    pub const fn code(&self) -> ErrorCode {
224        match self.kind {
225            FrameBatchErrorKind::ForeignTarget => ErrorCode::FRAME_BATCH_FOREIGN_TARGET,
226            FrameBatchErrorKind::DuplicateTarget => ErrorCode::FRAME_BATCH_DUPLICATE_TARGET,
227            FrameBatchErrorKind::SharedPreparationFailed => {
228                ErrorCode::FRAME_BATCH_SHARED_PREPARATION_FAILED
229            }
230        }
231    }
232
233    /// Returns this failure's stable serializable descriptor.
234    pub fn descriptor(&self) -> ErrorDescriptor {
235        ErrorDescriptor::frame_batch(self.code(), self.to_string())
236    }
237}
238
239impl fmt::Display for FrameBatchError {
240    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
241        match self.kind {
242            FrameBatchErrorKind::ForeignTarget => {
243                write!(
244                    formatter,
245                    "frame {} belongs to another scene",
246                    self.ordinal.unwrap_or(0)
247                )
248            }
249            FrameBatchErrorKind::DuplicateTarget => {
250                write!(
251                    formatter,
252                    "frame {} was selected more than once",
253                    self.ordinal.unwrap_or(0)
254                )
255            }
256            FrameBatchErrorKind::SharedPreparationFailed => write!(
257                formatter,
258                "shared frame preparation failed for {}",
259                self.preparation_subject.unwrap_or("unknown_subject")
260            ),
261        }
262    }
263}
264
265impl Error for FrameBatchError {}
266
267/// One target result yielded by a lazy multi-frame render operation.
268#[derive(Debug)]
269pub struct FrameRenderEntry<'scene> {
270    frame: FrameRef<'scene>,
271    result: Result<RenderOutcome, RenderError>,
272    target_diagnostics: Vec<CompatibilityDiagnostic>,
273}
274
275impl<'scene> FrameRenderEntry<'scene> {
276    /// Returns the target frame identity.
277    pub const fn frame(&self) -> FrameRef<'scene> {
278        self.frame
279    }
280
281    /// Returns the complete target render result.
282    pub const fn result(&self) -> &Result<RenderOutcome, RenderError> {
283        &self.result
284    }
285
286    /// Returns diagnostics attributed specifically to this target.
287    pub fn target_diagnostics(&self) -> &[CompatibilityDiagnostic] {
288        &self.target_diagnostics
289    }
290
291    /// Consumes the entry and returns its complete target result.
292    ///
293    /// # Errors
294    ///
295    /// Returns this frame's [`RenderError`] when it could not be rendered,
296    /// as [`Renderer::render_frame`](crate::Renderer::render_frame) would.
297    pub fn into_result(self) -> Result<RenderOutcome, RenderError> {
298        self.result
299    }
300}
301
302/// Lazy source-ordered rendering of selected frames from one scene document.
303pub struct FrameRenderCursor<'scene, 'request> {
304    catalog: &'static crate::catalog::RevisionCatalog,
305    frames: Vec<FrameRef<'scene>>,
306    request: &'request FrameRenderRequest,
307    prepared: Option<crate::render::PreparedFrameRender>,
308    next_index: usize,
309    global_diagnostics: Vec<CompatibilityDiagnostic>,
310    global_diagnostics_initialized: bool,
311}
312
313impl fmt::Debug for FrameRenderCursor<'_, '_> {
314    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
315        formatter
316            .debug_struct("FrameRenderCursor")
317            .field("frame_count", &self.frames.len())
318            .field("next_index", &self.next_index)
319            .field("prepared", &self.prepared.is_some())
320            .finish_non_exhaustive()
321    }
322}
323
324impl<'scene, 'request> FrameRenderCursor<'scene, 'request> {
325    pub(crate) fn new(
326        catalog: &'static crate::catalog::RevisionCatalog,
327        scene: &'scene SceneDocument,
328        frames: impl IntoIterator<Item = FrameRef<'scene>>,
329        request: &'request FrameRenderRequest,
330    ) -> Result<Self, FrameBatchError> {
331        let mut selected = Vec::new();
332        for frame in frames {
333            selected
334                .try_reserve(1)
335                .map_err(|_| FrameBatchError::from_preparation_subject("target_selection"))?;
336            selected.push(frame);
337        }
338        let mut frames = selected;
339        for frame in &frames {
340            if !frame.belongs_to(scene) {
341                return Err(FrameBatchError::new(
342                    FrameBatchErrorKind::ForeignTarget,
343                    frame.ordinal(),
344                ));
345            }
346        }
347        frames.sort_unstable_by_key(|frame| frame.ordinal());
348        if let Some(duplicate) = frames
349            .windows(2)
350            .find(|frames| frames[0].source_index == frames[1].source_index)
351        {
352            return Err(FrameBatchError::new(
353                FrameBatchErrorKind::DuplicateTarget,
354                duplicate[0].ordinal(),
355            ));
356        }
357        let prepared = if frames.is_empty() {
358            None
359        } else {
360            Some(
361                crate::render::PreparedFrameRender::new(
362                    catalog.frame_profile(),
363                    catalog.scene_surface(),
364                    scene,
365                    request.as_render_request(),
366                )
367                .map_err(|error| FrameBatchError::preparation(&error))?,
368            )
369        };
370        Ok(Self {
371            catalog,
372            frames,
373            request,
374            prepared,
375            next_index: 0,
376            global_diagnostics: Vec::new(),
377            global_diagnostics_initialized: false,
378        })
379    }
380
381    /// Returns scene-global diagnostics discovered by targets rendered so far.
382    pub fn global_diagnostics(&self) -> &[CompatibilityDiagnostic] {
383        &self.global_diagnostics
384    }
385}
386
387impl<'scene> Iterator for FrameRenderCursor<'scene, '_> {
388    type Item = FrameRenderEntry<'scene>;
389
390    fn next(&mut self) -> Option<Self::Item> {
391        let frame = *self.frames.get(self.next_index)?;
392        self.next_index += 1;
393        let (result, global_diagnostics, target_diagnostics) = crate::render::render_frame_scoped(
394            self.catalog,
395            frame,
396            self.request,
397            self.prepared.as_ref(),
398        );
399        if !self.global_diagnostics_initialized {
400            self.global_diagnostics = global_diagnostics;
401            self.global_diagnostics_initialized = true;
402        }
403        Some(FrameRenderEntry {
404            frame,
405            result,
406            target_diagnostics,
407        })
408    }
409}