mirror of
https://github.com/ValveSoftware/Proton.git
synced 2025-02-05 10:10:44 +03:00
a2eafa8d47
CW-Bug-Id: #21346
175 lines
5.4 KiB
Python
175 lines
5.4 KiB
Python
# Copied from https://github.com/adobe-type-tools/afdko/blob/develop/python/afdko/otf2ttf.py
|
|
|
|
import argparse
|
|
import glob
|
|
import logging
|
|
import os
|
|
import sys
|
|
from functools import partial, singledispatch
|
|
from itertools import chain
|
|
from multiprocessing import Pool
|
|
|
|
from fontTools import configLogger
|
|
from fontTools.misc.cliTools import makeOutputFileName
|
|
from fontTools.pens.cu2quPen import Cu2QuPen
|
|
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
|
from fontTools.ttLib import TTCollection, TTFont, TTLibError, newTable
|
|
|
|
log = logging.getLogger()
|
|
configLogger(logger=log)
|
|
|
|
# default approximation error, measured in UPEM
|
|
MAX_ERR = 1.0
|
|
|
|
# default 'post' table format
|
|
POST_FORMAT = 2.0
|
|
|
|
# assuming the input contours' direction is correctly set (counter-clockwise),
|
|
# we just flip it to clockwise
|
|
REVERSE_DIRECTION = True
|
|
|
|
|
|
def glyphs_to_quadratic(
|
|
glyphs, max_err=MAX_ERR, reverse_direction=REVERSE_DIRECTION):
|
|
quadGlyphs = {}
|
|
for gname in glyphs.keys():
|
|
glyph = glyphs[gname]
|
|
ttPen = TTGlyphPen(glyphs)
|
|
cu2quPen = Cu2QuPen(ttPen, max_err,
|
|
reverse_direction=reverse_direction)
|
|
glyph.draw(cu2quPen)
|
|
quadGlyphs[gname] = ttPen.glyph()
|
|
return quadGlyphs
|
|
|
|
|
|
def update_hmtx(ttFont, glyf):
|
|
hmtx = ttFont["hmtx"]
|
|
for glyphName, glyph in glyf.glyphs.items():
|
|
if hasattr(glyph, 'xMin'):
|
|
hmtx[glyphName] = (hmtx[glyphName][0], glyph.xMin)
|
|
|
|
|
|
@singledispatch
|
|
def otf_to_ttf(ttFont, post_format=POST_FORMAT, **kwargs):
|
|
if ttFont.sfntVersion != "OTTO":
|
|
raise TTLibError("Not a OpenType font (bad sfntVersion)")
|
|
assert "CFF " in ttFont
|
|
|
|
glyphOrder = ttFont.getGlyphOrder()
|
|
|
|
ttFont["loca"] = newTable("loca")
|
|
ttFont["glyf"] = glyf = newTable("glyf")
|
|
glyf.glyphOrder = glyphOrder
|
|
glyf.glyphs = glyphs_to_quadratic(ttFont.getGlyphSet(), **kwargs)
|
|
del ttFont["CFF "]
|
|
if "VORG" in ttFont:
|
|
del ttFont["VORG"]
|
|
glyf.compile(ttFont)
|
|
update_hmtx(ttFont, glyf)
|
|
|
|
ttFont["maxp"] = maxp = newTable("maxp")
|
|
maxp.tableVersion = 0x00010000
|
|
maxp.maxZones = 1
|
|
maxp.maxTwilightPoints = 0
|
|
maxp.maxStorage = 0
|
|
maxp.maxFunctionDefs = 0
|
|
maxp.maxInstructionDefs = 0
|
|
maxp.maxStackElements = 0
|
|
maxp.maxSizeOfInstructions = 0
|
|
maxp.maxComponentElements = max(
|
|
len(g.components if hasattr(g, 'components') else [])
|
|
for g in glyf.glyphs.values())
|
|
maxp.compile(ttFont)
|
|
|
|
post = ttFont["post"]
|
|
post.formatType = post_format
|
|
post.extraNames = []
|
|
post.mapping = {}
|
|
post.glyphOrder = glyphOrder
|
|
try:
|
|
post.compile(ttFont)
|
|
except OverflowError:
|
|
post.formatType = 3
|
|
log.warning("Dropping glyph names, they do not fit in 'post' table.")
|
|
|
|
ttFont.sfntVersion = "\000\001\000\000"
|
|
|
|
|
|
@otf_to_ttf.register(TTCollection)
|
|
def _(fonts, **kwargs):
|
|
skip = 0
|
|
for font in fonts:
|
|
try:
|
|
otf_to_ttf(font, **kwargs)
|
|
except TTLibError as warn:
|
|
skip += 1
|
|
log.warning(warn)
|
|
|
|
if skip == len(fonts):
|
|
raise TTLibError("a Font Collection that has Not a OpenType font")
|
|
|
|
|
|
def run(path, options):
|
|
try:
|
|
font = TTFont(path, fontNumber=options.face_index)
|
|
extension = '.ttf'
|
|
except TTLibError:
|
|
font = TTCollection(path)
|
|
extension = '.ttc'
|
|
|
|
if options.output and not os.path.isdir(options.output):
|
|
output = options.output
|
|
else:
|
|
output = makeOutputFileName(path, outputDir=options.output,
|
|
extension=extension,
|
|
overWrite=options.overwrite)
|
|
|
|
try:
|
|
otf_to_ttf(font,
|
|
post_format=options.post_format,
|
|
max_err=options.max_error,
|
|
reverse_direction=options.reverse_direction)
|
|
except TTLibError as warn:
|
|
log.warning(f'"{path}" cannot be converted since it is {warn}.')
|
|
else:
|
|
font.save(output)
|
|
|
|
|
|
def main(args=None):
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("input", nargs='+', metavar="INPUT")
|
|
parser.add_argument("-o", "--output")
|
|
parser.add_argument("-e", "--max-error", type=float, default=MAX_ERR)
|
|
parser.add_argument("--post-format", type=float, default=POST_FORMAT)
|
|
parser.add_argument(
|
|
"--keep-direction", dest='reverse_direction', action='store_false')
|
|
parser.add_argument("--face-index", type=int, default=-1)
|
|
parser.add_argument("--overwrite", action='store_true')
|
|
options = parser.parse_args(args)
|
|
|
|
if options.output and len(options.input) > 1:
|
|
if not os.path.isdir(options.output):
|
|
parser.error("-o/--output option must be a directory when "
|
|
"processing multiple fonts")
|
|
|
|
files = list(chain.from_iterable(map(glob.glob, options.input)))
|
|
|
|
# Set the pool capacity to be the minimum of file quantity and CPU count,
|
|
# at least 1.
|
|
poolCapacity = max(min(os.cpu_count(), len(files)), 1)
|
|
# Limit parallel capacity to 60 on win32 to avoid WaitForMultipleObjects
|
|
# errors. See https://bugs.python.org/issue45077
|
|
if poolCapacity > 60 and sys.platform == "win32":
|
|
poolCapacity = 60
|
|
# Do not use "with" statement, or code coverage will malfunction.
|
|
pool = Pool(poolCapacity)
|
|
try:
|
|
pool.map(partial(run, options=options), files)
|
|
finally:
|
|
pool.close()
|
|
pool.join()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|