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> {
398 let fragments = build_raw_fragments(inner, block_id);
399
400 if let Some(ref hl) = inner.highlight
401 && let Some(block_hl) = hl.blocks.get(&(block_id as usize))
402 && !block_hl.spans.is_empty()
403 {
404 return crate::highlight::merge_highlight_spans(fragments, &block_hl.spans);
405 }
406
407 fragments
408}
409
410fn build_raw_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
412 let block_dto = match block_commands::get_block(&inner.ctx, &block_id)
413 .ok()
414 .flatten()
415 {
416 Some(b) => b,
417 None => return Vec::new(),
418 };
419
420 let element_ids = &block_dto.elements;
421 let elements: Vec<_> = element_ids
422 .iter()
423 .filter_map(|&id| {
424 inline_element_commands::get_inline_element(&inner.ctx, &{ id })
425 .ok()
426 .flatten()
427 })
428 .collect();
429
430 let mut fragments = Vec::with_capacity(elements.len());
431 let mut offset: usize = 0;
432
433 for el in &elements {
434 let format = TextFormat::from(el);
435 match &el.content {
436 InlineContent::Text(text) => {
437 let length = text.chars().count();
438 fragments.push(FragmentContent::Text {
439 text: text.clone(),
440 format,
441 offset,
442 length,
443 });
444 offset += length;
445 }
446 InlineContent::Image {
447 name,
448 width,
449 height,
450 quality,
451 } => {
452 fragments.push(FragmentContent::Image {
453 name: name.clone(),
454 width: *width as u32,
455 height: *height as u32,
456 quality: *quality as u32,
457 format,
458 offset,
459 });
460 offset += 1; }
462 InlineContent::Empty => {
463 }
465 }
466 }
467
468 fragments
469}
470
471fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
473 let all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
474 let mut list_blocks: Vec<_> = all_blocks
475 .iter()
476 .filter(|b| b.list == Some(list_id))
477 .collect();
478 list_blocks.sort_by_key(|b| b.document_position);
479 list_blocks
480 .iter()
481 .position(|b| b.id == block_id)
482 .unwrap_or(0)
483}
484
485pub(crate) fn format_list_marker(
487 list_dto: &frontend::list::dtos::ListDto,
488 item_index: usize,
489) -> String {
490 let number = item_index + 1; let marker_body = match list_dto.style {
492 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
496 ListStyle::LowerAlpha => {
497 if number <= 26 {
498 ((b'a' + (number as u8 - 1)) as char).to_string()
499 } else {
500 format!("{number}")
501 }
502 }
503 ListStyle::UpperAlpha => {
504 if number <= 26 {
505 ((b'A' + (number as u8 - 1)) as char).to_string()
506 } else {
507 format!("{number}")
508 }
509 }
510 ListStyle::LowerRoman => to_roman_lower(number),
511 ListStyle::UpperRoman => to_roman_upper(number),
512 };
513 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
514}
515
516fn to_roman_upper(mut n: usize) -> String {
517 const VALUES: &[(usize, &str)] = &[
518 (1000, "M"),
519 (900, "CM"),
520 (500, "D"),
521 (400, "CD"),
522 (100, "C"),
523 (90, "XC"),
524 (50, "L"),
525 (40, "XL"),
526 (10, "X"),
527 (9, "IX"),
528 (5, "V"),
529 (4, "IV"),
530 (1, "I"),
531 ];
532 let mut result = String::new();
533 for &(val, sym) in VALUES {
534 while n >= val {
535 result.push_str(sym);
536 n -= val;
537 }
538 }
539 result
540}
541
542fn to_roman_lower(n: usize) -> String {
543 to_roman_upper(n).to_lowercase()
544}
545
546fn build_list_info(
548 inner: &TextDocumentInner,
549 block_dto: &frontend::block::dtos::BlockDto,
550) -> Option<ListInfo> {
551 let list_id = block_dto.list?;
552 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
553 .ok()
554 .flatten()?;
555
556 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
557 let marker = format_list_marker(&list_dto, item_index);
558
559 Some(ListInfo {
560 list_id: list_id as usize,
561 style: list_dto.style.clone(),
562 indent: list_dto.indent as u8,
563 marker,
564 item_index,
565 })
566}
567
568pub(crate) fn build_block_snapshot(
570 inner: &TextDocumentInner,
571 block_id: u64,
572) -> Option<BlockSnapshot> {
573 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
574 .ok()
575 .flatten()?;
576
577 let fragments = build_fragments(inner, block_id);
578 let block_format = BlockFormat::from(&block_dto);
579 let list_info = build_list_info(inner, &block_dto);
580
581 let parent_frame_id = find_parent_frame(inner, block_id).map(|id| id as usize);
582 let table_cell = find_table_cell_context(inner, block_id);
583
584 Some(BlockSnapshot {
585 block_id: block_id as usize,
586 position: to_usize(block_dto.document_position),
587 length: to_usize(block_dto.text_length),
588 text: block_dto.plain_text,
589 fragments,
590 block_format,
591 list_info,
592 parent_frame_id,
593 table_cell,
594 })
595}
596
597pub(crate) fn build_blocks_snapshot_for_frame(
599 inner: &TextDocumentInner,
600 frame_id: u64,
601) -> Vec<BlockSnapshot> {
602 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
603 .ok()
604 .flatten()
605 {
606 Some(f) => f,
607 None => return Vec::new(),
608 };
609
610 let mut block_dtos: Vec<_> = frame_dto
611 .blocks
612 .iter()
613 .filter_map(|&id| {
614 block_commands::get_block(&inner.ctx, &{ id })
615 .ok()
616 .flatten()
617 })
618 .collect();
619 block_dtos.sort_by_key(|b| b.document_position);
620
621 block_dtos
622 .iter()
623 .filter_map(|b| build_block_snapshot(inner, b.id))
624 .collect()
625}