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