blob: d7a06b4eba99ae54ba228f643ec6fa75226855f7 [file] [log] [blame]
jvrd4d15132002-05-11 00:59:27 +00001from DefaultTable import DefaultTable
2import otData
3import struct
4from types import TupleType
5
6
7class BaseTTXConverter(DefaultTable):
8
jvr64b5c802002-05-11 10:21:36 +00009 """Generic base class for TTX table converters. Functions as an adapter
10 between the TTX (ttLib actually) table model and the model we use for
11 OpenType tables, which is neccesarily subtly different."""
12
jvrd4d15132002-05-11 00:59:27 +000013 def decompile(self, data, font):
14 import otTables
15 reader = OTTableReader(data, self.tableTag)
16 tableClass = getattr(otTables, self.tableTag)
17 self.table = tableClass()
18 self.table.decompile(reader, font)
19
20 def compile(self, font):
21 writer = OTTableWriter(self.tableTag)
22 self.table.compile(writer, font)
23 return writer.getData()
24
25 def toXML(self, writer, font):
26 self.table.toXML2(writer, font)
27
28 def fromXML(self, (name, attrs, content), font):
29 import otTables
30 if not hasattr(self, "table"):
31 tableClass = getattr(otTables, self.tableTag)
32 self.table = tableClass()
33 self.table.fromXML((name, attrs, content), font)
34
35
36class OTTableReader:
37
jvr64b5c802002-05-11 10:21:36 +000038 """Helper class to retrieve data from an OpenType table."""
39
jvrd4d15132002-05-11 00:59:27 +000040 def __init__(self, data, tableType, offset=0, valueFormat=None, cachingStats=None):
41 self.data = data
42 self.offset = offset
43 self.pos = offset
44 self.tableType = tableType
45 if valueFormat is None:
46 valueFormat = (ValueRecordFactory(), ValueRecordFactory())
47 self.valueFormat = valueFormat
48 self.cachingStats = cachingStats
49
50 def getSubReader(self, offset):
51 offset = self.offset + offset
52 if self.cachingStats is not None:
53 try:
54 self.cachingStats[offset] = self.cachingStats[offset] + 1
55 except KeyError:
56 self.cachingStats[offset] = 1
57
58 subReader = self.__class__(self.data, self.tableType, offset,
59 self.valueFormat, self.cachingStats)
60 return subReader
61
62 def readUShort(self):
63 pos = self.pos
64 newpos = pos + 2
jvre69caf82002-05-13 18:08:19 +000065 value, = struct.unpack(">H", self.data[pos:newpos])
jvrd4d15132002-05-11 00:59:27 +000066 self.pos = newpos
67 return value
68
69 def readShort(self):
70 pos = self.pos
71 newpos = pos + 2
jvre69caf82002-05-13 18:08:19 +000072 value, = struct.unpack(">h", self.data[pos:newpos])
jvrd4d15132002-05-11 00:59:27 +000073 self.pos = newpos
74 return value
75
76 def readLong(self):
77 pos = self.pos
78 newpos = pos + 4
jvre69caf82002-05-13 18:08:19 +000079 value, = struct.unpack(">l", self.data[pos:newpos])
jvrd4d15132002-05-11 00:59:27 +000080 self.pos = newpos
81 return value
82
83 def readTag(self):
84 pos = self.pos
85 newpos = pos + 4
86 value = self.data[pos:newpos]
87 assert len(value) == 4
88 self.pos = newpos
89 return value
90
91 def readStruct(self, format, size=None):
92 if size is None:
93 size = struct.calcsize(format)
94 else:
95 assert size == struct.calcsize(format)
96 pos = self.pos
97 newpos = pos + size
98 values = struct.unpack(format, self.data[pos:newpos])
99 self.pos = newpos
100 return values
101
102 def setValueFormat(self, format, which):
103 self.valueFormat[which].setFormat(format)
104
105 def readValueRecord(self, font, which):
106 return self.valueFormat[which].readValueRecord(self, font)
107
108
109class OTTableWriter:
110
jvr64b5c802002-05-11 10:21:36 +0000111 """Helper class to gather and assemble data for OpenType tables."""
112
jvrd4d15132002-05-11 00:59:27 +0000113 def __init__(self, tableType, valueFormat=None):
114 self.items = []
115 self.tableType = tableType
116 if valueFormat is None:
117 valueFormat = ValueRecordFactory(), ValueRecordFactory()
118 self.valueFormat = valueFormat
119
120 def getSubWriter(self):
121 return self.__class__(self.tableType, self.valueFormat)
122
123 def getData(self):
124 items = list(self.items)
125 offset = 0
126 for item in items:
127 if hasattr(item, "getData") or hasattr(item, "getCount"):
128 offset = offset + 2 # sizeof(UShort)
129 else:
130 offset = offset + len(item)
131 subTables = []
132 cache = {}
133 for i in range(len(items)):
134 item = items[i]
135 if hasattr(item, "getData"):
136 subTableData = item.getData()
137 if cache.has_key(subTableData):
138 items[i] = packUShort(cache[subTableData])
139 else:
140 items[i] = packUShort(offset)
141 subTables.append(subTableData)
142 cache[subTableData] = offset
143 offset = offset + len(subTableData)
144 elif hasattr(item, "getCount"):
145 items[i] = item.getCount()
146 return "".join(items + subTables)
147
148 def writeUShort(self, value):
149 assert 0 <= value < 0x10000
150 self.items.append(struct.pack(">H", value))
151
152 def writeShort(self, value):
153 self.items.append(struct.pack(">h", value))
154
155 def writeLong(self, value):
156 self.items.append(struct.pack(">l", value))
157
158 def writeTag(self, tag):
159 assert len(tag) == 4
160 self.items.append(tag)
161
162 def writeSubTable(self, subWriter):
163 self.items.append(subWriter)
164
165 def writeCountReference(self, table, name):
166 self.items.append(CountReference(table, name))
167
168 def writeStruct(self, format, values):
169 data = apply(struct.pack, (format,) + values)
170 self.items.append(data)
171
172 def setValueFormat(self, format, which):
173 self.valueFormat[which].setFormat(format)
174
175 def writeValueRecord(self, value, font, which):
176 return self.valueFormat[which].writeValueRecord(self, font, value)
177
178
179class CountReference:
jvr64b5c802002-05-11 10:21:36 +0000180 """A reference to a Count value, not a count of a reference."""
jvrd4d15132002-05-11 00:59:27 +0000181 def __init__(self, table, name):
182 self.table = table
183 self.name = name
184 def getCount(self):
185 return packUShort(self.table[self.name])
186
187
jvr64b5c802002-05-11 10:21:36 +0000188def packUShort(value):
189 assert 0 <= value < 0x10000
190 return struct.pack(">H", value)
jvrd4d15132002-05-11 00:59:27 +0000191
192
193
jvr64b5c802002-05-11 10:21:36 +0000194class TableStack:
195 """A stack of table dicts, working as a stack of namespaces so we can
196 retrieve values from (and store values to) tables higher up the stack."""
197 def __init__(self):
198 self.stack = []
199 def push(self, table):
200 self.stack.insert(0, table)
201 def pop(self):
202 self.stack.pop(0)
203 def getTop(self):
204 return self.stack[0]
205 def getValue(self, name):
206 return self.__findTable(name)[name]
207 def storeValue(self, name, value):
208 table = self.__findTable(name)
209 if table[name] is None:
210 table[name] = value
211 else:
212 assert table[name] == value, (table[name], value)
213 def __findTable(self, name):
214 for table in self.stack:
215 if table.has_key(name):
216 return table
217 raise KeyError, name
218
219
jvrd4d15132002-05-11 00:59:27 +0000220class BaseTable:
221
jvr64b5c802002-05-11 10:21:36 +0000222 """Generic base class for all OpenType (sub)tables."""
223
jvrd4d15132002-05-11 00:59:27 +0000224 def getConverters(self):
225 return self.converters
226
227 def getConverterByName(self, name):
228 return self.convertersByName[name]
229
230 def decompile(self, reader, font, tableStack=None):
231 if tableStack is None:
232 tableStack = TableStack()
233 table = {}
234 self.__rawTable = table # for debugging
235 tableStack.push(table)
236 for conv in self.getConverters():
237 if conv.name == "SubTable":
238 conv = conv.getConverter(reader.tableType,
239 table["LookupType"])
240 if conv.repeat:
241 l = []
242 for i in range(tableStack.getValue(conv.repeat) + conv.repeatOffset):
243 l.append(conv.read(reader, font, tableStack))
244 table[conv.name] = l
245 else:
246 table[conv.name] = conv.read(reader, font, tableStack)
247 tableStack.pop()
248 self.postRead(table, font)
249 del self.__rawTable # succeeded, get rid of debugging info
250
251 def compile(self, writer, font, tableStack=None):
252 if tableStack is None:
253 tableStack = TableStack()
254 table = self.preWrite(font)
255 tableStack.push(table)
256 for conv in self.getConverters():
257 value = table.get(conv.name)
258 if conv.repeat:
259 if value is None:
jvr64b5c802002-05-11 10:21:36 +0000260 value = []
jvrd4d15132002-05-11 00:59:27 +0000261 tableStack.storeValue(conv.repeat, len(value) - conv.repeatOffset)
262 for item in value:
263 conv.write(writer, font, tableStack, item)
264 elif conv.isCount:
265 # Special-case Count values.
266 # Assumption: a Count field will *always* precede
267 # the actual array.
268 # We need a default value, as it may be set later by a nested
269 # table. TableStack.storeValue() will then find it here.
270 table[conv.name] = None
271 # We add a reference: by the time the data is assembled
272 # the Count value will be filled in.
273 writer.writeCountReference(table, conv.name)
274 else:
275 conv.write(writer, font, tableStack, value)
276 tableStack.pop()
277
278 def postRead(self, table, font):
279 self.__dict__.update(table)
280
281 def preWrite(self, font):
282 return self.__dict__.copy()
283
284 def toXML(self, xmlWriter, font, attrs=None):
285 tableName = self.__class__.__name__
286 if attrs is None:
287 attrs = []
288 if hasattr(self, "Format"):
jvr64b5c802002-05-11 10:21:36 +0000289 attrs = attrs + [("Format", self.Format)]
jvrd4d15132002-05-11 00:59:27 +0000290 xmlWriter.begintag(tableName, attrs)
291 xmlWriter.newline()
292 self.toXML2(xmlWriter, font)
293 xmlWriter.endtag(tableName)
294 xmlWriter.newline()
295
296 def toXML2(self, xmlWriter, font):
297 # Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB).
298 # This is because in TTX our parent writes our main tag, and in otBase.py we
299 # do it ourselves. I think I'm getting schizophrenic...
300 for conv in self.getConverters():
301 value = getattr(self, conv.name)
jvr64b5c802002-05-11 10:21:36 +0000302 if conv.repeat:
jvrd4d15132002-05-11 00:59:27 +0000303 for i in range(len(value)):
304 item = value[i]
jvr64b5c802002-05-11 10:21:36 +0000305 conv.xmlWrite(xmlWriter, font, item, conv.name,
306 [("index", i)])
307 else:
308 conv.xmlWrite(xmlWriter, font, value, conv.name, [])
jvrd4d15132002-05-11 00:59:27 +0000309
310 def fromXML(self, (name, attrs, content), font):
311 try:
312 conv = self.getConverterByName(name)
313 except KeyError:
jvr64b5c802002-05-11 10:21:36 +0000314## print self, name, attrs, content
jvrd4d15132002-05-11 00:59:27 +0000315 raise # XXX on KeyError, raise nice error
316 value = conv.xmlRead(attrs, content, font)
jvrd4d15132002-05-11 00:59:27 +0000317 if conv.repeat:
318 try:
jvr64b5c802002-05-11 10:21:36 +0000319 seq = getattr(self, conv.name)
jvrd4d15132002-05-11 00:59:27 +0000320 except AttributeError:
321 seq = []
jvr64b5c802002-05-11 10:21:36 +0000322 setattr(self, conv.name, seq)
jvrd4d15132002-05-11 00:59:27 +0000323 seq.append(value)
324 else:
jvr64b5c802002-05-11 10:21:36 +0000325 setattr(self, conv.name, value)
jvrd4d15132002-05-11 00:59:27 +0000326
327 def __cmp__(self, other):
328 # this is only for debugging, so it's ok to barf
329 # when 'other' has no __dict__ or __class__
330 rv = cmp(self.__class__, other.__class__)
331 if not rv:
332 rv = cmp(self.__dict__, other.__dict__)
333 return rv
334 else:
335 return rv
336
337
338class FormatSwitchingBaseTable(BaseTable):
339
jvr64b5c802002-05-11 10:21:36 +0000340 """Small specialization of BaseTable, for tables that have multiple
341 formats, eg. CoverageFormat1 vs. CoverageFormat2."""
342
jvrd4d15132002-05-11 00:59:27 +0000343 def getConverters(self):
344 return self.converters[self.Format]
345
346 def getConverterByName(self, name):
347 return self.convertersByName[self.Format][name]
348
349 def decompile(self, reader, font, tableStack=None):
350 self.Format = reader.readUShort()
351 assert self.Format <> 0, (self, reader.pos, len(reader.data))
352 BaseTable.decompile(self, reader, font, tableStack)
353
354 def compile(self, writer, font, tableStack=None):
355 writer.writeUShort(self.Format)
356 BaseTable.compile(self, writer, font, tableStack)
357
358
jvr64b5c802002-05-11 10:21:36 +0000359#
360# Support for ValueRecords
361#
362# This data type is so different from all other OpenType data types that
363# it requires quite a bit of code for itself. It even has special support
364# in OTTableReader and OTTableWriter...
365#
366
jvrd4d15132002-05-11 00:59:27 +0000367valueRecordFormat = [
368# Mask Name isDevice signed
369 (0x0001, "XPlacement", 0, 1),
370 (0x0002, "YPlacement", 0, 1),
371 (0x0004, "XAdvance", 0, 1),
372 (0x0008, "YAdvance", 0, 1),
373 (0x0010, "XPlaDevice", 1, 0),
374 (0x0020, "YPlaDevice", 1, 0),
375 (0x0040, "XAdvDevice", 1, 0),
376 (0x0080, "YAdvDevice", 1, 0),
377# reserved:
378 (0x0100, "Reserved1", 0, 0),
379 (0x0200, "Reserved2", 0, 0),
380 (0x0400, "Reserved3", 0, 0),
381 (0x0800, "Reserved4", 0, 0),
382 (0x1000, "Reserved5", 0, 0),
383 (0x2000, "Reserved6", 0, 0),
384 (0x4000, "Reserved7", 0, 0),
385 (0x8000, "Reserved8", 0, 0),
386]
387
388def _buildDict():
389 d = {}
390 for mask, name, isDevice, signed in valueRecordFormat:
391 d[name] = mask, isDevice, signed
392 return d
393
394valueRecordFormatDict = _buildDict()
395
396
397class ValueRecordFactory:
398
jvr64b5c802002-05-11 10:21:36 +0000399 """Given a format code, this object convert ValueRecords."""
400
jvrd4d15132002-05-11 00:59:27 +0000401 def setFormat(self, valueFormat):
402 format = []
403 for mask, name, isDevice, signed in valueRecordFormat:
404 if valueFormat & mask:
405 format.append((name, isDevice, signed))
406 self.format = format
407
408 def readValueRecord(self, reader, font):
409 format = self.format
410 if not format:
411 return None
412 valueRecord = ValueRecord()
413 for name, isDevice, signed in format:
414 if signed:
415 value = reader.readShort()
416 else:
417 value = reader.readUShort()
418 if isDevice:
419 if value:
420 import otTables
421 subReader = reader.getSubReader(value)
422 value = getattr(otTables, name)()
423 value.decompile(subReader, font)
424 else:
425 value = None
426 setattr(valueRecord, name, value)
427 return valueRecord
428
429 def writeValueRecord(self, writer, font, valueRecord):
430 for name, isDevice, signed in self.format:
431 value = getattr(valueRecord, name, 0)
432 if isDevice:
433 if value:
434 subWriter = writer.getSubWriter()
435 writer.writeSubTable(subWriter)
436 value.compile(subWriter, font)
437 else:
438 writer.writeUShort(0)
439 elif signed:
440 writer.writeShort(value)
441 else:
442 writer.writeUShort(value)
443
444
445class ValueRecord:
446
447 # see ValueRecordFactory
448
449 def getFormat(self):
450 format = 0
451 for name in self.__dict__.keys():
452 format = format | valueRecordFormatDict[name][0]
453 return format
454
455 def toXML(self, xmlWriter, font, valueName, attrs=None):
456 if attrs is None:
457 simpleItems = []
458 else:
459 simpleItems = list(attrs)
460 for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values
461 if hasattr(self, name):
462 simpleItems.append((name, getattr(self, name)))
463 deviceItems = []
464 for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records
465 if hasattr(self, name):
466 device = getattr(self, name)
467 if device is not None:
468 deviceItems.append((name, device))
469 if deviceItems:
470 xmlWriter.begintag(valueName, simpleItems)
471 xmlWriter.newline()
472 for name, deviceRecord in deviceItems:
473 if deviceRecord is not None:
474 deviceRecord.toXML(xmlWriter, font)
475 xmlWriter.endtag(valueName)
476 xmlWriter.newline()
477 else:
478 xmlWriter.simpletag(valueName, simpleItems)
479 xmlWriter.newline()
480
481 def fromXML(self, (name, attrs, content), font):
482 import otTables
483 for k, v in attrs.items():
484 setattr(self, k, int(v))
485 for element in content:
486 if type(element) <> TupleType:
487 continue
488 name, attrs, content = element
489 value = getattr(otTables, name)()
490 for elem2 in content:
491 if type(elem2) <> TupleType:
492 continue
493 value.fromXML(elem2, font)
494 setattr(self, name, value)
495
496 def __cmp__(self, other):
497 # this is only for debugging, so it's ok to barf
498 # when 'other' has no __dict__ or __class__
499 rv = cmp(self.__class__, other.__class__)
500 if not rv:
501 rv = cmp(self.__dict__, other.__dict__)
502 return rv
503 else:
504 return rv
505