Skip to content

Commit 2e5c317

Browse files
yanonefelipesanches
authored andcommitted
new check: GF family_name compliance
Renamed check ID on Google Fonts profile: com.google.fonts/check/metadata/fontname_not_camel_cased => com.google.fonts/check/name/family_name_compliance (issue fonttools#4049)
1 parent 1141697 commit 2e5c317

File tree

5 files changed

+202
-83
lines changed

5 files changed

+202
-83
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ A more detailed list of changes is available in the corresponding milestones for
4545
- **[com.google.fonts/check/empty_glyph_on_gid1_for_colrv0]:** Ensure that GID 1 is empty to work around Windows 10 rendering bug ([gftools issue #609](https://github.com/googlefonts/gftools/issues/609))
4646
- **[com.google.fonts/check/metadata/valid_nameid25]:** Check Name ID 25 for VF Italics (issue #3024)
4747
- **[com.google.fonts/check/metadata/consistent_repo_urls]:** Check URL on copyright string is the same as in repository_url field. (issue #4056)
48+
- **[com.google.fonts/check/name/family_name_compliance]:** Expanded and revised version of `metadata/fontname_not_camel_cased` check (issue #4049)
49+
50+
### Renamed check IDs
51+
#### On Google Fonts profile
52+
- **[com.google.fonts/check/metadata/fontname_not_camel_cased]** => com.google.fonts/check/name/family_name_compliance
4853

4954
### Deprecated checks
5055
#### Removed from the Open Type and Adobe Profiles
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# In general, we would not like to have abbreviations
2+
# font family names in the Google Fonts collection
3+
# but there are a few exceptions to that rule,
4+
# that we keep listed here, for now.
5+
6+
SIL
7+
PT
8+
IBM
9+
STIX
10+
VT

Lib/fontbakery/profiles/googlefonts.py

+109-28
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
'com.google.fonts/check/metadata/filenames',
7575
'com.google.fonts/check/metadata/italic_style',
7676
'com.google.fonts/check/metadata/normal_style',
77-
'com.google.fonts/check/metadata/fontname_not_camel_cased',
7877
'com.google.fonts/check/metadata/match_name_familyname',
7978
'com.google.fonts/check/metadata/canonical_weight_value',
8079
'com.google.fonts/check/metadata/os2_weightclass',
@@ -120,7 +119,8 @@
120119
'com.google.fonts/check/name/license_url',
121120
'com.google.fonts/check/name/family_and_style_max_length',
122121
'com.google.fonts/check/name/line_breaks',
123-
'com.google.fonts/check/name/rfn'
122+
'com.google.fonts/check/name/rfn',
123+
'com.google.fonts/check/name/family_name_compliance',
124124
]
125125

126126

@@ -2755,32 +2755,6 @@ def com_google_fonts_check_metadata_nameid_family_and_full_names(ttFont, font_me
27552755
" match corresponding name table entries.")
27562756

27572757

2758-
@check(
2759-
id = 'com.google.fonts/check/metadata/fontname_not_camel_cased',
2760-
rationale = """
2761-
We currently have a policy of avoiding camel-cased font family names other
2762-
than in a very small set of exceptions.
2763-
2764-
If you want to have your family name added to the exceptions list, please read
2765-
the instructions at https://github.com/googlefonts/fontbakery/issues/3270
2766-
""",
2767-
conditions = ['font_metadata',
2768-
'not camelcased_familyname_exception'],
2769-
proposal = 'legacy:check/109'
2770-
)
2771-
def com_google_fonts_check_metadata_fontname_not_camel_cased(font_metadata):
2772-
"""METADATA.pb: Check if fontname is not camel cased."""
2773-
import re
2774-
if bool(re.match(r'([A-Z][a-z]+){2,}', font_metadata.name)):
2775-
yield FAIL,\
2776-
Message("camelcase",
2777-
f'METADATA.pb: "{font_metadata.name}" is a CamelCased name.'
2778-
f' To solve this, simply use spaces'
2779-
f' instead in the font name.')
2780-
else:
2781-
yield PASS, "Font name is not camel-cased."
2782-
2783-
27842758
@check(
27852759
id = 'com.google.fonts/check/metadata/match_name_familyname',
27862760
conditions = ['family_metadata', # that's the family-wide metadata!
@@ -4377,6 +4351,113 @@ def com_google_fonts_check_name_rfn(ttFont, familyname):
43774351
yield PASS, 'None of the name table strings contain "Reserved Font Name".'
43784352

43794353

4354+
@check(
4355+
id = 'com.google.fonts/check/name/family_name_compliance',
4356+
rationale = """
4357+
Checks the family name for compliance with the Google Fonts Guide.
4358+
https://googlefonts.github.io/gf-guide/onboarding.html#new-fonts
4359+
4360+
If you want to have your family name added to the CamelCase
4361+
exceptions list, please submit a pull request to the
4362+
camelcased_familyname_exceptions.txt file.
4363+
4364+
Similarly, abbreviations can be submitted to the
4365+
abbreviations_familyname_exceptions.txt file.
4366+
4367+
These are located in the Lib/fontbakery/data/googlefonts/ directory
4368+
of the FontBakery source code currently hosted at
4369+
https://github.com/googlefonts/fontbakery/
4370+
""",
4371+
conditions = [],
4372+
proposal = "https://github.com/googlefonts/fontbakery/issues/4049"
4373+
)
4374+
def com_google_fonts_check_name_family_name_compliance(ttFont):
4375+
"""Check family name for GF Guide compliance."""
4376+
import re
4377+
from pkg_resources import resource_filename
4378+
from fontbakery.utils import get_name_entries
4379+
camelcase_exceptions_txt = 'data/googlefonts/camelcased_familyname_exceptions.txt'
4380+
abbreviations_exceptions_txt = 'data/googlefonts/abbreviations_familyname_exceptions.txt'
4381+
passed = True
4382+
4383+
if get_name_entries(ttFont, NameID.TYPOGRAPHIC_FAMILY_NAME):
4384+
family_name = get_name_entries(ttFont, NameID.TYPOGRAPHIC_FAMILY_NAME)[0].toUnicode()
4385+
else:
4386+
family_name = get_name_entries(ttFont, NameID.FONT_FAMILY_NAME)[0].toUnicode()
4387+
4388+
# CamelCase
4389+
if bool(re.match(r'([A-Z][a-z]+){2,}', family_name)):
4390+
known_exception = False
4391+
4392+
# Process exceptions
4393+
filename = resource_filename('fontbakery', camelcase_exceptions_txt)
4394+
for exception in open(filename, "r").readlines():
4395+
exception = exception.split('#')[0].strip()
4396+
if exception == "":
4397+
continue
4398+
if exception in family_name:
4399+
known_exception = True
4400+
yield PASS,\
4401+
Message("known-camelcase-exception",
4402+
"Family name is a known exception"
4403+
" to the CamelCase rule.")
4404+
break
4405+
4406+
if not known_exception:
4407+
passed = False
4408+
yield FAIL,\
4409+
Message("camelcase",
4410+
f'"{family_name}" is a CamelCased name.'
4411+
f' To solve this, simply use spaces'
4412+
f' instead in the font name.')
4413+
4414+
# Abbreviations
4415+
if bool(re.match(r'([A-Z]){2,}', family_name)):
4416+
known_exception = False
4417+
4418+
# Process exceptions
4419+
filename = resource_filename('fontbakery', abbreviations_exceptions_txt)
4420+
for exception in open(filename, "r").readlines():
4421+
exception = exception.split('#')[0].strip()
4422+
if exception == "":
4423+
continue
4424+
if exception in family_name:
4425+
known_exception = True
4426+
yield PASS,\
4427+
Message("known-abbreviation-exception",
4428+
"Family name is a known exception"
4429+
" to the abbreviation rule.")
4430+
break
4431+
4432+
if not known_exception:
4433+
# Allow SC ending
4434+
if not family_name.endswith("SC"):
4435+
passed = False
4436+
yield FAIL,\
4437+
Message("abbreviation",
4438+
f'"{family_name}" contains an abbreviation.')
4439+
4440+
# Allowed characters
4441+
forbidden_characters = re.findall(r'[^a-zA-Z0-9 ]', family_name)
4442+
if forbidden_characters:
4443+
forbidden_characters = "".join(sorted(list(set(forbidden_characters))))
4444+
passed = False
4445+
yield FAIL,\
4446+
Message("forbidden-characters",
4447+
f'"{family_name}" contains the following characters'
4448+
f' which are not allowed: "{forbidden_characters}".')
4449+
4450+
# Starts with uppercase
4451+
if not bool(re.match(r'^[A-Z]', family_name)):
4452+
passed = False
4453+
yield FAIL,\
4454+
Message("starts-with-not-uppercase",
4455+
f'"{family_name}" doesn\'t start with an uppercase letter.')
4456+
4457+
if passed:
4458+
yield PASS, "Font name looks good."
4459+
4460+
43804461
@check(
43814462
id = 'com.google.fonts/check/family/control_chars',
43824463
conditions = ['are_ttf'],

Lib/fontbakery/profiles/googlefonts_conditions.py

-23
Original file line numberDiff line numberDiff line change
@@ -426,29 +426,6 @@ def font_familyname(font_familynames):
426426
return font_familynames[0]
427427

428428

429-
# TODO: Design special case handling mechanism:
430-
# https://github.com/googlefonts/fontbakery/issues/1540
431-
# TODO: Improve camelcase check result explanation and whitelisting process:
432-
#https://github.com/googlefonts/fontbakery/issues/3270
433-
@condition
434-
def camelcased_familyname_exception(familyname):
435-
'''In general, we would not like to have camel-cased
436-
familynames but there are a few exceptions to that
437-
rule, that we keep listed here, for now.
438-
'''
439-
from pkg_resources import resource_filename
440-
441-
camelcase_exceptions_txt = 'data/googlefonts/camelcased_familyname_exceptions.txt'
442-
filename = resource_filename('fontbakery', camelcase_exceptions_txt)
443-
for exception in open(filename, "r").readlines():
444-
exception = exception.split('#')[0].strip()
445-
if exception == "":
446-
continue
447-
448-
if exception in familyname:
449-
return True
450-
451-
452429
@condition
453430
def rfn_exception(familyname):
454431
'''These are the very few font families where we accept usage of

tests/profiles/googlefonts_test.py

+78-32
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,84 @@ def test_check_name_rfn():
491491
assert "(FooBar)" in msg
492492

493493

494+
def test_check_name_family_name_compliance():
495+
"""Check family name for GF Guide compliance."""
496+
check = CheckTester(googlefonts_profile,
497+
"com.google.fonts/check/name/family_name_compliance")
498+
499+
def set_name(font, nameID, string):
500+
for record in font["name"].names:
501+
if record.nameID == nameID:
502+
old_string = record.toUnicode()
503+
if string != old_string:
504+
font["name"].setName(
505+
string,
506+
record.nameID,
507+
record.platformID,
508+
record.platEncID,
509+
record.langID,
510+
)
511+
512+
# CAMEL CASE
513+
ttFont = TTFont(TEST_FILE("cabin/Cabin-Regular.ttf"))
514+
assert_PASS(check(ttFont),
515+
'with a good font...')
516+
517+
# FAIL with a CamelCased name:
518+
set_name(ttFont, 1, "GollyGhost")
519+
assert_results_contain(check(ttFont),
520+
FAIL, 'camelcase',
521+
'with a bad font name (CamelCased)...')
522+
set_name(ttFont, 1, "KonKhmer_SleokChher")
523+
assert_results_contain(check(ttFont),
524+
FAIL, 'camelcase',
525+
'with a bad font name (CamelCased)...')
526+
527+
# PASS with a known CamelCased exception:
528+
set_name(ttFont, 1, "KoHo")
529+
assert_results_contain(check(ttFont),
530+
PASS, 'known-camelcase-exception',
531+
'with a bad font name (CamelCased)...')
532+
533+
# ABBREVIATIONS
534+
set_name(ttFont, 1, "DTL Prokyon")
535+
assert_results_contain(check(ttFont),
536+
FAIL, 'abbreviation',
537+
'with a bad font name')
538+
set_name(ttFont, 1, "PT Sans")
539+
assert_results_contain(check(ttFont),
540+
PASS, 'known-abbreviation-exception',
541+
'with a bad font name')
542+
# Allow SC ending
543+
set_name(ttFont, 1, "Amatic SC")
544+
assert_PASS(check(ttFont),
545+
'with a good font...')
546+
547+
# FORBIDDEN CHARACTERS
548+
set_name(ttFont, 1, "KonKhmer_SleokChher")
549+
message = assert_results_contain(check(ttFont),
550+
FAIL, 'forbidden-characters',
551+
'with a bad font name')
552+
assert message == '"KonKhmer_SleokChher" contains the following characters which are not allowed: "_".'
553+
set_name(ttFont, 1, "Kon*Khmer_Sleok-Chher")
554+
message = assert_results_contain(check(ttFont),
555+
FAIL, 'forbidden-characters',
556+
'with a bad font name')
557+
assert message == '"Kon*Khmer_Sleok-Chher" contains the following characters which are not allowed: "*-_".'
558+
559+
# STARTS WITH UPPERCASE
560+
set_name(ttFont, 1, "cabin")
561+
message = assert_results_contain(check(ttFont),
562+
FAIL, 'starts-with-not-uppercase',
563+
'with a bad font name')
564+
565+
566+
# # And we also make sure the check PASSes with a few known good names:
567+
set_name(ttFont, 1, "VT323")
568+
assert_PASS(check(ttFont),
569+
'with a good font...')
570+
571+
494572
def test_check_metadata_parses():
495573
""" Check METADATA.pb parse correctly. """
496574
check = CheckTester(googlefonts_profile,
@@ -2053,38 +2131,6 @@ def test_check_metadata_nameid_family_and_full_names():
20532131
ttFont['name'].names[i].string = backup
20542132

20552133

2056-
def test_check_metadata_fontname_not_camel_cased():
2057-
""" METADATA.pb: Check if fontname is not camel cased. """
2058-
check = CheckTester(googlefonts_profile,
2059-
"com.google.fonts/check/metadata/fontname_not_camel_cased")
2060-
2061-
# Our reference Cabin Regular is known to be good
2062-
font = TEST_FILE("cabin/Cabin-Regular.ttf")
2063-
assert_PASS(check(font),
2064-
'with a good font...')
2065-
2066-
# Then we FAIL with a CamelCased name:
2067-
md = check["font_metadata"]
2068-
md.name = "GollyGhost"
2069-
assert_results_contain(check(font, {"font_metadata": md}),
2070-
FAIL, 'camelcase',
2071-
'with a bad font name (CamelCased)...')
2072-
2073-
# Real-world case:
2074-
md.name = "KonKhmer_SleokChher"
2075-
assert_results_contain(check(font, {"font_metadata": md}),
2076-
FAIL, 'camelcase',
2077-
'with a bad font name (CamelCased)...')
2078-
2079-
# And we also make sure the check PASSes with a few known good names:
2080-
for good_name in ["VT323",
2081-
"PT Sans",
2082-
"Amatic SC"]:
2083-
md.name = good_name
2084-
assert_PASS(check(font, {"font_metadata": md}),
2085-
f'with a good font name "{good_name}"...')
2086-
2087-
20882134
def test_check_metadata_match_name_familyname():
20892135
""" METADATA.pb: Check font name is the same as family name. """
20902136
check = CheckTester(googlefonts_profile,

0 commit comments

Comments
 (0)