aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorAlexander Smirnov <alex@ydb.tech>2024-11-29 13:32:04 +0000
committerAlexander Smirnov <alex@ydb.tech>2024-11-29 13:32:04 +0000
commit03fc6a84cd47911dfcc873669a69da6cbcc308d1 (patch)
tree650bf49d8971dbf6bafa9dd0488b8305a5579092 /contrib/python
parent991917c6cbd9969fcb3741d24d0fa0d2e0cfaba0 (diff)
parent56a560baa86b52c66ce622414579975930421950 (diff)
downloadydb-03fc6a84cd47911dfcc873669a69da6cbcc308d1.tar.gz
Merge branch 'rightlib' into mergelibs-241129-1330
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/fonttools/.dist-info/METADATA21
-rw-r--r--contrib/python/fonttools/fontTools/__init__.py2
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py18
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/CFFToCFF2.py6
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/__init__.py15
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/specializer.py191
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/transforms.py2
-rw-r--r--contrib/python/fonttools/fontTools/fontBuilder.py27
-rw-r--r--contrib/python/fonttools/fontTools/misc/testTools.py2
-rw-r--r--contrib/python/fonttools/fontTools/subset/__init__.py7
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/removeOverlaps.py6
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/reorderGlyphs.py10
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/tables/TupleVariation.py84
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py77
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py13
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/tables/otConverters.py1
-rw-r--r--contrib/python/fonttools/fontTools/ufoLib/__init__.py71
-rw-r--r--contrib/python/fonttools/fontTools/ufoLib/glifLib.py14
-rw-r--r--contrib/python/fonttools/fontTools/ufoLib/plistlib.py2
-rw-r--r--contrib/python/fonttools/fontTools/ufoLib/pointPen.py2
-rw-r--r--contrib/python/fonttools/fontTools/varLib/__init__.py17
-rw-r--r--contrib/python/fonttools/fontTools/varLib/interpolatable.py167
-rw-r--r--contrib/python/fonttools/fontTools/varLib/interpolatableTestContourOrder.py8
-rw-r--r--contrib/python/fonttools/fontTools/varLib/multiVarStore.py2
-rw-r--r--contrib/python/fonttools/fontTools/varLib/mutator.py4
-rw-r--r--contrib/python/fonttools/fontTools/varLib/stat.py7
-rw-r--r--contrib/python/fonttools/ya.make2
27 files changed, 572 insertions, 206 deletions
diff --git a/contrib/python/fonttools/.dist-info/METADATA b/contrib/python/fonttools/.dist-info/METADATA
index 419722ab40..8ad26257c5 100644
--- a/contrib/python/fonttools/.dist-info/METADATA
+++ b/contrib/python/fonttools/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: fonttools
-Version: 4.54.1
+Version: 4.55.0
Summary: Tools to manipulate font files
Home-page: http://github.com/fonttools/fonttools
Author: Just van Rossum
@@ -23,6 +23,7 @@ Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Text Processing :: Fonts
Classifier: Topic :: Multimedia :: Graphics
@@ -376,6 +377,24 @@ Have fun!
Changelog
~~~~~~~~~
+4.55.0 (released 2024-11-14)
+----------------------------
+
+
+- [cffLib.specializer] Adjust stack use calculation (#3689)
+- [varLib] Lets not add mac names if the rest of name doesn't have them (#3688)
+- [ttLib.reorderGlyphs] Update CFF table charstrings and charset (#3682)
+- [cffLib.specializer] Add cmdline to specialize a CFF2 font (#3675, #3679)
+- [CFF2] Lift uint16 VariationStore.length limitation (#3674)
+- [subset] consider variation selectors subsetting cmap14 (#3672)
+- [varLib.interpolatable] Support CFF2 fonts (#3670)
+- Set isfinal to true in XML parser for proper resource cleanup (#3669)
+- [removeOverlaps] Fix CFF CharString width (#3659)
+- [glyf] Add optimizeSize option (#3657)
+- Python 3.13 support (#3656)
+- [TupleVariation] Optimize for loading speed, not size (#3650, #3653)
+
+
4.54.1 (released 2024-09-24)
----------------------------
diff --git a/contrib/python/fonttools/fontTools/__init__.py b/contrib/python/fonttools/fontTools/__init__.py
index 0ec8b4ecfd..502ca5cec6 100644
--- a/contrib/python/fonttools/fontTools/__init__.py
+++ b/contrib/python/fonttools/fontTools/__init__.py
@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "4.54.1"
+version = __version__ = "4.55.0"
__all__ = ["version", "log", "configLogger"]
diff --git a/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py b/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py
index 689412ce2b..f929cc9686 100644
--- a/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py
+++ b/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py
@@ -32,9 +32,10 @@ def _convertCFF2ToCFF(cff, otFont):
cff.major = 1
- topDictData = TopDictIndex(None, isCFF2=True)
+ topDictData = TopDictIndex(None)
for item in cff.topDictIndex:
# Iterate over, such that all are decompiled
+ item.cff2GetGlyphOrder = None
topDictData.append(item)
cff.topDictIndex = topDictData
topDict = topDictData[0]
@@ -99,6 +100,21 @@ def _convertCFF2ToCFF(cff, otFont):
if width != private.defaultWidthX:
cs.program.insert(0, width - private.nominalWidthX)
+ mapping = {
+ name: ("cid" + str(n) if n else ".notdef")
+ for n, name in enumerate(topDict.charset)
+ }
+ topDict.charset = [
+ "cid" + str(n) if n else ".notdef" for n in range(len(topDict.charset))
+ ]
+ charStrings.charStrings = {
+ mapping[name]: v for name, v in charStrings.charStrings.items()
+ }
+
+ # I'm not sure why the following is *not* necessary. And it breaks
+ # the output if I add it.
+ # topDict.ROS = ("Adobe", "Identity", 0)
+
def convertCFF2ToCFF(font, *, updatePostTable=True):
cff = font["CFF2"].cff
diff --git a/contrib/python/fonttools/fontTools/cffLib/CFFToCFF2.py b/contrib/python/fonttools/fontTools/cffLib/CFFToCFF2.py
index 37463a5b9b..2555f0b242 100644
--- a/contrib/python/fonttools/fontTools/cffLib/CFFToCFF2.py
+++ b/contrib/python/fonttools/fontTools/cffLib/CFFToCFF2.py
@@ -81,7 +81,7 @@ def _convertCFFToCFF2(cff, otFont):
thisLocalSubrs = (
localSubrs[fdIndex]
- if fdIndex
+ if fdIndex is not None
else (
getattr(topDict.Private, "Subrs", [])
if hasattr(topDict, "Private")
@@ -104,9 +104,10 @@ def _convertCFFToCFF2(cff, otFont):
# 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 program[1] in ["callsubr", "callgsubr"]:
+ 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
@@ -114,6 +115,7 @@ def _convertCFFToCFF2(cff, otFont):
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":
diff --git a/contrib/python/fonttools/fontTools/cffLib/__init__.py b/contrib/python/fonttools/fontTools/cffLib/__init__.py
index c192ec77af..d75e23b750 100644
--- a/contrib/python/fonttools/fontTools/cffLib/__init__.py
+++ b/contrib/python/fonttools/fontTools/cffLib/__init__.py
@@ -1,7 +1,7 @@
"""cffLib: read/write Adobe CFF fonts
-OpenType fonts with PostScript outlines contain a completely independent
-font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts
+OpenType fonts with PostScript outlines embed a completely independent
+font file in Adobe's *Compact Font Format*. So dealing with OpenType fonts
requires also dealing with CFF. This module allows you to read and write
fonts written in the CFF format.
@@ -867,7 +867,11 @@ class VarStoreData(object):
if self.file:
# read data in from file. Assume position is correct.
length = readCard16(self.file)
- self.data = self.file.read(length)
+ # https://github.com/fonttools/fonttools/issues/3673
+ if length == 65535:
+ self.data = self.file.read()
+ else:
+ self.data = self.file.read(length)
globalState = {}
reader = OTTableReader(self.data, globalState)
self.otVarStore = ot.VarStore()
@@ -1956,7 +1960,8 @@ class VarStoreCompiler(object):
self.parent = parent
if not varStoreData.data:
varStoreData.compile()
- data = [packCard16(len(varStoreData.data)), varStoreData.data]
+ varStoreDataLen = min(0xFFFF, len(varStoreData.data))
+ data = [packCard16(varStoreDataLen), varStoreData.data]
self.data = bytesjoin(data)
def setPos(self, pos, endPos):
@@ -2281,7 +2286,7 @@ class DictCompiler(object):
# For PrivateDict BlueValues, the default font
# values are absolute, not relative.
# Must convert these back to relative coordinates
- # befor writing to CFF2.
+ # before writing to CFF2.
defaultValue = value[i][0]
firstList[i] = defaultValue - prevVal
prevVal = defaultValue
diff --git a/contrib/python/fonttools/fontTools/cffLib/specializer.py b/contrib/python/fonttools/fontTools/cffLib/specializer.py
index bb7f89e4ff..5fddcb67dd 100644
--- a/contrib/python/fonttools/fontTools/cffLib/specializer.py
+++ b/contrib/python/fonttools/fontTools/cffLib/specializer.py
@@ -80,8 +80,9 @@ def programToCommands(program, getNumRegions=None):
numBlendArgs = numBlends * numSourceFonts + 1
# replace first blend op by a list of the blend ops.
stack[-numBlendArgs:] = [stack[-numBlendArgs:]]
- lenBlendStack += numBlends + len(stack) - 1
- lastBlendIndex = len(stack)
+ lenStack = len(stack)
+ lenBlendStack += numBlends + lenStack - 1
+ lastBlendIndex = lenStack
# if a blend op exists, this is or will be a CFF2 charstring.
continue
@@ -153,9 +154,10 @@ def commandsToProgram(commands):
def _everyN(el, n):
"""Group the list el into groups of size n"""
- if len(el) % n != 0:
+ l = len(el)
+ if l % n != 0:
raise ValueError(el)
- for i in range(0, len(el), n):
+ for i in range(0, l, n):
yield el[i : i + n]
@@ -218,9 +220,10 @@ class _GeneralizerDecombinerCommandsMap(object):
@staticmethod
def hhcurveto(args):
- if len(args) < 4 or len(args) % 4 > 1:
+ l = len(args)
+ if l < 4 or l % 4 > 1:
raise ValueError(args)
- if len(args) % 2 == 1:
+ if l % 2 == 1:
yield ("rrcurveto", [args[1], args[0], args[2], args[3], args[4], 0])
args = args[5:]
for args in _everyN(args, 4):
@@ -228,9 +231,10 @@ class _GeneralizerDecombinerCommandsMap(object):
@staticmethod
def vvcurveto(args):
- if len(args) < 4 or len(args) % 4 > 1:
+ l = len(args)
+ if l < 4 or l % 4 > 1:
raise ValueError(args)
- if len(args) % 2 == 1:
+ if l % 2 == 1:
yield ("rrcurveto", [args[0], args[1], args[2], args[3], 0, args[4]])
args = args[5:]
for args in _everyN(args, 4):
@@ -238,11 +242,12 @@ class _GeneralizerDecombinerCommandsMap(object):
@staticmethod
def hvcurveto(args):
- if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}:
+ l = len(args)
+ if l < 4 or l % 8 not in {0, 1, 4, 5}:
raise ValueError(args)
last_args = None
- if len(args) % 2 == 1:
- lastStraight = len(args) % 8 == 5
+ if l % 2 == 1:
+ lastStraight = l % 8 == 5
args, last_args = args[:-5], args[-5:]
it = _everyN(args, 4)
try:
@@ -262,11 +267,12 @@ class _GeneralizerDecombinerCommandsMap(object):
@staticmethod
def vhcurveto(args):
- if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}:
+ l = len(args)
+ if l < 4 or l % 8 not in {0, 1, 4, 5}:
raise ValueError(args)
last_args = None
- if len(args) % 2 == 1:
- lastStraight = len(args) % 8 == 5
+ if l % 2 == 1:
+ lastStraight = l % 8 == 5
args, last_args = args[:-5], args[-5:]
it = _everyN(args, 4)
try:
@@ -286,7 +292,8 @@ class _GeneralizerDecombinerCommandsMap(object):
@staticmethod
def rcurveline(args):
- if len(args) < 8 or len(args) % 6 != 2:
+ l = len(args)
+ if l < 8 or l % 6 != 2:
raise ValueError(args)
args, last_args = args[:-2], args[-2:]
for args in _everyN(args, 6):
@@ -295,7 +302,8 @@ class _GeneralizerDecombinerCommandsMap(object):
@staticmethod
def rlinecurve(args):
- if len(args) < 8 or len(args) % 2 != 0:
+ l = len(args)
+ if l < 8 or l % 2 != 0:
raise ValueError(args)
args, last_args = args[:-6], args[-6:]
for args in _everyN(args, 2):
@@ -330,8 +338,9 @@ def _convertBlendOpToArgs(blendList):
# comprehension. See calling context
args = args[:-1]
- numRegions = len(args) // numBlends - 1
- if not (numBlends * (numRegions + 1) == len(args)):
+ l = len(args)
+ numRegions = l // numBlends - 1
+ if not (numBlends * (numRegions + 1) == l):
raise ValueError(blendList)
defaultArgs = [[arg] for arg in args[:numBlends]]
@@ -368,7 +377,7 @@ def generalizeCommands(commands, ignoreErrors=False):
raise
func = getattr(mapping, op, None)
- if not func:
+ if func is None:
result.append((op, args))
continue
try:
@@ -446,9 +455,9 @@ def _convertToBlendCmds(args):
i = 0
while i < num_args:
arg = args[i]
+ i += 1
if not isinstance(arg, list):
new_args.append(arg)
- i += 1
stack_use += 1
else:
prev_stack_use = stack_use
@@ -458,21 +467,26 @@ def _convertToBlendCmds(args):
# up to the max stack limit.
num_sources = len(arg) - 1
blendlist = [arg]
- i += 1
stack_use += 1 + num_sources # 1 for the num_blends arg
- while (i < num_args) and isinstance(args[i], list):
+
+ # if we are here, max stack is the CFF2 max stack.
+ # I use the CFF2 max stack limit here rather than
+ # the 'maxstack' chosen by the client, as the default
+ # maxstack may have been used unintentionally. For all
+ # the other operators, this just produces a little less
+ # optimization, but here it puts a hard (and low) limit
+ # on the number of source fonts that can be used.
+ #
+ # Make sure the stack depth does not exceed (maxstack - 1), so
+ # that subroutinizer can insert subroutine calls at any point.
+ while (
+ (i < num_args)
+ and isinstance(args[i], list)
+ and stack_use + num_sources < maxStackLimit
+ ):
blendlist.append(args[i])
i += 1
stack_use += num_sources
- if stack_use + num_sources > maxStackLimit:
- # if we are here, max stack is the CFF2 max stack.
- # I use the CFF2 max stack limit here rather than
- # the 'maxstack' chosen by the client, as the default
- # maxstack may have been used unintentionally. For all
- # the other operators, this just produces a little less
- # optimization, but here it puts a hard (and low) limit
- # on the number of source fonts that can be used.
- break
# blendList now contains as many single blend tuples as can be
# combined without exceeding the CFF2 stack limit.
num_blends = len(blendlist)
@@ -504,6 +518,19 @@ def _addArgs(a, b):
return a + b
+def _argsStackUse(args):
+ stackLen = 0
+ maxLen = 0
+ for arg in args:
+ if type(arg) is list:
+ # Blended arg
+ maxLen = max(maxLen, stackLen + _argsStackUse(arg))
+ stackLen += arg[-1]
+ else:
+ stackLen += 1
+ return max(stackLen, maxLen)
+
+
def specializeCommands(
commands,
ignoreErrors=False,
@@ -697,6 +724,7 @@ def specializeCommands(
continue
# 5. Combine adjacent operators when possible, minding not to go over max stack size.
+ stackUse = _argsStackUse(commands[-1][1]) if commands else 0
for i in range(len(commands) - 1, 0, -1):
op1, args1 = commands[i - 1]
op2, args2 = commands[i]
@@ -707,9 +735,10 @@ def specializeCommands(
if op1 == op2:
new_op = op1
else:
- if op2 == "rrcurveto" and len(args2) == 6:
+ l = len(args2)
+ if op2 == "rrcurveto" and l == 6:
new_op = "rlinecurve"
- elif len(args2) == 2:
+ elif l == 2:
new_op = "rcurveline"
elif (op1, op2) in {("rlineto", "rlinecurve"), ("rrcurveto", "rcurveline")}:
@@ -746,9 +775,14 @@ def specializeCommands(
# Make sure the stack depth does not exceed (maxstack - 1), so
# that subroutinizer can insert subroutine calls at any point.
- if new_op and len(args1) + len(args2) < maxstack:
+ args1StackUse = _argsStackUse(args1)
+ combinedStackUse = max(args1StackUse, len(args1) + stackUse)
+ if new_op and combinedStackUse < maxstack:
commands[i - 1] = (new_op, args1 + args2)
del commands[i]
+ stackUse = combinedStackUse
+ else:
+ stackUse = args1StackUse
# 6. Resolve any remaining made-up operators into real operators.
for i in range(len(commands)):
@@ -759,9 +793,11 @@ def specializeCommands(
continue
if op[2:] == "curveto" and op[:2] not in {"rr", "hh", "vv", "vh", "hv"}:
+ l = len(args)
+
op0, op1 = op[:2]
if (op0 == "r") ^ (op1 == "r"):
- assert len(args) % 2 == 1
+ assert l % 2 == 1
if op0 == "0":
op0 = "h"
if op1 == "0":
@@ -772,9 +808,9 @@ def specializeCommands(
op1 = _negateCategory(op0)
assert {op0, op1} <= {"h", "v"}, (op0, op1)
- if len(args) % 2:
+ if l % 2:
if op0 != op1: # vhcurveto / hvcurveto
- if (op0 == "h") ^ (len(args) % 8 == 1):
+ if (op0 == "h") ^ (l % 8 == 1):
# Swap last two args order
args = args[:-2] + args[-1:] + args[-2:-1]
else: # hhcurveto / vvcurveto
@@ -822,26 +858,67 @@ if __name__ == "__main__":
default=None,
help="Number of variable-font regions for blend opertaions.",
)
+ parser.add_argument(
+ "--font",
+ metavar="FONTFILE",
+ default=None,
+ help="CFF2 font to specialize.",
+ )
+ parser.add_argument(
+ "-o",
+ "--output-file",
+ type=str,
+ help="Output font file name.",
+ )
options = parser.parse_args(sys.argv[1:])
- getNumRegions = (
- None
- if options.num_regions is None
- else lambda vsIndex: int(options.num_regions[0 if vsIndex is None else vsIndex])
- )
-
- program = stringToProgram(options.program)
- print("Program:")
- print(programToString(program))
- commands = programToCommands(program, getNumRegions)
- print("Commands:")
- print(commands)
- program2 = commandsToProgram(commands)
- print("Program from commands:")
- print(programToString(program2))
- assert program == program2
- print("Generalized program:")
- print(programToString(generalizeProgram(program, getNumRegions)))
- print("Specialized program:")
- print(programToString(specializeProgram(program, getNumRegions)))
+ if options.program:
+ getNumRegions = (
+ None
+ if options.num_regions is None
+ else lambda vsIndex: int(
+ options.num_regions[0 if vsIndex is None else vsIndex]
+ )
+ )
+
+ program = stringToProgram(options.program)
+ print("Program:")
+ print(programToString(program))
+ commands = programToCommands(program, getNumRegions)
+ print("Commands:")
+ print(commands)
+ program2 = commandsToProgram(commands)
+ print("Program from commands:")
+ print(programToString(program2))
+ assert program == program2
+ print("Generalized program:")
+ print(programToString(generalizeProgram(program, getNumRegions)))
+ print("Specialized program:")
+ print(programToString(specializeProgram(program, getNumRegions)))
+
+ if options.font:
+ from fontTools.ttLib import TTFont
+
+ font = TTFont(options.font)
+ cff2 = font["CFF2"].cff.topDictIndex[0]
+ charstrings = cff2.CharStrings
+ for glyphName in charstrings.keys():
+ charstring = charstrings[glyphName]
+ charstring.decompile()
+ getNumRegions = charstring.private.getNumRegions
+ charstring.program = specializeProgram(
+ charstring.program, getNumRegions, maxstack=maxStackLimit
+ )
+
+ if options.output_file is None:
+ from fontTools.misc.cliTools import makeOutputFileName
+
+ outfile = makeOutputFileName(
+ options.font, overWrite=True, suffix=".specialized"
+ )
+ else:
+ outfile = options.output_file
+ if outfile:
+ print("Saving", outfile)
+ font.save(outfile)
diff --git a/contrib/python/fonttools/fontTools/cffLib/transforms.py b/contrib/python/fonttools/fontTools/cffLib/transforms.py
index 91f6999fe6..5b474a7cd8 100644
--- a/contrib/python/fonttools/fontTools/cffLib/transforms.py
+++ b/contrib/python/fonttools/fontTools/cffLib/transforms.py
@@ -457,6 +457,8 @@ def remove_unused_subroutines(cff):
if subrs == font.GlobalSubrs:
if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"):
local_subrs = font.Private.Subrs
+ elif hasattr(font, "FDArray") and len(font.FDArray) == 1:
+ local_subrs = font.FDArray[0].Private.Subrs
else:
local_subrs = None
else:
diff --git a/contrib/python/fonttools/fontTools/fontBuilder.py b/contrib/python/fonttools/fontTools/fontBuilder.py
index 16b7ee167d..d4af38fba4 100644
--- a/contrib/python/fonttools/fontTools/fontBuilder.py
+++ b/contrib/python/fonttools/fontTools/fontBuilder.py
@@ -918,7 +918,15 @@ class FontBuilder(object):
"""
from .otlLib.builder import buildStatTable
- buildStatTable(self.font, axes, locations, elidedFallbackName)
+ assert "name" in self.font, "name must to be set up first"
+
+ buildStatTable(
+ self.font,
+ axes,
+ locations,
+ elidedFallbackName,
+ macNames=any(nr.platformID == 1 for nr in self.font["name"].names),
+ )
def buildCmapSubTable(cmapping, format, platformID, platEncID):
@@ -938,6 +946,15 @@ def addFvar(font, axes, instances):
fvar = newTable("fvar")
nameTable = font["name"]
+ # if there are not currently any mac names don't add them here, that's inconsistent
+ # https://github.com/fonttools/fonttools/issues/683
+ macNames = any(nr.platformID == 1 for nr in getattr(nameTable, "names", ()))
+
+ # we have all the best ways to express mac names
+ platforms = ((3, 1, 0x409),)
+ if macNames:
+ platforms = ((1, 0, 0),) + platforms
+
for axis_def in axes:
axis = Axis()
@@ -963,7 +980,7 @@ def addFvar(font, axes, instances):
if isinstance(name, str):
name = dict(en=name)
- axis.axisNameID = nameTable.addMultilingualName(name, ttFont=font)
+ axis.axisNameID = nameTable.addMultilingualName(name, ttFont=font, mac=macNames)
fvar.axes.append(axis)
for instance in instances:
@@ -980,9 +997,11 @@ def addFvar(font, axes, instances):
name = dict(en=name)
inst = NamedInstance()
- inst.subfamilyNameID = nameTable.addMultilingualName(name, ttFont=font)
+ inst.subfamilyNameID = nameTable.addMultilingualName(
+ name, ttFont=font, mac=macNames
+ )
if psname is not None:
- inst.postscriptNameID = nameTable.addName(psname)
+ inst.postscriptNameID = nameTable.addName(psname, platforms=platforms)
inst.coordinates = coordinates
fvar.instances.append(inst)
diff --git a/contrib/python/fonttools/fontTools/misc/testTools.py b/contrib/python/fonttools/fontTools/misc/testTools.py
index be6116132d..7d78721485 100644
--- a/contrib/python/fonttools/fontTools/misc/testTools.py
+++ b/contrib/python/fonttools/fontTools/misc/testTools.py
@@ -38,7 +38,7 @@ def parseXML(xmlSnippet):
% type(xmlSnippet).__name__
)
xml += b"</root>"
- reader.parser.Parse(xml, 0)
+ reader.parser.Parse(xml, 1)
return reader.root[2]
diff --git a/contrib/python/fonttools/fontTools/subset/__init__.py b/contrib/python/fonttools/fontTools/subset/__init__.py
index 99556d49e1..8458edc359 100644
--- a/contrib/python/fonttools/fontTools/subset/__init__.py
+++ b/contrib/python/fonttools/fontTools/subset/__init__.py
@@ -2873,7 +2873,9 @@ def closure_glyphs(self, s):
# Close glyphs
for table in tables:
if table.format == 14:
- for cmap in table.uvsDict.values():
+ for varSelector, cmap in table.uvsDict.items():
+ if varSelector not in s.unicodes_requested:
+ continue
glyphs = {g for u, g in cmap if u in s.unicodes_requested}
if None in glyphs:
glyphs.remove(None)
@@ -2928,6 +2930,7 @@ def subset_glyphs(self, s):
if g in s.glyphs_requested or u in s.unicodes_requested
]
for v, l in t.uvsDict.items()
+ if v in s.unicodes_requested
}
t.uvsDict = {v: l for v, l in t.uvsDict.items() if l}
elif t.isUnicode():
@@ -3797,6 +3800,8 @@ def main(args=None):
for t in font["cmap"].tables:
if t.isUnicode():
unicodes.extend(t.cmap.keys())
+ if t.format == 14:
+ unicodes.extend(t.uvsDict.keys())
assert "" not in glyphs
log.info("Text: '%s'" % text)
diff --git a/contrib/python/fonttools/fontTools/ttLib/removeOverlaps.py b/contrib/python/fonttools/fontTools/ttLib/removeOverlaps.py
index 312b56b294..6dadf4aa52 100644
--- a/contrib/python/fonttools/fontTools/ttLib/removeOverlaps.py
+++ b/contrib/python/fonttools/fontTools/ttLib/removeOverlaps.py
@@ -87,7 +87,11 @@ def ttfGlyphFromSkPath(path: pathops.Path) -> _g_l_y_f.Glyph:
def _charString_from_SkPath(
path: pathops.Path, charString: T2CharString
) -> T2CharString:
- t2Pen = T2CharStringPen(width=charString.width, glyphSet=None)
+ if charString.width == charString.private.defaultWidthX:
+ width = None
+ else:
+ width = charString.width - charString.private.nominalWidthX
+ t2Pen = T2CharStringPen(width=width, glyphSet=None)
path.draw(t2Pen)
return t2Pen.getCharString(charString.private, charString.globalSubrs)
diff --git a/contrib/python/fonttools/fontTools/ttLib/reorderGlyphs.py b/contrib/python/fonttools/fontTools/ttLib/reorderGlyphs.py
index 3221261f16..fd950ba0e3 100644
--- a/contrib/python/fonttools/fontTools/ttLib/reorderGlyphs.py
+++ b/contrib/python/fonttools/fontTools/ttLib/reorderGlyphs.py
@@ -19,9 +19,7 @@ from typing import (
Deque,
Iterable,
List,
- NamedTuple,
Tuple,
- Union,
)
@@ -276,3 +274,11 @@ def reorderGlyphs(font: ttLib.TTFont, new_glyph_order: List[str]):
reorder_key = (type(value), getattr(value, "Format", None))
for reorder in _REORDER_RULES.get(reorder_key, []):
reorder.apply(font, value)
+
+ if "CFF " in font:
+ cff_table = font["CFF "]
+ charstrings = cff_table.cff.topDictIndex[0].CharStrings.charStrings
+ cff_table.cff.topDictIndex[0].charset = new_glyph_order
+ cff_table.cff.topDictIndex[0].CharStrings.charStrings = {
+ k: charstrings.get(k) for k in new_glyph_order
+ }
diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/TupleVariation.py b/contrib/python/fonttools/fontTools/ttLib/tables/TupleVariation.py
index a98bca2e0e..bd6217e2ed 100644
--- a/contrib/python/fonttools/fontTools/ttLib/tables/TupleVariation.py
+++ b/contrib/python/fonttools/fontTools/ttLib/tables/TupleVariation.py
@@ -129,7 +129,9 @@ class TupleVariation(object):
else:
log.warning("bad delta format: %s" % ", ".join(sorted(attrs.keys())))
- def compile(self, axisTags, sharedCoordIndices={}, pointData=None):
+ def compile(
+ self, axisTags, sharedCoordIndices={}, pointData=None, *, optimizeSize=True
+ ):
assert set(self.axes.keys()) <= set(axisTags), (
"Unknown axis tag found.",
self.axes.keys(),
@@ -161,7 +163,7 @@ class TupleVariation(object):
flags |= PRIVATE_POINT_NUMBERS
auxData.append(pointData)
- auxData.append(self.compileDeltas())
+ auxData.append(self.compileDeltas(optimizeSize=optimizeSize))
auxData = b"".join(auxData)
tupleData.insert(0, struct.pack(">HH", len(auxData), flags))
@@ -322,7 +324,7 @@ class TupleVariation(object):
)
return (result, pos)
- def compileDeltas(self):
+ def compileDeltas(self, optimizeSize=True):
deltaX = []
deltaY = []
if self.getCoordWidth() == 2:
@@ -337,12 +339,12 @@ class TupleVariation(object):
continue
deltaX.append(c)
bytearr = bytearray()
- self.compileDeltaValues_(deltaX, bytearr)
- self.compileDeltaValues_(deltaY, bytearr)
+ self.compileDeltaValues_(deltaX, bytearr, optimizeSize=optimizeSize)
+ self.compileDeltaValues_(deltaY, bytearr, optimizeSize=optimizeSize)
return bytearr
@staticmethod
- def compileDeltaValues_(deltas, bytearr=None):
+ def compileDeltaValues_(deltas, bytearr=None, *, optimizeSize=True):
"""[value1, value2, value3, ...] --> bytearray
Emits a sequence of runs. Each run starts with a
@@ -360,18 +362,40 @@ class TupleVariation(object):
""" # Explaining the format because the 'gvar' spec is hard to understand.
if bytearr is None:
bytearr = bytearray()
+
pos = 0
numDeltas = len(deltas)
- while pos < numDeltas:
- value = deltas[pos]
- if value == 0:
+
+ if optimizeSize:
+ while pos < numDeltas:
+ value = deltas[pos]
+ if value == 0:
+ pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
+ elif -128 <= value <= 127:
+ pos = TupleVariation.encodeDeltaRunAsBytes_(deltas, pos, bytearr)
+ elif -32768 <= value <= 32767:
+ pos = TupleVariation.encodeDeltaRunAsWords_(deltas, pos, bytearr)
+ else:
+ pos = TupleVariation.encodeDeltaRunAsLongs_(deltas, pos, bytearr)
+ else:
+ minVal, maxVal = min(deltas), max(deltas)
+ if minVal == 0 == maxVal:
pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
- elif -128 <= value <= 127:
- pos = TupleVariation.encodeDeltaRunAsBytes_(deltas, pos, bytearr)
- elif -32768 <= value <= 32767:
- pos = TupleVariation.encodeDeltaRunAsWords_(deltas, pos, bytearr)
+ elif -128 <= minVal <= maxVal <= 127:
+ pos = TupleVariation.encodeDeltaRunAsBytes_(
+ deltas, pos, bytearr, optimizeSize=False
+ )
+ elif -32768 <= minVal <= maxVal <= 32767:
+ pos = TupleVariation.encodeDeltaRunAsWords_(
+ deltas, pos, bytearr, optimizeSize=False
+ )
else:
- pos = TupleVariation.encodeDeltaRunAsLongs_(deltas, pos, bytearr)
+ pos = TupleVariation.encodeDeltaRunAsLongs_(
+ deltas, pos, bytearr, optimizeSize=False
+ )
+
+ assert pos == numDeltas, (pos, numDeltas)
+
return bytearr
@staticmethod
@@ -389,7 +413,7 @@ class TupleVariation(object):
return pos
@staticmethod
- def encodeDeltaRunAsBytes_(deltas, offset, bytearr):
+ def encodeDeltaRunAsBytes_(deltas, offset, bytearr, optimizeSize=True):
pos = offset
numDeltas = len(deltas)
while pos < numDeltas:
@@ -404,7 +428,12 @@ class TupleVariation(object):
# (04 0F 0F 00 0F 0F) when storing the zero value
# literally, but 7 bytes (01 0F 0F 80 01 0F 0F)
# when starting a new run.
- if value == 0 and pos + 1 < numDeltas and deltas[pos + 1] == 0:
+ if (
+ optimizeSize
+ and value == 0
+ and pos + 1 < numDeltas
+ and deltas[pos + 1] == 0
+ ):
break
pos += 1
runLength = pos - offset
@@ -419,7 +448,7 @@ class TupleVariation(object):
return pos
@staticmethod
- def encodeDeltaRunAsWords_(deltas, offset, bytearr):
+ def encodeDeltaRunAsWords_(deltas, offset, bytearr, optimizeSize=True):
pos = offset
numDeltas = len(deltas)
while pos < numDeltas:
@@ -432,7 +461,7 @@ class TupleVariation(object):
# storing the zero literally (42 66 66 00 00 77 77),
# and equally 7 bytes when starting a new run
# (40 66 66 80 40 77 77).
- if value == 0:
+ if optimizeSize and value == 0:
break
# Within a word-encoded run of deltas, a single value
@@ -442,7 +471,8 @@ class TupleVariation(object):
# the value literally (42 66 66 00 02 77 77), but 8 bytes
# when starting a new run (40 66 66 00 02 40 77 77).
if (
- (-128 <= value <= 127)
+ optimizeSize
+ and (-128 <= value <= 127)
and pos + 1 < numDeltas
and (-128 <= deltas[pos + 1] <= 127)
):
@@ -470,12 +500,12 @@ class TupleVariation(object):
return pos
@staticmethod
- def encodeDeltaRunAsLongs_(deltas, offset, bytearr):
+ def encodeDeltaRunAsLongs_(deltas, offset, bytearr, optimizeSize=True):
pos = offset
numDeltas = len(deltas)
while pos < numDeltas:
value = deltas[pos]
- if -32768 <= value <= 32767:
+ if optimizeSize and -32768 <= value <= 32767:
break
pos += 1
runLength = pos - offset
@@ -677,7 +707,13 @@ def compileSharedTuples(
def compileTupleVariationStore(
- variations, pointCount, axisTags, sharedTupleIndices, useSharedPoints=True
+ variations,
+ pointCount,
+ axisTags,
+ sharedTupleIndices,
+ useSharedPoints=True,
+ *,
+ optimizeSize=True,
):
# pointCount is actually unused. Keeping for API compat.
del pointCount
@@ -733,7 +769,9 @@ def compileTupleVariationStore(
]
for v, p in zip(variations, pointDatas):
- thisTuple, thisData = v.compile(axisTags, sharedTupleIndices, pointData=p)
+ thisTuple, thisData = v.compile(
+ axisTags, sharedTupleIndices, pointData=p, optimizeSize=optimizeSize
+ )
tuples.append(thisTuple)
data.append(thisData)
diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py b/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py
index fa11cf8f47..bc7d4bf1e4 100644
--- a/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py
+++ b/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py
@@ -713,7 +713,9 @@ class Glyph(object):
else:
self.decompileCoordinates(data)
- def compile(self, glyfTable, recalcBBoxes=True, *, boundsDone=None):
+ def compile(
+ self, glyfTable, recalcBBoxes=True, *, boundsDone=None, optimizeSize=None
+ ):
if hasattr(self, "data"):
if recalcBBoxes:
# must unpack glyph in order to recalculate bounding box
@@ -730,7 +732,9 @@ class Glyph(object):
if self.isComposite():
data = data + self.compileComponents(glyfTable)
else:
- data = data + self.compileCoordinates()
+ if optimizeSize is None:
+ optimizeSize = getattr(glyfTable, "optimizeSize", True)
+ data = data + self.compileCoordinates(optimizeSize=optimizeSize)
return data
def toXML(self, writer, ttFont):
@@ -976,7 +980,7 @@ class Glyph(object):
data = data + struct.pack(">h", len(instructions)) + instructions
return data
- def compileCoordinates(self):
+ def compileCoordinates(self, *, optimizeSize=True):
assert len(self.coordinates) == len(self.flags)
data = []
endPtsOfContours = array.array("H", self.endPtsOfContours)
@@ -991,9 +995,12 @@ class Glyph(object):
deltas.toInt()
deltas.absoluteToRelative()
- # TODO(behdad): Add a configuration option for this?
- deltas = self.compileDeltasGreedy(self.flags, deltas)
- # deltas = self.compileDeltasOptimal(self.flags, deltas)
+ if optimizeSize:
+ # TODO(behdad): Add a configuration option for this?
+ deltas = self.compileDeltasGreedy(self.flags, deltas)
+ # deltas = self.compileDeltasOptimal(self.flags, deltas)
+ else:
+ deltas = self.compileDeltasForSpeed(self.flags, deltas)
data.extend(deltas)
return b"".join(data)
@@ -1110,6 +1117,63 @@ class Glyph(object):
return (compressedFlags, compressedXs, compressedYs)
+ def compileDeltasForSpeed(self, flags, deltas):
+ # uses widest representation needed, for all deltas.
+ compressedFlags = bytearray()
+ compressedXs = bytearray()
+ compressedYs = bytearray()
+
+ # Compute the necessary width for each axis
+ xs = [d[0] for d in deltas]
+ ys = [d[1] for d in deltas]
+ minX, minY, maxX, maxY = min(xs), min(ys), max(xs), max(ys)
+ xZero = minX == 0 and maxX == 0
+ yZero = minY == 0 and maxY == 0
+ xShort = -255 <= minX <= maxX <= 255
+ yShort = -255 <= minY <= maxY <= 255
+
+ lastflag = None
+ repeat = 0
+ for flag, (x, y) in zip(flags, deltas):
+ # Oh, the horrors of TrueType
+ # do x
+ if xZero:
+ flag = flag | flagXsame
+ elif xShort:
+ flag = flag | flagXShort
+ if x > 0:
+ flag = flag | flagXsame
+ else:
+ x = -x
+ compressedXs.append(x)
+ else:
+ compressedXs.extend(struct.pack(">h", x))
+ # do y
+ if yZero:
+ flag = flag | flagYsame
+ elif yShort:
+ flag = flag | flagYShort
+ if y > 0:
+ flag = flag | flagYsame
+ else:
+ y = -y
+ compressedYs.append(y)
+ else:
+ compressedYs.extend(struct.pack(">h", y))
+ # handle repeating flags
+ if flag == lastflag and repeat != 255:
+ repeat = repeat + 1
+ if repeat == 1:
+ compressedFlags.append(flag)
+ else:
+ compressedFlags[-2] = flag | flagRepeat
+ compressedFlags[-1] = repeat
+ else:
+ repeat = 0
+ compressedFlags.append(flag)
+ lastflag = flag
+ return (compressedFlags, compressedXs, compressedYs)
+
def recalcBounds(self, glyfTable, *, boundsDone=None):
"""Recalculates the bounds of the glyph.
@@ -1404,6 +1468,7 @@ class Glyph(object):
pen.addComponent(glyphName, transform)
return
+ self.expand(glyfTable)
coordinates, endPts, flags = self.getCoordinates(glyfTable)
if offset:
coordinates = coordinates.copy()
diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py b/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py
index 044f65f716..cdc9ef8e76 100644
--- a/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py
+++ b/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py
@@ -85,6 +85,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
def compileGlyphs_(self, ttFont, axisTags, sharedCoordIndices):
result = []
glyf = ttFont["glyf"]
+ optimizeSize = getattr(self, "optimizeSize", True)
for glyphName in ttFont.getGlyphOrder():
variations = self.variations.get(glyphName, [])
if not variations:
@@ -93,7 +94,11 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
pointCountUnused = 0 # pointCount is actually unused by compileGlyph
result.append(
compileGlyph_(
- variations, pointCountUnused, axisTags, sharedCoordIndices
+ variations,
+ pointCountUnused,
+ axisTags,
+ sharedCoordIndices,
+ optimizeSize=optimizeSize,
)
)
return result
@@ -248,9 +253,11 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
return len(getattr(glyph, "coordinates", [])) + NUM_PHANTOM_POINTS
-def compileGlyph_(variations, pointCount, axisTags, sharedCoordIndices):
+def compileGlyph_(
+ variations, pointCount, axisTags, sharedCoordIndices, *, optimizeSize=True
+):
tupleVariationCount, tuples, data = tv.compileTupleVariationStore(
- variations, pointCount, axisTags, sharedCoordIndices
+ variations, pointCount, axisTags, sharedCoordIndices, optimizeSize=optimizeSize
)
if tupleVariationCount == 0:
return b""
diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/otConverters.py b/contrib/python/fonttools/fontTools/ttLib/tables/otConverters.py
index 656836bd3c..8068540301 100644
--- a/contrib/python/fonttools/fontTools/ttLib/tables/otConverters.py
+++ b/contrib/python/fonttools/fontTools/ttLib/tables/otConverters.py
@@ -2016,6 +2016,7 @@ converterMapping = {
# type class
"int8": Int8,
"int16": Short,
+ "int32": Long,
"uint8": UInt8,
"uint16": UShort,
"uint24": UInt24,
diff --git a/contrib/python/fonttools/fontTools/ufoLib/__init__.py b/contrib/python/fonttools/fontTools/ufoLib/__init__.py
index aa57beede2..42c06734ea 100644
--- a/contrib/python/fonttools/fontTools/ufoLib/__init__.py
+++ b/contrib/python/fonttools/fontTools/ufoLib/__init__.py
@@ -1,35 +1,35 @@
"""
A library for importing .ufo files and their descendants.
-Refer to http://unifiedfontobject.com for the UFO specification.
+Refer to http://unifiedfontobject.org for the UFO specification.
-The UFOReader and UFOWriter classes support versions 1, 2 and 3
-of the specification.
+The main interfaces are the :class:`.UFOReader` and :class:`.UFOWriter`
+classes, which support versions 1, 2, and 3 of the UFO specification.
-Sets that list the font info attribute names for the fontinfo.plist
-formats are available for external use. These are:
+Set variables are available for external use that list the font
+info attribute names for the `fontinfo.plist` formats. These are:
-- fontInfoAttributesVersion1
-- fontInfoAttributesVersion2
-- fontInfoAttributesVersion3
+- :obj:`.fontInfoAttributesVersion1`
+- :obj:`.fontInfoAttributesVersion2`
+- :obj:`.fontInfoAttributesVersion3`
-A set listing the fontinfo.plist attributes that were deprecated
+A set listing the `fontinfo.plist` attributes that were deprecated
in version 2 is available for external use:
-- deprecatedFontInfoAttributesVersion2
+- :obj:`.deprecatedFontInfoAttributesVersion2`
-Functions that do basic validation on values for fontinfo.plist
+Functions that do basic validation on values for `fontinfo.plist`
are available for external use. These are
-- validateFontInfoVersion2ValueForAttribute
-- validateFontInfoVersion3ValueForAttribute
+- :func:`.validateFontInfoVersion2ValueForAttribute`
+- :func:`.validateFontInfoVersion3ValueForAttribute`
Value conversion functions are available for converting
-fontinfo.plist values between the possible format versions.
+`fontinfo.plist` values between the possible format versions.
-- convertFontInfoValueForAttributeFromVersion1ToVersion2
-- convertFontInfoValueForAttributeFromVersion2ToVersion1
-- convertFontInfoValueForAttributeFromVersion2ToVersion3
-- convertFontInfoValueForAttributeFromVersion3ToVersion2
+- :func:`.convertFontInfoValueForAttributeFromVersion1ToVersion2`
+- :func:`.convertFontInfoValueForAttributeFromVersion2ToVersion1`
+- :func:`.convertFontInfoValueForAttributeFromVersion2ToVersion3`
+- :func:`.convertFontInfoValueForAttributeFromVersion3ToVersion2`
"""
import os
@@ -201,8 +201,12 @@ class _UFOBaseIO:
class UFOReader(_UFOBaseIO):
- """
- Read the various components of the .ufo.
+ """Read the various components of a .ufo.
+
+ Attributes:
+ path: An `os.PathLike` object pointing to the .ufo.
+ validate: A boolean indicating if the data read should be
+ validated. Defaults to `True`.
By default read data is validated. Set ``validate`` to
``False`` to not validate the data.
@@ -884,20 +888,27 @@ class UFOReader(_UFOBaseIO):
class UFOWriter(UFOReader):
- """
- Write the various components of the .ufo.
+ """Write the various components of a .ufo.
+
+ Attributes:
+ path: An `os.PathLike` object pointing to the .ufo.
+ formatVersion: the UFO format version as a tuple of integers (major, minor),
+ or as a single integer for the major digit only (minor is implied to be 0).
+ By default, the latest formatVersion will be used; currently it is 3.0,
+ which is equivalent to formatVersion=(3, 0).
+ fileCreator: The creator of the .ufo file. Defaults to
+ `com.github.fonttools.ufoLib`.
+ structure: The internal structure of the .ufo file: either `ZIP` or `PACKAGE`.
+ validate: A boolean indicating if the data read should be validated. Defaults
+ to `True`.
By default, the written data will be validated before writing. Set ``validate`` to
``False`` if you do not want to validate the data. Validation can also be overriden
- on a per method level if desired.
-
- The ``formatVersion`` argument allows to specify the UFO format version as a tuple
- of integers (major, minor), or as a single integer for the major digit only (minor
- is implied as 0). By default the latest formatVersion will be used; currently it's
- 3.0, which is equivalent to formatVersion=(3, 0).
+ on a per-method level if desired.
- An UnsupportedUFOFormat exception is raised if the requested UFO formatVersion is
- not supported.
+ Raises:
+ UnsupportedUFOFormat: An exception indicating that the requested UFO
+ formatVersion is not supported.
"""
def __init__(
diff --git a/contrib/python/fonttools/fontTools/ufoLib/glifLib.py b/contrib/python/fonttools/fontTools/ufoLib/glifLib.py
index 62e87db0df..abbda49146 100644
--- a/contrib/python/fonttools/fontTools/ufoLib/glifLib.py
+++ b/contrib/python/fonttools/fontTools/ufoLib/glifLib.py
@@ -1191,8 +1191,12 @@ def _readGlyphFromTreeFormat1(
haveSeenAdvance = True
_readAdvance(glyphObject, element)
elif element.tag == "unicode":
+ v = element.get("hex")
+ if v is None:
+ raise GlifLibError(
+ "A unicode element is missing its required hex attribute."
+ )
try:
- v = element.get("hex")
v = int(v, 16)
if v not in unicodes:
unicodes.append(v)
@@ -1254,8 +1258,12 @@ def _readGlyphFromTreeFormat2(
haveSeenAdvance = True
_readAdvance(glyphObject, element)
elif element.tag == "unicode":
+ v = element.get("hex")
+ if v is None:
+ raise GlifLibError(
+ "A unicode element is missing its required hex attribute."
+ )
try:
- v = element.get("hex")
v = int(v, 16)
if v not in unicodes:
unicodes.append(v)
@@ -1757,7 +1765,7 @@ class _BaseParser:
parser = ParserCreate()
parser.StartElementHandler = self.startElementHandler
parser.EndElementHandler = self.endElementHandler
- parser.Parse(text)
+ parser.Parse(text, 1)
def startElementHandler(self, name, attrs):
self._elementStack.append(name)
diff --git a/contrib/python/fonttools/fontTools/ufoLib/plistlib.py b/contrib/python/fonttools/fontTools/ufoLib/plistlib.py
index 38bb266b21..0242e9d26f 100644
--- a/contrib/python/fonttools/fontTools/ufoLib/plistlib.py
+++ b/contrib/python/fonttools/fontTools/ufoLib/plistlib.py
@@ -1,5 +1,5 @@
"""DEPRECATED - This module is kept here only as a backward compatibility shim
-for the old ufoLib.plistlib module, which was moved to fontTools.misc.plistlib.
+for the old `ufoLib.plistlib` module, which was moved to :class:`fontTools.misc.plistlib`.
Please use the latter instead.
"""
diff --git a/contrib/python/fonttools/fontTools/ufoLib/pointPen.py b/contrib/python/fonttools/fontTools/ufoLib/pointPen.py
index baef9a583e..4a8126cd64 100644
--- a/contrib/python/fonttools/fontTools/ufoLib/pointPen.py
+++ b/contrib/python/fonttools/fontTools/ufoLib/pointPen.py
@@ -1,5 +1,5 @@
"""DEPRECATED - This module is kept here only as a backward compatibility shim
-for the old ufoLib.pointPen module, which was moved to fontTools.pens.pointPen.
+for the old `ufoLib.pointPen` module, which was moved to :class:`fontTools.pens.pointPen`.
Please use the latter instead.
"""
diff --git a/contrib/python/fonttools/fontTools/varLib/__init__.py b/contrib/python/fonttools/fontTools/varLib/__init__.py
index 36b1851cba..43a16da5a9 100644
--- a/contrib/python/fonttools/fontTools/varLib/__init__.py
+++ b/contrib/python/fonttools/fontTools/varLib/__init__.py
@@ -85,6 +85,15 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
fvar = newTable("fvar")
nameTable = font["name"]
+ # if there are not currently any mac names don't add them here, that's inconsistent
+ # https://github.com/fonttools/fonttools/issues/683
+ macNames = any(nr.platformID == 1 for nr in getattr(nameTable, "names", ()))
+
+ # we have all the best ways to express mac names
+ platforms = ((3, 1, 0x409),)
+ if macNames:
+ platforms = ((1, 0, 0),) + platforms
+
for a in axes.values():
axis = Axis()
axis.axisTag = Tag(a.tag)
@@ -95,7 +104,7 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
a.maximum,
)
axis.axisNameID = nameTable.addMultilingualName(
- a.labelNames, font, minNameID=256
+ a.labelNames, font, minNameID=256, mac=macNames
)
axis.flags = int(a.hidden)
fvar.axes.append(axis)
@@ -121,10 +130,12 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
psname = instance.postScriptFontName
inst = NamedInstance()
- inst.subfamilyNameID = nameTable.addMultilingualName(localisedStyleName)
+ inst.subfamilyNameID = nameTable.addMultilingualName(
+ localisedStyleName, mac=macNames
+ )
if psname is not None:
psname = tostr(psname)
- inst.postscriptNameID = nameTable.addName(psname)
+ inst.postscriptNameID = nameTable.addName(psname, platforms=platforms)
inst.coordinates = {
axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items()
}
diff --git a/contrib/python/fonttools/fontTools/varLib/interpolatable.py b/contrib/python/fonttools/fontTools/varLib/interpolatable.py
index 60dbfeb7fc..c5d7ecf525 100644
--- a/contrib/python/fonttools/fontTools/varLib/interpolatable.py
+++ b/contrib/python/fonttools/fontTools/varLib/interpolatable.py
@@ -693,7 +693,7 @@ def main(args=None):
from fontTools import configLogger
- configLogger(level=("INFO" if args.verbose else "ERROR"))
+ configLogger(level=("INFO" if args.verbose else "WARNING"))
if args.debug:
configLogger(level="DEBUG")
@@ -750,41 +750,43 @@ def main(args=None):
for k, vv in axis_triples.items()
}
- elif args.inputs[0].endswith(".ttf"):
+ elif args.inputs[0].endswith(".ttf") or args.inputs[0].endswith(".otf"):
from fontTools.ttLib import TTFont
+ # Is variable font?
+
font = TTFont(args.inputs[0])
upem = font["head"].unitsPerEm
- if "gvar" in font:
- # Is variable font
-
- fvar = font["fvar"]
- axisMapping = {}
- for axis in fvar.axes:
- axisMapping[axis.axisTag] = {
- -1: axis.minValue,
- 0: axis.defaultValue,
- 1: axis.maxValue,
- }
- normalized = False
- if "avar" in font:
- avar = font["avar"]
- if getattr(avar.table, "VarStore", None):
- axisMapping = {tag: {-1: -1, 0: 0, 1: 1} for tag in axisMapping}
- normalized = True
- else:
- for axisTag, segments in avar.segments.items():
- fvarMapping = axisMapping[axisTag].copy()
- for location, value in segments.items():
- axisMapping[axisTag][value] = piecewiseLinearMap(
- location, fvarMapping
- )
+ fvar = font["fvar"]
+ axisMapping = {}
+ for axis in fvar.axes:
+ axisMapping[axis.axisTag] = {
+ -1: axis.minValue,
+ 0: axis.defaultValue,
+ 1: axis.maxValue,
+ }
+ normalized = False
+ if "avar" in font:
+ avar = font["avar"]
+ if getattr(avar.table, "VarStore", None):
+ axisMapping = {tag: {-1: -1, 0: 0, 1: 1} for tag in axisMapping}
+ normalized = True
+ else:
+ for axisTag, segments in avar.segments.items():
+ fvarMapping = axisMapping[axisTag].copy()
+ for location, value in segments.items():
+ axisMapping[axisTag][value] = piecewiseLinearMap(
+ location, fvarMapping
+ )
+
+ # Gather all glyphs at their "master" locations
+ ttGlyphSets = {}
+ glyphsets = defaultdict(dict)
+
+ if "gvar" in font:
gvar = font["gvar"]
glyf = font["glyf"]
- # Gather all glyphs at their "master" locations
- ttGlyphSets = {}
- glyphsets = defaultdict(dict)
if glyphs is None:
glyphs = sorted(gvar.variations.keys())
@@ -806,32 +808,87 @@ def main(args=None):
glyphname, glyphsets[locTuple], ttGlyphSets[locTuple], glyf
)
- names = ["''"]
- fonts = [font.getGlyphSet()]
- locations = [{}]
- axis_triples = {a: (-1, 0, +1) for a in sorted(axisMapping.keys())}
- for locTuple in sorted(glyphsets.keys(), key=lambda v: (len(v), v)):
- name = (
- "'"
- + " ".join(
- "%s=%s"
- % (
- k,
- floatToFixedToStr(
- piecewiseLinearMap(v, axisMapping[k]), 14
- ),
- )
- for k, v in locTuple
+ elif "CFF2" in font:
+ fvarAxes = font["fvar"].axes
+ cff2 = font["CFF2"].cff.topDictIndex[0]
+ charstrings = cff2.CharStrings
+
+ if glyphs is None:
+ glyphs = sorted(charstrings.keys())
+ for glyphname in glyphs:
+ cs = charstrings[glyphname]
+ private = cs.private
+
+ # Extract vsindex for the glyph
+ vsindices = {getattr(private, "vsindex", 0)}
+ vsindex = getattr(private, "vsindex", 0)
+ last_op = 0
+ # The spec says vsindex can only appear once and must be the first
+ # operator in the charstring, but we support multiple.
+ # https://github.com/harfbuzz/boring-expansion-spec/issues/158
+ for op in enumerate(cs.program):
+ if op == "blend":
+ vsindices.add(vsindex)
+ elif op == "vsindex":
+ assert isinstance(last_op, int)
+ vsindex = last_op
+ last_op = op
+
+ if not hasattr(private, "vstore"):
+ continue
+
+ varStore = private.vstore.otVarStore
+ for vsindex in vsindices:
+ varData = varStore.VarData[vsindex]
+ for regionIndex in varData.VarRegionIndex:
+ region = varStore.VarRegionList.Region[regionIndex]
+
+ locDict = {}
+ loc = []
+ for axisIndex, axis in enumerate(region.VarRegionAxis):
+ tag = fvarAxes[axisIndex].axisTag
+ val = axis.PeakCoord
+ locDict[tag] = val
+ loc.append((tag, val))
+
+ locTuple = tuple(loc)
+ if locTuple not in ttGlyphSets:
+ ttGlyphSets[locTuple] = font.getGlyphSet(
+ location=locDict,
+ normalized=True,
+ recalcBounds=False,
+ )
+
+ glyphset = glyphsets[locTuple]
+ glyphset[glyphname] = ttGlyphSets[locTuple][glyphname]
+
+ names = ["''"]
+ fonts = [font.getGlyphSet()]
+ locations = [{}]
+ axis_triples = {a: (-1, 0, +1) for a in sorted(axisMapping.keys())}
+ for locTuple in sorted(glyphsets.keys(), key=lambda v: (len(v), v)):
+ name = (
+ "'"
+ + " ".join(
+ "%s=%s"
+ % (
+ k,
+ floatToFixedToStr(
+ piecewiseLinearMap(v, axisMapping[k]), 14
+ ),
)
- + "'"
+ for k, v in locTuple
)
- if normalized:
- name += " (normalized)"
- names.append(name)
- fonts.append(glyphsets[locTuple])
- locations.append(dict(locTuple))
- args.ignore_missing = True
- args.inputs = []
+ + "'"
+ )
+ if normalized:
+ name += " (normalized)"
+ names.append(name)
+ fonts.append(glyphsets[locTuple])
+ locations.append(dict(locTuple))
+
+ args.ignore_missing = True
+ args.inputs = []
if not locations:
locations = [{} for _ in fonts]
@@ -854,6 +911,10 @@ def main(args=None):
names.append(basename(filename).rsplit(".", 1)[0])
+ if len(fonts) < 2:
+ log.warning("Font file does not seem to be variable. Nothing to check.")
+ return
+
glyphsets = []
for font in fonts:
if hasattr(font, "getGlyphSet"):
diff --git a/contrib/python/fonttools/fontTools/varLib/interpolatableTestContourOrder.py b/contrib/python/fonttools/fontTools/varLib/interpolatableTestContourOrder.py
index 9edb1afcb5..3688529798 100644
--- a/contrib/python/fonttools/fontTools/varLib/interpolatableTestContourOrder.py
+++ b/contrib/python/fonttools/fontTools/varLib/interpolatableTestContourOrder.py
@@ -55,10 +55,10 @@ def test_contour_order(glyph0, glyph1):
m1GreenReversed = [(-m[0],) + m[1:] for m in m1Green]
(
matching_control_reversed,
- matching_cost_control_reversed,
- identity_cost_control_reversed,
- ) = matching_for_vectors(m0Control, m1ControlReversed)
- done = matching_cost_control_reversed == identity_cost_control_reversed
+ matching_cost_green_reversed,
+ identity_cost_green_reversed,
+ ) = matching_for_vectors(m0Green, m1GreenReversed)
+ done = matching_cost_green_reversed == identity_cost_green_reversed
if not done:
# Otherwise, use the worst of the two matchings.
diff --git a/contrib/python/fonttools/fontTools/varLib/multiVarStore.py b/contrib/python/fonttools/fontTools/varLib/multiVarStore.py
index f24a6e6f75..2d074a353d 100644
--- a/contrib/python/fonttools/fontTools/varLib/multiVarStore.py
+++ b/contrib/python/fonttools/fontTools/varLib/multiVarStore.py
@@ -50,7 +50,7 @@ class OnlineMultiVarStoreBuilder(object):
self._cache = None
self._data = None
- def finish(self, optimize=True):
+ def finish(self):
self._regionList.RegionCount = len(self._regionList.Region)
self._store.MultiVarDataCount = len(self._store.MultiVarData)
return self._store
diff --git a/contrib/python/fonttools/fontTools/varLib/mutator.py b/contrib/python/fonttools/fontTools/varLib/mutator.py
index 80e46bb244..f9f9379026 100644
--- a/contrib/python/fonttools/fontTools/varLib/mutator.py
+++ b/contrib/python/fonttools/fontTools/varLib/mutator.py
@@ -408,7 +408,9 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
if set(excludedUnicodeLangIDs) == set(range(len((varfont["ltag"].tags)))):
del varfont["ltag"]
varfont["name"].names[:] = [
- n for n in varfont["name"].names if n.nameID not in exclude
+ n
+ for n in varfont["name"].names
+ if n.nameID < 256 or n.nameID not in exclude
]
if "wght" in location and "OS/2" in varfont:
diff --git a/contrib/python/fonttools/fontTools/varLib/stat.py b/contrib/python/fonttools/fontTools/varLib/stat.py
index 46c9498dc7..eacbf580ae 100644
--- a/contrib/python/fonttools/fontTools/varLib/stat.py
+++ b/contrib/python/fonttools/fontTools/varLib/stat.py
@@ -39,11 +39,18 @@ def buildVFStatTable(ttFont: TTFont, doc: DesignSpaceDocument, vfName: str) -> N
region = getVFUserRegion(doc, vf)
+ # if there are not currently any mac names don't add them here, that's inconsistent
+ # https://github.com/fonttools/fonttools/issues/683
+ macNames = any(
+ nr.platformID == 1 for nr in getattr(ttFont.get("name"), "names", ())
+ )
+
return fontTools.otlLib.builder.buildStatTable(
ttFont,
getStatAxes(doc, region),
getStatLocations(doc, region),
doc.elidedFallbackName if doc.elidedFallbackName is not None else 2,
+ macNames=macNames,
)
diff --git a/contrib/python/fonttools/ya.make b/contrib/python/fonttools/ya.make
index 402baf8a09..874cdd1b3b 100644
--- a/contrib/python/fonttools/ya.make
+++ b/contrib/python/fonttools/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(4.54.1)
+VERSION(4.55.0)
LICENSE(MIT)