diff options
Diffstat (limited to 'src/silfont/fbtests/silttfchecks.py')
-rw-r--r-- | src/silfont/fbtests/silttfchecks.py | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/src/silfont/fbtests/silttfchecks.py b/src/silfont/fbtests/silttfchecks.py new file mode 100644 index 0000000..7be0ed5 --- /dev/null +++ b/src/silfont/fbtests/silttfchecks.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +'''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__ = 'https://github.com/silnrsi/pysilfont' +__copyright__ = 'Copyright (c) 2022 SIL International (https://www.sil.org)' +__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)' +__author__ = 'David Raymond' + +from fontbakery.status import 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" |