Fonts: the font scan leaves out faces with no outline tables

The font scan also listed faces with none of the tables skrifa reads
glyph outlines from (glyf, CFF, CFF2, VARC): faces whose glyphs are
bitmaps only or are stored as outlines in Apple's hvgl table.
FontDb::outline, which the renderer reads glyph outlines from, returns
an empty path for every glyph of such a face. On macOS 27.0.1 the scan
lists "GB18030 Bitmap" (/System/Library/Fonts/Supplemental/NISC18030.ttf),
which stores its glyphs as bitmaps (bdat) and has no color glyph
tables, so the renderer draws none of the 27,544 characters it maps.

file_face_names now leaves out a face whose table directory has none of
those tables, as vectorcraft's font scan does (storytold/vectorcraft#781).
This commit is contained in:
dotCooCoo
2026-10-12 21:43:39 -07:00
parent 1660982e58
commit 180cb4f36f
2 changed files with 40 additions and 0 deletions
+10
View File
@@ -382,9 +382,16 @@ fn legacy_names(f: &skrifa::FontRef<'_>, family: &str) -> Option<(String, String
Some((legacy, name(f, &[StringId::SUBFAMILY_NAME]).unwrap_or_else(|| "Regular".into())))
}
/// The tables skrifa reads glyph outlines from: `glyf` (TrueType), `CFF `, `CFF2` and `VARC`. For a
/// face with none of them (bitmaps only, or outlines in Apple's `hvgl` table), [`FontDb::outline`]
/// returns an empty path for every glyph.
#[cfg(not(target_arch = "wasm32"))]
const OUTLINE_TABLES: [&[u8; 4]; 4] = [b"glyf", b"CFF ", b"CFF2", b"VARC"];
/// (family, style) of every face in the font file at `path`, reading only its table directories
/// and `name` tables: a scan opens hundreds of font files, many of them megabytes long. A
/// variable font is cataloged under its default style; its named instances appear once it loads.
/// Faces with none of the [`OUTLINE_TABLES`] are left out.
#[cfg(not(target_arch = "wasm32"))]
fn file_face_names(path: &std::path::Path) -> Vec<(String, String)> {
use std::io::{Read, Seek, SeekFrom};
@@ -413,6 +420,9 @@ fn file_face_names(path: &std::path::Path) -> Vec<(String, String)> {
let dir = read_at(start.into(), 12)?;
let tables = u16::from_be_bytes(dir.get(4..6)?.try_into().ok()?) as usize;
let records = read_at(u64::from(start) + 12, tables * 16)?;
if !records.as_chunks::<16>().0.iter().any(|r| OUTLINE_TABLES.iter().any(|t| r.starts_with(*t))) {
return None;
}
let rec = records.as_chunks::<16>().0.iter().find(|r| r.starts_with(b"name"))?;
let (offset, len) = (be32(rec, 8)?, be32(rec, 12)?);
if len > MAX_NAME_TABLE {
+30
View File
@@ -220,6 +220,36 @@ mod tests {
}
}
#[test]
fn faces_without_outlines_are_not_cataloged() {
// Rename the Semibold's `glyf` table, as in fonts whose glyphs are bitmaps or are stored in
// Apple's `hvgl` table. The face still maps "A", but its outline is empty. The new tag
// keeps the table directory sorted.
let mut semibold = include_bytes!("../tests/fixtures/legacy-semibold.ttf").to_vec();
let tables = u16::from_be_bytes([semibold[4], semibold[5]]) as usize;
let glyf = (0..tables).map(|i| 12 + 16 * i).find(|&r| &semibold[r..r + 4] == b"glyf").unwrap();
semibold[glyf..glyf + 4].copy_from_slice(b"gxyz");
let loaded = FontDb::with_font_dirs(Vec::new());
assert_eq!(loaded.add_font(semibold.clone()), 1);
let face = loaded.face("DeckCraft Legacy", "Semibold");
assert!(face.covers('A'));
assert!(loaded.outline(&face, face.glyph_for('A')).elements().is_empty());
// The system font scan lists the Regular, which has outlines, and leaves out the Semibold
// under both of its names.
#[cfg(not(target_arch = "wasm32"))]
{
let dir = std::env::temp_dir().join(format!("deckcraft-fonts-outlines-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("legacy-semibold.ttf"), &semibold).unwrap();
std::fs::write(dir.join("legacy-regular.ttf"), include_bytes!("../tests/fixtures/legacy-regular.ttf")).unwrap();
let db = FontDb::with_font_dirs(vec![dir.clone()]);
db.set_system_fallback(false);
assert_eq!(db.styles("DeckCraft Legacy"), ["Regular"]);
assert!(!db.has_family("DeckCraft Legacy Semibold"));
let _ = std::fs::remove_dir_all(&dir);
}
}
#[test]
fn missing_document_fonts_are_drawn_in_calibri() {
// PowerPoint draws a missing family in Calibri, whatever its panose or the theme says.