def convert_curves(self, ufos, compatible=False): if compatible: fonts_to_quadratic(ufos, reverse_direction=True, dump_stats=True) else: for ufo in ufos: print('>> Converting curves for ' + self._font_name(ufo)) font_to_quadratic(ufo, reverse_direction=True, dump_stats=True)
def test_incompatible_fonts(self): font1 = Font() font1.info.unitsPerEm = 1000 glyph1 = font1.newGlyph("a") pen1 = glyph1.getPen() for operator, args in [("moveTo", ((0, 0),)), ("lineTo", ((1, 1),)), ("endPath", ())]: getattr(pen1, operator)(*args) font2 = Font() font2.info.unitsPerEm = 1000 glyph2 = font2.newGlyph("a") pen2 = glyph2.getPen() for operator, args in [("moveTo", ((0, 0),)), ("curveTo", ((1, 1), (2, 2), (3, 3))), ("endPath", ())]: getattr(pen2, operator)(*args) with pytest.raises(IncompatibleFontsError) as excinfo: fonts_to_quadratic([font1, font2]) assert excinfo.match("fonts contains incompatible glyphs: 'a'") assert hasattr(excinfo.value, "glyph_errors") error = excinfo.value.glyph_errors['a'] assert isinstance(error, IncompatibleSegmentTypesError) assert error.segments == {1: ["line", "curve"]}
def generateTTFs(self): """Build TTF for each font generated since last call to generateTTFs.""" fonts = [OpenFont(ufo) for ufo in self.generatedFonts] self.generatedFonts = [] log(">> Converting curves to quadratic") # using a slightly higher max error (e.g. 0.0025 em), dots will have # fewer control points and look noticeably different max_err = 0.002 if self.compatible: fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True) else: for font in fonts: fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True) log(">> Generating TTF files") for font in fonts: ttfName = self.generateOutputPath(font, "ttf") log(os.path.basename(ttfName)) for glyph in font: for contour in glyph: contour.reverseContour() saveOTF( font, ttfName, self.thinGlyphOrder if "Thin" in ttfName else self.glyphOrder, truetype=True)
def process(self): from cu2qu.ufo import fonts_to_quadratic # first apply all custom pre-filters for funcs, ufo, glyphSet in zip(self.preFilters, self.ufos, self.glyphSets): for func in funcs: func(ufo, glyphSet) fonts_to_quadratic( self.glyphSets, max_err=self._conversionErrors, reverse_direction=self._reverseDirection, dump_stats=True, remember_curve_type=self._rememberCurveType and self.inplace, ) decompose = DecomposeComponentsFilter(include=lambda g: len(g)) for ufo, glyphSet in zip(self.ufos, self.glyphSets): decompose(ufo, glyphSet) # finally apply all custom post-filters for funcs, ufo, glyphSet in zip(self.postFilters, self.ufos, self.glyphSets): for func in funcs: func(ufo, glyphSet) return self.glyphSets
def generateTTFs(self): """Build TTF for each font generated since last call to generateTTFs.""" fonts = [OpenFont(ufo) for ufo in self.generatedFonts] self.generatedFonts = [] log(">> Converting curves to quadratic") # using a slightly higher max error (e.g. 0.0025 em), dots will have # fewer control points and look noticeably different max_err = 0.002 if self.compatible: fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True) else: for font in fonts: fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True) log(">> Generating TTF files") for font in fonts: ttfName = self.generateOutputPath(font, "ttf") log(os.path.basename(ttfName)) saveOTF( font, ttfName, self.thinGlyphOrder if "Thin" in ttfName else self.glyphOrder, truetype=True)
def makeMasterGlyphsQuadractic(self): """ Optimize and convert all master ufo to quad curves. """ masters = self.fonts.values() # use cu2qu to optimize all masters fonts_to_quadratic(masters) for master in masters: master.segmentType = "qcurve"
def makeMasterGlyphsQuadractic(self): """ Optimize and convert all master ufo to quad curves. """ masters = self.masters.values() # use cu2qu to optimize all masters fonts_to_quadratic(masters) for master in masters: master.font.segmentType = "qcurve"
def convert_curves(self, ufos, compatible=False, reverse_direction=True, conversion_error=None): if compatible: fonts_to_quadratic( ufos, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True) else: for ufo in ufos: print('>> Converting curves for ' + self._font_name(ufo)) font_to_quadratic( ufo, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True)
def process(self): from cu2qu.ufo import fonts_to_quadratic fonts_to_quadratic(self.glyphSets, max_err=self._conversionErrors, reverse_direction=self._reverseDirection, dump_stats=True) decompose = DecomposeComponentsFilter(include=lambda g: len(g)) for ufo, glyphSet in zip(self.ufos, self.glyphSets): decompose(ufo, glyphSet) return self.glyphSets
def convert_curves(self, ufos, compatible=False, reverse_direction=True, conversion_error=None): from cu2qu.ufo import font_to_quadratic, fonts_to_quadratic if compatible: logger.info('Converting curves compatibly') fonts_to_quadratic( ufos, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True) else: for ufo in ufos: logger.info('Converting curves for ' + self._font_name(ufo)) font_to_quadratic( ufo, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True)
def convert_curves(self, ufos, compatible=False, reverse_direction=True, conversion_error=None): if compatible: fonts_to_quadratic(ufos, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True) else: for ufo in ufos: print('>> Converting curves for ' + self._font_name(ufo)) font_to_quadratic(ufo, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True)
def test_both_max_err_and_max_err_em(self, fonts): with pytest.raises(TypeError, match="Only one .* can be specified"): fonts_to_quadratic(fonts, max_err=1.000, max_err_em=0.001)
def main(args=None): parser = argparse.ArgumentParser(prog="cu2qu") parser.add_argument( "--version", action="version", version=cu2qu.__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 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_paths = [ os.path.join(options.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 = list(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 = [defcon.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) ufo.save(output_path) else: for input_path, output_path in zip(options.infiles, output_paths): _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: # can't use Pool.starmap as it's 3.3+ only pool.map(func, zip(options.infiles, output_paths)) else: for paths in zip(options.infiles, output_paths): _font_to_quadratic(paths, **kwargs)
''' First, install dependencies in requirements.txt. Then, use this script like this: python src/build-scripts/convert-fonts-cu2qu-CLI.py <directory path> ''' import sys import glob from defcon import Font from cu2qu.ufo import fonts_to_quadratic directory = sys.argv[1] print(directory) fonts = [] for filepath in glob.iglob(f'{directory}/*.ufo'): print(filepath) fonts.append(Font(str(filepath))) print(fonts) stats = {} print(fonts_to_quadratic(fonts, stats=stats, dump_stats=True))
from defcon import Font from cu2qu.ufo import fonts_to_quadratic casual_a = Font( 'src/masters--quadratic_production/experiments--curvier-casual/Recursive Mono-Casual A.ufo' ) casual_a_ital = Font( 'src/masters--quadratic_production/experiments--curvier-casual/Recursive Mono-Casual A Italic - gradually fixed.ufo' ) casual_b = Font( 'src/masters--quadratic_production/experiments--curvier-casual/Recursive Mono-Casual B.ufo' ) casual_b_ital = Font( 'src/masters--quadratic_production/experiments--curvier-casual/Recursive Mono-Casual B Italic - gradually fixed.ufo' ) # fonts_to_quadratic([casual_a, casual_a_ital, casual_b, casual_b_ital]) for font in [casual_a, casual_a_ital, casual_b, casual_b_ital]: fonts_to_quadratic([font])
def test_modified(self, fonts): modified = fonts_to_quadratic(fonts) assert modified
def test_stats(self, fonts): stats = {} fonts_to_quadratic(fonts, stats=stats) assert stats == {'1': 1, '2': 79, '3': 130, '4': 2}
def test_dump_stats(self, fonts): with CapturingLogHandler(logger, "INFO") as captor: fonts_to_quadratic(fonts, dump_stats=True) assert captor.assertRegex("New spline lengths:")
def test_no_remember_curve_type(self, fonts): assert CURVE_TYPE_LIB_KEY not in fonts[0].lib fonts_to_quadratic(fonts, remember_curve_type=False) assert CURVE_TYPE_LIB_KEY not in fonts[0].lib
def test_different_glyphsets(self, fonts): del fonts[0]['a'] assert 'a' not in fonts[0] assert 'a' in fonts[1] assert fonts_to_quadratic(fonts)
def test_max_err_em_list(self, fonts): stats = {} fonts_to_quadratic(fonts, max_err_em=[0.002, 0.002], stats=stats) assert stats == {'1': 5, '2': 193, '3': 14}
def test_max_err_list(self, fonts): stats = {} fonts_to_quadratic(fonts, max_err=[4.096, 4.096], stats=stats) assert stats == {'1': 5, '2': 193, '3': 14}
def test_remember_curve_type(self, fonts): fonts_to_quadratic(fonts, remember_curve_type=True) assert fonts[0].lib[CURVE_TYPE_LIB_KEY] == "quadratic" with CapturingLogHandler(logger, "INFO") as captor: fonts_to_quadratic(fonts, remember_curve_type=True) assert captor.assertRegex("already converted")