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