Skip to content

Commit dcbe80f

Browse files
committed
Valid/unique glyph names check
1 parent d46fdc3 commit dcbe80f

File tree

4 files changed

+141
-1
lines changed

4 files changed

+141
-1
lines changed

profile-universal/Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ fontspector-checkapi = { path = "../fontspector-checkapi" }
1212
read-fonts = {workspace = true}
1313
write-fonts = {workspace = true}
1414
font-types = {workspace = true}
15-
skrifa = {workspace = true}
15+
skrifa = {workspace = true}
16+
regex = "1.10.6"
17+
itertools = "0.13.0"
+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use std::collections::HashSet;
2+
3+
use font_types::Version16Dot16;
4+
use fontspector_checkapi::{prelude::*, skip, testfont, FileTypeConvert};
5+
use itertools::Itertools;
6+
use read_fonts::TableProvider;
7+
use regex::Regex;
8+
9+
enum NameValidity {
10+
OK,
11+
Naughty,
12+
Long,
13+
}
14+
fn test_glyph_name(s: &str) -> NameValidity {
15+
if s.starts_with(".null") || s.starts_with(".notdef") || s.starts_with(".ttfautohint") {
16+
return NameValidity::OK;
17+
}
18+
#[allow(clippy::unwrap_used)]
19+
let re = Regex::new(r"^[a-zA-z_][a-zA-Z._0-9]{0,62}$").unwrap();
20+
if !re.is_match(s) {
21+
return NameValidity::Naughty;
22+
}
23+
if s.len() > 31 && s.len() <= 63 {
24+
return NameValidity::Long;
25+
}
26+
NameValidity::OK
27+
}
28+
29+
fn valid_glyphnames(f: &Testable, _context: &Context) -> CheckFnResult {
30+
let font = testfont!(f);
31+
let mut problems: Vec<Status> = vec![];
32+
let post = font.font().post()?;
33+
if post.version() == Version16Dot16::new(3, 0) {
34+
skip!(
35+
"post-3",
36+
"TrueType fonts with a format 3 post table contain no glyph names."
37+
);
38+
}
39+
let mut badnames = HashSet::new();
40+
let mut warnnames = HashSet::new();
41+
let mut allnames = HashSet::new();
42+
let mut duplicates = HashSet::new();
43+
if let Some(indices) = post.glyph_name_index() {
44+
for index in indices {
45+
if let Some(Ok(name)) = post
46+
.string_data()
47+
.ok_or_else(|| {
48+
CheckError::Error("Failed to read post table string data".to_string())
49+
})?
50+
.get(index.get() as usize)
51+
{
52+
let name = name.as_str();
53+
if allnames.contains(name) {
54+
duplicates.insert(name);
55+
}
56+
allnames.insert(name);
57+
match test_glyph_name(name) {
58+
NameValidity::OK => {}
59+
NameValidity::Naughty => {
60+
badnames.insert(name);
61+
}
62+
NameValidity::Long => {
63+
warnnames.insert(name);
64+
}
65+
}
66+
}
67+
}
68+
}
69+
if !badnames.is_empty() {
70+
problems.push(Status::fail(
71+
"found-invalid-names",
72+
&format!(
73+
"The following glyph names do not comply with naming conventions: {:}\n\n
74+
A glyph name must be entirely comprised of characters
75+
from the following set: A-Z a-z 0-9 .(period) _(underscore).
76+
A glyph name must not start with a digit or period.
77+
There are a few exceptions such as the special glyph '.notdef'.
78+
The glyph names \"twocents\", \"a1\", and \"_\" are all valid,
79+
while \"2cents\" and \".twocents\" are not.'",
80+
Itertools::intersperse(badnames.into_iter(), ", ").collect::<String>()
81+
),
82+
));
83+
}
84+
if !warnnames.is_empty() {
85+
problems.push(Status::warn(
86+
"legacy-long-names",
87+
&format!(
88+
"The following glyph names are too long: {:?}",
89+
Itertools::intersperse(warnnames.into_iter(), ", ").collect::<String>()
90+
),
91+
));
92+
}
93+
if !duplicates.is_empty() {
94+
problems.push(Status::fail(
95+
"duplicated-glyph-names",
96+
&format!(
97+
"These glyph names occur more than once: {:?}",
98+
Itertools::intersperse(duplicates.into_iter(), ", ").collect::<String>()
99+
),
100+
));
101+
}
102+
103+
return_result(problems)
104+
}
105+
106+
pub const CHECK_VALID_GLYPHNAMES: Check = Check {
107+
id: "com.google.fonts/check/valid_glyphnames",
108+
title: "Glyph names are all valid?",
109+
rationale: "Microsoft's recommendations for OpenType Fonts states the following:
110+
111+
'NOTE: The PostScript glyph name must be no longer than 31 characters,
112+
include only uppercase or lowercase English letters, European digits,
113+
the period or the underscore, i.e. from the set `[A-Za-z0-9_.]` and
114+
should start with a letter, except the special glyph name `.notdef`
115+
which starts with a period.'
116+
117+
https://learn.microsoft.com/en-us/typography/opentype/otspec181/recom#-post--table
118+
119+
120+
In practice, though, particularly in modern environments, glyph names
121+
can be as long as 63 characters.
122+
123+
According to the \"Adobe Glyph List Specification\" available at:
124+
125+
https://github.com/adobe-type-tools/agl-specification
126+
127+
Glyph names must also be unique, as duplicate glyph names prevent font installation on Mac OS X.",
128+
proposal: "https://github.com/fonttools/fontbakery/issues/2832",
129+
implementation: CheckImplementation::CheckOne(&valid_glyphnames),
130+
applies_to: "TTF",
131+
hotfix: None,
132+
fix_source: None,
133+
flags: CheckFlags::default(),
134+
};

profile-universal/src/checks/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
mod bold_italic_unique;
22
mod fvar;
3+
mod glyphnames;
34
mod name_trailing_spaces;
45
mod required_tables;
56
mod unwanted_tables;
67
pub use fvar::CHECK_REGULAR_COORDS_CORRECT;
8+
pub use glyphnames::CHECK_VALID_GLYPHNAMES;
79
// pub use bold_italic_unique::CHECK_BOLD_ITALIC_UNIQUE;
810
pub use name_trailing_spaces::CHECK_NAME_TRAILING_SPACES;
911
pub use required_tables::CHECK_REQUIRED_TABLES;

profile-universal/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ impl fontspector_checkapi::Plugin for Universal {
1010
cr.register_check(checks::CHECK_UNWANTED_TABLES);
1111
cr.register_check(checks::CHECK_REQUIRED_TABLES);
1212
cr.register_check(checks::CHECK_REGULAR_COORDS_CORRECT);
13+
cr.register_check(checks::CHECK_VALID_GLYPHNAMES);
1314
let profile = Profile::from_toml(
1415
r#"
1516
[sections]
@@ -18,6 +19,7 @@ impl fontspector_checkapi::Plugin for Universal {
1819
"com.google.fonts/check/unwanted_tables",
1920
"com.google.fonts/check/required_tables",
2021
"com.google.fonts/check/fvar/regular_coords_correct",
22+
"com.google.fonts/check/valid_glyphnames"
2123
]
2224
"#,
2325
)

0 commit comments

Comments
 (0)