mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-22 18:22:22 +00:00
Fonts: faces with no outline tables are no longer listed
read_face (crates/text/src/sfnt.rs) also listed faces with none of the tables skrifa reads glyph outlines from (glyf, CFF, CFF2, VARC), such as faces whose glyphs are bitmaps only or whose outlines are in Apple's hvgl table. The renderer draws glyphs only from those outlines, so text set in such a face is blank. On macOS 27.0.1 the system scan listed "GB18030 Bitmap" (NISC18030.ttf, bdat bitmaps), so it appeared in the Font menus. read_face now returns no face when the table directory has none of these tables, before it reads the name table. In a collection, the faces with outlines keep their index. VectorCraft's font scan has the same check (storytold/vectorcraft#781).
This commit is contained in:
1 parent
cee1feb185
commit
eafc6208d9
1 file changed
+49
-1
+49
-1
@@ -4,6 +4,11 @@
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
/// The tables skrifa reads glyph outlines from: TrueType, CFF, CFF2 and VARC. The renderer draws
|
||||
/// no glyph of a face that has none of them, such as a face with bitmaps only or with its outlines
|
||||
/// in Apple's `hvgl` table.
|
||||
const OUTLINE_TABLES: [&[u8; 4]; 4] = [b"glyf", b"CFF ", b"CFF2", b"VARC"];
|
||||
|
||||
/// Names and style attributes of one face in a font file.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FaceNames {
|
||||
@@ -32,7 +37,8 @@ fn read_at<R: Read + Seek>(r: &mut R, off: u64, len: usize) -> Option<Vec<u8>> {
|
||||
Some(v)
|
||||
}
|
||||
|
||||
/// Read the names of every face in a font file (a single font or a collection).
|
||||
/// Read the names of every face in a font file (a single font or a collection). Faces with none
|
||||
/// of the `OUTLINE_TABLES` are left out.
|
||||
pub fn read_faces<R: Read + Seek>(r: &mut R) -> Vec<FaceNames> {
|
||||
let Some(head) = read_at(r, 0, 12) else { return Vec::new() };
|
||||
let offsets: Vec<u32> = if &head[0..4] == b"ttcf" {
|
||||
@@ -58,6 +64,9 @@ fn read_face<R: Read + Seek>(r: &mut R, off: u64, index: u32) -> Option<FaceName
|
||||
}
|
||||
let n = be16(&h, 4)? as usize;
|
||||
let dir = read_at(r, off + 12, n * 16)?;
|
||||
if !dir.as_chunks::<16>().0.iter().any(|rec| OUTLINE_TABLES.iter().any(|t| rec.starts_with(*t))) {
|
||||
return None;
|
||||
}
|
||||
let mut name = None;
|
||||
let mut os2 = None;
|
||||
for i in 0..n {
|
||||
@@ -137,4 +146,43 @@ mod tests {
|
||||
assert_eq!(n[0].family, "Noto Serif");
|
||||
assert!(read_faces_bytes(b"not a font").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn faces_without_outline_tables_are_left_out() {
|
||||
use skrifa::MetadataProvider;
|
||||
// Renaming the `glyf` record of a copy of Inter Regular gives a face with no outline
|
||||
// tables, like a face whose glyphs are bitmaps only or are kept in Apple's `hvgl` table.
|
||||
// The new tag keeps the table directory sorted.
|
||||
let font = crate::fonts::INTER_REGULAR;
|
||||
let n = usize::from(be16(font, 4).unwrap());
|
||||
let glyf = (0..n).map(|i| 12 + 16 * i).find(|&r| &font[r..r + 4] == b"glyf").unwrap();
|
||||
let mut renamed = font.to_vec();
|
||||
renamed[glyf..glyf + 4].copy_from_slice(b"gxyz");
|
||||
// skrifa maps 'A' to a glyph of the copy and reads no outline for it.
|
||||
let copy = skrifa::FontRef::new(&renamed).unwrap();
|
||||
assert!(copy.outline_glyphs().get(copy.charmap().map('A').unwrap()).is_none());
|
||||
assert!(read_faces_bytes(&renamed).is_empty());
|
||||
assert_eq!(read_faces_bytes(font).len(), 1);
|
||||
// A collection holds both faces, which share one copy of the tables. Table offsets count
|
||||
// from the start of the collection. Only the face with outlines is listed, with its index 1.
|
||||
let dir = 12 + 16 * n;
|
||||
let tables_at = 20 + 2 * dir;
|
||||
let shifted = |face: &[u8]| {
|
||||
let mut d = face[..dir].to_vec();
|
||||
for r in (12..dir).step_by(16) {
|
||||
let o = be32(&d, r + 8).unwrap() + tables_at as u32;
|
||||
d[r + 8..r + 12].copy_from_slice(&o.to_be_bytes());
|
||||
}
|
||||
d
|
||||
};
|
||||
// The header holds the tag, version 1.0, the number of faces and where each face's table
|
||||
// directory starts.
|
||||
let mut ttc = [*b"ttcf", [0, 1, 0, 0], 2u32.to_be_bytes(), 20u32.to_be_bytes(), (20 + dir as u32).to_be_bytes()].concat();
|
||||
ttc.extend(shifted(&renamed));
|
||||
ttc.extend(shifted(font));
|
||||
ttc.extend_from_slice(font);
|
||||
let faces = read_faces_bytes(&ttc);
|
||||
assert_eq!(faces.len(), 1);
|
||||
assert_eq!((faces[0].index, faces[0].family.as_str(), faces[0].style.as_str()), (1, "Inter", "Regular"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user