Skip to main content

text_document/
flow.rs

1//! Flow types for document traversal and layout engine support.
2//!
3//! The layout engine processes [`FlowElement`]s in order to build its layout
4//! tree. Snapshot types capture consistent views for thread-safe reads.
5
6use crate::text_block::TextBlock;
7use crate::text_frame::TextFrame;
8use crate::text_table::TextTable;
9use crate::{Alignment, BlockFormat, FrameFormat, ListStyle, TextFormat};
10
11// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12// FlowElement
13// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14
15/// An element in the document's visual flow.
16///
17/// The layout engine processes these in order to build its layout tree.
18/// Obtained from [`TextDocument::flow()`](crate::TextDocument::flow) or
19/// [`TextFrame::flow()`].
20#[derive(Clone)]
21pub enum FlowElement {
22    /// A paragraph or heading. Layout as a text block.
23    Block(TextBlock),
24
25    /// A table at this position in the flow. Layout as a grid.
26    /// The anchor frame's `table` field identifies the table entity.
27    Table(TextTable),
28
29    /// A non-table sub-frame (float, sidebar, blockquote).
30    /// Contains its own nested flow, accessible via
31    /// [`TextFrame::flow()`].
32    Frame(TextFrame),
33}
34
35impl FlowElement {
36    /// Snapshot this element into a thread-safe, plain-data representation.
37    ///
38    /// Dispatches to [`TextBlock::snapshot()`], [`TextTable::snapshot()`],
39    /// or [`TextFrame::snapshot()`] as appropriate.
40    pub fn snapshot(&self) -> FlowElementSnapshot {
41        match self {
42            FlowElement::Block(b) => FlowElementSnapshot::Block(b.snapshot()),
43            FlowElement::Table(t) => FlowElementSnapshot::Table(t.snapshot()),
44            FlowElement::Frame(f) => FlowElementSnapshot::Frame(f.snapshot()),
45        }
46    }
47}
48
49// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
50// FragmentContent
51// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52
53/// A contiguous run of content with uniform formatting within a block.
54///
55/// Offsets are **block-relative**: `offset` is the character position
56/// within the block where this fragment starts (0 = block start).
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum FragmentContent {
59    /// A text run. The layout engine shapes these into glyphs.
60    Text {
61        text: String,
62        format: TextFormat,
63        /// Character offset within the block (block-relative).
64        offset: usize,
65        /// Character count.
66        length: usize,
67    },
68    /// An inline image. The layout engine reserves space for it.
69    ///
70    /// To retrieve the image pixel data, use the existing
71    /// [`TextDocument::resource(name)`](crate::TextDocument::resource) method.
72    Image {
73        name: String,
74        width: u32,
75        height: u32,
76        quality: u32,
77        format: TextFormat,
78        /// Character offset within the block (block-relative).
79        offset: usize,
80    },
81}
82
83// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
84// BlockSnapshot
85// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
86
87/// All layout-relevant data for one block, captured atomically.
88#[derive(Debug, Clone, PartialEq)]
89pub struct BlockSnapshot {
90    pub block_id: usize,
91    pub position: usize,
92    pub length: usize,
93    pub text: String,
94    pub fragments: Vec<FragmentContent>,
95    pub block_format: BlockFormat,
96    pub list_info: Option<ListInfo>,
97    /// Parent frame ID. Needed to know where this block lives in the
98    /// frame tree (e.g. main frame vs. a sub-frame or table cell frame).
99    pub parent_frame_id: Option<usize>,
100    /// If this block is inside a table cell, the cell coordinates.
101    /// Needed so the typesetter can propagate height changes to the
102    /// enclosing table row.
103    pub table_cell: Option<TableCellContext>,
104}
105
106/// Snapshot-friendly reference to a table cell (plain IDs, no live handles).
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct TableCellContext {
109    pub table_id: usize,
110    pub row: usize,
111    pub column: usize,
112}
113
114// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
115// ListInfo
116// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
117
118/// List membership and marker information for a block.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct ListInfo {
121    pub list_id: usize,
122    /// The list style (Disc, Decimal, LowerAlpha, etc.).
123    pub style: ListStyle,
124    /// Indentation level.
125    pub indent: u8,
126    /// Pre-formatted marker text: "•", "3.", "(c)", "IV.", etc.
127    pub marker: String,
128    /// 0-based index of this item within its list.
129    pub item_index: usize,
130}
131
132// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
133// TableCellRef
134// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
135
136/// Reference to a table cell that contains a block.
137#[derive(Clone)]
138pub struct TableCellRef {
139    pub table: TextTable,
140    pub row: usize,
141    pub column: usize,
142}
143
144// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
145// CellRange / SelectionKind
146// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
147
148/// A rectangular range of cells within a single table (inclusive bounds).
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct CellRange {
151    pub table_id: usize,
152    pub start_row: usize,
153    pub start_col: usize,
154    pub end_row: usize,
155    pub end_col: usize,
156}
157
158impl CellRange {
159    /// Expand the range so that every merged cell whose span overlaps the
160    /// rectangle is fully included. `cells` is a slice of
161    /// `(row, col, row_span, col_span)` for every cell in the table.
162    ///
163    /// Uses fixed-point iteration (converges in 1-2 rounds for typical tables).
164    pub fn expand_for_spans(mut self, cells: &[(usize, usize, usize, usize)]) -> Self {
165        loop {
166            let mut expanded = false;
167            for &(row, col, rs, cs) in cells {
168                let cell_bottom = row + rs - 1;
169                let cell_right = col + cs - 1;
170                // Check overlap with current range
171                if row <= self.end_row
172                    && cell_bottom >= self.start_row
173                    && col <= self.end_col
174                    && cell_right >= self.start_col
175                {
176                    if row < self.start_row {
177                        self.start_row = row;
178                        expanded = true;
179                    }
180                    if cell_bottom > self.end_row {
181                        self.end_row = cell_bottom;
182                        expanded = true;
183                    }
184                    if col < self.start_col {
185                        self.start_col = col;
186                        expanded = true;
187                    }
188                    if cell_right > self.end_col {
189                        self.end_col = cell_right;
190                        expanded = true;
191                    }
192                }
193            }
194            if !expanded {
195                break;
196            }
197        }
198        self
199    }
200}
201
202/// Describes what kind of selection the cursor currently has.
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub enum SelectionKind {
205    /// No selection (position == anchor).
206    None,
207    /// Normal text selection within a single cell or outside any table.
208    Text,
209    /// Rectangular cell selection within a table.
210    Cells(CellRange),
211    /// Selection crosses a table boundary (starts/ends outside the table).
212    /// The table portion is a rectangular cell range; `text_before` /
213    /// `text_after` indicate whether text outside the table is also selected.
214    Mixed {
215        cell_range: CellRange,
216        text_before: bool,
217        text_after: bool,
218    },
219}
220
221// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
222// Table format types
223// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
224
225/// Table-level formatting.
226#[derive(Debug, Clone, Default, PartialEq, Eq)]
227pub struct TableFormat {
228    pub border: Option<i32>,
229    pub cell_spacing: Option<i32>,
230    pub cell_padding: Option<i32>,
231    pub width: Option<i32>,
232    pub alignment: Option<Alignment>,
233}
234
235/// Cell-level formatting.
236#[derive(Debug, Clone, Default, PartialEq, Eq)]
237pub struct CellFormat {
238    pub padding: Option<i32>,
239    pub border: Option<i32>,
240    pub vertical_alignment: Option<CellVerticalAlignment>,
241    pub background_color: Option<String>,
242}
243
244/// Vertical alignment within a table cell.
245#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
246pub enum CellVerticalAlignment {
247    #[default]
248    Top,
249    Middle,
250    Bottom,
251}
252
253// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
254// Table and Cell Snapshots
255// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
256
257/// Consistent snapshot of a table's structure and all cell content.
258#[derive(Debug, Clone, PartialEq)]
259pub struct TableSnapshot {
260    pub table_id: usize,
261    pub rows: usize,
262    pub columns: usize,
263    pub column_widths: Vec<i32>,
264    pub format: TableFormat,
265    pub cells: Vec<CellSnapshot>,
266}
267
268/// Snapshot of one table cell including its block content.
269#[derive(Debug, Clone, PartialEq)]
270pub struct CellSnapshot {
271    pub row: usize,
272    pub column: usize,
273    pub row_span: usize,
274    pub column_span: usize,
275    pub format: CellFormat,
276    pub blocks: Vec<BlockSnapshot>,
277}
278
279// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
280// Flow Snapshots
281// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
282
283/// Consistent snapshot of the entire document flow, captured in a
284/// single lock acquisition.
285#[derive(Debug, Clone, PartialEq)]
286pub struct FlowSnapshot {
287    pub elements: Vec<FlowElementSnapshot>,
288}
289
290/// Snapshot of one flow element.
291#[derive(Debug, Clone, PartialEq)]
292pub enum FlowElementSnapshot {
293    Block(BlockSnapshot),
294    Table(TableSnapshot),
295    Frame(FrameSnapshot),
296}
297
298/// Snapshot of a sub-frame and its contents.
299#[derive(Debug, Clone, PartialEq)]
300pub struct FrameSnapshot {
301    pub frame_id: usize,
302    pub format: FrameFormat,
303    pub elements: Vec<FlowElementSnapshot>,
304}
305
306// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
307// FormatChangeKind
308// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
309
310/// What kind of formatting changed.
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum FormatChangeKind {
313    /// Block-level: alignment, margins, indent, heading level.
314    /// Requires paragraph relayout.
315    Block,
316    /// Character-level: font, bold, italic, underline, color.
317    /// Requires reshaping but not necessarily reflow.
318    Character,
319    /// List-level: style, indent, prefix, suffix.
320    /// Requires marker relayout for list items.
321    List,
322}