1#![forbid(unsafe_code)]
2
3pub mod cursor;
58pub mod editor;
59pub mod rope;
60pub mod segment;
61pub mod text;
62pub mod view;
63pub mod width_cache;
64pub mod wrap;
65
66pub mod hyphenation;
67pub mod incremental_break;
68
69#[cfg(feature = "markup")]
70pub mod markup;
71
72#[cfg(feature = "bidi")]
73pub mod bidi;
74
75#[cfg(feature = "normalization")]
76pub mod normalization;
77
78pub mod justification;
79pub mod layout_policy;
80pub mod search;
81pub mod vertical_metrics;
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
85pub struct TextMeasurement {
86 pub minimum: usize,
88 pub maximum: usize,
90}
91
92impl TextMeasurement {
93 pub const ZERO: Self = Self {
95 minimum: 0,
96 maximum: 0,
97 };
98
99 pub fn union(self, other: Self) -> Self {
101 Self {
102 minimum: self.minimum.max(other.minimum),
103 maximum: self.maximum.max(other.maximum),
104 }
105 }
106
107 pub fn stack(self, other: Self) -> Self {
109 Self {
110 minimum: self.minimum.saturating_add(other.minimum),
111 maximum: self.maximum.saturating_add(other.maximum),
112 }
113 }
114
115 pub fn clamp(self, min_width: Option<usize>, max_width: Option<usize>) -> Self {
117 let mut result = self;
118 if let Some(min_w) = min_width {
119 result.minimum = result.minimum.max(min_w);
120 result.maximum = result.maximum.max(min_w);
121 }
122 if let Some(max_w) = max_width {
123 result.minimum = result.minimum.min(max_w);
124 result.maximum = result.maximum.min(max_w);
125 }
126 result
127 }
128}
129
130pub use cursor::{CursorNavigator, CursorPosition};
131pub use editor::{Editor, Selection};
132pub use hyphenation::{
133 HyphenBreakPoint, HyphenationDict, HyphenationPattern, PatternTrie, break_penalties,
134 compile_pattern, english_dict_mini,
135};
136pub use incremental_break::{BreakerSnapshot, EditEvent, IncrementalBreaker, ReflowResult};
137pub use justification::{
138 GlueSpec, JustificationControl, JustifyMode, SUBCELL_SCALE, SpaceCategory, SpacePenalty,
139};
140pub use layout_policy::{LayoutPolicy, LayoutTier, PolicyError, ResolvedPolicy, RuntimeCapability};
141pub use rope::Rope;
142pub use segment::{ControlCode, Segment, SegmentLine, SegmentLines, join_lines, split_into_lines};
143pub use text::{Line, Span, Text};
144pub use vertical_metrics::{
145 BaselineGrid, LeadingSpec, ParagraphSpacing, VerticalMetrics, VerticalPolicy,
146};
147pub use view::{TextView, ViewLine, Viewport};
148pub use width_cache::{
149 CacheStats, CountMinSketch, DEFAULT_CACHE_CAPACITY, Doorkeeper, S3FifoWidthCache,
150 TinyLfuWidthCache, WidthCache,
151};
152pub use wrap::{
153 KpBreakResult, WrapMode, WrapOptions, ascii_width, display_width, grapheme_count,
154 grapheme_width, graphemes, has_wide_chars, is_ascii_only, truncate_to_width,
155 truncate_to_width_with_info, truncate_with_ellipsis, word_boundaries, word_segments,
156 wrap_optimal, wrap_text, wrap_text_optimal, wrap_with_options,
157};
158
159#[cfg(feature = "markup")]
160pub use markup::{MarkupError, MarkupParser, parse_markup};
161
162#[cfg(feature = "normalization")]
163pub use normalization::{NormForm, eq_normalized, is_normalized, normalize, normalize_for_search};
164
165pub use search::{
166 SearchResult, search_ascii_case_insensitive, search_exact, search_exact_overlapping,
167};
168#[cfg(feature = "normalization")]
169pub use search::{search_case_insensitive, search_normalized};
170
171#[cfg(test)]
172mod measurement_tests {
173 use super::TextMeasurement;
174
175 #[test]
176 fn union_uses_max_bounds() {
177 let a = TextMeasurement {
178 minimum: 2,
179 maximum: 8,
180 };
181 let b = TextMeasurement {
182 minimum: 4,
183 maximum: 6,
184 };
185 let merged = a.union(b);
186 assert_eq!(
187 merged,
188 TextMeasurement {
189 minimum: 4,
190 maximum: 8
191 }
192 );
193 }
194
195 #[test]
196 fn stack_adds_bounds() {
197 let a = TextMeasurement {
198 minimum: 1,
199 maximum: 5,
200 };
201 let b = TextMeasurement {
202 minimum: 2,
203 maximum: 7,
204 };
205 let stacked = a.stack(b);
206 assert_eq!(
207 stacked,
208 TextMeasurement {
209 minimum: 3,
210 maximum: 12
211 }
212 );
213 }
214
215 #[test]
216 fn clamp_enforces_min() {
217 let measurement = TextMeasurement {
218 minimum: 2,
219 maximum: 6,
220 };
221 let clamped = measurement.clamp(Some(5), None);
222 assert_eq!(
223 clamped,
224 TextMeasurement {
225 minimum: 5,
226 maximum: 6
227 }
228 );
229 }
230
231 #[test]
232 fn clamp_enforces_max() {
233 let measurement = TextMeasurement {
234 minimum: 4,
235 maximum: 10,
236 };
237 let clamped = measurement.clamp(None, Some(6));
238 assert_eq!(
239 clamped,
240 TextMeasurement {
241 minimum: 4,
242 maximum: 6
243 }
244 );
245 }
246
247 #[test]
248 fn clamp_preserves_ordering() {
249 let measurement = TextMeasurement {
250 minimum: 3,
251 maximum: 5,
252 };
253 let clamped = measurement.clamp(Some(7), Some(4));
254 assert!(clamped.minimum <= clamped.maximum);
255 assert_eq!(clamped.minimum, 4);
256 assert_eq!(clamped.maximum, 4);
257 }
258
259 #[test]
260 fn zero_constant() {
261 assert_eq!(TextMeasurement::ZERO.minimum, 0);
262 assert_eq!(TextMeasurement::ZERO.maximum, 0);
263 }
264
265 #[test]
266 fn default_is_zero() {
267 let m = TextMeasurement::default();
268 assert_eq!(m, TextMeasurement::ZERO);
269 }
270
271 #[test]
272 fn union_with_zero_is_identity() {
273 let m = TextMeasurement {
274 minimum: 5,
275 maximum: 10,
276 };
277 assert_eq!(m.union(TextMeasurement::ZERO), m);
278 assert_eq!(TextMeasurement::ZERO.union(m), m);
279 }
280
281 #[test]
282 fn stack_with_zero_is_identity() {
283 let m = TextMeasurement {
284 minimum: 5,
285 maximum: 10,
286 };
287 assert_eq!(m.stack(TextMeasurement::ZERO), m);
288 assert_eq!(TextMeasurement::ZERO.stack(m), m);
289 }
290
291 #[test]
292 fn stack_saturates_on_overflow() {
293 let big = TextMeasurement {
294 minimum: usize::MAX - 1,
295 maximum: usize::MAX,
296 };
297 let one = TextMeasurement {
298 minimum: 5,
299 maximum: 5,
300 };
301 let stacked = big.stack(one);
302 assert_eq!(stacked.maximum, usize::MAX);
304 }
305
306 #[test]
307 fn clamp_no_constraints() {
308 let m = TextMeasurement {
309 minimum: 3,
310 maximum: 7,
311 };
312 let clamped = m.clamp(None, None);
313 assert_eq!(clamped, m);
314 }
315
316 #[test]
317 fn clamp_min_raises_both_bounds() {
318 let m = TextMeasurement {
319 minimum: 1,
320 maximum: 2,
321 };
322 let clamped = m.clamp(Some(5), None);
324 assert_eq!(clamped.minimum, 5);
325 assert_eq!(clamped.maximum, 5);
326 }
327
328 #[test]
329 fn union_is_commutative() {
330 let a = TextMeasurement {
331 minimum: 2,
332 maximum: 8,
333 };
334 let b = TextMeasurement {
335 minimum: 4,
336 maximum: 6,
337 };
338 assert_eq!(a.union(b), b.union(a));
339 }
340
341 #[test]
342 fn stack_is_commutative() {
343 let a = TextMeasurement {
344 minimum: 2,
345 maximum: 8,
346 };
347 let b = TextMeasurement {
348 minimum: 4,
349 maximum: 6,
350 };
351 assert_eq!(a.stack(b), b.stack(a));
352 }
353}