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 let store = inner.ctx.db_context.get_store();
162 crate::inner::refresh_block_positions(&mut block_dtos, store);
163 block_dtos.sort_by_key(|b| b.document_position);
164
165 let mut elements: Vec<FlowElement> = block_dtos
166 .iter()
167 .map(|b| {
168 FlowElement::Block(TextBlock {
169 doc: Arc::clone(doc_arc),
170 block_id: b.id as usize,
171 })
172 })
173 .collect();
174
175 let all_frames = frame_commands::get_all_frame(&inner.ctx).unwrap_or_default();
180 for f in &all_frames {
181 if f.id == frame_dto.id {
182 continue; }
184 if cell_frame_ids.contains(&(f.id as EntityId)) {
185 continue; }
187 if f.parent_frame == Some(frame_dto.id) {
189 if let Some(table_id) = f.table {
190 elements.push(FlowElement::Table(TextTable {
191 doc: Arc::clone(doc_arc),
192 table_id: table_id as usize,
193 }));
194 } else {
195 elements.push(FlowElement::Frame(TextFrame {
196 doc: Arc::clone(doc_arc),
197 frame_id: f.id as usize,
198 }));
199 }
200 }
201 }
202
203 elements
204}
205
206fn build_cell_frame_ids(inner: &TextDocumentInner) -> HashSet<EntityId> {
208 let mut ids = HashSet::new();
209 let all_cells = table_cell_commands::get_all_table_cell(&inner.ctx).unwrap_or_default();
210 for cell in &all_cells {
211 if let Some(frame_id) = cell.cell_frame {
212 ids.insert(frame_id);
213 }
214 }
215 ids
216}
217
218pub(crate) fn build_flow_snapshot(
228 inner: &TextDocumentInner,
229 frame_id: EntityId,
230) -> Vec<FlowElementSnapshot> {
231 let frame_dto = match frame_commands::get_frame(&inner.ctx, &frame_id)
232 .ok()
233 .flatten()
234 {
235 Some(f) => f,
236 None => return Vec::new(),
237 };
238
239 if !frame_dto.child_order.is_empty() {
240 let (elements, _) = snapshot_from_child_order(inner, &frame_dto.child_order, 0, frame_id);
241 elements
242 } else {
243 snapshot_fallback(inner, &frame_dto)
244 }
245}
246
247fn snapshot_from_child_order(
253 inner: &TextDocumentInner,
254 child_order: &[i64],
255 start_pos: usize,
256 parent_frame_id: EntityId,
257) -> (Vec<FlowElementSnapshot>, usize) {
258 let mut elements = Vec::with_capacity(child_order.len());
259 let mut running_pos = start_pos;
260
261 for &entry in child_order {
262 if entry > 0 {
263 let block_id = entry as u64;
264 if let Some(snap) = crate::text_block::build_block_snapshot_with_position_and_parent(
265 inner,
266 block_id,
267 Some(running_pos),
268 Some(parent_frame_id),
269 ) {
270 running_pos += snap.length + 1; elements.push(FlowElementSnapshot::Block(snap));
272 }
273 } else if entry < 0 {
274 let sub_frame_id = (-entry) as EntityId;
275 if let Some(sub_frame) = frame_commands::get_frame(&inner.ctx, &sub_frame_id)
276 .ok()
277 .flatten()
278 {
279 if let Some(table_id) = sub_frame.table {
280 if let Some((snap, new_pos)) =
281 build_table_snapshot_with_positions(inner, table_id, running_pos)
282 {
283 running_pos = new_pos;
284 elements.push(FlowElementSnapshot::Table(snap));
285 }
286 } else {
287 let (nested, new_pos) = snapshot_from_child_order(
288 inner,
289 &sub_frame.child_order,
290 running_pos,
291 sub_frame_id,
292 );
293 running_pos = new_pos;
294 elements.push(FlowElementSnapshot::Frame(FrameSnapshot {
295 frame_id: sub_frame_id as usize,
296 format: frame_dto_to_format(&sub_frame),
297 elements: nested,
298 }));
299 }
300 }
301 }
302 }
303
304 (elements, running_pos)
305}
306
307fn snapshot_fallback(
308 inner: &TextDocumentInner,
309 frame_dto: &frontend::frame::dtos::FrameDto,
310) -> Vec<FlowElementSnapshot> {
311 let cell_frame_ids = build_cell_frame_ids(inner);
312
313 let block_ids = &frame_dto.blocks;
314 let mut block_dtos: Vec<_> = block_ids
315 .iter()
316 .filter_map(|&id| {
317 block_commands::get_block(&inner.ctx, &{ id })
318 .ok()
319 .flatten()
320 })
321 .collect();
322 let store = inner.ctx.db_context.get_store();
323 crate::inner::refresh_block_positions(&mut block_dtos, store);
324 block_dtos.sort_by_key(|b| b.document_position);
325
326 let mut elements: Vec<FlowElementSnapshot> = block_dtos
327 .iter()
328 .filter_map(|b| crate::text_block::build_block_snapshot(inner, b.id))
329 .map(FlowElementSnapshot::Block)
330 .collect();
331
332 let all_frames = frame_commands::get_all_frame(&inner.ctx).unwrap_or_default();
333 for f in &all_frames {
334 if f.id == frame_dto.id {
335 continue;
336 }
337 if cell_frame_ids.contains(&(f.id as EntityId)) {
338 continue;
339 }
340 if f.parent_frame == Some(frame_dto.id) {
341 if let Some(table_id) = f.table {
342 if let Some(snap) = build_table_snapshot(inner, table_id) {
343 elements.push(FlowElementSnapshot::Table(snap));
344 }
345 } else {
346 let nested = build_flow_snapshot(inner, f.id as EntityId);
347 elements.push(FlowElementSnapshot::Frame(FrameSnapshot {
348 frame_id: f.id as usize,
349 format: frame_dto_to_format(f),
350 elements: nested,
351 }));
352 }
353 }
354 }
355
356 elements
357}
358
359pub(crate) fn build_table_snapshot(
361 inner: &TextDocumentInner,
362 table_id: u64,
363) -> Option<TableSnapshot> {
364 let table_dto = table_commands::get_table(&inner.ctx, &table_id)
365 .ok()
366 .flatten()?;
367
368 let mut cells = Vec::new();
369 for &cell_id in &table_dto.cells {
370 if let Some(cell_dto) = table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
371 .ok()
372 .flatten()
373 {
374 let blocks = if let Some(cell_frame_id) = cell_dto.cell_frame {
375 crate::text_block::build_blocks_snapshot_for_frame(inner, cell_frame_id)
376 } else {
377 Vec::new()
378 };
379 cells.push(CellSnapshot {
380 row: to_usize(cell_dto.row),
381 column: to_usize(cell_dto.column),
382 row_span: to_usize(cell_dto.row_span),
383 column_span: to_usize(cell_dto.column_span),
384 format: cell_dto_to_format(&cell_dto),
385 blocks,
386 });
387 }
388 }
389
390 Some(TableSnapshot {
391 table_id: table_id as usize,
392 rows: to_usize(table_dto.rows),
393 columns: to_usize(table_dto.columns),
394 column_widths: table_dto.column_widths.iter().map(|&v| v as i32).collect(),
395 format: table_dto_to_format(&table_dto),
396 cells,
397 })
398}
399
400fn build_table_snapshot_with_positions(
407 inner: &TextDocumentInner,
408 table_id: u64,
409 start_pos: usize,
410) -> Option<(TableSnapshot, usize)> {
411 let table_dto = table_commands::get_table(&inner.ctx, &table_id)
412 .ok()
413 .flatten()?;
414
415 let mut cell_dtos: Vec<_> = table_dto
417 .cells
418 .iter()
419 .filter_map(|&cell_id| {
420 table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
421 .ok()
422 .flatten()
423 })
424 .collect();
425 cell_dtos.sort_by(|a, b| a.row.cmp(&b.row).then(a.column.cmp(&b.column)));
426
427 let mut running_pos = start_pos;
428 let mut cells = Vec::with_capacity(cell_dtos.len());
429 for cell_dto in &cell_dtos {
430 let blocks = if let Some(cell_frame_id) = cell_dto.cell_frame {
431 let (snaps, new_pos) =
432 crate::text_block::build_blocks_snapshot_for_frame_with_positions(
433 inner,
434 cell_frame_id,
435 running_pos,
436 );
437 running_pos = new_pos;
438 snaps
439 } else {
440 Vec::new()
441 };
442 cells.push(CellSnapshot {
443 row: to_usize(cell_dto.row),
444 column: to_usize(cell_dto.column),
445 row_span: to_usize(cell_dto.row_span),
446 column_span: to_usize(cell_dto.column_span),
447 format: cell_dto_to_format(cell_dto),
448 blocks,
449 });
450 }
451
452 Some((
453 TableSnapshot {
454 table_id: table_id as usize,
455 rows: to_usize(table_dto.rows),
456 columns: to_usize(table_dto.columns),
457 column_widths: table_dto.column_widths.iter().map(|&v| v as i32).collect(),
458 format: table_dto_to_format(&table_dto),
459 cells,
460 },
461 running_pos,
462 ))
463}
464
465pub(crate) fn frame_dto_to_format(f: &frontend::frame::dtos::FrameDto) -> FrameFormat {
470 FrameFormat {
471 height: f.fmt_height.map(|v| v as i32),
472 width: f.fmt_width.map(|v| v as i32),
473 top_margin: f.fmt_top_margin.map(|v| v as i32),
474 bottom_margin: f.fmt_bottom_margin.map(|v| v as i32),
475 left_margin: f.fmt_left_margin.map(|v| v as i32),
476 right_margin: f.fmt_right_margin.map(|v| v as i32),
477 padding: f.fmt_padding.map(|v| v as i32),
478 border: f.fmt_border.map(|v| v as i32),
479 position: f.fmt_position.clone(),
480 is_blockquote: f.fmt_is_blockquote,
481 }
482}
483
484pub(crate) fn table_dto_to_format(t: &frontend::table::dtos::TableDto) -> crate::flow::TableFormat {
485 crate::flow::TableFormat {
486 border: t.fmt_border.map(|v| v as i32),
487 cell_spacing: t.fmt_cell_spacing.map(|v| v as i32),
488 cell_padding: t.fmt_cell_padding.map(|v| v as i32),
489 width: t.fmt_width.map(|v| v as i32),
490 alignment: t.fmt_alignment.clone(),
491 }
492}
493
494pub(crate) fn cell_dto_to_format(
495 c: &frontend::table_cell::dtos::TableCellDto,
496) -> crate::flow::CellFormat {
497 use frontend::common::entities::CellVerticalAlignment as BackendCVA;
498 crate::flow::CellFormat {
499 padding: c.fmt_padding.map(|v| v as i32),
500 border: c.fmt_border.map(|v| v as i32),
501 vertical_alignment: c.fmt_vertical_alignment.as_ref().map(|v| match v {
502 BackendCVA::Top => crate::flow::CellVerticalAlignment::Top,
503 BackendCVA::Middle => crate::flow::CellVerticalAlignment::Middle,
504 BackendCVA::Bottom => crate::flow::CellVerticalAlignment::Bottom,
505 }),
506 background_color: c.fmt_background_color.clone(),
507 }
508}