1use std::sync::Arc;
4
5use parking_lot::Mutex;
6
7use frontend::commands::{block_commands, frame_commands, inline_element_commands, list_commands};
8use frontend::common::types::EntityId;
9use frontend::inline_element::dtos::InlineContent;
10
11use crate::convert::to_usize;
12use crate::flow::{BlockSnapshot, FragmentContent, ListInfo, TableCellContext, TableCellRef};
13use crate::inner::TextDocumentInner;
14use crate::text_frame::TextFrame;
15use crate::text_list::TextList;
16use crate::text_table::TextTable;
17use crate::{BlockFormat, ListStyle, TextFormat};
18
19#[derive(Clone)]
26pub struct TextBlock {
27 pub(crate) doc: Arc<Mutex<TextDocumentInner>>,
28 pub(crate) block_id: usize,
29}
30
31impl TextBlock {
32 pub fn text(&self) -> String {
36 let inner = self.doc.lock();
37 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
38 .ok()
39 .flatten()
40 .map(|b| b.plain_text)
41 .unwrap_or_default()
42 }
43
44 pub fn length(&self) -> usize {
46 let inner = self.doc.lock();
47 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
48 .ok()
49 .flatten()
50 .map(|b| to_usize(b.text_length))
51 .unwrap_or(0)
52 }
53
54 pub fn is_empty(&self) -> bool {
56 let inner = self.doc.lock();
57 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
58 .ok()
59 .flatten()
60 .map(|b| b.text_length == 0)
61 .unwrap_or(true)
62 }
63
64 pub fn is_valid(&self) -> bool {
66 let inner = self.doc.lock();
67 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
68 .ok()
69 .flatten()
70 .is_some()
71 }
72
73 pub fn id(&self) -> usize {
77 self.block_id
78 }
79
80 pub fn position(&self) -> usize {
82 let inner = self.doc.lock();
83 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
84 .ok()
85 .flatten()
86 .map(|b| to_usize(b.document_position))
87 .unwrap_or(0)
88 }
89
90 pub fn block_number(&self) -> usize {
94 let inner = self.doc.lock();
95 compute_block_number(&inner, self.block_id as u64)
96 }
97
98 pub fn next(&self) -> Option<TextBlock> {
101 let inner = self.doc.lock();
102 let all_blocks = block_commands::get_all_block(&inner.ctx).ok()?;
103 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
104 sorted.sort_by_key(|b| b.document_position);
105 let idx = sorted.iter().position(|b| b.id == self.block_id as u64)?;
106 sorted.get(idx + 1).map(|b| TextBlock {
107 doc: Arc::clone(&self.doc),
108 block_id: b.id as usize,
109 })
110 }
111
112 pub fn previous(&self) -> Option<TextBlock> {
115 let inner = self.doc.lock();
116 let all_blocks = block_commands::get_all_block(&inner.ctx).ok()?;
117 let mut sorted: Vec<_> = all_blocks.into_iter().collect();
118 sorted.sort_by_key(|b| b.document_position);
119 let idx = sorted.iter().position(|b| b.id == self.block_id as u64)?;
120 if idx == 0 {
121 return None;
122 }
123 sorted.get(idx - 1).map(|b| TextBlock {
124 doc: Arc::clone(&self.doc),
125 block_id: b.id as usize,
126 })
127 }
128
129 pub fn frame(&self) -> TextFrame {
133 let inner = self.doc.lock();
134 let frame_id = find_parent_frame(&inner, self.block_id as u64);
135 TextFrame {
136 doc: Arc::clone(&self.doc),
137 frame_id: frame_id.map(|id| id as usize).unwrap_or(0),
138 }
139 }
140
141 pub fn table_cell(&self) -> Option<TableCellRef> {
147 let inner = self.doc.lock();
148 let frame_id = find_parent_frame(&inner, self.block_id as u64)?;
149
150 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
153 .ok()
154 .flatten()?;
155
156 if let Some(table_entity_id) = frame_dto.table {
157 let table_dto =
161 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
162 .ok()
163 .flatten()?;
164 for &cell_id in &table_dto.cells {
165 if let Some(cell_dto) =
166 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{
167 cell_id
168 })
169 .ok()
170 .flatten()
171 && cell_dto.cell_frame == Some(frame_id)
172 {
173 return Some(TableCellRef {
174 table: TextTable {
175 doc: Arc::clone(&self.doc),
176 table_id: table_entity_id as usize,
177 },
178 row: to_usize(cell_dto.row),
179 column: to_usize(cell_dto.column),
180 });
181 }
182 }
183 }
184
185 let all_tables =
188 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
189 for table_dto in &all_tables {
190 for &cell_id in &table_dto.cells {
191 if let Some(cell_dto) =
192 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{
193 cell_id
194 })
195 .ok()
196 .flatten()
197 && cell_dto.cell_frame == Some(frame_id)
198 {
199 return Some(TableCellRef {
200 table: TextTable {
201 doc: Arc::clone(&self.doc),
202 table_id: table_dto.id as usize,
203 },
204 row: to_usize(cell_dto.row),
205 column: to_usize(cell_dto.column),
206 });
207 }
208 }
209 }
210
211 None
212 }
213
214 pub fn block_format(&self) -> BlockFormat {
218 let inner = self.doc.lock();
219 block_commands::get_block(&inner.ctx, &(self.block_id as u64))
220 .ok()
221 .flatten()
222 .map(|b| BlockFormat::from(&b))
223 .unwrap_or_default()
224 }
225
226 pub fn char_format_at(&self, offset: usize) -> Option<TextFormat> {
233 let inner = self.doc.lock();
234 let fragments = build_fragments(&inner, self.block_id as u64);
235 for frag in &fragments {
236 match frag {
237 FragmentContent::Text {
238 format,
239 offset: frag_offset,
240 length,
241 ..
242 } => {
243 if offset >= *frag_offset && offset < frag_offset + length {
244 return Some(format.clone());
245 }
246 }
247 FragmentContent::Image {
248 format,
249 offset: frag_offset,
250 ..
251 } => {
252 if offset == *frag_offset {
253 return Some(format.clone());
254 }
255 }
256 }
257 }
258 None
259 }
260
261 pub fn fragments(&self) -> Vec<FragmentContent> {
265 let inner = self.doc.lock();
266 build_fragments(&inner, self.block_id as u64)
267 }
268
269 pub fn list(&self) -> Option<TextList> {
273 let inner = self.doc.lock();
274 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
275 .ok()
276 .flatten()?;
277 let list_id = block_dto.list?;
278 Some(TextList {
279 doc: Arc::clone(&self.doc),
280 list_id: list_id as usize,
281 })
282 }
283
284 pub fn list_item_index(&self) -> Option<usize> {
286 let inner = self.doc.lock();
287 let block_dto = block_commands::get_block(&inner.ctx, &(self.block_id as u64))
288 .ok()
289 .flatten()?;
290 let list_id = block_dto.list?;
291 Some(compute_list_item_index(
292 &inner,
293 list_id,
294 self.block_id as u64,
295 ))
296 }
297
298 pub fn snapshot(&self) -> BlockSnapshot {
302 let inner = self.doc.lock();
303 build_block_snapshot(&inner, self.block_id as u64).unwrap_or_else(|| BlockSnapshot {
304 block_id: self.block_id,
305 position: 0,
306 length: 0,
307 text: String::new(),
308 fragments: Vec::new(),
309 block_format: BlockFormat::default(),
310 list_info: None,
311 parent_frame_id: None,
312 table_cell: None,
313 })
314 }
315}
316
317fn find_parent_frame(inner: &TextDocumentInner, block_id: u64) -> Option<EntityId> {
323 let all_frames = frame_commands::get_all_frame(&inner.ctx).ok()?;
324 let block_entity_id = block_id as EntityId;
325 for frame in &all_frames {
326 if frame.blocks.contains(&block_entity_id) {
327 return Some(frame.id as EntityId);
328 }
329 }
330 None
331}
332
333fn find_table_cell_context(inner: &TextDocumentInner, block_id: u64) -> Option<TableCellContext> {
336 let frame_id = find_parent_frame(inner, block_id)?;
337
338 let frame_dto = frame_commands::get_frame(&inner.ctx, &frame_id)
339 .ok()
340 .flatten()?;
341
342 if let Some(table_entity_id) = frame_dto.table {
344 let table_dto =
345 frontend::commands::table_commands::get_table(&inner.ctx, &{ table_entity_id })
346 .ok()
347 .flatten()?;
348 for &cell_id in &table_dto.cells {
349 if let Some(cell_dto) =
350 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
351 .ok()
352 .flatten()
353 && cell_dto.cell_frame == Some(frame_id)
354 {
355 return Some(TableCellContext {
356 table_id: table_entity_id as usize,
357 row: to_usize(cell_dto.row),
358 column: to_usize(cell_dto.column),
359 });
360 }
361 }
362 }
363
364 let all_tables =
366 frontend::commands::table_commands::get_all_table(&inner.ctx).unwrap_or_default();
367 for table_dto in &all_tables {
368 for &cell_id in &table_dto.cells {
369 if let Some(cell_dto) =
370 frontend::commands::table_cell_commands::get_table_cell(&inner.ctx, &{ cell_id })
371 .ok()
372 .flatten()
373 && cell_dto.cell_frame == Some(frame_id)
374 {
375 return Some(TableCellContext {
376 table_id: table_dto.id as usize,
377 row: to_usize(cell_dto.row),
378 column: to_usize(cell_dto.column),
379 });
380 }
381 }
382 }
383
384 None
385}
386
387fn compute_block_number(inner: &TextDocumentInner, block_id: u64) -> usize {
389 let all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
390 let mut sorted: Vec<_> = all_blocks.iter().collect();
391 sorted.sort_by_key(|b| b.document_position);
392 sorted.iter().position(|b| b.id == block_id).unwrap_or(0)
393}
394
395pub(crate) fn build_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
397 let block_dto = match block_commands::get_block(&inner.ctx, &block_id)
398 .ok()
399 .flatten()
400 {
401 Some(b) => b,
402 None => return Vec::new(),
403 };
404
405 let element_ids = &block_dto.elements;
406 let elements: Vec<_> = element_ids
407 .iter()
408 .filter_map(|&id| {
409 inline_element_commands::get_inline_element(&inner.ctx, &{ id })
410 .ok()
411 .flatten()
412 })
413 .collect();
414
415 let mut fragments = Vec::with_capacity(elements.len());
416 let mut offset: usize = 0;
417
418 for el in &elements {
419 let format = TextFormat::from(el);
420 match &el.content {
421 InlineContent::Text(text) => {
422 let length = text.chars().count();
423 fragments.push(FragmentContent::Text {
424 text: text.clone(),
425 format,
426 offset,
427 length,
428 });
429 offset += length;
430 }
431 InlineContent::Image {
432 name,
433 width,
434 height,
435 quality,
436 } => {
437 fragments.push(FragmentContent::Image {
438 name: name.clone(),
439 width: *width as u32,
440 height: *height as u32,
441 quality: *quality as u32,
442 format,
443 offset,
444 });
445 offset += 1; }
447 InlineContent::Empty => {
448 }
450 }
451 }
452
453 fragments
454}
455
456fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
458 let all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
459 let mut list_blocks: Vec<_> = all_blocks
460 .iter()
461 .filter(|b| b.list == Some(list_id))
462 .collect();
463 list_blocks.sort_by_key(|b| b.document_position);
464 list_blocks
465 .iter()
466 .position(|b| b.id == block_id)
467 .unwrap_or(0)
468}
469
470pub(crate) fn format_list_marker(
472 list_dto: &frontend::list::dtos::ListDto,
473 item_index: usize,
474) -> String {
475 let number = item_index + 1; let marker_body = match list_dto.style {
477 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
481 ListStyle::LowerAlpha => {
482 if number <= 26 {
483 ((b'a' + (number as u8 - 1)) as char).to_string()
484 } else {
485 format!("{number}")
486 }
487 }
488 ListStyle::UpperAlpha => {
489 if number <= 26 {
490 ((b'A' + (number as u8 - 1)) as char).to_string()
491 } else {
492 format!("{number}")
493 }
494 }
495 ListStyle::LowerRoman => to_roman_lower(number),
496 ListStyle::UpperRoman => to_roman_upper(number),
497 };
498 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
499}
500
501fn to_roman_upper(mut n: usize) -> String {
502 const VALUES: &[(usize, &str)] = &[
503 (1000, "M"),
504 (900, "CM"),
505 (500, "D"),
506 (400, "CD"),
507 (100, "C"),
508 (90, "XC"),
509 (50, "L"),
510 (40, "XL"),
511 (10, "X"),
512 (9, "IX"),
513 (5, "V"),
514 (4, "IV"),
515 (1, "I"),
516 ];
517 let mut result = String::new();
518 for &(val, sym) in VALUES {
519 while n >= val {
520 result.push_str(sym);
521 n -= val;
522 }
523 }
524 result
525}
526
527fn to_roman_lower(n: usize) -> String {
528 to_roman_upper(n).to_lowercase()
529}
530
531fn build_list_info(
533 inner: &TextDocumentInner,
534 block_dto: &frontend::block::dtos::BlockDto,
535) -> Option<ListInfo> {
536 let list_id = block_dto.list?;
537 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
538 .ok()
539 .flatten()?;
540
541 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
542 let marker = format_list_marker(&list_dto, item_index);
543
544 Some(ListInfo {
545 list_id: list_id as usize,
546 style: list_dto.style.clone(),
547 indent: list_dto.indent as u8,
548 marker,
549 item_index,
550 })
551}
552
553pub(crate) fn build_block_snapshot(
555 inner: &TextDocumentInner,
556 block_id: u64,
557) -> Option<BlockSnapshot> {
558 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
559 .ok()
560 .flatten()?;
561
562 let fragments = build_fragments(inner, block_id);
563 let block_format = BlockFormat::from(&block_dto);
564 let list_info = build_list_info(inner, &block_dto);
565
566 let parent_frame_id = find_parent_frame(inner, block_id).map(|id| id as usize);
567 let table_cell = find_table_cell_context(inner, block_id);
568
569 Some(BlockSnapshot {
570 block_id: block_id as usize,
571 position: to_usize(block_dto.document_position),
572 length: to_usize(block_dto.text_length),
573 text: block_dto.plain_text,
574 fragments,
575 block_format,
576 list_info,
577 parent_frame_id,
578 table_cell,
579 })
580}
581
582pub(crate) fn build_blocks_snapshot_for_frame(
584 inner: &TextDocumentInner,
585 frame_id: u64,
586) -> Vec<BlockSnapshot> {
587 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
588 .ok()
589 .flatten()
590 {
591 Some(f) => f,
592 None => return Vec::new(),
593 };
594
595 let mut block_dtos: Vec<_> = frame_dto
596 .blocks
597 .iter()
598 .filter_map(|&id| {
599 block_commands::get_block(&inner.ctx, &{ id })
600 .ok()
601 .flatten()
602 })
603 .collect();
604 block_dtos.sort_by_key(|b| b.document_position);
605
606 block_dtos
607 .iter()
608 .filter_map(|b| build_block_snapshot(inner, b.id))
609 .collect()
610}