Warn, not panic, on invalid fonts (#22491)

# Objective

Fix panics due to invalid legacy fonts that we can't generate glyph
images from.

Fixes #22475

## Solution
* In `text_system` and `update_text2d_layout`, instead of panicking on
`TextError::FailedToGetGlyphImage`, `warn_once` and clear the text
layout.

* Given an invalid font, Cosmic Text returns an invalid text layout with
infinite text bounds. If Cosmic Text returns non-finite bounds, return a
zero size from the `buffer_dimensions` function.

* Added a `get_face_details` method to `CosmicFontSystem` to provide
better info for the warning messages.

`buffer_dimensions` probably should be reworked to return a `Result`,
with the text systems emitting a warning on invalid layout bounds. Not
so important though, left for a follow up.

## Testing

The `system_fonts` example should no longer panic now. Instead the text
with the invalid font is not rendered:

<img width="869" height="166" alt="system-fonts-list-fixed"
src="https://github.com/user-attachments/assets/a05cab45-68d2-453b-8fa6-16ec5b6bba76"
/>

One of the problematic fonts:
https://github.com/justrajdeep/fonts/blob/master/NISC18030.ttf

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
ickshonpe
2026-01-13 19:35:45 +00:00
committed by GitHub
parent 8a5286789b
commit eb1aaba895
4 changed files with 99 additions and 5 deletions
+7 -1
View File
@@ -259,9 +259,15 @@ pub fn update_text2d_layout(
reprocess_queue.insert(entity);
continue;
}
Err(e @ TextError::FailedToGetGlyphImage(key)) => {
bevy_log::warn_once!(
"{e}. Face: {:?}",
font_system.get_face_details(key.font_id)
);
text_layout_info.clear();
}
Err(
e @ (TextError::FailedToAddGlyph(_)
| TextError::FailedToGetGlyphImage(_)
| TextError::MissingAtlasLayout
| TextError::MissingAtlasTexture
| TextError::InconsistentAtlasState),
+85 -3
View File
@@ -12,8 +12,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use crate::{
add_glyph_to_atlas, error::TextError, get_glyph_atlas_info, ComputedTextBlock, Font,
FontAtlasKey, FontAtlasSet, FontHinting, FontSmoothing, FontSource, Justify, LineBreak,
LineHeight, PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout,
FontAtlasKey, FontAtlasSet, FontHinting, FontSmoothing, FontSource, FontStyle, FontWeight,
Justify, LineBreak, LineHeight, PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout,
};
use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap};
@@ -34,6 +34,83 @@ impl Default for CosmicFontSystem {
}
}
impl CosmicFontSystem {
/// Get information about a font face, if it exists
pub fn get_face_details(&self, id: cosmic_text::fontdb::ID) -> Option<FontFaceDetails> {
self.0.db().face(id).map(FontFaceDetails::from)
}
}
#[derive(Debug)]
/// Details about a Font Face
pub struct FontFaceDetails {
/// The path of the source file, if the font was loaded from a file.
pub path: Option<std::path::PathBuf>,
/// The face's index in the font data.
pub index: u32,
/// A list of family names.
///
/// Contains pairs of Name + Language. Where the first family is always English US,
/// unless it's missing from the font.
///
/// Corresponds to a *Typographic Family* (ID 16) or a *Font Family* (ID 1) [name ID]
/// in a TrueType font.
///
/// This is not an *Extended Typographic Family* or a *Full Name*.
/// Meaning it will contain _Arial_ and not _Arial Bold_.
///
/// [name ID]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
pub families: Vec<(String, String)>,
/// A PostScript name.
///
/// Corresponds to a *PostScript name* (6) [name ID] in a TrueType font.
///
/// [name ID]: https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
pub post_script_name: String,
/// A font face style.
pub style: FontStyle,
/// A font face weight.
pub weight: FontWeight,
/// A font face stretch.
pub stretch: u16,
/// Indicates that the font face is monospaced.
pub monospaced: bool,
}
impl From<&cosmic_text::fontdb::FaceInfo> for FontFaceDetails {
fn from(face: &cosmic_text::fontdb::FaceInfo) -> Self {
FontFaceDetails {
path: match face.source {
cosmic_text::fontdb::Source::Binary(_) => None,
cosmic_text::fontdb::Source::File(ref path)
| cosmic_text::fontdb::Source::SharedFile(ref path, _) => Some(path.clone()),
},
index: face.index,
families: face
.families
.iter()
.map(|(name, language)| (name.clone(), language.to_string()))
.collect(),
post_script_name: face.post_script_name.clone(),
style: match face.style {
cosmic_text::Style::Normal => FontStyle::Normal,
cosmic_text::Style::Italic => FontStyle::Italic,
cosmic_text::Style::Oblique => FontStyle::Oblique,
},
weight: FontWeight(face.weight.0),
stretch: face.stretch.to_number(),
monospaced: face.monospaced,
}
}
}
/// A wrapper resource around a [`cosmic_text::SwashCache`]
///
/// The swash cache rasterizer is used to rasterize glyphs
@@ -511,7 +588,12 @@ fn buffer_dimensions(buffer: &Buffer) -> Vec2 {
size.x = size.x.max(run.line_w);
size.y += run.line_height;
}
size.ceil()
if size.is_finite() {
size.ceil()
} else {
Vec2::ZERO
}
}
/// Discards stale data cached in `FontSystem`.
+1
View File
@@ -21,6 +21,7 @@ bevy_image = { path = "../bevy_image", version = "0.18.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.18.0-dev" }
bevy_input_focus = { path = "../bevy_input_focus", version = "0.18.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.18.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.18.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" }
bevy_sprite = { path = "../bevy_sprite", version = "0.18.0-dev", features = [
"bevy_text",
+6 -1
View File
@@ -15,6 +15,7 @@ use bevy_ecs::{
world::Ref,
};
use bevy_image::prelude::*;
use bevy_log::warn_once;
use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_text::{
@@ -369,9 +370,13 @@ pub fn text_system(
// There was an error processing the text layout, try again next frame
text_flags.needs_recompute = true;
}
Err(e @ TextError::FailedToGetGlyphImage(key)) => {
warn_once!("{e}. Face: {:?}", font_system.get_face_details(key.font_id));
text_flags.needs_recompute = false;
text_layout_info.clear();
}
Err(
e @ (TextError::FailedToAddGlyph(_)
| TextError::FailedToGetGlyphImage(_)
| TextError::MissingAtlasLayout
| TextError::MissingAtlasTexture
| TextError::InconsistentAtlasState),