From 60e30fe008857f9fca6fe79d0e96bd20e7119072 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Wed, 15 May 2024 17:22:24 -0700 Subject: [PATCH] [cffLib] Add a simple (and quite possibly incomplete) convertCFF2ToCFF() --- Lib/fontTools/cffLib/__init__.py | 41 +++++++++++++++++++ Lib/fontTools/varLib/instancer/__init__.py | 25 ++++++++--- .../CFF2Instancer-VF-1-instance-400.ttx | 21 ---------- .../CFF2Instancer-VF-3-instance-400.ttx | 21 ---------- 4 files changed, 61 insertions(+), 47 deletions(-) diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py index 06b4e8d19..2750c16d9 100644 --- a/Lib/fontTools/cffLib/__init__.py +++ b/Lib/fontTools/cffLib/__init__.py @@ -458,6 +458,11 @@ class CFFFontSet(object): if hasattr(privateDict, key): delattr(privateDict, key) # print "Removing privateDict attr", key + + # TODO(behdad): What does the following comment even mean? Both CFF and CFF2 + # use the same T2Charstring class. + # What I see missing is dropping the endchar and return operators... + # At this point, the Subrs and Charstrings are all still T2Charstring class # easiest to fix this by compiling, then decompiling again file = BytesIO() @@ -465,6 +470,42 @@ class CFFFontSet(object): file.seek(0) self.decompile(file, otFont, isCFF2=True) + def convertCFF2ToCFF(self, otFont): + """Converts this object from CFF2 format to CFF format. This conversion + is done 'in-place'. The conversion cannot be reversed. + + The CFF2 font cannot be variable. This method will remove the VarStore, + but will not process the CharStrings in any way (TODO instantiate to default). + + This assumes a decompiled CFF table. (i.e. that the object has been + filled via :meth:`decompile`.)""" + self.major = 1 + topDictData = TopDictIndex(None) + topDictData.items = self.topDictIndex.items + self.topDictIndex = topDictData + topDict = topDictData[0] + if hasattr(topDict, "Private"): + privateDict = topDict.Private + else: + privateDict = None + opOrder = buildOrder(topDictOperators) + topDict.order = opOrder + + fdArray = topDict.FDArray + charStrings = topDict.CharStrings + + for cs in charStrings.values(): + cs.program.append("endchar") + + # TODO Add "return" to subrs that don't end in endchar? + + # At this point, the Subrs and Charstrings are all still T2Charstring class + # easiest to fix this by compiling, then decompiling again + # file = BytesIO() + # self.compile(file, otFont, isCFF2=False) + # file.seek(0) + # self.decompile(file, otFont, isCFF2=False) + def desubroutinize(self): for fontName in self.fontNames: font = self[fontName] diff --git a/Lib/fontTools/varLib/instancer/__init__.py b/Lib/fontTools/varLib/instancer/__init__.py index 0952121c1..8d4d001dc 100644 --- a/Lib/fontTools/varLib/instancer/__init__.py +++ b/Lib/fontTools/varLib/instancer/__init__.py @@ -89,7 +89,7 @@ from fontTools.misc.fixedTools import ( otRound, ) from fontTools.varLib.models import normalizeValue, piecewiseLinearMap -from fontTools.ttLib import TTFont +from fontTools.ttLib import TTFont, getTableClass from fontTools.ttLib.tables.TupleVariation import TupleVariation from fontTools.ttLib.tables import _g_l_y_f from fontTools import varLib @@ -574,7 +574,13 @@ def changeTupleVariationAxisLimit(var, axisTag, axisLimit): def instantiateCFF2( - varfont, axisLimits, *, round=round, specialize=True, generalize=False + varfont, + axisLimits, + *, + round=round, + specialize=True, + generalize=False, + downgrade=False, ): # The algorithm here is rather simple: # @@ -594,7 +600,11 @@ def instantiateCFF2( topDict = cff.topDictIndex[0] varStore = topDict.VarStore.otVarStore if not varStore: - # TODO Downgrade to CFF if requested. + if downgrade: + table = varfont["CFF "] = getTableClass("CFF ")() + table.cff = cff + cff.convertCFF2ToCFF(varfont) + del varfont["CFF2"] return cff.desubroutinize() @@ -653,7 +663,6 @@ def instantiateCFF2( newDefaults = [] newDeltas = [] for i in range(count): - defaultValue = arg[i] major = vsindex @@ -804,12 +813,18 @@ def instantiateCFF2( # Remove empty VarStore if not varStore.VarData: + if "VarStore" in topDict.rawDict: + del topDict.rawDict["VarStore"] del topDict.VarStore del topDict.CharStrings.varStore for private in privateDicts: del private.vstore - # TODO Downgrade to CFF if requested. + if downgrade: + table = varfont["CFF "] = getTableClass("CFF ")() + table.cff = cff + cff.convertCFF2ToCFF(varfont) + del varfont["CFF2"] def _instantiateGvarGlyph( diff --git a/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-1-instance-400.ttx b/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-1-instance-400.ttx index fde30c8d5..44237b80c 100644 --- a/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-1-instance-400.ttx +++ b/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-1-instance-400.ttx @@ -208,27 +208,6 @@ 256 70 -256 hlineto - - - - - - - - - - - - - - - - - - - - - diff --git a/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-3-instance-400.ttx b/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-3-instance-400.ttx index bcc83baee..138cb1cce 100644 --- a/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-3-instance-400.ttx +++ b/Tests/varLib/instancer/data/test_results/CFF2Instancer-VF-3-instance-400.ttx @@ -234,27 +234,6 @@ -45 25 -14 -3 rlineto - - - - - - - - - - - - - - - - - - - - -