| import os |
| import argparse |
| import logging |
| import shutil |
| import multiprocessing as mp |
| from contextlib import closing |
| from functools import partial |
| |
| import fontTools |
| from .ufo import font_to_quadratic, fonts_to_quadratic |
| |
| ufo_module = None |
| try: |
| import ufoLib2 as ufo_module |
| except ImportError: |
| try: |
| import defcon as ufo_module |
| except ImportError as e: |
| pass |
| |
| |
| logger = logging.getLogger("fontTools.cu2qu") |
| |
| |
| def _cpu_count(): |
| try: |
| return mp.cpu_count() |
| except NotImplementedError: # pragma: no cover |
| return 1 |
| |
| |
| def _font_to_quadratic(input_path, output_path=None, **kwargs): |
| ufo = ufo_module.Font(input_path) |
| logger.info('Converting curves for %s', input_path) |
| if font_to_quadratic(ufo, **kwargs): |
| logger.info("Saving %s", output_path) |
| if output_path: |
| ufo.save(output_path) |
| else: |
| ufo.save() # save in-place |
| elif output_path: |
| _copytree(input_path, output_path) |
| |
| |
| def _samepath(path1, path2): |
| # TODO on python3+, there's os.path.samefile |
| path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1))) |
| path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2))) |
| return path1 == path2 |
| |
| |
| def _copytree(input_path, output_path): |
| if _samepath(input_path, output_path): |
| logger.debug("input and output paths are the same file; skipped copy") |
| return |
| if os.path.exists(output_path): |
| shutil.rmtree(output_path) |
| shutil.copytree(input_path, output_path) |
| |
| |
| def main(args=None): |
| """Convert a UFO font from cubic to quadratic curves""" |
| parser = argparse.ArgumentParser(prog="cu2qu") |
| parser.add_argument( |
| "--version", action="version", version=fontTools.__version__) |
| parser.add_argument( |
| "infiles", |
| nargs="+", |
| metavar="INPUT", |
| help="one or more input UFO source file(s).") |
| parser.add_argument("-v", "--verbose", action="count", default=0) |
| parser.add_argument( |
| "-e", |
| "--conversion-error", |
| type=float, |
| metavar="ERROR", |
| default=None, |
| help="maxiumum approximation error measured in EM (default: 0.001)") |
| parser.add_argument( |
| "--keep-direction", |
| dest="reverse_direction", |
| action="store_false", |
| help="do not reverse the contour direction") |
| |
| mode_parser = parser.add_mutually_exclusive_group() |
| mode_parser.add_argument( |
| "-i", |
| "--interpolatable", |
| action="store_true", |
| help="whether curve conversion should keep interpolation compatibility" |
| ) |
| mode_parser.add_argument( |
| "-j", |
| "--jobs", |
| type=int, |
| nargs="?", |
| default=1, |
| const=_cpu_count(), |
| metavar="N", |
| help="Convert using N multiple processes (default: %(default)s)") |
| |
| output_parser = parser.add_mutually_exclusive_group() |
| output_parser.add_argument( |
| "-o", |
| "--output-file", |
| default=None, |
| metavar="OUTPUT", |
| help=("output filename for the converted UFO. By default fonts are " |
| "modified in place. This only works with a single input.")) |
| output_parser.add_argument( |
| "-d", |
| "--output-dir", |
| default=None, |
| metavar="DIRECTORY", |
| help="output directory where to save converted UFOs") |
| |
| options = parser.parse_args(args) |
| |
| if ufo_module is None: |
| parser.error("Either ufoLib2 or defcon are required to run this script.") |
| |
| if not options.verbose: |
| level = "WARNING" |
| elif options.verbose == 1: |
| level = "INFO" |
| else: |
| level = "DEBUG" |
| logging.basicConfig(level=level) |
| |
| if len(options.infiles) > 1 and options.output_file: |
| parser.error("-o/--output-file can't be used with multile inputs") |
| |
| if options.output_dir: |
| output_dir = options.output_dir |
| if not os.path.exists(output_dir): |
| os.mkdir(output_dir) |
| elif not os.path.isdir(output_dir): |
| parser.error("'%s' is not a directory" % output_dir) |
| output_paths = [ |
| os.path.join(output_dir, os.path.basename(p)) |
| for p in options.infiles |
| ] |
| elif options.output_file: |
| output_paths = [options.output_file] |
| else: |
| # save in-place |
| output_paths = [None] * len(options.infiles) |
| |
| kwargs = dict(dump_stats=options.verbose > 0, |
| max_err_em=options.conversion_error, |
| reverse_direction=options.reverse_direction) |
| |
| if options.interpolatable: |
| logger.info('Converting curves compatibly') |
| ufos = [ufo_module.Font(infile) for infile in options.infiles] |
| if fonts_to_quadratic(ufos, **kwargs): |
| for ufo, output_path in zip(ufos, output_paths): |
| logger.info("Saving %s", output_path) |
| if output_path: |
| ufo.save(output_path) |
| else: |
| ufo.save() |
| else: |
| for input_path, output_path in zip(options.infiles, output_paths): |
| if output_path: |
| _copytree(input_path, output_path) |
| else: |
| jobs = min(len(options.infiles), |
| options.jobs) if options.jobs > 1 else 1 |
| if jobs > 1: |
| func = partial(_font_to_quadratic, **kwargs) |
| logger.info('Running %d parallel processes', jobs) |
| with closing(mp.Pool(jobs)) as pool: |
| pool.starmap(func, zip(options.infiles, output_paths)) |
| else: |
| for input_path, output_path in zip(options.infiles, output_paths): |
| _font_to_quadratic(input_path, output_path, **kwargs) |