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