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 11 """ This table is structured so that you can treat it like a dictionary keyed by glyph name. 12 ttFont['COLR'][<glyphName>] will return the color layers for any glyph 13 ttFont['COLR'][<glyphName>] = <value> will set the color layers for any glyph. 14 """ 15 16 @staticmethod 17 def _decompileColorLayersV0(table): 18 if not table.LayerRecordArray: 19 return {} 20 colorLayerLists = {} 21 layerRecords = table.LayerRecordArray.LayerRecord 22 numLayerRecords = len(layerRecords) 23 for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord: 24 baseGlyph = baseRec.BaseGlyph 25 firstLayerIndex = baseRec.FirstLayerIndex 26 numLayers = baseRec.NumLayers 27 assert (firstLayerIndex + numLayers <= numLayerRecords) 28 layers = [] 29 for i in range(firstLayerIndex, firstLayerIndex+numLayers): 30 layerRec = layerRecords[i] 31 layers.append( 32 LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex) 33 ) 34 colorLayerLists[baseGlyph] = layers 35 return colorLayerLists 36 37 def _toOTTable(self, ttFont): 38 from . import otTables 39 from fontTools.colorLib.builder import populateCOLRv0 40 41 tableClass = getattr(otTables, self.tableTag) 42 table = tableClass() 43 table.Version = self.version 44 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 54 55 def decompile(self, data, ttFont): 56 from .otBase import OTTableReader 57 from . import otTables 58 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) 65 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 72 73 def compile(self, ttFont): 74 from .otBase import OTTableWriter 75 76 if hasattr(self, "table"): 77 table = self.table 78 else: 79 table = self._toOTTable(ttFont) 80 81 writer = OTTableWriter(tableTag=self.tableTag) 82 table.compile(writer, ttFont) 83 return writer.getAllData() 84 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() 98 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 119 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 126 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] 131 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] 139 140 def __delitem__(self, glyphName): 141 del self.ColorLayers[glyphName] 142 143class LayerRecord(object): 144 145 def __init__(self, name=None, colorID=None): 146 self.name = name 147 self.colorID = colorID 148 149 def toXML(self, writer, ttFont): 150 writer.simpletag("layer", name=self.name, colorID=self.colorID) 151 writer.newline() 152 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)) 159