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