Skip to main content

text_document/
lib.rs

1//! # text-document
2//!
3//! A rich text document model for Rust.
4//!
5//! Provides a [`TextDocument`] as the main entry point and [`TextCursor`] for
6//! cursor-based editing, inspired by Qt's QTextDocument/QTextCursor API.
7//!
8//! ```rust,no_run
9//! use text_document::{TextDocument, MoveMode, MoveOperation};
10//!
11//! let doc = TextDocument::new();
12//! doc.set_plain_text("Hello world").unwrap();
13//!
14//! let cursor = doc.cursor();
15//! cursor.move_position(MoveOperation::EndOfWord, MoveMode::KeepAnchor, 1);
16//! cursor.insert_text("Goodbye").unwrap(); // replaces "Hello"
17//!
18//! // Multiple cursors on the same document
19//! let c1 = doc.cursor();
20//! let c2 = doc.cursor_at(5);
21//! c1.insert_text("A").unwrap();
22//! // c2's position is automatically adjusted
23//!
24//! doc.undo().unwrap();
25//! ```
26
27mod convert;
28mod cursor;
29mod document;
30mod events;
31mod flow;
32mod fragment;
33mod highlight;
34mod inner;
35mod operation;
36mod text_block;
37mod text_frame;
38mod text_list;
39mod text_table;
40
41// ── Re-exports from entity DTOs (enums that consumers need) ──────
42pub use frontend::block::dtos::{Alignment, MarkerType};
43pub use frontend::document::dtos::{TextDirection, WrapMode};
44pub use frontend::frame::dtos::FramePosition;
45pub use frontend::inline_element::dtos::{CharVerticalAlignment, InlineContent, UnderlineStyle};
46pub use frontend::list::dtos::ListStyle;
47pub use frontend::resource::dtos::ResourceType;
48
49// ── Error type ───────────────────────────────────────────────────
50pub type Result<T> = anyhow::Result<T>;
51
52// ── Public API types ─────────────────────────────────────────────
53pub use cursor::TextCursor;
54pub use document::TextDocument;
55pub use events::{DocumentEvent, Subscription};
56pub use fragment::DocumentFragment;
57pub use highlight::{HighlightContext, HighlightFormat, HighlightSpan, SyntaxHighlighter};
58pub use operation::{DocxExportResult, HtmlImportResult, MarkdownImportResult, Operation};
59
60// ── Layout engine API types ─────────────────────────────────────
61pub use flow::{
62    BlockSnapshot, CellFormat, CellSnapshot, CellVerticalAlignment, FlowElement,
63    FlowElementSnapshot, FlowSnapshot, FormatChangeKind, FragmentContent, FrameSnapshot, ListInfo,
64    TableCellContext, TableCellRef, TableFormat, TableSnapshot,
65};
66pub use text_block::TextBlock;
67pub use text_frame::TextFrame;
68pub use text_list::TextList;
69pub use text_table::{TextTable, TextTableCell};
70
71// All public handle types are Send + Sync (all fields are Arc<Mutex<...>> + Copy).
72const _: () = {
73    #[allow(dead_code)]
74    fn assert_send_sync<T: Send + Sync>() {}
75    fn _assert_all() {
76        assert_send_sync::<TextDocument>();
77        assert_send_sync::<TextCursor>();
78        assert_send_sync::<TextBlock>();
79        assert_send_sync::<TextFrame>();
80        assert_send_sync::<TextTable>();
81        assert_send_sync::<TextTableCell>();
82        assert_send_sync::<TextList>();
83    }
84};
85
86// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
87// Color
88// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
89
90/// An RGBA color value. Each component is 0–255.
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
92pub struct Color {
93    pub red: u8,
94    pub green: u8,
95    pub blue: u8,
96    pub alpha: u8,
97}
98
99impl Color {
100    /// Create an opaque color (alpha = 255).
101    pub fn rgb(red: u8, green: u8, blue: u8) -> Self {
102        Self {
103            red,
104            green,
105            blue,
106            alpha: 255,
107        }
108    }
109
110    /// Create a color with explicit alpha.
111    pub fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
112        Self {
113            red,
114            green,
115            blue,
116            alpha,
117        }
118    }
119}
120
121// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
122// Public format types
123// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
124
125/// Character/text formatting. All fields are optional: `None` means
126/// "not set — inherit from the block's default or the document's default."
127#[derive(Debug, Clone, Default, PartialEq, Eq)]
128pub struct TextFormat {
129    pub font_family: Option<String>,
130    pub font_point_size: Option<u32>,
131    pub font_weight: Option<u32>,
132    pub font_bold: Option<bool>,
133    pub font_italic: Option<bool>,
134    pub font_underline: Option<bool>,
135    pub font_overline: Option<bool>,
136    pub font_strikeout: Option<bool>,
137    pub letter_spacing: Option<i32>,
138    pub word_spacing: Option<i32>,
139    pub underline_style: Option<UnderlineStyle>,
140    pub vertical_alignment: Option<CharVerticalAlignment>,
141    pub anchor_href: Option<String>,
142    pub anchor_names: Vec<String>,
143    pub is_anchor: Option<bool>,
144    pub tooltip: Option<String>,
145    pub foreground_color: Option<Color>,
146    pub background_color: Option<Color>,
147    pub underline_color: Option<Color>,
148}
149
150/// Block (paragraph) formatting. All fields are optional.
151#[derive(Debug, Clone, Default, PartialEq)]
152pub struct BlockFormat {
153    pub alignment: Option<Alignment>,
154    pub top_margin: Option<i32>,
155    pub bottom_margin: Option<i32>,
156    pub left_margin: Option<i32>,
157    pub right_margin: Option<i32>,
158    pub heading_level: Option<u8>,
159    pub indent: Option<u8>,
160    pub text_indent: Option<i32>,
161    pub marker: Option<MarkerType>,
162    pub tab_positions: Vec<i32>,
163    pub line_height: Option<f32>,
164    pub non_breakable_lines: Option<bool>,
165    pub direction: Option<TextDirection>,
166    pub background_color: Option<String>,
167}
168
169/// Frame formatting. All fields are optional.
170#[derive(Debug, Clone, Default, PartialEq, Eq)]
171pub struct FrameFormat {
172    pub height: Option<i32>,
173    pub width: Option<i32>,
174    pub top_margin: Option<i32>,
175    pub bottom_margin: Option<i32>,
176    pub left_margin: Option<i32>,
177    pub right_margin: Option<i32>,
178    pub padding: Option<i32>,
179    pub border: Option<i32>,
180    pub position: Option<FramePosition>,
181}
182
183// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
184// Enums for cursor movement
185// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
186
187/// Controls whether a movement collapses or extends the selection.
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum MoveMode {
190    /// Move both position and anchor — collapses selection.
191    MoveAnchor,
192    /// Move only position, keep anchor — creates or extends selection.
193    KeepAnchor,
194}
195
196/// Semantic cursor movement operations.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum MoveOperation {
199    NoMove,
200    Start,
201    End,
202    StartOfLine,
203    EndOfLine,
204    StartOfBlock,
205    EndOfBlock,
206    StartOfWord,
207    EndOfWord,
208    PreviousBlock,
209    NextBlock,
210    PreviousCharacter,
211    NextCharacter,
212    PreviousWord,
213    NextWord,
214    Up,
215    Down,
216    Left,
217    Right,
218    WordLeft,
219    WordRight,
220}
221
222/// Quick-select a region around the cursor.
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub enum SelectionType {
225    WordUnderCursor,
226    LineUnderCursor,
227    BlockUnderCursor,
228    Document,
229}
230
231// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
232// Read-only info types
233// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
234
235/// Document-level statistics. O(1) cached.
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct DocumentStats {
238    pub character_count: usize,
239    pub word_count: usize,
240    pub block_count: usize,
241    pub frame_count: usize,
242    pub image_count: usize,
243    pub list_count: usize,
244    pub table_count: usize,
245}
246
247/// Info about a block at a given position.
248#[derive(Debug, Clone, PartialEq, Eq)]
249pub struct BlockInfo {
250    pub block_id: usize,
251    pub block_number: usize,
252    pub start: usize,
253    pub length: usize,
254}
255
256/// A single search match.
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct FindMatch {
259    pub position: usize,
260    pub length: usize,
261}
262
263/// Options for find / find_all / replace operations.
264#[derive(Debug, Clone, Default)]
265pub struct FindOptions {
266    pub case_sensitive: bool,
267    pub whole_word: bool,
268    pub use_regex: bool,
269    pub search_backward: bool,
270}