Beispiel #1
0
def get_module_profile(module, name=None):
    """
    Get or create a profile from a module and return it.

    If the name `module.profile` is present the value of that is returned.
    Otherwise, if the name `module.profile_factory` is present, a new profile
    is created using `module.profile_factory` and then `profile.auto_register`
    is called with the module namespace.
    If neither name is defined, the module is not considered a profile-module
    and None is returned.

    TODO: describe the `name` argument and better define the signature of `profile_factory`.

    The `module` argument is expected to behave like a python module.
    The optional `name` argument is used when `profile_factory` is called to
    give a name to the default section of the new profile. If name is not
    present `module.__name__` is the fallback.

    `profile_factory` is called like this:
        `profile = module.profile_factory(default_section=default_section)`

    """
    try:
        # if profile is defined we just use it
        return module.profile
    except AttributeError:  # > 'module' object has no attribute 'profile'
        # try to create one on the fly.
        # e.g. module.__name__ == "fontbakery.profiles.cmap"
        if "profile_factory" not in module.__dict__:
            return None
        default_section = Section(name or module.__name__)
        profile = module.profile_factory(default_section=default_section,
                                         module_spec=module.__spec__)
        profile.auto_register(module.__dict__)
        return profile
Beispiel #2
0
 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.'
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
import textwrap
from pathlib import Path
from fontbakery.callable import check, condition
from fontbakery.checkrunner import FAIL, PASS, SKIP
from fontbakery.section import Section
from fontbakery.message import Message
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",
]

STYLESHEET = """
<style type="text/css">
    @font-face {font-family: "TestFont"; src: url(%s);}
    .tf { font-family: "TestFont"; }
    .shaping pre { font-size: 1.2rem; }
    .shaping li { font-size: 1.2rem; border-top: 1px solid #ddd; padding: 12px; margin-top: 12px; }
    .shaping-svg { height: 100px; margin:10px; transform: matrix(1, 0, 0, -1, 0, 0); }
</style>
Beispiel #7
0
        "hhea",
        "dsig",
        "hmtx",
        "gdef",
        "gpos",
        "kern",
        "glyf",
        "fvar",
        "stat",
        "layout",
        "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',
from fontbakery.callable import check
from fontbakery.section import Section
from fontbakery.status import PASS, FAIL, WARN
from fontbakery.fonts_profile import profile_factory
from fontbakery.message import Message

profile = profile_factory(default_section=Section("Just a Test"))

@check(
    id='com.google.fonts/check_for_testing/configuration',
    rationale="""
        Check that we can inject the configuration object and read it.
    """,
)
def com_google_fonts_check_for_testing_configuration(config):
    """Check if we can inject a config file"""
    if config and "a_test_profile" in config and config["a_test_profile"]["OK"] == 123:
        yield PASS, 'we have injected a config'
    else:
        yield FAIL, "config variable didn't look like we expected"


profile.auto_register(globals())
Beispiel #9
0
                for yExpected in [-180, -90, 0, 90, 180]:
                    if close_but_not_on(angle, yExpected, 0.5):
                        warnings.append(f"{glyphname}: {s}")

    if warnings:
        formatted_list = " * " + pretty_print_list(sorted(warnings), sep="\n * ")
        yield WARN,\
             Message("found-semi-vertical",
                     f"The following glyphs have semi-vertical/semi-horizontal lines:\n"
                     f"{formatted_list}")
    else:
        yield PASS, "No semi-horizontal/semi-vertical lines found."


OUTLINE_PROFILE_IMPORTS = (
    ".",
    ("shared_conditions",),
)
profile_imports = (OUTLINE_PROFILE_IMPORTS,)
profile = profile_factory(default_section=Section("Outline Correctness Checks"))
OUTLINE_PROFILE_CHECKS = [
    "com.google.fonts/check/outline_alignment_miss",
    "com.google.fonts/check/outline_short_segments",
    "com.google.fonts/check/outline_colinear_vectors",
    "com.google.fonts/check/outline_jaggy_segments",
    "com.google.fonts/check/outline_semi_vertical",
]

profile.auto_register(globals())
profile.test_expected_checks(OUTLINE_PROFILE_CHECKS, exclusive=True)
Beispiel #10
0
from fontbakery.profiles.universal import UNIVERSAL_PROFILE_CHECKS
from fontbakery.section import Section
from fontbakery.status import 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',
       rationale="""
Beispiel #11
0
import os
from fontbakery.callable import check
from fontbakery.status import ERROR, FAIL, INFO, PASS, WARN
from fontbakery.section import Section
from fontbakery.message import Message
# 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"
    ]
Beispiel #12
0
    def __init__(
        self,
        sections=None,
        iterargs=None,
        derived_iterables=None,
        conditions=None,
        aliases=None,
        expected_values=None,
        default_section=None,
        check_skip_filter=None,
        profile_tag=None,
        module_spec=None,
    ):
        """
          sections: a list of sections, which are ideally ordered sets of
              individual checks.
              It makes no sense to have checks repeatedly, they yield the same
              results anyway, thus we don't allow this.
          iterargs: maping 'singular' variable names to the iterable in values
              e.g.: `{'font': 'fonts'}` in this case fonts must be iterable AND
              'font' may not be a value NOR a condition name.
          derived_iterables: a dictionary {"plural": ("singular", bool simple)}
              where singular points to a condition, that consumes directly or indirectly
              iterargs. plural will be a list of all values the condition produces
              with all combination of it's iterargs.
              If simple is False, the result returns tuples of: (iterars, value)
              where iterargs is a tuple of ('iterargname', number index)
              Especially for cases where only one iterarg is involved, simple
              can be set to True and the result list will just contain the values.
              Example:

              @condition
              def ttFont(font):
                  return TTFont(font)

              values={'fonts': ['font_0', 'font_1']}
              iterargs={'font': 'fonts'}

              derived_iterables={'ttFonts': ('ttFont', True)}
              # Then:
              ttfons = (
                  <TTFont object from font_0>
                , <TTFont object from font_1>
              )

              # However
              derived_iterables={'ttFonts': ('ttFont', False)}
              ttfons = [
                  ((('font', 0), ), <TTFont object from font_0>)
                , ((('font', 1), ), <TTFont object from font_1>)
              ]

        We will:
          a) get all needed values/variable names from here
          b) add some validation, so that we know the values match
             our expectations! These values must be treated as user input!
        """
        self._namespace = {
            "config": "config"  # Filled in by checkrunner
        }

        self.iterargs = {}
        if iterargs:
            self._add_dict_to_namespace("iterargs", iterargs)

        self.derived_iterables = {}
        if derived_iterables:
            self._add_dict_to_namespace("derived_iterables", derived_iterables)

        self.aliases = {}
        if aliases:
            self._add_dict_to_namespace("aliases", aliases)

        self.conditions = {}
        if conditions:
            self._add_dict_to_namespace("conditions", conditions)

        self.expected_values = {}
        if expected_values:
            self._add_dict_to_namespace("expected_values", expected_values)

        self._check_registry = {}
        self._sections = OrderedDict()
        if sections:
            for section in sections:
                self.add_section(section)

        if not default_section:
            default_section = (sections[0] if sections and len(sections) else
                               Section("Default"))
        self._default_section = default_section
        self.add_section(self._default_section)

        # currently only used for new check ids in self.check_log_override
        # only a-z everything else is deleted
        self.profile_tag = re.sub(r"[^a-z]", "",
                                  (profile_tag
                                   or self._default_section.name).lower())

        self._check_skip_filter = check_skip_filter

        # Used in multiprocessing because pickling the profiles fail on
        # Mac and Windows. See: googlefonts/fontbakery#2982
        # module_locator can actually a module.__spec__ but also just a dict
        # self.module_locator will always be just a dict
        if module_spec is None:
            # This is a bit of a hack, but the idea is to reduce boilerplate
            # when writing modules that directly define a profile.
            try:
                frame = inspect.currentframe().f_back
                while frame:
                    # Note, if __spec__ is a local variable we shpuld be at a
                    # module top level. It should also be the correct ModuleSpec
                    # according to how we do this "usually" (as documented and
                    # practiced as far as I'm aware of), e.g. the profile module
                    # defines a profile object directly by calling a Profile constructor
                    # (e.g. profies.ufo_sources) or indirectly via a profile_factory
                    # (e.g. profiles.google_fonts). Otherwise, if this fails
                    # or finds a wrong ModuleSpec, there's still the option to
                    # pass module_spec as an argument (module.__spec__), which is
                    # actually demonstrated in get_module_profile.
                    if "__spec__" in frame.f_locals:
                        module_spec = frame.f_locals["__spec__"]
                        if module_spec and isinstance(
                                module_spec, importlib.machinery.ModuleSpec):
                            break
                        module_spec = None  # reset
                    frame = frame.f_back
            finally:
                del frame

        # If not module_spec: this is only a problem in multiprocessing, in
        # that case we'll be failing to access this with an AttributeError.
        if module_spec is not None:
            self.module_locator = dict(name=module_spec.name,
                                       origin=module_spec.origin)
Beispiel #13
0
"""

from fontbakery.callable import check, condition
from fontbakery.section import Section
from fontbakery.status import PASS, FAIL, WARN
from fontbakery.fonts_profile import profile_factory
from fontbakery.message import Message
from fontTools.pens.boundsPen import BoundsPen
from beziers.path import BezierPath
from beziers.line import Line
from beziers.point import Point
import beziers
import uharfbuzz as hb


profile = profile_factory(default_section=Section("Suitability for In-Car Display"))

DISCLAIMER = """
        (Note that PASSing this check does not guarantee compliance with ISO 15008.)
"""

CHECKS = [
    "com.google.fonts/check/iso15008_proportions",
    "com.google.fonts/check/iso15008_stem_width",
    "com.google.fonts/check/iso15008_intercharacter_spacing",
    "com.google.fonts/check/iso15008_interword_spacing",
    "com.google.fonts/check/iso15008_interline_spacing",
]


def xheight_intersections(ttFont, glyph):
Beispiel #14
0
import os

from fontbakery.status import PASS, FAIL, WARN, ERROR, INFO, SKIP
from fontbakery.section import Section
from fontbakery.callable import condition, check, disable
from fontbakery.message import Message
from fontbakery.fonts_profile import profile_factory
from fontbakery.profiles.opentype import OPENTYPE_PROFILE_CHECKS
from fontbakery.profiles.outline import OUTLINE_PROFILE_CHECKS
from fontbakery.profiles.shaping import PROFILE_CHECKS as SHAPING_PROFILE_CHECKS

profile_imports = ('fontbakery.profiles.opentype',
                   'fontbakery.profiles.outline',
                   'fontbakery.profiles.shaping',
                   '.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 + \
    OUTLINE_PROFILE_CHECKS + \
Beispiel #15
0
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()
Beispiel #16
0
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()
Beispiel #17
0
"""
Checks for Font Bureau.
"""

from fontbakery.callable import check
from fontbakery.section import Section
from fontbakery.status import 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
Beispiel #18
0
"""
Checks for Adobe Fonts (formerly known as Typekit).
"""
import unicodedata

from fontbakery.callable import check
from fontbakery.status import PASS, FAIL, WARN
from fontbakery.section import Section
from fontbakery.fonts_profile import profile_factory
from fontbakery.profiles.universal import UNIVERSAL_PROFILE_CHECKS
from fontbakery.message import Message

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',
        'com.adobe.fonts/check/find_empty_letters'
    ]

OVERRIDDEN_CHECKS = [
    'com.google.fonts/check/dsig',
    'com.google.fonts/check/whitespace_glyphs',
    'com.google.fonts/check/valid_glyphnames',
]
ADOBEFONTS_PROFILE_CHECKS += [f'{cid}:{profile.profile_tag}' for cid in OVERRIDDEN_CHECKS]

ADOBEFONTS_PROFILE_CHECKS[:] = [cid for cid in ADOBEFONTS_PROFILE_CHECKS
                                            if cid not in OVERRIDDEN_CHECKS]
Beispiel #19
0
        return ('fonts',)


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 UFO checks")


@register_condition
@condition
def ufo_font(font):
    import defcon