1use std::collections::HashSet;
4use std::sync::Arc;
5
6use parking_lot::Mutex;
7
8use frontend::commands::{block_commands, frame_commands, table_cell_commands, table_commands};
9use frontend::common::types::EntityId;
10
11use crate::FrameFormat;
12use crate::convert::to_usize;
13use crate::flow::{CellSnapshot, FlowElement, FlowElementSnapshot, FrameSnapshot, TableSnapshot};
14use crate::inner::TextDocumentInner;
15use crate::text_block::TextBlock;
16use crate::text_table::TextTable;
17
18#[derive(Clone)]
22pub struct TextFrame {
23 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
24 pub(crate) frame_id: usize,
25}
26
27impl TextFrame {
28 pub fn id(&self) -> usize {
30 self.frame_id
31 }
32
33 pub fn format(&self) -> FrameFormat {
35 let inner = self.doc.lock();
36 let frame_dto = frame_commands::get_frame(&inner.ctx, &(self.frame_id as EntityId))
37 .ok()
38 .flatten();
39 match frame_dto {
40 Some(f) => frame_dto_to_format(&f),
41 None => FrameFormat::default(),
42 }
43 }
44
45 pub fn flow(&self) -> Vec<FlowElement> {
48 let inner = self.doc.lock();
49 build_flow_elements(&inner, &self.doc, self.frame_id as EntityId)
50 }
51
52 pub fn snapshot(&self) -> FrameSnapshot {
56 let inner = self.doc.lock();
57 let format = frame_commands::get_frame(&inner.ctx, &(self.frame_id as EntityId))
58 .ok()
59 .flatten()
60 .map(|f| frame_dto_to_format(&f))
61 .unwrap_or_default();
62 let elements = build_flow_snapshot(&inner, self.frame_id as EntityId);
63 FrameSnapshot {
64 frame_id: self.frame_id,
65 format,
66 elements,
67 }
68 }
69}
70
71pub(crate) fn build_flow_elements(
80 inner: &TextDocumentInner,
81 doc_arc: &Arc<Mutex<TextDocumentInner>>,
82 frame_id: EntityId,
83) -> Vec<FlowElement> {
84 let frame_dto = match frame_commands::get_frame(&inner.ctx, &frame_id)
85 .ok()
86 .flatten()
87 {
88 Some(f) => f,
89 None => return Vec::new(),
90 };
91
92 if !frame_dto.child_order.is_empty() {
93 flow_from_child_order(inner, doc_arc, &frame_dto.child_order)
94 } else {
95 flow_fallback(inner, doc_arc, &frame_dto)
96 }
97}
98
99fn flow_from_child_order(
101 inner: &TextDocumentInner,
102 doc_arc: &Arc<Mutex<TextDocumentInner>>,
103 child_order: &[i64],
104) -> Vec<FlowElement> {
105 let mut elements = Vec::with_capacity(child_order.len());
106
107 for &entry in child_order {
108 if entry > 0 {
109 elements.push(FlowElement::Block(TextBlock {
111 doc: Arc::clone(doc_arc),
112 block_id: entry as usize,
113 }));
114 } else if entry < 0 {
115 let sub_frame_id = (-entry) as EntityId;
117 if let Some(sub_frame) = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
118 .ok()
119 .flatten()
120 {
121 if let Some(table_id) = sub_frame.table {
122 elements.push(FlowElement::Table(TextTable {
124 doc: Arc::clone(doc_arc),
125 table_id: table_id as usize,
126 }));
127 } else {
128 elements.push(FlowElement::Frame(TextFrame {
130 doc: Arc::clone(doc_arc),
131 frame_id: sub_frame_id as usize,
132 }));
133 }
134 }
135 }
136 }
138
139 elements
140}
141
142fn flow_fallback(
144 inner: &TextDocumentInner,
145 doc_arc: &Arc<Mutex<TextDocumentInner>>,
146 frame_dto: &frontend::frame::dtos::FrameDto,
147) -> Vec<FlowElement> {
148 let cell_frame_ids = build_cell_frame_ids(inner);
150
151 let block_ids = &frame_dto.blocks;
153 let mut block_dtos: Vec<_> = block_ids
154 .iter()
155 .filter_map(|&id| {
156 block_commands::get_block(&inner.ctx, &{ id })
157 .ok()
158 .flatten()
159 })
160 .collect();
161 block_dtos.sort_by_key(|b| b.document_position);
162
163 let mut elements: Vec<FlowElement> = block_dtos
164 .iter()
165 .map(|b| {
166 FlowElement::Block(TextBlock {
167 doc: Arc::clone(doc_arc),
168 block_id: b.id as usize,
169 })
170 })
171 .collect();
172
173 let all_frames = frame_commands::get_all_frame(&inner.ctx).unwrap_or_default();
178 for f in &all_frames {
179 if f.id == frame_dto.id {
180 continue; }
182 if cell_frame_ids.contains(&(f.id as EntityId)) {
183 continue; }
185 if f.parent_frame == Some(frame_dto.id) {
187 if let Some(table_id) = f.table {
188 elements.push(FlowElement::Table(TextTable {
189 doc: Arc::clone(doc_arc),
190 table_id: table_id as usize,
191 }));
192 } else {
193 elements.push(FlowElement::Frame(TextFrame {
194 doc: Arc::clone(doc_arc),
195 frame_id: f.id as usize,
196 }));
197 }
198 }
199 }
200
201 elements
202}
203
204fn build_cell_frame_ids(inner: &TextDocumentInner) -> HashSet<EntityId> {
206 let mut ids = HashSet::new();
207 let all_cells = table_cell_commands::get_all_table_cell(&inner.ctx).unwrap_or_default();
208 for cell in &all_cells {
209 if let Some(frame_id) = cell.cell_frame {
210 ids.insert(frame_id);
211 }
212 }
213 ids
214}
215
216pub(crate) fn build_flow_snapshot(
222 inner: &TextDocumentInner,
223 frame_id: EntityId,
224) -> Vec<FlowElementSnapshot> {
225 let frame_dto = match frame_commands::get_frame(&inner.ctx, &frame_id)
226 .ok()
227 .flatten()
228 {
229 Some(f) => f,
230 None => return Vec::new(),
231 };
232
233 if !frame_dto.child_order.is_empty() {
234 snapshot_from_child_order(inner, &frame_dto.child_order)
235 } else {
236 snapshot_fallback(inner, &frame_dto)
237 }
238}
239
240fn snapshot_from_child_order(
241 inner: &TextDocumentInner,
242 child_order: &[i64],
243) -> Vec<FlowElementSnapshot> {
244 let mut elements = Vec::with_capacity(child_order.len());
245
246 for &entry in child_order {
247 if entry > 0 {
248 let block_id = entry as u64;
249 if let Some(snap) = crate::text_block::build_block_snapshot(inner, block_id) {
250 elements.push(FlowElementSnapshot::Block(snap));
251 }
252 } else if entry < 0 {
253 let sub_frame_id = (-entry) as EntityId;
254 if let Some(sub_frame) = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
255 .ok()
256 .flatten()
257 {
258 if let Some(table_id) = sub_frame.table {
259 if let Some(snap) = build_table_snapshot(inner, table_id) {
260 elements.push(FlowElementSnapshot::Table(snap));
261 }
262 } else {
263 let nested = build_flow_snapshot(inner, sub_frame_id);
264 elements.push(FlowElementSnapshot::Frame(FrameSnapshot {
265 frame_id: sub_frame_id as usize,
266 format: frame_dto_to_format(&sub_frame),
267 elements: nested,
268 }));
269 }
270 }
271 }
272 }
273
274 elements
275}
276
277fn snapshot_fallback(
278 inner: &TextDocumentInner,
279 frame_dto: &frontend::frame::dtos::FrameDto,
280) -> Vec<FlowElementSnapshot> {
281 let cell_frame_ids = build_cell_frame_ids(inner);
282
283 let block_ids = &frame_dto.blocks;
284 let mut block_dtos: Vec<_> = block_ids
285 .iter()
286 .filter_map(|&id| {
287 block_commands::get_block(&inner.ctx, &{ id })
288 .ok()
289 .flatten()
290 })
291 .collect();
292 block_dtos.sort_by_key(|b| b.document_position);
293
294 let mut elements: Vec<FlowElementSnapshot> = block_dtos
295 .iter()
296 .filter_map(|b| crate::text_block::build_block_snapshot(inner, b.id))
297 .map(FlowElementSnapshot::Block)
298 .collect();
299
300 let all_frames = frame_commands::get_all_frame(&inner.ctx).unwrap_or_default();
301 for f in &all_frames {
302 if f.id == frame_dto.id {
303 continue;
304 }
305 if cell_frame_ids.contains(&(f.id as EntityId)) {
306 continue;
307 }
308 if f.parent_frame == Some(frame_dto.id) {
309 if let Some(table_id) = f.table {
310 if let Some(snap) = build_table_snapshot(inner, table_id) {
311 elements.push(FlowElementSnapshot::Table(snap));
312 }
313 } else {
314 let nested = build_flow_snapshot(inner, f.id as EntityId);
315 elements.push(FlowElementSnapshot::Frame(FrameSnapshot {
316 frame_id: f.id as usize,
317 format: frame_dto_to_format(f),
318 elements: nested,
319 }));
320 }
321 }
322 }
323
324 elements
325}
326
327pub(crate) fn build_table_snapshot(
329 inner: &TextDocumentInner,
330 table_id: u64,
331) -> Option<TableSnapshot> {
332 let table_dto = table_commands::get_table(&inner.ctx, &table_id)
333 .ok()
334 .flatten()?;
335
336 let mut cells = Vec::new();
337 for &cell_id in &table_dto.cells {
338 if let Some(cell_dto) = table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
339 .ok()
340 .flatten()
341 {
342 let blocks = if let Some(cell_frame_id) = cell_dto.cell_frame {
343 crate::text_block::build_blocks_snapshot_for_frame(inner, cell_frame_id)
344 } else {
345 Vec::new()
346 };
347 cells.push(CellSnapshot {
348 row: to_usize(cell_dto.row),
349 column: to_usize(cell_dto.column),
350 row_span: to_usize(cell_dto.row_span),
351 column_span: to_usize(cell_dto.column_span),
352 format: cell_dto_to_format(&cell_dto),
353 blocks,
354 });
355 }
356 }
357
358 Some(TableSnapshot {
359 table_id: table_id as usize,
360 rows: to_usize(table_dto.rows),
361 columns: to_usize(table_dto.columns),
362 column_widths: table_dto.column_widths.iter().map(|&v| v as i32).collect(),
363 format: table_dto_to_format(&table_dto),
364 cells,
365 })
366}
367
368pub(crate) fn frame_dto_to_format(f: &frontend::frame::dtos::FrameDto) -> FrameFormat {
373 FrameFormat {
374 height: f.fmt_height.map(|v| v as i32),
375 width: f.fmt_width.map(|v| v as i32),
376 top_margin: f.fmt_top_margin.map(|v| v as i32),
377 bottom_margin: f.fmt_bottom_margin.map(|v| v as i32),
378 left_margin: f.fmt_left_margin.map(|v| v as i32),
379 right_margin: f.fmt_right_margin.map(|v| v as i32),
380 padding: f.fmt_padding.map(|v| v as i32),
381 border: f.fmt_border.map(|v| v as i32),
382 position: f.fmt_position.clone(),
383 }
384}
385
386pub(crate) fn table_dto_to_format(t: &frontend::table::dtos::TableDto) -> crate::flow::TableFormat {
387 crate::flow::TableFormat {
388 border: t.fmt_border.map(|v| v as i32),
389 cell_spacing: t.fmt_cell_spacing.map(|v| v as i32),
390 cell_padding: t.fmt_cell_padding.map(|v| v as i32),
391 width: t.fmt_width.map(|v| v as i32),
392 alignment: t.fmt_alignment.clone(),
393 }
394}
395
396pub(crate) fn cell_dto_to_format(
397 c: &frontend::table_cell::dtos::TableCellDto,
398) -> crate::flow::CellFormat {
399 use frontend::common::entities::CellVerticalAlignment as BackendCVA;
400 crate::flow::CellFormat {
401 padding: c.fmt_padding.map(|v| v as i32),
402 border: c.fmt_border.map(|v| v as i32),
403 vertical_alignment: c.fmt_vertical_alignment.as_ref().map(|v| match v {
404 BackendCVA::Top => crate::flow::CellVerticalAlignment::Top,
405 BackendCVA::Middle => crate::flow::CellVerticalAlignment::Middle,
406 BackendCVA::Bottom => crate::flow::CellVerticalAlignment::Bottom,
407 }),
408 background_color: c.fmt_background_color.clone(),
409 }
410}