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