2014-01-14 15:07:50 +08:00
|
|
|
from __future__ import print_function, division, absolute_import
|
2013-11-27 17:27:45 -05:00
|
|
|
from fontTools.misc.py23 import *
|
2016-10-16 06:33:36 +01:00
|
|
|
from fontTools.misc.fixedTools import (
|
|
|
|
fixedToFloat as fi2fl, floatToFixed as fl2fi, ensureVersionIsLong as fi2ve,
|
|
|
|
versionToFixed as ve2fi)
|
2017-06-13 09:42:55 +02:00
|
|
|
from fontTools.misc.textTools import safeEval
|
|
|
|
from fontTools.ttLib import getSearchRange
|
2016-12-31 23:41:46 -05:00
|
|
|
from .otBase import ValueRecordFactory, CountReference
|
2016-08-09 22:51:41 -07:00
|
|
|
from functools import partial
|
2016-01-24 14:43:54 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
2002-05-11 00:59:27 +00:00
|
|
|
|
|
|
|
|
2002-05-11 10:21:36 +00:00
|
|
|
def buildConverters(tableSpec, tableNamespace):
|
|
|
|
"""Given a table spec from otData.py, build a converter object for each
|
|
|
|
field of the table. This is called for each table in otData.py, and
|
|
|
|
the results are assigned to the corresponding class in otTables.py."""
|
2002-05-11 00:59:27 +00:00
|
|
|
converters = []
|
|
|
|
convertersByName = {}
|
2013-11-24 22:11:41 -05:00
|
|
|
for tp, name, repeat, aux, descr in tableSpec:
|
2013-12-09 00:39:25 -05:00
|
|
|
tableName = name
|
2002-05-11 00:59:27 +00:00
|
|
|
if name.startswith("ValueFormat"):
|
|
|
|
assert tp == "uint16"
|
|
|
|
converterClass = ValueFormat
|
2017-06-07 15:03:23 +02:00
|
|
|
elif name.endswith("Count") or name == "MorphType" or name == "StructLength":
|
2017-06-07 14:19:34 +02:00
|
|
|
converterClass = {
|
|
|
|
"uint8": ComputedUInt8,
|
|
|
|
"uint16": ComputedUShort,
|
|
|
|
"uint32": ComputedULong,
|
|
|
|
}[tp]
|
2002-05-11 00:59:27 +00:00
|
|
|
elif name == "SubTable":
|
|
|
|
converterClass = SubTable
|
2006-10-21 14:12:38 +00:00
|
|
|
elif name == "ExtSubTable":
|
|
|
|
converterClass = ExtSubTable
|
2017-06-07 10:49:47 +02:00
|
|
|
elif name == "SubStruct":
|
|
|
|
converterClass = SubStruct
|
2013-11-26 19:23:08 -05:00
|
|
|
elif name == "FeatureParams":
|
|
|
|
converterClass = FeatureParams
|
2002-05-11 00:59:27 +00:00
|
|
|
else:
|
2016-08-09 22:51:41 -07:00
|
|
|
if not tp in converterMapping and '(' not in tp:
|
2013-12-09 00:39:25 -05:00
|
|
|
tableName = tp
|
|
|
|
converterClass = Struct
|
|
|
|
else:
|
2016-08-09 22:51:41 -07:00
|
|
|
converterClass = eval(tp, tableNamespace, converterMapping)
|
2013-12-09 00:39:25 -05:00
|
|
|
tableClass = tableNamespace.get(tableName)
|
2016-08-09 22:51:41 -07:00
|
|
|
if tableClass is not None:
|
|
|
|
conv = converterClass(name, repeat, aux, tableClass=tableClass)
|
|
|
|
else:
|
|
|
|
conv = converterClass(name, repeat, aux)
|
2017-06-07 10:49:47 +02:00
|
|
|
if name in ["SubTable", "ExtSubTable", "SubStruct"]:
|
2002-05-11 00:59:27 +00:00
|
|
|
conv.lookupTypes = tableNamespace['lookupTypes']
|
|
|
|
# also create reverse mapping
|
|
|
|
for t in conv.lookupTypes.values():
|
|
|
|
for cls in t.values():
|
2013-11-24 22:11:41 -05:00
|
|
|
convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
|
2013-11-26 19:23:08 -05:00
|
|
|
if name == "FeatureParams":
|
|
|
|
conv.featureParamTypes = tableNamespace['featureParamTypes']
|
|
|
|
conv.defaultFeatureParams = tableNamespace['FeatureParams']
|
|
|
|
for cls in conv.featureParamTypes.values():
|
|
|
|
convertersByName[cls.__name__] = Table(name, repeat, aux, cls)
|
2002-05-11 00:59:27 +00:00
|
|
|
converters.append(conv)
|
2013-12-09 00:39:25 -05:00
|
|
|
assert name not in convertersByName, name
|
2002-05-11 00:59:27 +00:00
|
|
|
convertersByName[name] = conv
|
|
|
|
return converters, convertersByName
|
|
|
|
|
|
|
|
|
2015-07-03 00:49:28 -07:00
|
|
|
class _MissingItem(tuple):
|
|
|
|
__slots__ = ()
|
|
|
|
|
|
|
|
try:
|
|
|
|
from collections import UserList
|
|
|
|
except:
|
|
|
|
from UserList import UserList
|
|
|
|
|
|
|
|
class _LazyList(UserList):
|
|
|
|
|
|
|
|
def __getslice__(self, i, j):
|
|
|
|
return self.__getitem__(slice(i, j))
|
|
|
|
def __getitem__(self, k):
|
|
|
|
if isinstance(k, slice):
|
|
|
|
indices = range(*k.indices(len(self)))
|
|
|
|
return [self[i] for i in indices]
|
|
|
|
item = self.data[k]
|
|
|
|
if isinstance(item, _MissingItem):
|
|
|
|
self.reader.seek(self.pos + item[0] * self.recordSize)
|
|
|
|
item = self.conv.read(self.reader, self.font, {})
|
|
|
|
self.data[k] = item
|
|
|
|
return item
|
|
|
|
|
2013-11-24 21:58:53 -05:00
|
|
|
class BaseConverter(object):
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 10:21:36 +00:00
|
|
|
"""Base class for converter objects. Apart from the constructor, this
|
|
|
|
is an abstract class."""
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2016-08-09 22:51:41 -07:00
|
|
|
def __init__(self, name, repeat, aux, tableClass=None):
|
2002-05-11 00:59:27 +00:00
|
|
|
self.name = name
|
|
|
|
self.repeat = repeat
|
2013-11-24 22:11:41 -05:00
|
|
|
self.aux = aux
|
2002-05-11 00:59:27 +00:00
|
|
|
self.tableClass = tableClass
|
2017-06-07 15:03:23 +02:00
|
|
|
self.isCount = name.endswith("Count") or name in ['DesignAxisRecordSize', 'ValueRecordSize', 'StructLength']
|
2017-06-07 13:58:26 +02:00
|
|
|
self.isLookupType = name.endswith("LookupType") or name == "MorphType"
|
2017-06-07 15:03:23 +02:00
|
|
|
self.isPropagated = name in ["ClassCount", "Class2Count", "FeatureTag", "SettingsCount", "VarRegionCount", "MappingCount", "RegionAxisCount", 'DesignAxisCount', 'DesignAxisRecordSize', 'AxisValueCount', 'ValueRecordSize', 'StructLength']
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2015-07-01 22:54:12 -07:00
|
|
|
def readArray(self, reader, font, tableDict, count):
|
|
|
|
"""Read an array of values from the reader."""
|
2015-07-02 18:00:41 -07:00
|
|
|
lazy = font.lazy and count > 8
|
|
|
|
if lazy:
|
|
|
|
recordSize = self.getRecordSize(reader)
|
|
|
|
if recordSize is NotImplemented:
|
|
|
|
lazy = False
|
|
|
|
if not lazy:
|
|
|
|
l = []
|
|
|
|
for i in range(count):
|
|
|
|
l.append(self.read(reader, font, tableDict))
|
|
|
|
return l
|
|
|
|
else:
|
2015-07-03 00:49:28 -07:00
|
|
|
l = _LazyList()
|
2015-07-02 18:00:41 -07:00
|
|
|
l.reader = reader.copy()
|
|
|
|
l.pos = l.reader.pos
|
|
|
|
l.font = font
|
|
|
|
l.conv = self
|
|
|
|
l.recordSize = recordSize
|
2015-07-03 00:49:28 -07:00
|
|
|
l.extend(_MissingItem([i]) for i in range(count))
|
2015-07-03 00:53:15 -07:00
|
|
|
reader.advance(count * recordSize)
|
2015-07-02 18:00:41 -07:00
|
|
|
return l
|
|
|
|
|
|
|
|
def getRecordSize(self, reader):
|
|
|
|
if hasattr(self, 'staticSize'): return self.staticSize
|
|
|
|
return NotImplemented
|
2015-07-01 22:54:12 -07:00
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2002-05-11 10:21:36 +00:00
|
|
|
"""Read a value from the reader."""
|
2013-11-27 02:42:28 -05:00
|
|
|
raise NotImplementedError(self)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2015-07-01 22:54:12 -07:00
|
|
|
def writeArray(self, writer, font, tableDict, values):
|
2015-09-09 18:16:44 +01:00
|
|
|
for i, value in enumerate(values):
|
|
|
|
self.write(writer, font, tableDict, value, i)
|
2015-07-01 22:54:12 -07:00
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2002-05-11 10:21:36 +00:00
|
|
|
"""Write a value to the writer."""
|
2013-11-27 02:42:28 -05:00
|
|
|
raise NotImplementedError(self)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 10:21:36 +00:00
|
|
|
def xmlRead(self, attrs, content, font):
|
|
|
|
"""Read a value from XML."""
|
2013-11-27 02:42:28 -05:00
|
|
|
raise NotImplementedError(self)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 10:21:36 +00:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
"""Write a value to XML."""
|
2013-11-27 02:42:28 -05:00
|
|
|
raise NotImplementedError(self)
|
2002-05-11 00:59:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SimpleValue(BaseConverter):
|
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
xmlWriter.simpletag(name, attrs + [("value", value)])
|
|
|
|
xmlWriter.newline()
|
|
|
|
def xmlRead(self, attrs, content, font):
|
|
|
|
return attrs["value"]
|
|
|
|
|
|
|
|
class IntValue(SimpleValue):
|
|
|
|
def xmlRead(self, attrs, content, font):
|
2013-11-23 19:51:36 -05:00
|
|
|
return int(attrs["value"], 0)
|
2002-05-11 00:59:27 +00:00
|
|
|
|
|
|
|
class Long(IntValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 4
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2002-05-11 00:59:27 +00:00
|
|
|
return reader.readLong()
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2002-05-11 00:59:27 +00:00
|
|
|
writer.writeLong(value)
|
|
|
|
|
2014-09-30 18:55:57 -04:00
|
|
|
class ULong(IntValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 4
|
2014-09-30 18:55:57 -04:00
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
return reader.readULong()
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
writer.writeULong(value)
|
|
|
|
|
2017-06-07 10:49:47 +02:00
|
|
|
class Flags32(ULong):
|
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
xmlWriter.simpletag(name, attrs + [("value", "0x%08X" % value)])
|
|
|
|
xmlWriter.newline()
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
class Short(IntValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 2
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2002-05-11 00:59:27 +00:00
|
|
|
return reader.readShort()
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2002-05-11 00:59:27 +00:00
|
|
|
writer.writeShort(value)
|
|
|
|
|
|
|
|
class UShort(IntValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 2
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2002-05-11 00:59:27 +00:00
|
|
|
return reader.readUShort()
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2002-05-11 00:59:27 +00:00
|
|
|
writer.writeUShort(value)
|
|
|
|
|
2016-06-06 22:01:09 -07:00
|
|
|
class Int8(IntValue):
|
|
|
|
staticSize = 1
|
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
return reader.readInt8()
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
writer.writeInt8(value)
|
|
|
|
|
2015-10-16 00:17:22 +02:00
|
|
|
class UInt8(IntValue):
|
|
|
|
staticSize = 1
|
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
return reader.readUInt8()
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
writer.writeUInt8(value)
|
|
|
|
|
2013-11-26 19:23:08 -05:00
|
|
|
class UInt24(IntValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 3
|
2013-11-26 19:23:08 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
return reader.readUInt24()
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
writer.writeUInt24(value)
|
|
|
|
|
2016-09-04 20:00:21 -07:00
|
|
|
class ComputedInt(IntValue):
|
2002-05-11 00:59:27 +00:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
2016-12-28 16:45:24 -05:00
|
|
|
if value is not None:
|
|
|
|
xmlWriter.comment("%s=%s" % (name, value))
|
|
|
|
xmlWriter.newline()
|
2002-05-11 00:59:27 +00:00
|
|
|
|
2017-06-07 14:19:34 +02:00
|
|
|
class ComputedUInt8(ComputedInt, UInt8):
|
|
|
|
pass
|
2016-09-04 20:00:21 -07:00
|
|
|
class ComputedUShort(ComputedInt, UShort):
|
|
|
|
pass
|
|
|
|
class ComputedULong(ComputedInt, ULong):
|
|
|
|
pass
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
class Tag(SimpleValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 4
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2002-05-11 00:59:27 +00:00
|
|
|
return reader.readTag()
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2002-05-11 00:59:27 +00:00
|
|
|
writer.writeTag(value)
|
|
|
|
|
|
|
|
class GlyphID(SimpleValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 2
|
2015-07-03 01:12:16 -07:00
|
|
|
def readArray(self, reader, font, tableDict, count):
|
|
|
|
glyphOrder = font.getGlyphOrder()
|
2015-10-17 06:47:52 +02:00
|
|
|
gids = reader.readUShortArray(count)
|
2015-07-03 01:12:16 -07:00
|
|
|
try:
|
|
|
|
l = [glyphOrder[gid] for gid in gids]
|
|
|
|
except IndexError:
|
|
|
|
# Slower, but will not throw an IndexError on an invalid glyph id.
|
|
|
|
l = [font.getGlyphName(gid) for gid in gids]
|
|
|
|
return l
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2015-07-03 00:55:54 -07:00
|
|
|
return font.getGlyphName(reader.readUShort())
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2015-07-03 00:55:54 -07:00
|
|
|
writer.writeUShort(font.getGlyphID(value))
|
2002-05-11 00:59:27 +00:00
|
|
|
|
2016-10-14 21:04:35 +02:00
|
|
|
|
|
|
|
class NameID(UShort):
|
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
xmlWriter.simpletag(name, attrs + [("value", value)])
|
|
|
|
nameTable = font.get("name") if font else None
|
|
|
|
if nameTable:
|
|
|
|
name = nameTable.getDebugName(value)
|
|
|
|
xmlWriter.write(" ")
|
|
|
|
if name:
|
|
|
|
xmlWriter.comment(name)
|
|
|
|
else:
|
|
|
|
xmlWriter.comment("missing from name table")
|
|
|
|
log.warning("name id %d missing from name table" % value)
|
|
|
|
xmlWriter.newline()
|
|
|
|
|
|
|
|
|
2013-11-26 19:42:55 -05:00
|
|
|
class FloatValue(SimpleValue):
|
|
|
|
def xmlRead(self, attrs, content, font):
|
|
|
|
return float(attrs["value"])
|
|
|
|
|
|
|
|
class DeciPoints(FloatValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 2
|
2013-11-26 19:42:55 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2015-07-03 00:55:54 -07:00
|
|
|
return reader.readUShort() / 10
|
2013-11-26 19:42:55 -05:00
|
|
|
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
writer.writeUShort(int(round(value * 10)))
|
2002-05-13 18:10:05 +00:00
|
|
|
|
2015-03-11 15:29:35 -07:00
|
|
|
class Fixed(FloatValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 4
|
2015-03-11 15:29:35 -07:00
|
|
|
def read(self, reader, font, tableDict):
|
2015-07-03 00:55:54 -07:00
|
|
|
return fi2fl(reader.readLong(), 16)
|
2015-03-11 15:29:35 -07:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2015-07-03 00:55:54 -07:00
|
|
|
writer.writeLong(fl2fi(value, 16))
|
2015-03-11 15:29:35 -07:00
|
|
|
|
2016-06-06 22:01:09 -07:00
|
|
|
class F2Dot14(FloatValue):
|
|
|
|
staticSize = 2
|
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
return fi2fl(reader.readShort(), 14)
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
writer.writeShort(fl2fi(value, 14))
|
|
|
|
|
2015-03-11 15:29:35 -07:00
|
|
|
class Version(BaseConverter):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 4
|
2015-03-11 15:29:35 -07:00
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
value = reader.readLong()
|
|
|
|
assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
|
2016-09-04 20:58:46 -07:00
|
|
|
return value
|
2015-03-11 15:29:35 -07:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2016-10-16 06:33:36 +01:00
|
|
|
value = fi2ve(value)
|
2015-03-11 15:29:35 -07:00
|
|
|
assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
|
|
|
|
writer.writeLong(value)
|
|
|
|
def xmlRead(self, attrs, content, font):
|
|
|
|
value = attrs["value"]
|
2016-10-16 06:33:36 +01:00
|
|
|
value = ve2fi(value)
|
2015-03-11 15:29:35 -07:00
|
|
|
return value
|
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
2016-10-16 06:33:36 +01:00
|
|
|
value = fi2ve(value)
|
2016-09-04 20:58:46 -07:00
|
|
|
value = "0x%08x" % value
|
2015-03-11 15:29:35 -07:00
|
|
|
xmlWriter.simpletag(name, attrs + [("value", value)])
|
|
|
|
xmlWriter.newline()
|
|
|
|
|
2016-09-04 20:58:46 -07:00
|
|
|
@staticmethod
|
|
|
|
def fromFloat(v):
|
|
|
|
return fl2fi(v, 16)
|
|
|
|
|
2015-03-11 15:29:35 -07:00
|
|
|
|
2017-03-10 12:17:14 +01:00
|
|
|
class Char64(SimpleValue):
|
|
|
|
"""An ASCII string with up to 64 characters.
|
|
|
|
|
|
|
|
Unused character positions are filled with 0x00 bytes.
|
|
|
|
Used in Apple AAT fonts in the `gcid` table.
|
|
|
|
"""
|
|
|
|
staticSize = 64
|
|
|
|
|
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
data = reader.readData(self.staticSize)
|
|
|
|
zeroPos = data.find(b"\0")
|
|
|
|
if zeroPos >= 0:
|
|
|
|
data = data[:zeroPos]
|
|
|
|
s = tounicode(data, encoding="ascii", errors="replace")
|
|
|
|
if s != tounicode(data, encoding="ascii", errors="ignore"):
|
|
|
|
log.warning('replaced non-ASCII characters in "%s"' %
|
|
|
|
s)
|
|
|
|
return s
|
|
|
|
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
data = tobytes(value, encoding="ascii", errors="replace")
|
|
|
|
if data != tobytes(value, encoding="ascii", errors="ignore"):
|
|
|
|
log.warning('replacing non-ASCII characters in "%s"' %
|
|
|
|
value)
|
|
|
|
if len(data) > self.staticSize:
|
|
|
|
log.warning('truncating overlong "%s" to %d bytes' %
|
|
|
|
(value, self.staticSize))
|
|
|
|
data = (data + b"\0" * self.staticSize)[:self.staticSize]
|
|
|
|
writer.writeData(data)
|
|
|
|
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
class Struct(BaseConverter):
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2015-07-02 18:00:41 -07:00
|
|
|
def getRecordSize(self, reader):
|
|
|
|
return self.tableClass and self.tableClass.getRecordSize(reader)
|
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2002-05-11 00:59:27 +00:00
|
|
|
table = self.tableClass()
|
2013-11-24 17:08:06 -05:00
|
|
|
table.decompile(reader, font)
|
2002-05-11 00:59:27 +00:00
|
|
|
return table
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
value.compile(writer, font)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
if value is None:
|
2013-11-26 18:55:23 -05:00
|
|
|
if attrs:
|
|
|
|
# If there are attributes (probably index), then
|
|
|
|
# don't drop this even if it's NULL. It will mess
|
|
|
|
# up the array indices of the containing element.
|
2014-06-05 17:58:15 -04:00
|
|
|
xmlWriter.simpletag(name, attrs + [("empty", 1)])
|
2013-11-26 18:55:23 -05:00
|
|
|
xmlWriter.newline()
|
|
|
|
else:
|
|
|
|
pass # NULL table, ignore
|
2002-05-11 00:59:27 +00:00
|
|
|
else:
|
2013-12-09 00:39:25 -05:00
|
|
|
value.toXML(xmlWriter, font, attrs, name=name)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
def xmlRead(self, attrs, content, font):
|
2014-06-05 17:58:15 -04:00
|
|
|
if "empty" in attrs and safeEval(attrs["empty"]):
|
2013-11-26 18:55:23 -05:00
|
|
|
return None
|
2014-06-05 17:58:15 -04:00
|
|
|
table = self.tableClass()
|
2002-05-11 00:59:27 +00:00
|
|
|
Format = attrs.get("Format")
|
|
|
|
if Format is not None:
|
|
|
|
table.Format = int(Format)
|
2016-12-31 23:16:12 -05:00
|
|
|
|
2016-12-31 23:41:46 -05:00
|
|
|
noPostRead = not hasattr(table, 'postRead')
|
|
|
|
if noPostRead:
|
|
|
|
# TODO Cache table.hasPropagated.
|
|
|
|
cleanPropagation = False
|
|
|
|
for conv in table.getConverters():
|
|
|
|
if conv.isPropagated:
|
|
|
|
cleanPropagation = True
|
|
|
|
if not hasattr(font, '_propagator'):
|
|
|
|
font._propagator = {}
|
|
|
|
propagator = font._propagator
|
|
|
|
assert conv.name not in propagator
|
|
|
|
setattr(table, conv.name, None)
|
|
|
|
propagator[conv.name] = CountReference(table.__dict__, conv.name)
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
for element in content:
|
2013-11-27 05:17:37 -05:00
|
|
|
if isinstance(element, tuple):
|
2002-09-12 16:45:48 +00:00
|
|
|
name, attrs, content = element
|
2013-11-27 03:19:32 -05:00
|
|
|
table.fromXML(name, attrs, content, font)
|
2002-09-12 16:45:48 +00:00
|
|
|
else:
|
|
|
|
pass
|
2016-12-31 23:16:12 -05:00
|
|
|
|
2017-02-03 14:33:57 -08:00
|
|
|
table.populateDefaults(propagator=getattr(font, '_propagator', None))
|
|
|
|
|
2016-12-31 23:41:46 -05:00
|
|
|
if noPostRead:
|
|
|
|
if cleanPropagation:
|
|
|
|
for conv in table.getConverters():
|
|
|
|
if conv.isPropagated:
|
|
|
|
propagator = font._propagator
|
|
|
|
del propagator[conv.name]
|
|
|
|
if not propagator:
|
|
|
|
del font._propagator
|
2016-12-31 23:16:12 -05:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
return table
|
|
|
|
|
2015-07-01 23:02:40 -07:00
|
|
|
def __repr__(self):
|
|
|
|
return "Struct of " + repr(self.tableClass)
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
|
2017-06-07 10:49:47 +02:00
|
|
|
class StructWithLength(Struct):
|
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
pos = reader.pos
|
|
|
|
table = self.tableClass()
|
|
|
|
table.decompile(reader, font)
|
|
|
|
reader.seek(pos + table.StructLength)
|
2017-06-07 15:03:23 +02:00
|
|
|
del table.StructLength
|
2017-06-07 10:49:47 +02:00
|
|
|
return table
|
|
|
|
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
value.compile(writer, font)
|
2017-06-07 15:03:23 +02:00
|
|
|
writer['StructLength'].setValue(writer.getDataLength())
|
2017-06-07 10:49:47 +02:00
|
|
|
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
class Table(Struct):
|
2013-11-24 21:35:56 -05:00
|
|
|
|
2013-11-24 21:58:53 -05:00
|
|
|
longOffset = False
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 2
|
2013-11-24 21:58:53 -05:00
|
|
|
|
2013-11-24 21:35:56 -05:00
|
|
|
def readOffset(self, reader):
|
|
|
|
return reader.readUShort()
|
|
|
|
|
|
|
|
def writeNullOffset(self, writer):
|
2013-11-25 05:32:17 -05:00
|
|
|
if self.longOffset:
|
2013-11-24 21:35:56 -05:00
|
|
|
writer.writeULong(0)
|
|
|
|
else:
|
|
|
|
writer.writeUShort(0)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-24 19:03:18 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2013-11-24 21:35:56 -05:00
|
|
|
offset = self.readOffset(reader)
|
2002-05-11 00:59:27 +00:00
|
|
|
if offset == 0:
|
|
|
|
return None
|
|
|
|
table = self.tableClass()
|
2013-12-17 00:58:02 -05:00
|
|
|
reader = reader.getSubReader(offset)
|
|
|
|
if font.lazy:
|
|
|
|
table.reader = reader
|
|
|
|
table.font = font
|
|
|
|
else:
|
|
|
|
table.decompile(reader, font)
|
2002-05-11 00:59:27 +00:00
|
|
|
return table
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2002-05-11 00:59:27 +00:00
|
|
|
if value is None:
|
2013-11-24 21:35:56 -05:00
|
|
|
self.writeNullOffset(writer)
|
2002-05-11 00:59:27 +00:00
|
|
|
else:
|
|
|
|
subWriter = writer.getSubWriter()
|
2013-11-25 05:32:17 -05:00
|
|
|
subWriter.longOffset = self.longOffset
|
2006-10-21 14:12:38 +00:00
|
|
|
subWriter.name = self.name
|
|
|
|
if repeatIndex is not None:
|
|
|
|
subWriter.repeatIndex = repeatIndex
|
2002-05-11 00:59:27 +00:00
|
|
|
writer.writeSubTable(subWriter)
|
2013-11-24 17:08:06 -05:00
|
|
|
value.compile(subWriter, font)
|
2002-05-11 00:59:27 +00:00
|
|
|
|
2013-11-24 21:58:53 -05:00
|
|
|
class LTable(Table):
|
|
|
|
|
|
|
|
longOffset = True
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 4
|
2013-11-24 21:58:53 -05:00
|
|
|
|
|
|
|
def readOffset(self, reader):
|
|
|
|
return reader.readULong()
|
|
|
|
|
|
|
|
|
2017-06-07 14:20:01 +02:00
|
|
|
# TODO Clean / merge the SubTable and SubStruct
|
|
|
|
|
|
|
|
class SubStruct(Struct):
|
2002-05-11 00:59:27 +00:00
|
|
|
def getConverter(self, tableType, lookupType):
|
2013-11-24 21:35:56 -05:00
|
|
|
tableClass = self.lookupTypes[tableType][lookupType]
|
2013-11-24 22:11:41 -05:00
|
|
|
return self.__class__(self.name, self.repeat, self.aux, tableClass)
|
2006-10-21 14:12:38 +00:00
|
|
|
|
2016-01-13 15:58:18 +00:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
2017-06-07 14:20:01 +02:00
|
|
|
super(SubStruct, self).xmlWrite(xmlWriter, font, value, None, attrs)
|
|
|
|
|
|
|
|
class SubTable(Table):
|
|
|
|
def getConverter(self, tableType, lookupType):
|
|
|
|
tableClass = self.lookupTypes[tableType][lookupType]
|
|
|
|
return self.__class__(self.name, self.repeat, self.aux, tableClass)
|
2016-01-13 15:58:18 +00:00
|
|
|
|
2017-06-07 14:20:01 +02:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
super(SubTable, self).xmlWrite(xmlWriter, font, value, None, attrs)
|
2006-10-21 14:12:38 +00:00
|
|
|
|
2013-11-24 21:58:53 -05:00
|
|
|
class ExtSubTable(LTable, SubTable):
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2016-02-10 17:28:56 +07:00
|
|
|
writer.Extension = True # actually, mere presence of the field flags it as an Ext Subtable writer.
|
2013-11-24 21:35:56 -05:00
|
|
|
Table.write(self, writer, font, tableDict, value, repeatIndex)
|
2002-05-11 00:59:27 +00:00
|
|
|
|
2017-06-07 14:20:01 +02:00
|
|
|
|
2013-11-26 19:23:08 -05:00
|
|
|
class FeatureParams(Table):
|
|
|
|
def getConverter(self, featureTag):
|
|
|
|
tableClass = self.featureParamTypes.get(featureTag, self.defaultFeatureParams)
|
|
|
|
return self.__class__(self.name, self.repeat, self.aux, tableClass)
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
|
|
|
|
class ValueFormat(IntValue):
|
2015-07-02 18:00:41 -07:00
|
|
|
staticSize = 2
|
2016-08-09 22:51:41 -07:00
|
|
|
def __init__(self, name, repeat, aux, tableClass=None):
|
2013-11-24 22:11:41 -05:00
|
|
|
BaseConverter.__init__(self, name, repeat, aux, tableClass)
|
2013-11-26 17:07:37 -05:00
|
|
|
self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1")
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2002-05-11 00:59:27 +00:00
|
|
|
format = reader.readUShort()
|
2013-11-26 17:07:37 -05:00
|
|
|
reader[self.which] = ValueRecordFactory(format)
|
2002-05-11 00:59:27 +00:00
|
|
|
return format
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, format, repeatIndex=None):
|
2002-05-11 00:59:27 +00:00
|
|
|
writer.writeUShort(format)
|
2013-11-26 17:07:37 -05:00
|
|
|
writer[self.which] = ValueRecordFactory(format)
|
2002-05-11 00:59:27 +00:00
|
|
|
|
2002-05-13 18:10:05 +00:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
class ValueRecord(ValueFormat):
|
2015-07-02 18:00:41 -07:00
|
|
|
def getRecordSize(self, reader):
|
|
|
|
return 2 * len(reader[self.which])
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2013-11-26 17:07:37 -05:00
|
|
|
return reader[self.which].readValueRecord(reader, font)
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2013-11-26 17:07:37 -05:00
|
|
|
writer[self.which].writeValueRecord(writer, font, value)
|
2002-05-11 00:59:27 +00:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
if value is None:
|
|
|
|
pass # NULL table, ignore
|
|
|
|
else:
|
|
|
|
value.toXML(xmlWriter, font, self.name, attrs)
|
|
|
|
def xmlRead(self, attrs, content, font):
|
2013-11-27 02:34:11 -05:00
|
|
|
from .otBase import ValueRecord
|
2002-05-11 00:59:27 +00:00
|
|
|
value = ValueRecord()
|
2013-11-27 03:19:32 -05:00
|
|
|
value.fromXML(None, attrs, content, font)
|
2002-05-11 00:59:27 +00:00
|
|
|
return value
|
|
|
|
|
|
|
|
|
2017-06-07 10:49:47 +02:00
|
|
|
class AATLookup(BaseConverter):
|
2017-06-13 10:40:39 +02:00
|
|
|
BIN_SEARCH_HEADER_SIZE = 10
|
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
valueConverter = GlyphID(name='Glyph', repeat=None, aux=None)
|
|
|
|
|
|
|
|
# TODO: Use self.valueConverter for reading values.
|
2017-06-07 10:49:47 +02:00
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
format = reader.readUShort()
|
|
|
|
if format == 0:
|
|
|
|
glyphs = font.getGlyphOrder()
|
|
|
|
mapping = self.readFormat0(reader, len(glyphs))
|
|
|
|
elif format == 2:
|
|
|
|
mapping = self.readFormat2(reader)
|
|
|
|
elif format == 4:
|
2017-06-13 10:40:39 +02:00
|
|
|
mapping = self.readFormat4(reader)
|
2017-06-07 10:49:47 +02:00
|
|
|
elif format == 6:
|
2017-06-13 10:40:39 +02:00
|
|
|
mapping = self.readFormat6(reader)
|
2017-06-07 10:49:47 +02:00
|
|
|
elif format == 8:
|
2017-06-13 10:40:39 +02:00
|
|
|
mapping = self.readFormat8(reader)
|
2017-06-07 10:49:47 +02:00
|
|
|
else:
|
|
|
|
assert False, "unsupported lookup format: %d" % format
|
|
|
|
return {font.getGlyphName(k):font.getGlyphName(v)
|
|
|
|
for k, v in mapping.items() if k != v}
|
|
|
|
|
2017-06-07 18:12:29 +02:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2017-06-13 15:44:21 +02:00
|
|
|
values = list(sorted([(font.getGlyphID(glyph), val)
|
|
|
|
for glyph, val in value.items()]))
|
2017-06-13 14:39:13 +02:00
|
|
|
# TODO: Also implement format 4.
|
2017-06-13 10:40:39 +02:00
|
|
|
formats = list(sorted(filter(None, [
|
2017-06-13 20:18:42 +02:00
|
|
|
self.buildFormat0(writer, font, values),
|
|
|
|
self.buildFormat2(writer, font, values),
|
|
|
|
self.buildFormat6(writer, font, values),
|
|
|
|
self.buildFormat8(writer, font, values),
|
2017-06-13 10:40:39 +02:00
|
|
|
])))
|
|
|
|
# We use the format ID as secondary sort key to make the output
|
|
|
|
# deterministic when multiple formats have same encoded size.
|
2017-06-13 15:44:21 +02:00
|
|
|
dataSize, lookupFormat, writeMethod = formats[0]
|
|
|
|
pos = writer.getDataLength()
|
|
|
|
writeMethod()
|
|
|
|
actualSize = writer.getDataLength() - pos
|
|
|
|
assert actualSize == dataSize, (
|
|
|
|
"AATLookup format %d claimed to write %d bytes, but wrote %d" %
|
|
|
|
(lookupFormat, dataSize, actualSize))
|
2017-06-07 18:12:29 +02:00
|
|
|
|
2017-06-13 09:42:55 +02:00
|
|
|
@staticmethod
|
|
|
|
def writeBinSearchHeader(writer, numUnits, unitSize):
|
|
|
|
writer.writeUShort(unitSize)
|
|
|
|
writer.writeUShort(numUnits)
|
|
|
|
searchRange, entrySelector, rangeShift = \
|
|
|
|
getSearchRange(n=numUnits, itemSize=unitSize)
|
|
|
|
writer.writeUShort(searchRange)
|
|
|
|
writer.writeUShort(entrySelector)
|
|
|
|
writer.writeUShort(rangeShift)
|
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def buildFormat0(self, writer, font, values):
|
2017-06-13 15:44:21 +02:00
|
|
|
numGlyphs = len(font.getGlyphOrder())
|
|
|
|
if len(values) != numGlyphs:
|
2017-06-13 10:40:39 +02:00
|
|
|
return None
|
2017-06-13 20:18:42 +02:00
|
|
|
valueSize = self.valueConverter.staticSize
|
2017-06-13 15:44:21 +02:00
|
|
|
return (2 + numGlyphs * valueSize, 0,
|
2017-06-13 20:18:42 +02:00
|
|
|
lambda: self.writeFormat0(writer, font, values))
|
2017-06-13 10:40:39 +02:00
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def writeFormat0(self, writer, font, values):
|
2017-06-13 10:40:39 +02:00
|
|
|
writer.writeUShort(0)
|
2017-06-13 15:44:21 +02:00
|
|
|
for glyphID_, value in values:
|
2017-06-13 20:18:42 +02:00
|
|
|
self.valueConverter.write(
|
|
|
|
writer, font, tableDict=None,
|
|
|
|
value=value, repeatIndex=None)
|
2017-06-07 18:12:29 +02:00
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def buildFormat2(self, writer, font, values):
|
2017-06-13 15:44:21 +02:00
|
|
|
segStart, segValue = values[0]
|
2017-06-13 11:52:01 +02:00
|
|
|
segEnd = segStart
|
|
|
|
segments = []
|
2017-06-13 15:44:21 +02:00
|
|
|
for glyphID, curValue in values[1:]:
|
2017-06-13 11:52:01 +02:00
|
|
|
if glyphID != segEnd + 1 or curValue != segValue:
|
|
|
|
segments.append((segStart, segEnd, segValue))
|
|
|
|
segStart = segEnd = glyphID
|
|
|
|
segValue = curValue
|
|
|
|
else:
|
|
|
|
segEnd = glyphID
|
|
|
|
segments.append((segStart, segEnd, segValue))
|
2017-06-13 20:18:42 +02:00
|
|
|
valueSize = self.valueConverter.staticSize
|
2017-06-13 15:44:21 +02:00
|
|
|
numUnits, unitSize = len(segments) + 1, valueSize + 4
|
|
|
|
return (2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize, 2,
|
2017-06-13 20:18:42 +02:00
|
|
|
lambda: self.writeFormat2(writer, font, segments))
|
2017-06-13 11:52:01 +02:00
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def writeFormat2(self, writer, font, segments):
|
2017-06-13 11:52:01 +02:00
|
|
|
writer.writeUShort(2)
|
2017-06-13 20:18:42 +02:00
|
|
|
valueSize = self.valueConverter.staticSize
|
2017-06-13 15:44:21 +02:00
|
|
|
numUnits, unitSize = len(segments) + 1, valueSize + 4
|
|
|
|
self.writeBinSearchHeader(writer, numUnits, unitSize)
|
2017-06-13 11:52:01 +02:00
|
|
|
for firstGlyph, lastGlyph, value in segments:
|
|
|
|
writer.writeUShort(lastGlyph)
|
|
|
|
writer.writeUShort(firstGlyph)
|
2017-06-13 20:18:42 +02:00
|
|
|
self.valueConverter.write(
|
|
|
|
writer, font, tableDict=None,
|
|
|
|
value=value, repeatIndex=None)
|
2017-06-13 15:44:21 +02:00
|
|
|
writer.writeUShort(0xFFFF)
|
|
|
|
writer.writeUShort(0xFFFF)
|
|
|
|
writer.writeData(b'\xFF' * valueSize)
|
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def buildFormat6(self, writer, font, values):
|
|
|
|
valueSize = self.valueConverter.staticSize
|
2017-06-13 15:44:21 +02:00
|
|
|
numUnits, unitSize = len(values) + 1, valueSize + 2
|
|
|
|
return (2 + self.BIN_SEARCH_HEADER_SIZE + numUnits * unitSize, 6,
|
2017-06-13 20:18:42 +02:00
|
|
|
lambda: self.writeFormat6(writer, font, values))
|
2017-06-13 15:44:21 +02:00
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def writeFormat6(self, writer, font, values):
|
2017-06-13 10:40:39 +02:00
|
|
|
writer.writeUShort(6)
|
2017-06-13 20:18:42 +02:00
|
|
|
valueSize = self.valueConverter.staticSize
|
2017-06-13 15:44:21 +02:00
|
|
|
numUnits, unitSize = len(values) + 1, valueSize + 2
|
|
|
|
self.writeBinSearchHeader(writer, numUnits, unitSize)
|
|
|
|
for glyphID, value in values:
|
|
|
|
writer.writeUShort(glyphID)
|
2017-06-13 20:18:42 +02:00
|
|
|
self.valueConverter.write(
|
|
|
|
writer, font, tableDict=None,
|
|
|
|
value=value, repeatIndex=None)
|
2017-06-13 15:44:21 +02:00
|
|
|
writer.writeUShort(0xFFFF)
|
|
|
|
writer.writeData(b'\xFF' * valueSize)
|
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def buildFormat8(self, writer, font, values):
|
2017-06-13 15:44:21 +02:00
|
|
|
minGlyphID, maxGlyphID = values[0][0], values[-1][0]
|
|
|
|
if len(values) != maxGlyphID - minGlyphID + 1:
|
2017-06-13 14:39:13 +02:00
|
|
|
return None
|
2017-06-13 20:18:42 +02:00
|
|
|
valueSize = self.valueConverter.staticSize
|
2017-06-13 15:44:21 +02:00
|
|
|
return (6 + len(values) * valueSize, 8,
|
2017-06-13 20:18:42 +02:00
|
|
|
lambda: self.writeFormat8(writer, font, values))
|
2017-06-13 14:39:13 +02:00
|
|
|
|
2017-06-13 20:18:42 +02:00
|
|
|
def writeFormat8(self, writer, font, values):
|
2017-06-13 15:44:21 +02:00
|
|
|
firstGlyphID = values[0][0]
|
2017-06-13 14:39:13 +02:00
|
|
|
writer.writeUShort(8)
|
|
|
|
writer.writeUShort(firstGlyphID)
|
2017-06-13 15:44:21 +02:00
|
|
|
writer.writeUShort(len(values))
|
|
|
|
for _, value in values:
|
2017-06-13 20:18:42 +02:00
|
|
|
self.valueConverter.write(
|
|
|
|
writer, font, tableDict=None,
|
|
|
|
value=value, repeatIndex=None)
|
2017-06-13 14:39:13 +02:00
|
|
|
|
2017-06-07 10:49:47 +02:00
|
|
|
def readFormat0(self, reader, numGlyphs):
|
|
|
|
data = reader.readUShortArray(numGlyphs)
|
|
|
|
return {k:v for (k,v) in enumerate(data)}
|
|
|
|
|
|
|
|
def readFormat2(self, reader):
|
|
|
|
mapping = {}
|
|
|
|
pos = reader.pos - 2 # start of table is at UShort for format
|
|
|
|
size = reader.readUShort()
|
|
|
|
assert size == 6, size
|
|
|
|
for i in range(reader.readUShort()):
|
|
|
|
reader.seek(pos + i * size + 12)
|
|
|
|
last = reader.readUShort()
|
|
|
|
first = reader.readUShort()
|
|
|
|
value = reader.readUShort()
|
|
|
|
if last != 0xFFFF:
|
|
|
|
for k in range(first, last + 1):
|
|
|
|
mapping[k] = value
|
|
|
|
return mapping
|
|
|
|
|
|
|
|
def readFormat4(self, reader):
|
|
|
|
mapping = {}
|
|
|
|
pos = reader.pos - 2 # start of table is at UShort for format
|
|
|
|
size = reader.readUShort()
|
|
|
|
assert size == 6, size
|
|
|
|
for i in range(reader.readUShort()):
|
|
|
|
reader.seek(pos + i * size + 12)
|
|
|
|
last = reader.readUShort()
|
|
|
|
first = reader.readUShort()
|
|
|
|
offset = reader.readUShort()
|
|
|
|
if last != 0xFFFF:
|
|
|
|
dataReader = reader.getSubReader(pos + offset)
|
|
|
|
data = dataReader.readUShortArray(last - first + 1)
|
|
|
|
for k, v in enumerate(data):
|
|
|
|
mapping[first + k] = v
|
|
|
|
return mapping
|
|
|
|
|
|
|
|
def readFormat6(self, reader):
|
|
|
|
mapping = {}
|
|
|
|
pos = reader.pos - 2 # start of table is at UShort for format
|
|
|
|
size = reader.readUShort()
|
|
|
|
assert size == 4, size
|
|
|
|
for i in range(reader.readUShort()):
|
|
|
|
reader.seek(pos + i * size + 12)
|
|
|
|
glyph = reader.readUShort()
|
|
|
|
value = reader.readUShort()
|
|
|
|
if glyph != 0xFFFF:
|
|
|
|
mapping[glyph] = value
|
|
|
|
return mapping
|
|
|
|
|
|
|
|
def readFormat8(self, reader):
|
|
|
|
first = reader.readUShort()
|
|
|
|
count = reader.readUShort()
|
|
|
|
data = reader.readUShortArray(count)
|
|
|
|
return {(first + k):v for (k, v) in enumerate(data)}
|
|
|
|
|
2017-06-07 15:00:01 +02:00
|
|
|
def xmlRead(self, attrs, content, font):
|
|
|
|
value = {}
|
|
|
|
for element in content:
|
|
|
|
if isinstance(element, tuple):
|
|
|
|
name, a, eltContent = element
|
|
|
|
if name == "Substitution":
|
|
|
|
value[a["in"]] = a["out"]
|
|
|
|
return value
|
|
|
|
|
2017-06-07 10:49:47 +02:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
2017-06-07 14:46:31 +02:00
|
|
|
xmlWriter.begintag(name, attrs)
|
|
|
|
xmlWriter.newline()
|
2017-06-07 10:49:47 +02:00
|
|
|
items = sorted(value.items())
|
|
|
|
for inGlyph, outGlyph in items:
|
|
|
|
xmlWriter.simpletag("Substitution",
|
|
|
|
[("in", inGlyph), ("out", outGlyph)])
|
|
|
|
xmlWriter.newline()
|
2017-06-07 14:46:31 +02:00
|
|
|
xmlWriter.endtag(name)
|
|
|
|
xmlWriter.newline()
|
2017-06-07 10:49:47 +02:00
|
|
|
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
class DeltaValue(BaseConverter):
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def read(self, reader, font, tableDict):
|
2013-11-24 17:10:55 -05:00
|
|
|
StartSize = tableDict["StartSize"]
|
|
|
|
EndSize = tableDict["EndSize"]
|
|
|
|
DeltaFormat = tableDict["DeltaFormat"]
|
2002-05-11 00:59:27 +00:00
|
|
|
assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
|
|
|
|
nItems = EndSize - StartSize + 1
|
|
|
|
nBits = 1 << DeltaFormat
|
|
|
|
minusOffset = 1 << nBits
|
|
|
|
mask = (1 << nBits) - 1
|
|
|
|
signMask = 1 << (nBits - 1)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
DeltaValue = []
|
|
|
|
tmp, shift = 0, 0
|
|
|
|
for i in range(nItems):
|
|
|
|
if shift == 0:
|
|
|
|
tmp, shift = reader.readUShort(), 16
|
|
|
|
shift = shift - nBits
|
|
|
|
value = (tmp >> shift) & mask
|
|
|
|
if value & signMask:
|
|
|
|
value = value - minusOffset
|
|
|
|
DeltaValue.append(value)
|
|
|
|
return DeltaValue
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-24 17:08:06 -05:00
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
2013-11-24 17:10:55 -05:00
|
|
|
StartSize = tableDict["StartSize"]
|
|
|
|
EndSize = tableDict["EndSize"]
|
|
|
|
DeltaFormat = tableDict["DeltaFormat"]
|
2013-11-24 16:09:57 -05:00
|
|
|
DeltaValue = value
|
2002-05-11 00:59:27 +00:00
|
|
|
assert DeltaFormat in (1, 2, 3), "illegal DeltaFormat"
|
|
|
|
nItems = EndSize - StartSize + 1
|
|
|
|
nBits = 1 << DeltaFormat
|
|
|
|
assert len(DeltaValue) == nItems
|
|
|
|
mask = (1 << nBits) - 1
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
tmp, shift = 0, 16
|
|
|
|
for value in DeltaValue:
|
|
|
|
shift = shift - nBits
|
|
|
|
tmp = tmp | ((value & mask) << shift)
|
|
|
|
if shift == 0:
|
|
|
|
writer.writeUShort(tmp)
|
|
|
|
tmp, shift = 0, 16
|
2013-11-27 02:40:30 -05:00
|
|
|
if shift != 16:
|
2002-05-11 00:59:27 +00:00
|
|
|
writer.writeUShort(tmp)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
xmlWriter.simpletag(name, attrs + [("value", value)])
|
|
|
|
xmlWriter.newline()
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
def xmlRead(self, attrs, content, font):
|
|
|
|
return safeEval(attrs["value"])
|
|
|
|
|
|
|
|
|
2016-08-10 01:17:45 -07:00
|
|
|
class VarIdxMapValue(BaseConverter):
|
|
|
|
|
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
fmt = tableDict['EntryFormat']
|
|
|
|
nItems = tableDict['MappingCount']
|
|
|
|
|
|
|
|
innerBits = 1 + (fmt & 0x000F)
|
|
|
|
innerMask = (1<<innerBits) - 1
|
|
|
|
outerMask = 0xFFFFFFFF - innerMask
|
|
|
|
outerShift = 16 - innerBits
|
|
|
|
|
|
|
|
entrySize = 1 + ((fmt & 0x0030) >> 4)
|
|
|
|
read = {
|
|
|
|
1: reader.readUInt8,
|
|
|
|
2: reader.readUShort,
|
|
|
|
3: reader.readUInt24,
|
|
|
|
4: reader.readULong,
|
|
|
|
}[entrySize]
|
|
|
|
|
|
|
|
mapping = []
|
|
|
|
for i in range(nItems):
|
|
|
|
raw = read()
|
|
|
|
idx = ((raw & outerMask) << outerShift) | (raw & innerMask)
|
|
|
|
mapping.append(idx)
|
|
|
|
|
|
|
|
return mapping
|
|
|
|
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
fmt = tableDict['EntryFormat']
|
|
|
|
mapping = value
|
|
|
|
writer['MappingCount'].setValue(len(mapping))
|
|
|
|
|
|
|
|
innerBits = 1 + (fmt & 0x000F)
|
|
|
|
innerMask = (1<<innerBits) - 1
|
|
|
|
outerShift = 16 - innerBits
|
|
|
|
|
|
|
|
entrySize = 1 + ((fmt & 0x0030) >> 4)
|
|
|
|
write = {
|
|
|
|
1: writer.writeUInt8,
|
|
|
|
2: writer.writeUShort,
|
|
|
|
3: writer.writeUInt24,
|
|
|
|
4: writer.writeULong,
|
|
|
|
}[entrySize]
|
|
|
|
|
|
|
|
for idx in mapping:
|
|
|
|
raw = ((idx & 0xFFFF0000) >> outerShift) | (idx & innerMask)
|
|
|
|
write(raw)
|
|
|
|
|
|
|
|
|
2016-08-10 03:15:38 -07:00
|
|
|
class VarDataValue(BaseConverter):
|
|
|
|
|
|
|
|
def read(self, reader, font, tableDict):
|
|
|
|
values = []
|
|
|
|
|
|
|
|
regionCount = tableDict["VarRegionCount"]
|
|
|
|
shortCount = tableDict["NumShorts"]
|
|
|
|
|
|
|
|
for i in range(min(regionCount, shortCount)):
|
|
|
|
values.append(reader.readShort())
|
|
|
|
for i in range(min(regionCount, shortCount), regionCount):
|
|
|
|
values.append(reader.readInt8())
|
|
|
|
for i in range(regionCount, shortCount):
|
|
|
|
reader.readInt8()
|
|
|
|
|
|
|
|
return values
|
|
|
|
|
|
|
|
def write(self, writer, font, tableDict, value, repeatIndex=None):
|
|
|
|
regionCount = tableDict["VarRegionCount"]
|
|
|
|
shortCount = tableDict["NumShorts"]
|
|
|
|
|
|
|
|
for i in range(min(regionCount, shortCount)):
|
|
|
|
writer.writeShort(value[i])
|
|
|
|
for i in range(min(regionCount, shortCount), regionCount):
|
|
|
|
writer.writeInt8(value[i])
|
|
|
|
for i in range(regionCount, shortCount):
|
|
|
|
writer.writeInt8(0)
|
|
|
|
|
|
|
|
def xmlWrite(self, xmlWriter, font, value, name, attrs):
|
|
|
|
xmlWriter.simpletag(name, attrs + [("value", value)])
|
|
|
|
xmlWriter.newline()
|
|
|
|
|
|
|
|
def xmlRead(self, attrs, content, font):
|
|
|
|
return safeEval(attrs["value"])
|
|
|
|
|
|
|
|
|
2002-05-11 00:59:27 +00:00
|
|
|
converterMapping = {
|
2015-04-26 00:54:30 -04:00
|
|
|
# type class
|
2016-06-06 22:01:09 -07:00
|
|
|
"int8": Int8,
|
2015-10-16 00:17:22 +02:00
|
|
|
"int16": Short,
|
|
|
|
"uint8": UInt8,
|
2016-06-06 22:01:09 -07:00
|
|
|
"uint8": UInt8,
|
2015-04-26 02:06:36 -04:00
|
|
|
"uint16": UShort,
|
|
|
|
"uint24": UInt24,
|
|
|
|
"uint32": ULong,
|
2017-03-10 12:17:14 +01:00
|
|
|
"char64": Char64,
|
2017-06-07 10:49:47 +02:00
|
|
|
"Flags32": Flags32,
|
2015-04-26 02:06:36 -04:00
|
|
|
"Version": Version,
|
|
|
|
"Tag": Tag,
|
|
|
|
"GlyphID": GlyphID,
|
2016-10-14 21:04:35 +02:00
|
|
|
"NameID": NameID,
|
2015-04-26 02:06:36 -04:00
|
|
|
"DeciPoints": DeciPoints,
|
|
|
|
"Fixed": Fixed,
|
2016-07-01 15:31:00 -07:00
|
|
|
"F2Dot14": F2Dot14,
|
2015-04-26 02:06:36 -04:00
|
|
|
"struct": Struct,
|
|
|
|
"Offset": Table,
|
|
|
|
"LOffset": LTable,
|
|
|
|
"ValueRecord": ValueRecord,
|
|
|
|
"DeltaValue": DeltaValue,
|
2016-08-10 01:17:45 -07:00
|
|
|
"VarIdxMapValue": VarIdxMapValue,
|
2016-08-10 03:15:38 -07:00
|
|
|
"VarDataValue": VarDataValue,
|
2017-06-07 10:49:47 +02:00
|
|
|
# AAT
|
|
|
|
"AATLookup": AATLookup,
|
|
|
|
"MorphChain": StructWithLength,
|
|
|
|
"MorphSubtable":StructWithLength,
|
2016-08-09 22:51:41 -07:00
|
|
|
# "Template" types
|
|
|
|
"OffsetTo": lambda C: partial(Table, tableClass=C),
|
|
|
|
"LOffsetTo": lambda C: partial(LTable, tableClass=C),
|
2002-05-11 00:59:27 +00:00
|
|
|
}
|