xref: /aosp_15_r20/external/fonttools/Lib/fontTools/ttLib/tables/C_O_L_R_.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod
4
5from fontTools.misc.textTools import safeEval
6from . import DefaultTable
7
8
9class table_C_O_L_R_(DefaultTable.DefaultTable):
10    """This table is structured so that you can treat it like a dictionary keyed by glyph name.
11
12    ``ttFont['COLR'][<glyphName>]`` will return the color layers for any glyph.
13
14    ``ttFont['COLR'][<glyphName>] = <value>`` will set the color layers for any glyph.
15    """
16
17    @staticmethod
18    def _decompileColorLayersV0(table):
19        if not table.LayerRecordArray:
20            return {}
21        colorLayerLists = {}
22        layerRecords = table.LayerRecordArray.LayerRecord
23        numLayerRecords = len(layerRecords)
24        for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
25            baseGlyph = baseRec.BaseGlyph
26            firstLayerIndex = baseRec.FirstLayerIndex
27            numLayers = baseRec.NumLayers
28            assert firstLayerIndex + numLayers <= numLayerRecords
29            layers = []
30            for i in range(firstLayerIndex, firstLayerIndex + numLayers):
31                layerRec = layerRecords[i]
32                layers.append(LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex))
33            colorLayerLists[baseGlyph] = layers
34        return colorLayerLists
35
36    def _toOTTable(self, ttFont):
37        from . import otTables
38        from fontTools.colorLib.builder import populateCOLRv0
39
40        tableClass = getattr(otTables, self.tableTag)
41        table = tableClass()
42        table.Version = self.version
43
44        populateCOLRv0(
45            table,
46            {
47                baseGlyph: [(layer.name, layer.colorID) for layer in layers]
48                for baseGlyph, layers in self.ColorLayers.items()
49            },
50            glyphMap=ttFont.getReverseGlyphMap(rebuild=True),
51        )
52        return table
53
54    def decompile(self, data, ttFont):
55        from .otBase import OTTableReader
56        from . import otTables
57
58        # We use otData to decompile, but we adapt the decompiled otTables to the
59        # existing COLR v0 API for backward compatibility.
60        reader = OTTableReader(data, tableTag=self.tableTag)
61        tableClass = getattr(otTables, self.tableTag)
62        table = tableClass()
63        table.decompile(reader, ttFont)
64
65        self.version = table.Version
66        if self.version == 0:
67            self.ColorLayers = self._decompileColorLayersV0(table)
68        else:
69            # for new versions, keep the raw otTables around
70            self.table = table
71
72    def compile(self, ttFont):
73        from .otBase import OTTableWriter
74
75        if hasattr(self, "table"):
76            table = self.table
77        else:
78            table = self._toOTTable(ttFont)
79
80        writer = OTTableWriter(tableTag=self.tableTag)
81        table.compile(writer, ttFont)
82        return writer.getAllData()
83
84    def toXML(self, writer, ttFont):
85        if hasattr(self, "table"):
86            self.table.toXML2(writer, ttFont)
87        else:
88            writer.simpletag("version", value=self.version)
89            writer.newline()
90            for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID):
91                writer.begintag("ColorGlyph", name=baseGlyph)
92                writer.newline()
93                for layer in self.ColorLayers[baseGlyph]:
94                    layer.toXML(writer, ttFont)
95                writer.endtag("ColorGlyph")
96                writer.newline()
97
98    def fromXML(self, name, attrs, content, ttFont):
99        if name == "version":  # old COLR v0 API
100            setattr(self, name, safeEval(attrs["value"]))
101        elif name == "ColorGlyph":
102            if not hasattr(self, "ColorLayers"):
103                self.ColorLayers = {}
104            glyphName = attrs["name"]
105            for element in content:
106                if isinstance(element, str):
107                    continue
108            layers = []
109            for element in content:
110                if isinstance(element, str):
111                    continue
112                layer = LayerRecord()
113                layer.fromXML(element[0], element[1], element[2], ttFont)
114                layers.append(layer)
115            self.ColorLayers[glyphName] = layers
116        else:  # new COLR v1 API
117            from . import otTables
118
119            if not hasattr(self, "table"):
120                tableClass = getattr(otTables, self.tableTag)
121                self.table = tableClass()
122            self.table.fromXML(name, attrs, content, ttFont)
123            self.table.populateDefaults()
124            self.version = self.table.Version
125
126    def __getitem__(self, glyphName):
127        if not isinstance(glyphName, str):
128            raise TypeError(f"expected str, found {type(glyphName).__name__}")
129        return self.ColorLayers[glyphName]
130
131    def __setitem__(self, glyphName, value):
132        if not isinstance(glyphName, str):
133            raise TypeError(f"expected str, found {type(glyphName).__name__}")
134        if value is not None:
135            self.ColorLayers[glyphName] = value
136        elif glyphName in self.ColorLayers:
137            del self.ColorLayers[glyphName]
138
139    def __delitem__(self, glyphName):
140        del self.ColorLayers[glyphName]
141
142
143class LayerRecord(object):
144    def __init__(self, name=None, colorID=None):
145        self.name = name
146        self.colorID = colorID
147
148    def toXML(self, writer, ttFont):
149        writer.simpletag("layer", name=self.name, colorID=self.colorID)
150        writer.newline()
151
152    def fromXML(self, eltname, attrs, content, ttFont):
153        for name, value in attrs.items():
154            if name == "name":
155                setattr(self, name, value)
156            else:
157                setattr(self, name, safeEval(value))
158