Home | History | Annotate | Download | only in tables
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc.textTools import safeEval
      4 from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
      5 from .otBase import ValueRecordFactory
      6 
      7 
      8 def 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 
     53 class 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 
     84 class 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 
     91 class IntValue(SimpleValue):
     92 	def xmlRead(self, attrs, content, font):
     93 		return int(attrs["value"], 0)
     94 
     95 class 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 
    101 class 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 
    127 class 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 
    133 class 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 
    139 class 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 
    145 class ComputedUShort(UShort):
    146 	def xmlWrite(self, xmlWriter, font, value, name, attrs):
    147 		xmlWriter.comment("%s=%s" % (name, value))
    148 		xmlWriter.newline()
    149 
    150 class 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 
    156 class 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 
    166 class FloatValue(SimpleValue):
    167 	def xmlRead(self, attrs, content, font):
    168 		return float(attrs["value"])
    169 
    170 class 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 
    178 class 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 
    217 class 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 
    260 class LTable(Table):
    261 
    262 	longOffset = True
    263 
    264 	def readOffset(self, reader):
    265 		return reader.readULong()
    266 
    267 
    268 class 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 
    274 class 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 
    280 class 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 
    286 class 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 
    299 class 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 
    316 class 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 
    370 converterMapping = {
    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