otConverters.py revision ee27eb8517ee53594a6232d110e17403a37ff2d9
1from types import TupleType
2from fontTools.misc.textTools import safeEval
3
4
5def buildConverters(tableSpec, tableNamespace):
6	"""Given a table spec from otData.py, build a converter object for each
7	field of the table. This is called for each table in otData.py, and
8	the results are assigned to the corresponding class in otTables.py."""
9	converters = []
10	convertersByName = {}
11	for tp, name, repeat, repeatOffset, descr in tableSpec:
12		if name.startswith("ValueFormat"):
13			assert tp == "uint16"
14			converterClass = ValueFormat
15		elif name.endswith("Count"):
16			assert tp == "uint16"
17			converterClass = Count
18		elif name == "SubTable":
19			converterClass = SubTable
20		elif name == "ExtSubTable":
21			converterClass = ExtSubTable
22		else:
23			converterClass = converterMapping[tp]
24		tableClass = tableNamespace.get(name)
25		conv = converterClass(name, repeat, repeatOffset, tableClass)
26		if name in ["SubTable", "ExtSubTable"]:
27			conv.lookupTypes = tableNamespace['lookupTypes']
28			# also create reverse mapping
29			for t in conv.lookupTypes.values():
30				for cls in t.values():
31					convertersByName[cls.__name__] = Table(name, repeat, repeatOffset, cls)
32		converters.append(conv)
33		assert not convertersByName.has_key(name)
34		convertersByName[name] = conv
35	return converters, convertersByName
36
37
38class BaseConverter:
39
40	"""Base class for converter objects. Apart from the constructor, this
41	is an abstract class."""
42
43	def __init__(self, name, repeat, repeatOffset, tableClass):
44		self.name = name
45		self.repeat = repeat
46		self.repeatOffset = repeatOffset
47		self.tableClass = tableClass
48		self.isCount = name.endswith("Count")
49		self.isPropagatedCount = name in ["ClassCount", "Class2Count"]
50
51	def read(self, reader, font, tableDict):
52		"""Read a value from the reader."""
53		raise NotImplementedError, self
54
55	def write(self, writer, font, tableDict, value, repeatIndex=None):
56		"""Write a value to the writer."""
57		raise NotImplementedError, self
58
59	def xmlRead(self, attrs, content, font):
60		"""Read a value from XML."""
61		raise NotImplementedError, self
62
63	def xmlWrite(self, xmlWriter, font, value, name, attrs):
64		"""Write a value to XML."""
65		raise NotImplementedError, self
66
67
68class SimpleValue(BaseConverter):
69	def xmlWrite(self, xmlWriter, font, value, name, attrs):
70		xmlWriter.simpletag(name, attrs + [("value", value)])
71		xmlWriter.newline()
72	def xmlRead(self, attrs, content, font):
73		return attrs["value"]
74
75class IntValue(SimpleValue):
76	def xmlRead(self, attrs, content, font):
77		return int(attrs["value"], 0)
78
79class Long(IntValue):
80	def read(self, reader, font, tableDict):
81		return reader.readLong()
82	def write(self, writer, font, tableDict, value, repeatIndex=None):
83		writer.writeLong(value)
84
85class Version(BaseConverter):
86	def read(self, reader, font, tableDict):
87		value = reader.readLong()
88		assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
89		return float(value) / 0x10000
90	def write(self, writer, font, tableDict, value, repeatIndex=None):
91		if value < 0x10000:
92			value *= 0x10000
93		value = int(round(value))
94		assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
95		writer.writeLong(value)
96	def xmlRead(self, attrs, content, font):
97		value = attrs["value"]
98		value = float(int(value, 0)) if value.startswith("0") else float(value)
99		if value >= 0x10000:
100			value = float(value) / 0x10000
101		return value
102	def xmlWrite(self, xmlWriter, font, value, name, attrs):
103		if value >= 0x10000:
104			value = float(value) / 0x10000
105		if value % 1 != 0:
106			# Write as hex
107			value = "0x%08x" % (int(round(value * 0x10000)))
108		xmlWriter.simpletag(name, attrs + [("value", value)])
109		xmlWriter.newline()
110
111class Short(IntValue):
112	def read(self, reader, font, tableDict):
113		return reader.readShort()
114	def write(self, writer, font, tableDict, value, repeatIndex=None):
115		writer.writeShort(value)
116
117class UShort(IntValue):
118	def read(self, reader, font, tableDict):
119		return reader.readUShort()
120	def write(self, writer, font, tableDict, value, repeatIndex=None):
121		writer.writeUShort(value)
122
123class Count(Short):
124	def xmlWrite(self, xmlWriter, font, value, name, attrs):
125		xmlWriter.comment("%s=%s" % (name, value))
126		xmlWriter.newline()
127
128class Tag(SimpleValue):
129	def read(self, reader, font, tableDict):
130		return reader.readTag()
131	def write(self, writer, font, tableDict, value, repeatIndex=None):
132		writer.writeTag(value)
133
134class GlyphID(SimpleValue):
135	def read(self, reader, font, tableDict):
136		value = reader.readUShort()
137		value =  font.getGlyphName(value)
138		return value
139
140	def write(self, writer, font, tableDict, value, repeatIndex=None):
141		value =  font.getGlyphID(value)
142		writer.writeUShort(value)
143
144
145class Struct(BaseConverter):
146
147	def read(self, reader, font, tableDict):
148		table = self.tableClass()
149		table.decompile(reader, font)
150		return table
151
152	def write(self, writer, font, tableDict, value, repeatIndex=None):
153		value.compile(writer, font)
154
155	def xmlWrite(self, xmlWriter, font, value, name, attrs):
156		if value is None:
157			pass  # NULL table, ignore
158		else:
159			value.toXML(xmlWriter, font, attrs)
160
161	def xmlRead(self, attrs, content, font):
162		table = self.tableClass()
163		Format = attrs.get("Format")
164		if Format is not None:
165			table.Format = int(Format)
166		for element in content:
167			if type(element) == TupleType:
168				name, attrs, content = element
169				table.fromXML((name, attrs, content), font)
170			else:
171				pass
172		return table
173
174
175class Table(Struct):
176
177	def read(self, reader, font, tableDict, lazy=True):
178		offset = reader.readUShort()
179		if offset == 0:
180			return None
181		if offset <= 3:
182			# XXX hack to work around buggy pala.ttf
183			print "*** Warning: offset is not 0, yet suspiciously low (%s). table: %s" \
184					% (offset, self.tableClass.__name__)
185			return None
186		subReader = reader.getSubReader(offset, persistent=lazy)
187		table = self.tableClass()
188		if lazy:
189			table.reader = subReader
190			table.font = font
191			table.compileStatus = 1
192		else:
193			table.decompile(subReader, font)
194		return table
195
196	def write(self, writer, font, tableDict, value, repeatIndex=None):
197		if value is None:
198			writer.writeUShort(0)
199		else:
200			subWriter = writer.getSubWriter()
201			subWriter.name = self.name
202			if repeatIndex is not None:
203				subWriter.repeatIndex = repeatIndex
204			value.preCompile()
205			writer.writeSubTable(subWriter)
206			value.compile(subWriter, font)
207
208class SubTable(Table):
209	def getConverter(self, tableType, lookupType):
210		lookupTypes = self.lookupTypes[tableType]
211		tableClass = lookupTypes[lookupType]
212		return SubTable(self.name, self.repeat, self.repeatOffset, tableClass)
213
214
215class ExtSubTable(Table):
216	def getConverter(self, tableType, lookupType):
217		lookupTypes = self.lookupTypes[tableType]
218		tableClass = lookupTypes[lookupType]
219		return ExtSubTable(self.name, self.repeat, self.repeatOffset, tableClass)
220
221	def read(self, reader, font, tableDict, lazy=True):
222		offset = reader.readULong()
223		if offset == 0:
224			return None
225		subReader = reader.getSubReader(offset)
226		table = self.tableClass()
227		table.start = subReader.offset
228		if lazy:
229			table.reader = subReader
230			table.font = font
231			table.compileStatus = 1
232		else:
233			table.decompile(subReader, font)
234		return table
235
236	def write(self, writer, font, tableDict, value, repeatIndex=None):
237		writer.Extension = 1 # actually, mere presence of the field flags it as an Ext Subtable writer.
238		if value is None:
239			writer.writeULong(0)
240		else:
241			subWriter = writer.getSubWriter()
242			subWriter.name = self.name
243			writer.writeSubTable(subWriter)
244			# If the subtable has been sorted and we can just write the original
245			# data, then do so.
246			if value.compileStatus == 3:
247				data = value.reader.data[value.start:value.end]
248				subWriter.writeData(data)
249			else:
250				value.compile(subWriter, font)
251
252
253class ValueFormat(IntValue):
254	def __init__(self, name, repeat, repeatOffset, tableClass):
255		BaseConverter.__init__(self, name, repeat, repeatOffset, tableClass)
256		self.which = name[-1] == "2"
257	def read(self, reader, font, tableDict):
258		format = reader.readUShort()
259		reader.setValueFormat(format, self.which)
260		return format
261	def write(self, writer, font, tableDict, format, repeatIndex=None):
262		writer.writeUShort(format)
263		writer.setValueFormat(format, self.which)
264
265
266class ValueRecord(ValueFormat):
267	def read(self, reader, font, tableDict):
268		return reader.readValueRecord(font, self.which)
269	def write(self, writer, font, tableDict, value, repeatIndex=None):
270		writer.writeValueRecord(value, font, self.which)
271	def xmlWrite(self, xmlWriter, font, value, name, attrs):
272		if value is None:
273			pass  # NULL table, ignore
274		else:
275			value.toXML(xmlWriter, font, self.name, attrs)
276	def xmlRead(self, attrs, content, font):
277		from otBase import ValueRecord
278		value = ValueRecord()
279		value.fromXML((None, attrs, content), font)
280		return value
281
282
283class DeltaValue(BaseConverter):
284
285	def read(self, reader, font, tableDict):
286		StartSize = tableDict["StartSize"]
287		EndSize = tableDict["EndSize"]
288		DeltaFormat = tableDict["DeltaFormat"]
289		assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
290		nItems = EndSize - StartSize + 1
291		nBits = 1 << DeltaFormat
292		minusOffset = 1 << nBits
293		mask = (1 << nBits) - 1
294		signMask = 1 << (nBits - 1)
295
296		DeltaValue = []
297		tmp, shift = 0, 0
298		for i in range(nItems):
299			if shift == 0:
300				tmp, shift = reader.readUShort(), 16
301			shift = shift - nBits
302			value = (tmp >> shift) & mask
303			if value & signMask:
304				value = value - minusOffset
305			DeltaValue.append(value)
306		return DeltaValue
307
308	def write(self, writer, font, tableDict, value, repeatIndex=None):
309		StartSize = tableDict["StartSize"]
310		EndSize = tableDict["EndSize"]
311		DeltaFormat = tableDict["DeltaFormat"]
312		DeltaValue = value
313		assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
314		nItems = EndSize - StartSize + 1
315		nBits = 1 << DeltaFormat
316		assert len(DeltaValue) == nItems
317		mask = (1 << nBits) - 1
318
319		tmp, shift = 0, 16
320		for value in DeltaValue:
321			shift = shift - nBits
322			tmp = tmp | ((value & mask) << shift)
323			if shift == 0:
324				writer.writeUShort(tmp)
325				tmp, shift = 0, 16
326		if shift <> 16:
327			writer.writeUShort(tmp)
328
329	def xmlWrite(self, xmlWriter, font, value, name, attrs):
330		# XXX this could do with a nicer format
331		xmlWriter.simpletag(name, attrs + [("value", value)])
332		xmlWriter.newline()
333
334	def xmlRead(self, attrs, content, font):
335		return safeEval(attrs["value"])
336
337
338converterMapping = {
339	# type         class
340	"int16":       Short,
341	"uint16":      UShort,
342	"Version":     Version,
343	"Tag":         Tag,
344	"GlyphID":     GlyphID,
345	"struct":      Struct,
346	"Offset":      Table,
347	"LOffset":     ExtSubTable,
348	"ValueRecord": ValueRecord,
349	"DeltaValue":  DeltaValue,
350}
351
352