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