blob: b26f53121e5eb55f6fddf6a93a49eb7900c4e3bd [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
jvrcfadfd02002-07-22 22:13:57 +000015 cachingStats = None
16 reader = OTTableReader(data, self.tableTag, cachingStats=cachingStats)
jvrd4d15132002-05-11 00:59:27 +000017 tableClass = getattr(otTables, self.tableTag)
18 self.table = tableClass()
19 self.table.decompile(reader, font)
jvrcfadfd02002-07-22 22:13:57 +000020 if 0:
21 stats = [(v, k) for k, v in cachingStats.items()]
22 stats.sort()
23 stats.reverse()
24 print "cachingsstats for ", self.tableTag
25 for v, k in stats:
26 if v < 2:
27 break
28 print v, k
29 print "---", len(stats)
jvrd4d15132002-05-11 00:59:27 +000030
31 def compile(self, font):
32 writer = OTTableWriter(self.tableTag)
33 self.table.compile(writer, font)
jvrcfadfd02002-07-22 22:13:57 +000034 return writer.getAllData()
jvrd4d15132002-05-11 00:59:27 +000035
36 def toXML(self, writer, font):
37 self.table.toXML2(writer, font)
38
39 def fromXML(self, (name, attrs, content), font):
40 import otTables
41 if not hasattr(self, "table"):
42 tableClass = getattr(otTables, self.tableTag)
43 self.table = tableClass()
44 self.table.fromXML((name, attrs, content), font)
45
46
47class OTTableReader:
48
jvr64b5c802002-05-11 10:21:36 +000049 """Helper class to retrieve data from an OpenType table."""
50
jvrd4d15132002-05-11 00:59:27 +000051 def __init__(self, data, tableType, offset=0, valueFormat=None, cachingStats=None):
52 self.data = data
53 self.offset = offset
54 self.pos = offset
55 self.tableType = tableType
56 if valueFormat is None:
57 valueFormat = (ValueRecordFactory(), ValueRecordFactory())
58 self.valueFormat = valueFormat
59 self.cachingStats = cachingStats
60
61 def getSubReader(self, offset):
62 offset = self.offset + offset
63 if self.cachingStats is not None:
64 try:
65 self.cachingStats[offset] = self.cachingStats[offset] + 1
66 except KeyError:
67 self.cachingStats[offset] = 1
68
69 subReader = self.__class__(self.data, self.tableType, offset,
70 self.valueFormat, self.cachingStats)
71 return subReader
72
73 def readUShort(self):
74 pos = self.pos
75 newpos = pos + 2
jvre69caf82002-05-13 18:08:19 +000076 value, = struct.unpack(">H", self.data[pos:newpos])
jvrd4d15132002-05-11 00:59:27 +000077 self.pos = newpos
78 return value
79
80 def readShort(self):
81 pos = self.pos
82 newpos = pos + 2
jvre69caf82002-05-13 18:08:19 +000083 value, = struct.unpack(">h", self.data[pos:newpos])
jvrd4d15132002-05-11 00:59:27 +000084 self.pos = newpos
85 return value
86
87 def readLong(self):
88 pos = self.pos
89 newpos = pos + 4
jvre69caf82002-05-13 18:08:19 +000090 value, = struct.unpack(">l", self.data[pos:newpos])
jvrd4d15132002-05-11 00:59:27 +000091 self.pos = newpos
92 return value
93
94 def readTag(self):
95 pos = self.pos
96 newpos = pos + 4
97 value = self.data[pos:newpos]
98 assert len(value) == 4
99 self.pos = newpos
100 return value
101
102 def readStruct(self, format, size=None):
103 if size is None:
104 size = struct.calcsize(format)
105 else:
106 assert size == struct.calcsize(format)
107 pos = self.pos
108 newpos = pos + size
109 values = struct.unpack(format, self.data[pos:newpos])
110 self.pos = newpos
111 return values
112
113 def setValueFormat(self, format, which):
114 self.valueFormat[which].setFormat(format)
115
116 def readValueRecord(self, font, which):
117 return self.valueFormat[which].readValueRecord(self, font)
118
119
120class OTTableWriter:
121
jvr64b5c802002-05-11 10:21:36 +0000122 """Helper class to gather and assemble data for OpenType tables."""
123
jvrd4d15132002-05-11 00:59:27 +0000124 def __init__(self, tableType, valueFormat=None):
125 self.items = []
126 self.tableType = tableType
127 if valueFormat is None:
128 valueFormat = ValueRecordFactory(), ValueRecordFactory()
129 self.valueFormat = valueFormat
jvrcfadfd02002-07-22 22:13:57 +0000130 self.pos = None
jvrd4d15132002-05-11 00:59:27 +0000131
132 def getSubWriter(self):
133 return self.__class__(self.tableType, self.valueFormat)
134
jvrcfadfd02002-07-22 22:13:57 +0000135 def __hash__(self):
136 # only works after self._doneWriting() has been called
137 return hash(self.items)
138
139 def __cmp__(self, other):
140 if hasattr(other, "items"):
141 return cmp(self.items, other.items)
142 else:
143 return cmp(id(self), id(other))
144
jvrcfadfd02002-07-22 22:13:57 +0000145 def _doneWriting(self, internedTables=None):
146 if internedTables is None:
147 internedTables = {}
148 items = self.items
149 for i in range(len(items)):
150 item = items[i]
151 if hasattr(item, "getCountData"):
152 items[i] = item.getCountData()
153 elif hasattr(item, "getData"):
jvrcfadfd02002-07-22 22:13:57 +0000154 item._doneWriting(internedTables)
155 if internedTables.has_key(item):
156 items[i] = item = internedTables[item]
157 else:
158 internedTables[item] = item
jvrcfadfd02002-07-22 22:13:57 +0000159 self.items = tuple(items)
160
161 def _gatherTables(self, tables=None, done=None):
162 if tables is None:
163 tables = []
164 done = {}
165 for item in self.items:
166 if not hasattr(item, "getData"):
167 continue
168 if not done.has_key(item):
169 item._gatherTables(tables, done)
jvrf8f2a362002-07-22 22:22:47 +0000170 done[self] = 1
jvrcfadfd02002-07-22 22:13:57 +0000171 tables.append(self)
172 return tables
173
174 def getAllData(self):
jvrf8f2a362002-07-22 22:22:47 +0000175 """Return all data, including all subtables."""
jvrcfadfd02002-07-22 22:13:57 +0000176 self._doneWriting()
177 tables = self._gatherTables()
178 tables.reverse()
179
180 pos = 0
181 for table in tables:
182 table.pos = pos
jvr8e483122002-07-23 08:19:38 +0000183 pos = pos + table.getDataLength()
jvrcfadfd02002-07-22 22:13:57 +0000184
185 data = []
186 for table in tables:
187 tableData = table.getData()
188 data.append(tableData)
189
190 return "".join(data)
191
jvr8e483122002-07-23 08:19:38 +0000192 def getDataLength(self):
193 """Return the length of this table in bytes, without subtables."""
194 l = 0
195 for item in self.items:
196 if hasattr(item, "getData") or hasattr(item, "getCountData"):
197 l = l + 2 # sizeof(UShort)
198 else:
199 l = l + len(item)
200 return l
201
jvrcfadfd02002-07-22 22:13:57 +0000202 def getData(self):
jvrf8f2a362002-07-22 22:22:47 +0000203 """Return the data for this writer/table, without any subtables."""
jvrcfadfd02002-07-22 22:13:57 +0000204 items = list(self.items) # make a shallow copy
jvrd4d15132002-05-11 00:59:27 +0000205 for i in range(len(items)):
206 item = items[i]
207 if hasattr(item, "getData"):
jvrcfadfd02002-07-22 22:13:57 +0000208 items[i] = packUShort(item.pos - self.pos)
jvrcfadfd02002-07-22 22:13:57 +0000209 return "".join(items)
jvrd4d15132002-05-11 00:59:27 +0000210
211 def writeUShort(self, value):
212 assert 0 <= value < 0x10000
213 self.items.append(struct.pack(">H", value))
214
215 def writeShort(self, value):
216 self.items.append(struct.pack(">h", value))
217
218 def writeLong(self, value):
219 self.items.append(struct.pack(">l", value))
220
221 def writeTag(self, tag):
222 assert len(tag) == 4
223 self.items.append(tag)
224
225 def writeSubTable(self, subWriter):
226 self.items.append(subWriter)
227
228 def writeCountReference(self, table, name):
229 self.items.append(CountReference(table, name))
230
231 def writeStruct(self, format, values):
232 data = apply(struct.pack, (format,) + values)
233 self.items.append(data)
234
235 def setValueFormat(self, format, which):
236 self.valueFormat[which].setFormat(format)
237
238 def writeValueRecord(self, value, font, which):
239 return self.valueFormat[which].writeValueRecord(self, font, value)
240
241
242class CountReference:
jvrcfadfd02002-07-22 22:13:57 +0000243 """A reference to a Count value, not a count of references."""
jvrd4d15132002-05-11 00:59:27 +0000244 def __init__(self, table, name):
245 self.table = table
246 self.name = name
jvrcfadfd02002-07-22 22:13:57 +0000247 def getCountData(self):
jvrd4d15132002-05-11 00:59:27 +0000248 return packUShort(self.table[self.name])
249
250
jvr64b5c802002-05-11 10:21:36 +0000251def packUShort(value):
jvrcfadfd02002-07-22 22:13:57 +0000252 assert 0 <= value < 0x10000, value
jvr64b5c802002-05-11 10:21:36 +0000253 return struct.pack(">H", value)
jvrd4d15132002-05-11 00:59:27 +0000254
255
256
jvr64b5c802002-05-11 10:21:36 +0000257class TableStack:
258 """A stack of table dicts, working as a stack of namespaces so we can
259 retrieve values from (and store values to) tables higher up the stack."""
260 def __init__(self):
261 self.stack = []
262 def push(self, table):
263 self.stack.insert(0, table)
264 def pop(self):
265 self.stack.pop(0)
266 def getTop(self):
267 return self.stack[0]
268 def getValue(self, name):
269 return self.__findTable(name)[name]
270 def storeValue(self, name, value):
271 table = self.__findTable(name)
272 if table[name] is None:
273 table[name] = value
274 else:
275 assert table[name] == value, (table[name], value)
276 def __findTable(self, name):
277 for table in self.stack:
278 if table.has_key(name):
279 return table
280 raise KeyError, name
281
282
jvrd4d15132002-05-11 00:59:27 +0000283class BaseTable:
284
jvr64b5c802002-05-11 10:21:36 +0000285 """Generic base class for all OpenType (sub)tables."""
286
jvrd4d15132002-05-11 00:59:27 +0000287 def getConverters(self):
288 return self.converters
289
290 def getConverterByName(self, name):
291 return self.convertersByName[name]
292
293 def decompile(self, reader, font, tableStack=None):
294 if tableStack is None:
295 tableStack = TableStack()
296 table = {}
297 self.__rawTable = table # for debugging
298 tableStack.push(table)
299 for conv in self.getConverters():
300 if conv.name == "SubTable":
301 conv = conv.getConverter(reader.tableType,
302 table["LookupType"])
303 if conv.repeat:
304 l = []
305 for i in range(tableStack.getValue(conv.repeat) + conv.repeatOffset):
306 l.append(conv.read(reader, font, tableStack))
307 table[conv.name] = l
308 else:
309 table[conv.name] = conv.read(reader, font, tableStack)
310 tableStack.pop()
311 self.postRead(table, font)
312 del self.__rawTable # succeeded, get rid of debugging info
313
314 def compile(self, writer, font, tableStack=None):
315 if tableStack is None:
316 tableStack = TableStack()
317 table = self.preWrite(font)
318 tableStack.push(table)
319 for conv in self.getConverters():
320 value = table.get(conv.name)
321 if conv.repeat:
322 if value is None:
jvr64b5c802002-05-11 10:21:36 +0000323 value = []
jvrd4d15132002-05-11 00:59:27 +0000324 tableStack.storeValue(conv.repeat, len(value) - conv.repeatOffset)
325 for item in value:
326 conv.write(writer, font, tableStack, item)
327 elif conv.isCount:
328 # Special-case Count values.
329 # Assumption: a Count field will *always* precede
330 # the actual array.
331 # We need a default value, as it may be set later by a nested
332 # table. TableStack.storeValue() will then find it here.
333 table[conv.name] = None
334 # We add a reference: by the time the data is assembled
335 # the Count value will be filled in.
336 writer.writeCountReference(table, conv.name)
337 else:
338 conv.write(writer, font, tableStack, value)
339 tableStack.pop()
340
341 def postRead(self, table, font):
342 self.__dict__.update(table)
343
344 def preWrite(self, font):
345 return self.__dict__.copy()
346
347 def toXML(self, xmlWriter, font, attrs=None):
348 tableName = self.__class__.__name__
349 if attrs is None:
350 attrs = []
351 if hasattr(self, "Format"):
jvr64b5c802002-05-11 10:21:36 +0000352 attrs = attrs + [("Format", self.Format)]
jvrd4d15132002-05-11 00:59:27 +0000353 xmlWriter.begintag(tableName, attrs)
354 xmlWriter.newline()
355 self.toXML2(xmlWriter, font)
356 xmlWriter.endtag(tableName)
357 xmlWriter.newline()
358
359 def toXML2(self, xmlWriter, font):
360 # Simpler variant of toXML, *only* for the top level tables (like GPOS, GSUB).
361 # This is because in TTX our parent writes our main tag, and in otBase.py we
362 # do it ourselves. I think I'm getting schizophrenic...
363 for conv in self.getConverters():
364 value = getattr(self, conv.name)
jvr64b5c802002-05-11 10:21:36 +0000365 if conv.repeat:
jvrd4d15132002-05-11 00:59:27 +0000366 for i in range(len(value)):
367 item = value[i]
jvr64b5c802002-05-11 10:21:36 +0000368 conv.xmlWrite(xmlWriter, font, item, conv.name,
369 [("index", i)])
370 else:
371 conv.xmlWrite(xmlWriter, font, value, conv.name, [])
jvrd4d15132002-05-11 00:59:27 +0000372
373 def fromXML(self, (name, attrs, content), font):
374 try:
375 conv = self.getConverterByName(name)
376 except KeyError:
jvr64b5c802002-05-11 10:21:36 +0000377## print self, name, attrs, content
jvrd4d15132002-05-11 00:59:27 +0000378 raise # XXX on KeyError, raise nice error
379 value = conv.xmlRead(attrs, content, font)
jvrd4d15132002-05-11 00:59:27 +0000380 if conv.repeat:
381 try:
jvr64b5c802002-05-11 10:21:36 +0000382 seq = getattr(self, conv.name)
jvrd4d15132002-05-11 00:59:27 +0000383 except AttributeError:
384 seq = []
jvr64b5c802002-05-11 10:21:36 +0000385 setattr(self, conv.name, seq)
jvrd4d15132002-05-11 00:59:27 +0000386 seq.append(value)
387 else:
jvr64b5c802002-05-11 10:21:36 +0000388 setattr(self, conv.name, value)
jvrd4d15132002-05-11 00:59:27 +0000389
390 def __cmp__(self, other):
391 # this is only for debugging, so it's ok to barf
392 # when 'other' has no __dict__ or __class__
393 rv = cmp(self.__class__, other.__class__)
394 if not rv:
395 rv = cmp(self.__dict__, other.__dict__)
396 return rv
397 else:
398 return rv
399
400
401class FormatSwitchingBaseTable(BaseTable):
402
jvrcfadfd02002-07-22 22:13:57 +0000403 """Minor specialization of BaseTable, for tables that have multiple
jvr64b5c802002-05-11 10:21:36 +0000404 formats, eg. CoverageFormat1 vs. CoverageFormat2."""
405
jvrd4d15132002-05-11 00:59:27 +0000406 def getConverters(self):
407 return self.converters[self.Format]
408
409 def getConverterByName(self, name):
410 return self.convertersByName[self.Format][name]
411
412 def decompile(self, reader, font, tableStack=None):
413 self.Format = reader.readUShort()
414 assert self.Format <> 0, (self, reader.pos, len(reader.data))
415 BaseTable.decompile(self, reader, font, tableStack)
416
417 def compile(self, writer, font, tableStack=None):
418 writer.writeUShort(self.Format)
419 BaseTable.compile(self, writer, font, tableStack)
420
421
jvr64b5c802002-05-11 10:21:36 +0000422#
423# Support for ValueRecords
424#
425# This data type is so different from all other OpenType data types that
426# it requires quite a bit of code for itself. It even has special support
427# in OTTableReader and OTTableWriter...
428#
429
jvrd4d15132002-05-11 00:59:27 +0000430valueRecordFormat = [
431# Mask Name isDevice signed
432 (0x0001, "XPlacement", 0, 1),
433 (0x0002, "YPlacement", 0, 1),
434 (0x0004, "XAdvance", 0, 1),
435 (0x0008, "YAdvance", 0, 1),
436 (0x0010, "XPlaDevice", 1, 0),
437 (0x0020, "YPlaDevice", 1, 0),
438 (0x0040, "XAdvDevice", 1, 0),
439 (0x0080, "YAdvDevice", 1, 0),
440# reserved:
441 (0x0100, "Reserved1", 0, 0),
442 (0x0200, "Reserved2", 0, 0),
443 (0x0400, "Reserved3", 0, 0),
444 (0x0800, "Reserved4", 0, 0),
445 (0x1000, "Reserved5", 0, 0),
446 (0x2000, "Reserved6", 0, 0),
447 (0x4000, "Reserved7", 0, 0),
448 (0x8000, "Reserved8", 0, 0),
449]
450
451def _buildDict():
452 d = {}
453 for mask, name, isDevice, signed in valueRecordFormat:
454 d[name] = mask, isDevice, signed
455 return d
456
457valueRecordFormatDict = _buildDict()
458
459
460class ValueRecordFactory:
461
jvr64b5c802002-05-11 10:21:36 +0000462 """Given a format code, this object convert ValueRecords."""
463
jvrd4d15132002-05-11 00:59:27 +0000464 def setFormat(self, valueFormat):
465 format = []
466 for mask, name, isDevice, signed in valueRecordFormat:
467 if valueFormat & mask:
468 format.append((name, isDevice, signed))
469 self.format = format
470
471 def readValueRecord(self, reader, font):
472 format = self.format
473 if not format:
474 return None
475 valueRecord = ValueRecord()
476 for name, isDevice, signed in format:
477 if signed:
478 value = reader.readShort()
479 else:
480 value = reader.readUShort()
481 if isDevice:
482 if value:
483 import otTables
484 subReader = reader.getSubReader(value)
485 value = getattr(otTables, name)()
486 value.decompile(subReader, font)
487 else:
488 value = None
489 setattr(valueRecord, name, value)
490 return valueRecord
491
492 def writeValueRecord(self, writer, font, valueRecord):
493 for name, isDevice, signed in self.format:
494 value = getattr(valueRecord, name, 0)
495 if isDevice:
496 if value:
497 subWriter = writer.getSubWriter()
498 writer.writeSubTable(subWriter)
499 value.compile(subWriter, font)
500 else:
501 writer.writeUShort(0)
502 elif signed:
503 writer.writeShort(value)
504 else:
505 writer.writeUShort(value)
506
507
508class ValueRecord:
509
510 # see ValueRecordFactory
511
512 def getFormat(self):
513 format = 0
514 for name in self.__dict__.keys():
515 format = format | valueRecordFormatDict[name][0]
516 return format
517
518 def toXML(self, xmlWriter, font, valueName, attrs=None):
519 if attrs is None:
520 simpleItems = []
521 else:
522 simpleItems = list(attrs)
523 for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values
524 if hasattr(self, name):
525 simpleItems.append((name, getattr(self, name)))
526 deviceItems = []
527 for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records
528 if hasattr(self, name):
529 device = getattr(self, name)
530 if device is not None:
531 deviceItems.append((name, device))
532 if deviceItems:
533 xmlWriter.begintag(valueName, simpleItems)
534 xmlWriter.newline()
535 for name, deviceRecord in deviceItems:
536 if deviceRecord is not None:
537 deviceRecord.toXML(xmlWriter, font)
538 xmlWriter.endtag(valueName)
539 xmlWriter.newline()
540 else:
541 xmlWriter.simpletag(valueName, simpleItems)
542 xmlWriter.newline()
543
544 def fromXML(self, (name, attrs, content), font):
545 import otTables
546 for k, v in attrs.items():
547 setattr(self, k, int(v))
548 for element in content:
549 if type(element) <> TupleType:
550 continue
551 name, attrs, content = element
552 value = getattr(otTables, name)()
553 for elem2 in content:
554 if type(elem2) <> TupleType:
555 continue
556 value.fromXML(elem2, font)
557 setattr(self, name, value)
558
559 def __cmp__(self, other):
560 # this is only for debugging, so it's ok to barf
561 # when 'other' has no __dict__ or __class__
562 rv = cmp(self.__class__, other.__class__)
563 if not rv:
564 rv = cmp(self.__dict__, other.__dict__)
565 return rv
566 else:
567 return rv
568