Skip to content

Commit df5d4ec

Browse files
committed
Add can_render_samples check
1 parent 78bb21a commit df5d4ec

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

profile-googlefonts/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ protobuf ={ git = "https://github.com/cmyr/rust-protobuf", branch="parse-unicode
99
itertools = "0.13.0"
1010

1111
chrono="0.4.38" # For metadata date checks
12+
google-fonts-languages = { git="https://github.com/googlefonts/lang", branch = "rust" }
1213

1314
[build-dependencies]
1415
protobuf-codegen ={ git = "https://github.com/cmyr/rust-protobuf", branch="parse-unicode-strings" }

profile-googlefonts/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ impl fontspector_checkapi::Plugin for GoogleFonts {
1010
cr.register_filetype("MDPB", mdpb);
1111
cr.register_check(family::family_equal_codepoint_coverage);
1212
cr.register_check(metadata::validate_metadatapb);
13+
cr.register_check(metadata::can_render_samples);
1314
let profile = Profile::from_toml(
1415
r#"
1516
include_profiles = ["universal"]
1617
[sections]
1718
"Metadata Checks" = [
1819
"com.google.fonts/check/metadata/parses",
20+
"com.google.fonts/check/metadata/can_render_samples",
1921
]
2022
"Family Checks" = [
2123
"com.google.fonts/check/family/equal_codepoint_coverage"

profile-googlefonts/src/metadata.rs

+80-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
44
use chrono::prelude::*;
55
use fonts_public::FamilyProto;
66
use fontspector_checkapi::prelude::*;
7+
use fontspector_checkapi::{skip, testfont, FileTypeConvert};
8+
9+
fn family_proto(t: &Testable) -> Result<FamilyProto, CheckError> {
10+
let mdpb = std::str::from_utf8(&t.contents)
11+
.map_err(|_| CheckError::Error("METADATA.pb is not valid UTF-8".to_string()))?;
12+
protobuf::text_format::parse_from_str::<FamilyProto>(mdpb)
13+
.map_err(|e| CheckError::Error(format!("Error parsing METADATA.pb: {}", e)))
14+
}
715

816
#[check(
917
id = "com.google.fonts/check/metadata/parses",
@@ -16,10 +24,7 @@ use fontspector_checkapi::prelude::*;
1624
applies_to = "MDPB"
1725
)]
1826
fn validate_metadatapb(c: &Testable, _context: &Context) -> CheckFnResult {
19-
let mdpb = std::str::from_utf8(&c.contents)
20-
.map_err(|_| CheckError::Error("METADATA.pb is not valid UTF-8".to_string()))?;
21-
let msg = protobuf::text_format::parse_from_str::<FamilyProto>(mdpb)
22-
.map_err(|e| CheckError::Error(format!("Error parsing METADATA.pb: {}", e)))?;
27+
let msg = family_proto(c)?;
2328
let mut problems = vec![];
2429
if let Some(designer) = msg.designer.as_ref() {
2530
if designer.contains('/') {
@@ -50,3 +55,74 @@ fn validate_metadatapb(c: &Testable, _context: &Context) -> CheckFnResult {
5055
}
5156
return_result(problems)
5257
}
58+
59+
#[check(
60+
id = "com.google.fonts/check/metadata/can_render_samples",
61+
title = "Check samples can be rendered",
62+
rationale = "
63+
In order to prevent tofu from being seen on fonts.google.com, this check
64+
verifies that all samples specified by METADATA.pb can be properly
65+
rendered by the font.
66+
",
67+
proposal = "https://github.com/fonttools/fontbakery/issues/3419",
68+
applies_to = "MDPB",
69+
implementation = "all"
70+
)]
71+
fn can_render_samples(c: &TestableCollection, _context: &Context) -> CheckFnResult {
72+
let mdpb = c
73+
.get_file("METADATA.pb")
74+
.ok_or_else(|| CheckError::skip("no-mdpb", "No METADATA.pb file found"))?;
75+
let msg = family_proto(mdpb)?;
76+
let languages = msg.languages;
77+
if languages.is_empty() {
78+
skip!("no-languages", "No languages specified in METADATA.pb");
79+
}
80+
let fonts = msg
81+
.fonts
82+
.iter()
83+
.flat_map(|f| f.filename.as_ref())
84+
.flat_map(|f| c.get_file(f))
85+
.collect::<Vec<&Testable>>();
86+
if fonts.is_empty() {
87+
skip!("no-fonts", "No font files found in METADATA.pb");
88+
}
89+
let mut samples: Vec<(&str, String)> = vec![];
90+
for language in languages
91+
.iter()
92+
.flat_map(|l| google_fonts_languages::LANGUAGES.get(l.as_str()))
93+
{
94+
if let Some(st) = language.sample_text.as_ref() {
95+
if let Some(tester) = st.tester.as_ref() {
96+
let tester = tester
97+
.to_string()
98+
.replace("\n", " ")
99+
.replace("\u{200b}", "");
100+
if let Some(name) = &language.name {
101+
samples.push((name, tester));
102+
}
103+
}
104+
}
105+
}
106+
let mut problems = vec![];
107+
for font in fonts {
108+
// We could get all clever and use harfruzz here, but to honest,
109+
// the only way you get a .notdef is if you can't use cmap to map
110+
// the character to a glyph, so we'll just use that.
111+
let ttf = testfont!(font);
112+
let codepoints = ttf.codepoints();
113+
for (langid, sample) in samples.iter() {
114+
if sample.chars().any(|c| !codepoints.contains(&(c as u32))) {
115+
problems.push(Status::fail(
116+
"tofu",
117+
&format!(
118+
"Font {} cannot render {} sample text: {}",
119+
font.basename().unwrap_or_default(),
120+
langid,
121+
sample
122+
),
123+
));
124+
}
125+
}
126+
}
127+
return_result(problems)
128+
}

0 commit comments

Comments
 (0)