summaryrefslogtreecommitdiffstats
path: root/lib/silfont/fbtests/silttfchecks.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/silfont/fbtests/silttfchecks.py')
-rw-r--r--lib/silfont/fbtests/silttfchecks.py250
1 files changed, 250 insertions, 0 deletions
diff --git a/lib/silfont/fbtests/silttfchecks.py b/lib/silfont/fbtests/silttfchecks.py
new file mode 100644
index 0000000..d38ab77
--- /dev/null
+++ b/lib/silfont/fbtests/silttfchecks.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python
+'''Checks to be imported by ttfchecks.py
+Some checks based on examples from Font Bakery, copyright 2017 The Font Bakery Authors, licensed under the Apache 2.0 license'''
+__url__ = 'http://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2022 SIL International (http://www.sil.org)'
+__license__ = 'Released under the MIT License (http://opensource.org/licenses/MIT)'
+__author__ = 'David Raymond'
+
+from fontbakery.checkrunner import Section, PASS, FAIL, WARN, ERROR, INFO, SKIP
+from fontbakery.callable import condition, check, disable
+from fontbakery.message import Message
+from fontbakery.constants import NameID, PlatformID, WindowsEncodingID
+
+@check(
+ id = 'org.sil/check/name/version_format',
+ rationale = """
+ Based on com.google.fonts/check/name/version_format but:
+ - Checks for two valid formats:
+ - Production: exactly 3 digits after decimal point
+
+
+ - Allows major version to be 0
+ - Allows extra info after numbers, eg for beta or dev versions
+ """
+)
+def org_sil_version_format(ttFont):
+ "Version format is correct in 'name' table?"
+
+ from fontbakery.utils import get_name_entry_strings
+ import re
+
+ failed = False
+ version_entries = get_name_entry_strings(ttFont, NameID.VERSION_STRING)
+ if len(version_entries) == 0:
+ failed = True
+ yield FAIL,\
+ Message("no-version-string",
+ f"Font lacks a NameID.VERSION_STRING"
+ f" (nameID={NameID.VERSION_STRING}) entry")
+
+ for ventry in version_entries:
+ if not re.match(r'Version [0-9]+\.\d{3}( .+)*$', ventry):
+ failed = True
+ yield FAIL,\
+ Message("bad-version-strings",
+ f'The NameID.VERSION_STRING'
+ f' (nameID={NameID.VERSION_STRING}) value must'
+ f' follow the pattern "Version X.nnn devstring" with X.nnn'
+ f' greater than or equal to 0.000.'
+ f' Current version string is: "{ventry}"')
+ if not failed:
+ yield PASS, "Version format in NAME table entries is correct."
+
+@check(
+ id = 'org.sil/check/whitespace_widths'
+)
+def org_sil_whitespace_widths(ttFont):
+ """Checks with widths of space characters in the font against best practice"""
+ from fontbakery.utils import get_glyph_name
+
+ allok = True
+ space_data = {
+ 0x0020: ['Space'],
+ 0x00A0: ['No-break space'],
+ 0x2008: ['Punctuation space'],
+ 0x2003: ['Em space'],
+ 0x2002: ['En space'],
+ 0x2000: ['En quad'],
+ 0x2001: ['Em quad'],
+ 0x2004: ['Three-per-em space'],
+ 0x2005: ['Four-per-em space'],
+ 0x2006: ['Six-per-em space'],
+ 0x2009: ['Thin space'],
+ 0x200A: ['Hair space'],
+ 0x202F: ['Narrow no-break space'],
+ 0x002E: ['Full stop'], # Non-space character where the width is needed for comparison
+ }
+ for sp in space_data:
+ spname = get_glyph_name(ttFont, sp)
+ if spname is None:
+ spwidth = None
+ else:
+ spwidth = ttFont['hmtx'][spname][0]
+ space_data[sp].append(spname)
+ space_data[sp].append(spwidth)
+
+ # Other width info needed from the font
+ upm = ttFont['head'].unitsPerEm
+ fullstopw = space_data[46][2]
+
+ # Widths used for comparisons
+ spw = space_data[32][2]
+ if spw is None:
+ allok = False
+ yield WARN, "No space in the font so No-break space (if present) can't be checked"
+ emw = space_data[0x2003][2]
+ if emw is None:
+ allok = False
+ yield WARN, f'No em space in the font. Will be assumed to be units per em ({upm}) for other checking'
+ emw = upm
+ enw = space_data[0x2002][2]
+ if enw is None:
+ allok = False
+ yield WARN, f'No en space in the font. Will be assumed to be 1/2 em space width ({emw/2}) for checking en quad (if present)'
+ enw = emw/2
+
+ # Now check all the specific space widths. Only check if the space exists in the font
+ def checkspace(spacechar, minwidth, maxwidth=None):
+ sdata = space_data[spacechar]
+ if sdata[1]: # Name is set to None if not in font
+ # Allow for width(s) not being integer (eg em/6) so test against rounding up or down
+ minw = int(minwidth)
+ if maxwidth:
+ maxw = int(maxwidth)
+ if maxwidth > maxw: maxw += 1 # Had been rounded down, so round up
+ else:
+ maxw = minw if minw == minwidth else minw +1 # Had been rounded down, so allow rounded up as well
+ charw = sdata[2]
+ if not(minw <= charw <= maxw):
+ return (f'Width of {sdata[0]} ({spacechar:#04x}) is {str(charw)}: ', minw, maxw)
+ return (None,0,0)
+
+ # No-break space
+ (message, minw, maxw) = checkspace(0x00A0, spw)
+ if message: allok = False; yield FAIL, message + f"Should match width of space ({spw})"
+ # Punctuation space
+ (message, minw, maxw) = checkspace(0x2008, fullstopw)
+ if message: allok = False; yield FAIL, message + f"Should match width of full stop ({fullstopw})"
+ # Em space
+ (message, minw, maxw) = checkspace(0x2003, upm)
+ if message: allok = False; yield WARN, message + f"Should match units per em ({upm})"
+ # En space
+ (message, minw, maxw) = checkspace(0x2002, emw/2)
+ if message:
+ allok = False
+ widths = f'{minw}' if minw == maxw else f'{minw} or {maxw}'
+ yield WARN, message + f"Should be half the width of em ({widths})"
+ # En quad
+ (message, minw, maxw) = checkspace(0x2000, enw)
+ if message: allok = False; yield WARN, message + f"Should be the same width as en ({enw})"
+ # Em quad
+ (message, minw, maxw) = checkspace(0x2001, emw)
+ if message: allok = False; yield WARN, message + f"Should be the same width as em ({emw})"
+ # Three-per-em space
+ (message, minw, maxw) = checkspace(0x2004, emw/3)
+ if message:
+ allok = False
+ widths = f'{minw}' if minw == maxw else f'{minw} or {maxw}'
+ yield WARN, message + f"Should be 1/3 the width of em ({widths})"
+ # Four-per-em space
+ (message, minw, maxw) = checkspace(0x2005, emw/4)
+ if message:
+ allok = False
+ widths = f'{minw}' if minw == maxw else f'{minw} or {maxw}'
+ yield WARN, message + f"Should be 1/4 the width of em ({widths})",
+ # Six-per-em space
+ (message, minw, maxw) = checkspace(0x2006, emw/6)
+ if message:
+ allok = False
+ widths = f'{minw}' if minw == maxw else f'{minw} or {maxw}'
+ yield WARN, message + f"Should be 1/6 the width of em ({widths})",
+ # Thin space
+ (message, minw, maxw) = checkspace(0x2009, emw/6, emw/5)
+ if message:
+ allok = False
+ yield WARN, message + f"Should be between 1/6 and 1/5 the width of em ({minw} and {maxw})"
+ # Hair space
+ (message, minw, maxw) = checkspace(0x200A,
+ emw/16, emw/10)
+ if message:
+ allok = False
+ yield WARN, message + f"Should be between 1/16 and 1/10 the width of em ({minw} and {maxw})"
+ # Narrow no-break space
+ (message, minw, maxw) = checkspace(0x202F,
+ emw/6, emw/5)
+ if message:
+ allok = False
+ yield WARN, message + f"Should be between 1/6 and 1/5 the width of em ({minw} and {maxw})"
+
+ if allok:
+ yield PASS, "Space widths all match expected values"
+
+@check(
+ id = 'org.sil/check/number_widths'
+)
+def org_sil_number_widths(ttFont, config):
+ """Check widths of latin digits 0-9 are equal and match that of figure space"""
+ from fontbakery.utils import get_glyph_name
+
+ num_data = {
+ 0x0030: ['zero'],
+ 0x0031: ['one'],
+ 0x0032: ['two'],
+ 0x0033: ['three'],
+ 0x0034: ['four'],
+ 0x0035: ['five'],
+ 0x0036: ['six'],
+ 0x0037: ['seven'],
+ 0x0038: ['eight'],
+ 0x0039: ['nine'],
+ 0x2007: ['figurespace'] # Figure space should be the same as numerals
+ }
+
+ fontnames = []
+ for x in (ttFont['name'].names[1].string, ttFont['name'].names[2].string):
+ txt=""
+ for i in range(1,len(x),2): txt += x.decode()[i]
+ fontnames.append(txt)
+
+ for num in num_data:
+ name = get_glyph_name(ttFont, num)
+ if name is None:
+ width = -1 # So different from Zero!
+ else:
+ width = ttFont['hmtx'][name][0]
+ num_data[num].append(name)
+ num_data[num].append(width)
+
+ zerowidth = num_data[48][2]
+ if zerowidth ==-1:
+ yield FAIL, "No zero in font - remainder of check not run"
+ return
+
+ # Check non-zero digits are present and have same width as zero
+ digitsdiff = ""
+ digitsmissing = ""
+ for i in range(49,58):
+ ndata = num_data[i]
+ width = ndata[2]
+ if width != zerowidth:
+ if width == -1:
+ digitsmissing += ndata[1] + " "
+ else:
+ digitsdiff += ndata[1] + " "
+
+ # Check figure space
+ figuremess = ""
+ ndata = num_data[0x2007]
+ width = ndata[2]
+ if width != zerowidth:
+ if width == -1:
+ figuremess = "No figure space in font"
+ else:
+ figuremess = f'The width of figure space ({ndata[1]}) does not match the width of zero'
+ if digitsmissing or digitsdiff or figuremess:
+ if digitsmissing: yield FAIL, f"Digits missing: {digitsmissing}"
+ if digitsdiff: yield WARN, f"Digits with different width from Zero: {digitsdiff}"
+ if figuremess: yield WARN, figuremess
+ else:
+ yield PASS, "All number widths are OK"