denise_text/lib.rs
1//! Fonts, a bounded glyph cache, line layout and word wrapping.
2//!
3//! One [`TextEngine`] holds every font an application uses and one [`GlyphAtlas`]
4//! that caches what has been rasterised. Measurement and drawing both go through
5//! it, so a label that is measured during layout and drawn a moment later
6//! rasterises its glyphs exactly once.
7//!
8//! ```no_run
9//! # use denise::{Color, Point};
10//! # use denise_render::Pen;
11//! # use denise_text::{TextEngine, TextStyle};
12//! # fn demo(canvas: &mut Pen<'_>) {
13//! let mut text = TextEngine::new();
14//! let style = TextStyle::built_in(16);
15//! let extent = text.measure(style, "Kjærlighet på Øy");
16//! text.draw(canvas, style, Point::new(20, 20), "Kjærlighet på Øy", Color::WHITE);
17//! # let _ = extent;
18//! # }
19//! ```
20//!
21//! # Wrapping
22//!
23//! [`TextEngine::wrap`] breaks a string into the lines it becomes at a given
24//! width, greedily, returning slices that borrow the input. Explicit `\n` always
25//! breaks. A word wider than the line overflows on a line of its own rather than
26//! being cut between characters — breaking mid-word means knowing where a
27//! grapheme ends, and half of an `æ` is nothing.
28//!
29//! # Three tiers, and what each costs
30//!
31//! Measured as the increase in a stripped, statically linked
32//! `aarch64-unknown-linux-musl` binary:
33//!
34//! | Tier | Feature | Cost | What it buys |
35//! |---|---|---|---|
36//! | Built-in bitmap | none | 0 | Latin plus `æøå`, whole-number scales |
37//! | TrueType | `truetype` | +270 KB | Real fonts, proportional metrics, anti-aliasing |
38//! | Baked | none; `bake` in a build script | the tables | A real face at the sizes it was baked at, no parser on the panel |
39//! | Shaped | `shaping` | +3.1 MB | Ligatures, bidi, complex scripts, font fallback |
40//!
41//! For scale: the whole of Denise, DRM, evdev and the widgets is about 840 KB, so
42//! the shaping tier is four times the rest of the toolkit put together. It is
43//! there because some panels genuinely need it, and off by default because most
44//! do not — a temperature readout and a Norwegian name do not need a shaper.
45//!
46//! The baked tier is the TrueType tier's drawing without its reading: a build
47//! script rasterises the face at the sizes the UI uses, and the panel embeds
48//! the result as tables and links no parser at all. A product that ships one
49//! face wants this; one that lets its user pick a font at run time wants
50//! `truetype`, and the two coexist. See [`baked`].
51//!
52//! # What this is not
53//!
54//! Not a text editor's model: no bidi cursor movement, no grapheme-cluster
55//! segmentation, no line breaking by dictionary. `\n` breaks a line and nothing
56//! else does. Those belong to whoever turns this into a document viewer.
57
58#![cfg_attr(not(feature = "std"), no_std)]
59// Labels every feature-gated item on docs.rs with the feature it needs. Nightly
60// only, and `docsrs` is set by nothing but docs.rs — an ordinary build never
61// sees this line.
62#![cfg_attr(docsrs, feature(doc_cfg))]
63// `chunks_exact` over `as_chunks`, against clippy 1.98's advice: `as_chunks`
64// stabilised in 1.98 and this workspace supports 1.95, so taking the advice
65// would trade a style lint for a compile error on every older toolchain. Revisit
66// when the MSRV passes 1.98. `unknown_lints` because the lint does not exist
67// before 1.98 either, and naming an absent lint is itself a warning.
68#![allow(unknown_lints, clippy::chunks_exact_to_as_chunks)]
69
70extern crate alloc;
71
72pub mod atlas;
73#[cfg(feature = "bake")]
74pub mod bake;
75pub mod baked;
76pub mod bitmap;
77pub mod engine;
78#[cfg(feature = "truetype")]
79mod fill;
80#[cfg(feature = "shaping")]
81pub mod shaped;
82pub mod source;
83#[cfg(feature = "truetype")]
84pub mod truetype;
85
86pub use atlas::{AtlasStats, GlyphAtlas, GlyphKey, Placed};
87pub use baked::{BakedFont, BakedGlyph, BakedSize, BakedSource};
88pub use bitmap::BitmapSource;
89pub use engine::{PositionedGlyph, TextEngine, TextStyle};
90#[cfg(feature = "shaping")]
91pub use shaped::ShapedSource;
92pub use source::{
93 FontId, FontMetrics, GlyphId, GlyphMetrics, GlyphSource, Rasterised, ShapedGlyph,
94};
95#[cfg(feature = "truetype")]
96pub use truetype::TrueTypeSource;
97
98/// Compiles the examples in this crate's README, so they cannot drift from the API
99/// they claim to demonstrate. Never built except under `cargo test --doc`.
100#[cfg(doctest)]
101#[doc = include_str!("../README.md")]
102struct Readme;