Skip to content

Commit 35db31f

Browse files
committed
Three more checks
1 parent 4fe626b commit 35db31f

File tree

3 files changed

+189
-13
lines changed

3 files changed

+189
-13
lines changed

fontspector-checkapi/src/font.rs

+6
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,10 @@ impl TestFont<'_> {
258258
.map_err(|_| CheckError::Error("Failed to draw glyph".to_string()))?;
259259
Ok(())
260260
}
261+
262+
pub fn filename_suggests_italic(&self) -> bool {
263+
self.filename
264+
.to_str()
265+
.map_or(false, |f| f.contains("-Italic"))
266+
}
261267
}

profile-universal/src/checks/name.rs

+167-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::collections::HashMap;
2+
13
use font_types::NameId;
2-
use fontspector_checkapi::{prelude::*, testfont, FileTypeConvert};
4+
use fontspector_checkapi::{prelude::*, skip, testfont, FileTypeConvert};
35
use read_fonts::TableProvider;
46

57
#[check(
@@ -117,6 +119,170 @@ fn check_name_match_familyname_fullfont(t: &Testable, _context: &Context) -> Che
117119
return_result(vec![])
118120
}
119121

122+
#[check(
123+
id = "opentype/family/max_4_fonts_per_family_name",
124+
rationale = "
125+
Per the OpenType spec:
126+
127+
'The Font Family name [...] should be shared among at most four fonts that
128+
differ only in weight or style [...]'
129+
",
130+
proposal = "https://github.com/fonttools/fontbakery/pull/2372",
131+
title = "Verify that each group of fonts with the same nameID 1 has maximum of 4 fonts.",
132+
implementation = "all"
133+
)]
134+
fn family_max_4_fonts_per_family_name(t: &TestableCollection, _context: &Context) -> CheckFnResult {
135+
let fonts = TTF.from_collection(t);
136+
let mut counter = HashMap::new();
137+
let mut problems = vec![];
138+
for font in fonts {
139+
let family_name = font
140+
.get_name_entry_strings(NameId::FAMILY_NAME)
141+
.next()
142+
.ok_or_else(|| {
143+
CheckError::Error(format!(
144+
"Font {} is missing a Family Name entry",
145+
font.filename.to_string_lossy()
146+
))
147+
})?;
148+
*counter.entry(family_name).or_insert(0) += 1;
149+
}
150+
for (family_name, count) in counter {
151+
if count > 4 {
152+
problems.push(Status::fail(
153+
"too-many-fonts",
154+
&format!(
155+
"Family name '{}' has {} fonts, which is more than the maximum of 4",
156+
family_name, count
157+
),
158+
));
159+
}
160+
}
161+
162+
return_result(problems)
163+
}
164+
165+
#[check(
166+
id = "opentype/postscript_name",
167+
title = "PostScript name follows OpenType specification requirements?",
168+
rationale = "The PostScript name is used by some applications to identify the font. It should only consist of characters from the set A-Z, a-z, 0-9, and hyphen.",
169+
proposal = "https://github.com/miguelsousa/openbakery/issues/62"
170+
)]
171+
fn postscript_name(t: &Testable, _context: &Context) -> CheckFnResult {
172+
let font = testfont!(t);
173+
let mut problems = vec![];
174+
for name in font.get_name_entry_strings(NameId::POSTSCRIPT_NAME) {
175+
if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
176+
problems.push(Status::fail(
177+
"invalid-postscript-name",
178+
&format!("PostScript name '{}' contains invalid characters", name),
179+
));
180+
}
181+
}
182+
return_result(problems)
183+
}
184+
185+
const NAME_LIMITS: [(NameId, usize); 6] = [
186+
(NameId::FULL_NAME, 63),
187+
(NameId::POSTSCRIPT_NAME, 63),
188+
(NameId::FAMILY_NAME, 31),
189+
(NameId::SUBFAMILY_NAME, 31),
190+
(NameId::TYPOGRAPHIC_FAMILY_NAME, 31),
191+
(NameId::TYPOGRAPHIC_SUBFAMILY_NAME, 31),
192+
];
193+
194+
#[check(
195+
id = "opentype/family_naming_recommendations",
196+
rationale = "
197+
This check ensures that the length of various family name and style
198+
name strings in the name table are within the maximum length
199+
recommended by the OpenType specification.
200+
",
201+
proposal = "https://github.com/fonttools/fontbakery/issues/4829",
202+
title = "Font follows the family naming recommendations?"
203+
)]
204+
fn family_naming_recommendations(t: &Testable, _context: &Context) -> CheckFnResult {
205+
let font = testfont!(t);
206+
207+
let mut problems = vec![];
208+
for (name_id, max_length) in NAME_LIMITS.iter() {
209+
for name in font.get_name_entry_strings(*name_id) {
210+
if name.len() > *max_length {
211+
problems.push(Status::warn(
212+
"name-too-long",
213+
&format!(
214+
"{:?} (\"{}\") is too long ({} > {})",
215+
name_id,
216+
name,
217+
name.len(),
218+
max_length
219+
),
220+
));
221+
}
222+
}
223+
}
224+
return_result(problems)
225+
}
226+
227+
#[check(
228+
id = "opentype/name/italic_names",
229+
rationale = "
230+
This check ensures that several entries in the name table
231+
conform to the font's Upright or Italic style,
232+
namely IDs 1 & 2 as well as 16 & 17 if they're present.
233+
",
234+
proposal = "https://github.com/fonttools/fontbakery/issues/3666",
235+
title = "Check name table IDs 1, 2, 16, 17 to conform to Italic style."
236+
)]
237+
fn name_italic_names(t: &Testable, _context: &Context) -> CheckFnResult {
238+
let font = testfont!(t);
239+
let mut problems = vec![];
240+
skip!(
241+
!font.filename_suggests_italic(),
242+
"not-italic",
243+
"Font is not italic"
244+
);
245+
if let Some(family_name) = font.get_name_entry_strings(NameId::FAMILY_NAME).next() {
246+
if family_name.contains("Italic") {
247+
problems.push(Status::fail(
248+
"bad-familyname",
249+
"Name ID 1 (Family Name) must not contain 'Italic'.",
250+
));
251+
}
252+
}
253+
if let Some(subfamily_name) = font.get_name_entry_strings(NameId::SUBFAMILY_NAME).next() {
254+
if subfamily_name != "Italic" && subfamily_name != "Bold Italic" {
255+
problems.push(Status::fail(
256+
"bad-subfamilyname",
257+
&format!("Name ID 2 (Subfamily Name) does not conform to specs. Only R/I/B/BI are allowed, found {}", subfamily_name)
258+
));
259+
}
260+
}
261+
if let Some(typo_family_name) = font
262+
.get_name_entry_strings(NameId::TYPOGRAPHIC_FAMILY_NAME)
263+
.next()
264+
{
265+
if typo_family_name.contains("Italic") {
266+
problems.push(Status::fail(
267+
"bad-typographicfamilyname",
268+
"Name ID 16 (Typographic Family Name) must not contain 'Italic'.",
269+
));
270+
}
271+
}
272+
if let Some(typo_subfamily_name) = font
273+
.get_name_entry_strings(NameId::TYPOGRAPHIC_SUBFAMILY_NAME)
274+
.next()
275+
{
276+
if !typo_subfamily_name.ends_with("Italic") {
277+
problems.push(Status::fail(
278+
"bad-typographicsubfamilyname",
279+
"Name ID 16 (Typographic Family Name) must contain 'Italic'.",
280+
));
281+
}
282+
}
283+
return_result(problems)
284+
}
285+
120286
#[cfg(test)]
121287
#[allow(clippy::unwrap_used)]
122288
mod tests {

profile-universal/src/lib.rs

+16-12
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ impl fontspector_checkapi::Plugin for Universal {
1616
cr.register_check(checks::fvar::same_size_instance_records);
1717
cr.register_check(checks::fvar::slant_direction);
1818
cr.register_check(checks::fvar::varfont_foundry_defined_tag_name);
19-
cr.register_check(checks::fvar::varfont_valid_nameids);
2019
cr.register_check(checks::fvar::varfont_valid_default_instance_nameids);
21-
cr.register_check(checks::glyf::glyf_unused_data);
22-
cr.register_check(checks::glyf::check_point_out_of_bounds);
20+
cr.register_check(checks::fvar::varfont_valid_nameids);
2321
cr.register_check(checks::glyf::check_glyf_non_transformed_duplicate_components);
22+
cr.register_check(checks::glyf::check_point_out_of_bounds);
23+
cr.register_check(checks::glyf::glyf_unused_data);
2424
cr.register_check(checks::head::equal_font_versions);
2525
cr.register_check(checks::head::font_version);
2626
cr.register_check(checks::head::mac_style);
@@ -30,17 +30,21 @@ impl fontspector_checkapi::Plugin for Universal {
3030
cr.register_check(checks::layout::layout_valid_feature_tags);
3131
cr.register_check(checks::layout::layout_valid_language_tags);
3232
cr.register_check(checks::layout::layout_valid_script_tags);
33-
cr.register_check(checks::name::check_name_no_copyright_on_description);
3433
cr.register_check(checks::name::check_name_match_familyname_fullfont);
34+
cr.register_check(checks::name::check_name_no_copyright_on_description);
35+
cr.register_check(checks::name::family_max_4_fonts_per_family_name);
36+
cr.register_check(checks::name::family_naming_recommendations);
37+
cr.register_check(checks::name::name_italic_names);
38+
cr.register_check(checks::name::postscript_name);
39+
cr.register_check(checks::os2::check_vendor_id);
40+
cr.register_check(checks::os2::fsselection);
41+
cr.register_check(checks::os2::panose_familytype);
42+
cr.register_check(checks::os2::xavgcharwidth);
3543
cr.register_check(checks::post::post_table_version);
3644
cr.register_check(checks::post::underline_thickness);
3745
cr.register_check(checks::stat::stat_axis_record);
3846
cr.register_check(checks::stat::stat_has_axis_value_tables);
3947
cr.register_check(checks::stat::weight_class_fvar);
40-
cr.register_check(checks::os2::fsselection);
41-
cr.register_check(checks::os2::panose_familytype);
42-
cr.register_check(checks::os2::check_vendor_id);
43-
cr.register_check(checks::os2::xavgcharwidth);
4448

4549
let opentype_profile = Profile::from_toml(
4650
r#"
@@ -80,6 +84,10 @@ impl fontspector_checkapi::Plugin for Universal {
8084
"opentype/name/match_familyname_fullfont",
8185
"opentype/varfont/valid_default_instance_nameids",
8286
"opentype/xavgcharwidth",
87+
"opentype/family/max_4_fonts_per_family_name",
88+
"opentype/postscript_name",
89+
"opentype/family_naming_recommendations",
90+
"opentype/name/italic_names",
8391
8492
# Checks left to port
8593
"opentype/cff2_call_depth",
@@ -88,8 +96,6 @@ impl fontspector_checkapi::Plugin for Universal {
8896
"opentype/cff_deprecated_operators",
8997
"opentype/code_pages",
9098
"opentype/family/consistent_family_name",
91-
"opentype/family/max_4_fonts_per_family_name",
92-
"opentype/family_naming_recommendations",
9399
94100
# Checks we don't need because they have been integrated into other checks
95101
# "opentype/dsig", (unwanted_tables)
@@ -119,10 +125,8 @@ impl fontspector_checkapi::Plugin for Universal {
119125
"opentype/kern_table",
120126
"opentype/loca/maxp_num_glyphs",
121127
"opentype/monospace",
122-
"opentype/name/italic_names",
123128
"opentype/name/postscript_name_consistency",
124129
"opentype/name/postscript_vs_cff",
125-
"opentype/postscript_name",
126130
"opentype/slant_direction",
127131
]
128132
"#,

0 commit comments

Comments
 (0)