Never crash: text layout and font resolution without expect()

Text layout and font resolution without expect(): an empty line list keeps the default bounds,
the line breaker stops instead of expect()ing a checked option, and font resolution falls back
to the first bundled face. The one remaining expect (the bundled-font database is never empty)
carries the documented narrow allow. Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-04 13:27:38 -04:00
1 parent 10644a83aa
commit 0873393e43
4 files changed
+19 -12

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
allow-indexing-slicing-in-tests = true
+9 -8
View File
@@ -309,6 +309,9 @@ pub fn system_scanned() -> bool {
/// A face by id (panics never: unknown ids give the default face).
pub fn face(id: FaceId) -> Arc<Face> {
let d = db().read().unwrap_or_else(|e| e.into_inner());
// The bundled fonts are compiled in and always parse (`tests::bundled_families_and_resolution` checks them), so
// the database is never empty.
#[allow(clippy::expect_used)]
d.faces.get(id).or_else(|| d.faces.first()).cloned().expect("bundled fonts present")
}
@@ -388,7 +391,8 @@ pub fn resolve(family: &str, style: &str) -> Resolved {
return r;
}
}
let mut r = resolve_in(DEFAULT_FAMILY, style).expect("Inter is bundled");
// Inter is bundled; face 0 (the first bundled face) is the last resort.
let mut r = resolve_in(DEFAULT_FAMILY, style).unwrap_or(Resolved { face: 0, synth_bold: false, synth_italic: false, missing: true });
r.missing = !family.is_empty() && !family.eq_ignore_ascii_case(DEFAULT_FAMILY);
r
}
@@ -403,13 +407,10 @@ fn resolve_in(family: &str, style: &str) -> Option<Resolved> {
return Some(Resolved { face: f.id, synth_bold: false, synth_italic: false, missing: false });
}
let (w, it) = style_wants(style);
let best = fam
.iter()
.min_by_key(|f| {
let italic_pen = if f.info.italic == it { 0 } else { 1000 };
italic_pen + (f.info.weight as i32 - w as i32).unsigned_abs()
})
.expect("non-empty");
let best = fam.iter().min_by_key(|f| {
let italic_pen = if f.info.italic == it { 0 } else { 1000 };
italic_pen + (f.info.weight as i32 - w as i32).unsigned_abs()
})?;
Some(Resolved { face: best.id, synth_bold: w >= 600 && best.info.weight <= 500, synth_italic: it && !best.info.italic, missing: false })
}
+4 -4
View File
@@ -776,7 +776,7 @@ fn paragraph(text: &str, base: usize, runs: &[(Range<usize>, TextStyle)], primar
last_ok = Some(p);
break;
}
let b = last_ok.take().expect("checked");
let Some(b) = last_ok.take() else { break };
ranges.push(start..b);
start = b;
}
@@ -1015,9 +1015,9 @@ pub fn layout_rich_uncached(text: &str, runs: &[(Range<usize>, TextStyle)], para
x0 = x0.min(0.0);
x1 = x1.max(w);
}
let first = &lay.lines[0];
let last = lay.lines.last().expect("at least one line");
lay.bounds = [x0, first.baseline - first.ascent, x1.max(x0), last.baseline + last.descent];
if let (Some(first), Some(last)) = (lay.lines.first(), lay.lines.last()) {
lay.bounds = [x0, first.baseline - first.ascent, x1.max(x0), last.baseline + last.descent];
}
lay
}
+2
View File
@@ -5,6 +5,8 @@
//! - This module turns a layer's [`TextDoc`] into per-character glyph outlines (Bezier paths) with
//! character / word / line indices, which is what text animators and selectors work on.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
pub mod fonts;
pub mod layout;
pub mod path_text;