otConverters.py revision d58c38dc3660f764b659ec13fcbc6e54c1ec2078
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(object): 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 longOffset = False 178 179 def readOffset(self, reader): 180 return reader.readUShort() 181 182 def writeNullOffset(self, writer): 183 if hasattr(self, "Extension"): 184 writer.writeULong(0) 185 else: 186 writer.writeUShort(0) 187 188 def read(self, reader, font, tableDict): 189 offset = self.readOffset(reader) 190 if offset == 0: 191 return None 192 if offset <= 3: 193 # XXX hack to work around buggy pala.ttf 194 print "*** Warning: offset is not 0, yet suspiciously low (%s). table: %s" \ 195 % (offset, self.tableClass.__name__) 196 return None 197 table = self.tableClass() 198 table.reader = reader 199 table.offset = offset 200 table.font = font 201 table.compileStatus = 1 202 if not font.lazy: 203 table.ensureDecompiled() 204 return table 205 206 def write(self, writer, font, tableDict, value, repeatIndex=None): 207 writer.longOffset = self.longOffset 208 if value is None: 209 self.writeNullOffset(writer) 210 else: 211 subWriter = writer.getSubWriter() 212 subWriter.name = self.name 213 if repeatIndex is not None: 214 subWriter.repeatIndex = repeatIndex 215 writer.writeSubTable(subWriter) 216 value.compile(subWriter, font) 217 218class LTable(Table): 219 220 longOffset = True 221 222 def readOffset(self, reader): 223 return reader.readULong() 224 225 226class SubTable(Table): 227 def getConverter(self, tableType, lookupType): 228 tableClass = self.lookupTypes[tableType][lookupType] 229 return self.__class__(self.name, self.repeat, self.repeatOffset, tableClass) 230 231 232class ExtSubTable(LTable, SubTable): 233 234 def write(self, writer, font, tableDict, value, repeatIndex=None): 235 writer.Extension = 1 # actually, mere presence of the field flags it as an Ext Subtable writer. 236 Table.write(self, writer, font, tableDict, value, repeatIndex) 237 238 239class ValueFormat(IntValue): 240 def __init__(self, name, repeat, repeatOffset, tableClass): 241 BaseConverter.__init__(self, name, repeat, repeatOffset, tableClass) 242 self.which = name[-1] == "2" 243 def read(self, reader, font, tableDict): 244 format = reader.readUShort() 245 reader.setValueFormat(format, self.which) 246 return format 247 def write(self, writer, font, tableDict, format, repeatIndex=None): 248 writer.writeUShort(format) 249 writer.setValueFormat(format, self.which) 250 251 252class ValueRecord(ValueFormat): 253 def read(self, reader, font, tableDict): 254 return reader.readValueRecord(font, self.which) 255 def write(self, writer, font, tableDict, value, repeatIndex=None): 256 writer.writeValueRecord(value, font, self.which) 257 def xmlWrite(self, xmlWriter, font, value, name, attrs): 258 if value is None: 259 pass # NULL table, ignore 260 else: 261 value.toXML(xmlWriter, font, self.name, attrs) 262 def xmlRead(self, attrs, content, font): 263 from otBase import ValueRecord 264 value = ValueRecord() 265 value.fromXML((None, attrs, content), font) 266 return value 267 268 269class DeltaValue(BaseConverter): 270 271 def read(self, reader, font, tableDict): 272 StartSize = tableDict["StartSize"] 273 EndSize = tableDict["EndSize"] 274 DeltaFormat = tableDict["DeltaFormat"] 275 assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat" 276 nItems = EndSize - StartSize + 1 277 nBits = 1 << DeltaFormat 278 minusOffset = 1 << nBits 279 mask = (1 << nBits) - 1 280 signMask = 1 << (nBits - 1) 281 282 DeltaValue = [] 283 tmp, shift = 0, 0 284 for i in range(nItems): 285 if shift == 0: 286 tmp, shift = reader.readUShort(), 16 287 shift = shift - nBits 288 value = (tmp >> shift) & mask 289 if value & signMask: 290 value = value - minusOffset 291 DeltaValue.append(value) 292 return DeltaValue 293 294 def write(self, writer, font, tableDict, value, repeatIndex=None): 295 StartSize = tableDict["StartSize"] 296 EndSize = tableDict["EndSize"] 297 DeltaFormat = tableDict["DeltaFormat"] 298 DeltaValue = value 299 assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat" 300 nItems = EndSize - StartSize + 1 301 nBits = 1 << DeltaFormat 302 assert len(DeltaValue) == nItems 303 mask = (1 << nBits) - 1 304 305 tmp, shift = 0, 16 306 for value in DeltaValue: 307 shift = shift - nBits 308 tmp = tmp | ((value & mask) << shift) 309 if shift == 0: 310 writer.writeUShort(tmp) 311 tmp, shift = 0, 16 312 if shift <> 16: 313 writer.writeUShort(tmp) 314 315 def xmlWrite(self, xmlWriter, font, value, name, attrs): 316 # XXX this could do with a nicer format 317 xmlWriter.simpletag(name, attrs + [("value", value)]) 318 xmlWriter.newline() 319 320 def xmlRead(self, attrs, content, font): 321 return safeEval(attrs["value"]) 322 323 324converterMapping = { 325 # type class 326 "int16": Short, 327 "uint16": UShort, 328 "Version": Version, 329 "Tag": Tag, 330 "GlyphID": GlyphID, 331 "struct": Struct, 332 "Offset": Table, 333 "LOffset": LTable, 334 "ValueRecord": ValueRecord, 335 "DeltaValue": DeltaValue, 336} 337 338