up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
305
venv/lib/python3.13/site-packages/fontTools/cffLib/CFFToCFF2.py
Normal file
305
venv/lib/python3.13/site-packages/fontTools/cffLib/CFFToCFF2.py
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
"""CFF to CFF2 converter."""
|
||||
|
||||
from fontTools.ttLib import TTFont, newTable
|
||||
from fontTools.misc.cliTools import makeOutputFileName
|
||||
from fontTools.misc.psCharStrings import T2WidthExtractor
|
||||
from fontTools.cffLib import (
|
||||
TopDictIndex,
|
||||
FDArrayIndex,
|
||||
FontDict,
|
||||
buildOrder,
|
||||
topDictOperators,
|
||||
privateDictOperators,
|
||||
topDictOperators2,
|
||||
privateDictOperators2,
|
||||
)
|
||||
from io import BytesIO
|
||||
import logging
|
||||
|
||||
__all__ = ["convertCFFToCFF2", "main"]
|
||||
|
||||
|
||||
log = logging.getLogger("fontTools.cffLib")
|
||||
|
||||
|
||||
class _NominalWidthUsedError(Exception):
|
||||
def __add__(self, other):
|
||||
raise self
|
||||
|
||||
def __radd__(self, other):
|
||||
raise self
|
||||
|
||||
|
||||
def _convertCFFToCFF2(cff, otFont):
|
||||
"""Converts this object from CFF format to CFF2 format. This conversion
|
||||
is done 'in-place'. The conversion cannot be reversed.
|
||||
|
||||
This assumes a decompiled CFF table. (i.e. that the object has been
|
||||
filled via :meth:`decompile` and e.g. not loaded from XML.)"""
|
||||
|
||||
# Clean up T2CharStrings
|
||||
|
||||
topDict = cff.topDictIndex[0]
|
||||
fdArray = topDict.FDArray if hasattr(topDict, "FDArray") else None
|
||||
charStrings = topDict.CharStrings
|
||||
globalSubrs = cff.GlobalSubrs
|
||||
localSubrs = (
|
||||
[getattr(fd.Private, "Subrs", []) for fd in fdArray]
|
||||
if fdArray
|
||||
else (
|
||||
[topDict.Private.Subrs]
|
||||
if hasattr(topDict, "Private") and hasattr(topDict.Private, "Subrs")
|
||||
else []
|
||||
)
|
||||
)
|
||||
|
||||
for glyphName in charStrings.keys():
|
||||
cs, fdIndex = charStrings.getItemAndSelector(glyphName)
|
||||
cs.decompile()
|
||||
|
||||
# Clean up subroutines first
|
||||
for subrs in [globalSubrs] + localSubrs:
|
||||
for subr in subrs:
|
||||
program = subr.program
|
||||
i = j = len(program)
|
||||
try:
|
||||
i = program.index("return")
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
j = program.index("endchar")
|
||||
except ValueError:
|
||||
pass
|
||||
program[min(i, j) :] = []
|
||||
|
||||
# Clean up glyph charstrings
|
||||
removeUnusedSubrs = False
|
||||
nominalWidthXError = _NominalWidthUsedError()
|
||||
for glyphName in charStrings.keys():
|
||||
cs, fdIndex = charStrings.getItemAndSelector(glyphName)
|
||||
program = cs.program
|
||||
|
||||
thisLocalSubrs = (
|
||||
localSubrs[fdIndex]
|
||||
if fdIndex is not None
|
||||
else (
|
||||
getattr(topDict.Private, "Subrs", [])
|
||||
if hasattr(topDict, "Private")
|
||||
else []
|
||||
)
|
||||
)
|
||||
|
||||
# Intentionally use custom type for nominalWidthX, such that any
|
||||
# CharString that has an explicit width encoded will throw back to us.
|
||||
extractor = T2WidthExtractor(
|
||||
thisLocalSubrs,
|
||||
globalSubrs,
|
||||
nominalWidthXError,
|
||||
0,
|
||||
)
|
||||
try:
|
||||
extractor.execute(cs)
|
||||
except _NominalWidthUsedError:
|
||||
# Program has explicit width. We want to drop it, but can't
|
||||
# just pop the first number since it may be a subroutine call.
|
||||
# Instead, when seeing that, we embed the subroutine and recurse.
|
||||
# If this ever happened, we later prune unused subroutines.
|
||||
while len(program) >= 2 and program[1] in ["callsubr", "callgsubr"]:
|
||||
removeUnusedSubrs = True
|
||||
subrNumber = program.pop(0)
|
||||
assert isinstance(subrNumber, int), subrNumber
|
||||
op = program.pop(0)
|
||||
bias = extractor.localBias if op == "callsubr" else extractor.globalBias
|
||||
subrNumber += bias
|
||||
subrSet = thisLocalSubrs if op == "callsubr" else globalSubrs
|
||||
subrProgram = subrSet[subrNumber].program
|
||||
program[:0] = subrProgram
|
||||
# Now pop the actual width
|
||||
assert len(program) >= 1, program
|
||||
program.pop(0)
|
||||
|
||||
if program and program[-1] == "endchar":
|
||||
program.pop()
|
||||
|
||||
if removeUnusedSubrs:
|
||||
cff.remove_unused_subroutines()
|
||||
|
||||
# Upconvert TopDict
|
||||
|
||||
cff.major = 2
|
||||
cff2GetGlyphOrder = cff.otFont.getGlyphOrder
|
||||
topDictData = TopDictIndex(None, cff2GetGlyphOrder)
|
||||
for item in cff.topDictIndex:
|
||||
# Iterate over, such that all are decompiled
|
||||
topDictData.append(item)
|
||||
cff.topDictIndex = topDictData
|
||||
topDict = topDictData[0]
|
||||
if hasattr(topDict, "Private"):
|
||||
privateDict = topDict.Private
|
||||
else:
|
||||
privateDict = None
|
||||
opOrder = buildOrder(topDictOperators2)
|
||||
topDict.order = opOrder
|
||||
topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
|
||||
|
||||
if not hasattr(topDict, "FDArray"):
|
||||
fdArray = topDict.FDArray = FDArrayIndex()
|
||||
fdArray.strings = None
|
||||
fdArray.GlobalSubrs = topDict.GlobalSubrs
|
||||
topDict.GlobalSubrs.fdArray = fdArray
|
||||
charStrings = topDict.CharStrings
|
||||
if charStrings.charStringsAreIndexed:
|
||||
charStrings.charStringsIndex.fdArray = fdArray
|
||||
else:
|
||||
charStrings.fdArray = fdArray
|
||||
fontDict = FontDict()
|
||||
fontDict.setCFF2(True)
|
||||
fdArray.append(fontDict)
|
||||
fontDict.Private = privateDict
|
||||
privateOpOrder = buildOrder(privateDictOperators2)
|
||||
if privateDict is not None:
|
||||
for entry in privateDictOperators:
|
||||
key = entry[1]
|
||||
if key not in privateOpOrder:
|
||||
if key in privateDict.rawDict:
|
||||
# print "Removing private dict", key
|
||||
del privateDict.rawDict[key]
|
||||
if hasattr(privateDict, key):
|
||||
delattr(privateDict, key)
|
||||
# print "Removing privateDict attr", key
|
||||
else:
|
||||
# clean up the PrivateDicts in the fdArray
|
||||
fdArray = topDict.FDArray
|
||||
privateOpOrder = buildOrder(privateDictOperators2)
|
||||
for fontDict in fdArray:
|
||||
fontDict.setCFF2(True)
|
||||
for key in list(fontDict.rawDict.keys()):
|
||||
if key not in fontDict.order:
|
||||
del fontDict.rawDict[key]
|
||||
if hasattr(fontDict, key):
|
||||
delattr(fontDict, key)
|
||||
|
||||
privateDict = fontDict.Private
|
||||
for entry in privateDictOperators:
|
||||
key = entry[1]
|
||||
if key not in privateOpOrder:
|
||||
if key in list(privateDict.rawDict.keys()):
|
||||
# print "Removing private dict", key
|
||||
del privateDict.rawDict[key]
|
||||
if hasattr(privateDict, key):
|
||||
delattr(privateDict, key)
|
||||
# print "Removing privateDict attr", key
|
||||
|
||||
# Now delete up the deprecated topDict operators from CFF 1.0
|
||||
for entry in topDictOperators:
|
||||
key = entry[1]
|
||||
# We seem to need to keep the charset operator for now,
|
||||
# or we fail to compile with some fonts, like AdditionFont.otf.
|
||||
# I don't know which kind of CFF font those are. But keeping
|
||||
# charset seems to work. It will be removed when we save and
|
||||
# read the font again.
|
||||
#
|
||||
# AdditionFont.otf has <Encoding name="StandardEncoding"/>.
|
||||
if key == "charset":
|
||||
continue
|
||||
if key not in opOrder:
|
||||
if key in topDict.rawDict:
|
||||
del topDict.rawDict[key]
|
||||
if hasattr(topDict, key):
|
||||
delattr(topDict, key)
|
||||
|
||||
# TODO(behdad): What does the following comment even mean? Both CFF and CFF2
|
||||
# use the same T2Charstring class. I *think* what it means is that the CharStrings
|
||||
# were loaded for CFF1, and we need to reload them for CFF2 to set varstore, etc
|
||||
# on them. At least that's what I understand. It's probably safe to remove this
|
||||
# and just set vstore where needed.
|
||||
#
|
||||
# See comment above about charset as well.
|
||||
|
||||
# At this point, the Subrs and Charstrings are all still T2Charstring class
|
||||
# easiest to fix this by compiling, then decompiling again
|
||||
file = BytesIO()
|
||||
cff.compile(file, otFont, isCFF2=True)
|
||||
file.seek(0)
|
||||
cff.decompile(file, otFont, isCFF2=True)
|
||||
|
||||
|
||||
def convertCFFToCFF2(font):
|
||||
cff = font["CFF "].cff
|
||||
del font["CFF "]
|
||||
_convertCFFToCFF2(cff, font)
|
||||
table = font["CFF2"] = newTable("CFF2")
|
||||
table.cff = cff
|
||||
|
||||
|
||||
def main(args=None):
|
||||
"""Convert CFF OTF font to CFF2 OTF font"""
|
||||
if args is None:
|
||||
import sys
|
||||
|
||||
args = sys.argv[1:]
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
"fonttools cffLib.CFFToCFF2",
|
||||
description="Upgrade a CFF font to CFF2.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
metavar="OUTPUT.ttf",
|
||||
default=None,
|
||||
help="Output instance OTF file (default: INPUT-CFF2.ttf).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-recalc-timestamp",
|
||||
dest="recalc_timestamp",
|
||||
action="store_false",
|
||||
help="Don't set the output font's timestamp to the current time.",
|
||||
)
|
||||
loggingGroup = parser.add_mutually_exclusive_group(required=False)
|
||||
loggingGroup.add_argument(
|
||||
"-v", "--verbose", action="store_true", help="Run more verbosely."
|
||||
)
|
||||
loggingGroup.add_argument(
|
||||
"-q", "--quiet", action="store_true", help="Turn verbosity off."
|
||||
)
|
||||
options = parser.parse_args(args)
|
||||
|
||||
from fontTools import configLogger
|
||||
|
||||
configLogger(
|
||||
level=("DEBUG" if options.verbose else "ERROR" if options.quiet else "INFO")
|
||||
)
|
||||
|
||||
import os
|
||||
|
||||
infile = options.input
|
||||
if not os.path.isfile(infile):
|
||||
parser.error("No such file '{}'".format(infile))
|
||||
|
||||
outfile = (
|
||||
makeOutputFileName(infile, overWrite=True, suffix="-CFF2")
|
||||
if not options.output
|
||||
else options.output
|
||||
)
|
||||
|
||||
font = TTFont(infile, recalcTimestamp=options.recalc_timestamp, recalcBBoxes=False)
|
||||
|
||||
convertCFFToCFF2(font)
|
||||
|
||||
log.info(
|
||||
"Saving %s",
|
||||
outfile,
|
||||
)
|
||||
font.save(outfile)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
Loading…
Add table
Add a link
Reference in a new issue