Example #1
0
 def test_build_otfs(self, mock_build_ttfs, mock_build_otfs):
     project = FontProject()
     self.assertFalse(mock_build_otfs.called)
     self.assertFalse(mock_build_ttfs.called)
     project.run_from_ufos("path to ufo", output=('otf', 'ttf'))
     self.assertTrue(mock_build_ttfs.called)
     self.assertTrue(mock_build_otfs.called)
Example #2
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-c', '--compatible', action='store_true')
    parser.add_argument('-i', '--interpolate', action='store_true',
                        help='interpolate masters (Glyphs source only)')
    parser.add_argument('--mti-source')
    parser.add_argument('--use-afdko', action='store_true')
    args = vars(parser.parse_args())

    project = FontProject()

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    if not sum(1 for p in [glyphs_path, ufo_paths] if p) == 1:
        raise ValueError('Exactly one source type required (Glyphs or UFO).')

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)

    else:
        excluded = 'interpolate'
        if args[excluded]:
            raise ValueError(
                '"%s" argument only available for Glyphs source' % excluded)
        del args[excluded]
        project.run_from_ufos(ufo_paths, **args)
Example #3
0
 def test_run_from_ufos(self, mock_build_variable_font, mock_build_interpolatable_ttfs):
     project = FontProject()
     self.assertFalse(mock_build_interpolatable_ttfs.called)
     self.assertFalse(mock_build_variable_font.called)
     project.run_from_ufos("path to ufo", output=('variable'), designspace_path="design space")
     self.assertTrue(mock_build_interpolatable_ttfs.called)
     self.assertTrue(mock_build_variable_font.called)
Example #4
0
 def test_build_otfs(self, mock_build_ttfs, mock_build_otfs):
     project = FontProject()
     self.assertFalse(mock_build_otfs.called)
     self.assertFalse(mock_build_ttfs.called)
     project.run_from_ufos("path to ufo", output=('otf', 'ttf'))
     self.assertTrue(mock_build_ttfs.called)
     self.assertTrue(mock_build_otfs.called)
Example #5
0
 def test_run_from_ufos(self, mock_build_variable_font,
                        mock_build_interpolatable_ttfs):
     project = FontProject()
     self.assertFalse(mock_build_interpolatable_ttfs.called)
     self.assertFalse(mock_build_variable_font.called)
     project.run_from_ufos("path to ufo",
                           output=('variable'),
                           designspace_path="design space")
     self.assertTrue(mock_build_interpolatable_ttfs.called)
     self.assertTrue(mock_build_variable_font.called)
Example #6
0
def build_fonts(ufo_path):
    try:
        fp = FontProject()
        fp.run_from_ufos(ufo_path, output=BUILD_FILE_TYPE)
    except Exception as e:
        lock.acquire()
        print(" ")
        print("[ERROR] The fontmake compile for " + ufo_path +
              " failed with the following error"
              ":" + os.linesep)
        sys.stdout.flush()
        traceback.print_exc()
        print(str(e))
        sys.stdout.flush()
        lock.release()
Example #7
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument('-o',
                        '--output',
                        nargs='+',
                        default=('otf', 'ttf'),
                        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable'))
    parser.add_argument('-i',
                        '--interpolate',
                        action='store_true',
                        help='interpolate masters (Glyphs source only)')
    parser.add_argument('--mti-source')
    parser.add_argument('--use-afdko', action='store_true')
    parser.add_argument('--timing', action='store_true')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if not sum(1
               for p in [glyphs_path, ufo_paths, designspace_path] if p) == 1:
        raise ValueError('Exactly one source type required (Glyphs or UFO).')

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)

    elif designspace_path:
        project.run_from_designspace(designspace_path, **args)

    else:
        excluded = 'interpolate'
        if args[excluded]:
            raise ValueError(
                '"%s" argument only available for Glyphs or Designspace source'
                % excluded)
        del args[excluded]

    if ufo_paths:
        project.run_from_ufos(ufo_paths, **args)
Example #8
0
def build(args):
    designspace = os.path.join(args.build, args.designspace)

    os.environ["SOURCE_DATE_EPOCH"] = epoch(args)

    autohint = None
    if args.release:
        autohint = ""

    project = FontProject(verbose="WARNING")
    if args.ufo:
        project.run_from_ufos([args.ufo],
                              output=args.output,
                              remove_overlaps=False,
                              reverse_direction=False,
                              subroutinize=args.release,
                              autohint=autohint)
    else:
        project.run_from_designspace(designspace,
                                     output=args.output,
                                     subroutinize=args.release,
                                     autohint=autohint)
Example #9
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument('-o', '--output', nargs='+', default=('otf', 'ttf'),
                        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable'))
    parser.add_argument('-i', '--interpolate', action='store_true',
                        help='interpolate masters (Glyphs source only)')
    parser.add_argument('--mti-source')
    parser.add_argument('--use-afdko', action='store_true')
    parser.add_argument('--timing', action='store_true')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if not sum(1 for p in [glyphs_path, ufo_paths, designspace_path] if p) == 1:
        raise ValueError('Exactly one source type required (Glyphs or UFO).')

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)

    elif designspace_path:
        project.run_from_designspace(designspace_path, **args)

    else:
        excluded = 'interpolate'
        if args[excluded]:
            raise ValueError(
                '"%s" argument only available for Glyphs or Designspace source'
                % excluded)
        del args[excluded]

    if ufo_paths:
        project.run_from_ufos(ufo_paths, **args)
Example #10
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument('-o',
                        '--output',
                        nargs='+',
                        default=('otf', 'ttf'),
                        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable',
                                 'variable'))

    parser.add_argument('-i',
                        '--interpolate',
                        action='store_true',
                        help='interpolate masters (for Glyphs or MutatorMath '
                        'sources only)')
    parser.add_argument('-M',
                        '--masters-as-instances',
                        action='store_true',
                        help='treat masters as instances')
    parser.add_argument('-a',
                        '--autohint',
                        nargs='?',
                        const="",
                        help='can provide arguments to ttfautohint, quoted')
    parser.add_argument('--mti-source')
    parser.add_argument('--family-name',
                        help='Family name to use for masters,'
                        ' and to filter output instances by')
    parser.add_argument('--use-afdko', action='store_true')
    parser.add_argument('--keep-direction',
                        dest="reverse_direction",
                        action='store_false',
                        help='Do not reverse contour '
                        'direction when output is ttf or ttf-interpolatable')
    parser.add_argument('--keep-overlaps',
                        dest="remove_overlaps",
                        action='store_false',
                        help='Do not remove any overlap.')
    group1 = parser.add_mutually_exclusive_group(required=False)
    group1.add_argument(
        '--production-names',
        dest='use_production_names',
        action='store_true',
        help='Rename glyphs with '
        'production names if available otherwise use uninames.')
    group1.add_argument('--no-production-names',
                        dest='use_production_names',
                        action='store_false')
    group2 = parser.add_mutually_exclusive_group(required=False)
    group2.add_argument('--subset',
                        dest='subset',
                        action='store_true',
                        help='Subset font using export '
                        'flags set by glyphsLib')
    group2.add_argument('--no-subset', dest='subset', action='store_false')
    group3 = parser.add_mutually_exclusive_group(required=False)
    group3.add_argument('-s',
                        '--subroutinize',
                        action='store_true',
                        help='Optimize CFF table using compreffor (default)')
    group3.add_argument('-S',
                        '--no-subroutinize',
                        dest='subroutinize',
                        action='store_false')
    parser.add_argument('-e',
                        '--conversion-error',
                        type=float,
                        default=None,
                        metavar='ERROR',
                        help="Maximum approximation error for"
                        " cubic to quadratic conversion measured in EM")
    parser.set_defaults(use_production_names=None,
                        subset=None,
                        subroutinize=True)
    parser.add_argument('--timing', action='store_true')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if not sum(1
               for p in [glyphs_path, ufo_paths, designspace_path] if p) == 1:
        parser.error('Exactly one source type required (Glyphs, UFO, or '
                     'MutatorMath).')

    def exclude_args(parser, args, excluded_args):
        exclusive_msg = '"{}" argument only available for {} source'
        exclusive_src = {
            'interpolate': 'Glyphs or MutatorMath',
            'family_name': 'Glyphs'
        }
        for excluded in excluded_args:
            if args[excluded]:
                parser.error(
                    exclusive_msg.format(excluded, exclusive_src[excluded]))
            del args[excluded]

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)

    elif designspace_path:
        exclude_args(parser, args, ['family_name'])
        project.run_from_designspace(designspace_path, **args)

    else:
        exclude_args(parser, args, ['family_name', 'interpolate'])

    if ufo_paths:
        project.run_from_ufos(ufo_paths,
                              is_instance=args.pop('masters_as_instances'),
                              **args)
Example #11
0
def main(args=None):
    parser = ArgumentParser()
    parser.add_argument("--version", action="version", version=__version__)
    inputGroup = parser.add_argument_group(
        title="Input arguments",
        description="The following arguments are mutually exclusive.",
    )
    xInputGroup = inputGroup.add_mutually_exclusive_group(required=True)
    xInputGroup.add_argument("-g",
                             "--glyphs-path",
                             metavar="GLYPHS",
                             help="Path to .glyphs source file")
    xInputGroup.add_argument(
        "-u",
        "--ufo-paths",
        nargs="+",
        metavar="UFO",
        help="One or more paths to UFO files",
    )
    xInputGroup.add_argument(
        "-m",
        "--mm-designspace",
        metavar="DESIGNSPACE",
        help="Path to .designspace file",
    )

    outputGroup = parser.add_argument_group(title="Output arguments")
    outputGroup.add_argument(
        "-o",
        "--output",
        nargs="+",
        default=("otf", "ttf"),
        metavar="FORMAT",
        help="Output font formats. Choose between: %(choices)s. "
        "Default: otf, ttf",
        choices=(
            "ufo",
            "otf",
            "ttf",
            "ttf-interpolatable",
            "otf-interpolatable",
            "variable",
            "variable-cff2",
        ),
    )
    outputSubGroup = outputGroup.add_mutually_exclusive_group()
    outputSubGroup.add_argument(
        "--output-path",
        default=None,
        help="Output font file path. Only valid when the output is a single "
        "file (e.g. input is a single UFO or output is variable font)",
    )
    outputSubGroup.add_argument(
        "--output-dir",
        default=None,
        help="Output folder. By default, output folders are created in the "
        "current working directory, grouping output fonts by format.",
    )
    outputGroup.add_argument(
        "-i",
        "--interpolate",
        nargs="?",
        default=False,
        const=True,
        metavar="INSTANCE_NAME",
        help="Interpolate masters and generate all the instances defined. "
        "To only interpolate a specific instance (or instances) that "
        'match a given "name" attribute, you can pass as argument '
        "the full instance name or a regular expression. "
        'E.g.: -i "Noto Sans Bold"; or -i ".* UI Condensed". '
        "(for Glyphs or MutatorMath sources only). ",
    )
    outputGroup.add_argument(
        "-M",
        "--masters-as-instances",
        action="store_true",
        help="Output masters as instances",
    )
    outputGroup.add_argument(
        "--family-name",
        help="Family name to use for masters, and to filter output instances",
    )
    outputGroup.add_argument(
        "--round-instances",
        dest="round_instances",
        action="store_true",
        help="Apply integer rounding to all geometry when interpolating",
    )
    outputGroup.add_argument(
        "--designspace-path",
        default=None,
        help="Path to output designspace file (for Glyphs sources only).",
    )
    outputGroup.add_argument(
        "--master-dir",
        default=None,
        help='Directory where to write master UFO. Default: "./master_ufo". '
        'If value is "{tmp}", a temporary directory is created and '
        "removed at the end (for Glyphs sources only).",
    )
    outputGroup.add_argument(
        "--instance-dir",
        default=None,
        help="Directory where to write instance UFOs. Default: "
        '"./instance_ufo". If value is "{tmp}", a temporary directory '
        "is created and removed at the end (for Glyphs sources only).",
    )
    outputGroup.add_argument(
        "--validate-ufo",
        action="store_true",
        help="Enable ufoLib validation on reading/writing UFO files. It is "
        "disabled by default",
    )

    contourGroup = parser.add_argument_group(title="Handling of contours")
    contourGroup.add_argument(
        "--keep-overlaps",
        dest="remove_overlaps",
        action="store_false",
        help="Do not remove any overlap.",
    )
    contourGroup.add_argument(
        "--overlaps-backend",
        dest="overlaps_backend",
        metavar="BACKEND",
        choices=("booleanOperations", "pathops"),
        default="booleanOperations",
        help="Select library to remove overlaps. Choose between: %(choices)s "
        "(default: %(default)s)",
    )
    contourGroup.add_argument(
        "--keep-direction",
        dest="reverse_direction",
        action="store_false",
        help="Do not reverse contour direction when output is ttf or "
        "ttf-interpolatable",
    )
    contourGroup.add_argument(
        "-e",
        "--conversion-error",
        type=float,
        default=None,
        metavar="ERROR",
        help="Maximum approximation error for cubic to quadratic conversion "
        "measured in EM",
    )
    contourGroup.add_argument(
        "-a",
        "--autohint",
        nargs="?",
        const="",
        help="Run ttfautohint. Can provide arguments, quoted",
    )
    contourGroup.add_argument(
        "--cff-round-tolerance",
        type=float,
        default=None,
        metavar="FLOAT",
        help="Restrict rounding of point coordinates in CFF table to only "
        "those floats whose absolute difference from their integral part "
        "is less than or equal to the tolerance. By default, all floats "
        "are rounded to integer (tolerance 0.5); 0 disables rounding.",
    )
    contourGroup.add_argument(
        "--optimize-cff",
        type=lambda s: CFFOptimization(int(s)),
        default=CFFOptimization.SUBROUTINIZE,
        help="0 disables all optimizations; 1 specializes the CFF charstring "
        "operators; 2 (default) also enables subroutinization",
    )

    layoutGroup = parser.add_argument_group(
        title="Handling of OpenType Layout")
    layoutGroup.add_argument(
        "--interpolate-binary-layout",
        nargs="?",
        default=False,
        const=True,
        metavar="MASTER_DIR",
        help="Interpolate layout tables from compiled master binaries. "
        "Requires Glyphs or MutatorMath source.",
    )
    layoutGroup.add_argument(
        "--feature-writer",
        metavar="CLASS",
        action="append",
        dest="feature_writer_specs",
        help="string specifying a feature writer class to load, either "
        "built-in or from an external module, optionally initialized with "
        "the given keyword arguments. The class and module names are "
        "separated by '::'. The option can be repeated multiple times "
        "for each writer class. A special value of 'None' will disable "
        "all automatic feature generation. The option overrides both the "
        "default ufo2ft writers and those specified in the UFO lib.",
    )

    feaCompilerGroup = layoutGroup.add_mutually_exclusive_group(required=False)
    feaCompilerGroup.add_argument(
        "--use-afdko",
        action="store_true",
        help="Use makeOTF instead of feaLib to compile FEA.",
    )
    feaCompilerGroup.add_argument(
        "--mti-source",
        help="Path to mtiLib .txt feature definitions (use instead of FEA)",
    )

    glyphnamesGroup = parser.add_mutually_exclusive_group(required=False)
    glyphnamesGroup.add_argument(
        "--production-names",
        dest="use_production_names",
        action="store_true",
        help="Rename glyphs with production names if available otherwise use "
        "uninames.",
    )
    glyphnamesGroup.add_argument("--no-production-names",
                                 dest="use_production_names",
                                 action="store_false")

    subsetGroup = parser.add_mutually_exclusive_group(required=False)
    subsetGroup.add_argument(
        "--subset",
        dest="subset",
        action="store_true",
        help="Subset font using export flags set by glyphsLib",
    )
    subsetGroup.add_argument("--no-subset",
                             dest="subset",
                             action="store_false")

    subroutinizeGroup = parser.add_mutually_exclusive_group(required=False)
    subroutinizeGroup.add_argument(
        "-s",
        "--subroutinize",
        action="store_true",
        help="Optimize CFF table using compreffor (default) [DEPRECATED: use "
        "--optimize-cff option instead]",
    )
    subroutinizeGroup.add_argument("-S",
                                   "--no-subroutinize",
                                   dest="subroutinize",
                                   action="store_false")

    parser.set_defaults(use_production_names=None,
                        subset=None,
                        subroutinize=None)

    logGroup = parser.add_argument_group(title="Logging arguments")
    logGroup.add_argument("--timing",
                          action="store_true",
                          help="Print the elapsed time for each steps")
    logGroup.add_argument(
        "--verbose",
        default="INFO",
        metavar="LEVEL",
        choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
        help="Configure the logger verbosity level. Choose between: "
        "%(choices)s. Default: INFO",
    )
    args = vars(parser.parse_args(args))

    specs = args.pop("feature_writer_specs")
    if specs is not None:
        args["feature_writers"] = _loadFeatureWriters(parser, specs)

    glyphs_path = args.pop("glyphs_path")
    ufo_paths = args.pop("ufo_paths")
    designspace_path = args.pop("mm_designspace")
    input_format = ("Glyphs" if glyphs_path else
                    "designspace" if designspace_path else "UFO") + " source"

    if INTERPOLATABLE_OUTPUTS.intersection(args["output"]):
        if not (glyphs_path or designspace_path):
            parser.error(
                "Glyphs or designspace source required for variable font")
        exclude_args(
            parser,
            args,
            [
                "interpolate", "masters_as_instances",
                "interpolate_binary_layout"
            ],
            "variable output",
        )

    try:
        project = FontProject(
            timing=args.pop("timing"),
            verbose=args.pop("verbose"),
            validate_ufo=args.pop("validate_ufo"),
        )

        if glyphs_path:
            with _make_tempdirs(parser, args):
                project.run_from_glyphs(glyphs_path, **args)
            return

        exclude_args(
            parser,
            args,
            [
                "family_name",
                "mti_source",
                "designspace_path",
                "master_dir",
                "instance_dir",
            ],
            input_format,
        )
        if designspace_path:
            project.run_from_designspace(designspace_path, **args)
            return

        exclude_args(
            parser,
            args,
            ["interpolate", "interpolate_binary_layout", "round_instances"],
            input_format,
        )
        project.run_from_ufos(ufo_paths,
                              is_instance=args.pop("masters_as_instances"),
                              **args)
    except FontmakeError as e:
        import sys

        sys.exit("fontmake: error: %s" % e)
Example #12
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument('-o',
                        '--output',
                        nargs='+',
                        default=('otf', 'ttf'),
                        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable',
                                 'variable'))

    parser.add_argument(
        '-i',
        '--interpolate',
        action='store_true',
        help='Interpolate masters (for Glyphs or MutatorMath sources only)')
    parser.add_argument('-M',
                        '--masters-as-instances',
                        action='store_true',
                        help='Output masters as instances')
    parser.add_argument(
        '--family-name',
        help='Family name to use for masters, and to filter output instances')

    parser.add_argument('--mti-source')
    parser.add_argument(
        '--interpolate-binary-layout',
        action='store_true',
        help='Interpolate layout tables from compiled master binaries. '
        'Requires Glyphs or MutatorMath source.')
    parser.add_argument('--use-afdko', action='store_true')

    parser.add_argument('-a',
                        '--autohint',
                        nargs='?',
                        const='',
                        help='Run ttfautohint. Can provide arguments, quoted')
    parser.add_argument(
        '--keep-direction',
        dest='reverse_direction',
        action='store_false',
        help='Do not reverse contour direction when output is ttf or '
        'ttf-interpolatable')
    parser.add_argument('--keep-overlaps',
                        dest='remove_overlaps',
                        action='store_false',
                        help='Do not remove any overlap.')

    group1 = parser.add_mutually_exclusive_group(required=False)
    group1.add_argument(
        '--production-names',
        dest='use_production_names',
        action='store_true',
        help='Rename glyphs with production names if available otherwise use '
        'uninames.')
    group1.add_argument('--no-production-names',
                        dest='use_production_names',
                        action='store_false')

    group2 = parser.add_mutually_exclusive_group(required=False)
    group2.add_argument('--subset',
                        dest='subset',
                        action='store_true',
                        help='Subset font using export flags set by glyphsLib')
    group2.add_argument('--no-subset', dest='subset', action='store_false')

    group3 = parser.add_mutually_exclusive_group(required=False)
    group3.add_argument('-s',
                        '--subroutinize',
                        action='store_true',
                        help='Optimize CFF table using compreffor (default)')
    group3.add_argument('-S',
                        '--no-subroutinize',
                        dest='subroutinize',
                        action='store_false')
    parser.add_argument(
        '-e',
        '--conversion-error',
        type=float,
        default=None,
        metavar='ERROR',
        help='Maximum approximation error for cubic to quadratic conversion '
        'measured in EM')

    parser.set_defaults(use_production_names=None,
                        subset=None,
                        subroutinize=True)

    parser.add_argument('--timing', action='store_true')
    parser.add_argument('--verbose', default='INFO')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'),
                          verbose=args.pop('verbose'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if sum(1 for p in [glyphs_path, ufo_paths, designspace_path] if p) != 1:
        parser.error('Exactly one source type required (Glyphs, UFO, or '
                     'MutatorMath).')

    def exclude_args(parser, args, excluded_args, source_name):
        msg = '"%s" argument only available for %s source'
        for excluded in excluded_args:
            if args[excluded]:
                parser.error(msg % (excluded, source_name))
            del args[excluded]

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)
        return

    exclude_args(parser, args, ['family_name'], 'Glyphs')
    if designspace_path:
        project.run_from_designspace(designspace_path, **args)
        return

    exclude_args(parser, args, ['interpolate', 'interpolate_binary_layout'],
                 'Glyphs or MutatorMath')
    project.run_from_ufos(ufo_paths,
                          is_instance=args.pop('masters_as_instances'),
                          **args)
Example #13
0
def write_binary_fonts(path, hand):
    ufo_path = write_ufo_font(path, foundational)
    builder = FontProject(verbose="ERROR")
    builder._output_dir = lambda *args, **kwargs: path
    builder.run_from_ufos([ufo_path], output=("otf", "ttf"), remove_overlaps=False)
Example #14
0
def main(args=None):
    parser = ArgumentParser()
    parser.add_argument("--version", action="version", version=__version__)
    inputGroup = parser.add_argument_group(
        title="Input arguments (flags)",
        description=
        "The following arguments are mutually exclusive (pick only one):",
    )
    xInputGroup = inputGroup.add_mutually_exclusive_group()
    xInputGroup.add_argument("-g",
                             "--glyphs-path",
                             metavar="GLYPHS",
                             help="Path to .glyphs source file")
    xInputGroup.add_argument(
        "-u",
        "--ufo-paths",
        nargs="+",
        metavar="UFO",
        help="One or more paths to UFO files",
    )
    xInputGroup.add_argument(
        "-m",
        "--mm-designspace",
        metavar="DESIGNSPACE",
        help="Path to .designspace file",
    )
    positionalInputs = parser.add_argument_group(
        title="Input arguments (positonal)",
        description=
        "Alternatively, guess source format from filename extension",
    )
    positionalInputs.add_argument(
        "posargs",
        nargs="*",
        metavar="INPUTS",
        help="Either one *.designspace or *.glyphs file, or one or more *.ufo",
    )

    outputGroup = parser.add_argument_group(title="Output arguments")
    outputGroup.add_argument(
        "-o",
        "--output",
        nargs="+",
        default=("otf", "ttf"),
        metavar="FORMAT",
        help=
        "Output font formats. Choose 1 or more from: %(choices)s. Default: otf, ttf. "
        "(No file paths).",
        choices=(
            "ufo",
            "otf",
            "otf-cff2",
            "ttf",
            "ttf-interpolatable",
            "otf-interpolatable",
            "variable",
            "variable-cff2",
        ),
    )
    outputSubGroup = outputGroup.add_mutually_exclusive_group()
    outputSubGroup.add_argument(
        "--output-path",
        default=None,
        help="Output font file path. Only valid when the output is a single "
        "file (e.g. input is a single UFO or output is variable font)",
    )
    outputSubGroup.add_argument(
        "--output-dir",
        default=None,
        help="Output folder. By default, output folders are created in the "
        "current working directory, grouping output fonts by format.",
    )
    outputGroup.add_argument(
        "-i",
        "--interpolate",
        nargs="?",
        default=False,
        const=True,
        metavar="INSTANCE_NAME",
        help="Interpolate masters and generate all the instances defined. "
        "To only interpolate a specific instance (or instances) that "
        'match a given "name" attribute, you can pass as argument '
        "the full instance name or a regular expression. "
        'E.g.: -i "Noto Sans Bold"; or -i ".* UI Condensed". '
        "(for Glyphs or MutatorMath sources only). ",
    )
    outputGroup.add_argument(
        "--use-mutatormath",
        action="store_true",
        help=(
            "Use MutatorMath to generate instances (supports extrapolation and "
            "anisotropic locations)."),
    )
    outputGroup.add_argument(
        "-M",
        "--masters-as-instances",
        action="store_true",
        help="Output masters as instances",
    )
    outputGroup.add_argument(
        "--family-name",
        help="Family name to use for masters, and to filter output instances",
    )
    outputGroup.add_argument(
        "--round-instances",
        dest="round_instances",
        action="store_true",
        help="Apply integer rounding to all geometry when interpolating",
    )
    outputGroup.add_argument(
        "--designspace-path",
        default=None,
        help="Path to output designspace file (for Glyphs sources only).",
    )
    outputGroup.add_argument(
        "--master-dir",
        default=None,
        help='Directory where to write master UFO. Default: "./master_ufo". '
        'If value is "{tmp}", a temporary directory is created and '
        "removed at the end (for Glyphs sources only).",
    )
    outputGroup.add_argument(
        "--instance-dir",
        default=None,
        help="Directory where to write instance UFOs. Default: "
        '"./instance_ufo". If value is "{tmp}", a temporary directory '
        "is created and removed at the end (for Glyphs sources only).",
    )
    outputGroup.add_argument(
        "--no-write-skipexportglyphs",
        action="store_false",
        dest="write_skipexportglyphs",
        help=
        "Do not store the glyph export flags in the 'public.skipExportGlyphs' "
        "key of designspace/UFO lib, but use the old private glyph lib key "
        "'com.schriftgestaltung.Glyphs.Export' (for Glyphs sources only).",
    )
    outputGroup.add_argument(
        "--validate-ufo",
        action="store_true",
        help="Enable ufoLib validation on reading/writing UFO files. It is "
        "disabled by default",
    )
    outputGroup.add_argument(
        "--check-compatibility",
        action="store_true",
        help="Check if the source files are interpolatable. It is "
        "disabled by default, but enabled when building variable fonts "
        "or what the 'Enforce Compatibility Check' custom parameter is "
        "set on a Glyphs file",
    )
    outputGroup.add_argument(
        "--expand-features-to-instances",
        action="store_true",
        help="Resolves all include()s in the master feature file and writes "
        "the full feature file to all instance UFOs. Only valid when "
        "interpolating. Use if you share feature files of masters in "
        "external files, as instances can end up elsewhere.",
    )
    outputGroup.add_argument(
        "--no-generate-GDEF",
        dest="generate_GDEF",
        action="store_false",
        help=
        "Do not auto-generate a GDEF table, but keep an existing one intact.",
    )

    contourGroup = parser.add_argument_group(title="Handling of contours")
    contourGroup.add_argument(
        "--keep-overlaps",
        dest="remove_overlaps",
        action="store_false",
        help="Do not remove any overlap.",
    )
    contourGroup.add_argument(
        "--overlaps-backend",
        dest="overlaps_backend",
        metavar="BACKEND",
        choices=("booleanOperations", "pathops"),
        default="booleanOperations",
        help="Select library to remove overlaps. Choose between: %(choices)s "
        "(default: %(default)s)",
    )
    contourGroup.add_argument(
        "--keep-direction",
        dest="reverse_direction",
        action="store_false",
        help="Do not reverse contour direction when output is ttf or "
        "ttf-interpolatable",
    )
    contourGroup.add_argument(
        "-e",
        "--conversion-error",
        type=float,
        default=None,
        metavar="ERROR",
        help="Maximum approximation error for cubic to quadratic conversion "
        "measured in EM",
    )
    contourGroup.add_argument(
        "-f",
        "--flatten-components",
        dest="flatten_components",
        action="store_true",
        help="Flatten nested components to single level.",
    )
    contourGroup.add_argument(
        "-a",
        "--autohint",
        nargs="?",
        const="",
        help="Run ttfautohint. Can provide arguments, quoted",
    )
    contourGroup.add_argument(
        "--cff-round-tolerance",
        type=float,
        default=None,
        metavar="FLOAT",
        help="Restrict rounding of point coordinates in CFF table to only "
        "those floats whose absolute difference from their integral part "
        "is less than or equal to the tolerance. By default, all floats "
        "are rounded to integer (tolerance 0.5); 0 disables rounding.",
    )
    contourGroup.add_argument(
        "--optimize-cff",
        type=lambda s: CFFOptimization(int(s)),
        default=CFFOptimization.SUBROUTINIZE,
        help="0 disables all optimizations; 1 specializes the CFF charstring "
        "operators; 2 (default) also enables subroutinization",
    )
    contourGroup.add_argument(
        "--subroutinizer",
        default=None,
        choices=["compreffor", "cffsubr"],
        help="name of the library to use for compressing CFF charstrings. "
        "Choose between: %(choices)s. By default compreffor is used for CFF 1, "
        "and cffsubr for CFF2. NOTE: compreffor doesn't support CFF2.",
    )
    contourGroup.add_argument(
        "--no-optimize-gvar",
        dest="optimize_gvar",
        action="store_false",
        help="Do not perform IUP optimization on variable font's 'gvar' table. "
        "(only works with 'variable' TrueType-flavored output)",
    )
    contourGroup.add_argument(
        "--filter",
        metavar="CLASS",
        action="append",
        dest="filter_specs",
        help="string specifying a filter class to load, either "
        "built-in or from an external module, optionally initialized with "
        "the given keyword arguments. The class and module names are "
        "separated by '::'. The option can be repeated multiple times "
        "for each filter class. The option overrides the filters specified "
        "in the UFO lib.",
    )

    layoutGroup = parser.add_argument_group(
        title="Handling of OpenType Layout")
    layoutGroup.add_argument(
        "--interpolate-binary-layout",
        nargs="?",
        default=False,
        const=True,
        metavar="MASTER_DIR",
        help="Interpolate layout tables from compiled master binaries. "
        "Requires Glyphs or MutatorMath source.",
    )
    layoutGroup.add_argument(
        "--feature-writer",
        metavar="CLASS",
        action="append",
        dest="feature_writer_specs",
        help="string specifying a feature writer class to load, either "
        "built-in or from an external module, optionally initialized with "
        "the given keyword arguments. The class and module names are "
        "separated by '::'. The option can be repeated multiple times "
        "for each writer class. A special value of 'None' will disable "
        "all automatic feature generation. The option overrides both the "
        "default ufo2ft writers and those specified in the UFO lib.",
    )
    layoutGroup.add_argument(
        "--debug-feature-file",
        metavar="FILE",
        type=FileType("w", encoding="utf-8"),
        default=None,
        help=(
            "Path were to dump OpenType features text to debug auto-generated "
            "features (kern, mark, mkmk, etc.)."),
    )

    feaCompilerGroup = layoutGroup.add_mutually_exclusive_group(required=False)
    feaCompilerGroup.add_argument(
        "--mti-source",
        help="mtiLib feature definition .plist file path (use instead of FEA)",
    )

    glyphnamesGroup = parser.add_mutually_exclusive_group(required=False)
    glyphnamesGroup.add_argument(
        "--production-names",
        dest="use_production_names",
        action="store_true",
        help="Rename glyphs with production names if available otherwise use "
        "uninames.",
    )
    glyphnamesGroup.add_argument("--no-production-names",
                                 dest="use_production_names",
                                 action="store_false")

    subsetGroup = parser.add_mutually_exclusive_group(required=False)
    subsetGroup.add_argument(
        "--subset",
        dest="subset",
        action="store_true",
        help="Subset font using export flags set by glyphsLib",
    )
    subsetGroup.add_argument("--no-subset",
                             dest="subset",
                             action="store_false")

    subroutinizeGroup = parser.add_mutually_exclusive_group(required=False)
    subroutinizeGroup.add_argument(
        "-s",
        "--subroutinize",
        action="store_true",
        help="Optimize CFF table using compreffor (default) [DEPRECATED: use "
        "--optimize-cff option instead]",
    )
    subroutinizeGroup.add_argument("-S",
                                   "--no-subroutinize",
                                   dest="subroutinize",
                                   action="store_false")

    parser.set_defaults(use_production_names=None,
                        subset=None,
                        subroutinize=None)

    logGroup = parser.add_argument_group(title="Logging arguments")
    logGroup.add_argument("--timing",
                          action="store_true",
                          help="Print the elapsed time for each steps")
    logGroup.add_argument(
        "--verbose",
        default="INFO",
        metavar="LEVEL",
        choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
        help="Configure the logger verbosity level. Choose between: "
        "%(choices)s. Default: INFO",
    )

    args = vars(parser.parse_args(args))

    specs = args.pop("feature_writer_specs")
    if specs is not None:
        args["feature_writers"] = _loadFeatureWriters(parser, specs)

    specs = args.pop("filter_specs")
    if specs is not None:
        args["filters"] = _loadFilters(parser, specs)

    inputs = parse_mutually_exclusive_inputs(parser, args)

    if INTERPOLATABLE_OUTPUTS.intersection(args["output"]):
        if not (inputs.glyphs_path or inputs.designspace_path):
            parser.error(
                "Glyphs or designspace source required for variable font")
        exclude_args(
            parser,
            args,
            [
                "interpolate",
                "masters_as_instances",
                "interpolate_binary_layout",
                "use_mutatormath",
            ],
            "variable output",
        )
    else:
        exclude_args(parser,
                     args, ["optimize_gvar"],
                     "static output",
                     positive=False)

    if args.get("use_mutatormath"):
        for module in ("defcon", "mutatorMath"):
            try:
                __import__(module)
            except ImportError:
                parser.error(
                    f"{module} module not found; reinstall fontmake with the "
                    "[mutatormath] extra")

    PRINT_TRACEBACK = args.get("verbose", "INFO") == "DEBUG"
    try:
        project = FontProject(
            timing=args.pop("timing"),
            verbose=args.pop("verbose"),
            validate_ufo=args.pop("validate_ufo"),
        )

        if inputs.glyphs_path:
            with _make_tempdirs(parser, args):
                project.run_from_glyphs(inputs.glyphs_path, **args)
            return

        exclude_args(
            parser,
            args,
            [
                "family_name",
                "mti_source",
                "designspace_path",
                "master_dir",
                "instance_dir",
            ],
            inputs.format_name,
        )
        exclude_args(parser,
                     args, ["write_skipexportglyphs"],
                     inputs.format_name,
                     positive=False)
        if inputs.designspace_path:
            project.run_from_designspace(inputs.designspace_path, **args)
            return

        exclude_args(
            parser,
            args,
            [
                "interpolate",
                "use_mutatormath",
                "interpolate_binary_layout",
                "round_instances",
                "expand_features_to_instances",
                "check_compatibility",
            ],
            inputs.format_name,
        )
        project.run_from_ufos(inputs.ufo_paths,
                              is_instance=args.pop("masters_as_instances"),
                              **args)
    except FontmakeError as e:
        if PRINT_TRACEBACK:
            logging.exception(e)
            sys.exit(1)
        sys.exit(f"fontmake: Error: {str(e)}")
    finally:
        debug_feature_file = args.get("debug_feature_file")
        if debug_feature_file is not None:
            debug_feature_file.close()
Example #15
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument(
        '-o', '--output', nargs='+', default=('otf', 'ttf'),
        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable', 'variable'))

    parser.add_argument('-i', '--interpolate', action='store_true',
                        help='interpolate masters (for Glyphs or MutatorMath '
                             'sources only)')
    parser.add_argument('-M', '--masters-as-instances', action='store_true',
                        help='treat masters as instances')
    parser.add_argument('-a', '--autohint', nargs='?', const="",
                        help='can provide arguments to ttfautohint, quoted')
    parser.add_argument('--mti-source')
    parser.add_argument('--family-name', help='Family name to use for masters,'
                        ' and to filter output instances by')
    parser.add_argument('--use-afdko', action='store_true')
    parser.add_argument('--keep-direction', dest="reverse_direction",
                        action='store_false', help='Do not reverse contour '
                        'direction when output is ttf or ttf-interpolatable')
    parser.add_argument('--keep-overlaps', dest="remove_overlaps",
                        action='store_false',
                        help='Do not remove any overlap.')
    group1 = parser.add_mutually_exclusive_group(required=False)
    group1.add_argument('--production-names', dest='use_production_names',
                        action='store_true', help='Rename glyphs with '
                        'production names if available otherwise use uninames.')
    group1.add_argument('--no-production-names', dest='use_production_names',
                        action='store_false')
    group2 = parser.add_mutually_exclusive_group(required=False)
    group2.add_argument('--subset', dest='subset',
                        action='store_true', help='Subset font using export '
                        'flags set by glyphsLib')
    group2.add_argument('--no-subset', dest='subset', action='store_false')
    group3 = parser.add_mutually_exclusive_group(required=False)
    group3.add_argument('-s', '--subroutinize', action='store_true',
                        help='Optimize CFF table using compreffor (default)')
    group3.add_argument('-S', '--no-subroutinize', dest='subroutinize',
                        action='store_false')
    parser.add_argument('-e', '--conversion-error', type=float, default=None,
                        metavar='ERROR', help="Maximum approximation error for"
                        " cubic to quadratic conversion measured in EM")
    parser.set_defaults(use_production_names=None, subset=None,
                        subroutinize=True)
    parser.add_argument('--timing', action='store_true')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if not sum(1 for p in [glyphs_path, ufo_paths, designspace_path] if p) == 1:
        parser.error('Exactly one source type required (Glyphs, UFO, or '
                     'MutatorMath).')

    def exclude_args(parser, args, excluded_args):
        exclusive_msg = '"{}" argument only available for {} source'
        exclusive_src = {'interpolate': 'Glyphs or MutatorMath',
                         'family_name': 'Glyphs'}
        for excluded in excluded_args:
            if args[excluded]:
                parser.error(exclusive_msg.format(
                    excluded,
                    exclusive_src[excluded]))
            del args[excluded]

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)

    elif designspace_path:
        exclude_args(parser, args, ['family_name'])
        project.run_from_designspace(designspace_path, **args)

    else:
        exclude_args(parser, args, ['family_name', 'interpolate'])

    if ufo_paths:
        project.run_from_ufos(
            ufo_paths, is_instance=args.pop('masters_as_instances'), **args)
Example #16
0
def main(args=None):
    parser = ArgumentParser()
    parser.add_argument('--version', action='version', version=__version__)
    inputGroup = parser.add_argument_group(
        title='Input arguments',
        description='The following arguments are mutually exclusive.')
    xInputGroup = inputGroup.add_mutually_exclusive_group(required=True)
    xInputGroup.add_argument(
        '-g', '--glyphs-path', metavar='GLYPHS',
        help='Path to .glyphs source file')
    xInputGroup.add_argument(
        '-u', '--ufo-paths', nargs='+', metavar='UFO',
        help='One or more paths to UFO files')
    xInputGroup.add_argument(
        '-m', '--mm-designspace', metavar='DESIGNSPACE',
        help='Path to .designspace file')

    outputGroup = parser.add_argument_group(title='Output arguments')
    outputGroup.add_argument(
        '-o', '--output', nargs='+', default=('otf', 'ttf'), metavar="FORMAT",
        help='Output font formats. Choose between: %(choices)s. '
             'Default: otf, ttf',
        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable', 'variable'))
    outputGroup.add_argument(
        '-i', '--interpolate', action='store_true',
        help='Interpolate masters (for Glyphs or MutatorMath sources only)')
    outputGroup.add_argument(
        '-M', '--masters-as-instances', action='store_true',
        help='Output masters as instances')
    outputGroup.add_argument(
        '--family-name',
        help='Family name to use for masters, and to filter output instances')

    contourGroup = parser.add_argument_group(title='Handling of contours')
    contourGroup.add_argument(
        '--keep-overlaps', dest='remove_overlaps', action='store_false',
        help='Do not remove any overlap.')
    contourGroup.add_argument(
        '--keep-direction', dest='reverse_direction', action='store_false',
        help='Do not reverse contour direction when output is ttf or '
             'ttf-interpolatable')
    contourGroup.add_argument(
        '-e', '--conversion-error', type=float, default=None, metavar='ERROR',
        help='Maximum approximation error for cubic to quadratic conversion '
             'measured in EM')
    contourGroup.add_argument(
        '-a', '--autohint', nargs='?', const='',
        help='Run ttfautohint. Can provide arguments, quoted')

    layoutGroup = parser.add_argument_group(title='Handling of OpenType Layout')
    layoutGroup.add_argument(
        '--interpolate-binary-layout', action='store_true',
        help='Interpolate layout tables from compiled master binaries. '
             'Requires Glyphs or MutatorMath source.')
    layoutGroup.add_argument(
        '--use-afdko', action='store_true',
        help='Use makeOTF instead of feaLib to compile FEA.')
    layoutGroup.add_argument(
        '--mti-source',
        help='Path to mtiLib .txt feature definitions (use instead of FEA)')
    layoutGroup.add_argument(
        '--kern-writer-module', metavar="MODULE", dest='kern_writer_class',
        type=PyClassType('KernFeatureWriter'),
        help='Module containing a custom `KernFeatureWriter` class.')
    layoutGroup.add_argument(
        '--mark-writer-module', metavar="MODULE", dest='mark_writer_class',
        type=PyClassType('MarkFeatureWriter'),
        help='Module containing a custom `MarkFeatureWriter` class.')

    glyphnamesGroup = parser.add_mutually_exclusive_group(required=False)
    glyphnamesGroup.add_argument(
        '--production-names', dest='use_production_names', action='store_true',
        help='Rename glyphs with production names if available otherwise use '
             'uninames.')
    glyphnamesGroup.add_argument(
        '--no-production-names', dest='use_production_names',
        action='store_false')

    subsetGroup = parser.add_mutually_exclusive_group(required=False)
    subsetGroup.add_argument(
        '--subset', dest='subset', action='store_true',
        help='Subset font using export flags set by glyphsLib')
    subsetGroup.add_argument(
        '--no-subset', dest='subset', action='store_false')

    subroutinizeGroup = parser.add_mutually_exclusive_group(required=False)
    subroutinizeGroup.add_argument(
        '-s', '--subroutinize', action='store_true',
        help='Optimize CFF table using compreffor (default)')
    subroutinizeGroup.add_argument(
        '-S', '--no-subroutinize', dest='subroutinize', action='store_false')

    parser.set_defaults(use_production_names=None, subset=None,
                        subroutinize=True)

    logGroup = parser.add_argument_group(title='Logging arguments')
    logGroup.add_argument(
        '--timing', action='store_true',
        help="Print the elapsed time for each steps")
    logGroup.add_argument(
        '--verbose', default='INFO', metavar='LEVEL',
        choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'),
        help='Configure the logger verbosity level. Choose between: '
             '%(choices)s. Default: INFO')
    args = vars(parser.parse_args(args))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')

    if 'variable' in args['output']:
        if not (glyphs_path or designspace_path):
            parser.error(
                'Glyphs or designspace source required for variable font')
        for argname in ('interpolate', 'masters_as_instances',
                        'interpolate_binary_layout'):
            if args[argname]:
                parser.error('--%s option invalid for variable font'
                             % argname.replace("_", "-"))

    project = FontProject(timing=args.pop('timing'),
                          verbose=args.pop('verbose'))

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)
        return

    exclude_args(parser, args, ['family_name'], 'Glyphs')
    if designspace_path:
        project.run_from_designspace(designspace_path, **args)
        return

    exclude_args(
        parser, args, ['interpolate', 'interpolate_binary_layout'],
        'Glyphs or MutatorMath')
    project.run_from_ufos(
        ufo_paths, is_instance=args.pop('masters_as_instances'), **args)
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument('-o',
                        '--output',
                        nargs='+',
                        default=('otf', 'ttf'),
                        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable'))
    parser.add_argument('-i',
                        '--interpolate',
                        action='store_true',
                        help='interpolate masters (for Glyphs or MutatorMath '
                        'sources only)')
    parser.add_argument('-mi',
                        '--masters-as-instances',
                        action='store_true',
                        help='treat masters as instances')
    parser.add_argument('-a',
                        '--autohint',
                        nargs='?',
                        help='can provide arguments to ttfautohint, quoted')
    parser.add_argument('--mti-source')
    parser.add_argument('--family-name',
                        help='Family name to use for masters,'
                        ' and to filter output instances by')
    parser.add_argument('--use-afdko', action='store_true')
    parser.add_argument('--keep-direction',
                        dest="reverse_direction",
                        action='store_false',
                        help='Do not reverse contour '
                        'direction when output is ttf or ttf-interpolatable')
    parser.add_argument('--keep-overlaps',
                        dest="remove_overlaps",
                        action='store_false',
                        help='Do not remove any overlap.')
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--production-names',
                       dest='use_production_names',
                       action='store_true',
                       help='Rename glyphs with '
                       'production names if available otherwise use uninames.')
    group.add_argument('--no-production-names',
                       dest='use_production_names',
                       action='store_false',
                       help='Do not rename glyphs with production names. '
                       'Keeps original glyph names')
    parser.set_defaults(use_production_names=None)
    parser.add_argument('--timing', action='store_true')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if not sum(1
               for p in [glyphs_path, ufo_paths, designspace_path] if p) == 1:
        parser.error('Exactly one source type required (Glyphs, UFO, or '
                     'MutatorMath).')

    def exclude_args(parser, args, excluded_args):
        exclusive_msg = '"{}" argument only available for {} source'
        exclusive_src = {
            'interpolate': 'Glyphs or MutatorMath',
            'family_name': 'Glyphs'
        }
        for excluded in excluded_args:
            if args[excluded]:
                parser.error(
                    exclusive_msg.format(excluded, exclusive_src[excluded]))
            del args[excluded]

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)

    elif designspace_path:
        exclude_args(parser, args, ['family_name'])
        project.run_from_designspace(designspace_path, **args)

    else:
        exclude_args(parser, args, ['family_name', 'interpolate'])

    if ufo_paths:
        project.run_from_ufos(ufo_paths,
                              is_instance=args.pop('masters_as_instances'),
                              **args)
Example #18
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument('-o', '--output', nargs='+', default=('otf', 'ttf'),
                        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable'))
    parser.add_argument('-i', '--interpolate', action='store_true',
                        help='interpolate masters (for Glyphs or MutatorMath '
                             'sources only)')
    parser.add_argument('-mi', '--masters-as-instances', action='store_true',
                        help='treat masters as instances')
    parser.add_argument('-a', '--autohint', nargs='?',
                        help='can provide arguments to ttfautohint, quoted')
    parser.add_argument('--mti-source')
    parser.add_argument('--family-name', help='Family name to use for masters,'
                        ' and to filter output instances by')
    parser.add_argument('--use-afdko', action='store_true')
    parser.add_argument('--keep-direction', dest="reverse_direction",
                        action='store_false', help='Do not reverse contour '
                        'direction when output is ttf or ttf-interpolatable')
    parser.add_argument('--keep-overlaps', dest="remove_overlaps",
                        action='store_false',
                        help='Do not remove any overlap.')
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--production-names', dest='use_production_names',
                       action='store_true', help='Rename glyphs with '
                       'production names if available otherwise use uninames.')
    group.add_argument('--no-production-names', dest='use_production_names',
                       action='store_false',
                       help='Do not rename glyphs with production names. '
                       'Keeps original glyph names')
    parser.set_defaults(use_production_names=None)
    parser.add_argument('--timing', action='store_true')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if not sum(1 for p in [glyphs_path, ufo_paths, designspace_path] if p) == 1:
        parser.error('Exactly one source type required (Glyphs, UFO, or '
                     'MutatorMath).')

    def exclude_args(parser, args, excluded_args):
        exclusive_msg = '"{}" argument only available for {} source'
        exclusive_src = {'interpolate': 'Glyphs or MutatorMath',
                         'family_name': 'Glyphs'}
        for excluded in excluded_args:
            if args[excluded]:
                parser.error(exclusive_msg.format(
                    excluded,
                    exclusive_src[excluded]))
            del args[excluded]

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)

    elif designspace_path:
        exclude_args(parser, args, ['family_name'])
        project.run_from_designspace(designspace_path, **args)

    else:
        exclude_args(parser, args, ['family_name', 'interpolate'])

    if ufo_paths:
        project.run_from_ufos(
            ufo_paths, is_instance=args.pop('masters_as_instances'), **args)
Example #19
0
baseDir = os.path.split(__file__)[0]

if makeTTF:

    # gather UFOs from build script parent folder
    paths = []
    for filename in os.listdir(os.path.abspath(baseDir)):
        if filename.endswith('.ufo'):
            paths.append(filename)

    # make fontmake project
    project = FontProject()
    project.run_from_ufos(paths,
                          output=("ttf"),
                          remove_overlaps=False,
                          reverse_direction=False,
                          use_production_names=False)

if makeWOFF:

    #define woff and woff2 tools
    sfnt2woffPath = 'sfnt2woff-zopfli'
    sfnt2woff2Path = 'woff2_compress'
    returnedInfo = os.system(sfnt2woffPath)
    returnedInfo2 = os.system(sfnt2woff2Path)
    # hack for hardcoded absolute paths as fallback, sorry!
    if returnedInfo == 32512:
        sfnt2woffPath = u"~/Documents/Tools/fontGenerate/sfnt2woff-zopfli/sfnt2woff-zopfli"
        sfnt2woff2Path = u"~/Documents/Tools/fontGenerate/woff2/woff2_compress"
Example #20
0
from pathlib import Path

SCRIPTING_DIR = Path(__file__).parent

INPUT_DIR = SCRIPTING_DIR / "eac"
UFO_PATH = INPUT_DIR / "variants" / "variants.ufo"
FEA_PATH = INPUT_DIR / "otl.fea"

OUTPUT_DIR = SCRIPTING_DIR / "products"
FAMILY_NAME = "Sandbox EAC"
FORMAT = "otf"

ufo = defcon.Font(path=UFO_PATH)

ufo.info.familyName = FAMILY_NAME
ufo.info.styleName = "Regular"

project = FontProject()

font_name = project._font_name(ufo)
output_path = project._output_path(font_name, FORMAT, output_dir=OUTPUT_DIR)

project.run_from_ufos(
    [ufo],
    output=[FORMAT],
    # For .save_otfs:
    remove_overlaps=False,
    output_path=output_path,
    debug_feature_file=(OUTPUT_DIR / "debug.fea").open("w")
)
Example #21
0
    os.makedirs("master_ttf")
if os.path.exists("master_ufo"):
    shutil.rmtree("master_ufo", ignore_errors=True)
    os.makedirs("master_ufo")
if os.path.exists("master_ttf_interpolatable"):
    shutil.rmtree("master_ttf_interpolatable", ignore_errors=True)
    os.makedirs("master_ttf_interpolatable")

if not os.path.exists("fonts"):
    os.makedirs("fonts")

project = FontProject()
project.run_from_ufos(
    romans,
    output=("ttf-interpolatable"
            ),  # FIXME this also build master_ttf and should not.
    remove_overlaps=False,
    reverse_direction=False,
    use_production_names=False)

#designSpace = "sources/Amstelvar-NewCharset/Amstelvar-Roman-006.designspace"
designSpace = "sources/Amstelvar-NewCharset/Amstelvar-Roman-009.designspace"

#outfile = "Amstelvar-Roman-VF.ttf"
outfile = "fonts/Amstelvar-Roman-VF.ttf"

finder = lambda s: s.replace("sources/Amstelvar-NewCharset/Roman/",
                             "master_ttf/").replace(".ufo", ".ttf")

varfont, _, _ = build(designSpace, finder)
print("Saving Variable Font...")
Example #22
0
def main(args=None):
    parser = ArgumentParser()
    parser.add_argument('--version', action='version', version=__version__)
    inputGroup = parser.add_argument_group(
        title='Input arguments',
        description='The following arguments are mutually exclusive.')
    xInputGroup = inputGroup.add_mutually_exclusive_group(required=True)
    xInputGroup.add_argument('-g',
                             '--glyphs-path',
                             metavar='GLYPHS',
                             help='Path to .glyphs source file')
    xInputGroup.add_argument('-u',
                             '--ufo-paths',
                             nargs='+',
                             metavar='UFO',
                             help='One or more paths to UFO files')
    xInputGroup.add_argument('-m',
                             '--mm-designspace',
                             metavar='DESIGNSPACE',
                             help='Path to .designspace file')

    outputGroup = parser.add_argument_group(title='Output arguments')
    outputGroup.add_argument(
        '-o',
        '--output',
        nargs='+',
        default=('otf', 'ttf'),
        metavar="FORMAT",
        help='Output font formats. Choose between: %(choices)s. '
        'Default: otf, ttf',
        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable', 'variable'))
    outputSubGroup = outputGroup.add_mutually_exclusive_group()
    outputSubGroup.add_argument(
        '--output-path',
        default=None,
        help="Output font file path. Only valid when the output is a single "
        "file (e.g. input is a single UFO or output is variable font)")
    outputSubGroup.add_argument(
        '--output-dir',
        default=None,
        help="Output folder. By default, output folders are created in the "
        "current working directory, grouping output fonts by format.")
    outputGroup.add_argument(
        '-i',
        '--interpolate',
        nargs="?",
        default=False,
        const=True,
        metavar="INSTANCE_NAME",
        help='Interpolate masters and generate all the instances defined. '
        'To only interpolate a specific instance (or instances) that '
        'match a given "name" attribute, you can pass as argument '
        'the full instance name or a regular expression. '
        'E.g.: -i "Noto Sans Bold"; or -i ".* UI Condensed". '
        '(for Glyphs or MutatorMath sources only). ')
    outputGroup.add_argument('-M',
                             '--masters-as-instances',
                             action='store_true',
                             help='Output masters as instances')
    outputGroup.add_argument(
        '--family-name',
        help='Family name to use for masters, and to filter output instances')
    outputGroup.add_argument(
        '--round-instances',
        dest='round_instances',
        action='store_true',
        help='Apply integer rounding to all geometry when interpolating')
    outputGroup.add_argument(
        '--designspace-path',
        default=None,
        help='Path to output designspace file (for Glyphs sources only).')
    outputGroup.add_argument(
        '--master-dir',
        default=None,
        help='Directory where to write master UFO. Default: "./master_ufo". '
        'If value is "{tmp}", a temporary directory is created and '
        'removed at the end (for Glyphs sources only).')
    outputGroup.add_argument(
        '--instance-dir',
        default=None,
        help='Directory where to write instance UFOs. Default: '
        '"./instance_ufo". If value is "{tmp}", a temporary directory '
        'is created and removed at the end (for Glyphs sources only).')

    contourGroup = parser.add_argument_group(title='Handling of contours')
    contourGroup.add_argument('--keep-overlaps',
                              dest='remove_overlaps',
                              action='store_false',
                              help='Do not remove any overlap.')
    contourGroup.add_argument(
        '--keep-direction',
        dest='reverse_direction',
        action='store_false',
        help='Do not reverse contour direction when output is ttf or '
        'ttf-interpolatable')
    contourGroup.add_argument(
        '-e',
        '--conversion-error',
        type=float,
        default=None,
        metavar='ERROR',
        help='Maximum approximation error for cubic to quadratic conversion '
        'measured in EM')
    contourGroup.add_argument(
        '-a',
        '--autohint',
        nargs='?',
        const='',
        help='Run ttfautohint. Can provide arguments, quoted')
    contourGroup.add_argument(
        '--cff-round-tolerance',
        type=float,
        default=None,
        metavar='FLOAT',
        help='Restrict rounding of point coordinates in CFF table to only '
        'those floats whose absolute difference from their integral part '
        'is less than or equal to the tolerance. By default, all floats '
        'are rounded to integer (tolerance 0.5); 0 disables rounding.')

    layoutGroup = parser.add_argument_group(
        title='Handling of OpenType Layout')
    layoutGroup.add_argument(
        '--interpolate-binary-layout',
        nargs="?",
        default=False,
        const=True,
        metavar="MASTER_DIR",
        help='Interpolate layout tables from compiled master binaries. '
        'Requires Glyphs or MutatorMath source.')
    layoutGroup.add_argument(
        "--feature-writer",
        metavar="CLASS",
        action="append",
        dest="feature_writer_specs",
        help="string specifying a feature writer class to load, either "
        "built-in or from an external module, optionally initialized with "
        "the given keyword arguments. The class and module names are "
        "separated by '::'. The option can be repeated multiple times "
        "for each writer class. A special value of 'None' will disable "
        "all automatic feature generation. The option overrides both the "
        "default ufo2ft writers and those specified in the UFO lib.")

    feaCompilerGroup = layoutGroup.add_mutually_exclusive_group(required=False)
    feaCompilerGroup.add_argument(
        '--use-afdko',
        action='store_true',
        help='Use makeOTF instead of feaLib to compile FEA.')
    feaCompilerGroup.add_argument(
        '--mti-source',
        help='Path to mtiLib .txt feature definitions (use instead of FEA)')

    glyphnamesGroup = parser.add_mutually_exclusive_group(required=False)
    glyphnamesGroup.add_argument(
        '--production-names',
        dest='use_production_names',
        action='store_true',
        help='Rename glyphs with production names if available otherwise use '
        'uninames.')
    glyphnamesGroup.add_argument('--no-production-names',
                                 dest='use_production_names',
                                 action='store_false')

    subsetGroup = parser.add_mutually_exclusive_group(required=False)
    subsetGroup.add_argument(
        '--subset',
        dest='subset',
        action='store_true',
        help='Subset font using export flags set by glyphsLib')
    subsetGroup.add_argument('--no-subset',
                             dest='subset',
                             action='store_false')

    subroutinizeGroup = parser.add_mutually_exclusive_group(required=False)
    subroutinizeGroup.add_argument(
        '-s',
        '--subroutinize',
        action='store_true',
        help='Optimize CFF table using compreffor (default)')
    subroutinizeGroup.add_argument('-S',
                                   '--no-subroutinize',
                                   dest='subroutinize',
                                   action='store_false')

    parser.set_defaults(use_production_names=None,
                        subset=None,
                        subroutinize=True)

    logGroup = parser.add_argument_group(title='Logging arguments')
    logGroup.add_argument('--timing',
                          action='store_true',
                          help="Print the elapsed time for each steps")
    logGroup.add_argument(
        '--verbose',
        default='INFO',
        metavar='LEVEL',
        choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'),
        help='Configure the logger verbosity level. Choose between: '
        '%(choices)s. Default: INFO')
    args = vars(parser.parse_args(args))

    specs = args.pop("feature_writer_specs")
    if specs is not None:
        args["feature_writers"] = _loadFeatureWriters(parser, specs)

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    input_format = ("Glyphs" if glyphs_path else
                    "designspace" if designspace_path else "UFO") + " source"

    if 'variable' in args['output']:
        if not (glyphs_path or designspace_path):
            parser.error(
                'Glyphs or designspace source required for variable font')
        exclude_args(parser, args, [
            'interpolate', 'masters_as_instances', 'interpolate_binary_layout'
        ], "variable output")

    try:
        project = FontProject(timing=args.pop('timing'),
                              verbose=args.pop('verbose'))

        if glyphs_path:
            with _make_tempdirs(parser, args):
                project.run_from_glyphs(glyphs_path, **args)
            return

        exclude_args(parser, args, [
            'family_name', 'mti_source', 'designspace_path', 'master_dir',
            'instance_dir'
        ], input_format)
        if designspace_path:
            project.run_from_designspace(designspace_path, **args)
            return

        exclude_args(
            parser, args,
            ['interpolate', 'interpolate_binary_layout', 'round_instances'],
            input_format)
        project.run_from_ufos(ufo_paths,
                              is_instance=args.pop('masters_as_instances'),
                              **args)
    except FontmakeError as e:
        import sys
        sys.exit("fontmake: error: %s" % e)
Example #23
0
def main(args=None):
    parser = ArgumentParser()
    parser.add_argument('--version', action='version', version=__version__)
    inputGroup = parser.add_argument_group(
        title='Input arguments',
        description='The following arguments are mutually exclusive.')
    xInputGroup = inputGroup.add_mutually_exclusive_group(required=True)
    xInputGroup.add_argument(
        '-g', '--glyphs-path', metavar='GLYPHS',
        help='Path to .glyphs source file')
    xInputGroup.add_argument(
        '-u', '--ufo-paths', nargs='+', metavar='UFO',
        help='One or more paths to UFO files')
    xInputGroup.add_argument(
        '-m', '--mm-designspace', metavar='DESIGNSPACE',
        help='Path to .designspace file')

    outputGroup = parser.add_argument_group(title='Output arguments')
    outputGroup.add_argument(
        '-o', '--output', nargs='+', default=('otf', 'ttf'), metavar="FORMAT",
        help='Output font formats. Choose between: %(choices)s. '
             'Default: otf, ttf',
        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable', 'variable'))
    outputGroup.add_argument(
        '-i', '--interpolate', action='store_true',
        help='Interpolate masters (for Glyphs or MutatorMath sources only)')
    outputGroup.add_argument(
        '-M', '--masters-as-instances', action='store_true',
        help='Output masters as instances')
    outputGroup.add_argument(
        '--family-name',
        help='Family name to use for masters, and to filter output instances')

    contourGroup = parser.add_argument_group(title='Handling of contours')
    contourGroup.add_argument(
        '--keep-overlaps', dest='remove_overlaps', action='store_false',
        help='Do not remove any overlap.')
    contourGroup.add_argument(
        '--keep-direction', dest='reverse_direction', action='store_false',
        help='Do not reverse contour direction when output is ttf or '
             'ttf-interpolatable')
    contourGroup.add_argument(
        '-e', '--conversion-error', type=float, default=None, metavar='ERROR',
        help='Maximum approximation error for cubic to quadratic conversion '
             'measured in EM')
    contourGroup.add_argument(
        '-a', '--autohint', nargs='?', const='',
        help='Run ttfautohint. Can provide arguments, quoted')

    layoutGroup = parser.add_argument_group(title='Handling of OpenType Layout')
    layoutGroup.add_argument(
        '--interpolate-binary-layout', action='store_true',
        help='Interpolate layout tables from compiled master binaries. '
             'Requires Glyphs or MutatorMath source.')
    layoutGroup.add_argument(
        '--kern-writer-module', metavar="MODULE", dest='kern_writer_class',
        type=PyClassType('KernFeatureWriter'),
        help='Module containing a custom `KernFeatureWriter` class.')
    layoutGroup.add_argument(
        '--mark-writer-module', metavar="MODULE", dest='mark_writer_class',
        type=PyClassType('MarkFeatureWriter'),
        help='Module containing a custom `MarkFeatureWriter` class.')
    feaCompilerGroup = layoutGroup.add_mutually_exclusive_group(required=False)
    feaCompilerGroup.add_argument(
        '--use-afdko', action='store_true',
        help='Use makeOTF instead of feaLib to compile FEA.')
    feaCompilerGroup.add_argument(
        '--mti-source',
        help='Path to mtiLib .txt feature definitions (use instead of FEA)')

    glyphnamesGroup = parser.add_mutually_exclusive_group(required=False)
    glyphnamesGroup.add_argument(
        '--production-names', dest='use_production_names', action='store_true',
        help='Rename glyphs with production names if available otherwise use '
             'uninames.')
    glyphnamesGroup.add_argument(
        '--no-production-names', dest='use_production_names',
        action='store_false')

    subsetGroup = parser.add_mutually_exclusive_group(required=False)
    subsetGroup.add_argument(
        '--subset', dest='subset', action='store_true',
        help='Subset font using export flags set by glyphsLib')
    subsetGroup.add_argument(
        '--no-subset', dest='subset', action='store_false')

    subroutinizeGroup = parser.add_mutually_exclusive_group(required=False)
    subroutinizeGroup.add_argument(
        '-s', '--subroutinize', action='store_true',
        help='Optimize CFF table using compreffor (default)')
    subroutinizeGroup.add_argument(
        '-S', '--no-subroutinize', dest='subroutinize', action='store_false')

    parser.set_defaults(use_production_names=None, subset=None,
                        subroutinize=True)

    logGroup = parser.add_argument_group(title='Logging arguments')
    logGroup.add_argument(
        '--timing', action='store_true',
        help="Print the elapsed time for each steps")
    logGroup.add_argument(
        '--verbose', default='INFO', metavar='LEVEL',
        choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'),
        help='Configure the logger verbosity level. Choose between: '
             '%(choices)s. Default: INFO')
    args = vars(parser.parse_args(args))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')

    if 'variable' in args['output']:
        if not (glyphs_path or designspace_path):
            parser.error(
                'Glyphs or designspace source required for variable font')
        for argname in ('interpolate', 'masters_as_instances',
                        'interpolate_binary_layout'):
            if args[argname]:
                parser.error('--%s option invalid for variable font'
                             % argname.replace("_", "-"))

    project = FontProject(timing=args.pop('timing'),
                          verbose=args.pop('verbose'))

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)
        return

    exclude_args(parser, args, ['family_name', 'mti_source'], 'Glyphs')
    if designspace_path:
        project.run_from_designspace(designspace_path, **args)
        return

    exclude_args(
        parser, args, ['interpolate', 'interpolate_binary_layout'],
        'Glyphs or MutatorMath')
    project.run_from_ufos(
        ufo_paths, is_instance=args.pop('masters_as_instances'), **args)
Example #24
0
def main():
    parser = ArgumentParser()
    parser.add_argument('-g', '--glyphs-path')
    parser.add_argument('-u', '--ufo-paths', nargs='+')
    parser.add_argument('-m', '--mm-designspace')
    parser.add_argument(
        '-o', '--output', nargs='+', default=('otf', 'ttf'),
        choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable', 'variable'))

    parser.add_argument(
        '-i', '--interpolate', action='store_true',
        help='Interpolate masters (for Glyphs or MutatorMath sources only)')
    parser.add_argument(
        '-M', '--masters-as-instances', action='store_true',
        help='Output masters as instances')
    parser.add_argument(
        '--family-name',
        help='Family name to use for masters, and to filter output instances')

    parser.add_argument(
        '--mti-source')
    parser.add_argument(
        '--interpolate-binary-layout', action='store_true',
        help='Interpolate layout tables from compiled master binaries. '
             'Requires Glyphs or MutatorMath source.')
    parser.add_argument(
        '--use-afdko', action='store_true')

    parser.add_argument(
        '-a', '--autohint', nargs='?', const='',
        help='Run ttfautohint. Can provide arguments, quoted')
    parser.add_argument(
        '--keep-direction', dest='reverse_direction', action='store_false',
        help='Do not reverse contour direction when output is ttf or '
             'ttf-interpolatable')
    parser.add_argument(
        '--keep-overlaps', dest='remove_overlaps', action='store_false',
        help='Do not remove any overlap.')

    group1 = parser.add_mutually_exclusive_group(required=False)
    group1.add_argument(
        '--production-names', dest='use_production_names', action='store_true',
        help='Rename glyphs with production names if available otherwise use '
             'uninames.')
    group1.add_argument(
        '--no-production-names', dest='use_production_names',
        action='store_false')

    group2 = parser.add_mutually_exclusive_group(required=False)
    group2.add_argument(
        '--subset', dest='subset', action='store_true',
        help='Subset font using export flags set by glyphsLib')
    group2.add_argument(
        '--no-subset', dest='subset', action='store_false')

    group3 = parser.add_mutually_exclusive_group(required=False)
    group3.add_argument(
        '-s', '--subroutinize', action='store_true',
        help='Optimize CFF table using compreffor (default)')
    group3.add_argument(
        '-S', '--no-subroutinize', dest='subroutinize', action='store_false')
    parser.add_argument(
        '-e', '--conversion-error', type=float, default=None, metavar='ERROR',
        help='Maximum approximation error for cubic to quadratic conversion '
             'measured in EM')

    parser.set_defaults(use_production_names=None, subset=None,
                        subroutinize=True)

    parser.add_argument('--timing', action='store_true')
    parser.add_argument('--verbose', default='INFO')
    args = vars(parser.parse_args())

    project = FontProject(timing=args.pop('timing'),
                          verbose=args.pop('verbose'))

    glyphs_path = args.pop('glyphs_path')
    ufo_paths = args.pop('ufo_paths')
    designspace_path = args.pop('mm_designspace')
    if sum(1 for p in [glyphs_path, ufo_paths, designspace_path] if p) != 1:
        parser.error('Exactly one source type required (Glyphs, UFO, or '
                     'MutatorMath).')

    def exclude_args(parser, args, excluded_args, source_name):
        msg = '"%s" argument only available for %s source'
        for excluded in excluded_args:
            if args[excluded]:
                parser.error(msg % (excluded, source_name))
            del args[excluded]

    if glyphs_path:
        project.run_from_glyphs(glyphs_path, **args)
        return

    exclude_args(parser, args, ['family_name'], 'Glyphs')
    if designspace_path:
        project.run_from_designspace(designspace_path, **args)
        return

    exclude_args(
        parser, args, ['interpolate', 'interpolate_binary_layout'],
        'Glyphs or MutatorMath')
    project.run_from_ufos(
        ufo_paths, is_instance=args.pop('masters_as_instances'), **args)