Skip to content

Commit

Permalink
split out typenetwork checks into individual files
Browse files Browse the repository at this point in the history
  • Loading branch information
felipesanches committed Nov 22, 2024
1 parent 2a5225f commit 9f49523
Show file tree
Hide file tree
Showing 21 changed files with 1,328 additions and 1,320 deletions.
1,320 changes: 0 additions & 1,320 deletions Lib/fontbakery/checks/vendorspecific/typenetwork.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from fontbakery.prelude import check, Message, PASS, WARN
from fontbakery.utils import bullet_list


def in_PUA_range(codepoint):
"""
Three private use areas are defined:
one in the Basic Multilingual Plane (U+E000–U+F8FF),
and one each in, and nearly covering, planes 15 and 16
(U+F0000–U+FFFFD, U+100000–U+10FFFD).
"""
return (
(codepoint >= 0xE000 and codepoint <= 0xF8FF)
or (codepoint >= 0xF0000 and codepoint <= 0xFFFFD)
or (codepoint >= 0x100000 and codepoint <= 0x10FFFD)
)


@check(
id="typenetwork/PUA_encoded_glyphs",
rationale="""
Using Private Use Area (PUA) encodings is not recommended. They are
defined by users and are not standardized. That said, PUA are font
specific so they will break if the user tries to copy/paste,
search/replace, or change the font. Using PUA to encode small caps,
for example, is not recommended as small caps can and should be
accessible via Open Type substitution instead.
If you must encode your characters in the Private Use Area (PUA),
do so with great caution.
""",
proposal=["https://github.com/fonttools/fontbakery/pull/4260"],
)
def check_PUA_encoded_glyphs(ttFont, config):
"""Check if font has PUA encoded glyphs."""

pua_encoded_glyphs = []

for cp, glyphName in ttFont.getBestCmap().items():
if in_PUA_range(cp) and cp != 0xF8FF:
pua_encoded_glyphs.append(glyphName + f" U+{cp:02x}".upper())

if pua_encoded_glyphs:
yield WARN, Message(
"pua-encoded",
f"Glyphs with PUA codepoints:\n\n"
f"{bullet_list(config, pua_encoded_glyphs)}",
)
else:
yield PASS, "No PUA encoded glyphs."
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import string

from fontbakery.prelude import check, Message, PASS, WARN


@check(
id="typenetwork/composite_glyphs",
rationale="""
For performance reasons, it is recommended that TTF fonts use composite glyphs.
""",
conditions=["is_ttf"],
proposal=["https://github.com/fonttools/fontbakery/pull/4260"],
)
def check_composite_glyphs(ttFont):
"""Check if TTF font uses composite glyphs."""
baseGlyphs = [*string.printable]
failed = []

numberOfGlyphs = ttFont["maxp"].numGlyphs
for glyph_name in ttFont["glyf"].keys():
glyph = ttFont["glyf"][glyph_name]
if glyph_name not in baseGlyphs and glyph.isComposite() is False:
failed.append(glyph_name)

percentageOfNotCompositeGlyphs = round(len(failed) * 100 / numberOfGlyphs)
if percentageOfNotCompositeGlyphs > 50:
yield WARN, Message(
"low-composites",
f"{percentageOfNotCompositeGlyphs}% of the glyphs are not composites.",
)
else:
yield PASS, (
f"{100-percentageOfNotCompositeGlyphs}% of the glyphs are composites."
)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from fontbakery.prelude import check, Message, PASS, FAIL
from fontbakery.constants import (
PlatformID,
WindowsEncodingID,
WindowsLanguageID,
)


@check(
id="typenetwork/family/duplicated_names",
rationale="""
Having duplicated name records can produce several issues like not all fonts
being listed on design apps or incorrect automatic creation of CSS classes
and @font-face rules.
""",
proposal=[
"https://github.com/fonttools/fontbakery/pull/4260",
# "https://github.com/TypeNetwork/fontQA/issues/25", # Currently private repo.
],
)
def check_family_duplicated_names(ttFonts):
"""Check if font doesn’t have duplicated names within a family."""
duplicate_subfamilyNames = set()
seen_fullNames = set()
duplicate_fullNames = set()
seen_postscriptNames = set()
duplicate_postscriptNames = set()

PLAT_ID = PlatformID.WINDOWS
ENC_ID = WindowsEncodingID.UNICODE_BMP
LANG_ID = WindowsLanguageID.ENGLISH_USA

for ttFont in list(ttFonts):
# # Subfamily name
# if ttFont["name"].getName(17, PLAT_ID, ENC_ID, LANG_ID):
# subfamName = ttFont["name"].getName(17, PLAT_ID, ENC_ID, LANG_ID)
# else:
# subfamName = ttFont["name"].getName(2, PLAT_ID, ENC_ID, LANG_ID)

# if subfamName:
# subfamName = subfamName.toUnicode()
# if subfamName in seen_subfamilyNames:
# duplicate_subfamilyNames.add(subfamName)
# else:
# seen_subfamilyNames.add(subfamName)

# FullName name
fullName = ttFont["name"].getName(4, PLAT_ID, ENC_ID, LANG_ID)

if fullName:
fullName = fullName.toUnicode()
if fullName in seen_fullNames:
duplicate_fullNames.add(fullName)
else:
seen_fullNames.add(fullName)

# Postscript name
postscriptName = ttFont["name"].getName(6, PLAT_ID, ENC_ID, LANG_ID)
if postscriptName:
postscriptName = postscriptName.toUnicode()
if postscriptName in seen_postscriptNames:
duplicate_subfamilyNames.add(postscriptName)
else:
seen_postscriptNames.add(postscriptName)

# if duplicate_subfamilyNames:
# duplicate_subfamilyNamesString = \
# "".join(f"* {inst}\n" for inst in sorted(duplicate_subfamilyNames))
# yield FAIL, Message(
# "duplicate-subfamily-names",
# "Following subfamily names are duplicate:\n\n"
# f"{duplicate_subfamilyNamesString}",
# )

if duplicate_fullNames:
duplicate_fullNamesString = "".join(
f"* {inst}\n" for inst in sorted(duplicate_fullNames)
)
yield FAIL, Message(
"duplicate-full-names",
"Following full names are duplicate:\n\n" f"{duplicate_fullNamesString}",
)

if duplicate_postscriptNames:
duplicate_postscriptNamesString = "".join(
f"* {inst}\n" for inst in sorted(duplicate_postscriptNames)
)
yield FAIL, Message(
"duplicate-postscript-names",
"Following postscript names are duplicate:\n\n"
f"{duplicate_postscriptNamesString}",
)

if not duplicate_fullNames and not duplicate_postscriptNames:
yield PASS, "All names are unique"
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from fontbakery.testable import CheckRunContext
from fontbakery.prelude import check, condition, Message, PASS, WARN


@condition(CheckRunContext)
def roman_ttFonts(context):
return [font.ttFont for font in context.fonts if not font.is_italic]


@condition(CheckRunContext)
def italic_ttFonts(context):
return [font.ttFont for font in context.fonts if font.is_italic]


@check(
id="typenetwork/family/equal_numbers_of_glyphs",
rationale="""
Check if all fonts in a family have the same number of glyphs.
""",
conditions=["roman_ttFonts", "italic_ttFonts"],
proposal=["https://github.com/fonttools/fontbakery/pull/4260"],
)
def equal_numbers_of_glyphs(roman_ttFonts, italic_ttFonts):
"""Equal number of glyphs"""
max_roman_count = 0
max_roman_font = None
roman_failed_fonts = {}

# Checks roman
for ttFont in list(roman_ttFonts):
fontname = ttFont.reader.file.name
this_count = ttFont["maxp"].numGlyphs
if this_count > max_roman_count:
max_roman_count = this_count
max_roman_font = fontname

for ttFont in list(roman_ttFonts):
this_count = ttFont["maxp"].numGlyphs
fontname = ttFont.reader.file.name
if this_count != max_roman_count:
roman_failed_fonts[fontname] = this_count

max_italic_count = 0
max_italic_font = None
italic_failed_fonts = {}

# Checks Italics
for ttFont in list(italic_ttFonts):
fontname = ttFont.reader.file.name
this_count = ttFont["maxp"].numGlyphs
if this_count > max_italic_count:
max_italic_count = this_count
max_italic_font = fontname

for ttFont in list(italic_ttFonts):
this_count = ttFont["maxp"].numGlyphs
fontname = ttFont.reader.file.name
if this_count != max_italic_count:
italic_failed_fonts[fontname] = this_count

if len(roman_failed_fonts) > 0:
yield WARN, Message(
"roman-different-number-of-glyphs",
f"Romans doesn’t have the same number of glyphs"
f"{max_roman_font} has {max_roman_count} and \n\t{roman_failed_fonts}",
)
else:
yield PASS, (
"All roman files in this family have an equal total ammount of glyphs."
)

if len(italic_failed_fonts) > 0:
yield WARN, Message(
"italic-different-number-of-glyphs",
f"Italics doesn’t have the same number of glyphs"
f"{max_italic_font} has {max_italic_count} and \n\t{italic_failed_fonts}",
)
else:
yield PASS, (
"All italics files in this family have an equal total ammount of glyphs."
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from fontbakery.prelude import check, Message, PASS, WARN, INFO
from fontbakery.utils import (
bullet_list,
pretty_print_list,
)


@check(
id="typenetwork/family/tnum_horizontal_metrics",
rationale="""
Tabular figures need to have the same metrics in all styles in order to allow
tables to be set with proper typographic control, but to maintain the placement
of decimals and numeric columns between rows.
""",
proposal=["https://github.com/fonttools/fontbakery/pull/4260"],
)
def check_family_tnum_horizontal_metrics(ttFonts, config):
"""All tabular figures must have the same width across the family."""
tnum_widths = {}
half_width_glyphs = {}
for ttFont in list(ttFonts):
glyphs = ttFont.getGlyphSet()

tabular_suffixes = (".tnum", ".tf", ".tosf", ".tsc", ".tab", ".tabular")
tnum_glyphs = [
(glyph_id, glyphs[glyph_id])
for glyph_id in glyphs.keys()
if any(suffix in glyph_id for suffix in tabular_suffixes)
]

for glyph_id, glyph in tnum_glyphs:
if glyph.width not in tnum_widths:
tnum_widths[glyph.width] = [glyph_id]
else:
tnum_widths[glyph.width].append(glyph_id)

max_num = 0
most_common_width = None
half_width = None

# Get most common width
for width, glyphs in tnum_widths.items():
if len(glyphs) > max_num:
max_num = len(glyphs)
most_common_width = width
if most_common_width:
del tnum_widths[most_common_width]

# Get Half width
for width, glyphs in tnum_widths.items():
if round(most_common_width / 2) == width:
half_width = width
half_width_glyphs = glyphs

if half_width:
del tnum_widths[half_width]

if half_width:
yield INFO, Message(
"half-widths",
f"The are other glyphs with half of the width ({half_width}) of the"
f" most common width such as the following ones:\n\n"
f"{bullet_list(config, half_width_glyphs)}.",
)

if len(tnum_widths.keys()):
# prepare string to display
tnumWidthsString = ""
for width, glyphs in tnum_widths.items():
tnumWidthsString += f"{width}: {pretty_print_list(config, glyphs)}\n\n"
yield WARN, Message(
"inconsistent-widths",
f"The most common tabular glyph width is {most_common_width}."
f" But there are other tabular glyphs with different widths"
f" such as the following ones:\n\n{tnumWidthsString}.",
)
else:
yield PASS, "OK"
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fontbakery.prelude import check, Message, FAIL


@check(
id="typenetwork/family/valid_strikeout",
rationale="""
If strikeout size is not set, nothing gets rendered on Figma.
""",
proposal=["https://github.com/fonttools/fontbakery/pull/4260"],
misc_metadata={"affects": [("Figma", "unspecified")]},
)
def check_family_valid_strikeout(ttFont):
"""Font has a value strikeout size?"""

strikeoutSize = ttFont["OS/2"].yStrikeoutSize
if strikeoutSize is None or strikeoutSize == 0:
yield FAIL, Message(
"invalid-strikeout-size",
f"Size of the strikeout is {strikeoutSize} which is not valid.",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fontbakery.prelude import check, Message, FAIL


@check(
id="typenetwork/family/valid_underline",
rationale="""
If underline thickness is not set nothing gets rendered on Figma.
""",
proposal=["https://github.com/fonttools/fontbakery/pull/4260"],
misc_metadata={"affects": [("Figma", "unspecified")]},
)
def check_family_valid_underline(ttFont):
"""Font has a valid underline thickness?"""

underlineThickness = ttFont["post"].underlineThickness
if underlineThickness is None or underlineThickness == 0:
yield FAIL, Message(
"invalid-underline-thickness",
f"Thickness of the underline is {underlineThickness} which is not valid.",
)
Loading

0 comments on commit 9f49523

Please sign in to comment.