C_O_L_R_.py revision 2b06aaa2a6bcd363c25fb0c43f6bb906906594bd
1# Copyright 2013 Google, Inc. All Rights Reserved.
2#
3# Google Author(s): Behdad Esfahbod
4
5import operator
6from . import DefaultTable
7import struct
8from fontTools.ttLib import sfnt
9from fontTools.misc.textTools import safeEval, readHex
10from types import IntType, StringType
11
12
13class table_C_O_L_R_(DefaultTable.DefaultTable):
14
15	""" This table is structured so that you can treat it like a dictionary keyed by glyph name.
16	ttFont['COLR'][<glyphName>] will return the color layers for any glyph
17	ttFont['COLR'][<glyphName>] = <value> will set the color layers for any glyph.
18	"""
19
20	def decompile(self, data, ttFont):
21		self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
22		self.version, numBaseGlyphRecords, offsetBaseGlyphRecord, offsetLayerRecord, numLayerRecords = struct.unpack(">HHLLH", data[:14])
23		assert (self.version == 0), "Version of COLR table is higher than I know how to handle"
24		glyphOrder = ttFont.getGlyphOrder()
25		gids = []
26		layerLists = []
27		glyphPos = offsetBaseGlyphRecord
28		for i in range(numBaseGlyphRecords):
29			gid, firstLayerIndex, numLayers = struct.unpack(">HHH", data[glyphPos:glyphPos+6])
30			glyphPos += 6
31			gids.append(gid)
32			assert (firstLayerIndex + numLayers <= numLayerRecords)
33			layerPos = offsetLayerRecord + firstLayerIndex * 4
34			layers = []
35			for j in range(numLayers):
36				layerGid, colorID = struct.unpack(">HH", data[layerPos:layerPos+4])
37				try:
38					layerName = glyphOrder[layerGid]
39				except IndexError:
40					layerName = self.getGlyphName(layerGid)
41				layerPos += 4
42				layers.append(LayerRecord(layerName, colorID))
43			layerLists.append(layers)
44
45		self.ColorLayers = colorLayerLists = {}
46		try:
47			names = map(operator.getitem, [glyphOrder]*numBaseGlyphRecords, gids)
48		except IndexError:
49			getGlyphName = self.getGlyphName
50			names = map(getGlyphName, gids )
51
52		map(operator.setitem, [colorLayerLists]*numBaseGlyphRecords, names, layerLists)
53
54
55	def compile(self, ttFont):
56		ordered = []
57		ttFont.getReverseGlyphMap(rebuild=1)
58		glyphNames = self.ColorLayers.keys()
59		for glyphName in glyphNames:
60			try:
61				gid = ttFont.getGlyphID(glyphName)
62			except:
63				assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
64			ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
65		ordered.sort()
66
67		glyphMap = []
68		layerMap = []
69		for (gid, glyphName, layers) in ordered:
70			glyphMap.append(struct.pack(">HHH", gid, len(layerMap), len(layers)))
71			for layer in layers:
72				layerMap.append(struct.pack(">HH", ttFont.getGlyphID(layer.name), layer.colorID))
73
74		dataList = [struct.pack(">HHLLH", self.version, len(glyphMap), 14, 14+6*len(glyphMap), len(layerMap))]
75		dataList.extend(glyphMap)
76		dataList.extend(layerMap)
77		data = "".join(dataList)
78		return data
79
80	def toXML(self, writer, ttFont):
81		writer.simpletag("version", value=self.version)
82		writer.newline()
83		ordered = []
84		glyphNames = self.ColorLayers.keys()
85		for glyphName in glyphNames:
86			try:
87				gid = ttFont.getGlyphID(glyphName)
88			except:
89				assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
90			ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
91		ordered.sort()
92		for entry in ordered:
93			writer.begintag("ColorGlyph", name=entry[1])
94			writer.newline()
95			for layer in entry[2]:
96				layer.toXML(writer, ttFont)
97			writer.endtag("ColorGlyph")
98			writer.newline()
99
100	def fromXML(self, (name, attrs, content), ttFont):
101		if not hasattr(self, "ColorLayers"):
102			self.ColorLayers = {}
103		self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
104		if name == "ColorGlyph":
105			glyphName = attrs["name"]
106			for element in content:
107				if isinstance(element, StringType):
108					continue
109			layers = []
110			for element in content:
111				if isinstance(element, StringType):
112					continue
113				layer = LayerRecord()
114				layer.fromXML(element, ttFont)
115				layers.append (layer)
116			operator.setitem(self, glyphName, layers)
117		elif "value" in attrs:
118			value =  safeEval(attrs["value"])
119			setattr(self, name, value)
120
121
122	def __getitem__(self, glyphSelector):
123		if type(glyphSelector) == IntType:
124			# its a gid, convert to glyph name
125			glyphSelector = self.getGlyphName(glyphSelector)
126
127		if glyphSelector not in self.ColorLayers:
128			return None
129
130		return self.ColorLayers[glyphSelector]
131
132	def __setitem__(self, glyphSelector, value):
133		if type(glyphSelector) == IntType:
134			# its a gid, convert to glyph name
135			glyphSelector = self.getGlyphName(glyphSelector)
136
137		if  value:
138			self.ColorLayers[glyphSelector] = value
139		elif glyphSelector in self.ColorLayers:
140			del self.ColorLayers[glyphSelector]
141
142class LayerRecord:
143
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				if type(value) == IntType:
156					value = ttFont.getGlyphName(value)
157				setattr(self, name, value)
158			else:
159				try:
160					value = safeEval(value)
161				except OverflowError:
162					value = long(value)
163				setattr(self, name, value)
164