Skip to content

Text and fonts

A line of text, and the fonts it is drawn in. A font is named by the role the text plays, never by typeface, and the host answers with a face it registered, so every width and line height here comes from the font engine that paints the text and nothing is estimated.

A book row: a book icon in outline beside Middlemarch in bold over George Eliot in the small interface face, and a bookmark ribbon at the far edge

Images is the other half of what puts something on the panel: a bitmap you ship and an icon the host draws. Canvas and chrome is how a host measures and draws the text these name. This page is what each piece does.

TextDraws a single line of text.
FontA font plus the style it is drawn in, as widgets reach for it.
FontIdA font the host has registered, as a number only the host can interpret.
FontRoleWhat a piece of text is for, rather than which typeface it uses.
FontStyleWeight and slant, as a host-independent value.

Draws a single line of text.

pub struct Text

Four lines in the interface face: Regular, Bold, Italic drawn upright, and Bold italic drawn bold, because this host has no italic cut

BuilderSetsWhen not called
Text::newthe string—
fontthe fontFont::ui(), the interface face
bold · italicthe style, replacing any set beforeregular

The italic lines in the picture are upright. The family its host draws was never cut in italic, so the host draws the nearest style it has, which is FontStyle's rule.

It is measured by the host. While the tree is laid out, a Text asks its font for the width of its string and the height of one line, and keeps both. That is the size it reports, whatever it is offered.

It is one line, and never shortened. A string wider than the space it is given reports its whole width and draws past the edge. Shorten it in update, or put it in a ListRow, whose theme decides what a long title does.

It draws nothing in a missing face. In Font::UNAVAILABLE it measures zero and never hands its string to the host, so no backend paints it in another face over space nothing reserved.

It holds the string it was built with. Build the string in update and keep it. A format! inside body runs on every paint and every frame carrying input, and brings core::fmt with it.

Example — styles

Rust
use xpui::{Font, FontStyle, Text, VStack, vstack};
let styles: VStack<()> = vstack![8;
Text::new("Regular"),
Text::new("Bold").bold(),
Text::new("Italic").italic(),
Text::new("Bold italic").font(Font::ui().with_style(FontStyle::BoldItalic)),
];

Example — a figure formatted once

Rust
use xpui::{NavigationScreen, Screen, Text, View, vstack};
struct Battery {
label: String, // "72%", rebuilt only when the level changes
}
impl Screen for Battery {
type Message = u8;
fn body(&self) -> impl View<u8> {
NavigationScreen::new(vstack![8;
Text::new("Battery"),
Text::new(self.label.as_str()).bold(),
])
}
fn update(&mut self, percent: u8) {
self.label = format!("{percent}%");
}
fn title(&self) -> Option<&'static str> {
Some("Power")
}
}
let mut screen = Battery { label: String::new() };
screen.update(72);
assert_eq!(screen.label, "72%");

Text in the default interface font.

pub fn new(content: impl Into<String>) -> Self

It asks the installed host for the interface font, so a host must be installed first; the examples on this page install the test host for that reason. A string containing an interior NUL may render as empty: a host with a C boundary cannot pass it.

Draws in font instead of the default.

pub fn font(self, font: Font) -> Self
Rust
use xpui::{Font, Text};
let caption = Text::new("Last synced an hour ago").font(Font::ui_small());
let page = Text::new("It is a truth universally acknowledged").font(Font::reader());

Draws bold.

pub fn bold(self) -> Self

Draws italic.

pub fn italic(self) -> Self

See also: Font, FontRole, ListRow

A font plus the style it is drawn in, as widgets reach for it.

pub struct Font

Fonts are named by what the text is for, never by typeface. Which face a FontRole resolves to is the host's business, and a role survives the host's assets being changed. A Font is Copy and compares by face and style.

ToCall
choose a facerole · ui · ui_small · reader
set the stylebold · italic · with_style
measuretext_width · line_height · is_available

Never estimate a width. Measure through the font: an estimate drifts from what is painted, and content drifts off the panel with it. Text measures this way for you.

Example — naming fonts by role

Rust
use xpui::{Font, FontRole, FontStyle};
xpui::testing::install();
let ui = Font::ui(); // interface text: the widget default
let small = Font::ui_small(); // captions, secondary labels
let reader = Font::reader(); // the face the user reads in
let bold = Font::ui().bold(); // also .italic()
let both = Font::ui().with_style(FontStyle::BoldItalic);
assert_eq!(Font::role(FontRole::Ui), ui);
assert_eq!(both.id(), ui.id(), "a style is not another face");
assert!(ui.text_width("Battery") > 0);
assert!(ui.line_height() > 0);

Example — choosing the face that fits

Rust
use xpui::{Font, Text, Theme};
xpui::testing::install();
let title = "The Collected Poems of Wallace Stevens";
let room = Theme::content_area().width();
let font = if Font::ui().text_width(title) <= room { Font::ui() } else { Font::ui_small() };
let label = Text::new(title).font(font);

The host's font for role, in regular style.

pub fn role(role: FontRole) -> Self

It asks the installed host when it is called, and keeps the answer.

Interface text: the default for every widget.

pub fn ui() -> Self

Smaller interface text, for captions and secondary labels.

pub fn ui_small() -> Self

The face the user chose for reading.

pub fn reader() -> Self

A font this build does not ship, which measures zero.

pub const UNAVAILABLE: Font = Font

A build may compile a face out, and the host answers its role with this. A missing face therefore takes no room in a layout, rather than being given a size nobody measured, and a Text in it draws nothing: the string never reaches the host, which might otherwise paint it in a face it does know.

A widget of your own that calls Renderer::draw_text directly makes the same check with is_available.

Rust
use xpui::{Font, Point, Text, View, testing};
testing::install();
testing::reset();
let missing = Font::UNAVAILABLE;
assert!(!missing.is_available());
assert_eq!(missing.text_width("Battery"), 0);
assert_eq!(missing.line_height(), 0);
let mut label = Text::new("Battery").font(missing);
View::<()>::measure(&mut label, testing::screen());
View::<()>::render(&label, Point::ORIGIN);
assert!(testing::drawn_text().is_empty());

The same font in bold, replacing any style set before.

pub fn bold(self) -> Self

The same font in italic, replacing any style set before.

pub fn italic(self) -> Self

The same font in style.

pub fn with_style(self, style: FontStyle) -> Self

The only way to ask for FontStyle::BoldItalic.

The width text paints at, or 0 for a face this build lacks.

pub fn text_width(self, text: &str) -> i32

In pixels, in this font's style: bold text is usually wider.

The height one line occupies, or 0 for a face this build lacks.

pub fn line_height(self) -> i32

Ascent and descent included, so lines stacked at this pitch do not touch.

Whether this build ships the face.

pub fn is_available(self) -> bool

What a host is handed when it draws. A screen has no need of these.

The host's id for the face.

pub fn id(self) -> FontId

The weight and slant it is drawn in.

pub fn style(self) -> FontStyle

See also: Text, FontRole, FontStyle, FontId

A font the host has registered, as a number only the host can interpret.

pub struct FontId(pub i32)

The host's side of a Font. A host's TextMetrics::font answers a role with one, and receives it back with every measurement and every string it draws; see writing a backend. 0 means "this build does not ship that font". The number is public, FontId(7), because a host makes them; a screen only ever receives them.

Rust
use xpui::{Font, FontId};
assert!(FontId(7).is_available());
assert!(!FontId::UNAVAILABLE.is_available());
assert_eq!(Font::UNAVAILABLE.id(), FontId::UNAVAILABLE);

The font a build compiled out, which measures zero.

pub const UNAVAILABLE: FontId = FontId(0)

Whether this build ships the font.

pub fn is_available(self) -> bool

See also: Font, Font::UNAVAILABLE

What a piece of text is for, rather than which typeface it uses.

pub enum FontRole

Three lines: Interface text, a smaller caption beneath it, and a line in the face chosen for reading

The host decides what each role means. Naming families here would tie the framework to one product's assets, and a role survives those assets being changed.

Variant
FontRole::UiInterface text: labels, list rows, values.
FontRole::UiSmallSmaller interface text, for captions and secondary labels.
FontRole::ReaderThe face the user chose for reading, including fonts loaded from storage.

Each has a shorthand on Font: ui, ui_small and reader.

Rust
use xpui::{Font, FontRole, Text, VStack, vstack};
assert_eq!(Font::role(FontRole::UiSmall), Font::ui_small());
let roles: VStack<()> = vstack![8;
Text::new("Interface text"),
Text::new("A caption beneath it").font(Font::role(FontRole::UiSmall)),
Text::new("The face chosen for reading").font(Font::role(FontRole::Reader)),
];

See also: Font::role, FontStyle

Weight and slant, as a host-independent value.

pub enum FontStyle
Variant
FontStyle::RegularUpright, regular weight.
FontStyle::BoldHeavier weight.
FontStyle::ItalicSlanted.
FontStyle::BoldItalicBoth.

Regular is the default. The type is #[repr(u8)], and the numbers are what a host with a C boundary receives: Regular is 0, Bold 1, Italic 2 and BoldItalic 3. A host without a face in that style draws what it has.

Rust
use xpui::{Font, FontStyle};
assert_eq!(FontStyle::default(), FontStyle::Regular);
assert_eq!(Font::ui().bold().italic().style(), FontStyle::Italic, "the last one wins");
assert_eq!(Font::ui().with_style(FontStyle::BoldItalic).style() as u8, 3);

See also: Font::with_style, FontRole