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