• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3import argparse
4import logging
5import os
6import sys
7
8from fontTools.pens.cu2quPen import Cu2QuPen
9from fontTools import configLogger
10from fontTools.misc.cliTools import makeOutputFileName
11from fontTools.pens.ttGlyphPen import TTGlyphPen
12from fontTools.ttLib import TTFont, newTable
13
14
15log = logging.getLogger()
16
17# default approximation error, measured in UPEM
18MAX_ERR = 1.0
19
20# default 'post' table format
21POST_FORMAT = 2.0
22
23# assuming the input contours' direction is correctly set (counter-clockwise),
24# we just flip it to clockwise
25REVERSE_DIRECTION = True
26
27
28def glyphs_to_quadratic(
29        glyphs, max_err=MAX_ERR, reverse_direction=REVERSE_DIRECTION):
30    quadGlyphs = {}
31    for gname in glyphs.keys():
32        glyph = glyphs[gname]
33        ttPen = TTGlyphPen(glyphs)
34        cu2quPen = Cu2QuPen(ttPen, max_err,
35                            reverse_direction=reverse_direction)
36        glyph.draw(cu2quPen)
37        quadGlyphs[gname] = ttPen.glyph()
38    return quadGlyphs
39
40
41def update_hmtx(ttFont, glyf):
42    hmtx = ttFont["hmtx"]
43    for glyphName, glyph in glyf.glyphs.items():
44        if hasattr(glyph, 'xMin'):
45            hmtx[glyphName] = (hmtx[glyphName][0], glyph.xMin)
46
47
48def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs):
49    assert ttFont.sfntVersion == "OTTO"
50    assert "CFF " in ttFont
51
52    glyphOrder = ttFont.getGlyphOrder()
53
54    ttFont["loca"] = newTable("loca")
55    ttFont["glyf"] = glyf = newTable("glyf")
56    glyf.glyphOrder = glyphOrder
57    glyf.glyphs = glyphs_to_quadratic(ttFont.getGlyphSet(), **kwargs)
58    del ttFont["CFF "]
59    glyf.compile(ttFont)
60    update_hmtx(ttFont, glyf)
61
62    ttFont["maxp"] = maxp = newTable("maxp")
63    maxp.tableVersion = 0x00010000
64    maxp.maxZones = 1
65    maxp.maxTwilightPoints = 0
66    maxp.maxStorage = 0
67    maxp.maxFunctionDefs = 0
68    maxp.maxInstructionDefs = 0
69    maxp.maxStackElements = 0
70    maxp.maxSizeOfInstructions = 0
71    maxp.maxComponentElements = max(
72        len(g.components if hasattr(g, 'components') else [])
73        for g in glyf.glyphs.values())
74    maxp.compile(ttFont)
75
76    post = ttFont["post"]
77    post.formatType = post_format
78    post.extraNames = []
79    post.mapping = {}
80    post.glyphOrder = glyphOrder
81    try:
82        post.compile(ttFont)
83    except OverflowError:
84        post.formatType = 3
85        log.warning("Dropping glyph names, they do not fit in 'post' table.")
86
87    ttFont.sfntVersion = "\000\001\000\000"
88
89
90def main(args=None):
91    configLogger(logger=log)
92
93    parser = argparse.ArgumentParser()
94    parser.add_argument("input", nargs='+', metavar="INPUT")
95    parser.add_argument("-o", "--output")
96    parser.add_argument("-e", "--max-error", type=float, default=MAX_ERR)
97    parser.add_argument("--post-format", type=float, default=POST_FORMAT)
98    parser.add_argument(
99        "--keep-direction", dest='reverse_direction', action='store_false')
100    parser.add_argument("--face-index", type=int, default=0)
101    parser.add_argument("--overwrite", action='store_true')
102    options = parser.parse_args(args)
103
104    if options.output and len(options.input) > 1:
105        if not os.path.isdir(options.output):
106            parser.error("-o/--output option must be a directory when "
107                         "processing multiple fonts")
108
109    for path in options.input:
110        if options.output and not os.path.isdir(options.output):
111            output = options.output
112        else:
113            output = makeOutputFileName(path, outputDir=options.output,
114                                        extension='.ttf',
115                                        overWrite=options.overwrite)
116
117        font = TTFont(path, fontNumber=options.face_index)
118        otf_to_ttf(font,
119                   post_format=options.post_format,
120                   max_err=options.max_error,
121                   reverse_direction=options.reverse_direction)
122        font.save(output)
123
124
125if __name__ == "__main__":
126    sys.exit(main())
127