From 6a812ce925563f1dcebc13258dba9afbd706669c Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 24 May 2024 14:44:11 -0600 Subject: [PATCH 1/6] [CFFToCFF2] Fix for non-FDArray fonts and subroutines --- Lib/fontTools/cffLib/CFFToCFF2.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Lib/fontTools/cffLib/CFFToCFF2.py b/Lib/fontTools/cffLib/CFFToCFF2.py index 78347c666..37463a5b9 100644 --- a/Lib/fontTools/cffLib/CFFToCFF2.py +++ b/Lib/fontTools/cffLib/CFFToCFF2.py @@ -43,7 +43,15 @@ def _convertCFFToCFF2(cff, otFont): 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 [] + 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) @@ -70,13 +78,21 @@ def _convertCFFToCFF2(cff, otFont): for glyphName in charStrings.keys(): cs, fdIndex = charStrings.getItemAndSelector(glyphName) program = cs.program - if fdIndex == None: - fdIndex = 0 + + thisLocalSubrs = ( + localSubrs[fdIndex] + if fdIndex + 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( - localSubrs[fdIndex] if localSubrs else [], + thisLocalSubrs, globalSubrs, nominalWidthXError, 0, @@ -94,7 +110,7 @@ def _convertCFFToCFF2(cff, otFont): op = program.pop(0) bias = extractor.localBias if op == "callsubr" else extractor.globalBias subrNumber += bias - subrSet = localSubrs[fdIndex] if op == "callsubr" else globalSubrs + subrSet = thisLocalSubrs if op == "callsubr" else globalSubrs subrProgram = subrSet[subrNumber].program program[:0] = subrProgram # Now pop the actual width From 1536efc22ecd174fb0074ab5a4cbc9e90fb55116 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 24 May 2024 15:07:05 -0600 Subject: [PATCH 2/6] [CFFToCFF2] Add a test Exercises: https://github.com/fonttools/fonttools/pull/3518#discussion_r1611033728 --- Tests/cffLib/cffLib_test.py | 13 +++++++++++++ Tests/cffLib/data/CFFToCFF2-1.otf | Bin 0 -> 1788 bytes 2 files changed, 13 insertions(+) create mode 100644 Tests/cffLib/data/CFFToCFF2-1.otf diff --git a/Tests/cffLib/cffLib_test.py b/Tests/cffLib/cffLib_test.py index 2d4d3023e..7146e5d66 100644 --- a/Tests/cffLib/cffLib_test.py +++ b/Tests/cffLib/cffLib_test.py @@ -5,6 +5,7 @@ import copy import os import sys import unittest +from io import BytesIO class CffLibTest(DataFilesHandler): @@ -119,5 +120,17 @@ class CffLibTest(DataFilesHandler): self.assertEqual(len(glyphOrder), len(set(glyphOrder))) +class CFFToCFF2Test(DataFilesHandler): + + def test_conversion(self): + font_path = self.getpath("CFFToCFF2-1.otf") + font = TTFont(font_path) + from fontTools.cffLib.CFFToCFF2 import convertCFFToCFF2 + + convertCFFToCFF2(font) + f = BytesIO() + font.save(f) + + if __name__ == "__main__": sys.exit(unittest.main()) diff --git a/Tests/cffLib/data/CFFToCFF2-1.otf b/Tests/cffLib/data/CFFToCFF2-1.otf new file mode 100644 index 0000000000000000000000000000000000000000..2536fdef4151aa3347d1e4532ccbf65d89e606f8 GIT binary patch literal 1788 zcmb7FZD?Cn7=G@($z7VnmT4>1HQ{XQK55e~>x!#$YrC#>IG56_;zqIFC3j6|b8oo0 z{Srh)e>p#gto~bZASmiDVfafC6%|(zH#!iRNEzY}2Z}Tvi3<+q-RU6StyCaR>pRU zwqv+vR^%<==1ehc<)xN(X=J*UZL=L=)8mMYJ!!d4-Yg{!+QnHXKbCVvvVB*3i`x91i^FYEIM<`16leS2GD<+xsXpmMI^2mQK*P`WKav z;F7*M7STjF(j40oi&UIHnuD!+ z?!&oPx$Bj}+`8LrA=+x-PN)O_M!=#ZOPO-~97t*Uj%jwkCT| zlYJtG`XY|2f;#iHD>tvR?OtbGSIG7d{UCo=p{Zfz6$F{}y!t4O5BaC^7?R3nS|dSC z3uvkupilBy9X)-$DRiR;J?bx2T~<=o)9J-1*HnHXkQ{(@I3c+NYAAok<;x1<)O=Y* zEtXbf4M*_niX2!I6GT?ozhc+1Q+We#5kHA`?BWt=UDY}$^&kryCt*@fV-_X2lnXos z4#Pr*UI#^QH#}dKp6$p{c4_ZI8|Sn)6EIfM5?*G3z8z>`#x(Mb&rup8eu>M$6nBZ% zPjri3fulqy&?-bD+1Qw5R0gKF2Z^;hBh!quJeGruu|3o=#!t}h@U{uwRo?l2-mnk7 zNKsmYxSH>0x89cTXQh~T2#CK!8)~J=LneS3mN*jB(zmF0l5$ z4Ve{PaVX63C)jP<5vFz@0)72Qj$vPB)|o)}m}8EkZ^CdbJcz)!=~zhqbN6`4?%he` z1edJc=~a>SREf&71N&KlUu5+LSCuk{cT)0B>63QHk7DpLhOO`%=pXa?E&$!1k P?(vL6{>ylSSR8)=m1KIM literal 0 HcmV?d00001 From 1076f2c558780ad0e5109e9f0b3d7bedb3045f29 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 24 May 2024 16:24:28 -0600 Subject: [PATCH 3/6] [CFF2ToCFF] Remove unused code --- Lib/fontTools/cffLib/CFF2ToCFF.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/fontTools/cffLib/CFF2ToCFF.py b/Lib/fontTools/cffLib/CFF2ToCFF.py index 5dc48a7fc..247f513ad 100644 --- a/Lib/fontTools/cffLib/CFF2ToCFF.py +++ b/Lib/fontTools/cffLib/CFF2ToCFF.py @@ -36,10 +36,6 @@ def _convertCFF2ToCFF(cff, otFont): if hasattr(topDict, "VarStore"): raise ValueError("Variable CFF2 font cannot be converted to CFF format.") - if hasattr(topDict, "Private"): - privateDict = topDict.Private - else: - privateDict = None opOrder = buildOrder(topDictOperators) topDict.order = opOrder From 9943a109e8021a80c20765d9c43467becd1b8a91 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 24 May 2024 16:53:51 -0600 Subject: [PATCH 4/6] [CFF2ToCFF] Clean up dicts and privates --- Lib/fontTools/cffLib/CFF2ToCFF.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Lib/fontTools/cffLib/CFF2ToCFF.py b/Lib/fontTools/cffLib/CFF2ToCFF.py index 247f513ad..689412ce2 100644 --- a/Lib/fontTools/cffLib/CFF2ToCFF.py +++ b/Lib/fontTools/cffLib/CFF2ToCFF.py @@ -2,7 +2,13 @@ from fontTools.ttLib import TTFont, newTable from fontTools.misc.cliTools import makeOutputFileName -from fontTools.cffLib import TopDictIndex, buildOrder, topDictOperators +from fontTools.cffLib import ( + TopDictIndex, + buildOrder, + buildDefaults, + topDictOperators, + privateDictOperators, +) from .width import optimizeWidths from collections import defaultdict import logging @@ -38,10 +44,30 @@ def _convertCFF2ToCFF(cff, otFont): opOrder = buildOrder(topDictOperators) topDict.order = opOrder + for key in topDict.rawDict.keys(): + if key not in opOrder: + del topDict.rawDict[key] + if hasattr(topDict, key): + delattr(topDict, key) fdArray = topDict.FDArray charStrings = topDict.CharStrings + defaults = buildDefaults(privateDictOperators) + order = buildOrder(privateDictOperators) + for fd in fdArray: + fd.setCFF2(False) + privateDict = fd.Private + privateDict.order = order + for key in order: + if key not in privateDict.rawDict and key in defaults: + privateDict.rawDict[key] = defaults[key] + for key in privateDict.rawDict.keys(): + if key not in order: + del privateDict.rawDict[key] + if hasattr(privateDict, key): + delattr(privateDict, key) + for cs in charStrings.values(): cs.decompile() cs.program.append("endchar") From 129e8e34a57425e30d899403a6fe79c46e8f27e6 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Fri, 24 May 2024 18:04:03 -0600 Subject: [PATCH 5/6] [cffLib] Typo --- Lib/fontTools/cffLib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py index 9cfdebaa1..c192ec77a 100644 --- a/Lib/fontTools/cffLib/__init__.py +++ b/Lib/fontTools/cffLib/__init__.py @@ -1470,7 +1470,7 @@ class CharsetConverter(SimpleConverter): else: # offset == 0 -> no charset data. if isCID or "CharStrings" not in parent.rawDict: # We get here only when processing fontDicts from the FDArray of - # CFF-CID fonts. Only the real topDict references the chrset. + # CFF-CID fonts. Only the real topDict references the charset. assert value == 0 charset = None elif value == 0: From 39093b9b7ee5b837cd192015725ae79e563272c2 Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Sat, 25 May 2024 09:40:19 -0600 Subject: [PATCH 6/6] [cffLib] Add optional removeUnusedSubrs arg to remove_hints() --- Lib/fontTools/cffLib/transforms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/cffLib/transforms.py b/Lib/fontTools/cffLib/transforms.py index 0772d85e2..91f6999fe 100644 --- a/Lib/fontTools/cffLib/transforms.py +++ b/Lib/fontTools/cffLib/transforms.py @@ -342,7 +342,7 @@ def _cs_drop_hints(charstring): del charstring._hints -def remove_hints(cff): +def remove_hints(cff, *, removeUnusedSubrs: bool = True): for fontname in cff.keys(): font = cff[fontname] cs = font.CharStrings @@ -404,7 +404,8 @@ def remove_hints(cff): ]: if hasattr(priv, k): setattr(priv, k, None) - remove_unused_subroutines(cff) + if removeUnusedSubrs: + remove_unused_subroutines(cff) def _pd_delete_empty_subrs(private_dict):