From 42716d503877f253643a5239dddcef288d369a89 Mon Sep 17 00:00:00 2001 From: ftCLI Date: Fri, 12 Apr 2024 12:30:15 +0200 Subject: [PATCH 1/3] Add 'ttf2otf.py' snippet --- Snippets/ttf2otf.py | 464 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 Snippets/ttf2otf.py diff --git a/Snippets/ttf2otf.py b/Snippets/ttf2otf.py new file mode 100644 index 000000000..23145221c --- /dev/null +++ b/Snippets/ttf2otf.py @@ -0,0 +1,464 @@ +import logging +import sys +import typing as t +from pathlib import Path + +import click +import pathops +from cffsubr import subroutinize as subr +from fontTools import configLogger +from fontTools.cffLib import PrivateDict +from fontTools.fontBuilder import FontBuilder +from fontTools.misc.cliTools import makeOutputFileName +from fontTools.misc.psCharStrings import T2CharString +from fontTools.misc.roundTools import otRound +from fontTools.pens.cu2quPen import Cu2QuPen +from fontTools.pens.qu2cuPen import Qu2CuPen +from fontTools.pens.recordingPen import DecomposingRecordingPen +from fontTools.pens.t2CharStringPen import T2CharStringPen +from fontTools.pens.ttGlyphPen import TTGlyphPen +from fontTools.ttLib import TTFont, TTLibError +from fontTools.ttLib.scaleUpem import scale_upem + +log = logging.getLogger(__name__) + + +def decomponentize_tt(font: TTFont) -> None: + """ + Decomposes all composite glyphs of a TrueType font. + """ + if not font.sfntVersion == "\x00\x01\x00\x00": + raise NotImplementedError("Decomponentization is only supported for TrueType fonts.") + + glyph_set = font.getGlyphSet() + glyf_table = font["glyf"] + dr_pen = DecomposingRecordingPen(glyphSet=glyph_set) + tt_pen = TTGlyphPen(glyphSet=None) + + for glyph_name in font.glyphOrder: + glyph = glyf_table[glyph_name] + if not glyph.isComposite(): + continue + dr_pen.value = [] + tt_pen.init() + glyph.draw(dr_pen, glyf_table) + dr_pen.replay(tt_pen) + glyf_table[glyph_name] = tt_pen.glyph() + + +def skia_path_from_charstring(charstring: T2CharString) -> pathops.Path: + """ + Get a Skia path from a T2CharString. + """ + path = pathops.Path() + path_pen = path.getPen(glyphSet=None) + charstring.draw(path_pen) + return path + + +def charstring_from_skia_path(path: pathops.Path, width: int) -> T2CharString: + """ + Get a T2CharString from a Skia path. + """ + t2_pen = T2CharStringPen(width=width, glyphSet=None) + path.draw(t2_pen) + return t2_pen.getCharString() + + +def round_path(path: pathops.Path, rounder: t.Callable[[float], float] = otRound) -> pathops.Path: + """ + Rounds the points coordinate of a ``pathops.Path`` + + Args: + path (pathops.Path): The ``pathops.Path`` + rounder (Callable[[float], float], optional): The rounding function. Defaults to otRound. + + Returns: + pathops.Path: The rounded path + """ + + rounded_path = pathops.Path() + for verb, points in path: + rounded_path.add(verb, *((rounder(p[0]), rounder(p[1])) for p in points)) + return rounded_path + + +def simplify_path(path: pathops.Path, glyph_name: str, clockwise: bool) -> pathops.Path: + """ + Simplify a ``pathops.Path by`` removing overlaps, fixing contours direction and, optionally, + removing tiny paths + + Args: + path (pathops.Path): The ``pathops.Path`` to simplify + glyph_name (str): The glyph name + clockwise (bool): The winding direction. Must be ``True`` for TrueType glyphs and ``False`` + for OpenType-PS fonts. + + Returns: + pathops.Path: The simplified path + """ + + try: + return pathops.simplify(path, fix_winding=True, clockwise=clockwise) + except pathops.PathOpsError: + pass + + path = round_path(path) + try: + path = pathops.simplify(path, fix_winding=True, clockwise=clockwise) + return path + except pathops.PathOpsError as e: + raise pathops.PathOpsError(f"Failed to simplify path for glyph {glyph_name}: {e}") + + +def quadratics_to_cubics( + font: TTFont, tolerance: float = 1.0, correct_contours: bool = True +) -> t.Dict[str, T2CharString]: + """ + Get CFF charstrings using Qu2CuPen + + Args: + font (TTFont): The TTFont object. + tolerance (float, optional): The tolerance for the conversion. Defaults to 1.0. + correct_contours (bool, optional): Whether to correct the contours with pathops. Defaults to + False. + + Returns: + tuple: A tuple containing the list of failed glyphs and the T2 charstrings. + """ + + qu2cu_charstrings = {} + glyph_set = font.getGlyphSet() + + for k, v in glyph_set.items(): + width = v.width + + try: + t2_pen = T2CharStringPen(width=width, glyphSet={k: v}) + qu2cu_pen = Qu2CuPen(t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True) + glyph_set[k].draw(qu2cu_pen) + qu2cu_charstrings[k] = t2_pen.getCharString() + + except NotImplementedError: + temp_t2_pen = T2CharStringPen(width=width, glyphSet=None) + glyph_set[k].draw(temp_t2_pen) + t2_charstring = temp_t2_pen.getCharString() + t2_charstring.private = PrivateDict() + + tt_pen = TTGlyphPen(glyphSet=None) + cu2qu_pen = Cu2QuPen(other_pen=tt_pen, max_err=tolerance, reverse_direction=False) + t2_charstring.draw(cu2qu_pen) + tt_glyph = tt_pen.glyph() + + t2_pen = T2CharStringPen(width=width, glyphSet=None) + qu2cu_pen = Qu2CuPen(t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True) + tt_glyph.draw(pen=qu2cu_pen, glyfTable=None) + log.info( + f"Failed to convert glyph {k} to cubic at first attempt, but succeeded at second " + f"attempt." + ) + + charstring = t2_pen.getCharString() + + if correct_contours: + charstring.private = PrivateDict() + path = skia_path_from_charstring(charstring) + simplified_path = simplify_path(path, glyph_name=k, clockwise=False) + charstring = charstring_from_skia_path(path=simplified_path, width=width) + + qu2cu_charstrings[k] = charstring + + return qu2cu_charstrings + + +def build_font_info_dict(font: TTFont) -> t.Dict[str, t.Any]: + """ + Builds CFF topDict from a TTFont object. + + Args: + font (TTFont): The TTFont object. + + Returns: + dict: The CFF topDict. + """ + + font_revision = str(round(font["head"].fontRevision, 3)).split(".") + major_version = str(font_revision[0]) + minor_version = str(font_revision[1]).ljust(3, "0") + + name_table = font["name"] + post_table = font["post"] + cff_font_info = { + "version": ".".join([major_version, str(int(minor_version))]), + "FullName": name_table.getBestFullName(), + "FamilyName": name_table.getBestFamilyName(), + "ItalicAngle": post_table.italicAngle, + "UnderlinePosition": post_table.underlinePosition, + "UnderlineThickness": post_table.underlineThickness, + "isFixedPitch": bool(post_table.isFixedPitch), + } + + return cff_font_info + + +def get_post_values(font: TTFont) -> t.Dict[str, t.Any]: + """ + Setup CFF post table values + + Args: + font (TTFont): The TTFont object. + + Returns: + dict: The post table values. + """ + post_table = font["post"] + post_info = { + "italicAngle": otRound(post_table.italicAngle), + "underlinePosition": post_table.underlinePosition, + "underlineThickness": post_table.underlineThickness, + "isFixedPitch": post_table.isFixedPitch, + "minMemType42": post_table.minMemType42, + "maxMemType42": post_table.maxMemType42, + "minMemType1": post_table.minMemType1, + "maxMemType1": post_table.maxMemType1, + } + return post_info + + +def get_hmtx_values( + font: TTFont, charstrings: t.Dict[str, T2CharString] +) -> t.Dict[str, t.Tuple[int, int]]: + """ + Get the horizontal metrics for a font. + + Args: + font (TTFont): The TTFont object. + charstrings (dict): The charstrings dictionary. + + Returns: + dict: The horizontal metrics. + """ + glyph_set = font.getGlyphSet() + advance_widths = {k: v.width for k, v in glyph_set.items()} + lsb = {} + for gn, cs in charstrings.items(): + lsb[gn] = cs.calcBounds(None)[0] if cs.calcBounds(None) is not None else 0 + metrics = {} + for gn, advance_width in advance_widths.items(): + metrics[gn] = (advance_width, lsb[gn]) + return metrics + + +def build_otf( + font: TTFont, + charstrings_dict: t.Dict[str, T2CharString], + ps_name: t.Optional[str] = None, + font_info: t.Optional[t.Dict[str, t.Any]] = None, + private_dict: t.Optional[t.Dict[str, t.Any]] = None, +) -> None: + """ + Builds an OpenType font with FontBuilder. + + Args: + font (TTFont): The TTFont object. + charstrings_dict (dict): The charstrings dictionary. + ps_name (str, optional): The PostScript name of the font. Defaults to None. + font_info (dict, optional): The font info dictionary. Defaults to None. + private_dict (dict, optional): The private dictionary. Defaults to None. + """ + + if not ps_name: + ps_name = font["name"].getDebugName(6) + if not font_info: + font_info = build_font_info_dict(font=font) + if not private_dict: + private_dict = {} + + fb = FontBuilder(font=font) + fb.isTTF = False + ttf_tables = ["glyf", "cvt ", "loca", "fpgm", "prep", "gasp", "LTSH", "hdmx"] + for table in ttf_tables: + if table in font: + del font[table] + fb.setupGlyphOrder(font.getGlyphOrder()) + fb.setupCFF( + psName=ps_name, + charStringsDict=charstrings_dict, + fontInfo=font_info, + privateDict=private_dict, + ) + metrics = get_hmtx_values(font=fb.font, charstrings=charstrings_dict) + fb.setupHorizontalMetrics(metrics) + fb.setupDummyDSIG() + fb.setupMaxp() + post_values = get_post_values(font=fb.font) + fb.setupPost(**post_values) + + +def find_fonts( + input_path: Path, recursive: bool = False, recalc_timestamp: bool = False +) -> t.List[TTFont]: + """ + Returns a list of TTFont objects found in the input path. + + Args: + input_path (Path): The input file or directory. + recursive (bool): If input_path is a directory, search for fonts recursively in + subdirectories. + + Returns: + List[TTFont]: A list of TTFont objects. + """ + + if input_path.is_file(): + files = [input_path] + elif input_path.is_dir(): + if recursive: + files = [x for x in input_path.rglob("*") if x.is_file()] + else: + files = [x for x in input_path.glob("*") if x.is_file()] + else: + raise ValueError("Input path must be a file or directory.") + + fonts = [] + for file in files: + try: + font = TTFont(file, recalcTimestamp=recalc_timestamp) + # Filter out CFF and variable fonts + if font.sfntVersion == "\x00\x01\x00\x00" and "fvar" not in font: + fonts.append(font) + except (TTLibError, PermissionError): + pass + return fonts + + +@click.command("ttf2otf", no_args_is_help=True) +@click.argument("input_path", type=click.Path(exists=True, resolve_path=True, path_type=Path)) +@click.option( + "-r", + "--recursive", + is_flag=True, + default=False, + help="If INPUT_PATH is a directory, search for fonts recursively in subdirectories.", +) +@click.option( + "-o", + "--output-dir", + type=click.Path(file_okay=False, path_type=Path), + help="Specify a directory where the output files are to be saved. If the output directory " + "doesn't exist, it will be automatically created. If not specified, files will be saved to " + "the source directory.", +) +@click.option( + "--no-overwrite", + "overwrite", + is_flag=True, + default=True, + help="Do not overwrite the output file if it already exists.", +) +@click.option( + "-rt", + "--recalc-timestamp", + is_flag=True, + default=False, + help="Recalculate the font's timestamp.", +) +@click.option( + "--max-err", + type=float, + default=1.0, + help="The maximum error allowed when converting the font to TrueType.", +) +@click.option( + "--new-upem", + type=click.IntRange(min=16, max=16384), + help="The target UPM to scale the font to.", +) +@click.option( + "--correct-contours", + is_flag=True, + default=False, + help=""" + If the TrueType contours fonts have overlaps contours or incorrect directions, set this flag to + correct them with pathops. + """, +) +@click.option( + "--no-subroutinize", + "subroutinize", + is_flag=True, + default=True, + help="Do not subroutinize the converted font.", +) +def main( + input_path: Path, + recursive: bool = False, + output_dir: t.Optional[Path] = None, + overwrite: bool = True, + recalc_timestamp: bool = False, + max_err: float = 1.0, + new_upem: t.Optional[int] = None, + correct_contours: bool = True, + subroutinize: bool = True, +) -> None: + """ + Convert TrueType flavored fonts to CFF flavored fonts. + + INPUT_PATH argument can be a file or a directory. If it is a directory, all the TrueType + flavored fonts found in the directory will be converted. + """ + + fonts = find_fonts(input_path, recursive=recursive, recalc_timestamp=recalc_timestamp) + if not fonts: + log.error("No fonts found.") + return + + if output_dir and not output_dir.exists(): + output_dir.mkdir(parents=True) + + for font in fonts: + with font: + in_file = font.reader.file.name + log.info(f"Converting {in_file}...") + + log.info("Decomponentizing source font...") + decomponentize_tt(font) + + if new_upem: + log.info(f"Scaling UPM to {new_upem}...") + scale_upem(font=font, new_upem=new_upem) + + log.info("Converting to OTF...") + charstrings_dict = quadratics_to_cubics( + font, tolerance=max_err, correct_contours=correct_contours + ) + ps_name = font["name"].getDebugName(6) + font_info = build_font_info_dict(font=font) + private_dict: t.Dict[str, t.Any] = {} + build_otf(font, charstrings_dict, ps_name, font_info, private_dict) + + os_2_table = font["OS/2"] + os_2_table.recalcAvgCharWidth(ttFont=font) + + if subroutinize: + flavor = font.flavor + font.flavor = None + log.info("Subroutinizing...") + subr(otf=font) + font.flavor = flavor + + out_file = makeOutputFileName( + input=in_file, + outputDir=output_dir, + extension=".otf" if font.flavor is None else f".{font.flavor}", + suffix=".otf" if font.flavor is not None else "", + overWrite=overwrite, + ) + font.save(out_file) + log.info(f"File saved to {out_file}") + print() + + +if __name__ == "__main__": + configLogger(logger=log, level="INFO") + sys.exit(main()) From 94cb4f97337daad6dbf35193749b2af0df13469d Mon Sep 17 00:00:00 2001 From: ftCLI Date: Sat, 28 Sep 2024 08:15:31 +0200 Subject: [PATCH 2/3] Refactor ttf2otf Replaced Click with argparse. Updated docstring with usage examples. Modified logging to use warnings for conversion failures. --- Snippets/ttf2otf.py | 175 +++++++++++++++++++++++++------------------- 1 file changed, 100 insertions(+), 75 deletions(-) diff --git a/Snippets/ttf2otf.py b/Snippets/ttf2otf.py index 23145221c..0876f069d 100644 --- a/Snippets/ttf2otf.py +++ b/Snippets/ttf2otf.py @@ -1,9 +1,28 @@ +__doc__ = """ +Convert TrueType flavored fonts to CFF flavored fonts. + +INPUT_PATH argument can be a file or a directory. If it is a directory, all the TrueType +flavored fonts found in the directory will be converted. + +Examples: + + $ ttf2otf font.ttf + $ ttf2otf font.ttf -o output_dir + $ ttf2otf fonts_dir + $ ttf2otf fonts_dir -r + $ ttf2otf fonts_dir -o output_dir + $ ttf2otf fonts_dir -o output_dir --no-overwrite + $ ttf2otf fonts_dir -o output_dir --max-err 0.5 + $ ttf2otf fonts_dir -o output_dir --new-upem 1000 + $ ttf2otf fonts_dir -o output_dir --correct-contours +""" + +import argparse import logging import sys import typing as t from pathlib import Path -import click import pathops from cffsubr import subroutinize as subr from fontTools import configLogger @@ -112,7 +131,7 @@ def simplify_path(path: pathops.Path, glyph_name: str, clockwise: bool) -> patho def quadratics_to_cubics( - font: TTFont, tolerance: float = 1.0, correct_contours: bool = True + font: TTFont, tolerance: float = 1.0, correct_contours: bool = False ) -> t.Dict[str, T2CharString]: """ Get CFF charstrings using Qu2CuPen @@ -140,6 +159,7 @@ def quadratics_to_cubics( qu2cu_charstrings[k] = t2_pen.getCharString() except NotImplementedError: + # Workaround for "oncurve-less contours with all_cubic not implemented" temp_t2_pen = T2CharStringPen(width=width, glyphSet=None) glyph_set[k].draw(temp_t2_pen) t2_charstring = temp_t2_pen.getCharString() @@ -153,9 +173,9 @@ def quadratics_to_cubics( t2_pen = T2CharStringPen(width=width, glyphSet=None) qu2cu_pen = Qu2CuPen(t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True) tt_glyph.draw(pen=qu2cu_pen, glyfTable=None) - log.info( - f"Failed to convert glyph {k} to cubic at first attempt, but succeeded at second " - f"attempt." + log.warning( + f"Failed to convert glyph '{k}' to cubic at first attempt, but succeeded at second " + f"one." ) charstring = t2_pen.getCharString() @@ -305,6 +325,7 @@ def find_fonts( input_path (Path): The input file or directory. recursive (bool): If input_path is a directory, search for fonts recursively in subdirectories. + recalc_timestamp (bool): Weather to recalculate the font's timestamp on save. Returns: List[TTFont]: A list of TTFont objects. @@ -332,75 +353,7 @@ def find_fonts( return fonts -@click.command("ttf2otf", no_args_is_help=True) -@click.argument("input_path", type=click.Path(exists=True, resolve_path=True, path_type=Path)) -@click.option( - "-r", - "--recursive", - is_flag=True, - default=False, - help="If INPUT_PATH is a directory, search for fonts recursively in subdirectories.", -) -@click.option( - "-o", - "--output-dir", - type=click.Path(file_okay=False, path_type=Path), - help="Specify a directory where the output files are to be saved. If the output directory " - "doesn't exist, it will be automatically created. If not specified, files will be saved to " - "the source directory.", -) -@click.option( - "--no-overwrite", - "overwrite", - is_flag=True, - default=True, - help="Do not overwrite the output file if it already exists.", -) -@click.option( - "-rt", - "--recalc-timestamp", - is_flag=True, - default=False, - help="Recalculate the font's timestamp.", -) -@click.option( - "--max-err", - type=float, - default=1.0, - help="The maximum error allowed when converting the font to TrueType.", -) -@click.option( - "--new-upem", - type=click.IntRange(min=16, max=16384), - help="The target UPM to scale the font to.", -) -@click.option( - "--correct-contours", - is_flag=True, - default=False, - help=""" - If the TrueType contours fonts have overlaps contours or incorrect directions, set this flag to - correct them with pathops. - """, -) -@click.option( - "--no-subroutinize", - "subroutinize", - is_flag=True, - default=True, - help="Do not subroutinize the converted font.", -) -def main( - input_path: Path, - recursive: bool = False, - output_dir: t.Optional[Path] = None, - overwrite: bool = True, - recalc_timestamp: bool = False, - max_err: float = 1.0, - new_upem: t.Optional[int] = None, - correct_contours: bool = True, - subroutinize: bool = True, -) -> None: +def main(args=None) -> None: """ Convert TrueType flavored fonts to CFF flavored fonts. @@ -408,9 +361,77 @@ def main( flavored fonts found in the directory will be converted. """ + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "input_path", + type=Path, + help="The input file or directory.", + ) + parser.add_argument( + "-r", + "--recursive", + action="store_true", + help="Search for fonts recursively in subdirectories.", + ) + parser.add_argument( + "-o", + "--output-dir", + type=Path, + help="Specify a directory where the output files are to be saved.", + ) + parser.add_argument( + "--no-overwrite", + dest="overwrite", + action="store_false", + help="Do not overwrite the output file if it already exists.", + ) + parser.add_argument( + "-rt", + "--recalc-timestamp", + action="store_true", + help="Recalculate the font's modified timestamp on save.", + ) + parser.add_argument( + "--max-err", + type=float, + default=1.0, + help="The maximum error allowed when converting the font to TrueType." + ) + parser.add_argument( + "--new-upem", + type=int, + help="The target UPM to scale the font to.", + ) + parser.add_argument( + "--correct-contours", + action="store_true", + help="Correct contours with pathops.", + ) + parser.add_argument( + "--no-subroutinize", + dest="subroutinize", + action="store_false", + help="Do not subroutinize the converted font.", + ) + + args = parser.parse_args(args) + + input_path = args.input_path + recursive = args.recursive + output_dir = args.output_dir + overwrite = args.overwrite + recalc_timestamp = args.recalc_timestamp + max_err = args.max_err + new_upem = args.new_upem + correct_contours = args.correct_contours + subroutinize = args.subroutinize + fonts = find_fonts(input_path, recursive=recursive, recalc_timestamp=recalc_timestamp) if not fonts: - log.error("No fonts found.") + log.error("No TrueType flavored fonts found.") return if output_dir and not output_dir.exists(): @@ -418,6 +439,9 @@ def main( for font in fonts: with font: + if font.sfntVersion != "\x00\x01\x00\x00": + log.error(f"Font {font.reader.file.name} is not a TrueType font.") + continue in_file = font.reader.file.name log.info(f"Converting {in_file}...") @@ -432,6 +456,7 @@ def main( charstrings_dict = quadratics_to_cubics( font, tolerance=max_err, correct_contours=correct_contours ) + ps_name = font["name"].getDebugName(6) font_info = build_font_info_dict(font=font) private_dict: t.Dict[str, t.Any] = {} From 1fbc897f9f795a2e5ee6f69a6ac83ed9fefd588d Mon Sep 17 00:00:00 2001 From: ftCLI Date: Sun, 29 Sep 2024 11:42:19 +0200 Subject: [PATCH 3/3] Run black --- Snippets/ttf2otf.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Snippets/ttf2otf.py b/Snippets/ttf2otf.py index 0876f069d..2aef727db 100644 --- a/Snippets/ttf2otf.py +++ b/Snippets/ttf2otf.py @@ -47,7 +47,9 @@ def decomponentize_tt(font: TTFont) -> None: Decomposes all composite glyphs of a TrueType font. """ if not font.sfntVersion == "\x00\x01\x00\x00": - raise NotImplementedError("Decomponentization is only supported for TrueType fonts.") + raise NotImplementedError( + "Decomponentization is only supported for TrueType fonts." + ) glyph_set = font.getGlyphSet() glyf_table = font["glyf"] @@ -84,7 +86,9 @@ def charstring_from_skia_path(path: pathops.Path, width: int) -> T2CharString: return t2_pen.getCharString() -def round_path(path: pathops.Path, rounder: t.Callable[[float], float] = otRound) -> pathops.Path: +def round_path( + path: pathops.Path, rounder: t.Callable[[float], float] = otRound +) -> pathops.Path: """ Rounds the points coordinate of a ``pathops.Path`` @@ -127,7 +131,9 @@ def simplify_path(path: pathops.Path, glyph_name: str, clockwise: bool) -> patho path = pathops.simplify(path, fix_winding=True, clockwise=clockwise) return path except pathops.PathOpsError as e: - raise pathops.PathOpsError(f"Failed to simplify path for glyph {glyph_name}: {e}") + raise pathops.PathOpsError( + f"Failed to simplify path for glyph {glyph_name}: {e}" + ) def quadratics_to_cubics( @@ -154,7 +160,9 @@ def quadratics_to_cubics( try: t2_pen = T2CharStringPen(width=width, glyphSet={k: v}) - qu2cu_pen = Qu2CuPen(t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True) + qu2cu_pen = Qu2CuPen( + t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True + ) glyph_set[k].draw(qu2cu_pen) qu2cu_charstrings[k] = t2_pen.getCharString() @@ -166,12 +174,16 @@ def quadratics_to_cubics( t2_charstring.private = PrivateDict() tt_pen = TTGlyphPen(glyphSet=None) - cu2qu_pen = Cu2QuPen(other_pen=tt_pen, max_err=tolerance, reverse_direction=False) + cu2qu_pen = Cu2QuPen( + other_pen=tt_pen, max_err=tolerance, reverse_direction=False + ) t2_charstring.draw(cu2qu_pen) tt_glyph = tt_pen.glyph() t2_pen = T2CharStringPen(width=width, glyphSet=None) - qu2cu_pen = Qu2CuPen(t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True) + qu2cu_pen = Qu2CuPen( + t2_pen, max_err=tolerance, all_cubic=True, reverse_direction=True + ) tt_glyph.draw(pen=qu2cu_pen, glyfTable=None) log.warning( f"Failed to convert glyph '{k}' to cubic at first attempt, but succeeded at second " @@ -398,7 +410,7 @@ def main(args=None) -> None: "--max-err", type=float, default=1.0, - help="The maximum error allowed when converting the font to TrueType." + help="The maximum error allowed when converting the font to TrueType.", ) parser.add_argument( "--new-upem", @@ -429,7 +441,9 @@ def main(args=None) -> None: correct_contours = args.correct_contours subroutinize = args.subroutinize - fonts = find_fonts(input_path, recursive=recursive, recalc_timestamp=recalc_timestamp) + fonts = find_fonts( + input_path, recursive=recursive, recalc_timestamp=recalc_timestamp + ) if not fonts: log.error("No TrueType flavored fonts found.") return