1from fontTools.ttLib.ttFont import TTFont 2from fontTools.ttLib.sfnt import readTTCHeader, writeTTCHeader 3from io import BytesIO 4import struct 5import logging 6 7log = logging.getLogger(__name__) 8 9 10class TTCollection(object): 11 """Object representing a TrueType Collection / OpenType Collection. 12 The main API is self.fonts being a list of TTFont instances. 13 14 If shareTables is True, then different fonts in the collection 15 might point to the same table object if the data for the table was 16 the same in the font file. Note, however, that this might result 17 in suprises and incorrect behavior if the different fonts involved 18 have different GlyphOrder. Use only if you know what you are doing. 19 """ 20 21 def __init__(self, file=None, shareTables=False, **kwargs): 22 fonts = self.fonts = [] 23 if file is None: 24 return 25 26 assert "fontNumber" not in kwargs, kwargs 27 28 closeStream = False 29 if not hasattr(file, "read"): 30 file = open(file, "rb") 31 closeStream = True 32 33 tableCache = {} if shareTables else None 34 35 header = readTTCHeader(file) 36 for i in range(header.numFonts): 37 font = TTFont(file, fontNumber=i, _tableCache=tableCache, **kwargs) 38 fonts.append(font) 39 40 # don't close file if lazy=True, as the TTFont hold a reference to the original 41 # file; the file will be closed once the TTFonts are closed in the 42 # TTCollection.close(). We still want to close the file if lazy is None or 43 # False, because in that case the TTFont no longer need the original file 44 # and we want to avoid 'ResourceWarning: unclosed file'. 45 if not kwargs.get("lazy") and closeStream: 46 file.close() 47 48 def __enter__(self): 49 return self 50 51 def __exit__(self, type, value, traceback): 52 self.close() 53 54 def close(self): 55 for font in self.fonts: 56 font.close() 57 58 def save(self, file, shareTables=True): 59 """Save the font to disk. Similarly to the constructor, 60 the 'file' argument can be either a pathname or a writable 61 file object. 62 """ 63 if not hasattr(file, "write"): 64 final = None 65 file = open(file, "wb") 66 else: 67 # assume "file" is a writable file object 68 # write to a temporary stream to allow saving to unseekable streams 69 final = file 70 file = BytesIO() 71 72 tableCache = {} if shareTables else None 73 74 offsets_offset = writeTTCHeader(file, len(self.fonts)) 75 offsets = [] 76 for font in self.fonts: 77 offsets.append(file.tell()) 78 font._save(file, tableCache=tableCache) 79 file.seek(0, 2) 80 81 file.seek(offsets_offset) 82 file.write(struct.pack(">%dL" % len(self.fonts), *offsets)) 83 84 if final: 85 final.write(file.getvalue()) 86 file.close() 87 88 def saveXML(self, fileOrPath, newlinestr="\n", writeVersion=True, **kwargs): 89 from fontTools.misc import xmlWriter 90 91 writer = xmlWriter.XMLWriter(fileOrPath, newlinestr=newlinestr) 92 93 if writeVersion: 94 from fontTools import version 95 96 version = ".".join(version.split(".")[:2]) 97 writer.begintag("ttCollection", ttLibVersion=version) 98 else: 99 writer.begintag("ttCollection") 100 writer.newline() 101 writer.newline() 102 103 for font in self.fonts: 104 font._saveXML(writer, writeVersion=False, **kwargs) 105 writer.newline() 106 107 writer.endtag("ttCollection") 108 writer.newline() 109 110 writer.close() 111 112 def __getitem__(self, item): 113 return self.fonts[item] 114 115 def __setitem__(self, item, value): 116 self.fonts[item] = value 117 118 def __delitem__(self, item): 119 return self.fonts[item] 120 121 def __len__(self): 122 return len(self.fonts) 123 124 def __iter__(self): 125 return iter(self.fonts) 126