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