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