1use super::ir::{
2 FlatGlyphItem, FlatModule, GlyphRef, LayoutRegionNode, LayoutSourceMapping, Module,
3 ModuleMetadata, MultiVecDocument, Page, SourceMappingNode,
4};
5use crate::{error::prelude::*, TakeAs};
6
7#[derive(Default)]
9pub struct IncrDocClient {
10 pub doc: MultiVecDocument,
12 pub glyphs: Vec<(GlyphRef, FlatGlyphItem)>,
14
15 pub layout: Option<LayoutRegionNode>,
17 pub source_mapping_data: Vec<SourceMappingNode>,
19 pub page_source_mapping: LayoutSourceMapping,
21}
22
23impl IncrDocClient {
24 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 pub fn set_layout(&mut self, layout: LayoutRegionNode) {
48 self.layout = Some(layout);
49 }
50
51 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 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 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 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 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}