Refactor ttf2otf

Replaced Click with argparse.

Updated docstring with usage examples.

Modified logging to use warnings for conversion failures.
This commit is contained in:
ftCLI 2024-09-28 08:15:31 +02:00
parent d4f89c43b2
commit 94cb4f9733

View File

@ -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 logging
import sys import sys
import typing as t import typing as t
from pathlib import Path from pathlib import Path
import click
import pathops import pathops
from cffsubr import subroutinize as subr from cffsubr import subroutinize as subr
from fontTools import configLogger from fontTools import configLogger
@ -112,7 +131,7 @@ def simplify_path(path: pathops.Path, glyph_name: str, clockwise: bool) -> patho
def quadratics_to_cubics( 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]: ) -> t.Dict[str, T2CharString]:
""" """
Get CFF charstrings using Qu2CuPen Get CFF charstrings using Qu2CuPen
@ -140,6 +159,7 @@ def quadratics_to_cubics(
qu2cu_charstrings[k] = t2_pen.getCharString() qu2cu_charstrings[k] = t2_pen.getCharString()
except NotImplementedError: except NotImplementedError:
# Workaround for "oncurve-less contours with all_cubic not implemented"
temp_t2_pen = T2CharStringPen(width=width, glyphSet=None) temp_t2_pen = T2CharStringPen(width=width, glyphSet=None)
glyph_set[k].draw(temp_t2_pen) glyph_set[k].draw(temp_t2_pen)
t2_charstring = temp_t2_pen.getCharString() t2_charstring = temp_t2_pen.getCharString()
@ -153,9 +173,9 @@ def quadratics_to_cubics(
t2_pen = T2CharStringPen(width=width, glyphSet=None) 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) tt_glyph.draw(pen=qu2cu_pen, glyfTable=None)
log.info( log.warning(
f"Failed to convert glyph {k} to cubic at first attempt, but succeeded at second " f"Failed to convert glyph '{k}' to cubic at first attempt, but succeeded at second "
f"attempt." f"one."
) )
charstring = t2_pen.getCharString() charstring = t2_pen.getCharString()
@ -305,6 +325,7 @@ def find_fonts(
input_path (Path): The input file or directory. input_path (Path): The input file or directory.
recursive (bool): If input_path is a directory, search for fonts recursively in recursive (bool): If input_path is a directory, search for fonts recursively in
subdirectories. subdirectories.
recalc_timestamp (bool): Weather to recalculate the font's timestamp on save.
Returns: Returns:
List[TTFont]: A list of TTFont objects. List[TTFont]: A list of TTFont objects.
@ -332,75 +353,7 @@ def find_fonts(
return fonts return fonts
@click.command("ttf2otf", no_args_is_help=True) def main(args=None) -> None:
@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. Convert TrueType flavored fonts to CFF flavored fonts.
@ -408,9 +361,77 @@ def main(
flavored fonts found in the directory will be converted. 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) fonts = find_fonts(input_path, recursive=recursive, recalc_timestamp=recalc_timestamp)
if not fonts: if not fonts:
log.error("No fonts found.") log.error("No TrueType flavored fonts found.")
return return
if output_dir and not output_dir.exists(): if output_dir and not output_dir.exists():
@ -418,6 +439,9 @@ def main(
for font in fonts: for font in fonts:
with font: 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 in_file = font.reader.file.name
log.info(f"Converting {in_file}...") log.info(f"Converting {in_file}...")
@ -432,6 +456,7 @@ def main(
charstrings_dict = quadratics_to_cubics( charstrings_dict = quadratics_to_cubics(
font, tolerance=max_err, correct_contours=correct_contours font, tolerance=max_err, correct_contours=correct_contours
) )
ps_name = font["name"].getDebugName(6) ps_name = font["name"].getDebugName(6)
font_info = build_font_info_dict(font=font) font_info = build_font_info_dict(font=font)
private_dict: t.Dict[str, t.Any] = {} private_dict: t.Dict[str, t.Any] = {}