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

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(LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex)) 

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 

143 

144class LayerRecord(object): 

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))