mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-27 20:16:50 -06:00
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:
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user