Work in progress on CFF, GPOS and GSUB. Since it's only partly working, it's diasabled by default.
git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@189 4cde692c-a291-49d1-8350-778aa11640f8
This commit is contained in:
parent
2a9c630193
commit
a36fd88a20
@ -2,6 +2,12 @@ import DefaultTable
|
|||||||
from fontTools import cffLib
|
from fontTools import cffLib
|
||||||
|
|
||||||
|
|
||||||
|
# temporary switch:
|
||||||
|
# - if true use possibly incomplete compile/decompile/toXML/fromXML implementation
|
||||||
|
# - if false use DefaultTable, ie. dump as hex.
|
||||||
|
TESTING_CFF = 0
|
||||||
|
|
||||||
|
|
||||||
class table_C_F_F_(DefaultTable.DefaultTable):
|
class table_C_F_F_(DefaultTable.DefaultTable):
|
||||||
|
|
||||||
def __init__(self, tag):
|
def __init__(self, tag):
|
||||||
@ -28,11 +34,11 @@ class table_C_F_F_(DefaultTable.DefaultTable):
|
|||||||
self.cff.fonts[self.cff.fontNames[0]].setGlyphOrder(glyphOrder)
|
self.cff.fonts[self.cff.fontNames[0]].setGlyphOrder(glyphOrder)
|
||||||
|
|
||||||
def toXML(self, writer, otFont, progress=None):
|
def toXML(self, writer, otFont, progress=None):
|
||||||
if "disableCFFdump":
|
if TESTING_CFF:
|
||||||
|
self.cff.toXML(writer, progress)
|
||||||
|
else:
|
||||||
# dump as hex as long as we can't compile
|
# dump as hex as long as we can't compile
|
||||||
DefaultTable.DefaultTable.toXML(self, writer, otFont)
|
DefaultTable.DefaultTable.toXML(self, writer, otFont)
|
||||||
else:
|
|
||||||
self.cff.toXML(writer, progress)
|
|
||||||
|
|
||||||
#def fromXML(self, (name, attrs, content), otFont):
|
#def fromXML(self, (name, attrs, content), otFont):
|
||||||
# xxx
|
# xxx
|
||||||
|
@ -10,17 +10,39 @@ class table_G_P_O_S_(otCommon.base_GPOS_GSUB):
|
|||||||
class SinglePos:
|
class SinglePos:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
pass
|
self.format = reader.readUShort()
|
||||||
|
if self.format == 1:
|
||||||
|
self.decompileFormat1(reader, otFont)
|
||||||
|
elif self.format == 2:
|
||||||
|
self.decompileFormat2(reader, otFont)
|
||||||
|
else:
|
||||||
|
from fontTools import ttLib
|
||||||
|
raise ttLib.TTLibError, "unknown SinglePos format: %d" % self.format
|
||||||
|
|
||||||
def compile(self, otFont):
|
def decompileFormat1(self, reader, otFont):
|
||||||
|
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
||||||
|
valueFactory = ValueRecordFactory(reader.readUShort())
|
||||||
|
self.coverage = coverage.getGlyphNames()
|
||||||
|
self.value = valueFactory.readValueRecord(reader, otFont)
|
||||||
|
|
||||||
|
def decompileFormat2(self, reader, otFont):
|
||||||
|
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
||||||
|
valueFactory = ValueRecordFactory(reader.readUShort())
|
||||||
|
valueCount = reader.readUShort()
|
||||||
|
glyphNames = coverage.getGlyphNames()
|
||||||
|
self.pos = pos = {}
|
||||||
|
for i in range(valueCount):
|
||||||
|
pos[glyphNames[i]] = valueFactory.readValueRecord(reader, otFont)
|
||||||
|
|
||||||
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class PairPos:
|
class PairPos:
|
||||||
@ -43,11 +65,8 @@ class PairPos:
|
|||||||
self.pairs = pairs = {}
|
self.pairs = pairs = {}
|
||||||
for i in range(reader.readUShort()):
|
for i in range(reader.readUShort()):
|
||||||
firstGlyphName = glyphNames[i]
|
firstGlyphName = glyphNames[i]
|
||||||
offset = reader.readOffset()
|
set = reader.readTable(PairSet, otFont, valueFactory1, valueFactory2)
|
||||||
setData = reader.getSubString(offset)
|
pairs[firstGlyphName] = set.getValues()
|
||||||
set = PairSet()
|
|
||||||
set.decompile(setData, otFont, valueFactory1, valueFactory2)
|
|
||||||
pairs[firstGlyphName] = set.values
|
|
||||||
|
|
||||||
def decompileFormat2(self, reader, otFont):
|
def decompileFormat2(self, reader, otFont):
|
||||||
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
||||||
@ -62,14 +81,48 @@ class PairPos:
|
|||||||
for i in range(class1Count):
|
for i in range(class1Count):
|
||||||
row = {}
|
row = {}
|
||||||
for j in range(class2Count):
|
for j in range(class2Count):
|
||||||
value1 = valueFactory1.getValueRecord(reader)
|
value1 = valueFactory1.readValueRecord(reader, otFont)
|
||||||
value2 = valueFactory2.getValueRecord(reader)
|
value2 = valueFactory2.readValueRecord(reader, otFont)
|
||||||
if value1 or value2:
|
if value1 or value2:
|
||||||
row[j] = (value1, value2)
|
row[j] = (value1, value2)
|
||||||
if row:
|
if row:
|
||||||
pairs[i] = row
|
pairs[i] = row
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
|
if self.format == 1:
|
||||||
|
self.compileFormat1(writer, otFont)
|
||||||
|
elif self.format == 2:
|
||||||
|
self.compileFormat2(writer, otFont)
|
||||||
|
else:
|
||||||
|
from fontTools import ttLib
|
||||||
|
raise ttLib.TTLibError, "unknown PairPos format: %d" % self.format
|
||||||
|
|
||||||
|
def compileFormat1(self, writer, otFont):
|
||||||
|
pairs = self.pairs
|
||||||
|
glyphNames = pairs.keys()
|
||||||
|
coverage = otCommon.CoverageTable()
|
||||||
|
glyphNames = coverage.setGlyphNames(glyphNames, otFont)
|
||||||
|
writer.writeTable(coverage, otFont)
|
||||||
|
# dumb approach: just take the first pair and grab the value.
|
||||||
|
dummy, sample1, sample2 = pairs[pairs.keys()[0]][0]
|
||||||
|
valueFormat1 = valueFormat2 = 0
|
||||||
|
if sample1:
|
||||||
|
valueFormat1 = sample1.getFormat()
|
||||||
|
if sample2:
|
||||||
|
valueFormat2 = sample2.getFormat()
|
||||||
|
writer.writeUShort(valueFormat1)
|
||||||
|
writer.writeUShort(valueFormat2)
|
||||||
|
|
||||||
|
valueFactory1 = ValueRecordFactory(valueFormat1)
|
||||||
|
valueFactory2 = ValueRecordFactory(valueFormat2)
|
||||||
|
|
||||||
|
writer.writeUShort(len(pairs))
|
||||||
|
for glyphName in glyphNames:
|
||||||
|
set = PairSet(valueFactory1, valueFactory2)
|
||||||
|
set.setValues(pairs[glyphName])
|
||||||
|
writer.writeTable(set, otFont)
|
||||||
|
|
||||||
|
def compileFormat2(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
@ -86,38 +139,56 @@ class PairPos:
|
|||||||
pairs.sort()
|
pairs.sort()
|
||||||
for firstGlyph, secondGlyphs in pairs:
|
for firstGlyph, secondGlyphs in pairs:
|
||||||
for secondGlyph, value1, value2 in secondGlyphs:
|
for secondGlyph, value1, value2 in secondGlyphs:
|
||||||
#XXXXXXXXX
|
xmlWriter.begintag("Pair", pair=firstGlyph+","+secondGlyph)
|
||||||
xmlWriter.begintag("Pair", first=firstGlyph, second=secondGlyph)
|
|
||||||
xmlWriter.newline()
|
|
||||||
if value1:
|
if value1:
|
||||||
value1.toXML(xmlWriter, otFont)
|
value1.toXML(xmlWriter, otFont)
|
||||||
|
else:
|
||||||
|
xmlWriter.simpletag("Value")
|
||||||
if value2:
|
if value2:
|
||||||
value2.toXML(xmlWriter, otFont)
|
value2.toXML(xmlWriter, otFont)
|
||||||
|
#else: # the second value can be omitted
|
||||||
|
# xmlWriter.simpletag("Value")
|
||||||
xmlWriter.endtag("Pair")
|
xmlWriter.endtag("Pair")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def toXMLFormat2(self, xmlWriter, otFont):
|
def toXMLFormat2(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class PairSet:
|
class PairSet:
|
||||||
|
|
||||||
def decompile(self, reader, otFont, valueFactory1, valueFactory2):
|
def __init__(self, valueFactory1=None, valueFactory2=None):
|
||||||
|
self.valueFactory1 = valueFactory1
|
||||||
|
self.valueFactory2 = valueFactory2
|
||||||
|
|
||||||
|
def getValues(self):
|
||||||
|
return self.values
|
||||||
|
|
||||||
|
def setValues(self, values):
|
||||||
|
self.values = values
|
||||||
|
|
||||||
|
def decompile(self, reader, otFont):
|
||||||
pairValueCount = reader.readUShort()
|
pairValueCount = reader.readUShort()
|
||||||
self.values = values = []
|
self.values = values = []
|
||||||
for j in range(pairValueCount):
|
for j in range(pairValueCount):
|
||||||
secondGlyphID = reader.readUShort()
|
secondGlyphID = reader.readUShort()
|
||||||
secondGlyphName = otFont.getGlyphName(secondGlyphID)
|
secondGlyphName = otFont.getGlyphName(secondGlyphID)
|
||||||
value1 = valueFactory1.getValueRecord(reader)
|
value1 = self.valueFactory1.readValueRecord(reader, otFont)
|
||||||
value2 = valueFactory2.getValueRecord(reader)
|
value2 = self.valueFactory2.readValueRecord(reader, otFont)
|
||||||
values.append((secondGlyphName, value1, value2))
|
values.append((secondGlyphName, value1, value2))
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
values = self.values
|
||||||
|
writer.writeUShort(len(values))
|
||||||
|
for secondGlyphName, value1, value2 in values:
|
||||||
|
writer.writeUShort(otFont.getGlyphID(secondGlyphName))
|
||||||
|
self.valueFactory1.writeValuerecord(value1, writer, otFont)
|
||||||
|
self.valueFactory2.writeValuerecord(value2, writer, otFont)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# ------------------
|
# ------------------
|
||||||
@ -126,81 +197,180 @@ class PairSet:
|
|||||||
class CursivePos:
|
class CursivePos:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
pass
|
xxx
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
class MarkBasePos:
|
class MarkBasePos:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
pass
|
xxx
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
class MarkLigPos:
|
class MarkLigPos:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
pass
|
xxx
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
class MarkMarkPos:
|
class MarkMarkPos:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
pass
|
xxx
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
class ContextPos:
|
class ContextPos:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
pass
|
xxx
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
class ChainContextPos:
|
class ChainContextPos:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
pass
|
xxx
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
|
valueRecordFormat = [
|
||||||
|
# Mask Name isDevice struct format char
|
||||||
|
(0x0001, "XPlacement", 0, "h"),
|
||||||
|
(0x0002, "YPlacement", 0, "h"),
|
||||||
|
(0x0004, "XAdvance", 0, "h"),
|
||||||
|
(0x0008, "YAdvance", 0, "h"),
|
||||||
|
(0x0010, "XPlaDevice", 1, "H"),
|
||||||
|
(0x0020, "YPlaDevice", 1, "H"),
|
||||||
|
(0x0040, "XAdvDevice", 1, "H"),
|
||||||
|
(0x0080, "YAdvDevice", 1, "H"),
|
||||||
|
# reserved:
|
||||||
|
(0x0100, "Reserved1", 0, "H"),
|
||||||
|
(0x0200, "Reserved2", 0, "H"),
|
||||||
|
(0x0400, "Reserved3", 0, "H"),
|
||||||
|
(0x0800, "Reserved4", 0, "H"),
|
||||||
|
(0x1000, "Reserved5", 0, "H"),
|
||||||
|
(0x2000, "Reserved6", 0, "H"),
|
||||||
|
(0x4000, "Reserved7", 0, "H"),
|
||||||
|
(0x8000, "Reserved8", 0, "H"),
|
||||||
|
]
|
||||||
|
|
||||||
|
valueRecordFormatDict = {}
|
||||||
|
for mask, name, isDevice, format in valueRecordFormat:
|
||||||
|
valueRecordFormatDict[name] = mask, isDevice, format
|
||||||
|
|
||||||
|
|
||||||
|
class ValueRecordFactory:
|
||||||
|
|
||||||
|
def __init__(self, valueFormat):
|
||||||
|
format = ">"
|
||||||
|
names = []
|
||||||
|
for mask, name, isDevice, formatChar in valueRecordFormat:
|
||||||
|
if valueFormat & mask:
|
||||||
|
names.append((name, isDevice))
|
||||||
|
format = format + formatChar
|
||||||
|
self.names, self.format = names, format
|
||||||
|
self.size = 2 * len(names)
|
||||||
|
|
||||||
|
def readValueRecord(self, reader, otFont):
|
||||||
|
names = self.names
|
||||||
|
if not names:
|
||||||
|
return None
|
||||||
|
values = reader.readStruct(self.format, self.size)
|
||||||
|
values = map(int, values)
|
||||||
|
valueRecord = ValueRecord()
|
||||||
|
items = map(None, names, values)
|
||||||
|
for (name, isDevice), value in items:
|
||||||
|
if isDevice:
|
||||||
|
if value:
|
||||||
|
device = otCommon.DeviceTable()
|
||||||
|
device.decompile(reader.getSubString(value), otFont)
|
||||||
|
else:
|
||||||
|
device = None
|
||||||
|
setattr(valueRecord, name, device)
|
||||||
|
else:
|
||||||
|
setattr(valueRecord, name, value)
|
||||||
|
return valueRecord
|
||||||
|
|
||||||
|
def writeValuerecord(self, valueRecord, writer, otFont):
|
||||||
|
values = []
|
||||||
|
for (name, isDevice) in self.names:
|
||||||
|
if isDevice:
|
||||||
|
raise NotImplementedError
|
||||||
|
else:
|
||||||
|
values.append(valueRecord.__dict__.get(name, 0))
|
||||||
|
writer.writeStruct(self.format, tuple(values))
|
||||||
|
|
||||||
|
|
||||||
|
class ValueRecord:
|
||||||
|
# see ValueRecordFactory
|
||||||
|
|
||||||
|
def getFormat(self):
|
||||||
|
format = 0
|
||||||
|
for name in self.__dict__.keys():
|
||||||
|
format = format | valueRecordFormatDict[name][0]
|
||||||
|
return format
|
||||||
|
|
||||||
|
def toXML(self, xmlWriter, otFont):
|
||||||
|
simpleItems = []
|
||||||
|
for mask, name, isDevice, format in valueRecordFormat[:4]: # "simple" values
|
||||||
|
if hasattr(self, name):
|
||||||
|
simpleItems.append((name, getattr(self, name)))
|
||||||
|
deviceItems = []
|
||||||
|
for mask, name, isDevice, format in valueRecordFormat[4:8]: # device records
|
||||||
|
if hasattr(self, name):
|
||||||
|
deviceItems.append((name, getattr(self, name)))
|
||||||
|
if deviceItems:
|
||||||
|
xmlWriter.begintag("Value", simpleItems)
|
||||||
|
xmlWriter.newline()
|
||||||
|
for name, deviceRecord in deviceItems:
|
||||||
|
xxx
|
||||||
|
xmlWriter.endtag("Value")
|
||||||
|
else:
|
||||||
|
xmlWriter.simpletag("Value", simpleItems)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<ValueRecord>"
|
||||||
|
|
||||||
|
|
||||||
lookupTypeClasses = {
|
lookupTypeClasses = {
|
||||||
1: SinglePos,
|
1: SinglePos,
|
||||||
2: PairPos,
|
2: PairPos,
|
||||||
@ -212,83 +382,3 @@ lookupTypeClasses = {
|
|||||||
8: ChainContextPos,
|
8: ChainContextPos,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
valueRecordFormat = [
|
|
||||||
# Mask Name struct format char
|
|
||||||
(0x0001, "XPlacement", "h"),
|
|
||||||
(0x0002, "YPlacement", "h"),
|
|
||||||
(0x0004, "XAdvance", "h"),
|
|
||||||
(0x0008, "YAdvance", "h"),
|
|
||||||
(0x0010, "XPlaDevice", "H"),
|
|
||||||
(0x0020, "YPlaDevice", "H"),
|
|
||||||
(0x0040, "XAdvDevice", "H"),
|
|
||||||
(0x0080, "YAdvDevice", "H"),
|
|
||||||
# reserved:
|
|
||||||
(0x0100, "Reserved1", "H"),
|
|
||||||
(0x0200, "Reserved2", "H"),
|
|
||||||
(0x0400, "Reserved3", "H"),
|
|
||||||
(0x0800, "Reserved4", "H"),
|
|
||||||
(0x1000, "Reserved5", "H"),
|
|
||||||
(0x2000, "Reserved6", "H"),
|
|
||||||
(0x4000, "Reserved7", "H"),
|
|
||||||
(0x8000, "Reserved8", "H"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ValueRecordFactory:
|
|
||||||
|
|
||||||
def __init__(self, valueFormat):
|
|
||||||
format = ">"
|
|
||||||
names = []
|
|
||||||
for mask, name, formatChar in valueRecordFormat:
|
|
||||||
if valueFormat & mask:
|
|
||||||
names.append(name)
|
|
||||||
format = format + formatChar
|
|
||||||
self.names, self.format = names, format
|
|
||||||
self.size = 2 * len(names)
|
|
||||||
|
|
||||||
def getValueRecord(self, reader):
|
|
||||||
names = self.names
|
|
||||||
if not names:
|
|
||||||
return None
|
|
||||||
values = reader.readStruct(self.format, self.size)
|
|
||||||
values = map(int, values)
|
|
||||||
valueRecord = ValueRecord()
|
|
||||||
items = map(None, names, values)
|
|
||||||
for name, value in items:
|
|
||||||
setattr(valueRecord, name, value)
|
|
||||||
return valueRecord
|
|
||||||
|
|
||||||
|
|
||||||
class ValueRecord:
|
|
||||||
# see ValueRecordFactory
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
for value in self.__dict__.values():
|
|
||||||
if value:
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
|
||||||
simpleItems = []
|
|
||||||
for mask, name, format in valueRecordFormat[:4]: # "simple" values
|
|
||||||
if hasattr(self, name):
|
|
||||||
simpleItems.append((name, getattr(self, name)))
|
|
||||||
deviceItems = []
|
|
||||||
for mask, name, format in valueRecordFormat[4:8]: # device records
|
|
||||||
if hasattr(self, name):
|
|
||||||
deviceItems.append((name, getattr(self, name)))
|
|
||||||
if deviceItems:
|
|
||||||
xmlWriter.begintag("ValueRecord", simpleItems)
|
|
||||||
xmlWriter.newline()
|
|
||||||
for name, deviceRecord in deviceItems:
|
|
||||||
xxx
|
|
||||||
xmlWriter.endtag("ValueRecord")
|
|
||||||
xmlWriter.newline()
|
|
||||||
else:
|
|
||||||
xmlWriter.simpletag("ValueRecord", simpleItems)
|
|
||||||
xmlWriter.newline()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<ValueRecord>"
|
|
||||||
|
|
||||||
|
@ -41,9 +41,33 @@ class SingleSubst:
|
|||||||
input = glyphNames[i]
|
input = glyphNames[i]
|
||||||
substitutions[input] = output
|
substitutions[input] = output
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
|
writer.writeUShort(self.format)
|
||||||
|
if self.format == 1:
|
||||||
|
self.compileFormat1(writer, otFont)
|
||||||
|
elif self.format == 2:
|
||||||
|
self.compileFormat2(writer, otFont)
|
||||||
|
else:
|
||||||
|
from fontTools import ttLib
|
||||||
|
raise ttLib.TTLibError, "unknown SingleSub format: %d" % self.format
|
||||||
|
|
||||||
|
def compileFormat1(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
|
def compileFormat2(self, writer, otFont):
|
||||||
|
substitutions = self.substitutions
|
||||||
|
coverage = otCommon.CoverageTable()
|
||||||
|
glyphNames = substitutions.keys()
|
||||||
|
glyphNames = coverage.setGlyphNames(glyphNames, otFont)
|
||||||
|
|
||||||
|
writer.writeTable(coverage, otFont)
|
||||||
|
writer.writeUShort(len(substitutions))
|
||||||
|
|
||||||
|
for i in range(len(substitutions)):
|
||||||
|
glyphName = glyphNames[i]
|
||||||
|
output = substitutions[glyphName]
|
||||||
|
writer.writeUShort(otFont.getGlyphID(output))
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
substitutions = self.substitutions.items()
|
substitutions = self.substitutions.items()
|
||||||
substitutions.sort()
|
substitutions.sort()
|
||||||
@ -52,7 +76,7 @@ class SingleSubst:
|
|||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class MultipleSubst:
|
class MultipleSubst:
|
||||||
@ -67,9 +91,9 @@ class MultipleSubst:
|
|||||||
self.substitutions = substitutions = {}
|
self.substitutions = substitutions = {}
|
||||||
for i in range(sequenceCount):
|
for i in range(sequenceCount):
|
||||||
sequence = reader.readTable(Sequence, otFont)
|
sequence = reader.readTable(Sequence, otFont)
|
||||||
substitutions[glyphNames[i]] = sequence.glyphs
|
substitutions[glyphNames[i]] = sequence.getGlyphs()
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
@ -83,12 +107,15 @@ class MultipleSubst:
|
|||||||
|
|
||||||
class Sequence:
|
class Sequence:
|
||||||
|
|
||||||
|
def getGlyphs(self):
|
||||||
|
return self.glyphs
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
self.glyphs = []
|
self.glyphs = []
|
||||||
for i in range(reader.readUShort()):
|
for i in range(reader.readUShort()):
|
||||||
self.glyphs.append(otFont.getGlyphName(reader.readUShort()))
|
self.glyphs.append(otFont.getGlyphName(reader.readUShort()))
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
|
|
||||||
@ -102,16 +129,30 @@ class AlternateSubst:
|
|||||||
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
||||||
glyphNames = coverage.getGlyphNames()
|
glyphNames = coverage.getGlyphNames()
|
||||||
alternateSetCount = reader.readUShort()
|
alternateSetCount = reader.readUShort()
|
||||||
self.alternateSet = alternateSet = {}
|
self.alternateSets = alternateSets = {}
|
||||||
for i in range(alternateSetCount):
|
for i in range(alternateSetCount):
|
||||||
set = reader.readTable(AlternateSet, otFont)
|
set = reader.readTable(AlternateSet, otFont)
|
||||||
alternateSet[glyphNames[i]] = set.glyphs
|
alternateSets[glyphNames[i]] = set.getGlyphs()
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
writer.writeUShort(1) # format = 1
|
||||||
|
alternateSets = self.alternateSets
|
||||||
|
alternateSetCount = len(alternateSets)
|
||||||
|
glyphNames = alternateSets.keys()
|
||||||
|
coverage = otCommon.CoverageTable()
|
||||||
|
glyphNames = coverage.setGlyphNames(glyphNames, otFont)
|
||||||
|
|
||||||
|
writer.writeTable(coverage, otFont)
|
||||||
|
writer.writeUShort(alternateSetCount)
|
||||||
|
|
||||||
|
for i in range(alternateSetCount):
|
||||||
|
glyphName = glyphNames[i]
|
||||||
|
set = AlternateSet()
|
||||||
|
set.setGlyphs(alternateSets[glyphName])
|
||||||
|
writer.writeTable(set, otFont)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
alternates = self.alternateSet.items()
|
alternates = self.alternateSets.items()
|
||||||
alternates.sort()
|
alternates.sort()
|
||||||
for input, substList in alternates:
|
for input, substList in alternates:
|
||||||
xmlWriter.begintag("AlternateSet", [("in", input)])
|
xmlWriter.begintag("AlternateSet", [("in", input)])
|
||||||
@ -125,22 +166,31 @@ class AlternateSubst:
|
|||||||
|
|
||||||
class AlternateSet:
|
class AlternateSet:
|
||||||
|
|
||||||
|
def getGlyphs(self):
|
||||||
|
return self.glyphs
|
||||||
|
|
||||||
|
def setGlyphs(self, glyphs):
|
||||||
|
self.glyphs = glyphs
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
glyphCount = reader.readUShort()
|
glyphCount = reader.readUShort()
|
||||||
glyphIDs = reader.readUShortArray(glyphCount)
|
glyphIDs = reader.readUShortArray(glyphCount)
|
||||||
self.glyphs = map(otFont.getGlyphName, glyphIDs)
|
self.glyphs = map(otFont.getGlyphName, glyphIDs)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
glyphs = self.glyphs
|
||||||
|
writer.writeUShort(len(glyphs))
|
||||||
|
glyphIDs = map(otFont.getGlyphID, glyphs)
|
||||||
|
writer.writeUShortArray(glyphIDs)
|
||||||
|
|
||||||
|
|
||||||
class LigatureSubst:
|
class LigatureSubst:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
format = reader.readUShort()
|
self.format = reader.readUShort()
|
||||||
if format <> 1:
|
if self.format <> 1:
|
||||||
from fontTools import ttLib
|
from fontTools import ttLib
|
||||||
raise ttLib.TTLibError, "unknown LigatureSubst format: %d" % format
|
raise ttLib.TTLibError, "unknown LigatureSubst format: %d" % self.format
|
||||||
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
coverage = reader.readTable(otCommon.CoverageTable, otFont)
|
||||||
glyphNames = coverage.getGlyphNames()
|
glyphNames = coverage.getGlyphNames()
|
||||||
ligSetCount = reader.readUShort()
|
ligSetCount = reader.readUShort()
|
||||||
@ -148,11 +198,33 @@ class LigatureSubst:
|
|||||||
for i in range(ligSetCount):
|
for i in range(ligSetCount):
|
||||||
firstGlyph = glyphNames[i]
|
firstGlyph = glyphNames[i]
|
||||||
ligSet = reader.readTable(LigatureSet, otFont)
|
ligSet = reader.readTable(LigatureSet, otFont)
|
||||||
for ligatureGlyph, components in ligSet.ligatures:
|
for components, ligatureGlyph in ligSet.getLigatures():
|
||||||
ligatures.append(((firstGlyph,) + tuple(components)), ligatureGlyph)
|
ligatures.append((((firstGlyph,) + tuple(components)), ligatureGlyph))
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
lastGlyph = None
|
||||||
|
sets = {}
|
||||||
|
currentSet = None
|
||||||
|
for input, output in self.ligatures:
|
||||||
|
firstGlyph = input[0]
|
||||||
|
if firstGlyph <> lastGlyph:
|
||||||
|
assert not sets.has_key(firstGlyph)
|
||||||
|
currentSet = LigatureSet()
|
||||||
|
sets[firstGlyph] = currentSet
|
||||||
|
lastGlyph = firstGlyph
|
||||||
|
currentSet.appendLigature(input[1:], output)
|
||||||
|
|
||||||
|
glyphNames = sets.keys()
|
||||||
|
coverage = otCommon.CoverageTable()
|
||||||
|
glyphNames = coverage.setGlyphNames(glyphNames, otFont)
|
||||||
|
|
||||||
|
writer.writeUShort(self.format)
|
||||||
|
writer.writeTable(coverage, otFont)
|
||||||
|
writer.writeUShort(len(sets))
|
||||||
|
|
||||||
|
for i in range(len(glyphNames)):
|
||||||
|
set = sets[glyphNames[i]]
|
||||||
|
writer.writeTable(set, otFont)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
import string
|
import string
|
||||||
@ -163,19 +235,39 @@ class LigatureSubst:
|
|||||||
|
|
||||||
class LigatureSet:
|
class LigatureSet:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ligatures = []
|
||||||
|
|
||||||
|
def getLigatures(self):
|
||||||
|
return self.ligatures
|
||||||
|
|
||||||
|
def appendLigature(self, components, ligatureGlyph):
|
||||||
|
self.ligatures.append((components, ligatureGlyph))
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
ligatureCount = reader.readUShort()
|
ligatureCount = reader.readUShort()
|
||||||
self.ligatures = ligatures = []
|
self.ligatures = ligatures = []
|
||||||
for i in range(ligatureCount):
|
for i in range(ligatureCount):
|
||||||
lig = reader.readTable(Ligature, otFont)
|
lig = reader.readTable(Ligature, otFont)
|
||||||
ligatures.append((lig.ligatureGlyph, lig.components))
|
ligatures.append(lig.get())
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
writer.writeUShort(len(self.ligatures))
|
||||||
|
|
||||||
|
for components, output in self.ligatures:
|
||||||
|
lig = Ligature()
|
||||||
|
lig.set(components, output)
|
||||||
|
writer.writeTable(lig, otFont)
|
||||||
|
|
||||||
|
|
||||||
class Ligature:
|
class Ligature:
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return self.components, self.ligatureGlyph
|
||||||
|
|
||||||
|
def set(self, components, ligatureGlyph):
|
||||||
|
self.components, self.ligatureGlyph = components, ligatureGlyph
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
self.ligatureGlyph = otFont.getGlyphName(reader.readUShort())
|
self.ligatureGlyph = otFont.getGlyphName(reader.readUShort())
|
||||||
compCount = reader.readUShort()
|
compCount = reader.readUShort()
|
||||||
@ -183,8 +275,12 @@ class Ligature:
|
|||||||
for i in range(compCount-1):
|
for i in range(compCount-1):
|
||||||
components.append(otFont.getGlyphName(reader.readUShort()))
|
components.append(otFont.getGlyphName(reader.readUShort()))
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
ligGlyphID = otFont.getGlyphID(self.ligatureGlyph)
|
||||||
|
writer.writeUShort(ligGlyphID)
|
||||||
|
writer.writeUShort(len(self.components) + 1)
|
||||||
|
for compo in self.components:
|
||||||
|
writer.writeUShort(otFont.getGlyphID(compo))
|
||||||
|
|
||||||
|
|
||||||
class ContextSubst:
|
class ContextSubst:
|
||||||
@ -219,11 +315,11 @@ class ContextSubst:
|
|||||||
lookupRecord.decompile(reader, otFont)
|
lookupRecord.decompile(reader, otFont)
|
||||||
substitutions.append((coverage[i].getGlyphNames(), lookupRecord))
|
substitutions.append((coverage[i].getGlyphNames(), lookupRecord))
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
xxx
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
@ -242,19 +338,63 @@ class ChainContextSubst:
|
|||||||
raise ttLib.TTLibError, "unknown ChainContextSubst format: %d" % self.format
|
raise ttLib.TTLibError, "unknown ChainContextSubst format: %d" % self.format
|
||||||
|
|
||||||
def decompileFormat1(self, reader, otFont):
|
def decompileFormat1(self, reader, otFont):
|
||||||
pass
|
XXX
|
||||||
|
|
||||||
def decompileFormat2(self, reader, otFont):
|
def decompileFormat2(self, reader, otFont):
|
||||||
pass
|
XXX
|
||||||
|
|
||||||
def decompileFormat3(self, reader, otFont):
|
def decompileFormat3(self, reader, otFont):
|
||||||
pass
|
backtrackGlyphCount = reader.readUShort()
|
||||||
|
backtrackCoverage = reader.readTableArray(backtrackGlyphCount, otCommon.CoverageTable, otFont)
|
||||||
|
self.backtrack = otCommon.unpackCoverageArray(backtrackCoverage)
|
||||||
|
|
||||||
def compile(self, otFont):
|
inputGlyphCount = reader.readUShort()
|
||||||
xxx
|
inputCoverage = reader.readTableArray(inputGlyphCount, otCommon.CoverageTable, otFont)
|
||||||
|
self.input = otCommon.unpackCoverageArray(inputCoverage)
|
||||||
|
|
||||||
|
lookaheadGlyphCount = reader.readUShort()
|
||||||
|
lookaheadCoverage = reader.readTableArray(lookaheadGlyphCount, otCommon.CoverageTable, otFont)
|
||||||
|
self.lookahead = otCommon.unpackCoverageArray(lookaheadCoverage)
|
||||||
|
|
||||||
|
substCount = reader.readUShort()
|
||||||
|
self.substitutions = reader.readTableArray(substCount, SubstLookupRecord, otFont)
|
||||||
|
|
||||||
|
def compile(self, writer, otFont):
|
||||||
|
writer.writeUShort(self.format)
|
||||||
|
if self.format == 1:
|
||||||
|
self.compileFormat1(writer, otFont)
|
||||||
|
elif self.format == 2:
|
||||||
|
self.compileFormat2(writer, otFont)
|
||||||
|
elif self.format == 3:
|
||||||
|
self.compileFormat3(writer, otFont)
|
||||||
|
else:
|
||||||
|
from fontTools import ttLib
|
||||||
|
raise ttLib.TTLibError, "unknown ChainContextSubst format: %d" % self.format
|
||||||
|
|
||||||
|
def compileFormat1(self, writer, otFont):
|
||||||
|
XXX
|
||||||
|
|
||||||
|
def compileFormat2(self, writer, otFont):
|
||||||
|
XXX
|
||||||
|
|
||||||
|
def compileFormat3(self, writer, otFont):
|
||||||
|
writer.writeUShort(len(self.backtrack))
|
||||||
|
backtrack = otCommon.buildCoverageArray(self.backtrack, otFont)
|
||||||
|
writer.writeTableArray(backtrack, otFont)
|
||||||
|
|
||||||
|
writer.writeUShort(len(self.input))
|
||||||
|
input = otCommon.buildCoverageArray(self.input, otFont)
|
||||||
|
writer.writeTableArray(input, otFont)
|
||||||
|
|
||||||
|
writer.writeUShort(len(self.lookahead))
|
||||||
|
lookahead = otCommon.buildCoverageArray(self.lookahead, otFont)
|
||||||
|
writer.writeTableArray(lookahead, otFont)
|
||||||
|
|
||||||
|
writer.writeUShort(len(self.substitutions))
|
||||||
|
writer.writeTableArray(self.substitutions, otFont)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.comment("XXX")
|
xmlWriter.comment("NotImplemented")
|
||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
|
|
||||||
@ -278,6 +418,7 @@ class SubstLookupRecord:
|
|||||||
self.sequenceIndex = reader.readUShort()
|
self.sequenceIndex = reader.readUShort()
|
||||||
self.lookupListIndex = reader.readUShort()
|
self.lookupListIndex = reader.readUShort()
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
writer.writeUShort(self.sequenceIndex)
|
||||||
|
writer.writeUShort(self.lookupListIndex)
|
||||||
|
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
"""ttLib.tables.otCommon.py -- Various data structures used by various OpenType tables.
|
"""fontTools.ttLib.tables.otCommon.py -- Various data structures used
|
||||||
"""
|
by various OpenType tables."""
|
||||||
|
|
||||||
import struct, sstruct
|
import struct, sstruct
|
||||||
|
import string
|
||||||
import DefaultTable
|
import DefaultTable
|
||||||
from fontTools import ttLib
|
from fontTools import ttLib
|
||||||
|
|
||||||
|
# temporary switch:
|
||||||
|
# - if true use possibly incomplete compile/decompile/toXML/fromXML implementation
|
||||||
|
# - if false use DefaultTable, ie. dump as hex.
|
||||||
|
TESTING_OT = 0
|
||||||
|
|
||||||
|
|
||||||
class base_GPOS_GSUB(DefaultTable.DefaultTable):
|
class base_GPOS_GSUB(DefaultTable.DefaultTable):
|
||||||
|
|
||||||
@ -13,8 +19,7 @@ class base_GPOS_GSUB(DefaultTable.DefaultTable):
|
|||||||
version = 0x00010000
|
version = 0x00010000
|
||||||
|
|
||||||
def decompile(self, data, otFont):
|
def decompile(self, data, otFont):
|
||||||
self.data = data # while work is in progress, dump as hex
|
#self.data = data # handy for debugging
|
||||||
return
|
|
||||||
reader = OTTableReader(data)
|
reader = OTTableReader(data)
|
||||||
self.version = reader.readLong()
|
self.version = reader.readLong()
|
||||||
if self.version <> 0x00010000:
|
if self.version <> 0x00010000:
|
||||||
@ -25,11 +30,13 @@ class base_GPOS_GSUB(DefaultTable.DefaultTable):
|
|||||||
self.lookupList = reader.readTable(LookupList, otFont, self.tableTag)
|
self.lookupList = reader.readTable(LookupList, otFont, self.tableTag)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, otFont):
|
||||||
return self.data
|
writer = OTTableWriter()
|
||||||
|
writer.writeLong(self.version)
|
||||||
|
writer.writeTable(self.scriptList, otFont)
|
||||||
|
writer.writeTable(self.featureList, otFont)
|
||||||
|
writer.writeTable(self.lookupList, otFont)
|
||||||
|
return writer.getData()
|
||||||
|
|
||||||
|
|
||||||
class Dummy:
|
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
names = [("ScriptList", "scriptList"),
|
names = [("ScriptList", "scriptList"),
|
||||||
("FeatureList", "featureList"),
|
("FeatureList", "featureList"),
|
||||||
@ -45,9 +52,13 @@ class Dummy:
|
|||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
if not TESTING_OT:
|
||||||
|
# disable the GPOS/GSUB code, dump as hex.
|
||||||
|
base_GPOS_GSUB = DefaultTable.DefaultTable
|
||||||
|
|
||||||
#
|
#
|
||||||
# Script List and subtables
|
# Script List and subtables
|
||||||
#
|
#
|
||||||
@ -61,8 +72,9 @@ class ScriptList:
|
|||||||
scriptCount = reader.readUShort()
|
scriptCount = reader.readUShort()
|
||||||
self.scripts = reader.readTagList(scriptCount, Script, otFont)
|
self.scripts = reader.readTagList(scriptCount, Script, otFont)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
XXXXXX
|
writer.writeUShort(len(self.scripts))
|
||||||
|
writer.writeTagList(self.scripts, otFont)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
for tag, script in self.scripts:
|
for tag, script in self.scripts:
|
||||||
@ -73,19 +85,20 @@ class ScriptList:
|
|||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Script:
|
class Script:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
self.defaultLangSystem = None
|
|
||||||
self.defaultLangSystem = reader.readTable(LanguageSystem, otFont)
|
self.defaultLangSystem = reader.readTable(LanguageSystem, otFont)
|
||||||
langSysCount = reader.readUShort()
|
langSysCount = reader.readUShort()
|
||||||
self.languageSystems = reader.readTagList(langSysCount, LanguageSystem, otFont)
|
self.languageSystems = reader.readTagList(langSysCount, LanguageSystem, otFont)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
XXXXX
|
writer.writeTable(self.defaultLangSystem, otFont)
|
||||||
|
writer.writeUShort(len(self.languageSystems))
|
||||||
|
writer.writeTagList(self.languageSystems, otFont)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.begintag("DefaultLanguageSystem")
|
xmlWriter.begintag("DefaultLanguageSystem")
|
||||||
@ -109,8 +122,11 @@ class LanguageSystem:
|
|||||||
featureCount = reader.readUShort()
|
featureCount = reader.readUShort()
|
||||||
self.featureIndex = reader.readUShortArray(featureCount)
|
self.featureIndex = reader.readUShortArray(featureCount)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
xxx
|
writer.writeUShort(self.lookupOrder)
|
||||||
|
writer.writeUShort(self.reqFeatureIndex)
|
||||||
|
writer.writeUShort(len(self.featureIndex))
|
||||||
|
writer.writeUShortArray(self.featureIndex)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.simpletag("LookupOrder", value=self.lookupOrder)
|
xmlWriter.simpletag("LookupOrder", value=self.lookupOrder)
|
||||||
@ -135,8 +151,9 @@ class FeatureList:
|
|||||||
featureCount = reader.readUShort()
|
featureCount = reader.readUShort()
|
||||||
self.features = reader.readTagList(featureCount, Feature, otFont)
|
self.features = reader.readTagList(featureCount, Feature, otFont)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
XXXXX
|
writer.writeUShort(len(self.features))
|
||||||
|
writer.writeTagList(self.features, otFont)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
for index in range(len(self.features)):
|
for index in range(len(self.features)):
|
||||||
@ -148,7 +165,7 @@ class FeatureList:
|
|||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Feature:
|
class Feature:
|
||||||
@ -158,8 +175,10 @@ class Feature:
|
|||||||
lookupCount = reader.readUShort()
|
lookupCount = reader.readUShort()
|
||||||
self.lookupListIndex = reader.readUShortArray(lookupCount)
|
self.lookupListIndex = reader.readUShortArray(lookupCount)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
XXXXX
|
writer.writeUShort(self.featureParams)
|
||||||
|
writer.writeUShort(len(self.lookupListIndex))
|
||||||
|
writer.writeUShortArray(self.lookupListIndex)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
xmlWriter.simpletag("FeatureParams", value=hex(self.featureParams))
|
xmlWriter.simpletag("FeatureParams", value=hex(self.featureParams))
|
||||||
@ -169,7 +188,7 @@ class Feature:
|
|||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -183,12 +202,11 @@ class LookupList:
|
|||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
lookupCount = reader.readUShort()
|
lookupCount = reader.readUShort()
|
||||||
self.lookup = lookup = []
|
self.lookup = reader.readTableArray(lookupCount, LookupTable, otFont, self.parentTag)
|
||||||
for i in range(lookupCount):
|
|
||||||
lookup.append(reader.readTable(LookupTable, otFont, self.parentTag))
|
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
XXXXX
|
writer.writeUShort(len(self.lookup))
|
||||||
|
writer.writeTableArray(self.lookup, otFont)
|
||||||
|
|
||||||
def toXML(self, xmlWriter, otFont):
|
def toXML(self, xmlWriter, otFont):
|
||||||
for i in range(len(self.lookup)):
|
for i in range(len(self.lookup)):
|
||||||
@ -202,7 +220,7 @@ class LookupList:
|
|||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class LookupTable:
|
class LookupTable:
|
||||||
@ -215,13 +233,14 @@ class LookupTable:
|
|||||||
self.lookupType = reader.readUShort()
|
self.lookupType = reader.readUShort()
|
||||||
self.lookupFlag = reader.readUShort()
|
self.lookupFlag = reader.readUShort()
|
||||||
subTableCount = reader.readUShort()
|
subTableCount = reader.readUShort()
|
||||||
self.subTables = subTables = []
|
|
||||||
lookupTypeClass = parentTable.getLookupTypeClass(self.lookupType)
|
lookupTypeClass = parentTable.getLookupTypeClass(self.lookupType)
|
||||||
for i in range(subTableCount):
|
self.subTables = reader.readTableArray(subTableCount, lookupTypeClass, otFont)
|
||||||
subTables.append(reader.readTable(lookupTypeClass, otFont))
|
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
XXXXXX
|
writer.writeUShort(self.lookupType)
|
||||||
|
writer.writeUShort(self.lookupFlag)
|
||||||
|
writer.writeUShort(len(self.subTables))
|
||||||
|
writer.writeTableArray(self.subTables, otFont)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if not hasattr(self, "lookupTypeName"):
|
if not hasattr(self, "lookupTypeName"):
|
||||||
@ -241,7 +260,7 @@ class LookupTable:
|
|||||||
xmlWriter.newline()
|
xmlWriter.newline()
|
||||||
|
|
||||||
def fromXML(self, (name, attrs, content), otFont):
|
def fromXML(self, (name, attrs, content), otFont):
|
||||||
xxx
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -256,6 +275,14 @@ class CoverageTable:
|
|||||||
def getGlyphNames(self):
|
def getGlyphNames(self):
|
||||||
return self.glyphNames
|
return self.glyphNames
|
||||||
|
|
||||||
|
def setGlyphNames(self, glyphNames, otFont):
|
||||||
|
glyphIDs = map(otFont.getGlyphID, glyphNames)
|
||||||
|
glyphIDs = map(None, glyphIDs, glyphNames)
|
||||||
|
glyphIDs.sort()
|
||||||
|
self.glyphNames = map(lambda (x, y): y, glyphIDs)
|
||||||
|
self.glyphIDs = map(lambda (x, y): x, glyphIDs)
|
||||||
|
return self.glyphNames
|
||||||
|
|
||||||
def makeGlyphNames(self, otFont):
|
def makeGlyphNames(self, otFont):
|
||||||
self.glyphNames = map(lambda i, o=otFont.getGlyphOrder(): o[i], self.glyphIDs)
|
self.glyphNames = map(lambda i, o=otFont.getGlyphOrder(): o[i], self.glyphIDs)
|
||||||
|
|
||||||
@ -286,49 +313,66 @@ class CoverageTable:
|
|||||||
for glyphID in range(startID, endID + 1):
|
for glyphID in range(startID, endID + 1):
|
||||||
glyphIDs.append(glyphID)
|
glyphIDs.append(glyphID)
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
# brute force ;-)
|
# figure out which format is more compact, doing so by brute force...
|
||||||
data1 = self.compileFormat1(otFont)
|
|
||||||
data2 = self.compileFormat2(otFont)
|
# compile format 1
|
||||||
if len(data1) <= len(data2):
|
writer1 = OTTableWriter()
|
||||||
format = 1
|
writer1.writeUShort(1)
|
||||||
reader = data1
|
self.compileFormat1(writer1, otFont)
|
||||||
|
data1 = writer1.getData()
|
||||||
|
|
||||||
|
# compile format 2
|
||||||
|
writer2 = OTTableWriter()
|
||||||
|
writer2.writeUShort(2)
|
||||||
|
self.compileFormat2(writer2, otFont)
|
||||||
|
data2 = writer2.getData()
|
||||||
|
|
||||||
|
if len(data1) < len(data2):
|
||||||
|
writer.writeRaw(data1)
|
||||||
else:
|
else:
|
||||||
format = 2
|
writer.writeRaw(data2)
|
||||||
reader = data2
|
|
||||||
return struct.pack(">H", format) + reader
|
|
||||||
|
|
||||||
def compileFormat1(self, otFont):
|
def compileFormat1(self, writer, otFont):
|
||||||
xxxxx
|
writer.writeUShort(len(self.glyphIDs))
|
||||||
glyphIDs = map(otFont.getGlyphID, self.glyphNames)
|
writer.writeUShortArray(self.glyphIDs)
|
||||||
data = pack(">H", len(glyphIDs))
|
|
||||||
pack = struct.pack
|
|
||||||
for glyphID in glyphIDs:
|
|
||||||
data = data + pack(">H", glyphID)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def compileFormat2(self, otFont):
|
def compileFormat2(self, writer, otFont):
|
||||||
xxxxx
|
|
||||||
glyphIDs = map(otFont.getGlyphID, self.glyphNames)
|
|
||||||
ranges = []
|
ranges = []
|
||||||
lastID = startID = glyphIDs[0]
|
lastID = startID = self.glyphIDs[0]
|
||||||
startCoverageIndex = 0
|
startCoverageIndex = 0
|
||||||
glyphCount = len(glyphIDs)
|
glyphCount = len(self.glyphIDs)
|
||||||
for i in range(1, glyphCount+1):
|
for i in range(1, glyphCount+1):
|
||||||
if i == glyphCount:
|
if i == glyphCount:
|
||||||
glyphID = 0x1ffff # arbitrary, but larger than 0x10000
|
glyphID = 0x1ffff # arbitrary, but larger than 0x10000
|
||||||
else:
|
else:
|
||||||
glyphID = glyphIDs[i]
|
glyphID = self.glyphIDs[i]
|
||||||
if glyphID <> (lastID + 1):
|
if glyphID <> (lastID + 1):
|
||||||
ranges.append((startID, lastID, startCoverageIndex))
|
ranges.append((startID, lastID, startCoverageIndex))
|
||||||
startCoverageIndex = i
|
startCoverageIndex = i
|
||||||
startID = glyphID
|
startID = glyphID
|
||||||
lastID = glyphID
|
lastID = glyphID
|
||||||
ranges.sort() # sort by startID
|
ranges.sort() # sort by startID
|
||||||
rangeData = ""
|
writer.writeUShort(len(ranges))
|
||||||
for startID, endID, startCoverageIndex in ranges:
|
for startID, endID, startCoverageIndex in ranges:
|
||||||
rangeData = rangeData + struct.pack(">HHH", startID, endID, startCoverageIndex)
|
writer.writeUShort(startID)
|
||||||
return pack(">H", len(ranges)) + rangeData
|
writer.writeUShort(endID)
|
||||||
|
writer.writeUShort(startCoverageIndex)
|
||||||
|
|
||||||
|
|
||||||
|
def unpackCoverageArray(coverageArray):
|
||||||
|
coverageArray = coverageArray[:]
|
||||||
|
for i in range(len(coverageArray)):
|
||||||
|
coverageArray[i] = coverageArray[i].getGlyphNames()
|
||||||
|
return coverageArray
|
||||||
|
|
||||||
|
def buildCoverageArray(coverageArray, otFont):
|
||||||
|
coverageArray = coverageArray[:]
|
||||||
|
for i in range(len(coverageArray)):
|
||||||
|
coverage = CoverageTable()
|
||||||
|
coverage.setGlyphNames(coverageArray[i], otFont)
|
||||||
|
coverageArray[i] = coverage
|
||||||
|
return coverageArray
|
||||||
|
|
||||||
|
|
||||||
class ClassDefinitionTable:
|
class ClassDefinitionTable:
|
||||||
@ -374,7 +418,8 @@ class ClassDefinitionTable:
|
|||||||
glyphName = otFont.getGlyphName(glyphID)
|
glyphName = otFont.getGlyphName(glyphID)
|
||||||
classDefs.append((glyphName, classValue))
|
classDefs.append((glyphName, classValue))
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
|
XXX
|
||||||
# brute force again
|
# brute force again
|
||||||
data1 = self.compileFormat1(otFont)
|
data1 = self.compileFormat1(otFont)
|
||||||
data2 = self.compileFormat2(otFont)
|
data2 = self.compileFormat2(otFont)
|
||||||
@ -387,6 +432,7 @@ class ClassDefinitionTable:
|
|||||||
return struct.pack(">H", format) + data
|
return struct.pack(">H", format) + data
|
||||||
|
|
||||||
def compileFormat1(self, otFont):
|
def compileFormat1(self, otFont):
|
||||||
|
XXX
|
||||||
items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID:
|
items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID:
|
||||||
(getGlyphID(glyphName), classValue), self.glyphs.items())
|
(getGlyphID(glyphName), classValue), self.glyphs.items())
|
||||||
items.sort()
|
items.sort()
|
||||||
@ -402,6 +448,7 @@ class ClassDefinitionTable:
|
|||||||
return struct.pack(">H", endGlyphID - startGlyphID + 1) + data
|
return struct.pack(">H", endGlyphID - startGlyphID + 1) + data
|
||||||
|
|
||||||
def compileFormat2(self, otFont):
|
def compileFormat2(self, otFont):
|
||||||
|
XXX
|
||||||
items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID:
|
items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID:
|
||||||
(getGlyphID(glyphName), classValue), self.glyphs.items())
|
(getGlyphID(glyphName), classValue), self.glyphs.items())
|
||||||
items.sort()
|
items.sort()
|
||||||
@ -436,11 +483,9 @@ class ClassDefinitionTable:
|
|||||||
class DeviceTable:
|
class DeviceTable:
|
||||||
|
|
||||||
def decompile(self, reader, otFont):
|
def decompile(self, reader, otFont):
|
||||||
xxxxxx
|
self.startSize = reader.readUShort()
|
||||||
self.startSize = unpack_uint16(reader[:2])
|
endSize = reader.readUShort()
|
||||||
endSize = unpack_uint16(reader[2:4])
|
deltaFormat = reader.readUShort()
|
||||||
deltaFormat = unpack_uint16(reader[4:6])
|
|
||||||
reader = reader[6:]
|
|
||||||
if deltaFormat == 1:
|
if deltaFormat == 1:
|
||||||
bits = 2
|
bits = 2
|
||||||
elif deltaFormat == 2:
|
elif deltaFormat == 2:
|
||||||
@ -457,7 +502,7 @@ class DeviceTable:
|
|||||||
shift = 1 << bits
|
shift = 1 << bits
|
||||||
for i in range(0, deltaCount, numCount):
|
for i in range(0, deltaCount, numCount):
|
||||||
offset = 2*i/numCount
|
offset = 2*i/numCount
|
||||||
chunk = unpack_uint16(reader[offset:offset+2])
|
chunk = reader.readUShort()
|
||||||
deltas = []
|
deltas = []
|
||||||
for j in range(numCount):
|
for j in range(numCount):
|
||||||
delta = chunk & mask
|
delta = chunk & mask
|
||||||
@ -469,7 +514,9 @@ class DeviceTable:
|
|||||||
deltaValues = deltaValues + deltas
|
deltaValues = deltaValues + deltas
|
||||||
self.deltaValues = deltaValues[:deltaCount]
|
self.deltaValues = deltaValues[:deltaCount]
|
||||||
|
|
||||||
def compile(self, otFont):
|
def compile(self, writer, otFont):
|
||||||
|
raise NotImplementedError
|
||||||
|
# XXX
|
||||||
deltaValues = self.deltaValues
|
deltaValues = self.deltaValues
|
||||||
startSize = self.startSize
|
startSize = self.startSize
|
||||||
endSize = startSize + len(deltaValues) - 1
|
endSize = startSize + len(deltaValues) - 1
|
||||||
@ -515,6 +562,15 @@ class OTTableReader:
|
|||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.pos = offset
|
self.pos = offset
|
||||||
|
|
||||||
|
def readTable(self, tableClass, otFont, *args):
|
||||||
|
offset = self.readOffset()
|
||||||
|
if offset == 0:
|
||||||
|
return None
|
||||||
|
newReader = self.getSubString(offset)
|
||||||
|
table = apply(tableClass, args)
|
||||||
|
table.decompile(newReader, otFont)
|
||||||
|
return table
|
||||||
|
|
||||||
def readUShort(self):
|
def readUShort(self):
|
||||||
pos = self.pos
|
pos = self.pos
|
||||||
newpos = pos + 2
|
newpos = pos + 2
|
||||||
@ -549,14 +605,12 @@ class OTTableReader:
|
|||||||
def readUShortArray(self, count):
|
def readUShortArray(self, count):
|
||||||
return self.readArray(count, "H")
|
return self.readArray(count, "H")
|
||||||
|
|
||||||
readOffsetArray = readUShortArray
|
|
||||||
|
|
||||||
def readShortArray(self, count):
|
def readShortArray(self, count):
|
||||||
return self.readArray(count, "h")
|
return self.readArray(count, "h")
|
||||||
|
|
||||||
def readArray(self, count, format):
|
def readArray(self, count, format):
|
||||||
assert format in "Hh"
|
|
||||||
from array import array
|
from array import array
|
||||||
|
assert format in "Hh"
|
||||||
pos = self.pos
|
pos = self.pos
|
||||||
newpos = pos + 2 * count
|
newpos = pos + 2 * count
|
||||||
a = array(format)
|
a = array(format)
|
||||||
@ -566,15 +620,6 @@ class OTTableReader:
|
|||||||
self.pos = newpos
|
self.pos = newpos
|
||||||
return a.tolist()
|
return a.tolist()
|
||||||
|
|
||||||
def readTable(self, tableClass, otFont, *args):
|
|
||||||
offset = self.readOffset()
|
|
||||||
if offset == 0:
|
|
||||||
return None
|
|
||||||
newReader = self.getSubString(offset)
|
|
||||||
table = apply(tableClass, args)
|
|
||||||
table.decompile(newReader, otFont)
|
|
||||||
return table
|
|
||||||
|
|
||||||
def readTableArray(self, count, tableClass, otFont, *args):
|
def readTableArray(self, count, tableClass, otFont, *args):
|
||||||
list = []
|
list = []
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
@ -608,3 +653,86 @@ class OTTableReader:
|
|||||||
self.pos = self.pos + n
|
self.pos = self.pos + n
|
||||||
|
|
||||||
|
|
||||||
|
class OTTableWriter:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.items = []
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
items = self.items[:]
|
||||||
|
offset = 0
|
||||||
|
for item in items:
|
||||||
|
if hasattr(item, "getData"):
|
||||||
|
offset = offset + 2 # sizeof(Offset)
|
||||||
|
else:
|
||||||
|
offset = offset + len(item)
|
||||||
|
subTables = []
|
||||||
|
cache = {}
|
||||||
|
for i in range(len(items)):
|
||||||
|
item = items[i]
|
||||||
|
if hasattr(item, "getData"):
|
||||||
|
subTableData = item.getData()
|
||||||
|
if cache.has_key(subTableData):
|
||||||
|
items[i] = packOffset(cache[subTableData])
|
||||||
|
else:
|
||||||
|
items[i] = packOffset(offset)
|
||||||
|
subTables.append(subTableData)
|
||||||
|
cache[subTableData] = offset
|
||||||
|
offset = offset + len(subTableData)
|
||||||
|
return string.join(items, "") + string.join(subTables, "")
|
||||||
|
|
||||||
|
def writeTable(self, subTable, otFont):
|
||||||
|
if subTable is None:
|
||||||
|
self.writeUShort(0)
|
||||||
|
else:
|
||||||
|
subWriter = self.__class__()
|
||||||
|
self.items.append(subWriter)
|
||||||
|
subTable.compile(subWriter, otFont)
|
||||||
|
|
||||||
|
def writeUShort(self, value):
|
||||||
|
self.items.append(struct.pack(">H", value))
|
||||||
|
|
||||||
|
def writeShort(self, value):
|
||||||
|
self.items.append(struct.pack(">h", value))
|
||||||
|
|
||||||
|
def writeLong(self, value):
|
||||||
|
self.items.append(struct.pack(">l", value))
|
||||||
|
|
||||||
|
def writeTag(self, tag):
|
||||||
|
assert len(tag) == 4
|
||||||
|
self.items.append(tag)
|
||||||
|
|
||||||
|
def writeUShortArray(self, array):
|
||||||
|
return self.writeArray(array, "H")
|
||||||
|
|
||||||
|
def writeShortArray(self, array):
|
||||||
|
return self.writeArray(array, "h")
|
||||||
|
|
||||||
|
def writeArray(self, list, format):
|
||||||
|
from array import array
|
||||||
|
assert format in "Hh"
|
||||||
|
a = array(format, list)
|
||||||
|
if ttLib.endian <> 'big':
|
||||||
|
a.byteswap()
|
||||||
|
self.items.append(a.tostring())
|
||||||
|
|
||||||
|
def writeTableArray(self, list, otFont):
|
||||||
|
for subTable in list:
|
||||||
|
self.writeTable(subTable, otFont)
|
||||||
|
|
||||||
|
def writeTagList(self, list, otFont):
|
||||||
|
for tag, subTable in list:
|
||||||
|
self.writeTag(tag)
|
||||||
|
self.writeTable(subTable, otFont)
|
||||||
|
|
||||||
|
def writeStruct(self, format, values):
|
||||||
|
data = apply(struct.pack, (format,) + values)
|
||||||
|
self.items.append(data)
|
||||||
|
|
||||||
|
def writeRaw(self, data):
|
||||||
|
self.items.append(data)
|
||||||
|
|
||||||
|
|
||||||
|
def packOffset(offset):
|
||||||
|
return struct.pack(">H", offset)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user