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 fragment;
32mod inner;
33mod operation;
34
35// ── Re-exports from entity DTOs (enums that consumers need) ──────
36pub use frontend::block::dtos::{Alignment, MarkerType};
37pub use frontend::document::dtos::{TextDirection, WrapMode};
38pub use frontend::frame::dtos::FramePosition;
39pub use frontend::inline_element::dtos::{CharVerticalAlignment, InlineContent, UnderlineStyle};
40pub use frontend::list::dtos::ListStyle;
41pub use frontend::resource::dtos::ResourceType;
42
43// ── Error type ───────────────────────────────────────────────────
44pub type Result<T> = anyhow::Result<T>;
45
46// ── Public API types ─────────────────────────────────────────────
47pub use cursor::TextCursor;
48pub use document::TextDocument;
49pub use events::{DocumentEvent, Subscription};
50pub use fragment::DocumentFragment;
51pub use operation::{DocxExportResult, HtmlImportResult, MarkdownImportResult, Operation};
52
53// TextDocument and TextCursor are Send + Sync (all fields are Arc<Mutex<...>>).
54const _: () = {
55    fn assert_send_sync<T: Send + Sync>() {}
56    fn _assert_all() {
57        assert_send_sync::<TextDocument>();
58        assert_send_sync::<TextCursor>();
59    }
60};
61
62// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
63// Public format types
64// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
65
66/// Character/text formatting. All fields are optional: `None` means
67/// "not set — inherit from the block's default or the document's default."
68#[derive(Debug, Clone, Default, PartialEq)]
69pub struct TextFormat {
70    pub font_family: Option<String>,
71    pub font_point_size: Option<u32>,
72    pub font_weight: Option<u32>,
73    pub font_bold: Option<bool>,
74    pub font_italic: Option<bool>,
75    pub font_underline: Option<bool>,
76    pub font_overline: Option<bool>,
77    pub font_strikeout: Option<bool>,
78    pub letter_spacing: Option<i32>,
79    pub word_spacing: Option<i32>,
80    pub underline_style: Option<UnderlineStyle>,
81    pub vertical_alignment: Option<CharVerticalAlignment>,
82    pub anchor_href: Option<String>,
83    pub anchor_names: Vec<String>,
84    pub is_anchor: Option<bool>,
85    pub tooltip: Option<String>,
86}
87
88/// Block (paragraph) formatting. All fields are optional.
89#[derive(Debug, Clone, Default, PartialEq)]
90pub struct BlockFormat {
91    pub alignment: Option<Alignment>,
92    pub top_margin: Option<i32>,
93    pub bottom_margin: Option<i32>,
94    pub left_margin: Option<i32>,
95    pub right_margin: Option<i32>,
96    pub heading_level: Option<u8>,
97    pub indent: Option<u8>,
98    pub text_indent: Option<i32>,
99    pub marker: Option<MarkerType>,
100    pub tab_positions: Vec<i32>,
101}
102
103/// Frame formatting. All fields are optional.
104#[derive(Debug, Clone, Default, PartialEq)]
105pub struct FrameFormat {
106    pub height: Option<i32>,
107    pub width: Option<i32>,
108    pub top_margin: Option<i32>,
109    pub bottom_margin: Option<i32>,
110    pub left_margin: Option<i32>,
111    pub right_margin: Option<i32>,
112    pub padding: Option<i32>,
113    pub border: Option<i32>,
114    pub position: Option<FramePosition>,
115}
116
117// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
118// Enums for cursor movement
119// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
120
121/// Controls whether a movement collapses or extends the selection.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum MoveMode {
124    /// Move both position and anchor — collapses selection.
125    MoveAnchor,
126    /// Move only position, keep anchor — creates or extends selection.
127    KeepAnchor,
128}
129
130/// Semantic cursor movement operations.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum MoveOperation {
133    NoMove,
134    Start,
135    End,
136    StartOfLine,
137    EndOfLine,
138    StartOfBlock,
139    EndOfBlock,
140    StartOfWord,
141    EndOfWord,
142    PreviousBlock,
143    NextBlock,
144    PreviousCharacter,
145    NextCharacter,
146    PreviousWord,
147    NextWord,
148    Up,
149    Down,
150    Left,
151    Right,
152    WordLeft,
153    WordRight,
154}
155
156/// Quick-select a region around the cursor.
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum SelectionType {
159    WordUnderCursor,
160    LineUnderCursor,
161    BlockUnderCursor,
162    Document,
163}
164
165// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
166// Read-only info types
167// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
168
169/// Document-level statistics. O(1) cached.
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct DocumentStats {
172    pub character_count: usize,
173    pub word_count: usize,
174    pub block_count: usize,
175    pub frame_count: usize,
176    pub image_count: usize,
177    pub list_count: usize,
178}
179
180/// Info about a block at a given position.
181#[derive(Debug, Clone, PartialEq, Eq)]
182pub struct BlockInfo {
183    pub block_id: usize,
184    pub block_number: usize,
185    pub start: usize,
186    pub length: usize,
187}
188
189/// A single search match.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct FindMatch {
192    pub position: usize,
193    pub length: usize,
194}
195
196/// Options for find / find_all / replace operations.
197#[derive(Debug, Clone, Default)]
198pub struct FindOptions {
199    pub case_sensitive: bool,
200    pub whole_word: bool,
201    pub use_regex: bool,
202    pub search_backward: bool,
203}