From 0f2bae8e577535b71d87ac9a3a320558a0f3b9e1 Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Fri, 12 Feb 2021 11:33:17 +0000 Subject: [PATCH] Beginnings of a designspace profile --- Lib/fontbakery/commands/check_designspace.py | 29 +++ Lib/fontbakery/profiles/designspace.py | 173 ++++++++++++++++++ .../Stupid Font Bold.ufo/fontinfo.plist | 40 ++++ .../Stupid Font Bold.ufo/glyphs/A_.glif | 22 +++ .../Stupid Font Bold.ufo/glyphs/B_.glif | 22 +++ .../glyphs/contents.plist | 10 + .../Stupid Font Bold.ufo/layercontents.plist | 10 + .../stupidfont/Stupid Font Bold.ufo/lib.plist | 19 ++ .../Stupid Font Bold.ufo/metainfo.plist | 10 + .../Stupid Font Regular.ufo/fontinfo.plist | 40 ++++ .../Stupid Font Regular.ufo/glyphs/A_.glif | 22 +++ .../glyphs/contents.plist | 8 + .../layercontents.plist | 10 + .../Stupid Font Regular.ufo/lib.plist | 18 ++ .../Stupid Font Regular.ufo/metainfo.plist | 10 + data/test/stupidfont/Stupid Font.designspace | 18 ++ 16 files changed, 461 insertions(+) create mode 100644 Lib/fontbakery/commands/check_designspace.py create mode 100644 Lib/fontbakery/profiles/designspace.py create mode 100644 data/test/stupidfont/Stupid Font Bold.ufo/fontinfo.plist create mode 100644 data/test/stupidfont/Stupid Font Bold.ufo/glyphs/A_.glif create mode 100644 data/test/stupidfont/Stupid Font Bold.ufo/glyphs/B_.glif create mode 100644 data/test/stupidfont/Stupid Font Bold.ufo/glyphs/contents.plist create mode 100644 data/test/stupidfont/Stupid Font Bold.ufo/layercontents.plist create mode 100644 data/test/stupidfont/Stupid Font Bold.ufo/lib.plist create mode 100644 data/test/stupidfont/Stupid Font Bold.ufo/metainfo.plist create mode 100644 data/test/stupidfont/Stupid Font Regular.ufo/fontinfo.plist create mode 100644 data/test/stupidfont/Stupid Font Regular.ufo/glyphs/A_.glif create mode 100644 data/test/stupidfont/Stupid Font Regular.ufo/glyphs/contents.plist create mode 100644 data/test/stupidfont/Stupid Font Regular.ufo/layercontents.plist create mode 100644 data/test/stupidfont/Stupid Font Regular.ufo/lib.plist create mode 100644 data/test/stupidfont/Stupid Font Regular.ufo/metainfo.plist create mode 100644 data/test/stupidfont/Stupid Font.designspace diff --git a/Lib/fontbakery/commands/check_designspace.py b/Lib/fontbakery/commands/check_designspace.py new file mode 100644 index 0000000000..3f69d1e3c1 --- /dev/null +++ b/Lib/fontbakery/commands/check_designspace.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +import sys +from functools import partial + +from fontbakery.commands.check_profile import ( + runner_factory as super_runner_factory, main as super_main) +from fontbakery.profiles.designspace import profile + +# The values dict will probably get one or more specific blacklists +# for the google font project. It would be good if it was not necessary +# to copy paste this kind of configuration, thus a central init for +# the google/fonts repository is good. +GOOGLEFONTS_SPECIFICS = {} + + +# runner_factory is used by the fontbakery dashboard. +# It is here in order to have a single place from which +# the profile is configured for the CLI and the worker. +def runner_factory(fonts): + values = {} + values.update(GOOGLEFONTS_SPECIFICS) + values['fonts'] = fonts + return super_runner_factory(profile, values=values) + + +main = partial(super_main, profile, values=GOOGLEFONTS_SPECIFICS) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/Lib/fontbakery/profiles/designspace.py b/Lib/fontbakery/profiles/designspace.py new file mode 100644 index 0000000000..eb5e04e904 --- /dev/null +++ b/Lib/fontbakery/profiles/designspace.py @@ -0,0 +1,173 @@ +import os + +from fontbakery.checkrunner import Section, PASS, FAIL, WARN, ERROR, INFO, SKIP, Profile +from fontbakery.callable import condition, check, disable +from fontbakery.callable import FontBakeryExpectedValue as ExpectedValue +from fontbakery.message import Message +from fontbakery.fonts_profile import profile_factory +from fontbakery.utils import pretty_print_list +import defcon + +profile_imports = ".shared_conditions" + + +class DesignspaceProfile(Profile): + def setup_argparse(self, argument_parser): + """Set up custom arguments needed for this profile.""" + import glob + import logging + import argparse + + def get_fonts(pattern): + + fonts_to_check = [] + # use glob.glob to accept *.designsapce + + for fullpath in glob.glob(pattern): + fullpath_absolute = os.path.abspath(fullpath) + if fullpath_absolute.lower().endswith( + ".designspace" + ) and os.path.isfile(fullpath_absolute): + fonts_to_check.append(fullpath) + else: + logging.warning( + ( + "Skipping '{}' as it does not seem to be" + " valid Designspace file." + ).format(fullpath) + ) + return fonts_to_check + + class MergeAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + target = [item for l in values for item in l] + setattr(namespace, self.dest, target) + + argument_parser.add_argument( + "fonts", + # To allow optional commands like "-L" to work without other input + # files: + nargs="*", + type=get_fonts, + action=MergeAction, + help="font file path(s) to check." + " Wildcards like *.designspace are allowed.", + ) + + return ("fonts",) + + +fonts_expected_value = ExpectedValue( + "fonts", + default=[], + description="A list of the designspace 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 = DesignspaceProfile( + default_section=Section("Default"), + iterargs={"font": "fonts"}, + expected_values={fonts_expected_value.name: fonts_expected_value}, +) + +register_check = profile.register_check +register_condition = profile.register_condition + +basic_checks = Section("Basic Designspace checks") + + +@register_condition +@condition +def designspace(font): + from fontTools.designspaceLib import DesignSpaceDocument + + ds = DesignSpaceDocument.fromfile(font) + return ds + + +@register_condition +@condition +def sources(designspace): + return designspace.loadSourceFonts(defcon.Font) + + +@register_check(section=basic_checks) +@check(id="com.google.fonts/check/designspace_has_sources") +def com_google_fonts_check_designspace_has_sources(sources): + """See if we can actually load the source files.""" + if not sources: + yield FAIL, "Unable to load source files." + else: + yield PASS, "We have sources." + + +@register_check(section=basic_checks) +@check(id="com.google.fonts/check/designspace_has_default_master") +def com_google_fonts_check_designspace_has_default_master(designspace): + """Ensure a default master is defined.""" + if not designspace.findDefault(): + yield FAIL, "Unable to find a default master." + else: + yield PASS, "We located a default master." + + +@register_check(section=basic_checks) +@check(id="com.google.fonts/check/designspace_has_consistent_glyphset") +def com_google_fonts_check_designspace_has_consistent_glyphset(designspace, sources): + """Ensure non-default masters don't have glyphs not present in the default.""" + default_glyphset = set(designspace.findDefault().font.keys()) + failures = [] + for source in designspace.sources: + master_glyphset = set(source.font.keys()) + outliers = master_glyphset - default_glyphset + if outliers: + failures.append( + "Source %s has glyphs not present in the default master: %s" + % ( + source.filename, + ", ".join(list(outliers)), + ) + ) + if failures: + formatted_list = "\t* " + pretty_print_list(failures, sep="\n\t* ") + yield FAIL, Message( + "inconsistent-glyphset", "Glyphsets were not consistent:\n" + formatted_list + ) + else: + yield PASS, "Glyphsets were consistent." + + +@register_check(section=basic_checks) +@check(id="com.google.fonts/check/designspace_has_consistent_unicodes") +def com_google_fonts_check_designspace_has_consistent_unicodes(designspace, sources): + """Ensure Unicode assignments are consistent across sources.""" + default_source = designspace.findDefault() + default_unicodes = {g.name: g.unicode for g in default_source.font} + failures = [] + for source in designspace.sources: + for g in source.font: + if g.name not in default_unicodes: + # Previous test will cover this + continue + if g.unicode != default_unicodes[g.name]: + failures.append( + "Source %s has %s=%s; default master has %s=%s" + % ( + source.filename, + g.name, + g.unicode, + g.name, + default_unicodes[g.name], + ) + ) + if failures: + formatted_list = "\t* " + pretty_print_list(failures, sep="\n\t* ") + yield FAIL, Message( + "inconsistent-unicodes", + "Unicode assignments were not consistent:\n" + formatted_list, + ) + else: + yield PASS, "Unicode assignments were consistent." diff --git a/data/test/stupidfont/Stupid Font Bold.ufo/fontinfo.plist b/data/test/stupidfont/Stupid Font Bold.ufo/fontinfo.plist new file mode 100644 index 0000000000..1ed69ea41f --- /dev/null +++ b/data/test/stupidfont/Stupid Font Bold.ufo/fontinfo.plist @@ -0,0 +1,40 @@ + + + + + ascender + 800 + capHeight + 700 + descender + -200 + openTypeHeadCreated + 2021/02/05 10:10:29 + postscriptBlueValues + + -16.0 + 0.0 + 500.0 + 516.0 + 700.0 + 716.0 + 800.0 + 816.0 + + postscriptOtherBlues + + -216.0 + -200.0 + + styleName + Regular + unitsPerEm + 1000 + versionMajor + 1 + versionMinor + 0 + xHeight + 500 + + diff --git a/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/A_.glif b/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/A_.glif new file mode 100644 index 0000000000..087cb9298f --- /dev/null +++ b/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/A_.glif @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/B_.glif b/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/B_.glif new file mode 100644 index 0000000000..e6eb766fb7 --- /dev/null +++ b/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/B_.glif @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/contents.plist b/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/contents.plist new file mode 100644 index 0000000000..5d14fd46f2 --- /dev/null +++ b/data/test/stupidfont/Stupid Font Bold.ufo/glyphs/contents.plist @@ -0,0 +1,10 @@ + + + + + A + A_.glif + B + B_.glif + + diff --git a/data/test/stupidfont/Stupid Font Bold.ufo/layercontents.plist b/data/test/stupidfont/Stupid Font Bold.ufo/layercontents.plist new file mode 100644 index 0000000000..cf95d35736 --- /dev/null +++ b/data/test/stupidfont/Stupid Font Bold.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/data/test/stupidfont/Stupid Font Bold.ufo/lib.plist b/data/test/stupidfont/Stupid Font Bold.ufo/lib.plist new file mode 100644 index 0000000000..71152d0ec4 --- /dev/null +++ b/data/test/stupidfont/Stupid Font Bold.ufo/lib.plist @@ -0,0 +1,19 @@ + + + + + com.schriftgestaltung.disablesAutomaticAlignment + + com.schriftgestaltung.fontMasterID + m01 + com.schriftgestaltung.glyphOrder + + com.schriftgestaltung.useNiceNames + + public.glyphOrder + + A + B + + + diff --git a/data/test/stupidfont/Stupid Font Bold.ufo/metainfo.plist b/data/test/stupidfont/Stupid Font Bold.ufo/metainfo.plist new file mode 100644 index 0000000000..74e4b3b4fd --- /dev/null +++ b/data/test/stupidfont/Stupid Font Bold.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.schriftgestaltung.GlyphsUFOExport + formatVersion + 3 + + diff --git a/data/test/stupidfont/Stupid Font Regular.ufo/fontinfo.plist b/data/test/stupidfont/Stupid Font Regular.ufo/fontinfo.plist new file mode 100644 index 0000000000..1ed69ea41f --- /dev/null +++ b/data/test/stupidfont/Stupid Font Regular.ufo/fontinfo.plist @@ -0,0 +1,40 @@ + + + + + ascender + 800 + capHeight + 700 + descender + -200 + openTypeHeadCreated + 2021/02/05 10:10:29 + postscriptBlueValues + + -16.0 + 0.0 + 500.0 + 516.0 + 700.0 + 716.0 + 800.0 + 816.0 + + postscriptOtherBlues + + -216.0 + -200.0 + + styleName + Regular + unitsPerEm + 1000 + versionMajor + 1 + versionMinor + 0 + xHeight + 500 + + diff --git a/data/test/stupidfont/Stupid Font Regular.ufo/glyphs/A_.glif b/data/test/stupidfont/Stupid Font Regular.ufo/glyphs/A_.glif new file mode 100644 index 0000000000..1d831c0b42 --- /dev/null +++ b/data/test/stupidfont/Stupid Font Regular.ufo/glyphs/A_.glif @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/test/stupidfont/Stupid Font Regular.ufo/glyphs/contents.plist b/data/test/stupidfont/Stupid Font Regular.ufo/glyphs/contents.plist new file mode 100644 index 0000000000..43097d5cde --- /dev/null +++ b/data/test/stupidfont/Stupid Font Regular.ufo/glyphs/contents.plist @@ -0,0 +1,8 @@ + + + + + A + A_.glif + + diff --git a/data/test/stupidfont/Stupid Font Regular.ufo/layercontents.plist b/data/test/stupidfont/Stupid Font Regular.ufo/layercontents.plist new file mode 100644 index 0000000000..cf95d35736 --- /dev/null +++ b/data/test/stupidfont/Stupid Font Regular.ufo/layercontents.plist @@ -0,0 +1,10 @@ + + + + + + public.default + glyphs + + + diff --git a/data/test/stupidfont/Stupid Font Regular.ufo/lib.plist b/data/test/stupidfont/Stupid Font Regular.ufo/lib.plist new file mode 100644 index 0000000000..e020ff1b6f --- /dev/null +++ b/data/test/stupidfont/Stupid Font Regular.ufo/lib.plist @@ -0,0 +1,18 @@ + + + + + com.schriftgestaltung.disablesAutomaticAlignment + + com.schriftgestaltung.fontMasterID + m01 + com.schriftgestaltung.glyphOrder + + com.schriftgestaltung.useNiceNames + + public.glyphOrder + + A + + + diff --git a/data/test/stupidfont/Stupid Font Regular.ufo/metainfo.plist b/data/test/stupidfont/Stupid Font Regular.ufo/metainfo.plist new file mode 100644 index 0000000000..74e4b3b4fd --- /dev/null +++ b/data/test/stupidfont/Stupid Font Regular.ufo/metainfo.plist @@ -0,0 +1,10 @@ + + + + + creator + com.schriftgestaltung.GlyphsUFOExport + formatVersion + 3 + + diff --git a/data/test/stupidfont/Stupid Font.designspace b/data/test/stupidfont/Stupid Font.designspace new file mode 100644 index 0000000000..9af89ae501 --- /dev/null +++ b/data/test/stupidfont/Stupid Font.designspace @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +