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