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