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