Rust port of Pretext, a fast, accurate, and multilingual text measurement and line-breaking library.
- Two-phase API: analyze text with
prepare(), then wrap it withlayout(). - Backend agnostic: plug in your own
FontMetrics,WordSegmenter, andGraphemeSegmenterimplementations. - No browser required: replaces
CanvasRenderingContext2D,Intl.Segmenter, and user-agent sniffing with native Rust backends. - Slint integration: drop-in
PretextLabelcomponent rendered on the CPU.
- Installation
- Quick start
- Workspace layout
- Architecture
- Performance
- Current limitations
- Contributing
- License
Add the crates you need to your Cargo.toml:
[dependencies]
pretext-core = { git = "https://github.com/juice094/pretext-rust.git" }
pretext-fontdb = { git = "https://github.com/juice094/pretext-rust.git" }
pretext-slint = { git = "https://github.com/juice094/pretext-rust.git" }When the project is published to crates.io you will be able to use the
version = "..." form instead.
use pretext_core::{EngineProfile, Font, FontMetrics, PrepareOptions, prepare};
use pretext_core::layout::layout_with_lines;
struct FixedWidthMetrics { width_per_char: f32 }
impl FontMetrics for FixedWidthMetrics {
fn measure(&self, text: &str, _font: &Font) -> f32 {
text.chars().count() as f32 * self.width_per_char
}
fn supports_char(&self, _c: char, _font: &Font) -> bool { true }
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let metrics = FixedWidthMetrics { width_per_char: 10.0 };
let font = Font::new("16px Inter");
let prepared = prepare("hello world", &font, &metrics, &PrepareOptions::default())?;
let result = layout_with_lines(&prepared, 50.0, &EngineProfile::chromium())?;
for line in &result.lines {
println!("{:?} width={}", line.text, line.width);
}
Ok(())
}use pretext_core::{EngineProfile, Font, FontMetrics, PrepareOptions, prepare};
use pretext_core::rich_inline::{
prepare_rich_inline, walk_rich_inline_line_ranges, RichInlineItem,
};
struct FixedWidthMetrics { width_per_char: f32 }
impl FontMetrics for FixedWidthMetrics {
fn measure(&self, text: &str, _font: &Font) -> f32 {
text.chars().count() as f32 * self.width_per_char
}
fn supports_char(&self, _c: char, _font: &Font) -> bool { true }
}
fn main() {
let metrics = FixedWidthMetrics { width_per_char: 10.0 };
let font = Font::new("16px Inter");
let items = vec![
RichInlineItem::new("hello ", font.clone()),
RichInlineItem::new("@mention", font),
];
let prepared = prepare_rich_inline(&items, &metrics, &EngineProfile::chromium());
walk_rich_inline_line_ranges(&prepared, 80.0, &EngineProfile::chromium(), |line| {
for fragment in &line.fragments {
println!("item {} width={}", fragment.item_index, fragment.occupied_width);
}
});
}Add pretext-slint to your dependencies, then in Rust:
use slint::{Color, ComponentHandle};
fn main() -> Result<(), slint::PlatformError> {
let app = pretext_slint::PretextLabel::new()?;
pretext_slint::install_render_callback(&app);
app.set_text("The quick brown fox jumps over the lazy dog.".into());
app.set_font("22px sans-serif".into());
app.set_requested_width(240.0);
app.set_text_color(Color::from_rgb_u8(220, 220, 220));
app.run()
}Run the included demo:
cargo run -p pretext-slint-demopretext-rust/
├── crates/pretext-core # Core algorithm crate (no Slint dependency)
├── crates/pretext-fontdb # Default font backend (fontdb + rustybuzz + fontdue)
├── crates/pretext-slint # Slint UI bridge
└── crates/pretext-slint-demo # Demo application
The library is split into layers so callers can plug in their own backends:
- Backends (
pretext_core::measurementtraits)FontMetrics— measure text widths and glyph support.WordSegmenter/GraphemeSegmenter— language-aware segmentation.
- Analysis (
pretext_core::analysis)- Normalize whitespace, segment text, merge punctuation, handle CJK and Arabic shaping rules.
- Measurement (
pretext_core::measurement)- Cache segment widths and pre-compute grapheme-level fit advances for overflow wrapping.
- Line breaking (
pretext_core::line_break)- Pure-arithmetic greedy walker over the prepared segment stream.
- High-level layout (
pretext_core::layout,pretext_core::line_text)- Materialize line ranges into text/width pairs.
- Rich inline (
pretext_core::rich_inline)- Multi-item inline flow with collapsed boundary whitespace and atomic items.
- Font backend (
pretext-fontdb)- System font resolution via
fontdb, shaping viarustybuzz, rasterization viafontdue.
- System font resolution via
- Slint bridge (
pretext-slint)- CPU-rendered
Imagecomponent backed by the font backend.
- CPU-rendered
See ARCHITECTURE.md for a deeper design record.
pretext-fontdbcaches raw font data, parsedfontdue::Fontobjects, and rasterized glyphs per(font_id, glyph_index, size).pretext-slintcaches the final renderedSharedPixelBufferkeyed by(text, font, width, color).- For repeated rendering of the same label, the hot path is now essentially a
cache lookup plus an
Imageconstruction.
- Single font / color per label:
pretext-slintdoes not yet support mixed fonts, spans, or inline images. - CPU rasterization: rendering is done on the CPU with manual alpha blending. It is good for labels and static text, but not optimized for high-frequency animation.
- No text selection / cursor / IME: these require extra state machines on top of the layout results.
- Color change still re-renders: only the final pixel buffer is cached; a color change currently rebuilds the image. Future work can cache glyph coverage and re-tint.
git clone https://github.com/juice094/pretext-rust.git
cd pretext-rust
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --check
cargo doc --workspace --no-depsSee CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License.