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, 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 })
312 }
313}
314
315fn find_parent_frame(inner: &TextDocumentInner, block_id: u64) -> Option<EntityId> {
321 let all_frames = frame_commands::get_all_frame(&inner.ctx).ok()?;
322 let block_entity_id = block_id as EntityId;
323 for frame in &all_frames {
324 if frame.blocks.contains(&block_entity_id) {
325 return Some(frame.id as EntityId);
326 }
327 }
328 None
329}
330
331fn compute_block_number(inner: &TextDocumentInner, block_id: u64) -> usize {
333 let all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
334 let mut sorted: Vec<_> = all_blocks.iter().collect();
335 sorted.sort_by_key(|b| b.document_position);
336 sorted.iter().position(|b| b.id == block_id).unwrap_or(0)
337}
338
339pub(crate) fn build_fragments(inner: &TextDocumentInner, block_id: u64) -> Vec<FragmentContent> {
341 let block_dto = match block_commands::get_block(&inner.ctx, &block_id)
342 .ok()
343 .flatten()
344 {
345 Some(b) => b,
346 None => return Vec::new(),
347 };
348
349 let element_ids = &block_dto.elements;
350 let elements: Vec<_> = element_ids
351 .iter()
352 .filter_map(|&id| {
353 inline_element_commands::get_inline_element(&inner.ctx, &{ id })
354 .ok()
355 .flatten()
356 })
357 .collect();
358
359 let mut fragments = Vec::with_capacity(elements.len());
360 let mut offset: usize = 0;
361
362 for el in &elements {
363 let format = TextFormat::from(el);
364 match &el.content {
365 InlineContent::Text(text) => {
366 let length = text.chars().count();
367 fragments.push(FragmentContent::Text {
368 text: text.clone(),
369 format,
370 offset,
371 length,
372 });
373 offset += length;
374 }
375 InlineContent::Image {
376 name,
377 width,
378 height,
379 quality,
380 } => {
381 fragments.push(FragmentContent::Image {
382 name: name.clone(),
383 width: *width as u32,
384 height: *height as u32,
385 quality: *quality as u32,
386 format,
387 offset,
388 });
389 offset += 1; }
391 InlineContent::Empty => {
392 }
394 }
395 }
396
397 fragments
398}
399
400fn compute_list_item_index(inner: &TextDocumentInner, list_id: EntityId, block_id: u64) -> usize {
402 let all_blocks = block_commands::get_all_block(&inner.ctx).unwrap_or_default();
403 let mut list_blocks: Vec<_> = all_blocks
404 .iter()
405 .filter(|b| b.list == Some(list_id))
406 .collect();
407 list_blocks.sort_by_key(|b| b.document_position);
408 list_blocks
409 .iter()
410 .position(|b| b.id == block_id)
411 .unwrap_or(0)
412}
413
414pub(crate) fn format_list_marker(
416 list_dto: &frontend::list::dtos::ListDto,
417 item_index: usize,
418) -> String {
419 let number = item_index + 1; let marker_body = match list_dto.style {
421 ListStyle::Disc => "\u{2022}".to_string(), ListStyle::Circle => "\u{25E6}".to_string(), ListStyle::Square => "\u{25AA}".to_string(), ListStyle::Decimal => format!("{number}"),
425 ListStyle::LowerAlpha => {
426 if number <= 26 {
427 ((b'a' + (number as u8 - 1)) as char).to_string()
428 } else {
429 format!("{number}")
430 }
431 }
432 ListStyle::UpperAlpha => {
433 if number <= 26 {
434 ((b'A' + (number as u8 - 1)) as char).to_string()
435 } else {
436 format!("{number}")
437 }
438 }
439 ListStyle::LowerRoman => to_roman_lower(number),
440 ListStyle::UpperRoman => to_roman_upper(number),
441 };
442 format!("{}{marker_body}{}", list_dto.prefix, list_dto.suffix)
443}
444
445fn to_roman_upper(mut n: usize) -> String {
446 const VALUES: &[(usize, &str)] = &[
447 (1000, "M"),
448 (900, "CM"),
449 (500, "D"),
450 (400, "CD"),
451 (100, "C"),
452 (90, "XC"),
453 (50, "L"),
454 (40, "XL"),
455 (10, "X"),
456 (9, "IX"),
457 (5, "V"),
458 (4, "IV"),
459 (1, "I"),
460 ];
461 let mut result = String::new();
462 for &(val, sym) in VALUES {
463 while n >= val {
464 result.push_str(sym);
465 n -= val;
466 }
467 }
468 result
469}
470
471fn to_roman_lower(n: usize) -> String {
472 to_roman_upper(n).to_lowercase()
473}
474
475fn build_list_info(
477 inner: &TextDocumentInner,
478 block_dto: &frontend::block::dtos::BlockDto,
479) -> Option<ListInfo> {
480 let list_id = block_dto.list?;
481 let list_dto = list_commands::get_list(&inner.ctx, &{ list_id })
482 .ok()
483 .flatten()?;
484
485 let item_index = compute_list_item_index(inner, list_id, block_dto.id);
486 let marker = format_list_marker(&list_dto, item_index);
487
488 Some(ListInfo {
489 list_id: list_id as usize,
490 style: list_dto.style.clone(),
491 indent: list_dto.indent as u8,
492 marker,
493 item_index,
494 })
495}
496
497pub(crate) fn build_block_snapshot(
499 inner: &TextDocumentInner,
500 block_id: u64,
501) -> Option<BlockSnapshot> {
502 let block_dto = block_commands::get_block(&inner.ctx, &block_id)
503 .ok()
504 .flatten()?;
505
506 let fragments = build_fragments(inner, block_id);
507 let block_format = BlockFormat::from(&block_dto);
508 let list_info = build_list_info(inner, &block_dto);
509
510 Some(BlockSnapshot {
511 block_id: block_id as usize,
512 position: to_usize(block_dto.document_position),
513 length: to_usize(block_dto.text_length),
514 text: block_dto.plain_text,
515 fragments,
516 block_format,
517 list_info,
518 })
519}
520
521pub(crate) fn build_blocks_snapshot_for_frame(
523 inner: &TextDocumentInner,
524 frame_id: u64,
525) -> Vec<BlockSnapshot> {
526 let frame_dto = match frame_commands::get_frame(&inner.ctx, &(frame_id as EntityId))
527 .ok()
528 .flatten()
529 {
530 Some(f) => f,
531 None => return Vec::new(),
532 };
533
534 let mut block_dtos: Vec<_> = frame_dto
535 .blocks
536 .iter()
537 .filter_map(|&id| {
538 block_commands::get_block(&inner.ctx, &{ id })
539 .ok()
540 .flatten()
541 })
542 .collect();
543 block_dtos.sort_by_key(|b| b.document_position);
544
545 block_dtos
546 .iter()
547 .filter_map(|b| build_block_snapshot(inner, b.id))
548 .collect()
549}