Skip to main content

martensite_text/
lib.rs

1//! Typography and text layout for Martensite.
2//!
3//! This crate provides:
4//! - [`font`]: font system abstraction, system font discovery via
5//!   `fontdb`, and custom font asset loading.
6//! - [`shaping`]: complex text shaping with BiDi, line breaking, and
7//!   font fallback via cosmic-text.
8//! - [`cache`]: two-tier text measurement and glyph shaping cache
9//!   (Tier 1 inline in `ColdNode`, Tier 2 global LRU with 16 MB budget).
10//! - [`ime`]: velocity-damped kinetic IME candidate positioning that
11//!   tracks the caret during active scrolling.
12//!
13//! ## IME candidate projection
14//!
15//! The `compute_ime_bounds` function computes IME candidate window
16//! bounds from the cursor position and line height. For scrolling
17//! containers, prefer the [`ime`] module's [`ImePositioner`], which
18//! applies velocity-damped projection and viewport clamping.
19#![forbid(unsafe_code)]
20#![deny(missing_docs)]
21
22/// Two-tier text cache: `TextShapeCache`, `ShapeCacheKey`.
23pub mod cache;
24/// Font system abstraction: `FontManager`, `FontId`, `FontSource`.
25pub mod font;
26/// Velocity-damped kinetic IME candidate positioning.
27pub mod ime;
28/// Complex text shaping: `Shaper`, `TextMetrics`, `ShapedLine`.
29pub mod shaping;
30
31pub use cache::{
32    CachedShape, FontSizeBits, LineHeightBits, MaxWidthBits, ShapeCacheKey, TextHash,
33    TextShapeCache, DEFAULT_MEMORY_BUDGET,
34};
35pub use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
36pub use font::{FontFaceInfo, FontId, FontManager, FontSource, FontStyle};
37pub use ime::{ImePositioner, ScrollKinematics, Viewport};
38pub use shaping::{
39    measure_text, measure_text_with_attrs, shape_text, ShapedGlyph, ShapedLine, Shaper, TextMetrics,
40};
41
42#[cfg(test)]
43use winit::dpi::{LogicalPosition, LogicalSize};
44
45/// Computes the IME candidate window bounds from the cursor position and line height.
46///
47/// Returns a logical position and size describing where the IME candidate window
48/// should be anchored relative to the text being composed.
49///
50/// # Limitation
51///
52/// The width is a minimal placeholder (`2.0` logical pixels) because the
53/// actual IME candidate window width depends on platform-specific IME
54/// APIs and the composing text content, which are not available at the
55/// text-shaping layer. A future milestone will integrate platform IME
56/// APIs to compute the real candidate window width. The position and
57/// height are accurate.
58///
59/// For scrolling containers, prefer [`ImePositioner::compute_bounds`],
60/// which applies velocity-damped projection and viewport clamping.
61#[deprecated(
62    since = "0.5.0",
63    note = "use `ImePositioner::compute_bounds` for velocity-damped, viewport-clamped IME bounds"
64)]
65#[cfg(test)]
66fn compute_ime_bounds(x: f64, y: f64, height: f64) -> (LogicalPosition<f64>, LogicalSize<f64>) {
67    (LogicalPosition::new(x, y), LogicalSize::new(2.0, height))
68}
69
70#[cfg(test)]
71mod tests {
72    #![allow(deprecated)]
73    use super::compute_ime_bounds;
74
75    #[test]
76    fn returns_correct_position_and_size() {
77        let (pos, size) = compute_ime_bounds(10.0, 20.0, 30.0);
78        assert_eq!(pos.x, 10.0);
79        assert_eq!(pos.y, 20.0);
80        assert_eq!(size.width, 2.0);
81        assert_eq!(size.height, 30.0);
82    }
83
84    #[test]
85    fn zero_height() {
86        let (pos, size) = compute_ime_bounds(5.0, 5.0, 0.0);
87        assert_eq!(pos.x, 5.0);
88        assert_eq!(pos.y, 5.0);
89        assert_eq!(size.width, 2.0);
90        assert_eq!(size.height, 0.0);
91    }
92
93    #[test]
94    fn negative_coordinates() {
95        let (pos, size) = compute_ime_bounds(-10.0, -20.0, 30.0);
96        assert_eq!(pos.x, -10.0);
97        assert_eq!(pos.y, -20.0);
98        assert_eq!(size.width, 2.0);
99        assert_eq!(size.height, 30.0);
100    }
101}