19682b4198b8449ba02ae2aaaa5fc76307167845dJust"""fontTools.ttLib -- a package for dealing with TrueType fonts. 27842e56b97ce677b83bdab09cda48bc2d89ac75aJust 37842e56b97ce677b83bdab09cda48bc2d89ac75aJustThis package offers translators to convert TrueType fonts to Python 49682b4198b8449ba02ae2aaaa5fc76307167845dJustobjects and vice versa, and additionally from Python to TTX (an XML-based 59682b4198b8449ba02ae2aaaa5fc76307167845dJusttext format) and vice versa. 67842e56b97ce677b83bdab09cda48bc2d89ac75aJust 77842e56b97ce677b83bdab09cda48bc2d89ac75aJustExample interactive session: 87842e56b97ce677b83bdab09cda48bc2d89ac75aJust 97842e56b97ce677b83bdab09cda48bc2d89ac75aJustPython 1.5.2c1 (#43, Mar 9 1999, 13:06:43) [CW PPC w/GUSI w/MSL] 107842e56b97ce677b83bdab09cda48bc2d89ac75aJustCopyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam 117842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> from fontTools import ttLib 127842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> tt = ttLib.TTFont("afont.ttf") 137842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> tt['maxp'].numGlyphs 147842e56b97ce677b83bdab09cda48bc2d89ac75aJust242 157842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> tt['OS/2'].achVendID 167842e56b97ce677b83bdab09cda48bc2d89ac75aJust'B&H\000' 177842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> tt['head'].unitsPerEm 187842e56b97ce677b83bdab09cda48bc2d89ac75aJust2048 199682b4198b8449ba02ae2aaaa5fc76307167845dJust>>> tt.saveXML("afont.ttx") 207842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'LTSH' table... 217842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'OS/2' table... 227842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'VDMX' table... 237842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'cmap' table... 247842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'cvt ' table... 257842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'fpgm' table... 267842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'glyf' table... 277842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'hdmx' table... 287842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'head' table... 297842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'hhea' table... 307842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'hmtx' table... 317842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'loca' table... 327842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'maxp' table... 337842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'name' table... 347842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'post' table... 357842e56b97ce677b83bdab09cda48bc2d89ac75aJustDumping 'prep' table... 367842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> tt2 = ttLib.TTFont() 379682b4198b8449ba02ae2aaaa5fc76307167845dJust>>> tt2.importXML("afont.ttx") 387842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> tt2['maxp'].numGlyphs 397842e56b97ce677b83bdab09cda48bc2d89ac75aJust242 407842e56b97ce677b83bdab09cda48bc2d89ac75aJust>>> 417842e56b97ce677b83bdab09cda48bc2d89ac75aJust 427842e56b97ce677b83bdab09cda48bc2d89ac75aJust""" 437842e56b97ce677b83bdab09cda48bc2d89ac75aJust 441ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import 45bb0beb7385d00a0f5c99895e7299a5a1307ec193Behdad Esfahbodfrom fontTools.misc.py23 import * 4630e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport os 4730e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport sys 481cff4cb190c49020bd34cfcc7a67fcb143055ae4jvr 491cff4cb190c49020bd34cfcc7a67fcb143055ae4jvrhaveMacSupport = 0 501cff4cb190c49020bd34cfcc7a67fcb143055ae4jvrif sys.platform == "mac": 511cff4cb190c49020bd34cfcc7a67fcb143055ae4jvr haveMacSupport = 1 521cff4cb190c49020bd34cfcc7a67fcb143055ae4jvrelif sys.platform == "darwin" and sys.version_info[:3] != (2, 2, 0): 531cff4cb190c49020bd34cfcc7a67fcb143055ae4jvr # Python 2.2's Mac support is broken, so don't enable it there. 541cff4cb190c49020bd34cfcc7a67fcb143055ae4jvr haveMacSupport = 1 557842e56b97ce677b83bdab09cda48bc2d89ac75aJust 56d04a3bb4f932dff5dfbd45ed33fa4c357b471527jvr 577842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass TTLibError(Exception): pass 587842e56b97ce677b83bdab09cda48bc2d89ac75aJust 597842e56b97ce677b83bdab09cda48bc2d89ac75aJust 60e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass TTFont(object): 617842e56b97ce677b83bdab09cda48bc2d89ac75aJust 627842e56b97ce677b83bdab09cda48bc2d89ac75aJust """The main font object. It manages file input and output, and offers 637842e56b97ce677b83bdab09cda48bc2d89ac75aJust a convenient way of accessing tables. 641344bc9b6208d9f779ac67bd8121c82de0eb085fpabs Tables will be only decompiled when necessary, ie. when they're actually 657842e56b97ce677b83bdab09cda48bc2d89ac75aJust accessed. This means that simple operations can be extremely fast. 667842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 677842e56b97ce677b83bdab09cda48bc2d89ac75aJust 680011bb691018c7feed1f8c3554d88b9ae096e127jvr def __init__(self, file=None, res_name_or_index=None, 69dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod sfntVersion="\000\001\000\000", flavor=None, checkChecksums=False, 70dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod verbose=False, recalcBBoxes=True, allowVID=False, ignoreDecompileErrors=False, 71497863a190b70a51e9f6c8013e19e2424a69acfbBehdad Esfahbod recalcTimestamp=True, fontNumber=-1, lazy=False, quiet=False): 727842e56b97ce677b83bdab09cda48bc2d89ac75aJust 737842e56b97ce677b83bdab09cda48bc2d89ac75aJust """The constructor can be called with a few different arguments. 747842e56b97ce677b83bdab09cda48bc2d89ac75aJust When reading a font from disk, 'file' should be either a pathname 757842e56b97ce677b83bdab09cda48bc2d89ac75aJust pointing to a file, or a readable file object. 767842e56b97ce677b83bdab09cda48bc2d89ac75aJust 777842e56b97ce677b83bdab09cda48bc2d89ac75aJust It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt 787842e56b97ce677b83bdab09cda48bc2d89ac75aJust resource name or an sfnt resource index number or zero. The latter 797842e56b97ce677b83bdab09cda48bc2d89ac75aJust case will cause TTLib to autodetect whether the file is a flat file 807842e56b97ce677b83bdab09cda48bc2d89ac75aJust or a suitcase. (If it's a suitcase, only the first 'sfnt' resource 817842e56b97ce677b83bdab09cda48bc2d89ac75aJust will be read!) 827842e56b97ce677b83bdab09cda48bc2d89ac75aJust 83ea9dfa9fb28966175bf2275d20aeb62c3040c86djvr The 'checkChecksums' argument is used to specify how sfnt 847842e56b97ce677b83bdab09cda48bc2d89ac75aJust checksums are treated upon reading a file from disk: 857842e56b97ce677b83bdab09cda48bc2d89ac75aJust 0: don't check (default) 860011bb691018c7feed1f8c3554d88b9ae096e127jvr 1: check, print warnings if a wrong checksum is found 877842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2: check, raise an exception if a wrong checksum is found. 887842e56b97ce677b83bdab09cda48bc2d89ac75aJust 897842e56b97ce677b83bdab09cda48bc2d89ac75aJust The TTFont constructor can also be called without a 'file' 907842e56b97ce677b83bdab09cda48bc2d89ac75aJust argument: this is the way to create a new empty font. 9158d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod In this case you can optionally supply the 'sfntVersion' argument, 9258d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod and a 'flavor' which can be None, or 'woff'. 9388cb4f33c63dcad4f3ecebf03fe381cacb24799eJust 9498d780d766a62fb5077102a9785e77c30dbffefaJust If the recalcBBoxes argument is false, a number of things will *not* 953e097c609540944dd9290ad58df346ca86492031Just be recalculated upon save/compile: 9698d780d766a62fb5077102a9785e77c30dbffefaJust 1) glyph bounding boxes 9798d780d766a62fb5077102a9785e77c30dbffefaJust 2) maxp font bounding box 9898d780d766a62fb5077102a9785e77c30dbffefaJust 3) hhea min/max values 9998d780d766a62fb5077102a9785e77c30dbffefaJust (1) is needed for certain kinds of CJK fonts (ask Werner Lemberg ;-). 1009682b4198b8449ba02ae2aaaa5fc76307167845dJust Additionally, upon importing an TTX file, this option cause glyphs 10198d780d766a62fb5077102a9785e77c30dbffefaJust to be compiled right away. This should reduce memory consumption 10298d780d766a62fb5077102a9785e77c30dbffefaJust greatly, and therefore should have some impact on the time needed 10398d780d766a62fb5077102a9785e77c30dbffefaJust to parse/compile large fonts. 104823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 105497863a190b70a51e9f6c8013e19e2424a69acfbBehdad Esfahbod If the recalcTimestamp argument is false, the modified timestamp in the 106497863a190b70a51e9f6c8013e19e2424a69acfbBehdad Esfahbod 'head' table will *not* be recalculated upon save/compile. 107497863a190b70a51e9f6c8013e19e2424a69acfbBehdad Esfahbod 108823f8cd15f16bb9dc3991c2672f16dd90579711bjvr If the allowVID argument is set to true, then virtual GID's are 109823f8cd15f16bb9dc3991c2672f16dd90579711bjvr supported. Asking for a glyph ID with a glyph name or GID that is not in 110823f8cd15f16bb9dc3991c2672f16dd90579711bjvr the font will return a virtual GID. This is valid for GSUB and cmap 111823f8cd15f16bb9dc3991c2672f16dd90579711bjvr tables. For SING glyphlets, the cmap table is used to specify Unicode 112ebefbbaa58f9e821dd535b354fcccecc2d24d914Behdad Esfahbod values for virtual GI's used in GSUB/GPOS rules. If the gid N is requested 113823f8cd15f16bb9dc3991c2672f16dd90579711bjvr and does not exist in the font, or the glyphname has the form glyphN 114823f8cd15f16bb9dc3991c2672f16dd90579711bjvr and does not exist in the font, then N is used as the virtual GID. 115823f8cd15f16bb9dc3991c2672f16dd90579711bjvr Else, the first virtual GID is assigned as 0x1000 -1; for subsequent new 116823f8cd15f16bb9dc3991c2672f16dd90579711bjvr virtual GIDs, the next is one less than the previous. 1172545f16b54798fe809238c76e2240f901fce8f8djvr 1182545f16b54798fe809238c76e2240f901fce8f8djvr If ignoreDecompileErrors is set to True, exceptions raised in 1192545f16b54798fe809238c76e2240f901fce8f8djvr individual tables during decompilation will be ignored, falling 1202545f16b54798fe809238c76e2240f901fce8f8djvr back to the DefaultTable implementation, which simply keeps the 1212545f16b54798fe809238c76e2240f901fce8f8djvr binary data. 1227ef23a85ae49039ae491e337be173caba7bdd534Behdad Esfahbod 1237ef23a85ae49039ae491e337be173caba7bdd534Behdad Esfahbod If lazy is set to True, many data structures are loaded lazily, upon 1247ef23a85ae49039ae491e337be173caba7bdd534Behdad Esfahbod access only. 1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 1267842e56b97ce677b83bdab09cda48bc2d89ac75aJust 127d8b32bf6e208a18421cfd4705801d6237e281f93Behdad Esfahbod from fontTools.ttLib import sfnt 1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.verbose = verbose 129d7efd5692cac74702d487e641ca7ddf9c6cfececDave Crossland self.quiet = quiet 1307ef23a85ae49039ae491e337be173caba7bdd534Behdad Esfahbod self.lazy = lazy 13188cb4f33c63dcad4f3ecebf03fe381cacb24799eJust self.recalcBBoxes = recalcBBoxes 132497863a190b70a51e9f6c8013e19e2424a69acfbBehdad Esfahbod self.recalcTimestamp = recalcTimestamp 1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables = {} 1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.reader = None 135823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 136823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # Permit the user to reference glyphs that are not int the font. 137823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.last_vid = 0xFFFE # Can't make it be 0xFFFF, as the world is full unsigned short integer counters that get incremented after the last seen GID value. 138823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.reverseVIDDict = {} 139823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.VIDDict = {} 140823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.allowVID = allowVID 1412545f16b54798fe809238c76e2240f901fce8f8djvr self.ignoreDecompileErrors = ignoreDecompileErrors 142823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not file: 1447842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.sfntVersion = sfntVersion 14558d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod self.flavor = flavor 14658d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod self.flavorData = None 1477842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 1480bb3ba522291076df18825a63c2f367d8ce3e48ajvr if not hasattr(file, "read"): 1490bb3ba522291076df18825a63c2f367d8ce3e48ajvr # assume file is a string 1500bb3ba522291076df18825a63c2f367d8ce3e48ajvr if haveMacSupport and res_name_or_index is not None: 1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust # on the mac, we deal with sfnt resources as well as flat files 1522b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod from . import macUtils 1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust if res_name_or_index == 0: 1547842e56b97ce677b83bdab09cda48bc2d89ac75aJust if macUtils.getSFNTResIndices(file): 1557842e56b97ce677b83bdab09cda48bc2d89ac75aJust # get the first available sfnt font. 1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust file = macUtils.SFNTResourceReader(file, 1) 1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1587842e56b97ce677b83bdab09cda48bc2d89ac75aJust file = open(file, "rb") 1597842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1607842e56b97ce677b83bdab09cda48bc2d89ac75aJust file = macUtils.SFNTResourceReader(file, res_name_or_index) 1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust file = open(file, "rb") 1637842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1647842e56b97ce677b83bdab09cda48bc2d89ac75aJust pass # assume "file" is a readable file object 1657e91e776c9d10d3b295de06ee7f665d8106306d8pabs self.reader = sfnt.SFNTReader(file, checkChecksums, fontNumber=fontNumber) 1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.sfntVersion = self.reader.sfntVersion 16758d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod self.flavor = self.reader.flavor 16858d7416124dc0ebaa3faccb1b77dd5f7926a628aBehdad Esfahbod self.flavorData = self.reader.flavorData 1697842e56b97ce677b83bdab09cda48bc2d89ac75aJust 1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust def close(self): 1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust """If we still have a reader object, close it.""" 1727842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.reader is not None: 1737842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.reader.close() 1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust 175dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod def save(self, file, makeSuitcase=False, reorderTables=True): 1767842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Save the font to disk. Similarly to the constructor, 1777842e56b97ce677b83bdab09cda48bc2d89ac75aJust the 'file' argument can be either a pathname or a writable 1787842e56b97ce677b83bdab09cda48bc2d89ac75aJust file object. 1797842e56b97ce677b83bdab09cda48bc2d89ac75aJust 18088cb4f33c63dcad4f3ecebf03fe381cacb24799eJust On the Mac, if makeSuitcase is true, a suitcase (resource fork) 18188cb4f33c63dcad4f3ecebf03fe381cacb24799eJust file will we made instead of a flat .ttf file. 1827842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 1830f675860ce081e9aeaf880ccc4a8e5d49e83f553Just from fontTools.ttLib import sfnt 1840bb3ba522291076df18825a63c2f367d8ce3e48ajvr if not hasattr(file, "write"): 1850f675860ce081e9aeaf880ccc4a8e5d49e83f553Just closeStream = 1 18688cb4f33c63dcad4f3ecebf03fe381cacb24799eJust if os.name == "mac" and makeSuitcase: 1872b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod from . import macUtils 1887842e56b97ce677b83bdab09cda48bc2d89ac75aJust file = macUtils.SFNTResourceWriter(file, self) 1897842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1907842e56b97ce677b83bdab09cda48bc2d89ac75aJust file = open(file, "wb") 1917842e56b97ce677b83bdab09cda48bc2d89ac75aJust if os.name == "mac": 19291bca4244286fb519c93fe92329da96b0e6f32eejvr from fontTools.misc.macCreator import setMacCreatorAndType 19391bca4244286fb519c93fe92329da96b0e6f32eejvr setMacCreatorAndType(file.name, 'mdos', 'BINA') 1947842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 1950f675860ce081e9aeaf880ccc4a8e5d49e83f553Just # assume "file" is a writable file object 1960f675860ce081e9aeaf880ccc4a8e5d49e83f553Just closeStream = 0 1977842e56b97ce677b83bdab09cda48bc2d89ac75aJust 198c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod tags = list(self.keys()) 199700df031313741e79b0647f045949a1a04d03371jvr if "GlyphOrder" in tags: 200700df031313741e79b0647f045949a1a04d03371jvr tags.remove("GlyphOrder") 2017842e56b97ce677b83bdab09cda48bc2d89ac75aJust numTables = len(tags) 20228ae1962292b66ad67117aef2a99d5735a70b779jvr if reorderTables: 20328ae1962292b66ad67117aef2a99d5735a70b779jvr import tempfile 20428ae1962292b66ad67117aef2a99d5735a70b779jvr tmp = tempfile.TemporaryFile(prefix="ttx-fonttools") 20528ae1962292b66ad67117aef2a99d5735a70b779jvr else: 20628ae1962292b66ad67117aef2a99d5735a70b779jvr tmp = file 207b0dc6dfc8baf01db94782fccc2e734a281b9ba12Behdad Esfahbod writer = sfnt.SFNTWriter(tmp, numTables, self.sfntVersion, self.flavor, self.flavorData) 2087842e56b97ce677b83bdab09cda48bc2d89ac75aJust 2097842e56b97ce677b83bdab09cda48bc2d89ac75aJust done = [] 2107842e56b97ce677b83bdab09cda48bc2d89ac75aJust for tag in tags: 2117842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._writeTable(tag, writer, done) 2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust 21328ae1962292b66ad67117aef2a99d5735a70b779jvr writer.close() 21428ae1962292b66ad67117aef2a99d5735a70b779jvr 21528ae1962292b66ad67117aef2a99d5735a70b779jvr if reorderTables: 21628ae1962292b66ad67117aef2a99d5735a70b779jvr tmp.flush() 21728ae1962292b66ad67117aef2a99d5735a70b779jvr tmp.seek(0) 21828ae1962292b66ad67117aef2a99d5735a70b779jvr reorderFontTables(tmp, file) 21928ae1962292b66ad67117aef2a99d5735a70b779jvr tmp.close() 22028ae1962292b66ad67117aef2a99d5735a70b779jvr 22128ae1962292b66ad67117aef2a99d5735a70b779jvr if closeStream: 22228ae1962292b66ad67117aef2a99d5735a70b779jvr file.close() 2237842e56b97ce677b83bdab09cda48bc2d89ac75aJust 22438fdae6342ae5339fbd148ef920f16291aa1e259Behdad Esfahbod def saveXML(self, fileOrPath, progress=None, quiet=False, 22538fdae6342ae5339fbd148ef920f16291aa1e259Behdad Esfahbod tables=None, skipTables=None, splitTables=False, disassembleInstructions=True, 226c33b0a22ef0046c392275e3dba974dfbadee24faMatt Fontaine bitmapGlyphDataFormat='raw'): 2279682b4198b8449ba02ae2aaaa5fc76307167845dJust """Export the font as TTX (an XML-based text file), or as a series of text 2287dcc91674e8df02f80711ee2587fd2a6a205ca94Just files when splitTables is true. In the latter case, the 'fileOrPath' 2297dcc91674e8df02f80711ee2587fd2a6a205ca94Just argument should be a path to a directory. 230ff3499de6618199b152484e5a89d10bb6aff384dJust The 'tables' argument must either be false (dump all tables) or a 231ff3499de6618199b152484e5a89d10bb6aff384dJust list of tables to dump. The 'skipTables' argument may be a list of tables 232ff3499de6618199b152484e5a89d10bb6aff384dJust to skip, but only when the 'tables' argument is false. 2337dcc91674e8df02f80711ee2587fd2a6a205ca94Just """ 234d04a3bb4f932dff5dfbd45ed33fa4c357b471527jvr from fontTools import version 235f65033e277ec5161fd77ec340ab899edc2e03764Behdad Esfahbod from fontTools.misc import xmlWriter 23653602486b491e003b8dbba0b1c86709bd0c011bfJust 23753602486b491e003b8dbba0b1c86709bd0c011bfJust self.disassembleInstructions = disassembleInstructions 238c33b0a22ef0046c392275e3dba974dfbadee24faMatt Fontaine self.bitmapGlyphDataFormat = bitmapGlyphDataFormat 2397842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not tables: 240c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod tables = list(self.keys()) 241700df031313741e79b0647f045949a1a04d03371jvr if "GlyphOrder" not in tables: 242700df031313741e79b0647f045949a1a04d03371jvr tables = ["GlyphOrder"] + tables 243ff3499de6618199b152484e5a89d10bb6aff384dJust if skipTables: 244ff3499de6618199b152484e5a89d10bb6aff384dJust for tag in skipTables: 245ff3499de6618199b152484e5a89d10bb6aff384dJust if tag in tables: 246ff3499de6618199b152484e5a89d10bb6aff384dJust tables.remove(tag) 2477842e56b97ce677b83bdab09cda48bc2d89ac75aJust numTables = len(tables) 2487842e56b97ce677b83bdab09cda48bc2d89ac75aJust if progress: 2496ab979cacaa9c15666a526e05c669b7f87bb6de9jvr progress.set(0, numTables) 2506ab979cacaa9c15666a526e05c669b7f87bb6de9jvr idlefunc = getattr(progress, "idle", None) 2516ab979cacaa9c15666a526e05c669b7f87bb6de9jvr else: 2526ab979cacaa9c15666a526e05c669b7f87bb6de9jvr idlefunc = None 253fe665777eae7b67a274e6804295191c1b7a995bfjvr 2546ab979cacaa9c15666a526e05c669b7f87bb6de9jvr writer = xmlWriter.XMLWriter(fileOrPath, idlefunc=idlefunc) 255dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod writer.begintag("ttFont", sfntVersion=repr(self.sfntVersion)[1:-1], 256fe665777eae7b67a274e6804295191c1b7a995bfjvr ttLibVersion=version) 257fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.newline() 258fe665777eae7b67a274e6804295191c1b7a995bfjvr 259d28479b066106e1c18528d2a4230b471cf76889dJust if not splitTables: 260d28479b066106e1c18528d2a4230b471cf76889dJust writer.newline() 261d28479b066106e1c18528d2a4230b471cf76889dJust else: 2620f293034749578d29494c2560c042c01ced50601jvr # 'fileOrPath' must now be a path 2630f293034749578d29494c2560c042c01ced50601jvr path, ext = os.path.splitext(fileOrPath) 2640f293034749578d29494c2560c042c01ced50601jvr fileNameTemplate = path + ".%s" + ext 265d28479b066106e1c18528d2a4230b471cf76889dJust 2667842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(numTables): 2676ab979cacaa9c15666a526e05c669b7f87bb6de9jvr if progress: 2686ab979cacaa9c15666a526e05c669b7f87bb6de9jvr progress.set(i) 2697842e56b97ce677b83bdab09cda48bc2d89ac75aJust tag = tables[i] 270d28479b066106e1c18528d2a4230b471cf76889dJust if splitTables: 2718307fa42cd145bb1b961ff942bbe3eef63908e55jvr tablePath = fileNameTemplate % tagToIdentifier(tag) 2726ab979cacaa9c15666a526e05c669b7f87bb6de9jvr tableWriter = xmlWriter.XMLWriter(tablePath, idlefunc=idlefunc) 273fe665777eae7b67a274e6804295191c1b7a995bfjvr tableWriter.begintag("ttFont", ttLibVersion=version) 274fe665777eae7b67a274e6804295191c1b7a995bfjvr tableWriter.newline() 275fe665777eae7b67a274e6804295191c1b7a995bfjvr tableWriter.newline() 2760011bb691018c7feed1f8c3554d88b9ae096e127jvr writer.simpletag(tagToXML(tag), src=os.path.basename(tablePath)) 277d28479b066106e1c18528d2a4230b471cf76889dJust writer.newline() 2787842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 279fe665777eae7b67a274e6804295191c1b7a995bfjvr tableWriter = writer 280d7efd5692cac74702d487e641ca7ddf9c6cfececDave Crossland self._tableToXML(tableWriter, tag, progress, quiet) 281d28479b066106e1c18528d2a4230b471cf76889dJust if splitTables: 282fe665777eae7b67a274e6804295191c1b7a995bfjvr tableWriter.endtag("ttFont") 283fe665777eae7b67a274e6804295191c1b7a995bfjvr tableWriter.newline() 284fe665777eae7b67a274e6804295191c1b7a995bfjvr tableWriter.close() 2856ab979cacaa9c15666a526e05c669b7f87bb6de9jvr if progress: 2866ab979cacaa9c15666a526e05c669b7f87bb6de9jvr progress.set((i + 1)) 287fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.endtag("ttFont") 288fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.newline() 289fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.close() 2907842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.verbose: 2919682b4198b8449ba02ae2aaaa5fc76307167845dJust debugmsg("Done dumping TTX") 2927842e56b97ce677b83bdab09cda48bc2d89ac75aJust 293d7efd5692cac74702d487e641ca7ddf9c6cfececDave Crossland def _tableToXML(self, writer, tag, progress, quiet): 294bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if tag in self: 295fe665777eae7b67a274e6804295191c1b7a995bfjvr table = self[tag] 296fe665777eae7b67a274e6804295191c1b7a995bfjvr report = "Dumping '%s' table..." % tag 297fe665777eae7b67a274e6804295191c1b7a995bfjvr else: 298fe665777eae7b67a274e6804295191c1b7a995bfjvr report = "No '%s' table found." % tag 299fe665777eae7b67a274e6804295191c1b7a995bfjvr if progress: 3006ab979cacaa9c15666a526e05c669b7f87bb6de9jvr progress.setLabel(report) 301fe665777eae7b67a274e6804295191c1b7a995bfjvr elif self.verbose: 302fe665777eae7b67a274e6804295191c1b7a995bfjvr debugmsg(report) 303fe665777eae7b67a274e6804295191c1b7a995bfjvr else: 304d7efd5692cac74702d487e641ca7ddf9c6cfececDave Crossland if not quiet: 3053ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print(report) 306bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if tag not in self: 307fe665777eae7b67a274e6804295191c1b7a995bfjvr return 3080011bb691018c7feed1f8c3554d88b9ae096e127jvr xmlTag = tagToXML(tag) 309fe665777eae7b67a274e6804295191c1b7a995bfjvr if hasattr(table, "ERROR"): 310fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.begintag(xmlTag, ERROR="decompilation error") 311fe665777eae7b67a274e6804295191c1b7a995bfjvr else: 312fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.begintag(xmlTag) 313fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.newline() 314fe665777eae7b67a274e6804295191c1b7a995bfjvr if tag in ("glyf", "CFF "): 315fe665777eae7b67a274e6804295191c1b7a995bfjvr table.toXML(writer, self, progress) 316fe665777eae7b67a274e6804295191c1b7a995bfjvr else: 317fe665777eae7b67a274e6804295191c1b7a995bfjvr table.toXML(writer, self) 318fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.endtag(xmlTag) 319fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.newline() 320fe665777eae7b67a274e6804295191c1b7a995bfjvr writer.newline() 321fe665777eae7b67a274e6804295191c1b7a995bfjvr 32238fdae6342ae5339fbd148ef920f16291aa1e259Behdad Esfahbod def importXML(self, file, progress=None, quiet=False): 323ca4c45681ef2ea9290c6845f8bf61dff281fc5c4jvr """Import a TTX file (an XML-based text format), so as to recreate 3247842e56b97ce677b83bdab09cda48bc2d89ac75aJust a font object. 3257842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 326bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if "maxp" in self and "post" in self: 327d57c4346e5725bbfe0e1b13067b292c3faaaeb8ejvr # Make sure the glyph order is loaded, as it otherwise gets 328d57c4346e5725bbfe0e1b13067b292c3faaaeb8ejvr # lost if the XML doesn't contain the glyph order, yet does 329d57c4346e5725bbfe0e1b13067b292c3faaaeb8ejvr # contain the table which was originally used to extract the 330d57c4346e5725bbfe0e1b13067b292c3faaaeb8ejvr # glyph names from (ie. 'post', 'cmap' or 'CFF '). 33122f068929cbc8f3e5d18931c69a14d757eaa4de1jvr self.getGlyphOrder() 3323ebfea491ea795217e0f9649c23ee81df769a250Behdad Esfahbod 3333ebfea491ea795217e0f9649c23ee81df769a250Behdad Esfahbod from fontTools.misc import xmlReader 3343ebfea491ea795217e0f9649c23ee81df769a250Behdad Esfahbod 3353ebfea491ea795217e0f9649c23ee81df769a250Behdad Esfahbod reader = xmlReader.XMLReader(file, self, progress, quiet) 3363ebfea491ea795217e0f9649c23ee81df769a250Behdad Esfahbod reader.read() 3377842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3387842e56b97ce677b83bdab09cda48bc2d89ac75aJust def isLoaded(self, tag): 3397842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Return true if the table identified by 'tag' has been 3407842e56b97ce677b83bdab09cda48bc2d89ac75aJust decompiled and loaded into memory.""" 341bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod return tag in self.tables 3427842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3437842e56b97ce677b83bdab09cda48bc2d89ac75aJust def has_key(self, tag): 3447842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.isLoaded(tag): 345dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod return True 346bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod elif self.reader and tag in self.reader: 347dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod return True 3480011bb691018c7feed1f8c3554d88b9ae096e127jvr elif tag == "GlyphOrder": 349dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod return True 3507842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 351dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod return False 3527842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3538df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr __contains__ = has_key 3548df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 3557842e56b97ce677b83bdab09cda48bc2d89ac75aJust def keys(self): 356c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod keys = list(self.tables.keys()) 3577842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.reader: 358c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod for key in list(self.reader.keys()): 3597842e56b97ce677b83bdab09cda48bc2d89ac75aJust if key not in keys: 3607842e56b97ce677b83bdab09cda48bc2d89ac75aJust keys.append(key) 361700df031313741e79b0647f045949a1a04d03371jvr 36228ae1962292b66ad67117aef2a99d5735a70b779jvr if "GlyphOrder" in keys: 36328ae1962292b66ad67117aef2a99d5735a70b779jvr keys.remove("GlyphOrder") 36428ae1962292b66ad67117aef2a99d5735a70b779jvr keys = sortedTagList(keys) 36528ae1962292b66ad67117aef2a99d5735a70b779jvr return ["GlyphOrder"] + keys 3667842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3677842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __len__(self): 368c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod return len(list(self.keys())) 3697842e56b97ce677b83bdab09cda48bc2d89ac75aJust 3707842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __getitem__(self, tag): 371960280bbd6277b56be45595a050a720a49fd5917Behdad Esfahbod tag = Tag(tag) 3727842e56b97ce677b83bdab09cda48bc2d89ac75aJust try: 3737842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.tables[tag] 3747842e56b97ce677b83bdab09cda48bc2d89ac75aJust except KeyError: 3750011bb691018c7feed1f8c3554d88b9ae096e127jvr if tag == "GlyphOrder": 376c4af3964fbcfefb562fcc72171f5b48bb5065040Behdad Esfahbod table = GlyphOrder(tag) 3770011bb691018c7feed1f8c3554d88b9ae096e127jvr self.tables[tag] = table 3780011bb691018c7feed1f8c3554d88b9ae096e127jvr return table 3797842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.reader is not None: 380f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust import traceback 3817842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.verbose: 3826ab979cacaa9c15666a526e05c669b7f87bb6de9jvr debugmsg("Reading '%s' table from disk" % tag) 3837842e56b97ce677b83bdab09cda48bc2d89ac75aJust data = self.reader[tag] 3848307fa42cd145bb1b961ff942bbe3eef63908e55jvr tableClass = getTableClass(tag) 3858307fa42cd145bb1b961ff942bbe3eef63908e55jvr table = tableClass(tag) 3867842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.tables[tag] = table 3877842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.verbose: 3886ab979cacaa9c15666a526e05c669b7f87bb6de9jvr debugmsg("Decompiling '%s' table" % tag) 389f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust try: 390f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust table.decompile(data, self) 3912545f16b54798fe809238c76e2240f901fce8f8djvr except: 3922545f16b54798fe809238c76e2240f901fce8f8djvr if not self.ignoreDecompileErrors: 3932545f16b54798fe809238c76e2240f901fce8f8djvr raise 3942545f16b54798fe809238c76e2240f901fce8f8djvr # fall back to DefaultTable, retaining the binary table data 3953ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print("An exception occurred during the decompilation of the '%s' table" % tag) 3962b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod from .tables.DefaultTable import DefaultTable 397b92c08059d49a13364257629dd5601dad4ff91d7Behdad Esfahbod file = StringIO() 398f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust traceback.print_exc(file=file) 399f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust table = DefaultTable(tag) 400f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust table.ERROR = file.getvalue() 401f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust self.tables[tag] = table 402f8fd4777d273836a1222b72f6761cb6fdf9ec87aJust table.decompile(data, self) 4037842e56b97ce677b83bdab09cda48bc2d89ac75aJust return table 4047842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 405cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise KeyError("'%s' table not found" % tag) 4067842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4077842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __setitem__(self, tag, table): 4085cf40083364e1d2dce119de25cb42ce69d2fb53cBehdad Esfahbod self.tables[Tag(tag)] = table 4097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4107842e56b97ce677b83bdab09cda48bc2d89ac75aJust def __delitem__(self, tag): 411bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if tag not in self: 412cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise KeyError("'%s' table not found" % tag) 413bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if tag in self.tables: 414f70746325687393330f7a765814a0fe78f11f847jvr del self.tables[tag] 415bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if self.reader and tag in self.reader: 416f70746325687393330f7a765814a0fe78f11f847jvr del self.reader[tag] 4179c5e2ce1b6cb09728b962ed9e055ffe8fd72b372Behdad Esfahbod 4189c5e2ce1b6cb09728b962ed9e055ffe8fd72b372Behdad Esfahbod def get(self, tag, default=None): 4199c5e2ce1b6cb09728b962ed9e055ffe8fd72b372Behdad Esfahbod try: 4209c5e2ce1b6cb09728b962ed9e055ffe8fd72b372Behdad Esfahbod return self[tag] 4219c5e2ce1b6cb09728b962ed9e055ffe8fd72b372Behdad Esfahbod except KeyError: 4229c5e2ce1b6cb09728b962ed9e055ffe8fd72b372Behdad Esfahbod return default 4237842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4247842e56b97ce677b83bdab09cda48bc2d89ac75aJust def setGlyphOrder(self, glyphOrder): 4257842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.glyphOrder = glyphOrder 4267842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4277842e56b97ce677b83bdab09cda48bc2d89ac75aJust def getGlyphOrder(self): 428cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr try: 429cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr return self.glyphOrder 430cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr except AttributeError: 431cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr pass 432bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if 'CFF ' in self: 4330b63b28615933a1332ea55ce4f5daedf5269192cjvr cff = self['CFF '] 434700df031313741e79b0647f045949a1a04d03371jvr self.glyphOrder = cff.getGlyphOrder() 435bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod elif 'post' in self: 436cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr # TrueType font 437cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr glyphOrder = self['post'].getGlyphOrder() 438cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr if glyphOrder is None: 439cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr # 440cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr # No names found in the 'post' table. 441cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr # Try to create glyph names from the unicode cmap (if available) 442cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr # in combination with the Adobe Glyph List (AGL). 443cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr # 444c91a95189abd06cd2c314f511f3f8cb306aba8e7Just self._getGlyphNamesFromCmap() 445cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr else: 446cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr self.glyphOrder = glyphOrder 447cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr else: 448cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr self._getGlyphNamesFromCmap() 4497842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.glyphOrder 4507842e56b97ce677b83bdab09cda48bc2d89ac75aJust 4517842e56b97ce677b83bdab09cda48bc2d89ac75aJust def _getGlyphNamesFromCmap(self): 4529f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # 4539f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # This is rather convoluted, but then again, it's an interesting problem: 4549f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # - we need to use the unicode values found in the cmap table to 4559f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # build glyph names (eg. because there is only a minimal post table, 4569f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # or none at all). 4579f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # - but the cmap parser also needs glyph names to work with... 4589f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # So here's what we do: 4599f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # - make up glyph names based on glyphID 4609f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # - load a temporary cmap table based on those names 4619f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # - extract the unicode values, build the "real" glyph names 4629f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # - unload the temporary cmap table 4639f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # 4649f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr if self.isLoaded("cmap"): 4659f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # Bootstrapping: we're getting called by the cmap parser 4669f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # itself. This means self.tables['cmap'] contains a partially 4679f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # loaded cmap, making it impossible to get at a unicode 4689f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # subtable here. We remove the partially loaded cmap and 4699f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # restore it later. 4709f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # This only happens if the cmap table is loaded before any 4719f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # other table that does f.getGlyphOrder() or f.getGlyphName(). 4729f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr cmapLoading = self.tables['cmap'] 4739f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr del self.tables['cmap'] 4749f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr else: 4759f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr cmapLoading = None 4769f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # Make up glyph names based on glyphID, which will be used by the 4779f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # temporary cmap and by the real cmap in case we don't find a unicode 4789f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # cmap. 4797842e56b97ce677b83bdab09cda48bc2d89ac75aJust numGlyphs = int(self['maxp'].numGlyphs) 4807842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphOrder = [None] * numGlyphs 4817842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphOrder[0] = ".notdef" 4827842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(1, numGlyphs): 4837842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphOrder[i] = "glyph%.5d" % i 4847842e56b97ce677b83bdab09cda48bc2d89ac75aJust # Set the glyph order, so the cmap parser has something 4859f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # to work with (so we don't get called recursively). 4867842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.glyphOrder = glyphOrder 4879f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # Get a (new) temporary cmap (based on the just invented names) 4887842e56b97ce677b83bdab09cda48bc2d89ac75aJust tempcmap = self['cmap'].getcmap(3, 1) 4897842e56b97ce677b83bdab09cda48bc2d89ac75aJust if tempcmap is not None: 4907842e56b97ce677b83bdab09cda48bc2d89ac75aJust # we have a unicode cmap 4917dcc91674e8df02f80711ee2587fd2a6a205ca94Just from fontTools import agl 4927842e56b97ce677b83bdab09cda48bc2d89ac75aJust cmap = tempcmap.cmap 4937842e56b97ce677b83bdab09cda48bc2d89ac75aJust # create a reverse cmap dict 4947842e56b97ce677b83bdab09cda48bc2d89ac75aJust reversecmap = {} 495c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod for unicode, name in list(cmap.items()): 4967842e56b97ce677b83bdab09cda48bc2d89ac75aJust reversecmap[name] = unicode 497a556f51db59a347549c08dd6483ba5b408d0bcfdJust allNames = {} 4987842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(numGlyphs): 4997842e56b97ce677b83bdab09cda48bc2d89ac75aJust tempName = glyphOrder[i] 500bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if tempName in reversecmap: 5017842e56b97ce677b83bdab09cda48bc2d89ac75aJust unicode = reversecmap[tempName] 502bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if unicode in agl.UV2AGL: 5037842e56b97ce677b83bdab09cda48bc2d89ac75aJust # get name from the Adobe Glyph List 504a556f51db59a347549c08dd6483ba5b408d0bcfdJust glyphName = agl.UV2AGL[unicode] 5057842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 5067842e56b97ce677b83bdab09cda48bc2d89ac75aJust # create uni<CODE> name 50714fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod glyphName = "uni%04X" % unicode 508a556f51db59a347549c08dd6483ba5b408d0bcfdJust tempName = glyphName 509a556f51db59a347549c08dd6483ba5b408d0bcfdJust n = 1 510bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod while tempName in allNames: 511dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod tempName = glyphName + "#" + repr(n) 512a556f51db59a347549c08dd6483ba5b408d0bcfdJust n = n + 1 513a556f51db59a347549c08dd6483ba5b408d0bcfdJust glyphOrder[i] = tempName 514a556f51db59a347549c08dd6483ba5b408d0bcfdJust allNames[tempName] = 1 5159f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # Delete the temporary cmap table from the cache, so it can 5169f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # be parsed again with the right names. 5177842e56b97ce677b83bdab09cda48bc2d89ac75aJust del self.tables['cmap'] 5187842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 5197842e56b97ce677b83bdab09cda48bc2d89ac75aJust pass # no unicode cmap available, stick with the invented names 5207842e56b97ce677b83bdab09cda48bc2d89ac75aJust self.glyphOrder = glyphOrder 5219f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr if cmapLoading: 5229f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # restore partially loaded cmap, so it can continue loading 5239f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr # using the proper names. 5249f1e14bec9dad6d59300bbff6a5a9d5a8d0828f9jvr self.tables['cmap'] = cmapLoading 5257842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5267842e56b97ce677b83bdab09cda48bc2d89ac75aJust def getGlyphNames(self): 5277842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Get a list of glyph names, sorted alphabetically.""" 528ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod glyphNames = sorted(self.getGlyphOrder()[:]) 5297842e56b97ce677b83bdab09cda48bc2d89ac75aJust return glyphNames 5307842e56b97ce677b83bdab09cda48bc2d89ac75aJust 5317842e56b97ce677b83bdab09cda48bc2d89ac75aJust def getGlyphNames2(self): 5328bc8cf850df75c660e15d34fac8ac70f18a4da4aJust """Get a list of glyph names, sorted alphabetically, 5338bc8cf850df75c660e15d34fac8ac70f18a4da4aJust but not case sensitive. 5348bc8cf850df75c660e15d34fac8ac70f18a4da4aJust """ 5357842e56b97ce677b83bdab09cda48bc2d89ac75aJust from fontTools.misc import textTools 5367842e56b97ce677b83bdab09cda48bc2d89ac75aJust return textTools.caselessSort(self.getGlyphOrder()) 5377842e56b97ce677b83bdab09cda48bc2d89ac75aJust 538dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod def getGlyphName(self, glyphID, requireReal=False): 5390b63b28615933a1332ea55ce4f5daedf5269192cjvr try: 5400b63b28615933a1332ea55ce4f5daedf5269192cjvr return self.getGlyphOrder()[glyphID] 5410b63b28615933a1332ea55ce4f5daedf5269192cjvr except IndexError: 542823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if requireReal or not self.allowVID: 543823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # XXX The ??.W8.otf font that ships with OSX uses higher glyphIDs in 544823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # the cmap table than there are glyphs. I don't think it's legal... 545823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return "glyph%.5d" % glyphID 546823f8cd15f16bb9dc3991c2672f16dd90579711bjvr else: 547823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # user intends virtual GID support 548823f8cd15f16bb9dc3991c2672f16dd90579711bjvr try: 549823f8cd15f16bb9dc3991c2672f16dd90579711bjvr glyphName = self.VIDDict[glyphID] 550823f8cd15f16bb9dc3991c2672f16dd90579711bjvr except KeyError: 551823f8cd15f16bb9dc3991c2672f16dd90579711bjvr glyphName ="glyph%.5d" % glyphID 552823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.last_vid = min(glyphID, self.last_vid ) 553823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.reverseVIDDict[glyphName] = glyphID 554823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.VIDDict[glyphID] = glyphName 555823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return glyphName 556823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 557dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod def getGlyphID(self, glyphName, requireReal=False): 5587842e56b97ce677b83bdab09cda48bc2d89ac75aJust if not hasattr(self, "_reverseGlyphOrderDict"): 5597842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._buildReverseGlyphOrderDict() 5607842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphOrder = self.getGlyphOrder() 5617842e56b97ce677b83bdab09cda48bc2d89ac75aJust d = self._reverseGlyphOrderDict 562bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if glyphName not in d: 5637842e56b97ce677b83bdab09cda48bc2d89ac75aJust if glyphName in glyphOrder: 5647842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._buildReverseGlyphOrderDict() 5657842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.getGlyphID(glyphName) 5667842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 567e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod if requireReal: 568cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise KeyError(glyphName) 569e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod elif not self.allowVID: 570e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod # Handle glyphXXX only 571e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod if glyphName[:5] == "glyph": 572e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod try: 573e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod return int(glyphName[5:]) 574e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod except (NameError, ValueError): 575e5bee3716e537cf53d3f0fe7a58cf76e78e7b7b9Behdad Esfahbod raise KeyError(glyphName) 576823f8cd15f16bb9dc3991c2672f16dd90579711bjvr else: 577823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # user intends virtual GID support 578823f8cd15f16bb9dc3991c2672f16dd90579711bjvr try: 579823f8cd15f16bb9dc3991c2672f16dd90579711bjvr glyphID = self.reverseVIDDict[glyphName] 580823f8cd15f16bb9dc3991c2672f16dd90579711bjvr except KeyError: 581823f8cd15f16bb9dc3991c2672f16dd90579711bjvr # if name is in glyphXXX format, use the specified name. 582823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if glyphName[:5] == "glyph": 583823f8cd15f16bb9dc3991c2672f16dd90579711bjvr try: 584823f8cd15f16bb9dc3991c2672f16dd90579711bjvr glyphID = int(glyphName[5:]) 585823f8cd15f16bb9dc3991c2672f16dd90579711bjvr except (NameError, ValueError): 586823f8cd15f16bb9dc3991c2672f16dd90579711bjvr glyphID = None 5879e6ef94b5554c5b7dda2de9c863c11ed4b996b7aBehdad Esfahbod if glyphID is None: 588823f8cd15f16bb9dc3991c2672f16dd90579711bjvr glyphID = self.last_vid -1 589823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.last_vid = glyphID 590823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.reverseVIDDict[glyphName] = glyphID 591823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self.VIDDict[glyphID] = glyphName 592823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return glyphID 593823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 5947842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphID = d[glyphName] 595180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod if glyphName != glyphOrder[glyphID]: 5967842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._buildReverseGlyphOrderDict() 5977842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.getGlyphID(glyphName) 5987842e56b97ce677b83bdab09cda48bc2d89ac75aJust return glyphID 599823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 600dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod def getReverseGlyphMap(self, rebuild=False): 601823f8cd15f16bb9dc3991c2672f16dd90579711bjvr if rebuild or not hasattr(self, "_reverseGlyphOrderDict"): 602823f8cd15f16bb9dc3991c2672f16dd90579711bjvr self._buildReverseGlyphOrderDict() 603823f8cd15f16bb9dc3991c2672f16dd90579711bjvr return self._reverseGlyphOrderDict 604823f8cd15f16bb9dc3991c2672f16dd90579711bjvr 6057842e56b97ce677b83bdab09cda48bc2d89ac75aJust def _buildReverseGlyphOrderDict(self): 6067842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._reverseGlyphOrderDict = d = {} 6077842e56b97ce677b83bdab09cda48bc2d89ac75aJust glyphOrder = self.getGlyphOrder() 6087842e56b97ce677b83bdab09cda48bc2d89ac75aJust for glyphID in range(len(glyphOrder)): 6097842e56b97ce677b83bdab09cda48bc2d89ac75aJust d[glyphOrder[glyphID]] = glyphID 6107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 6117842e56b97ce677b83bdab09cda48bc2d89ac75aJust def _writeTable(self, tag, writer, done): 6127842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Internal helper function for self.save(). Keeps track of 6137842e56b97ce677b83bdab09cda48bc2d89ac75aJust inter-table dependencies. 6147842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 6157842e56b97ce677b83bdab09cda48bc2d89ac75aJust if tag in done: 6167842e56b97ce677b83bdab09cda48bc2d89ac75aJust return 6178307fa42cd145bb1b961ff942bbe3eef63908e55jvr tableClass = getTableClass(tag) 6188307fa42cd145bb1b961ff942bbe3eef63908e55jvr for masterTable in tableClass.dependencies: 6197842e56b97ce677b83bdab09cda48bc2d89ac75aJust if masterTable not in done: 620bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if masterTable in self: 6217842e56b97ce677b83bdab09cda48bc2d89ac75aJust self._writeTable(masterTable, writer, done) 6227842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 6237842e56b97ce677b83bdab09cda48bc2d89ac75aJust done.append(masterTable) 624cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr tabledata = self.getTableData(tag) 6257842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.verbose: 6267842e56b97ce677b83bdab09cda48bc2d89ac75aJust debugmsg("writing '%s' table to disk" % tag) 6277842e56b97ce677b83bdab09cda48bc2d89ac75aJust writer[tag] = tabledata 6287842e56b97ce677b83bdab09cda48bc2d89ac75aJust done.append(tag) 6297842e56b97ce677b83bdab09cda48bc2d89ac75aJust 630cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr def getTableData(self, tag): 631cf4b3b3d9b0f8b9777097a86810c842e7042d516jvr """Returns raw table data, whether compiled or directly read from disk. 6327842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 6335cf40083364e1d2dce119de25cb42ce69d2fb53cBehdad Esfahbod tag = Tag(tag) 6347842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.isLoaded(tag): 6357842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.verbose: 6367842e56b97ce677b83bdab09cda48bc2d89ac75aJust debugmsg("compiling '%s' table" % tag) 6377842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.tables[tag].compile(self) 638bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod elif self.reader and tag in self.reader: 6397842e56b97ce677b83bdab09cda48bc2d89ac75aJust if self.verbose: 6406ab979cacaa9c15666a526e05c669b7f87bb6de9jvr debugmsg("Reading '%s' table from disk" % tag) 6417842e56b97ce677b83bdab09cda48bc2d89ac75aJust return self.reader[tag] 6427842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 643cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise KeyError(tag) 6448df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 645dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbod def getGlyphSet(self, preferCFF=True): 6468df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr """Return a generic GlyphSet, which is a dict-like object 6478df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr mapping glyph names to glyph objects. The returned glyph objects 6488df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr have a .draw() method that supports the Pen protocol, and will 6498df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr have an attribute named 'width', but only *after* the .draw() method 6508df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr has been called. 6518df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6528df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr If the font is CFF-based, the outlines will be taken from the 'CFF ' 6538df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr table. Otherwise the outlines will be taken from the 'glyf' table. 6548df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr If the font contains both a 'CFF ' and a 'glyf' table, you can use 6558df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr the 'preferCFF' argument to specify which one should be taken. 6568df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr """ 657bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if preferCFF and "CFF " in self: 658c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod return list(self["CFF "].cff.values())[0].CharStrings 659bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if "glyf" in self: 6608df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr return _TTGlyphSet(self) 661bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod if "CFF " in self: 662c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod return list(self["CFF "].cff.values())[0].CharStrings 663cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod raise TTLibError("Font contains no outlines") 6648df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6658df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 666e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass _TTGlyphSet(object): 6678df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6688df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr """Generic dict-like GlyphSet class, meant as a TrueType counterpart 6698df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr to CFF's CharString dict. See TTFont.getGlyphSet(). 6708df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr """ 6718df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6728df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr # This class is distinct from the 'glyf' table itself because we need 6738df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr # access to the 'hmtx' table, which could cause a dependency problem 6748df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr # there when reading from XML. 6758df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6768df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr def __init__(self, ttFont): 6778df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr self._ttFont = ttFont 6788df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6798df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr def keys(self): 680c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod return list(self._ttFont["glyf"].keys()) 6818df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6828df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr def has_key(self, glyphName): 683bc5e1cb195c0bfa1c8e7507326d5a9ad05aecb4bBehdad Esfahbod return glyphName in self._ttFont["glyf"] 6848df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6858df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr __contains__ = has_key 6868df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6878df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr def __getitem__(self, glyphName): 6888df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr return _TTGlyph(glyphName, self._ttFont) 6898df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 6902e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr def get(self, glyphName, default=None): 6912e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr try: 6922e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr return self[glyphName] 6932e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr except KeyError: 6942e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr return default 6952e4cc02ca31c43eafb6f752e44dbca9b004a3a2fjvr 6968df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 697e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass _TTGlyph(object): 6988df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 699d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr """Wrapper for a TrueType glyph that supports the Pen protocol, meaning 700d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr that it has a .draw() method that takes a pen object as its only 701d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr argument. Additionally there is a 'width' attribute. 7028df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr """ 7038df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 7048df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr def __init__(self, glyphName, ttFont): 7058df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr self._glyphName = glyphName 7068df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr self._ttFont = ttFont 707d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr self.width, self.lsb = self._ttFont['hmtx'][self._glyphName] 7088df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr 7098df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr def draw(self, pen): 710d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr """Draw the glyph onto Pen. See fontTools.pens.basePen for details 711d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr how that works. 712d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr """ 7138df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr glyfTable = self._ttFont['glyf'] 7148df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr glyph = glyfTable[self._glyphName] 7158df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr if hasattr(glyph, "xMin"): 716d028b7be3cffa4ce796e494a9c9ea0f11a432e36jvr offset = self.lsb - glyph.xMin 7178df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr else: 7188df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr offset = 0 7198df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr if glyph.isComposite(): 7208df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr for component in glyph: 7218df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr glyphName, transform = component.getComponentInfo() 7228df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr pen.addComponent(glyphName, transform) 7238df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr else: 7248df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr coordinates, endPts, flags = glyph.getCoordinates(glyfTable) 7258df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr if offset: 7268df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr coordinates = coordinates + (offset, 0) 7278df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr start = 0 7288df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr for end in endPts: 7298df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr end = end + 1 7308df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr contour = coordinates[start:end].tolist() 7318df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr cFlags = flags[start:end].tolist() 7328df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr start = end 7338df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr if 1 not in cFlags: 7348df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr # There is not a single on-curve point on the curve, 7358df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr # use pen.qCurveTo's special case by specifying None 7368df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr # as the on-curve point. 7378df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr contour.append(None) 7388df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr pen.qCurveTo(*contour) 7398df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr else: 7401c9917bcf4fc789b0addc1dc078674972f13660ejvr # Shuffle the points so that contour the is guaranteed 7411c9917bcf4fc789b0addc1dc078674972f13660ejvr # to *end* in an on-curve point, which we'll use for 7421c9917bcf4fc789b0addc1dc078674972f13660ejvr # the moveTo. 7438df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr firstOnCurve = cFlags.index(1) + 1 7448df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr contour = contour[firstOnCurve:] + contour[:firstOnCurve] 7458df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr cFlags = cFlags[firstOnCurve:] + cFlags[:firstOnCurve] 7468df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr pen.moveTo(contour[-1]) 7478df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr while contour: 7488df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr nextOnCurve = cFlags.index(1) + 1 7498df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr if nextOnCurve == 1: 7508df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr pen.lineTo(contour[0]) 7518df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr else: 7528df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr pen.qCurveTo(*contour[:nextOnCurve]) 7538df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr contour = contour[nextOnCurve:] 7548df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr cFlags = cFlags[nextOnCurve:] 7558df8f6385e045bc70f538a0d047ea6d13c9cacf0jvr pen.closePath() 7567842e56b97ce677b83bdab09cda48bc2d89ac75aJust 7577842e56b97ce677b83bdab09cda48bc2d89ac75aJust 758e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass GlyphOrder(object): 7590011bb691018c7feed1f8c3554d88b9ae096e127jvr 76022f068929cbc8f3e5d18931c69a14d757eaa4de1jvr """A pseudo table. The glyph order isn't in the font as a separate 76122f068929cbc8f3e5d18931c69a14d757eaa4de1jvr table, but it's nice to present it as such in the TTX format. 7620011bb691018c7feed1f8c3554d88b9ae096e127jvr """ 7630011bb691018c7feed1f8c3554d88b9ae096e127jvr 76450d6c7298ad51eade3a2b4f693ff8dff7913213eBehdad Esfahbod def __init__(self, tag=None): 765c4af3964fbcfefb562fcc72171f5b48bb5065040Behdad Esfahbod pass 7660011bb691018c7feed1f8c3554d88b9ae096e127jvr 7670011bb691018c7feed1f8c3554d88b9ae096e127jvr def toXML(self, writer, ttFont): 768c4af3964fbcfefb562fcc72171f5b48bb5065040Behdad Esfahbod glyphOrder = ttFont.getGlyphOrder() 7694e5af60930726d06a58a30bae45bb27ae50aea77jvr writer.comment("The 'id' attribute is only for humans; " 7704e5af60930726d06a58a30bae45bb27ae50aea77jvr "it is ignored when parsed.") 7710011bb691018c7feed1f8c3554d88b9ae096e127jvr writer.newline() 772c4af3964fbcfefb562fcc72171f5b48bb5065040Behdad Esfahbod for i in range(len(glyphOrder)): 773c4af3964fbcfefb562fcc72171f5b48bb5065040Behdad Esfahbod glyphName = glyphOrder[i] 7740011bb691018c7feed1f8c3554d88b9ae096e127jvr writer.simpletag("GlyphID", id=i, name=glyphName) 7750011bb691018c7feed1f8c3554d88b9ae096e127jvr writer.newline() 7760011bb691018c7feed1f8c3554d88b9ae096e127jvr 7773a9fd301808f5a8991ca9ac44028d1ecb22d307fBehdad Esfahbod def fromXML(self, name, attrs, content, ttFont): 7780011bb691018c7feed1f8c3554d88b9ae096e127jvr if not hasattr(self, "glyphOrder"): 7790011bb691018c7feed1f8c3554d88b9ae096e127jvr self.glyphOrder = [] 7800011bb691018c7feed1f8c3554d88b9ae096e127jvr ttFont.setGlyphOrder(self.glyphOrder) 7810011bb691018c7feed1f8c3554d88b9ae096e127jvr if name == "GlyphID": 7820011bb691018c7feed1f8c3554d88b9ae096e127jvr self.glyphOrder.append(attrs["name"]) 7830011bb691018c7feed1f8c3554d88b9ae096e127jvr 7840011bb691018c7feed1f8c3554d88b9ae096e127jvr 7857842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef getTableModule(tag): 7867842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Fetch the packer/unpacker module for a table. 7877842e56b97ce677b83bdab09cda48bc2d89ac75aJust Return None when no module is found. 7887842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 7892b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod from . import tables 7908307fa42cd145bb1b961ff942bbe3eef63908e55jvr pyTag = tagToIdentifier(tag) 7917842e56b97ce677b83bdab09cda48bc2d89ac75aJust try: 7920bb3ba522291076df18825a63c2f367d8ce3e48ajvr __import__("fontTools.ttLib.tables." + pyTag) 793223273f7923ef25e8b697b378a4b89ab1f88674fBehdad Esfahbod except ImportError as err: 794400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod # If pyTag is found in the ImportError message, 795400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod # means table is not implemented. If it's not 796400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod # there, then some other module is missing, don't 797400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod # suppress the error. 798400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod if str(err).find(pyTag) >= 0: 799400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod return None 800400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod else: 801400e9539fd5968fcbfeeb36abaea54d9c8997872Behdad Esfahbod raise err 8027842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 8038307fa42cd145bb1b961ff942bbe3eef63908e55jvr return getattr(tables, pyTag) 8047842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8057842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8067842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef getTableClass(tag): 8077842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Fetch the packer/unpacker class for a table. 8087842e56b97ce677b83bdab09cda48bc2d89ac75aJust Return None when no class is found. 8097842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 8107842e56b97ce677b83bdab09cda48bc2d89ac75aJust module = getTableModule(tag) 8117842e56b97ce677b83bdab09cda48bc2d89ac75aJust if module is None: 8122b06aaa2a6bcd363c25fb0c43f6bb906906594bdBehdad Esfahbod from .tables.DefaultTable import DefaultTable 8137842e56b97ce677b83bdab09cda48bc2d89ac75aJust return DefaultTable 8148307fa42cd145bb1b961ff942bbe3eef63908e55jvr pyTag = tagToIdentifier(tag) 8158307fa42cd145bb1b961ff942bbe3eef63908e55jvr tableClass = getattr(module, "table_" + pyTag) 8168307fa42cd145bb1b961ff942bbe3eef63908e55jvr return tableClass 8177842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8187842e56b97ce677b83bdab09cda48bc2d89ac75aJust 819d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahboddef getClassTag(klass): 820d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod """Fetch the table tag for a class object.""" 821d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod name = klass.__name__ 822d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod assert name[:6] == 'table_' 823d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod name = name[6:] # Chop 'table_' 824d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod return identifierToTag(name) 825d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod 826d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod 827d0a31f5a43c4743671c4941fe45dda059aada0beBehdad Esfahbod 8288307fa42cd145bb1b961ff942bbe3eef63908e55jvrdef newTable(tag): 8297842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Return a new instance of a table.""" 8308307fa42cd145bb1b961ff942bbe3eef63908e55jvr tableClass = getTableClass(tag) 8318307fa42cd145bb1b961ff942bbe3eef63908e55jvr return tableClass(tag) 8327842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8337842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8347842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef _escapechar(c): 8358307fa42cd145bb1b961ff942bbe3eef63908e55jvr """Helper function for tagToIdentifier()""" 8367842e56b97ce677b83bdab09cda48bc2d89ac75aJust import re 8377842e56b97ce677b83bdab09cda48bc2d89ac75aJust if re.match("[a-z0-9]", c): 8387842e56b97ce677b83bdab09cda48bc2d89ac75aJust return "_" + c 8397842e56b97ce677b83bdab09cda48bc2d89ac75aJust elif re.match("[A-Z]", c): 8407842e56b97ce677b83bdab09cda48bc2d89ac75aJust return c + "_" 8417842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 842319c5fd10e2ea84304bd299b7483e05b5b0d5480Behdad Esfahbod return hex(byteord(c))[2:] 8437842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8447842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8458307fa42cd145bb1b961ff942bbe3eef63908e55jvrdef tagToIdentifier(tag): 8467842e56b97ce677b83bdab09cda48bc2d89ac75aJust """Convert a table tag to a valid (but UGLY) python identifier, 8477842e56b97ce677b83bdab09cda48bc2d89ac75aJust as well as a filename that's guaranteed to be unique even on a 8487842e56b97ce677b83bdab09cda48bc2d89ac75aJust caseless file system. Each character is mapped to two characters. 8497842e56b97ce677b83bdab09cda48bc2d89ac75aJust Lowercase letters get an underscore before the letter, uppercase 8507842e56b97ce677b83bdab09cda48bc2d89ac75aJust letters get an underscore after the letter. Trailing spaces are 8517842e56b97ce677b83bdab09cda48bc2d89ac75aJust trimmed. Illegal characters are escaped as two hex bytes. If the 8527842e56b97ce677b83bdab09cda48bc2d89ac75aJust result starts with a number (as the result of a hex escape), an 8537842e56b97ce677b83bdab09cda48bc2d89ac75aJust extra underscore is prepended. Examples: 8547842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'glyf' -> '_g_l_y_f' 8557842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'cvt ' -> '_c_v_t' 8567842e56b97ce677b83bdab09cda48bc2d89ac75aJust 'OS/2' -> 'O_S_2f_2' 8577842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 8587842e56b97ce677b83bdab09cda48bc2d89ac75aJust import re 859d2f5d2f8b4bce13ff7811cbf4c1ede81b73b8192Behdad Esfahbod tag = Tag(tag) 860e5ae28e8424192ce7ea19897473601f590dc80eajvr if tag == "GlyphOrder": 861e5ae28e8424192ce7ea19897473601f590dc80eajvr return tag 8627842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert len(tag) == 4, "tag should be 4 characters long" 8637842e56b97ce677b83bdab09cda48bc2d89ac75aJust while len(tag) > 1 and tag[-1] == ' ': 8647842e56b97ce677b83bdab09cda48bc2d89ac75aJust tag = tag[:-1] 8657842e56b97ce677b83bdab09cda48bc2d89ac75aJust ident = "" 8667842e56b97ce677b83bdab09cda48bc2d89ac75aJust for c in tag: 8677842e56b97ce677b83bdab09cda48bc2d89ac75aJust ident = ident + _escapechar(c) 8687842e56b97ce677b83bdab09cda48bc2d89ac75aJust if re.match("[0-9]", ident): 8697842e56b97ce677b83bdab09cda48bc2d89ac75aJust ident = "_" + ident 8707842e56b97ce677b83bdab09cda48bc2d89ac75aJust return ident 8717842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8727842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8738307fa42cd145bb1b961ff942bbe3eef63908e55jvrdef identifierToTag(ident): 8748307fa42cd145bb1b961ff942bbe3eef63908e55jvr """the opposite of tagToIdentifier()""" 875e5ae28e8424192ce7ea19897473601f590dc80eajvr if ident == "GlyphOrder": 876e5ae28e8424192ce7ea19897473601f590dc80eajvr return ident 8777842e56b97ce677b83bdab09cda48bc2d89ac75aJust if len(ident) % 2 and ident[0] == "_": 8787842e56b97ce677b83bdab09cda48bc2d89ac75aJust ident = ident[1:] 8797842e56b97ce677b83bdab09cda48bc2d89ac75aJust assert not (len(ident) % 2) 8807842e56b97ce677b83bdab09cda48bc2d89ac75aJust tag = "" 8817842e56b97ce677b83bdab09cda48bc2d89ac75aJust for i in range(0, len(ident), 2): 8827842e56b97ce677b83bdab09cda48bc2d89ac75aJust if ident[i] == "_": 8837842e56b97ce677b83bdab09cda48bc2d89ac75aJust tag = tag + ident[i+1] 8847842e56b97ce677b83bdab09cda48bc2d89ac75aJust elif ident[i+1] == "_": 8857842e56b97ce677b83bdab09cda48bc2d89ac75aJust tag = tag + ident[i] 8867842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 8877842e56b97ce677b83bdab09cda48bc2d89ac75aJust # assume hex 8884bb028e44f6fc320a5935e80730002ca54d15ec3Behdad Esfahbod tag = tag + chr(int(ident[i:i+2], 16)) 8897842e56b97ce677b83bdab09cda48bc2d89ac75aJust # append trailing spaces 8907842e56b97ce677b83bdab09cda48bc2d89ac75aJust tag = tag + (4 - len(tag)) * ' ' 891d2f5d2f8b4bce13ff7811cbf4c1ede81b73b8192Behdad Esfahbod return Tag(tag) 8927842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8937842e56b97ce677b83bdab09cda48bc2d89ac75aJust 8948307fa42cd145bb1b961ff942bbe3eef63908e55jvrdef tagToXML(tag): 8958307fa42cd145bb1b961ff942bbe3eef63908e55jvr """Similarly to tagToIdentifier(), this converts a TT tag 8967842e56b97ce677b83bdab09cda48bc2d89ac75aJust to a valid XML element name. Since XML element names are 8977842e56b97ce677b83bdab09cda48bc2d89ac75aJust case sensitive, this is a fairly simple/readable translation. 8987842e56b97ce677b83bdab09cda48bc2d89ac75aJust """ 8997dcc91674e8df02f80711ee2587fd2a6a205ca94Just import re 900d2f5d2f8b4bce13ff7811cbf4c1ede81b73b8192Behdad Esfahbod tag = Tag(tag) 9017842e56b97ce677b83bdab09cda48bc2d89ac75aJust if tag == "OS/2": 9027842e56b97ce677b83bdab09cda48bc2d89ac75aJust return "OS_2" 9030011bb691018c7feed1f8c3554d88b9ae096e127jvr elif tag == "GlyphOrder": 90428ae1962292b66ad67117aef2a99d5735a70b779jvr return tag 9057842e56b97ce677b83bdab09cda48bc2d89ac75aJust if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag): 90614fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod return tag.strip() 9077842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 9088307fa42cd145bb1b961ff942bbe3eef63908e55jvr return tagToIdentifier(tag) 9097842e56b97ce677b83bdab09cda48bc2d89ac75aJust 9107842e56b97ce677b83bdab09cda48bc2d89ac75aJust 9118307fa42cd145bb1b961ff942bbe3eef63908e55jvrdef xmlToTag(tag): 9128307fa42cd145bb1b961ff942bbe3eef63908e55jvr """The opposite of tagToXML()""" 9137842e56b97ce677b83bdab09cda48bc2d89ac75aJust if tag == "OS_2": 914153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod return Tag("OS/2") 9157842e56b97ce677b83bdab09cda48bc2d89ac75aJust if len(tag) == 8: 9168307fa42cd145bb1b961ff942bbe3eef63908e55jvr return identifierToTag(tag) 9177842e56b97ce677b83bdab09cda48bc2d89ac75aJust else: 918153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod return Tag(tag + " " * (4 - len(tag))) 9197842e56b97ce677b83bdab09cda48bc2d89ac75aJust 9207842e56b97ce677b83bdab09cda48bc2d89ac75aJust 9217842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef debugmsg(msg): 9227842e56b97ce677b83bdab09cda48bc2d89ac75aJust import time 9233ec6a258238b6068e4eef3fe579f1f5c0a06bbbaBehdad Esfahbod print(msg + time.strftime(" (%H:%M:%S)", time.localtime(time.time()))) 9247842e56b97ce677b83bdab09cda48bc2d89ac75aJust 925700df031313741e79b0647f045949a1a04d03371jvr 92628ae1962292b66ad67117aef2a99d5735a70b779jvr# Table order as recommended in the OpenType specification 1.4 92728ae1962292b66ad67117aef2a99d5735a70b779jvrTTFTableOrder = ["head", "hhea", "maxp", "OS/2", "hmtx", "LTSH", "VDMX", 92828ae1962292b66ad67117aef2a99d5735a70b779jvr "hdmx", "cmap", "fpgm", "prep", "cvt ", "loca", "glyf", 92928ae1962292b66ad67117aef2a99d5735a70b779jvr "kern", "name", "post", "gasp", "PCLT"] 930700df031313741e79b0647f045949a1a04d03371jvr 93128ae1962292b66ad67117aef2a99d5735a70b779jvrOTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post", 93228ae1962292b66ad67117aef2a99d5735a70b779jvr "CFF "] 933700df031313741e79b0647f045949a1a04d03371jvr 93428ae1962292b66ad67117aef2a99d5735a70b779jvrdef sortedTagList(tagList, tableOrder=None): 93528ae1962292b66ad67117aef2a99d5735a70b779jvr """Return a sorted copy of tagList, sorted according to the OpenType 93628ae1962292b66ad67117aef2a99d5735a70b779jvr specification, or according to a custom tableOrder. If given and not 93728ae1962292b66ad67117aef2a99d5735a70b779jvr None, tableOrder needs to be a list of tag names. 93828ae1962292b66ad67117aef2a99d5735a70b779jvr """ 939ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod tagList = sorted(tagList) 94028ae1962292b66ad67117aef2a99d5735a70b779jvr if tableOrder is None: 94128ae1962292b66ad67117aef2a99d5735a70b779jvr if "DSIG" in tagList: 94228ae1962292b66ad67117aef2a99d5735a70b779jvr # DSIG should be last (XXX spec reference?) 94328ae1962292b66ad67117aef2a99d5735a70b779jvr tagList.remove("DSIG") 94428ae1962292b66ad67117aef2a99d5735a70b779jvr tagList.append("DSIG") 94528ae1962292b66ad67117aef2a99d5735a70b779jvr if "CFF " in tagList: 94628ae1962292b66ad67117aef2a99d5735a70b779jvr tableOrder = OTFTableOrder 94728ae1962292b66ad67117aef2a99d5735a70b779jvr else: 94828ae1962292b66ad67117aef2a99d5735a70b779jvr tableOrder = TTFTableOrder 94928ae1962292b66ad67117aef2a99d5735a70b779jvr orderedTables = [] 95028ae1962292b66ad67117aef2a99d5735a70b779jvr for tag in tableOrder: 95128ae1962292b66ad67117aef2a99d5735a70b779jvr if tag in tagList: 95228ae1962292b66ad67117aef2a99d5735a70b779jvr orderedTables.append(tag) 95328ae1962292b66ad67117aef2a99d5735a70b779jvr tagList.remove(tag) 95428ae1962292b66ad67117aef2a99d5735a70b779jvr orderedTables.extend(tagList) 95528ae1962292b66ad67117aef2a99d5735a70b779jvr return orderedTables 95628ae1962292b66ad67117aef2a99d5735a70b779jvr 95728ae1962292b66ad67117aef2a99d5735a70b779jvr 958dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahboddef reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=False): 95928ae1962292b66ad67117aef2a99d5735a70b779jvr """Rewrite a font file, ordering the tables as recommended by the 96028ae1962292b66ad67117aef2a99d5735a70b779jvr OpenType specification 1.4. 96128ae1962292b66ad67117aef2a99d5735a70b779jvr """ 96228ae1962292b66ad67117aef2a99d5735a70b779jvr from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter 96328ae1962292b66ad67117aef2a99d5735a70b779jvr reader = SFNTReader(inFile, checkChecksums=checkChecksums) 964d7921e33d9d1944c394e6c17b3746e7108dd1da4Roozbeh Pournader writer = SFNTWriter(outFile, len(reader.tables), reader.sfntVersion, reader.flavor, reader.flavorData) 965c2297cd41d6c00b95f857b65bc9fd4b57559ac5eBehdad Esfahbod tables = list(reader.keys()) 96628ae1962292b66ad67117aef2a99d5735a70b779jvr for tag in sortedTagList(tables, tableOrder): 96728ae1962292b66ad67117aef2a99d5735a70b779jvr writer[tag] = reader[tag] 96828ae1962292b66ad67117aef2a99d5735a70b779jvr writer.close() 96962dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod 97062dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod 97162dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahboddef maxPowerOfTwo(x): 97262dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod """Return the highest exponent of two, so that 97362dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod (2 ** exponent) <= x. Return 0 if x is 0. 97462dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod """ 97562dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod exponent = 0 97662dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod while x: 97762dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod x = x >> 1 97862dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod exponent = exponent + 1 97962dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod return max(exponent - 1, 0) 98062dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod 98162dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod 98262dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahboddef getSearchRange(n, itemSize): 98362dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod """Calculate searchRange, entrySelector, rangeShift. 98462dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod """ 98562dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod # This stuff needs to be stored in the file, because? 98662dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod exponent = maxPowerOfTwo(n) 98762dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod searchRange = (2 ** exponent) * itemSize 98862dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod entrySelector = exponent 98962dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod rangeShift = max(0, n * itemSize - searchRange) 99062dd7b2a0e0ab1109b56572c568ef5f582d8a0fdBehdad Esfahbod return searchRange, entrySelector, rangeShift 991