otConverters.py revision d58c38dc3660f764b659ec13fcbc6e54c1ec2078
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(object):
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	longOffset = False
178
179	def readOffset(self, reader):
180		return reader.readUShort()
181
182	def writeNullOffset(self, writer):
183		if hasattr(self, "Extension"):
184			writer.writeULong(0)
185		else:
186			writer.writeUShort(0)
187
188	def read(self, reader, font, tableDict):
189		offset = self.readOffset(reader)
190		if offset == 0:
191			return None
192		if offset <= 3:
193			# XXX hack to work around buggy pala.ttf
194			print "*** Warning: offset is not 0, yet suspiciously low (%s). table: %s" \
195					% (offset, self.tableClass.__name__)
196			return None
197		table = self.tableClass()
198		table.reader = reader
199		table.offset = offset
200		table.font = font
201		table.compileStatus = 1
202		if not font.lazy:
203			table.ensureDecompiled()
204		return table
205
206	def write(self, writer, font, tableDict, value, repeatIndex=None):
207		writer.longOffset = self.longOffset
208		if value is None:
209			self.writeNullOffset(writer)
210		else:
211			subWriter = writer.getSubWriter()
212			subWriter.name = self.name
213			if repeatIndex is not None:
214				subWriter.repeatIndex = repeatIndex
215			writer.writeSubTable(subWriter)
216			value.compile(subWriter, font)
217
218class LTable(Table):
219
220	longOffset = True
221
222	def readOffset(self, reader):
223		return reader.readULong()
224
225
226class SubTable(Table):
227	def getConverter(self, tableType, lookupType):
228		tableClass = self.lookupTypes[tableType][lookupType]
229		return self.__class__(self.name, self.repeat, self.repeatOffset, tableClass)
230
231
232class ExtSubTable(LTable, SubTable):
233
234	def write(self, writer, font, tableDict, value, repeatIndex=None):
235		writer.Extension = 1 # actually, mere presence of the field flags it as an Ext Subtable writer.
236		Table.write(self, writer, font, tableDict, value, repeatIndex)
237
238
239class ValueFormat(IntValue):
240	def __init__(self, name, repeat, repeatOffset, tableClass):
241		BaseConverter.__init__(self, name, repeat, repeatOffset, tableClass)
242		self.which = name[-1] == "2"
243	def read(self, reader, font, tableDict):
244		format = reader.readUShort()
245		reader.setValueFormat(format, self.which)
246		return format
247	def write(self, writer, font, tableDict, format, repeatIndex=None):
248		writer.writeUShort(format)
249		writer.setValueFormat(format, self.which)
250
251
252class ValueRecord(ValueFormat):
253	def read(self, reader, font, tableDict):
254		return reader.readValueRecord(font, self.which)
255	def write(self, writer, font, tableDict, value, repeatIndex=None):
256		writer.writeValueRecord(value, font, self.which)
257	def xmlWrite(self, xmlWriter, font, value, name, attrs):
258		if value is None:
259			pass  # NULL table, ignore
260		else:
261			value.toXML(xmlWriter, font, self.name, attrs)
262	def xmlRead(self, attrs, content, font):
263		from otBase import ValueRecord
264		value = ValueRecord()
265		value.fromXML((None, attrs, content), font)
266		return value
267
268
269class DeltaValue(BaseConverter):
270
271	def read(self, reader, font, tableDict):
272		StartSize = tableDict["StartSize"]
273		EndSize = tableDict["EndSize"]
274		DeltaFormat = tableDict["DeltaFormat"]
275		assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
276		nItems = EndSize - StartSize + 1
277		nBits = 1 << DeltaFormat
278		minusOffset = 1 << nBits
279		mask = (1 << nBits) - 1
280		signMask = 1 << (nBits - 1)
281
282		DeltaValue = []
283		tmp, shift = 0, 0
284		for i in range(nItems):
285			if shift == 0:
286				tmp, shift = reader.readUShort(), 16
287			shift = shift - nBits
288			value = (tmp >> shift) & mask
289			if value & signMask:
290				value = value - minusOffset
291			DeltaValue.append(value)
292		return DeltaValue
293
294	def write(self, writer, font, tableDict, value, repeatIndex=None):
295		StartSize = tableDict["StartSize"]
296		EndSize = tableDict["EndSize"]
297		DeltaFormat = tableDict["DeltaFormat"]
298		DeltaValue = value
299		assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
300		nItems = EndSize - StartSize + 1
301		nBits = 1 << DeltaFormat
302		assert len(DeltaValue) == nItems
303		mask = (1 << nBits) - 1
304
305		tmp, shift = 0, 16
306		for value in DeltaValue:
307			shift = shift - nBits
308			tmp = tmp | ((value & mask) << shift)
309			if shift == 0:
310				writer.writeUShort(tmp)
311				tmp, shift = 0, 16
312		if shift <> 16:
313			writer.writeUShort(tmp)
314
315	def xmlWrite(self, xmlWriter, font, value, name, attrs):
316		# XXX this could do with a nicer format
317		xmlWriter.simpletag(name, attrs + [("value", value)])
318		xmlWriter.newline()
319
320	def xmlRead(self, attrs, content, font):
321		return safeEval(attrs["value"])
322
323
324converterMapping = {
325	# type         class
326	"int16":       Short,
327	"uint16":      UShort,
328	"Version":     Version,
329	"Tag":         Tag,
330	"GlyphID":     GlyphID,
331	"struct":      Struct,
332	"Offset":      Table,
333	"LOffset":     LTable,
334	"ValueRecord": ValueRecord,
335	"DeltaValue":  DeltaValue,
336}
337
338