iced-rs/iced · error
Write font system
Error message
Write font system
What it means
The CPU text-outline path (stroked text in iced's geometry/canvas API) takes the global font-system write lock to shape glyph outlines via the swash cache. `.expect("Write font system")` panics if that lock is poisoned; calling it re-entrantly while the same thread already holds a font-system guard would deadlock instead.
Source
Thrown at graphics/src/geometry/text.rs:84
let translation_x = match self.align_x {
Alignment::Default | Alignment::Left | Alignment::Justified => self.position.x,
Alignment::Center => self.position.x - paragraph.min_width() / 2.0,
Alignment::Right => self.position.x - paragraph.min_width(),
};
let translation_y = {
match self.align_y {
alignment::Vertical::Top => self.position.y,
alignment::Vertical::Center => self.position.y - paragraph.min_height() / 2.0,
alignment::Vertical::Bottom => self.position.y - paragraph.min_height(),
}
};
let buffer = paragraph.buffer();
let mut swash_cache = cosmic_text::SwashCache::new();
let mut font_system = text::font_system().write().expect("Write font system");
for run in buffer.layout_runs() {
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0.0, 0.0), 1.0);
let start_x = translation_x + glyph.x + glyph.x_offset;
let start_y = translation_y + glyph.y_offset + run.line_y;
let offset = Vector::new(start_x, start_y);
if let Some(commands) =
swash_cache.get_outline_commands(font_system.raw(), physical_glyph.cache_key)
{
let glyph = Path::new(|path| {
use cosmic_text::Command;
for command in commands {
match command {
Command::MoveTo(p) => {View on GitHub (pinned to 2cffa99b39)
Solutions
- Find and fix the original panic under the font-system lock — this expect is collateral
- Draw geometry text only on the render thread, never from worker threads
- Recover from poison in the library: `write().unwrap_or_else(PoisonError::into_inner)`
- Prefer the filled-text path when outline rendering is optional for your visuals
Example fix
// before
let mut font_system = text::font_system().write().expect("Write font system");
// after
let mut font_system = text::font_system()
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner); Defensive patterns
Strategy: validation
Try / catch
let outlined = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
draw_stroked_text(&frame, ¶graph);
}));
if outlined.is_err() {
draw_filled_text(&frame, ¶graph); // fall back to the filled-text path
} Prevention
- Draw geometry/canvas text only on the render thread, never from worker threads
- Never invoke text outlining from inside callbacks that already run under a font-system lock
- Prefer the filled-text path when outlines are cosmetic — it avoids the write lock entirely
- Treat any first panic in the text pipeline as session-ending; the poisoned lock will cascade here
When it happens
Trigger: Drawing stroked/outlined text in a canvas frame once another thread has panicked inside a critical section of the shared font system; outlining text from inside a callback that itself is running under a font-system lock.
Common situations: Canvas-heavy apps mixing stroke text with filled text after any swallowed panic in the text pipeline; debug rendering overlays that outline glyphs on multiple threads.
Related errors
- Write to font system
- Read from font system
- Write application metadata
- Read last palette
- Write last palette
AI-assisted analysis of iced-rs/iced@2cffa99b39 (2026-08-16).
Data as JSON: /api/errors/767c3c444dc96526.
Report an issue: GitHub.