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