def make_profile(check_list, variable_font=False): profile = profile_factory(default_section=Section("SIL Fonts")) profile.auto_register(globals()) # Exclude all the checks we don't want to run for checkid in check_list: if checkid in profile._check_registry: check_item = check_list[checkid] exclude = check_item[ "exclude"] if "exclude" in check_item else False if exclude: profile.remove_check(checkid) # Exclude further sets of test to reduce number of skips and so have less clutter in html results # (Currently just working with variable font tests, but structured to cover more types of checks later) for checkid in sorted(set(profile._check_registry.keys())): section = profile._check_registry[checkid] check = section.get_check(checkid) conditions = getattr(check, "conditions") exclude = False if variable_font and "not is_variable_font" in conditions: exclude = True if not variable_font and "is_variable_font" in conditions: exclude = True if exclude: profile.remove_check(checkid) return profile
def _test(profile_imports, expected_tests, expected_conditions=tuple()): profile = profile_factory(default_section=Section("Testing")) profile.auto_register({}, profile_imports=profile_imports) profile.test_expected_checks(expected_tests) if expected_conditions: registered_conditions = profile.conditions.keys() for name in expected_conditions: assert name in registered_conditions, \ f'"{name}" is expected to be registered as a condition.'
def _test(spec_imports, expected_tests, expected_conditions=tuple()): specification = spec_factory(default_section=Section("Testing")) specification.auto_register({}, spec_imports=spec_imports) specification.test_expected_checks(expected_tests) if expected_conditions: registered_conditions = specification.conditions.keys() for name in expected_conditions: assert name in registered_conditions, ( '"{}" is expected to be ' 'registered as a condition.'.format(name))
def test_in_and_exclude_checks_default(): profile_imports = ("fontbakery.profiles.opentype", ) profile = profile_factory(default_section=Section("OpenType Testing")) profile.auto_register({}, profile_imports=profile_imports) profile.test_dependencies() explicit_checks = None # "All checks aboard" exclude_checks = None # "No checks left behind" iterargs = {"font": 1} check_names = { c[1].id for c in profile.execution_order(iterargs, explicit_checks=explicit_checks, exclude_checks=exclude_checks) } check_names_expected = set() for section in profile.sections: for check in section.checks: check_names_expected.add(check.id) assert check_names == check_names_expected
def all_checks_dict( ): # An ordered dict of all checks designed for exporting the data profile = profile_factory(default_section=Section("SIL Fonts")) profile.auto_register(globals()) check_dict = OrderedDict() for checkid in sorted(set(profile._check_registry.keys())): section = profile._check_registry[checkid] check = section.get_check(checkid) conditions = getattr(check, "conditions") conditionstxt = "" for condition in conditions: conditionstxt += condition + "\n" conditionstxt = conditionstxt.strip() rationale = getattr(check, "rationale") rationale = "" if rationale is None else rationale.strip().replace( "\n ", "\n") # Remove extraneous whitespace psfaction = psfcheck_list[ checkid] if checkid in psfcheck_list else "Not in psfcheck_list" item = { "psfaction": psfaction, "section": section.name, "description": getattr(check, "description"), "rationale": rationale, "conditions": conditionstxt } check_dict[checkid] = item for checkid in psfcheck_list: # Look for checks no longer in Font Bakery if checkid not in check_dict: check_dict[checkid] = { "psfaction": psfcheck_list[checkid], "section": "Missing", "description": "Check not found", "rationale": "", "conditions": "" } return check_dict
def test_external_profile(): """Test the creation of external profiles.""" profile = profile_factory(default_section=Section("Dalton Maag OpenType")) profile.auto_register(globals(), profile_imports=["fontbakery.profiles.opentype"], filter_func=check_filter) # Probe some tests expected_tests = [ "com.google.fonts/check/family/panose_proportion", "com.google.fonts/check/varfont/regular_opsz_coord" ] profile.test_expected_checks(expected_tests) # Probe tests we don't want assert "com.google.fonts/check/ftxvalidator" not in profile._check_registry.keys( ) assert len(profile.sections) > 1
def test_external_specification(): """Test the creation of external specifications.""" specification = spec_factory( default_section=Section("Dalton Maag OpenType")) specification.auto_register( globals(), spec_imports=["fontbakery.specifications.opentype"], filter_func=check_filter) # Probe some tests expected_tests = [ "com.google.fonts/check/002", "com.google.fonts/check/171" ] specification.test_expected_checks(expected_tests) # Probe tests we don't want assert "com.google.fonts/check/035" not in specification._check_registry.keys( ) assert len(specification.sections) > 1
def test_in_and_exclude_checks(): profile_imports = ("fontbakery.profiles.opentype", ) profile = profile_factory(default_section=Section("OpenType Testing")) profile.auto_register({}, profile_imports=profile_imports) profile.test_dependencies() explicit_checks = ["06", "07"] # "06" or "07" in check ID exclude_checks = ["065", "079"] # "065" or "079" in check ID iterargs = {"font": 1} check_names = { c[1].id for c in profile.execution_order(iterargs, explicit_checks=explicit_checks, exclude_checks=exclude_checks) } check_names_expected = set() for section in profile.sections: for check in section.checks: if any(i in check.id for i in explicit_checks) and not any( x in check.id for x in exclude_checks): check_names_expected.add(check.id) assert check_names == check_names_expected
import os from fontbakery.checkrunner import Section, PASS, FAIL, WARN, ERROR, INFO, SKIP from fontbakery.callable import condition, check, disable from fontbakery.constants import PriorityLevel from fontbakery.message import Message from fontbakery.fonts_profile import profile_factory from fontbakery.profiles.universal import UNIVERSAL_PROFILE_CHECKS profile_imports = ("fontbakery.profiles.universal", ) profile = profile_factory(default_section=Section("Custom Checks")) # ================================================ # # Custom check list # # ================================================ # define new custom checks that are implemented in # this source file by check ID. Format these as a # Python list. # The check ID here is an example that demonstrates # the example pass check in this module. It is safe # to remove the check ID when you remove the example # check from this module CUSTOM_PROFILE_CHECKS = UNIVERSAL_PROFILE_CHECKS + [ "org.example/check/valid/testpass", ] # ================================================ # # Fontbakery check exclusion list
def test_googlefonts_checks_load(): profile_imports = ("fontbakery.profiles.googlefonts", ) profile = profile_factory(default_section=Section("Google Fonts Testing")) profile.auto_register({}, profile_imports=profile_imports) profile.test_dependencies()
def test_googlefonts_checks_load(): spec_imports = ("fontbakery.specifications.googlefonts", ) specification = spec_factory( default_section=Section("Google Fonts Testing")) specification.auto_register({}, spec_imports=spec_imports) specification.test_dependencies()
import os from fontbakery.callable import check from fontbakery.checkrunner import ERROR, FAIL, INFO, PASS, WARN, Section # used to inform get_module_profile whether and how to create a profile from fontbakery.fonts_profile import profile_factory # NOQA pylint: disable=unused-import from .shared_conditions import is_variable_font profile_imports = ['.shared_conditions'] profile = profile_factory( default_section=Section("Checks inherited from Microsoft Font Validator")) @check(id='com.google.fonts/check/fontvalidator') def com_google_fonts_check_fontvalidator(font): """Checking with Microsoft Font Validator.""" # In some cases we want to override the severity level of # certain checks in FontValidator: downgrade_to_warn = [ # There are reports that this fontval check has an out-of-date # understanding of valid bits in fsSelection. # More info at: # https://github.com/googlei18n/fontmake/issues/414#issuecomment-379408127 "There are undefined bits set in fsSelection field", # FIX-ME: Why did we downgrade this one to WARN? "Misoriented contour" ] # Some other checks we want to completely disable: disabled_fval_checks = [
""" Checks for Font Bureau. """ from fontbakery.callable import check from fontbakery.checkrunner import Section, PASS, FAIL, WARN from fontbakery.fonts_profile import profile_factory from fontbakery.message import Message from fontbakery.profiles.universal import UNIVERSAL_PROFILE_CHECKS profile_imports = ('fontbakery.profiles.universal', ) profile = profile_factory(default_section=Section("Type Network")) TYPENETWORK_PROFILE_CHECKS = \ UNIVERSAL_PROFILE_CHECKS + [ 'io.github.abysstypeco/check/ytlc_sanity' ] @check(id='io.github.abysstypeco/check/ytlc_sanity', rationale=""" This check follows the proposed values of the ytlc axis proposed by font bureau at the site url. add more later. """, conditions=["is_variable_font"]) def io_github_abysstypeco_check_ytlc_sanity(ttFont): """Check if ytlc values are sane in vf""" passed = True for axis in ttFont['fvar'].axes: if not axis.axisTag == 'ytlc': continue
"os2", "post", "name", "loca", "hhea", "dsig", "hmtx", "gpos", "kern", "glyf", "fvar", "shared_conditions", ), ) profile_imports = (OPENTYPE_PROFILE_IMPORTS, ) profile = profile_factory(default_section=Section("OpenType Specification Checks")) OPENTYPE_PROFILE_CHECKS = [ 'com.google.fonts/check/family/underline_thickness', 'com.google.fonts/check/family/panose_proportion', 'com.google.fonts/check/family/panose_familytype', 'com.google.fonts/check/family/equal_unicode_encodings', 'com.google.fonts/check/family/equal_font_versions', 'com.adobe.fonts/check/family/bold_italic_unique_for_nameid1', 'com.adobe.fonts/check/family/max_4_fonts_per_family_name', 'com.adobe.fonts/check/name/postscript_vs_cff', 'com.adobe.fonts/check/name/postscript_name_consistency', 'com.adobe.fonts/check/name/empty_records', 'com.google.fonts/check/name/no_copyright_on_description', 'com.google.fonts/check/name/line_breaks', 'com.google.fonts/check/name/rfn',
fonts_expected_value = ExpectedValue( 'fonts' , default=[] , description='A list of the ufo file paths to check.' , validator=lambda fonts: (True, None) if len(fonts) \ else (False, 'Value is empty.') ) # ---------------------------------------------------------------------------- # This variable serves as an exportable anchor point, see e.g. the # Lib/fontbakery/commands/check_ufo_sources.py script. specification = UFOSpec( default_section=Section('Default'), iterargs={'font': 'fonts'}, derived_iterables={'ufo_fonts': ('ufo_font', True)}, expected_values={fonts_expected_value.name: fonts_expected_value}) register_check = specification.register_check register_condition = specification.register_condition # ---------------------------------------------------------------------------- @register_condition @condition def ufo_font(font): import defcon return defcon.Font(font)
from fontbakery.checkrunner import Section, PASS, FAIL from fontbakery.callable import check from fontbakery.fonts_profile import profile_factory profile_imports = () profile = profile_factory(default_section=Section("Test profile for Action CI")) PROFILE_CHECKS = [ "com.factions/tests/alwayspass", ] excluded_check_ids = () @check( id="com.factions/tests/alwayspass", rationale=""" A test check for CI testing of the wcys-co/fontbakery GitHub Action """, ) def com_factions_tests_alwayspass(ttFonts): """Fake test for testing purposes""" yield PASS, "This always passes so that checks themselves do not fail CI" # ================================================ # # End check definitions # # ================================================
# needed to import universal checks from fontbakery.profiles.universal import UNIVERSAL_PROFILE_CHECKS # At this point we already have a importable profile # It needs some checks though, to be useful. # profile_imports can be used to mix other profiles # into this profile. We are only using two profiles # for this example, containing checks for the accordingly # named tables # also needed to import universal checks? # profile_imports = ('fontbakery.profiles.universal',) # seems to be needed to mark this as a profile? profile = profile_factory(default_section=Section("Typotheque")) # putting this at the top of the file # can give a guick overview of checks below: TYPOTHEQUE_CHECK_IDS = [ 'com.typotheque/check/short-segments--otf_ttf', ] # the "Expected" check list can concatenate lists of checks in this profile and other imported ones EXPECTED_CHECK_IDS = \ TYPOTHEQUE_CHECK_IDS # UNIVERSAL_PROFILE_CHECKS + \ @check(id='com.typotheque/check/short-segments--otf_ttf') def short_segments_otf_ttf(ttFont):
# Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys from fontbakery.checkrunner import Section, PASS, FAIL from fontbakery.callable import check from fontbakery.fonts_profile import profile_factory profile_imports = () profile = profile_factory( default_section=Section("An always pass custom profile for testing")) ALWAYSPASS_PROFILE_CHECKS = [ "com.google.fonts/check/testing/alwayspass", ] @check( id="com.google.fonts/check/testing/alwayspass", rationale=""" This is an always passing fontbakery check for testing purposes. """, ) def com_google_fonts_check_testing_alwayspass(ttFonts): """This is an always passing fontbakery check for testing purposes.""" try:
from fontbakery.callable import check from fontbakery.callable import condition from fontbakery.checkrunner import Section, PASS, FAIL, WARN from fontbakery.fonts_profile import profile_factory from tests.test_general import ( is_italic, com_roboto_fonts_check_italic_angle, com_roboto_fonts_check_fs_type, com_roboto_fonts_check_vendorid, com_roboto_fonts_check_digit_widths, com_roboto_fonts_check_charset_coverage, ) profile = profile_factory(default_section=Section("Roboto android v3")) exclude_glyphs = frozenset([0x00A0]) ROBOTO_PROFILE_CHECKS = [ "com.roboto.fonts/check/vertical_metrics", "com.roboto.fonts/check/italic_angle", "com.roboto.fonts/check/fs_type", "com.roboto.fonts/check/vendorid", "com.roboto.fonts/check/digit_widths", "com.roboto.fonts/check/glyph_dont_round_to_grid", "com.roboto.fonts/check/charset_coverage", ] @condition def include_glyphs(): return frozenset([
from fontbakery.callable import check from fontbakery.callable import condition from fontbakery.checkrunner import Section, PASS, FAIL, WARN from fontbakery.fonts_profile import profile_factory from tests.test_general import ( font_family, font_style, is_italic, com_roboto_fonts_check_italic_angle, com_roboto_fonts_check_fs_type, com_roboto_fonts_check_vendorid, com_roboto_fonts_check_digit_widths, ) profile = profile_factory(default_section=Section("Roboto web v3")) ROBOTO_PROFILE_CHECKS = [ "com.roboto.fonts/check/vertical_metrics", "com.roboto.fonts/check/oblique_bits_not_set", "com.roboto.fonts/check/unique_id", "com.roboto.fonts/check/hinting", "com.roboto.fonts/check/italic_angle", "com.roboto.fonts/check/fs_type", "com.roboto.fonts/check/vendorid", "com.roboto.fonts/check/digit_widths", ] @check( id="com.roboto.fonts/check/vertical_metrics", ) def com_roboto_fonts_check_vertical_metrics(ttFont):
import os from fontbakery.profiles.universal import UNIVERSAL_PROFILE_CHECKS from fontbakery.checkrunner import Section, WARN, PASS #, INFO, ERROR, SKIP, FAIL from fontbakery.callable import check #, disable from fontbakery.message import Message from fontbakery.fonts_profile import profile_factory from fontbakery.constants import (PlatformID, WindowsEncodingID, UnicodeEncodingID, MacintoshEncodingID) from .googlefonts_conditions import * # pylint: disable=wildcard-import,unused-wildcard-import profile_imports = ('fontbakery.profiles.universal', ) # Maybe this should be .googlefonts instead... profile = profile_factory(default_section=Section("Noto Fonts")) CMAP_TABLE_CHECKS = [ 'com.google.fonts/check/cmap/unexpected_subtables', ] OS2_TABLE_CHECKS = [ 'com.google.fonts/check/unicode_range_bits', ] # Maybe this should be GOOGLEFONTS_PROFILE_CHECKS instead... NOTOFONTS_PROFILE_CHECKS = \ UNIVERSAL_PROFILE_CHECKS + \ CMAP_TABLE_CHECKS + \ OS2_TABLE_CHECKS @check(id='com.google.fonts/check/cmap/unexpected_subtables',
fonts_expected_value = ExpectedValue( 'fonts' , default=[] , description='A list of the ufo file paths to check.' , validator=lambda fonts: (True, None) if len(fonts) \ else (False, 'Value is empty.') ) # ---------------------------------------------------------------------------- # This variable serves as an exportable anchor point, see e.g. the # Lib/fontbakery/commands/check_ufo_sources.py script. profile = UFOProfile( default_section=Section('Default'), iterargs={'font': 'fonts'}, derived_iterables={'ufo_fonts': ('ufo_font', True)}, expected_values={fonts_expected_value.name: fonts_expected_value}) register_check = profile.register_check register_condition = profile.register_condition # ---------------------------------------------------------------------------- basic_checks = Section("Basic checks") @register_condition @condition def ufo_font(font): import defcon
# template config file for local testing of ttfs with fontbakery. from fontbakery.checkrunner import Section, PASS, FAIL, WARN, ERROR, INFO, SKIP from fontbakery.callable import condition, check, disable from fontbakery.constants import PriorityLevel from fontbakery.message import Message from fontbakery.fonts_profile import profile_factory # imports are used to mix in other external profiles profile_imports = ('fontbakery.profiles.opentype', 'fontbakery.profiles.name', 'fontbakery.profiles.head', 'fontbakery.profiles.glyf') # example of import params for SIL common profile and ABS profile directly in pysilfont # profile_imports = ['silfont.fbtests.common', 'silfont.fbtests.abs'] profile = profile_factory(default_section=Section("SIL font project")) # Our own checks below # See https://font-bakery.readthedocs.io/en/latest/developer/writing-profiles.html # putting this at the top of the file # can give a quick overview: expected_check_ids = ('org.sil.software/checks/helloworld', 'org.sil.software/check/has-R') # We use `check` as a decorator to wrap an ordinary python # function into an instance of FontBakeryCheck to prepare # and mark it as a check. # A check id is mandatory and must be globally and timely # unique. See "Naming Things: check-ids" below.
""" Checks for Adobe Fonts (formerly known as Typekit). """ from fontbakery.callable import check from fontbakery.checkrunner import Section, PASS, FAIL from fontbakery.fonts_profile import profile_factory from fontbakery.profiles.universal import UNIVERSAL_PROFILE_CHECKS profile_imports = ('fontbakery.profiles.universal', ) profile = profile_factory(default_section=Section("Adobe Fonts")) ADOBEFONTS_PROFILE_CHECKS = \ UNIVERSAL_PROFILE_CHECKS + [ 'com.adobe.fonts/check/family/consistent_upm' ] @check(id='com.adobe.fonts/check/family/consistent_upm', rationale="""While not required by the OpenType spec, we (Adobe) expect that a group of fonts designed & produced as a family have consistent units per em. """) def com_adobe_fonts_check_family_consistent_upm(ttFonts): """Fonts have consistent Units Per Em?""" upm_set = set() for ttFont in ttFonts: upm_set.add(ttFont['head'].unitsPerEm) if len(upm_set) > 1: yield FAIL, ("Fonts have different units per em: {}.").format( sorted(upm_set)) else: yield PASS, "Fonts have consistent units per em."
import sys import textwrap from pathlib import Path from fontbakery.callable import check, condition from fontbakery.checkrunner import FAIL, PASS, SKIP, Section from fontbakery.fonts_profile import profile_factory from vharfbuzz import Vharfbuzz from os.path import basename, relpath from stringbrewer import StringBrewer from collidoscope import Collidoscope shaping_basedir = Path("qa", "shaping_tests") profile_imports = () profile = profile_factory(default_section=Section("Shaping Checks")) PROFILE_CHECKS = [ "com.google.fonts/check/shaping/regression", "com.google.fonts/check/shaping/forbidden", "com.google.fonts/check/shaping/collides", ] HTML_HEADER = """ <!doctype html> <html> <head> <meta charset="utf-8"> <title>Shaping Report</title> <style type="text/css"> @font-face {font-family: "TestFont"; src: url(%s);}
"loca", "hhea", "dsig", "hmtx", "gdef", "gpos", "kern", "glyf", "fvar", "stat", "shared_conditions", ), ) profile_imports = (OPENTYPE_PROFILE_IMPORTS, ) profile = profile_factory( default_section=Section("OpenType Specification Checks")) OPENTYPE_PROFILE_CHECKS = [ 'com.google.fonts/check/family/underline_thickness', 'com.google.fonts/check/family/panose_proportion', 'com.google.fonts/check/family/panose_familytype', 'com.google.fonts/check/family/equal_unicode_encodings', 'com.google.fonts/check/family/equal_font_versions', 'com.adobe.fonts/check/family/bold_italic_unique_for_nameid1', 'com.adobe.fonts/check/family/max_4_fonts_per_family_name', 'com.adobe.fonts/check/name/postscript_vs_cff', 'com.adobe.fonts/check/name/postscript_name_consistency', 'com.adobe.fonts/check/name/empty_records', 'com.google.fonts/check/name/no_copyright_on_description', 'com.google.fonts/check/name/match_familyname_fullfont', 'com.google.fonts/check/varfont/regular_wght_coord',
def test_opentype_checks_load(): profile_imports = ("fontbakery.profiles.opentype", ) profile = profile_factory(default_section=Section("OpenType Testing")) profile.auto_register({}, profile_imports=profile_imports) profile.test_dependencies()
def test_opentype_checks_load(): spec_imports = ("fontbakery.specifications.opentype", ) specification = spec_factory(default_section=Section("OpenType Testing")) specification.auto_register({}, spec_imports=spec_imports) specification.test_dependencies()
import os from fontbakery.checkrunner import Section, PASS, FAIL, WARN, ERROR, INFO, SKIP from fontbakery.callable import condition, check, disable from fontbakery.constants import PriorityLevel from fontbakery.message import Message from fontbakery.fonts_profile import profile_factory from fontbakery.profiles.opentype import OPENTYPE_PROFILE_CHECKS profile_imports = ('fontbakery.profiles.opentype', '.shared_conditions') profile = profile_factory(default_section=Section("Universal")) THIRDPARTY_CHECKS = [ 'com.google.fonts/check/ots', 'com.google.fonts/check/ftxvalidator', 'com.google.fonts/check/ftxvalidator_is_available' ] SUPERFAMILY_CHECKS = [ 'com.google.fonts/check/superfamily/list', 'com.google.fonts/check/superfamily/vertical_metrics', ] UNIVERSAL_PROFILE_CHECKS = \ OPENTYPE_PROFILE_CHECKS + \ THIRDPARTY_CHECKS + \ SUPERFAMILY_CHECKS + [ 'com.google.fonts/check/name/trailing_spaces', 'com.google.fonts/check/family/win_ascent_and_descent', 'com.google.fonts/check/os2_metrics_match_hhea', 'com.google.fonts/check/fontbakery_version', 'com.google.fonts/check/ttx-roundtrip',
ROBOTO_GENERAL_CHECKS += [ "com.roboto.fonts/check/italic_angle", "com.roboto.fonts/check/fs_type", "com.roboto.fonts/check/vendorid", "com.roboto.fonts/check/charset_coverage", "com.roboto.fonts/check/digit_widths", "com.roboto.fonts/check/numr_mapped_to_supr", "com.roboto.fonts/check/name_copyright", "com.roboto.fonts/check/name_unique_id", "com.roboto.fonts/check/vertical_metrics", "com.roboto.fonts/check/cmap4", ] profile_imports = ('fontbakery.profiles.universal',) profile = profile_factory(default_section=Section("Roboto v3 general")) # Checks ported from https://github.com/googlefonts/roboto/blob/master/scripts/run_general_tests.py @condition def is_italic(ttFont): return True if "Italic" in ttFont.reader.file.name else False @condition def is_vf(ttFont): return True if "fvar" in ttFont else False def font_style(ttFont):