xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ttLib/ttCollection.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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