Skip to content

Commit 4995194

Browse files
authored
Merge pull request #1 from felipesanches/new_check_required_tables
new check: com.google.fonts/check/required_tables
2 parents e2b81d7 + 6eb0358 commit 4995194

File tree

6 files changed

+166
-2
lines changed

6 files changed

+166
-2
lines changed

Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
[package]
22
name = "fontspector"
33
version = "0.1.0"
4-
authors = ["Simon Cozens <[email protected]>"]
4+
authors = [
5+
"Simon Cozens <[email protected]>",
6+
"Felipe Sanches <[email protected]>",
7+
]
58
edition = "2021"
69
description = "Quality control for OpenType fonts"
710
repository = "https://github.com/simoncozens/fontspector"

src/check.rs

+6
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ impl Status {
5555
code: StatusCode::Pass,
5656
}
5757
}
58+
pub fn info(s: &str) -> Self {
59+
Self {
60+
message: Some(s.to_string()),
61+
code: StatusCode::Info,
62+
}
63+
}
5864
pub fn fail(s: &str) -> Self {
5965
Self {
6066
message: Some(s.to_string()),

src/checks/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
mod bold_italic_unique;
22
mod name_trailing_spaces;
33
mod unwanted_tables;
4+
mod required_tables;
45
pub use bold_italic_unique::BOLD_ITALIC_UNIQUE_CHECK;
56
pub use name_trailing_spaces::NAME_TRAILING_SPACES_CHECK;
67
pub use unwanted_tables::UNWANTED_TABLES_CHECK;
8+
pub use required_tables::REQUIRED_TABLES_CHECK;

src/checks/required_tables.rs

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use crate::{
2+
check::{return_result, Status, StatusList},
3+
Check, TestFont,
4+
};
5+
6+
use skrifa::Tag;
7+
8+
fn required_tables(f: &TestFont) -> StatusList {
9+
let mut required_table_tags: Vec<Tag> = vec![
10+
Tag::new(b"cmap"),
11+
Tag::new(b"head"),
12+
Tag::new(b"hhea"),
13+
Tag::new(b"hmtx"),
14+
Tag::new(b"maxp"),
15+
Tag::new(b"name"),
16+
Tag::new(b"OS/2"),
17+
Tag::new(b"post"),
18+
];
19+
20+
if f.is_variable_font() {
21+
// According to https://github.com/fonttools/fontbakery/issues/1671
22+
// STAT table is required on WebKit on MacOS 10.12 for variable fonts.
23+
required_table_tags.push(Tag::new(b"STAT"));
24+
}
25+
26+
const OPTIONAL_TABLE_TAGS: [Tag; 20] = [
27+
Tag::new(b"cvt "),
28+
Tag::new(b"fpgm"),
29+
Tag::new(b"loca"),
30+
Tag::new(b"prep"),
31+
Tag::new(b"VORG"),
32+
Tag::new(b"EBDT"),
33+
Tag::new(b"EBLC"),
34+
Tag::new(b"EBSC"),
35+
Tag::new(b"BASE"),
36+
Tag::new(b"GPOS"),
37+
Tag::new(b"GSUB"),
38+
Tag::new(b"JSTF"),
39+
Tag::new(b"gasp"),
40+
Tag::new(b"hdmx"),
41+
Tag::new(b"LTSH"),
42+
Tag::new(b"PCLT"),
43+
Tag::new(b"VDMX"),
44+
Tag::new(b"vhea"),
45+
Tag::new(b"vmtx"),
46+
Tag::new(b"kern"),
47+
];
48+
49+
// See https://github.com/fonttools/fontbakery/issues/617
50+
//
51+
// We should collect the rationale behind the need for each of the
52+
// required tables above. Perhaps split it into individual checks
53+
// with the correspondent rationales for each subset of required tables.
54+
//
55+
// com.google.fonts/check/kern_table is a good example of a separate
56+
// check for a specific table providing a detailed description of
57+
// the rationale behind it.
58+
59+
let mut problems: Vec<Status> = vec![];
60+
61+
let mut optional = vec![];
62+
for tag in OPTIONAL_TABLE_TAGS.iter() {
63+
if f.font().table_data(*tag).is_some() {
64+
optional.push(format!("{}", *tag));
65+
}
66+
}
67+
if !optional.is_empty() {
68+
problems.push(Status::info(&format!("This font contains the following optional tables:\n\n{}", optional.join("\n"))))
69+
}
70+
71+
let mut missing = vec![];
72+
for tag in required_table_tags.iter() {
73+
if !f.font().table_data(*tag).is_some() {
74+
missing.push(format!("{}", *tag));
75+
}
76+
}
77+
78+
// Note (from the OpenType spec):
79+
// OpenType fonts that contain TrueType outlines should use the value of 0x00010000
80+
// for sfntVersion. OpenType fonts containing CFF data (version 1 or 2) should use
81+
// 0x4F54544F ('OTTO', when re-interpreted as a Tag) for sfntVersion.
82+
if f.font().table_directory.sfnt_version() == 0x4F54544F && (
83+
!f.font().table_data(Tag::new(b"CFF ")).is_some() &&
84+
!f.font().table_data(Tag::new(b"CFF2")).is_some()
85+
) {
86+
if f.font().table_data(Tag::new(b"fvar")).is_some() {
87+
missing.push("CFF2".to_string());
88+
} else {
89+
missing.push("CFF ".to_string());
90+
}
91+
} else {
92+
if f.font().table_directory.sfnt_version() == 0x00010000 && !f.font().table_data(Tag::new(b"glyf")).is_some() {
93+
missing.push("glyf".to_string());
94+
}
95+
}
96+
97+
if !missing.is_empty() {
98+
problems.push(Status::fail(&format!("This font is missing the following required tables:\n\n{}", missing.join("\n"))))
99+
}
100+
101+
if problems.is_empty() {
102+
Status::just_one_pass()
103+
} else {
104+
return_result(problems)
105+
}
106+
}
107+
108+
pub const REQUIRED_TABLES_CHECK: Check = Check {
109+
id: "com.google.fonts/check/required_tables",
110+
title: "Font contains all required tables?",
111+
rationale: Some("
112+
According to the OpenType spec
113+
https://docs.microsoft.com/en-us/typography/opentype/spec/otff#required-tables
114+
115+
Whether TrueType or CFF outlines are used in an OpenType font, the following
116+
tables are required for the font to function correctly:
117+
118+
- cmap (Character to glyph mapping)⏎
119+
- head (Font header)⏎
120+
- hhea (Horizontal header)⏎
121+
- hmtx (Horizontal metrics)⏎
122+
- maxp (Maximum profile)⏎
123+
- name (Naming table)⏎
124+
- OS/2 (OS/2 and Windows specific metrics)⏎
125+
- post (PostScript information)
126+
127+
The spec also documents that variable fonts require the following table:
128+
129+
- STAT (Style attributes)
130+
131+
Depending on the typeface and coverage of a font, certain tables are
132+
recommended for optimum quality.
133+
134+
For example:⏎
135+
- the performance of a non-linear font is improved if the VDMX, LTSH,
136+
and hdmx tables are present.⏎
137+
- Non-monospaced Latin fonts should have a kern table.⏎
138+
- A gasp table is necessary if a designer wants to influence the sizes
139+
at which grayscaling is used under Windows. Etc.
140+
"),
141+
proposal: Some("legacy:check/052"),
142+
check_one: Some(&required_tables),
143+
check_all: None
144+
};

src/font.rs

+8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use read_fonts::{tables::os2::SelectionFlags, TableProvider};
33
use skrifa::{
44
font::FontRef,
55
string::{LocalizedStrings, StringId},
6+
Tag,
67
MetadataProvider,
78
};
89
use std::error::Error;
@@ -22,9 +23,11 @@ impl TestFont {
2223
font_data,
2324
})
2425
}
26+
2527
pub fn font(&self) -> FontRef {
2628
FontRef::new(&self.font_data).expect("Can't parse font")
2729
}
30+
2831
pub fn style(&self) -> Option<&str> {
2932
Some("Regular")
3033
}
@@ -33,9 +36,14 @@ impl TestFont {
3336
let os2 = self.font().os2()?;
3437
Ok(os2.fs_selection())
3538
}
39+
3640
pub fn get_name_entry_strings(&self, name_id: StringId) -> LocalizedStrings {
3741
self.font().localized_strings(name_id)
3842
}
43+
44+
pub fn is_variable_font(&self) -> bool {
45+
self.font().table_data(Tag::new(b"fvar")).is_some()
46+
}
3947
}
4048

4149
pub struct FontCollection<'a>(pub Vec<&'a TestFont>);

src/universal.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::checks::*;
22
use crate::Check;
33

4-
pub const UNIVERSAL_PROFILE: [Check<'_>; 3] = [
4+
pub const UNIVERSAL_PROFILE: [Check<'_>; 4] = [
55
BOLD_ITALIC_UNIQUE_CHECK,
66
NAME_TRAILING_SPACES_CHECK,
77
UNWANTED_TABLES_CHECK,
8+
REQUIRED_TABLES_CHECK,
89
];

0 commit comments

Comments
 (0)