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