reflexo/vector/
incr.rs

1use super::ir::{
2    FlatGlyphItem, FlatModule, GlyphRef, LayoutRegionNode, LayoutSourceMapping, Module,
3    ModuleMetadata, MultiVecDocument, Page, SourceMappingNode,
4};
5use crate::{error::prelude::*, TakeAs};
6
7/// maintains the data of the incremental rendering at client side
8#[derive(Default)]
9pub struct IncrDocClient {
10    /// Full information of the current document from server.
11    pub doc: MultiVecDocument,
12    /// Hold glyphs.
13    pub glyphs: Vec<(GlyphRef, FlatGlyphItem)>,
14
15    /// checkout of the current document.
16    pub layout: Option<LayoutRegionNode>,
17    /// Optional source mapping data.
18    pub source_mapping_data: Vec<SourceMappingNode>,
19    /// Optional page source mapping references.
20    pub page_source_mapping: LayoutSourceMapping,
21}
22
23impl IncrDocClient {
24    /// Merge the delta from server.
25    pub fn merge_delta(&mut self, delta: FlatModule) {
26        self.doc.merge_delta(&delta);
27        for metadata in delta.metadata {
28            match metadata {
29                ModuleMetadata::Glyph(data) => {
30                    self.glyphs.extend(data.take().items.into_iter());
31                }
32                ModuleMetadata::SourceMappingData(data) => {
33                    self.source_mapping_data = data;
34                }
35                ModuleMetadata::PageSourceMapping(data) => {
36                    self.page_source_mapping = data.take();
37                }
38                _ => {}
39            }
40        }
41    }
42
43    /// Set the current layout of the document.
44    /// This is so bare-bone that stupidly takes a selected layout.
45    ///
46    /// Please wrap this for your own use case.
47    pub fn set_layout(&mut self, layout: LayoutRegionNode) {
48        self.layout = Some(layout);
49    }
50
51    /// Kern of the client without leaking abstraction.
52    pub fn kern(&self) -> IncrDocClientKern<'_> {
53        IncrDocClientKern::new(self)
54    }
55
56    pub fn module(&self) -> &Module {
57        &self.doc.module
58    }
59
60    pub fn module_mut(&mut self) -> &mut Module {
61        &mut self.doc.module
62    }
63}
64
65fn access_slice<'a, T>(v: &'a [T], idx: usize, kind: &'static str, pos: usize) -> Result<&'a T> {
66    v.get(idx).ok_or_else(
67        || error_once!("out of bound access", pos: pos, kind: kind, idx: idx, actual: v.len()),
68    )
69}
70
71pub struct IncrDocClientKern<'a>(&'a IncrDocClient);
72
73impl<'a> IncrDocClientKern<'a> {
74    pub fn new(client: &'a IncrDocClient) -> Self {
75        Self(client)
76    }
77
78    /// Get current pages meta of the selected document.
79    pub fn pages_meta(&self) -> Option<&[Page]> {
80        let layout = self.0.layout.as_ref();
81        layout.and_then(LayoutRegionNode::pages_meta)
82    }
83
84    /// Get estimated width of the document (in flavor of PDF Viewer).
85    pub fn doc_width(&self) -> Option<f32> {
86        let view = self.pages_meta()?.iter();
87        Some(view.map(|p| p.size.x).max().unwrap_or_default().0)
88    }
89
90    /// Get estimated height of the document (in flavor of PDF Viewer).
91    pub fn doc_height(&self) -> Option<f32> {
92        let view = self.pages_meta()?.iter();
93        Some(view.map(|p| p.size.y.0).sum())
94    }
95
96    /// Get the source location of the given path.
97    pub fn source_span(&self, path: &[u32]) -> Result<Option<String>> {
98        const SOURCE_MAPPING_TYPE_TEXT: u32 = 0;
99        const SOURCE_MAPPING_TYPE_GROUP: u32 = 1;
100        const SOURCE_MAPPING_TYPE_IMAGE: u32 = 2;
101        const SOURCE_MAPPING_TYPE_SHAPE: u32 = 3;
102        const SOURCE_MAPPING_TYPE_PAGE: u32 = 4;
103
104        if self.0.page_source_mapping.is_empty() {
105            return Ok(None);
106        }
107
108        let mut index_item: Option<&SourceMappingNode> = None;
109
110        let source_mapping = self.0.source_mapping_data.as_slice();
111        let page_sources = self.0.page_source_mapping[0]
112            .source_mapping(&self.0.doc.module)
113            .unwrap();
114        let page_sources = page_sources.source_mapping();
115
116        for (chunk_idx, v) in path.chunks_exact(2).enumerate() {
117            let (ty, idx) = (v[0], v[1] as usize);
118
119            let this_item: &SourceMappingNode = match index_item {
120                Some(SourceMappingNode::Group(q)) => {
121                    let idx = *access_slice(q, idx, "group_index", chunk_idx)? as usize;
122                    access_slice(source_mapping, idx, "source_mapping", chunk_idx)?
123                }
124                Some(_) => {
125                    return Err(error_once!("cannot index", pos:
126        chunk_idx, indexing: format!("{:?}", index_item)))
127                }
128                None => access_slice(page_sources, idx, "page_sources", chunk_idx)?,
129            };
130
131            match (ty, this_item) {
132                (SOURCE_MAPPING_TYPE_PAGE, SourceMappingNode::Page(page_index)) => {
133                    index_item = Some(access_slice(
134                        source_mapping,
135                        *page_index as usize,
136                        "source_mapping",
137                        chunk_idx,
138                    )?);
139                }
140                (SOURCE_MAPPING_TYPE_GROUP, SourceMappingNode::Group(_)) => {
141                    index_item = Some(this_item);
142                }
143                (SOURCE_MAPPING_TYPE_TEXT, SourceMappingNode::Text(n))
144                | (SOURCE_MAPPING_TYPE_IMAGE, SourceMappingNode::Image(n))
145                | (SOURCE_MAPPING_TYPE_SHAPE, SourceMappingNode::Shape(n)) => {
146                    return Ok(Some(format!("{n:x}")));
147                }
148                _ => {
149                    return Err(error_once!("invalid/mismatch node
150                    type",                         pos: chunk_idx, ty: ty,
151                        actual: format!("{:?}", this_item),
152                        parent: format!("{:?}", index_item),
153                        child_idx_in_parent: idx,
154                    ))
155                }
156            }
157        }
158
159        Ok(None)
160    }
161}