xref: /aosp_15_r20/external/fonttools/Lib/fontTools/cu2qu/cli.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesimport os
2*e1fe3e4aSElliott Hughesimport argparse
3*e1fe3e4aSElliott Hughesimport logging
4*e1fe3e4aSElliott Hughesimport shutil
5*e1fe3e4aSElliott Hughesimport multiprocessing as mp
6*e1fe3e4aSElliott Hughesfrom contextlib import closing
7*e1fe3e4aSElliott Hughesfrom functools import partial
8*e1fe3e4aSElliott Hughes
9*e1fe3e4aSElliott Hughesimport fontTools
10*e1fe3e4aSElliott Hughesfrom .ufo import font_to_quadratic, fonts_to_quadratic
11*e1fe3e4aSElliott Hughes
12*e1fe3e4aSElliott Hughesufo_module = None
13*e1fe3e4aSElliott Hughestry:
14*e1fe3e4aSElliott Hughes    import ufoLib2 as ufo_module
15*e1fe3e4aSElliott Hughesexcept ImportError:
16*e1fe3e4aSElliott Hughes    try:
17*e1fe3e4aSElliott Hughes        import defcon as ufo_module
18*e1fe3e4aSElliott Hughes    except ImportError as e:
19*e1fe3e4aSElliott Hughes        pass
20*e1fe3e4aSElliott Hughes
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott Hugheslogger = logging.getLogger("fontTools.cu2qu")
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott Hughes
25*e1fe3e4aSElliott Hughesdef _cpu_count():
26*e1fe3e4aSElliott Hughes    try:
27*e1fe3e4aSElliott Hughes        return mp.cpu_count()
28*e1fe3e4aSElliott Hughes    except NotImplementedError:  # pragma: no cover
29*e1fe3e4aSElliott Hughes        return 1
30*e1fe3e4aSElliott Hughes
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott Hughesdef open_ufo(path):
33*e1fe3e4aSElliott Hughes    if hasattr(ufo_module.Font, "open"):  # ufoLib2
34*e1fe3e4aSElliott Hughes        return ufo_module.Font.open(path)
35*e1fe3e4aSElliott Hughes    return ufo_module.Font(path)  # defcon
36*e1fe3e4aSElliott Hughes
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughesdef _font_to_quadratic(input_path, output_path=None, **kwargs):
39*e1fe3e4aSElliott Hughes    ufo = open_ufo(input_path)
40*e1fe3e4aSElliott Hughes    logger.info("Converting curves for %s", input_path)
41*e1fe3e4aSElliott Hughes    if font_to_quadratic(ufo, **kwargs):
42*e1fe3e4aSElliott Hughes        logger.info("Saving %s", output_path)
43*e1fe3e4aSElliott Hughes        if output_path:
44*e1fe3e4aSElliott Hughes            ufo.save(output_path)
45*e1fe3e4aSElliott Hughes        else:
46*e1fe3e4aSElliott Hughes            ufo.save()  # save in-place
47*e1fe3e4aSElliott Hughes    elif output_path:
48*e1fe3e4aSElliott Hughes        _copytree(input_path, output_path)
49*e1fe3e4aSElliott Hughes
50*e1fe3e4aSElliott Hughes
51*e1fe3e4aSElliott Hughesdef _samepath(path1, path2):
52*e1fe3e4aSElliott Hughes    # TODO on python3+, there's os.path.samefile
53*e1fe3e4aSElliott Hughes    path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1)))
54*e1fe3e4aSElliott Hughes    path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2)))
55*e1fe3e4aSElliott Hughes    return path1 == path2
56*e1fe3e4aSElliott Hughes
57*e1fe3e4aSElliott Hughes
58*e1fe3e4aSElliott Hughesdef _copytree(input_path, output_path):
59*e1fe3e4aSElliott Hughes    if _samepath(input_path, output_path):
60*e1fe3e4aSElliott Hughes        logger.debug("input and output paths are the same file; skipped copy")
61*e1fe3e4aSElliott Hughes        return
62*e1fe3e4aSElliott Hughes    if os.path.exists(output_path):
63*e1fe3e4aSElliott Hughes        shutil.rmtree(output_path)
64*e1fe3e4aSElliott Hughes    shutil.copytree(input_path, output_path)
65*e1fe3e4aSElliott Hughes
66*e1fe3e4aSElliott Hughes
67*e1fe3e4aSElliott Hughesdef main(args=None):
68*e1fe3e4aSElliott Hughes    """Convert a UFO font from cubic to quadratic curves"""
69*e1fe3e4aSElliott Hughes    parser = argparse.ArgumentParser(prog="cu2qu")
70*e1fe3e4aSElliott Hughes    parser.add_argument("--version", action="version", version=fontTools.__version__)
71*e1fe3e4aSElliott Hughes    parser.add_argument(
72*e1fe3e4aSElliott Hughes        "infiles",
73*e1fe3e4aSElliott Hughes        nargs="+",
74*e1fe3e4aSElliott Hughes        metavar="INPUT",
75*e1fe3e4aSElliott Hughes        help="one or more input UFO source file(s).",
76*e1fe3e4aSElliott Hughes    )
77*e1fe3e4aSElliott Hughes    parser.add_argument("-v", "--verbose", action="count", default=0)
78*e1fe3e4aSElliott Hughes    parser.add_argument(
79*e1fe3e4aSElliott Hughes        "-e",
80*e1fe3e4aSElliott Hughes        "--conversion-error",
81*e1fe3e4aSElliott Hughes        type=float,
82*e1fe3e4aSElliott Hughes        metavar="ERROR",
83*e1fe3e4aSElliott Hughes        default=None,
84*e1fe3e4aSElliott Hughes        help="maxiumum approximation error measured in EM (default: 0.001)",
85*e1fe3e4aSElliott Hughes    )
86*e1fe3e4aSElliott Hughes    parser.add_argument(
87*e1fe3e4aSElliott Hughes        "-m",
88*e1fe3e4aSElliott Hughes        "--mixed",
89*e1fe3e4aSElliott Hughes        default=False,
90*e1fe3e4aSElliott Hughes        action="store_true",
91*e1fe3e4aSElliott Hughes        help="whether to used mixed quadratic and cubic curves",
92*e1fe3e4aSElliott Hughes    )
93*e1fe3e4aSElliott Hughes    parser.add_argument(
94*e1fe3e4aSElliott Hughes        "--keep-direction",
95*e1fe3e4aSElliott Hughes        dest="reverse_direction",
96*e1fe3e4aSElliott Hughes        action="store_false",
97*e1fe3e4aSElliott Hughes        help="do not reverse the contour direction",
98*e1fe3e4aSElliott Hughes    )
99*e1fe3e4aSElliott Hughes
100*e1fe3e4aSElliott Hughes    mode_parser = parser.add_mutually_exclusive_group()
101*e1fe3e4aSElliott Hughes    mode_parser.add_argument(
102*e1fe3e4aSElliott Hughes        "-i",
103*e1fe3e4aSElliott Hughes        "--interpolatable",
104*e1fe3e4aSElliott Hughes        action="store_true",
105*e1fe3e4aSElliott Hughes        help="whether curve conversion should keep interpolation compatibility",
106*e1fe3e4aSElliott Hughes    )
107*e1fe3e4aSElliott Hughes    mode_parser.add_argument(
108*e1fe3e4aSElliott Hughes        "-j",
109*e1fe3e4aSElliott Hughes        "--jobs",
110*e1fe3e4aSElliott Hughes        type=int,
111*e1fe3e4aSElliott Hughes        nargs="?",
112*e1fe3e4aSElliott Hughes        default=1,
113*e1fe3e4aSElliott Hughes        const=_cpu_count(),
114*e1fe3e4aSElliott Hughes        metavar="N",
115*e1fe3e4aSElliott Hughes        help="Convert using N multiple processes (default: %(default)s)",
116*e1fe3e4aSElliott Hughes    )
117*e1fe3e4aSElliott Hughes
118*e1fe3e4aSElliott Hughes    output_parser = parser.add_mutually_exclusive_group()
119*e1fe3e4aSElliott Hughes    output_parser.add_argument(
120*e1fe3e4aSElliott Hughes        "-o",
121*e1fe3e4aSElliott Hughes        "--output-file",
122*e1fe3e4aSElliott Hughes        default=None,
123*e1fe3e4aSElliott Hughes        metavar="OUTPUT",
124*e1fe3e4aSElliott Hughes        help=(
125*e1fe3e4aSElliott Hughes            "output filename for the converted UFO. By default fonts are "
126*e1fe3e4aSElliott Hughes            "modified in place. This only works with a single input."
127*e1fe3e4aSElliott Hughes        ),
128*e1fe3e4aSElliott Hughes    )
129*e1fe3e4aSElliott Hughes    output_parser.add_argument(
130*e1fe3e4aSElliott Hughes        "-d",
131*e1fe3e4aSElliott Hughes        "--output-dir",
132*e1fe3e4aSElliott Hughes        default=None,
133*e1fe3e4aSElliott Hughes        metavar="DIRECTORY",
134*e1fe3e4aSElliott Hughes        help="output directory where to save converted UFOs",
135*e1fe3e4aSElliott Hughes    )
136*e1fe3e4aSElliott Hughes
137*e1fe3e4aSElliott Hughes    options = parser.parse_args(args)
138*e1fe3e4aSElliott Hughes
139*e1fe3e4aSElliott Hughes    if ufo_module is None:
140*e1fe3e4aSElliott Hughes        parser.error("Either ufoLib2 or defcon are required to run this script.")
141*e1fe3e4aSElliott Hughes
142*e1fe3e4aSElliott Hughes    if not options.verbose:
143*e1fe3e4aSElliott Hughes        level = "WARNING"
144*e1fe3e4aSElliott Hughes    elif options.verbose == 1:
145*e1fe3e4aSElliott Hughes        level = "INFO"
146*e1fe3e4aSElliott Hughes    else:
147*e1fe3e4aSElliott Hughes        level = "DEBUG"
148*e1fe3e4aSElliott Hughes    logging.basicConfig(level=level)
149*e1fe3e4aSElliott Hughes
150*e1fe3e4aSElliott Hughes    if len(options.infiles) > 1 and options.output_file:
151*e1fe3e4aSElliott Hughes        parser.error("-o/--output-file can't be used with multile inputs")
152*e1fe3e4aSElliott Hughes
153*e1fe3e4aSElliott Hughes    if options.output_dir:
154*e1fe3e4aSElliott Hughes        output_dir = options.output_dir
155*e1fe3e4aSElliott Hughes        if not os.path.exists(output_dir):
156*e1fe3e4aSElliott Hughes            os.mkdir(output_dir)
157*e1fe3e4aSElliott Hughes        elif not os.path.isdir(output_dir):
158*e1fe3e4aSElliott Hughes            parser.error("'%s' is not a directory" % output_dir)
159*e1fe3e4aSElliott Hughes        output_paths = [
160*e1fe3e4aSElliott Hughes            os.path.join(output_dir, os.path.basename(p)) for p in options.infiles
161*e1fe3e4aSElliott Hughes        ]
162*e1fe3e4aSElliott Hughes    elif options.output_file:
163*e1fe3e4aSElliott Hughes        output_paths = [options.output_file]
164*e1fe3e4aSElliott Hughes    else:
165*e1fe3e4aSElliott Hughes        # save in-place
166*e1fe3e4aSElliott Hughes        output_paths = [None] * len(options.infiles)
167*e1fe3e4aSElliott Hughes
168*e1fe3e4aSElliott Hughes    kwargs = dict(
169*e1fe3e4aSElliott Hughes        dump_stats=options.verbose > 0,
170*e1fe3e4aSElliott Hughes        max_err_em=options.conversion_error,
171*e1fe3e4aSElliott Hughes        reverse_direction=options.reverse_direction,
172*e1fe3e4aSElliott Hughes        all_quadratic=False if options.mixed else True,
173*e1fe3e4aSElliott Hughes    )
174*e1fe3e4aSElliott Hughes
175*e1fe3e4aSElliott Hughes    if options.interpolatable:
176*e1fe3e4aSElliott Hughes        logger.info("Converting curves compatibly")
177*e1fe3e4aSElliott Hughes        ufos = [open_ufo(infile) for infile in options.infiles]
178*e1fe3e4aSElliott Hughes        if fonts_to_quadratic(ufos, **kwargs):
179*e1fe3e4aSElliott Hughes            for ufo, output_path in zip(ufos, output_paths):
180*e1fe3e4aSElliott Hughes                logger.info("Saving %s", output_path)
181*e1fe3e4aSElliott Hughes                if output_path:
182*e1fe3e4aSElliott Hughes                    ufo.save(output_path)
183*e1fe3e4aSElliott Hughes                else:
184*e1fe3e4aSElliott Hughes                    ufo.save()
185*e1fe3e4aSElliott Hughes        else:
186*e1fe3e4aSElliott Hughes            for input_path, output_path in zip(options.infiles, output_paths):
187*e1fe3e4aSElliott Hughes                if output_path:
188*e1fe3e4aSElliott Hughes                    _copytree(input_path, output_path)
189*e1fe3e4aSElliott Hughes    else:
190*e1fe3e4aSElliott Hughes        jobs = min(len(options.infiles), options.jobs) if options.jobs > 1 else 1
191*e1fe3e4aSElliott Hughes        if jobs > 1:
192*e1fe3e4aSElliott Hughes            func = partial(_font_to_quadratic, **kwargs)
193*e1fe3e4aSElliott Hughes            logger.info("Running %d parallel processes", jobs)
194*e1fe3e4aSElliott Hughes            with closing(mp.Pool(jobs)) as pool:
195*e1fe3e4aSElliott Hughes                pool.starmap(func, zip(options.infiles, output_paths))
196*e1fe3e4aSElliott Hughes        else:
197*e1fe3e4aSElliott Hughes            for input_path, output_path in zip(options.infiles, output_paths):
198*e1fe3e4aSElliott Hughes                _font_to_quadratic(input_path, output_path, **kwargs)
199