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        /// Stable entity id of the underlying `InlineElement`. Survives
68        /// edits that don't delete the element (character insertions
69        /// inside the run keep the same id). Used by accessibility
70        /// layers to build stable `NodeId`s for AccessKit `TextRun`
71        /// children.
72        element_id: u64,
73        /// Unicode word starts within `text`, expressed as character
74        /// indices (not byte offsets). Computed per UAX #29 via
75        /// `unicode-segmentation`. Fed directly into AccessKit's
76        /// `set_word_starts` on the corresponding `Role::TextRun`.
77        word_starts: Vec<u8>,
78    },
79    /// An inline image. The layout engine reserves space for it.
80    ///
81    /// To retrieve the image pixel data, use the existing
82    /// [`TextDocument::resource(name)`](crate::TextDocument::resource) method.
83    Image {
84        name: String,
85        width: u32,
86        height: u32,
87        quality: u32,
88        format: TextFormat,
89        /// Character offset within the block (block-relative).
90        offset: usize,
91        /// Stable entity id of the underlying `InlineElement`.
92        element_id: u64,
93    },
94}
95
96// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
97// BlockSnapshot
98// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
99
100/// All layout-relevant data for one block, captured atomically.
101#[derive(Debug, Clone, PartialEq)]
102pub struct BlockSnapshot {
103    pub block_id: usize,
104    pub position: usize,
105    pub length: usize,
106    pub text: String,
107    pub fragments: Vec<FragmentContent>,
108    pub block_format: BlockFormat,
109    pub list_info: Option<ListInfo>,
110    /// Parent frame ID. Needed to know where this block lives in the
111    /// frame tree (e.g. main frame vs. a sub-frame or table cell frame).
112    pub parent_frame_id: Option<usize>,
113    /// If this block is inside a table cell, the cell coordinates.
114    /// Needed so the typesetter can propagate height changes to the
115    /// enclosing table row.
116    pub table_cell: Option<TableCellContext>,
117}
118
119/// Snapshot-friendly reference to a table cell (plain IDs, no live handles).
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct TableCellContext {
122    pub table_id: usize,
123    pub row: usize,
124    pub column: usize,
125}
126
127// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
128// ListInfo
129// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
130
131/// List membership and marker information for a block.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct ListInfo {
134    pub list_id: usize,
135    /// The list style (Disc, Decimal, LowerAlpha, etc.).
136    pub style: ListStyle,
137    /// Indentation level.
138    pub indent: u8,
139    /// Pre-formatted marker text: "•", "3.", "(c)", "IV.", etc.
140    pub marker: String,
141    /// 0-based index of this item within its list.
142    pub item_index: usize,
143}
144
145// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
146// TableCellRef
147// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
148
149/// Reference to a table cell that contains a block.
150#[derive(Clone)]
151pub struct TableCellRef {
152    pub table: TextTable,
153    pub row: usize,
154    pub column: usize,
155}
156
157// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
158// CellRange / SelectionKind
159// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
160
161/// A rectangular range of cells within a single table (inclusive bounds).
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct CellRange {
164    pub table_id: usize,
165    pub start_row: usize,
166    pub start_col: usize,
167    pub end_row: usize,
168    pub end_col: usize,
169}
170
171impl CellRange {
172    /// Expand the range so that every merged cell whose span overlaps the
173    /// rectangle is fully included. `cells` is a slice of
174    /// `(row, col, row_span, col_span)` for every cell in the table.
175    ///
176    /// Uses fixed-point iteration (converges in 1-2 rounds for typical tables).
177    pub fn expand_for_spans(mut self, cells: &[(usize, usize, usize, usize)]) -> Self {
178        loop {
179            let mut expanded = false;
180            for &(row, col, rs, cs) in cells {
181                let cell_bottom = row + rs - 1;
182                let cell_right = col + cs - 1;
183                // Check overlap with current range
184                if row <= self.end_row
185                    && cell_bottom >= self.start_row
186                    && col <= self.end_col
187                    && cell_right >= self.start_col
188                {
189                    if row < self.start_row {
190                        self.start_row = row;
191                        expanded = true;
192                    }
193                    if cell_bottom > self.end_row {
194                        self.end_row = cell_bottom;
195                        expanded = true;
196                    }
197                    if col < self.start_col {
198                        self.start_col = col;
199                        expanded = true;
200                    }
201                    if cell_right > self.end_col {
202                        self.end_col = cell_right;
203                        expanded = true;
204                    }
205                }
206            }
207            if !expanded {
208                break;
209            }
210        }
211        self
212    }
213}
214
215/// Describes what kind of selection the cursor currently has.
216#[derive(Debug, Clone, PartialEq, Eq)]
217pub enum SelectionKind {
218    /// No selection (position == anchor).
219    None,
220    /// Normal text selection within a single cell or outside any table.
221    Text,
222    /// Rectangular cell selection within a table.
223    Cells(CellRange),
224    /// Selection crosses a table boundary (starts/ends outside the table).
225    /// The table portion is a rectangular cell range; `text_before` /
226    /// `text_after` indicate whether text outside the table is also selected.
227    Mixed {
228        cell_range: CellRange,
229        text_before: bool,
230        text_after: bool,
231    },
232}
233
234// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
235// Table format types
236// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
237
238/// Table-level formatting.
239#[derive(Debug, Clone, Default, PartialEq, Eq)]
240pub struct TableFormat {
241    pub border: Option<i32>,
242    pub cell_spacing: Option<i32>,
243    pub cell_padding: Option<i32>,
244    pub width: Option<i32>,
245    pub alignment: Option<Alignment>,
246}
247
248/// Cell-level formatting.
249#[derive(Debug, Clone, Default, PartialEq, Eq)]
250pub struct CellFormat {
251    pub padding: Option<i32>,
252    pub border: Option<i32>,
253    pub vertical_alignment: Option<CellVerticalAlignment>,
254    pub background_color: Option<String>,
255}
256
257/// Vertical alignment within a table cell.
258#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
259pub enum CellVerticalAlignment {
260    #[default]
261    Top,
262    Middle,
263    Bottom,
264}
265
266// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
267// Table and Cell Snapshots
268// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
269
270/// Consistent snapshot of a table's structure and all cell content.
271#[derive(Debug, Clone, PartialEq)]
272pub struct TableSnapshot {
273    pub table_id: usize,
274    pub rows: usize,
275    pub columns: usize,
276    pub column_widths: Vec<i32>,
277    pub format: TableFormat,
278    pub cells: Vec<CellSnapshot>,
279}
280
281/// Snapshot of one table cell including its block content.
282#[derive(Debug, Clone, PartialEq)]
283pub struct CellSnapshot {
284    pub row: usize,
285    pub column: usize,
286    pub row_span: usize,
287    pub column_span: usize,
288    pub format: CellFormat,
289    pub blocks: Vec<BlockSnapshot>,
290}
291
292// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
293// Flow Snapshots
294// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
295
296/// Consistent snapshot of the entire document flow, captured in a
297/// single lock acquisition.
298#[derive(Debug, Clone, PartialEq)]
299pub struct FlowSnapshot {
300    pub elements: Vec<FlowElementSnapshot>,
301}
302
303/// Snapshot of one flow element.
304#[derive(Debug, Clone, PartialEq)]
305pub enum FlowElementSnapshot {
306    Block(BlockSnapshot),
307    Table(TableSnapshot),
308    Frame(FrameSnapshot),
309}
310
311/// Snapshot of a sub-frame and its contents.
312#[derive(Debug, Clone, PartialEq)]
313pub struct FrameSnapshot {
314    pub frame_id: usize,
315    pub format: FrameFormat,
316    pub elements: Vec<FlowElementSnapshot>,
317}
318
319// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
320// FormatChangeKind
321// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
322
323/// What kind of formatting changed.
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub enum FormatChangeKind {
326    /// Block-level: alignment, margins, indent, heading level.
327    /// Requires paragraph relayout.
328    Block,
329    /// Character-level: font, bold, italic, underline, color.
330    /// Requires reshaping but not necessarily reflow.
331    Character,
332    /// List-level: style, indent, prefix, suffix.
333    /// Requires marker relayout for list items.
334    List,
335}