1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use super::ir::{
    FlatGlyphItem, FlatModule, GlyphRef, LayoutRegionNode, LayoutSourceMapping, Module,
    ModuleMetadata, MultiVecDocument, Page, SourceMappingNode,
};
use crate::{error::prelude::*, TakeAs};

/// maintains the data of the incremental rendering at client side
#[derive(Default)]
pub struct IncrDocClient {
    /// Full information of the current document from server.
    pub doc: MultiVecDocument,
    /// Hold glyphs.
    pub glyphs: Vec<(GlyphRef, FlatGlyphItem)>,

    /// checkout of the current document.
    pub layout: Option<LayoutRegionNode>,
    /// Optional source mapping data.
    pub source_mapping_data: Vec<SourceMappingNode>,
    /// Optional page source mapping references.
    pub page_source_mappping: LayoutSourceMapping,
}

impl IncrDocClient {
    /// Merge the delta from server.
    pub fn merge_delta(&mut self, delta: FlatModule) {
        self.doc.merge_delta(&delta);
        for metadata in delta.metadata {
            match metadata {
                ModuleMetadata::Glyph(data) => {
                    self.glyphs.extend(data.take().items.into_iter());
                }
                ModuleMetadata::SourceMappingData(data) => {
                    self.source_mapping_data = data;
                }
                ModuleMetadata::PageSourceMapping(data) => {
                    self.page_source_mappping = data.take();
                }
                _ => {}
            }
        }
    }

    /// Set the current layout of the document.
    /// This is so bare-bone that stupidly takes a selected layout.
    ///
    /// Please wrap this for your own use case.
    pub fn set_layout(&mut self, layout: LayoutRegionNode) {
        self.layout = Some(layout);
    }

    /// Kern of the client without leaking abstraction.
    pub fn kern(&self) -> IncrDocClientKern<'_> {
        IncrDocClientKern::new(self)
    }

    pub fn module(&self) -> &Module {
        &self.doc.module
    }

    pub fn module_mut(&mut self) -> &mut Module {
        &mut self.doc.module
    }
}

fn access_slice<'a, T>(v: &'a [T], idx: usize, kind: &'static str, pos: usize) -> ZResult<&'a T> {
    v.get(idx).ok_or_else(
        || error_once!("out of bound access", pos: pos, kind: kind, idx: idx, actual: v.len()),
    )
}

pub struct IncrDocClientKern<'a>(&'a IncrDocClient);

impl<'a> IncrDocClientKern<'a> {
    pub fn new(client: &'a IncrDocClient) -> Self {
        Self(client)
    }

    /// Get current pages meta of the selected document.
    pub fn pages_meta(&self) -> Option<&[Page]> {
        let layout = self.0.layout.as_ref();
        layout.and_then(LayoutRegionNode::pages_meta)
    }

    /// Get estimated width of the document (in flavor of PDF Viewer).
    pub fn doc_width(&self) -> Option<f32> {
        let view = self.pages_meta()?.iter();
        Some(view.map(|p| p.size.x).max().unwrap_or_default().0)
    }

    /// Get estimated height of the document (in flavor of PDF Viewer).
    pub fn doc_height(&self) -> Option<f32> {
        let view = self.pages_meta()?.iter();
        Some(view.map(|p| p.size.y.0).sum())
    }

    /// Get the source location of the given path.
    pub fn source_span(&self, path: &[u32]) -> ZResult<Option<String>> {
        const SOURCE_MAPPING_TYPE_TEXT: u32 = 0;
        const SOURCE_MAPPING_TYPE_GROUP: u32 = 1;
        const SOURCE_MAPPING_TYPE_IMAGE: u32 = 2;
        const SOURCE_MAPPING_TYPE_SHAPE: u32 = 3;
        const SOURCE_MAPPING_TYPE_PAGE: u32 = 4;

        if self.0.page_source_mappping.is_empty() {
            return Ok(None);
        }

        let mut index_item: Option<&SourceMappingNode> = None;

        let source_mapping = self.0.source_mapping_data.as_slice();
        let page_sources = self.0.page_source_mappping[0]
            .source_mapping(&self.0.doc.module)
            .unwrap();
        let page_sources = page_sources.source_mapping();

        for (chunk_idx, v) in path.chunks_exact(2).enumerate() {
            let (ty, idx) = (v[0], v[1] as usize);

            let this_item: &SourceMappingNode = match index_item {
                Some(SourceMappingNode::Group(q)) => {
                    let idx = *access_slice(q, idx, "group_index", chunk_idx)? as usize;
                    access_slice(source_mapping, idx, "source_mapping", chunk_idx)?
                }
                Some(_) => {
                    return Err(error_once!("cannot index", pos:
        chunk_idx, indexing: format!("{:?}", index_item)))
                }
                None => access_slice(page_sources, idx, "page_sources", chunk_idx)?,
            };

            match (ty, this_item) {
                (SOURCE_MAPPING_TYPE_PAGE, SourceMappingNode::Page(page_index)) => {
                    index_item = Some(access_slice(
                        source_mapping,
                        *page_index as usize,
                        "source_mapping",
                        chunk_idx,
                    )?);
                }
                (SOURCE_MAPPING_TYPE_GROUP, SourceMappingNode::Group(_)) => {
                    index_item = Some(this_item);
                }
                (SOURCE_MAPPING_TYPE_TEXT, SourceMappingNode::Text(n))
                | (SOURCE_MAPPING_TYPE_IMAGE, SourceMappingNode::Image(n))
                | (SOURCE_MAPPING_TYPE_SHAPE, SourceMappingNode::Shape(n)) => {
                    return Ok(Some(format!("{n:x}")));
                }
                _ => {
                    return Err(error_once!("invalid/mismatch node
                    type",                         pos: chunk_idx, ty: ty,
                        actual: format!("{:?}", this_item),
                        parent: format!("{:?}", index_item),
                        child_idx_in_parent: idx,
                    ))
                }
            }
        }

        Ok(None)
    }
}