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 13 ``ttFont['COLR'][<glyphName>]`` will return the color layers for any glyph. 14 15 ``ttFont['COLR'][<glyphName>] = <value>`` will set the color layers for any glyph. 16 """ 17 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( 34 LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex) 35 ) 36 colorLayerLists[baseGlyph] = layers 37 return colorLayerLists 38 39 def _toOTTable(self, ttFont): 40 from . import otTables 41 from fontTools.colorLib.builder import populateCOLRv0 42 43 tableClass = getattr(otTables, self.tableTag) 44 table = tableClass() 45 table.Version = self.version 46 47 populateCOLRv0( 48 table, 49 { 50 baseGlyph: [(layer.name, layer.colorID) for layer in layers] 51 for baseGlyph, layers in self.ColorLayers.items() 52 }, 53 glyphMap=ttFont.getReverseGlyphMap(rebuild=True), 54 ) 55 return table 56 57 def decompile(self, data, ttFont): 58 from .otBase import OTTableReader 59 from . import otTables 60 61 # We use otData to decompile, but we adapt the decompiled otTables to the 62 # existing COLR v0 API for backward compatibility. 63 reader = OTTableReader(data, tableTag=self.tableTag) 64 tableClass = getattr(otTables, self.tableTag) 65 table = tableClass() 66 table.decompile(reader, ttFont) 67 68 self.version = table.Version 69 if self.version == 0: 70 self.ColorLayers = self._decompileColorLayersV0(table) 71 else: 72 # for new versions, keep the raw otTables around 73 self.table = table 74 75 def compile(self, ttFont): 76 from .otBase import OTTableWriter 77 78 if hasattr(self, "table"): 79 table = self.table 80 else: 81 table = self._toOTTable(ttFont) 82 83 writer = OTTableWriter(tableTag=self.tableTag) 84 table.compile(writer, ttFont) 85 return writer.getAllData() 86 87 def toXML(self, writer, ttFont): 88 if hasattr(self, "table"): 89 self.table.toXML2(writer, ttFont) 90 else: 91 writer.simpletag("version", value=self.version) 92 writer.newline() 93 for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID): 94 writer.begintag("ColorGlyph", name=baseGlyph) 95 writer.newline() 96 for layer in self.ColorLayers[baseGlyph]: 97 layer.toXML(writer, ttFont) 98 writer.endtag("ColorGlyph") 99 writer.newline() 100 101 def fromXML(self, name, attrs, content, ttFont): 102 if name == "version": # old COLR v0 API 103 setattr(self, name, safeEval(attrs["value"])) 104 elif name == "ColorGlyph": 105 if not hasattr(self, "ColorLayers"): 106 self.ColorLayers = {} 107 glyphName = attrs["name"] 108 for element in content: 109 if isinstance(element, str): 110 continue 111 layers = [] 112 for element in content: 113 if isinstance(element, str): 114 continue 115 layer = LayerRecord() 116 layer.fromXML(element[0], element[1], element[2], ttFont) 117 layers.append (layer) 118 self.ColorLayers[glyphName] = layers 119 else: # new COLR v1 API 120 from . import otTables 121 122 if not hasattr(self, "table"): 123 tableClass = getattr(otTables, self.tableTag) 124 self.table = tableClass() 125 self.table.fromXML(name, attrs, content, ttFont) 126 self.table.populateDefaults() 127 self.version = self.table.Version 128 129 def __getitem__(self, glyphName): 130 if not isinstance(glyphName, str): 131 raise TypeError(f"expected str, found {type(glyphName).__name__}") 132 return self.ColorLayers[glyphName] 133 134 def __setitem__(self, glyphName, value): 135 if not isinstance(glyphName, str): 136 raise TypeError(f"expected str, found {type(glyphName).__name__}") 137 if value is not None: 138 self.ColorLayers[glyphName] = value 139 elif glyphName in self.ColorLayers: 140 del self.ColorLayers[glyphName] 141 142 def __delitem__(self, glyphName): 143 del self.ColorLayers[glyphName] 144 145class LayerRecord(object): 146 147 def __init__(self, name=None, colorID=None): 148 self.name = name 149 self.colorID = colorID 150 151 def toXML(self, writer, ttFont): 152 writer.simpletag("layer", name=self.name, colorID=self.colorID) 153 writer.newline() 154 155 def fromXML(self, eltname, attrs, content, ttFont): 156 for (name, value) in attrs.items(): 157 if name == "name": 158 setattr(self, name, value) 159 else: 160 setattr(self, name, safeEval(value)) 161