Merge pull request #677 from fonttools/opentype-gx

Merge Opentype Variation Fonts (previously known as OpenType GX)
This commit is contained in:
Behdad Esfahbod 2016-09-28 13:18:50 +01:00 committed by GitHub
commit 7740a5fe74
92 changed files with 2001 additions and 695 deletions

View File

@ -79,6 +79,10 @@ class CFFFontSet(object):
writer.toFile(file) writer.toFile(file)
def toXML(self, xmlWriter, progress=None): def toXML(self, xmlWriter, progress=None):
xmlWriter.simpletag("major", value=self.major)
xmlWriter.newline()
xmlWriter.simpletag("minor", value=self.minor)
xmlWriter.newline()
for fontName in self.fontNames: for fontName in self.fontNames:
xmlWriter.begintag("CFFFont", name=tostr(fontName)) xmlWriter.begintag("CFFFont", name=tostr(fontName))
xmlWriter.newline() xmlWriter.newline()
@ -155,6 +159,10 @@ class CFFWriter(object):
log.log(DEBUG, "CFFWriter.toFile() writing to file.") log.log(DEBUG, "CFFWriter.toFile() writing to file.")
begin = file.tell() begin = file.tell()
posList = [0] posList = [0]
cffOffSize = calcOffSize(lastPosList[-1])
headerBytes = self.data[0]
headerBytes = headerBytes[:-1] + bytechr(cffOffSize)
self.data[0] = headerBytes
for item in self.data: for item in self.data:
if hasattr(item, "toFile"): if hasattr(item, "toFile"):
item.toFile(file) item.toFile(file)
@ -186,43 +194,53 @@ class IndexCompiler(object):
return items return items
def getOffsets(self): def getOffsets(self):
pos = 1 # An empty INDEX contains only the count field.
offsets = [pos] if self.items:
for item in self.items: pos = 1
if hasattr(item, "getDataLength"): offsets = [pos]
pos = pos + item.getDataLength() for item in self.items:
else: if hasattr(item, "getDataLength"):
pos = pos + len(item) pos = pos + item.getDataLength()
offsets.append(pos) else:
pos = pos + len(item)
offsets.append(pos)
else:
offsets = []
return offsets return offsets
def getDataLength(self): def getDataLength(self):
lastOffset = self.getOffsets()[-1] if self.items:
offSize = calcOffSize(lastOffset) lastOffset = self.getOffsets()[-1]
dataLength = ( offSize = calcOffSize(lastOffset)
2 + # count dataLength = (
1 + # offSize 2 + # count
(len(self.items) + 1) * offSize + # the offsets 1 + # offSize
lastOffset - 1 # size of object data (len(self.items) + 1) * offSize + # the offsets
) lastOffset - 1 # size of object data
)
else:
dataLength = 2 # count. For empty INDEX tables, this is the only entry.
return dataLength return dataLength
def toFile(self, file): def toFile(self, file):
offsets = self.getOffsets() offsets = self.getOffsets()
writeCard16(file, len(self.items)) writeCard16(file, len(self.items))
offSize = calcOffSize(offsets[-1]) # An empty INDEX contains only the count field.
writeCard8(file, offSize) if self.items:
offSize = -offSize offSize = calcOffSize(offsets[-1])
pack = struct.pack writeCard8(file, offSize)
for offset in offsets: offSize = -offSize
binOffset = pack(">l", offset)[offSize:] pack = struct.pack
assert len(binOffset) == -offSize for offset in offsets:
file.write(binOffset) binOffset = pack(">l", offset)[offSize:]
for item in self.items: assert len(binOffset) == -offSize
if hasattr(item, "toFile"): file.write(binOffset)
item.toFile(file) for item in self.items:
else: if hasattr(item, "toFile"):
file.write(tobytes(item, encoding="latin1")) item.toFile(file)
else:
file.write(tobytes(item, encoding="latin1"))
class IndexedStringsCompiler(IndexCompiler): class IndexedStringsCompiler(IndexCompiler):
@ -433,6 +451,11 @@ class FDArrayIndex(TopDictIndex):
compilerClass = FDArrayIndexCompiler compilerClass = FDArrayIndexCompiler
def produceItem(self, index, data, file, offset, size):
fontDict = FontDict(self.strings, file, offset, self.GlobalSubrs)
fontDict.decompile(data)
return fontDict
def fromXML(self, name, attrs, content): def fromXML(self, name, attrs, content):
if name != "FontDict": if name != "FontDict":
return return
@ -441,7 +464,11 @@ class FDArrayIndex(TopDictIndex):
if isinstance(element, basestring): if isinstance(element, basestring):
continue continue
name, attrs, content = element name, attrs, content = element
fontDict.fromXML(name, attrs, content) try:
fontDict.fromXML(name, attrs, content)
except KeyError:
"""Since fonttools used to pass a lot of fields that are not relevant in the FDArray FontDict, there are 'ttx' files in the wild that contain all these. These got in the ttx files because fonttools writes explicit values for all the TopDict default values. These are not actually illegal in the context of an FDArray FontDict - you can legally, per spec, put any arbitrary key/value pair in a FontDict - but are useless since current major company CFF interpreters ignore anything but the set listed in this file. So, we just silently skip them. An exception is Weight: this is not used by any interepreter, but some foundries have asked that this be supported in FDArray FontDicts just to preserve information about the design when the font is being inspected."""
pass
self.append(fontDict) self.append(fontDict)
@ -490,7 +517,6 @@ class FDSelect:
def append(self, fdSelectValue): def append(self, fdSelectValue):
self.gidArray.append(fdSelectValue) self.gidArray.append(fdSelectValue)
class CharStrings(object): class CharStrings(object):
def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray): def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
@ -500,9 +526,11 @@ class CharStrings(object):
for i in range(len(charset)): for i in range(len(charset)):
charStrings[charset[i]] = i charStrings[charset[i]] = i
self.charStringsAreIndexed = 1 self.charStringsAreIndexed = 1
# read from OTF file: charStrings.values() are indices into charStringsIndex.
else: else:
self.charStrings = {} self.charStrings = {}
self.charStringsAreIndexed = 0 self.charStringsAreIndexed = 0
# read from ttx file : charStrings.values() are actual charstrings
self.globalSubrs = globalSubrs self.globalSubrs = globalSubrs
self.private = private self.private = private
if fdSelect is not None: if fdSelect is not None:
@ -806,6 +834,8 @@ class CharsetConverter(object):
charset = cffIExpertStrings charset = cffIExpertStrings
elif value == 2: elif value == 2:
charset = cffExpertSubsetStrings charset = cffExpertSubsetStrings
if charset and len(charset) != parent.numGlyphs:
charset = charset[:parent.numGlyphs]
return charset return charset
def write(self, parent, value): def write(self, parent, value):
@ -843,6 +873,25 @@ class CharsetCompiler(object):
def toFile(self, file): def toFile(self, file):
file.write(self.data) file.write(self.data)
def getStdCharSet(charset):
# check to see if we can use a predefined charset value.
charsetCode = None
predefinedCharSets = [
(cffISOAdobeStringCount, cffISOAdobeStrings, 0),
(cffExpertStringCount, cffIExpertStrings, 1),
(cffExpertSubsetStringCount, cffExpertSubsetStrings, 2) ]
len_cs = len(charset)
for cnt, charList, code in predefinedCharSets:
if charsetCode != None:
break
if len_cs > cnt:
continue
charsetCode = code
for i in range(len_cs):
if charset[i] != charList[i]:
charsetCode = None
break
return charsetCode
def getCIDfromName(name, strings): def getCIDfromName(name, strings):
return int(name[3:]) return int(name[3:])
@ -1253,6 +1302,16 @@ topDictOperators = [
(17, 'CharStrings', 'number', None, CharStringsConverter()), (17, 'CharStrings', 'number', None, CharStringsConverter()),
] ]
fontDictOperators = [
# opcode name argument type default converter
((12, 38), 'FontName', 'SID', None, None),
((12, 7), 'FontMatrix', 'array', [0.001, 0, 0, 0.001, 0, 0], None),
(4, 'Weight', 'SID', None, None),
(18, 'Private', ('number', 'number'), None, PrivateDictConverter()),
]
# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, # Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
# in order for the font to compile back from xml. # in order for the font to compile back from xml.
@ -1298,11 +1357,14 @@ def addConverters(table):
addConverters(privateDictOperators) addConverters(privateDictOperators)
addConverters(topDictOperators) addConverters(topDictOperators)
addConverters(fontDictOperators)
class TopDictDecompiler(psCharStrings.DictDecompiler): class TopDictDecompiler(psCharStrings.DictDecompiler):
operators = buildOperatorDict(topDictOperators) operators = buildOperatorDict(topDictOperators)
class FontDictDecompiler(psCharStrings.DictDecompiler):
operators = buildOperatorDict(fontDictOperators)
class PrivateDictDecompiler(psCharStrings.DictDecompiler): class PrivateDictDecompiler(psCharStrings.DictDecompiler):
operators = buildOperatorDict(privateDictOperators) operators = buildOperatorDict(privateDictOperators)
@ -1394,7 +1456,14 @@ class TopDictCompiler(DictCompiler):
def getChildren(self, strings): def getChildren(self, strings):
children = [] children = []
if hasattr(self.dictObj, "charset") and self.dictObj.charset: if hasattr(self.dictObj, "charset") and self.dictObj.charset:
children.append(CharsetCompiler(strings, self.dictObj.charset, self)) if hasattr(self.dictObj, "ROS"): # aka isCID
charsetCode = None
else:
charsetCode = getStdCharSet(self.dictObj.charset)
if charsetCode == None:
children.append(CharsetCompiler(strings, self.dictObj.charset, self))
else:
self.rawDict["charset"] = charsetCode
if hasattr(self.dictObj, "Encoding"): if hasattr(self.dictObj, "Encoding"):
encoding = self.dictObj.Encoding encoding = self.dictObj.Encoding
if not isinstance(encoding, basestring): if not isinstance(encoding, basestring):
@ -1402,7 +1471,7 @@ class TopDictCompiler(DictCompiler):
if hasattr(self.dictObj, "FDSelect"): if hasattr(self.dictObj, "FDSelect"):
# I have not yet supported merging a ttx CFF-CID font, as there are interesting # I have not yet supported merging a ttx CFF-CID font, as there are interesting
# issues about merging the FDArrays. Here I assume that # issues about merging the FDArrays. Here I assume that
# either the font was read from XML, and teh FDSelect indices are all # either the font was read from XML, and the FDSelect indices are all
# in the charstring data, or the FDSelect array is already fully defined. # in the charstring data, or the FDSelect array is already fully defined.
fdSelect = self.dictObj.FDSelect fdSelect = self.dictObj.FDSelect
if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data
@ -1434,7 +1503,7 @@ class TopDictCompiler(DictCompiler):
class FontDictCompiler(DictCompiler): class FontDictCompiler(DictCompiler):
opcodes = buildOpcodeDict(topDictOperators) opcodes = buildOpcodeDict(fontDictOperators)
def getChildren(self, strings): def getChildren(self, strings):
children = [] children = []
@ -1563,23 +1632,18 @@ class TopDict(BaseDict):
i = i + 1 i = i + 1
class FontDict(BaseDict): class FontDict(TopDict):
defaults = buildDefaults(topDictOperators) defaults = buildDefaults(fontDictOperators)
converters = buildConverters(topDictOperators) converters = buildConverters(fontDictOperators)
order = buildOrder(topDictOperators) order = buildOrder(fontDictOperators)
decompilerClass = None decompilerClass = FontDictDecompiler
compilerClass = FontDictCompiler compilerClass = FontDictCompiler
def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
BaseDict.__init__(self, strings, file, offset) TopDict.__init__(self, strings, file, offset)
self.GlobalSubrs = GlobalSubrs
def getGlyphOrder(self):
return self.charset
def toXML(self, xmlWriter, progress): def toXML(self, xmlWriter, progress):
self.skipNames = ['Encoding']
BaseDict.toXML(self, xmlWriter, progress) BaseDict.toXML(self, xmlWriter, progress)

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<AttachList> <AttachList>
<Coverage> <Coverage>
<Glyph value="a"/> <Glyph value="a"/>

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="a" class="1"/> <ClassDef glyph="a" class="1"/>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
@ -17,7 +17,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
<ClassDef glyph="c_t" class="2"/> <ClassDef glyph="c_t" class="2"/>
@ -17,7 +17,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
<ClassDef glyph="caron" class="3"/> <ClassDef glyph="caron" class="3"/>
@ -15,7 +15,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="a" class="1"/> <ClassDef glyph="a" class="1"/>
<ClassDef glyph="b" class="2"/> <ClassDef glyph="b" class="2"/>

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<LigCaretList> <LigCaretList>
<Coverage> <Coverage>
<Glyph value="c_t"/> <Glyph value="c_t"/>

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<LigCaretList> <LigCaretList>
<Coverage> <Coverage>
<Glyph value="c_h"/> <Glyph value="c_h"/>

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
<ClassDef glyph="e" class="1"/> <ClassDef glyph="e" class="1"/>
@ -10,7 +10,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=2 --> <!-- ScriptCount=2 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -33,7 +33,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
<ClassDef glyph="breve" class="3"/> <ClassDef glyph="breve" class="3"/>

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> <ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=2 --> <!-- ScriptCount=2 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=3 --> <!-- ScriptCount=3 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> <ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=4 --> <!-- ScriptCount=4 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="a" class="1"/> <ClassDef glyph="a" class="1"/>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
@ -17,7 +17,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="kasratan" class="3"/> <ClassDef glyph="kasratan" class="3"/>
<ClassDef glyph="lam_meem_jeem" class="2"/> <ClassDef glyph="lam_meem_jeem" class="2"/>
@ -11,7 +11,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="damma" class="3"/> <ClassDef glyph="damma" class="3"/>
<ClassDef glyph="hamza" class="3"/> <ClassDef glyph="hamza" class="3"/>
@ -10,7 +10,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
<ClassDef glyph="c" class="1"/> <ClassDef glyph="c" class="1"/>
@ -12,7 +12,7 @@
</GDEF> </GDEF>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=3 --> <!-- ScriptCount=3 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -14,7 +14,7 @@
</name> </name>
<GPOS> <GPOS>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -17,7 +17,7 @@
</name> </name>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
<ScriptList> <ScriptList>
<!-- ScriptCount=1 --> <!-- ScriptCount=1 -->
<ScriptRecord index="0"> <ScriptRecord index="0">

View File

@ -2,7 +2,7 @@
<ttFont> <ttFont>
<BASE> <BASE>
<Version value="1.0"/> <Version value="0x00010000"/>
<HorizAxis> <HorizAxis>
<BaseTagList> <BaseTagList>
<!-- BaseTagCount=2 --> <!-- BaseTagCount=2 -->

View File

@ -2,7 +2,7 @@
<ttFont sfntVersion="true" ttLibVersion="3.0"> <ttFont sfntVersion="true" ttLibVersion="3.0">
<GDEF> <GDEF>
<Version value="1.0"/> <Version value="0x00010000"/>
<GlyphClassDef> <GlyphClassDef>
<ClassDef glyph="acute" class="3"/> <ClassDef glyph="acute" class="3"/>
<ClassDef glyph="c_s" class="2"/> <ClassDef glyph="c_s" class="2"/>

View File

@ -1582,13 +1582,13 @@ def prune_post_subset(self, options):
table.MarkGlyphSetsDef and table.MarkGlyphSetsDef and
not table.MarkGlyphSetsDef.Coverage): not table.MarkGlyphSetsDef.Coverage):
table.MarkGlyphSetsDef = None table.MarkGlyphSetsDef = None
if table.Version == 0x00010002/0x10000: if table.Version == 0x00010002:
table.Version = 1.0 table.Version = 0x00010000
return bool(table.LigCaretList or return bool(table.LigCaretList or
table.MarkAttachClassDef or table.MarkAttachClassDef or
table.GlyphClassDef or table.GlyphClassDef or
table.AttachList or table.AttachList or
(table.Version >= 0x00010002/0x10000 and table.MarkGlyphSetsDef)) (table.Version >= 0x00010002 and table.MarkGlyphSetsDef))
@_add_method(ttLib.getTableClass('kern')) @_add_method(ttLib.getTableClass('kern'))
def prune_pre_subset(self, font, options): def prune_pre_subset(self, font, options):
@ -2368,8 +2368,10 @@ def prune_pre_subset(self, font, options):
nameIDs = set(options.name_IDs) nameIDs = set(options.name_IDs)
fvar = font.get('fvar') fvar = font.get('fvar')
if fvar: if fvar:
nameIDs.update([inst.nameID for inst in fvar.instances]) nameIDs.update([axis.axisNameID for axis in fvar.axes])
nameIDs.update([axis.nameID for axis in fvar.axes]) nameIDs.update([inst.subfamilyNameID for inst in fvar.instances])
nameIDs.update([inst.postscriptNameID for inst in fvar.instances
if inst.postscriptNameID != 0xFFFF])
if '*' not in options.name_IDs: if '*' not in options.name_IDs:
self.names = [n for n in self.names if n.nameID in nameIDs] self.names = [n for n in self.names if n.nameID in nameIDs]
if not options.name_legacy: if not options.name_legacy:

View File

@ -374,7 +374,7 @@
</gasp> </gasp>
<GSUB> <GSUB>
<Version value="1.0"/> <Version value="0x00010000"/>
</GSUB> </GSUB>
<avar> <avar>
@ -394,31 +394,31 @@
<MinValue>100.0</MinValue> <MinValue>100.0</MinValue>
<DefaultValue>400.0</DefaultValue> <DefaultValue>400.0</DefaultValue>
<MaxValue>900.0</MaxValue> <MaxValue>900.0</MaxValue>
<NameID>257</NameID> <AxisNameID>257</AxisNameID>
</Axis> </Axis>
<!-- Thin --> <!-- Thin -->
<NamedInstance nameID="258"> <NamedInstance subfamilyNameID="258">
<coord axis="wght" value="100.0"/> <coord axis="wght" value="100.0"/>
</NamedInstance> </NamedInstance>
<!-- Light --> <!-- Light -->
<NamedInstance nameID="259"> <NamedInstance subfamilyNameID="259">
<coord axis="wght" value="300.0"/> <coord axis="wght" value="300.0"/>
</NamedInstance> </NamedInstance>
<!-- Regular --> <!-- Regular -->
<NamedInstance nameID="260"> <NamedInstance subfamilyNameID="260">
<coord axis="wght" value="400.0"/> <coord axis="wght" value="400.0"/>
</NamedInstance> </NamedInstance>
<!-- Bold --> <!-- Bold -->
<NamedInstance nameID="261"> <NamedInstance subfamilyNameID="261">
<coord axis="wght" value="700.0"/> <coord axis="wght" value="700.0"/>
</NamedInstance> </NamedInstance>
<!-- Black --> <!-- Black -->
<NamedInstance nameID="262"> <NamedInstance subfamilyNameID="262">
<coord axis="wght" value="900.0"/> <coord axis="wght" value="900.0"/>
</NamedInstance> </NamedInstance>
</fvar> </fvar>

View File

@ -5330,7 +5330,7 @@
</CFF> </CFF>
<MATH> <MATH>
<Version value="1.0"/> <Version value="0x00010000"/>
<MathConstants> <MathConstants>
<ScriptPercentScaleDown value="75"/> <ScriptPercentScaleDown value="75"/>
<ScriptScriptPercentScaleDown value="60"/> <ScriptScriptPercentScaleDown value="60"/>

View File

@ -25,31 +25,31 @@
<MinValue>100.0</MinValue> <MinValue>100.0</MinValue>
<DefaultValue>400.0</DefaultValue> <DefaultValue>400.0</DefaultValue>
<MaxValue>900.0</MaxValue> <MaxValue>900.0</MaxValue>
<NameID>257</NameID> <AxisNameID>257</AxisNameID>
</Axis> </Axis>
<!-- Thin --> <!-- Thin -->
<NamedInstance nameID="258"> <NamedInstance subfamilyNameID="258">
<coord axis="wght" value="100.0"/> <coord axis="wght" value="100.0"/>
</NamedInstance> </NamedInstance>
<!-- Light --> <!-- Light -->
<NamedInstance nameID="259"> <NamedInstance subfamilyNameID="259">
<coord axis="wght" value="300.0"/> <coord axis="wght" value="300.0"/>
</NamedInstance> </NamedInstance>
<!-- Regular --> <!-- Regular -->
<NamedInstance nameID="260"> <NamedInstance subfamilyNameID="260">
<coord axis="wght" value="400.0"/> <coord axis="wght" value="400.0"/>
</NamedInstance> </NamedInstance>
<!-- Bold --> <!-- Bold -->
<NamedInstance nameID="261"> <NamedInstance subfamilyNameID="261">
<coord axis="wght" value="700.0"/> <coord axis="wght" value="700.0"/>
</NamedInstance> </NamedInstance>
<!-- Black --> <!-- Black -->
<NamedInstance nameID="262"> <NamedInstance subfamilyNameID="262">
<coord axis="wght" value="900.0"/> <coord axis="wght" value="900.0"/>
</NamedInstance> </NamedInstance>
</fvar> </fvar>

View File

@ -24,31 +24,31 @@
<MinValue>100.0</MinValue> <MinValue>100.0</MinValue>
<DefaultValue>400.0</DefaultValue> <DefaultValue>400.0</DefaultValue>
<MaxValue>900.0</MaxValue> <MaxValue>900.0</MaxValue>
<NameID>257</NameID> <AxisNameID>257</AxisNameID>
</Axis> </Axis>
<!-- Thin --> <!-- Thin -->
<NamedInstance nameID="258"> <NamedInstance subfamilyNameID="258">
<coord axis="wght" value="100.0"/> <coord axis="wght" value="100.0"/>
</NamedInstance> </NamedInstance>
<!-- Light --> <!-- Light -->
<NamedInstance nameID="259"> <NamedInstance subfamilyNameID="259">
<coord axis="wght" value="300.0"/> <coord axis="wght" value="300.0"/>
</NamedInstance> </NamedInstance>
<!-- Regular --> <!-- Regular -->
<NamedInstance nameID="260"> <NamedInstance subfamilyNameID="260">
<coord axis="wght" value="400.0"/> <coord axis="wght" value="400.0"/>
</NamedInstance> </NamedInstance>
<!-- Bold --> <!-- Bold -->
<NamedInstance nameID="261"> <NamedInstance subfamilyNameID="261">
<coord axis="wght" value="700.0"/> <coord axis="wght" value="700.0"/>
</NamedInstance> </NamedInstance>
<!-- Black --> <!-- Black -->
<NamedInstance nameID="262"> <NamedInstance subfamilyNameID="262">
<coord axis="wght" value="900.0"/> <coord axis="wght" value="900.0"/>
</NamedInstance> </NamedInstance>
</fvar> </fvar>

View File

@ -24,6 +24,8 @@
</GlyphOrder> </GlyphOrder>
<CFF> <CFF>
<major value="1"/>
<minor value="0"/>
<CFFFont name="XITSMath"> <CFFFont name="XITSMath">
<version value="1.108"/> <version value="1.108"/>
<Notice value="Copyright (c) 2001-2011 by the STI Pub Companies, consisting of the American Chemical Society, the American Institute of Physics, the American Mathematical Society, the American Physical Society, Elsevier, Inc., and The Institute of Electrical and Electronic Engineers, Inc. Portions copyright (c) 1998-2003 by MicroPress, Inc. Portions copyright (c) 1990 by Elsevier, Inc. Portions copyright (c) 2009-2012 by Khaled Hosny. All rights reserved. "/> <Notice value="Copyright (c) 2001-2011 by the STI Pub Companies, consisting of the American Chemical Society, the American Institute of Physics, the American Mathematical Society, the American Physical Society, Elsevier, Inc., and The Institute of Electrical and Electronic Engineers, Inc. Portions copyright (c) 1998-2003 by MicroPress, Inc. Portions copyright (c) 1990 by Elsevier, Inc. Portions copyright (c) 2009-2012 by Khaled Hosny. All rights reserved. "/>
@ -252,7 +254,7 @@
</CFF> </CFF>
<MATH> <MATH>
<Version value="1.0"/> <Version value="0x00010000"/>
<MathConstants> <MathConstants>
<ScriptPercentScaleDown value="75"/> <ScriptPercentScaleDown value="75"/>
<ScriptScriptPercentScaleDown value="60"/> <ScriptScriptPercentScaleDown value="60"/>

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="OTTO" ttLibVersion="2.5"> <ttFont sfntVersion="OTTO" ttLibVersion="3.0">
<CFF> <CFF>
<major value="1"/>
<minor value="0"/>
<CFFFont name="TestCID-Regular"> <CFFFont name="TestCID-Regular">
<ROS Registry="Adobe" Order="Identity" Supplement="0"/> <ROS Registry="Adobe" Order="Identity" Supplement="0"/>
<FullName value="Test CID Regular"/> <FullName value="Test CID Regular"/>
@ -25,15 +27,7 @@
<FDArray> <FDArray>
<FontDict index="0"> <FontDict index="0">
<FontName value="TestCID-Regular-One"/> <FontName value="TestCID-Regular-One"/>
<isFixedPitch value="0"/>
<ItalicAngle value="0"/>
<UnderlineThickness value="50"/>
<PaintType value="0"/>
<CharstringType value="2"/>
<FontMatrix value="0.001 0 0 0.001 0 0"/> <FontMatrix value="0.001 0 0 0.001 0 0"/>
<FontBBox value="0 0 0 0"/>
<StrokeWidth value="0"/>
<Encoding name="StandardEncoding"/>
<Private> <Private>
<BlueValues value="-250 -250 1100 1100"/> <BlueValues value="-250 -250 1100 1100"/>
<BlueScale value="0.039625"/> <BlueScale value="0.039625"/>

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="OTTO" ttLibVersion="2.5"> <ttFont sfntVersion="OTTO" ttLibVersion="3.0">
<CFF> <CFF>
<major value="1"/>
<minor value="0"/>
<CFFFont name="TestOTF-Regular"> <CFFFont name="TestOTF-Regular">
<version value="1.0"/> <version value="1.0"/>
<FamilyName value="Test OTF"/> <FamilyName value="Test OTF"/>

View File

@ -0,0 +1,384 @@
"""cffLib_test.py -- unit test for Adobe CFF fonts."""
from __future__ import print_function, division, absolute_import, unicode_literals
from fontTools.misc.py23 import *
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTFont, newTable
import re
import unittest
cffXML = """
<major value="1"/>
<minor value="0"/>
<CFFFont name="CFF2TestFont1Master-0">
<version value="1.0"/>
<FullName value="CFF2 Test Font1 Master 0"/>
<FamilyName value="CFF2 Test Font1 Master"/>
<isFixedPitch value="0"/>
<ItalicAngle value="0"/>
<UnderlineThickness value="50"/>
<PaintType value="0"/>
<CharstringType value="2"/>
<FontMatrix value="0.001 0 0 0.001 0 0"/>
<FontBBox value="0 -128 651 762"/>
<StrokeWidth value="0"/>
<!-- charset is dumped separately as the 'GlyphOrder' element -->
<Encoding name="StandardEncoding"/>
<Private>
<BlueValues value="-20 0 466 484 531 546 652 667 677 697 738 758"/>
<OtherBlues value="-255 -245"/>
<FamilyBlues value="-20 0 473 491 525 540 644 659 669 689 729 749"/>
<FamilyOtherBlues value="-249 -239"/>
<BlueScale value="0.0375"/>
<BlueShift value="7"/>
<BlueFuzz value="0"/>
<StdHW value="26"/>
<StdVW value="28"/>
<StemSnapH value="20 26"/>
<StemSnapV value="28 32"/>
<ForceBold value="0"/>
<LanguageGroup value="0"/>
<ExpansionFactor value="0.06"/>
<initialRandomSeed value="0"/>
<defaultWidthX value="490"/>
<nominalWidthX value="597"/>
</Private>
<CharStrings>
<CharString name=".notdef">
-97 0 50 600 50 hstem
0 50 400 50 vstem
0 0 rmoveto
500 0 rlineto
0 700 rlineto
-500 0 rlineto
0 -700 rlineto
250 395 rmoveto
-170 255 rlineto
340 0 rlineto
-170 -255 rlineto
30 -45 rmoveto
170 255 rlineto
0 -510 rlineto
-170 255 rlineto
-200 -300 rmoveto
170 255 rlineto
170 -255 rlineto
-340 0 rlineto
-30 555 rmoveto
170 -255 rlineto
-170 -255 rlineto
0 510 rlineto
endchar
</CharString>
<CharString name="A">
56 523 26 rmoveto
-120 -6 rlineto
0 -20 rlineto
248 0 rlineto
0 20 rlineto
-114 6 rlineto
-14 0 rlineto
-424 0 rmoveto
-87 -6 rlineto
0 -20 rlineto
198 0 rlineto
0 20 rlineto
-97 6 rlineto
-14 0 rlineto
369 221 rmoveto
-8 20 rlineto
-278 0 rlineto
-9 -20 rlineto
295 0 rlineto
-161 430 rmoveto
-222 -677 rlineto
27 0 rlineto
211 660 rlineto
-17 -10 rlineto
216 -650 rlineto
34 0 rlineto
-229 677 rlineto
-20 0 rlineto
endchar
</CharString>
<CharString name="B">
-3 167 310 rmoveto
0 -104 0 -104 -2 -102 rrcurveto
34 0 rlineto
-2 102 0 104 0 144 rrcurveto
0 7 rlineto
0 114 0 104 2 102 rrcurveto
-34 0 rlineto
2 -102 0 -104 0 -104 rrcurveto
0 -57 rlineto
8 340 rmoveto
7 0 rlineto
0 27 rlineto
-124 0 rlineto
0 -20 rlineto
117 -7 rlineto
0 -623 rmoveto
-117 -7 rlineto
0 -20 rlineto
124 0 rlineto
0 27 rlineto
-7 0 rlineto
7 316 rmoveto
101 0 rlineto
162 0 69 -60 0 -102 rrcurveto
0 -102 -75 -57 -125 0 rrcurveto
-132 0 rlineto
0 -22 rlineto
131 0 rlineto
156 0 75 77 0 102 rrcurveto
0 100 -68 76 -162 2 rrcurveto
-10 -8 rlineto
141 11 64 75 0 84 rrcurveto
0 95 -66 63 -146 0 rrcurveto
-115 0 rlineto
0 -22 rlineto
104 0 rlineto
145 0 50 -57 0 -76 rrcurveto
0 -95 -75 -64 -136 0 rrcurveto
-88 0 rlineto
0 -20 rlineto
endchar
</CharString>
<CharString name="C">
47 386 7 rmoveto
-167 0 -123 128 0 203 rrcurveto
0 199 116 133 180 0 rrcurveto
73 0 40 -17 56 -37 rrcurveto
-21 29 rlineto
18 -145 rlineto
24 0 rlineto
-4 139 rlineto
-60 35 -49 18 -80 0 rrcurveto
-190 0 -135 -144 0 -210 rrcurveto
0 -209 129 -144 195 0 rrcurveto
72 0 57 12 67 41 rrcurveto
4 139 rlineto
-24 0 rlineto
-18 -139 rlineto
17 20 rlineto
-55 -37 -55 -14 -67 0 rrcurveto
endchar
</CharString>
<CharString name="dollar">
245 5 rmoveto
-65 0 -39 15 -46 50 rrcurveto
36 -48 rlineto
-28 100 rlineto
-6 15 -10 5 -11 0 rrcurveto
-14 0 -8 -7 -1 -14 rrcurveto
24 -85 61 -51 107 0 rrcurveto
91 0 90 54 0 112 rrcurveto
0 70 -26 66 -134 57 rrcurveto
-19 8 rlineto
-93 39 -42 49 0 68 rrcurveto
0 91 60 48 88 0 rrcurveto
56 0 35 -14 44 -50 rrcurveto
-38 47 rlineto
28 -100 rlineto
6 -15 10 -5 11 0 rrcurveto
14 0 8 7 1 14 rrcurveto
-24 88 -67 48 -84 0 rrcurveto
-92 0 -82 -51 0 -108 rrcurveto
0 -80 45 -53 92 -42 rrcurveto
37 -17 rlineto
114 -52 26 -46 0 -65 rrcurveto
0 -93 -65 -55 -90 0 rrcurveto
18 318 rmoveto
0 439 rlineto
-22 0 rlineto
0 -425 rlineto
22 -14 rlineto
-20 -438 rmoveto
22 0 rlineto
0 438 rlineto
-22 14 rlineto
0 -452 rlineto
endchar
</CharString>
<CharString name="dollar.black">
3 245 5 rmoveto
-65 0 -39 15 -46 50 rrcurveto
36 -48 rlineto
-28 100 rlineto
-6 15 -10 5 -11 0 rrcurveto
-14 0 -8 -7 -1 -14 rrcurveto
24 -85 61 -51 107 0 rrcurveto
91 0 90 54 0 112 rrcurveto
0 70 -26 66 -134 57 rrcurveto
-19 8 rlineto
-93 39 -42 49 0 68 rrcurveto
0 91 60 48 88 0 rrcurveto
56 0 35 -14 44 -50 rrcurveto
-38 47 rlineto
28 -100 rlineto
6 -15 10 -5 11 0 rrcurveto
14 0 8 7 1 14 rrcurveto
-24 88 -67 48 -84 0 rrcurveto
-92 0 -82 -51 0 -108 rrcurveto
0 -80 45 -53 92 -42 rrcurveto
37 -17 rlineto
114 -52 26 -46 0 -65 rrcurveto
0 -93 -65 -55 -90 0 rrcurveto
17 651 rmoveto
1 106 rlineto
-22 0 rlineto
1 -107 rlineto
20 1 rlineto
-15 -784 rmoveto
22 0 rlineto
-3 121 rlineto
-20 2 rlineto
1 -123 rlineto
endchar
</CharString>
<CharString name="one">
91 618 rmoveto
0 -20 rlineto
155 35 rlineto
0 -421 rlineto
0 -70 -1 -71 -2 -72 rrcurveto
34 0 rlineto
-2 72 -1 71 0 70 rrcurveto
0 297 rlineto
4 146 rlineto
-14 12 rlineto
-173 -49 rlineto
176 -593 rmoveto
-14 0 rlineto
-170 -6 rlineto
0 -20 rlineto
344 0 rlineto
0 20 rlineto
-160 6 rlineto
endchar
</CharString>
<CharString name="three">
endchar
</CharString>
<CharString name="two">
endchar
</CharString>
</CharStrings>
</CFFFont>
<GlobalSubrs>
<!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
</GlobalSubrs>
"""
cffHexData = """
01 00 04 02 00 01 01 01 16 43 46 46 32 54 65 73 74 46 6F 6E 74 31 4D 61 73 74
65 72 2D 30 00 01 01 01 1C F8 1C 00 F8 1D 02 F8 1E 03 8B FB 14 F9 1F F9 8E 05 F7
19 0F CA FA E4 12 F7 26 11 00 04 01 01 0D 10 28 3E 64 6F 6C 6C 61 72 2E 62 6C 61
63 6B 31 2E 30 43 46 46 32 20 54 65 73 74 20 46 6F 6E 74 31 20 4D 61 73 74 65 72
20 30 43 46 46 32 20 54 65 73 74 20 46 6F 6E 74 31 20 4D 61 73 74 65 72 00 00 01
00 05 00 00 12 02 00 22 02 01 87 00 00 09 02 00 01 00 6B 01 1A 01 64 01 65 01 66
01 D0 02 90 02 FA 03 A8 2A 8B BD F8 EC BD 01 8B BD F8 24 BD 03 8B 8B 15 F8 88 8B
05 8B F9 50 05 FC 88 8B 05 8B FD 50 05 F7 8E F8 1F 15 FB 3E F7 93 05 F7 E8 8B 05
FB 3E FB 93 05 A9 5E 15 F7 3E F7 93 05 8B FC 92 05 FB 3E F7 93 05 FB 5C FB C0 15
F7 3E F7 93 05 F7 3E FB 93 05 FB E8 8B 05 6D F8 BF 15 F7 3E FB 93 05 FB 3E FB 93
05 8B F8 92 05 0E F7 89 90 15 4A 8B 64 9A 5D BD 08 AF 5B 05 6F EF 05 85 9A 81 90
80 8B 08 7D 8B 83 84 8A 7D 08 A3 36 C8 58 F6 8B 08 E6 8B E5 C1 8B F7 04 08 8B D1
71 CD FB 1A C4 08 78 93 05 2E B2 61 BC 8B CF 08 8B E6 C7 BB E3 8B 08 C3 8B AE 7D
B7 59 08 65 BA 05 A7 27 05 91 7C 95 86 96 8B 08 99 8B 93 92 8C 99 08 73 E3 48 BB
37 8B 08 2F 8B 39 58 8B FB 00 08 8B 3B B8 56 E7 61 08 B0 7A 05 F7 06 57 A5 5D 8B
4A 08 8B 2E 4A 54 31 8B 08 9D F7 D2 15 8B F8 4B 05 75 8B 05 8B FC 3D 05 A1 7D 05
77 FC 4A 15 A1 8B 05 8B F8 4A 05 75 99 05 8B FC 58 05 0E E6 F8 FE 15 8B 77 05 F7
2F AE 05 8B FC 39 05 8B 45 8A 44 89 43 08 AD 8B 05 89 D3 8A D2 8B D1 08 8B F7 BD
05 8F F7 26 05 7D 97 05 FB 41 5A 05 F7 44 FC E5 15 7D 8B 05 FB 3E 85 05 8B 77 05
F7 EC 8B 05 8B 9F 05 FB 34 91 05 0E 0E 0E C3 F8 9F A5 15 FB 0C 85 05 8B 77 05 F7
8C 8B 05 8B 9F 05 FB 06 91 05 7D 8B 05 FC 3C 8B 15 34 85 05 8B 77 05 F7 5A 8B 05
8B 9F 05 2A 91 05 7D 8B 05 F8 05 F7 71 15 83 9F 05 FB AA 8B 05 82 77 05 F7 BB 8B
05 FB 35 F8 42 15 FB 72 FD 39 05 A6 8B 05 F7 67 F9 28 05 7A 81 05 F7 6C FD 1E 05
AD 8B 05 FB 79 F9 39 05 77 8B 05 0E 88 F7 3B F7 CA 15 8B 23 8B 23 89 25 08 AD 8B
05 89 F1 8B F3 8B F7 24 08 8B 92 05 8B F7 06 8B F3 8D F1 08 69 8B 05 8D 25 8B 23
8B 23 08 8B 52 05 93 F7 E8 15 92 8B 05 8B A6 05 FB 10 8B 05 8B 77 05 F7 09 84 05
8B FD 03 15 FB 09 84 05 8B 77 05 F7 10 8B 05 8B A6 05 84 8B 05 92 F7 D0 15 F0 8B
05 F7 36 8B D0 4F 8B 25 08 8B 25 40 52 FB 11 8B 08 FB 18 8B 05 8B 75 05 F7 17 8B
05 F7 30 8B D6 D8 8B F1 08 8B EF 47 D7 FB 36 8D 08 81 83 05 F7 21 96 CB D6 8B DF
08 8B EA 49 CA FB 26 8B 08 FB 07 8B 05 8B 75 05 F3 8B 05 F7 25 8B BD 52 8B 3F 08
8B 2C 40 4B FB 1C 8B 08 33 8B 05 8B 77 05 0E BA F8 16 92 15 FB 3B 8B FB 0F F7 14
8B F7 5F 08 8B F7 5B F7 08 F7 19 F7 48 8B 08 D4 8B B3 7A C3 66 08 76 A8 05 9D FB
25 05 A3 8B 05 87 F7 1F 05 4F AE 5A 9D 3B 8B 08 FB 52 8B FB 1B FB 24 8B FB 66 08
8B FB 65 F7 15 FB 24 F7 57 8B 08 D3 8B C4 97 CE B4 08 8F F7 1F 05 73 8B 05 79 FB
1F 05 9C 9F 05 54 66 54 7D 48 8B 08 0E 8E F7 89 90 15 4A 8B 64 9A 5D BD 08 AF 5B
05 6F EF 05 85 9A 81 90 80 8B 08 7D 8B 83 84 8A 7D 08 A3 36 C8 58 F6 8B 08 E6 8B
E5 C1 8B F7 04 08 8B D1 71 CD FB 1A C4 08 78 93 05 2E B2 61 BC 8B CF 08 8B E6 C7
BB E3 8B 08 C3 8B AE 7D B7 59 08 65 BA 05 A7 27 05 91 7C 95 86 96 8B 08 99 8B 93
92 8C 99 08 73 E3 48 BB 37 8B 08 2F 8B 39 58 8B FB 00 08 8B 3B B8 56 E7 61 08 B0
7A 05 F7 06 57 A5 5D 8B 4A 08 8B 2E 4A 54 31 8B 08 9C F9 1F 15 8C F5 05 75 8B 05
8C 20 05 9F 8C 05 7C FD A4 15 A1 8B 05 88 F7 0D 05 77 8D 05 8C FB 0F 05 0E 77 9F
F8 66 9D BA 9A F5 9A 95 9F B4 9F 06 FB 93 95 07 77 9F F8 6D 9D AD 9A F3 9A 95 9F
B3 9F 08 FB 8D 95 09 1E A0 37 5F 0C 09 8B 0C 0B A5 0A A7 0B 9F 91 0C 0C A7 8F 0C
0D F8 7E 14 F8 E9 15"""
glyphOrder = ['.notdef', 'dollar', 'one', 'two', 'three', 'A', 'B', 'C', 'dollar.black']
def hexencode(s):
h = hexStr(s).upper()
return ' '.join([h[i:i+2] for i in range(0, len(h), 2)])
def parseTableXML(otTable, xmlText, font):
xmlList = parseXML(xmlText)
for entry in xmlList:
if len(entry) != 3: # skip comments and newlines.
continue
name, attrs, content = entry
otTable.fromXML(name, attrs, content, font)
def MakeFont():
font = TTFont()
return font
class CFFTableTest(unittest.TestCase):
def test_toXML(self):
global cffXML
# make font with post table.
font = MakeFont()
cffData = deHexStr(cffHexData)
CFF2Table = font['CFF '] = newTable('CFF ')
CFF2Table.decompile(cffData, font)
writer = XMLWriter(BytesIO())
font['CFF '].toXML(writer, font)
xml = writer.file.getvalue().decode("utf-8")
# normalize spacing and new-lines, so we can edit the XML without being very careful.
xml = re.sub(r" +", " ", xml)
xml = re.sub(r"[\r\n][\r\n]+", "\r", xml)
# cffXML does not have the initial xml definition line.
xml = re.sub(r"\<\?xml[^\r\n]+", "", xml)
cffXML = re.sub(r"\t", " ", cffXML)
cffXML = re.sub(r" +", " ", cffXML)
cffXML = re.sub(r"[\r\n][\r\n]+", "\r", cffXML)
self.assertEqual(xml, cffXML)
def test_fromXML(self):
global cffHexData
from fontTools.misc import xmlReader
font = MakeFont()
# charset data is stored in the ttx GlyphOrder table, not
# in the CFF XML dump. Need to to provide this externally to the
# ttx dump of the CFF table.
font.glyphOrder = glyphOrder
cffTable = font['CFF '] = newTable('CFF ')
cffXML2 = cffXML
parseTableXML(cffTable, cffXML, font)
cffData = cffTable.compile(font)
cffHexDatafromTable = hexencode(cffData)
cffHexDatafromTable = re.sub(r"\s+", " ", cffHexDatafromTable)
cffHexDatafromTable = cffHexDatafromTable.strip()
cffHexData = re.sub(r"\s+", " ", cffHexData)
cffHexData = cffHexData.strip()
self.assertEqual(cffHexDatafromTable, cffHexData)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,7 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
class table_H_V_A_R_(BaseTTXConverter):
pass

View File

@ -0,0 +1,7 @@
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from .otBase import BaseTTXConverter
class table_V_V_A_R_(BaseTTXConverter):
pass

View File

@ -24,6 +24,7 @@ def _moduleFinderHint():
from . import G_P_K_G_ from . import G_P_K_G_
from . import G_P_O_S_ from . import G_P_O_S_
from . import G_S_U_B_ from . import G_S_U_B_
from . import H_V_A_R_
from . import J_S_T_F_ from . import J_S_T_F_
from . import L_T_S_H_ from . import L_T_S_H_
from . import M_A_T_H_ from . import M_A_T_H_
@ -44,6 +45,7 @@ def _moduleFinderHint():
from . import T_S_I__5 from . import T_S_I__5
from . import V_D_M_X_ from . import V_D_M_X_
from . import V_O_R_G_ from . import V_O_R_G_
from . import V_V_A_R_
from . import _a_v_a_r from . import _a_v_a_r
from . import _c_m_a_p from . import _c_m_a_p
from . import _c_v_t from . import _c_v_t

View File

@ -29,12 +29,12 @@ FVAR_AXIS_FORMAT = """
defaultValue: 16.16F defaultValue: 16.16F
maxValue: 16.16F maxValue: 16.16F
flags: H flags: H
nameID: H axisNameID: H
""" """
FVAR_INSTANCE_FORMAT = """ FVAR_INSTANCE_FORMAT = """
> # big endian > # big endian
nameID: H subfamilyNameID: H
flags: H flags: H
""" """
@ -47,6 +47,9 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
self.instances = [] self.instances = []
def compile(self, ttFont): def compile(self, ttFont):
instanceSize = sstruct.calcsize(FVAR_INSTANCE_FORMAT) + (len(self.axes) * 4)
if any(instance.postscriptNameID != 0xFFFF for instance in self.instances):
instanceSize += 2
header = { header = {
"version": 0x00010000, "version": 0x00010000,
"offsetToData": sstruct.calcsize(FVAR_HEADER_FORMAT), "offsetToData": sstruct.calcsize(FVAR_HEADER_FORMAT),
@ -54,12 +57,12 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
"axisCount": len(self.axes), "axisCount": len(self.axes),
"axisSize": sstruct.calcsize(FVAR_AXIS_FORMAT), "axisSize": sstruct.calcsize(FVAR_AXIS_FORMAT),
"instanceCount": len(self.instances), "instanceCount": len(self.instances),
"instanceSize": sstruct.calcsize(FVAR_INSTANCE_FORMAT) + len(self.axes) * 4 "instanceSize": instanceSize,
} }
result = [sstruct.pack(FVAR_HEADER_FORMAT, header)] result = [sstruct.pack(FVAR_HEADER_FORMAT, header)]
result.extend([axis.compile() for axis in self.axes]) result.extend([axis.compile() for axis in self.axes])
axisTags = [axis.axisTag for axis in self.axes] axisTags = [axis.axisTag for axis in self.axes]
result.extend([instance.compile(axisTags) for instance in self.instances]) result.extend([instance.compile(axisTags)[:instanceSize] for instance in self.instances])
return bytesjoin(result) return bytesjoin(result)
def decompile(self, data, ttFont): def decompile(self, data, ttFont):
@ -102,7 +105,7 @@ class table__f_v_a_r(DefaultTable.DefaultTable):
class Axis(object): class Axis(object):
def __init__(self): def __init__(self):
self.axisTag = None self.axisTag = None
self.nameID = 0 self.axisNameID = 0
self.flags = 0 # not exposed in XML because spec defines no values self.flags = 0 # not exposed in XML because spec defines no values
self.minValue = -1.0 self.minValue = -1.0
self.defaultValue = 0.0 self.defaultValue = 0.0
@ -115,7 +118,7 @@ class Axis(object):
sstruct.unpack2(FVAR_AXIS_FORMAT, data, self) sstruct.unpack2(FVAR_AXIS_FORMAT, data, self)
def toXML(self, writer, ttFont): def toXML(self, writer, ttFont):
name = ttFont["name"].getDebugName(self.nameID) name = ttFont["name"].getDebugName(self.axisNameID)
if name is not None: if name is not None:
writer.newline() writer.newline()
writer.comment(name) writer.comment(name)
@ -126,7 +129,7 @@ class Axis(object):
("MinValue", str(self.minValue)), ("MinValue", str(self.minValue)),
("DefaultValue", str(self.defaultValue)), ("DefaultValue", str(self.defaultValue)),
("MaxValue", str(self.maxValue)), ("MaxValue", str(self.maxValue)),
("NameID", str(self.nameID))]: ("AxisNameID", str(self.axisNameID))]:
writer.begintag(tag) writer.begintag(tag)
writer.write(value) writer.write(value)
writer.endtag(tag) writer.endtag(tag)
@ -139,13 +142,14 @@ class Axis(object):
for tag, _, value in filter(lambda t: type(t) is tuple, content): for tag, _, value in filter(lambda t: type(t) is tuple, content):
value = ''.join(value) value = ''.join(value)
if tag == "AxisTag": if tag == "AxisTag":
self.axisTag = value self.axisTag = Tag(value)
elif tag in ["MinValue", "DefaultValue", "MaxValue", "NameID"]: elif tag in ["MinValue", "DefaultValue", "MaxValue", "AxisNameID"]:
setattr(self, tag[0].lower() + tag[1:], safeEval(value)) setattr(self, tag[0].lower() + tag[1:], safeEval(value))
class NamedInstance(object): class NamedInstance(object):
def __init__(self): def __init__(self):
self.nameID = 0 self.subfamilyNameID = 0
self.postscriptNameID = 0xFFFF
self.flags = 0 # not exposed in XML because spec defines no values self.flags = 0 # not exposed in XML because spec defines no values
self.coordinates = {} self.coordinates = {}
@ -154,6 +158,7 @@ class NamedInstance(object):
for axis in axisTags: for axis in axisTags:
fixedCoord = floatToFixed(self.coordinates[axis], 16) fixedCoord = floatToFixed(self.coordinates[axis], 16)
result.append(struct.pack(">l", fixedCoord)) result.append(struct.pack(">l", fixedCoord))
result.append(struct.pack(">H", self.postscriptNameID))
return bytesjoin(result) return bytesjoin(result)
def decompile(self, data, axisTags): def decompile(self, data, axisTags):
@ -163,14 +168,22 @@ class NamedInstance(object):
value = struct.unpack(">l", data[pos : pos + 4])[0] value = struct.unpack(">l", data[pos : pos + 4])[0]
self.coordinates[axis] = fixedToFloat(value, 16) self.coordinates[axis] = fixedToFloat(value, 16)
pos += 4 pos += 4
if pos + 2 <= len(data):
self.postscriptNameID = struct.unpack(">H", data[pos : pos + 2])[0]
else:
self.postscriptNameID = 0xFFFF
def toXML(self, writer, ttFont): def toXML(self, writer, ttFont):
name = ttFont["name"].getDebugName(self.nameID) name = ttFont["name"].getDebugName(self.subfamilyNameID)
if name is not None: if name is not None:
writer.newline() writer.newline()
writer.comment(name) writer.comment(name)
writer.newline() writer.newline()
writer.begintag("NamedInstance", nameID=self.nameID) if self.postscriptNameID == 0xFFFF:
writer.begintag("NamedInstance", subfamilyNameID=self.subfamilyNameID)
else:
writer.begintag("NamedInstance", subfamilyNameID=self.subfamilyNameID,
postscriptNameID=self.postscriptNameID, )
writer.newline() writer.newline()
for axis in ttFont["fvar"].axes: for axis in ttFont["fvar"].axes:
writer.simpletag("coord", axis=axis.axisTag, writer.simpletag("coord", axis=axis.axisTag,
@ -181,7 +194,12 @@ class NamedInstance(object):
def fromXML(self, name, attrs, content, ttFont): def fromXML(self, name, attrs, content, ttFont):
assert(name == "NamedInstance") assert(name == "NamedInstance")
self.nameID = safeEval(attrs["nameID"]) self.subfamilyNameID = safeEval(attrs["subfamilyNameID"])
if "postscriptNameID" in attrs:
self.postscriptNameID = safeEval(attrs["postscriptNameID"])
else:
self.postscriptNameID = 0xFFFF
for tag, elementAttrs, _ in filter(lambda t: type(t) is tuple, content): for tag, elementAttrs, _ in filter(lambda t: type(t) is tuple, content):
if tag == "coord": if tag == "coord":
self.coordinates[elementAttrs["axis"]] = safeEval(elementAttrs["value"]) self.coordinates[elementAttrs["axis"]] = safeEval(elementAttrs["value"])

View File

@ -20,7 +20,7 @@ FVAR_DATA = deHexStr(
FVAR_AXIS_DATA = deHexStr( FVAR_AXIS_DATA = deHexStr(
"6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59") "6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59")
FVAR_INSTANCE_DATA = deHexStr("01 59 00 00 00 00 b3 33 00 00 80 00") FVAR_INSTANCE_DATA = deHexStr("01 59 00 00 00 00 b3 33 00 00 80 00 ff ff")
def xml_lines(writer): def xml_lines(writer):
@ -51,11 +51,11 @@ def MakeFont():
axis.axisTag = tag axis.axisTag = tag
axis.defaultValue = defaultValue axis.defaultValue = defaultValue
axis.minValue, axis.maxValue = minValue, maxValue axis.minValue, axis.maxValue = minValue, maxValue
axis.nameID = AddName(font, name).nameID axis.axisNameID = AddName(font, name).nameID
fvarTable.axes.append(axis) fvarTable.axes.append(axis)
for name, weight, width in instances: for name, weight, width in instances:
inst = NamedInstance() inst = NamedInstance()
inst.nameID = AddName(font, name).nameID inst.subfamilyNameID = AddName(font, name).nameID
inst.coordinates = {"wght": weight, "wdth": width} inst.coordinates = {"wght": weight, "wdth": width}
fvarTable.instances.append(inst) fvarTable.instances.append(inst)
return font return font
@ -71,7 +71,7 @@ class FontVariationTableTest(unittest.TestCase):
fvar = table__f_v_a_r() fvar = table__f_v_a_r()
fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar}) fvar.decompile(FVAR_DATA, ttFont={"fvar": fvar})
self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes]) self.assertEqual(["wght", "wdth"], [a.axisTag for a in fvar.axes])
self.assertEqual([259, 260], [i.nameID for i in fvar.instances]) self.assertEqual([259, 260], [i.subfamilyNameID for i in fvar.instances])
def test_toXML(self): def test_toXML(self):
font = MakeFont() font = MakeFont()
@ -94,17 +94,17 @@ class FontVariationTableTest(unittest.TestCase):
'<Axis>' '<Axis>'
' <AxisTag>slnt</AxisTag>' ' <AxisTag>slnt</AxisTag>'
'</Axis>' '</Axis>'
'<NamedInstance nameID="765"/>' '<NamedInstance subfamilyNameID="765"/>'
'<NamedInstance nameID="234"/>'): '<NamedInstance subfamilyNameID="234"/>'):
fvar.fromXML(name, attrs, content, ttFont=None) fvar.fromXML(name, attrs, content, ttFont=None)
self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes]) self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes])
self.assertEqual([765, 234], [i.nameID for i in fvar.instances]) self.assertEqual([765, 234], [i.subfamilyNameID for i in fvar.instances])
class AxisTest(unittest.TestCase): class AxisTest(unittest.TestCase):
def test_compile(self): def test_compile(self):
axis = Axis() axis = Axis()
axis.axisTag, axis.nameID = ('opsz', 345) axis.axisTag, axis.axisNameID = ('opsz', 345)
axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5) axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5)
self.assertEqual(FVAR_AXIS_DATA, axis.compile()) self.assertEqual(FVAR_AXIS_DATA, axis.compile())
@ -112,7 +112,7 @@ class AxisTest(unittest.TestCase):
axis = Axis() axis = Axis()
axis.decompile(FVAR_AXIS_DATA) axis.decompile(FVAR_AXIS_DATA)
self.assertEqual("opsz", axis.axisTag) self.assertEqual("opsz", axis.axisTag)
self.assertEqual(345, axis.nameID) self.assertEqual(345, axis.axisNameID)
self.assertEqual(-0.5, axis.minValue) self.assertEqual(-0.5, axis.minValue)
self.assertEqual(1.3, axis.defaultValue) self.assertEqual(1.3, axis.defaultValue)
self.assertEqual(1.5, axis.maxValue) self.assertEqual(1.5, axis.maxValue)
@ -122,7 +122,7 @@ class AxisTest(unittest.TestCase):
axis = Axis() axis = Axis()
axis.decompile(FVAR_AXIS_DATA) axis.decompile(FVAR_AXIS_DATA)
AddName(font, "Optical Size").nameID = 256 AddName(font, "Optical Size").nameID = 256
axis.nameID = 256 axis.axisNameID = 256
writer = XMLWriter(BytesIO()) writer = XMLWriter(BytesIO())
axis.toXML(writer, font) axis.toXML(writer, font)
self.assertEqual([ self.assertEqual([
@ -133,7 +133,7 @@ class AxisTest(unittest.TestCase):
'<MinValue>-0.5</MinValue>', '<MinValue>-0.5</MinValue>',
'<DefaultValue>1.3</DefaultValue>', '<DefaultValue>1.3</DefaultValue>',
'<MaxValue>1.5</MaxValue>', '<MaxValue>1.5</MaxValue>',
'<NameID>256</NameID>', '<AxisNameID>256</AxisNameID>',
'</Axis>' '</Axis>'
], xml_lines(writer)) ], xml_lines(writer))
@ -145,40 +145,40 @@ class AxisTest(unittest.TestCase):
' <MinValue>100</MinValue>' ' <MinValue>100</MinValue>'
' <DefaultValue>400</DefaultValue>' ' <DefaultValue>400</DefaultValue>'
' <MaxValue>900</MaxValue>' ' <MaxValue>900</MaxValue>'
' <NameID>256</NameID>' ' <AxisNameID>256</AxisNameID>'
'</Axis>'): '</Axis>'):
axis.fromXML(name, attrs, content, ttFont=None) axis.fromXML(name, attrs, content, ttFont=None)
self.assertEqual("wght", axis.axisTag) self.assertEqual("wght", axis.axisTag)
self.assertEqual(100, axis.minValue) self.assertEqual(100, axis.minValue)
self.assertEqual(400, axis.defaultValue) self.assertEqual(400, axis.defaultValue)
self.assertEqual(900, axis.maxValue) self.assertEqual(900, axis.maxValue)
self.assertEqual(256, axis.nameID) self.assertEqual(256, axis.axisNameID)
class NamedInstanceTest(unittest.TestCase): class NamedInstanceTest(unittest.TestCase):
def test_compile(self): def test_compile(self):
inst = NamedInstance() inst = NamedInstance()
inst.nameID = 345 inst.subfamilyNameID = 345
inst.coordinates = {"wght": 0.7, "wdth": 0.5} inst.coordinates = {"wght": 0.7, "wdth": 0.5}
self.assertEqual(FVAR_INSTANCE_DATA, inst.compile(["wght", "wdth"])) self.assertEqual(FVAR_INSTANCE_DATA, inst.compile(["wght", "wdth"]))
def test_decompile(self): def test_decompile(self):
inst = NamedInstance() inst = NamedInstance()
inst.decompile(FVAR_INSTANCE_DATA, ["wght", "wdth"]) inst.decompile(FVAR_INSTANCE_DATA, ["wght", "wdth"])
self.assertEqual(345, inst.nameID) self.assertEqual(345, inst.subfamilyNameID)
self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)
def test_toXML(self): def test_toXML(self):
font = MakeFont() font = MakeFont()
inst = NamedInstance() inst = NamedInstance()
inst.nameID = AddName(font, "Light Condensed").nameID inst.subfamilyNameID = AddName(font, "Light Condensed").nameID
inst.coordinates = {"wght": 0.7, "wdth": 0.5} inst.coordinates = {"wght": 0.7, "wdth": 0.5}
writer = XMLWriter(BytesIO()) writer = XMLWriter(BytesIO())
inst.toXML(writer, font) inst.toXML(writer, font)
self.assertEqual([ self.assertEqual([
'', '',
'<!-- Light Condensed -->', '<!-- Light Condensed -->',
'<NamedInstance nameID="%s">' % inst.nameID, '<NamedInstance subfamilyNameID="%s">' % inst.subfamilyNameID,
'<coord axis="wght" value="0.7"/>', '<coord axis="wght" value="0.7"/>',
'<coord axis="wdth" value="0.5"/>', '<coord axis="wdth" value="0.5"/>',
'</NamedInstance>' '</NamedInstance>'
@ -187,12 +187,12 @@ class NamedInstanceTest(unittest.TestCase):
def test_fromXML(self): def test_fromXML(self):
inst = NamedInstance() inst = NamedInstance()
for name, attrs, content in parseXML( for name, attrs, content in parseXML(
'<NamedInstance nameID="345">' '<NamedInstance subfamilyNameID="345">'
' <coord axis="wght" value="0.7"/>' ' <coord axis="wght" value="0.7"/>'
' <coord axis="wdth" value="0.5"/>' ' <coord axis="wdth" value="0.5"/>'
'</NamedInstance>'): '</NamedInstance>'):
inst.fromXML(name, attrs, content, ttFont=MakeFont()) inst.fromXML(name, attrs, content, ttFont=MakeFont())
self.assertEqual(345, inst.nameID) self.assertEqual(345, inst.subfamilyNameID)
self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates) self.assertEqual({"wght": 0.7, "wdth": 0.5}, inst.coordinates)

View File

@ -427,6 +427,8 @@ class GlyphVariation(object):
def compile(self, axisTags, sharedCoordIndices, sharedPoints): def compile(self, axisTags, sharedCoordIndices, sharedPoints):
tupleData = [] tupleData = []
assert all(tag in axisTags for tag in self.axes.keys()), ("Unknown axis tag found.", self.axes.keys(), axisTags)
coord = self.compileCoord(axisTags) coord = self.compileCoord(axisTags)
if coord in sharedCoordIndices: if coord in sharedCoordIndices:
flags = sharedCoordIndices[coord] flags = sharedCoordIndices[coord]

View File

@ -164,6 +164,13 @@ class OTTableReader(object):
self.pos = newpos self.pos = newpos
return value return value
def readInt8(self):
pos = self.pos
newpos = pos + 1
value, = struct.unpack(">b", self.data[pos:newpos])
self.pos = newpos
return value
def readShort(self): def readShort(self):
pos = self.pos pos = self.pos
newpos = pos + 2 newpos = pos + 2
@ -429,12 +436,17 @@ class OTTableWriter(object):
self.items.append(struct.pack(">H", value)) self.items.append(struct.pack(">H", value))
def writeShort(self, value): def writeShort(self, value):
assert -32768 <= value < 32768, value
self.items.append(struct.pack(">h", value)) self.items.append(struct.pack(">h", value))
def writeUInt8(self, value): def writeUInt8(self, value):
assert 0 <= value < 256 assert 0 <= value < 256, value
self.items.append(struct.pack(">B", value)) self.items.append(struct.pack(">B", value))
def writeInt8(self, value):
assert -128 <= value < 128, value
self.items.append(struct.pack(">b", value))
def writeUInt24(self, value): def writeUInt24(self, value):
assert 0 <= value < 0x1000000, value assert 0 <= value < 0x1000000, value
b = struct.pack(">L", value) b = struct.pack(">L", value)
@ -454,8 +466,8 @@ class OTTableWriter(object):
def writeSubTable(self, subWriter): def writeSubTable(self, subWriter):
self.items.append(subWriter) self.items.append(subWriter)
def writeCountReference(self, table, name): def writeCountReference(self, table, name, size=2):
ref = CountReference(table, name) ref = CountReference(table, name, size=size)
self.items.append(ref) self.items.append(ref)
return ref return ref
@ -502,9 +514,10 @@ class OTTableWriter(object):
class CountReference(object): class CountReference(object):
"""A reference to a Count value, not a count of references.""" """A reference to a Count value, not a count of references."""
def __init__(self, table, name): def __init__(self, table, name, size=None):
self.table = table self.table = table
self.name = name self.name = name
self.size = size
def setValue(self, value): def setValue(self, value):
table = self.table table = self.table
name = self.name name = self.name
@ -513,9 +526,10 @@ class CountReference(object):
else: else:
assert table[name] == value, (name, table[name], value) assert table[name] == value, (name, table[name], value)
def getCountData(self): def getCountData(self):
assert self.size in (2, 4)
v = self.table[self.name] v = self.table[self.name]
if v is None: v = 0 if v is None: v = 0
return packUShort(v) return packUShort(v) if self.size == 2 else packULong(v)
def packUShort(value): def packUShort(value):
@ -645,12 +659,12 @@ class BaseTable(object):
# table. We will later store it here. # table. We will later store it here.
# We add a reference: by the time the data is assembled # We add a reference: by the time the data is assembled
# the Count value will be filled in. # the Count value will be filled in.
ref = writer.writeCountReference(table, conv.name) ref = writer.writeCountReference(table, conv.name, conv.staticSize)
table[conv.name] = None table[conv.name] = None
if conv.isPropagated: if conv.isPropagated:
writer[conv.name] = ref writer[conv.name] = ref
elif conv.isLookupType: elif conv.isLookupType:
ref = writer.writeCountReference(table, conv.name) ref = writer.writeCountReference(table, conv.name, conv.staticSize)
table[conv.name] = None table[conv.name] = None
writer['LookupType'] = ref writer['LookupType'] = ref
else: else:

View File

@ -3,6 +3,7 @@ from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval from fontTools.misc.textTools import safeEval
from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi
from .otBase import ValueRecordFactory from .otBase import ValueRecordFactory
from functools import partial
import logging import logging
@ -21,8 +22,8 @@ def buildConverters(tableSpec, tableNamespace):
assert tp == "uint16" assert tp == "uint16"
converterClass = ValueFormat converterClass = ValueFormat
elif name.endswith("Count") or name.endswith("LookupType"): elif name.endswith("Count") or name.endswith("LookupType"):
assert tp == "uint16" assert tp in ("uint16", "uint32")
converterClass = ComputedUShort converterClass = ComputedUShort if tp == 'uint16' else ComputedULong
elif name == "SubTable": elif name == "SubTable":
converterClass = SubTable converterClass = SubTable
elif name == "ExtSubTable": elif name == "ExtSubTable":
@ -30,13 +31,16 @@ def buildConverters(tableSpec, tableNamespace):
elif name == "FeatureParams": elif name == "FeatureParams":
converterClass = FeatureParams converterClass = FeatureParams
else: else:
if not tp in converterMapping: if not tp in converterMapping and '(' not in tp:
tableName = tp tableName = tp
converterClass = Struct converterClass = Struct
else: else:
converterClass = converterMapping[tp] converterClass = eval(tp, tableNamespace, converterMapping)
tableClass = tableNamespace.get(tableName) tableClass = tableNamespace.get(tableName)
conv = converterClass(name, repeat, aux, tableClass) if tableClass is not None:
conv = converterClass(name, repeat, aux, tableClass=tableClass)
else:
conv = converterClass(name, repeat, aux)
if name in ["SubTable", "ExtSubTable"]: if name in ["SubTable", "ExtSubTable"]:
conv.lookupTypes = tableNamespace['lookupTypes'] conv.lookupTypes = tableNamespace['lookupTypes']
# also create reverse mapping # also create reverse mapping
@ -82,14 +86,14 @@ class BaseConverter(object):
"""Base class for converter objects. Apart from the constructor, this """Base class for converter objects. Apart from the constructor, this
is an abstract class.""" is an abstract class."""
def __init__(self, name, repeat, aux, tableClass): def __init__(self, name, repeat, aux, tableClass=None):
self.name = name self.name = name
self.repeat = repeat self.repeat = repeat
self.aux = aux self.aux = aux
self.tableClass = tableClass self.tableClass = tableClass
self.isCount = name.endswith("Count") self.isCount = name.endswith("Count")
self.isLookupType = name.endswith("LookupType") self.isLookupType = name.endswith("LookupType")
self.isPropagated = name in ["ClassCount", "Class2Count", "FeatureTag", "SettingsCount", "AxisCount"] self.isPropagated = name in ["ClassCount", "Class2Count", "FeatureTag", "SettingsCount", "VarRegionCount", "MappingCount", "RegionAxisCount"]
def readArray(self, reader, font, tableDict, count): def readArray(self, reader, font, tableDict, count):
"""Read an array of values from the reader.""" """Read an array of values from the reader."""
@ -178,6 +182,13 @@ class UShort(IntValue):
def write(self, writer, font, tableDict, value, repeatIndex=None): def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeUShort(value) writer.writeUShort(value)
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)
class UInt8(IntValue): class UInt8(IntValue):
staticSize = 1 staticSize = 1
def read(self, reader, font, tableDict): def read(self, reader, font, tableDict):
@ -192,11 +203,16 @@ class UInt24(IntValue):
def write(self, writer, font, tableDict, value, repeatIndex=None): def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeUInt24(value) writer.writeUInt24(value)
class ComputedUShort(UShort): class ComputedInt(IntValue):
def xmlWrite(self, xmlWriter, font, value, name, attrs): def xmlWrite(self, xmlWriter, font, value, name, attrs):
xmlWriter.comment("%s=%s" % (name, value)) xmlWriter.comment("%s=%s" % (name, value))
xmlWriter.newline() xmlWriter.newline()
class ComputedUShort(ComputedInt, UShort):
pass
class ComputedULong(ComputedInt, ULong):
pass
class Tag(SimpleValue): class Tag(SimpleValue):
staticSize = 4 staticSize = 4
def read(self, reader, font, tableDict): def read(self, reader, font, tableDict):
@ -239,33 +255,47 @@ class Fixed(FloatValue):
def write(self, writer, font, tableDict, value, repeatIndex=None): def write(self, writer, font, tableDict, value, repeatIndex=None):
writer.writeLong(fl2fi(value, 16)) writer.writeLong(fl2fi(value, 16))
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))
class Version(BaseConverter): class Version(BaseConverter):
staticSize = 4 staticSize = 4
def read(self, reader, font, tableDict): def read(self, reader, font, tableDict):
value = reader.readLong() value = reader.readLong()
assert (value >> 16) == 1, "Unsupported version 0x%08x" % value assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
return fi2fl(value, 16) return value
def write(self, writer, font, tableDict, value, repeatIndex=None): def write(self, writer, font, tableDict, value, repeatIndex=None):
if value < 0x10000: if value < 0x10000:
value = fl2fi(value, 16) newValue = self.fromFloat(value)
value = int(round(value)) log.warning("Table version value is a float: %g; fix code to use hex instead: %08x", value, newValue)
value = newValue
assert (value >> 16) == 1, "Unsupported version 0x%08x" % value assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
writer.writeLong(value) writer.writeLong(value)
def xmlRead(self, attrs, content, font): def xmlRead(self, attrs, content, font):
value = attrs["value"] value = attrs["value"]
value = float(int(value, 0)) if value.startswith("0") else float(value) value = int(value, 0) if value.startswith("0") else float(value)
if value >= 0x10000: if value < 0x10000:
value = fi2fl(value, 16) newValue = self.fromFloat(value)
log.warning("Table version value is a float: %g; fix XML to use hex instead: %08x", value, newValue)
value = newValue
return value return value
def xmlWrite(self, xmlWriter, font, value, name, attrs): def xmlWrite(self, xmlWriter, font, value, name, attrs):
if value >= 0x10000: if value < 0x10000:
value = fi2fl(value, 16) newValue = self.fromFloat(value)
if value % 1 != 0: log.warning("Table version value is a float: %g; fix code to use hex instead: %08x", value, newValue)
# Write as hex value = newValue
value = "0x%08x" % fl2fi(value, 16) value = "0x%08x" % value
xmlWriter.simpletag(name, attrs + [("value", value)]) xmlWriter.simpletag(name, attrs + [("value", value)])
xmlWriter.newline() xmlWriter.newline()
@staticmethod
def fromFloat(v):
return fl2fi(v, 16)
class Struct(BaseConverter): class Struct(BaseConverter):
@ -389,7 +419,7 @@ class FeatureParams(Table):
class ValueFormat(IntValue): class ValueFormat(IntValue):
staticSize = 2 staticSize = 2
def __init__(self, name, repeat, aux, tableClass): def __init__(self, name, repeat, aux, tableClass=None):
BaseConverter.__init__(self, name, repeat, aux, tableClass) BaseConverter.__init__(self, name, repeat, aux, tableClass)
self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1") self.which = "ValueFormat" + ("2" if name[-1] == "2" else "1")
def read(self, reader, font, tableDict): def read(self, reader, font, tableDict):
@ -474,10 +504,97 @@ class DeltaValue(BaseConverter):
return safeEval(attrs["value"]) return safeEval(attrs["value"])
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)
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"])
converterMapping = { converterMapping = {
# type class # type class
"int8": Int8,
"int16": Short, "int16": Short,
"uint8": UInt8, "uint8": UInt8,
"uint8": UInt8,
"uint16": UShort, "uint16": UShort,
"uint24": UInt24, "uint24": UInt24,
"uint32": ULong, "uint32": ULong,
@ -486,9 +603,15 @@ converterMapping = {
"GlyphID": GlyphID, "GlyphID": GlyphID,
"DeciPoints": DeciPoints, "DeciPoints": DeciPoints,
"Fixed": Fixed, "Fixed": Fixed,
"F2Dot14": F2Dot14,
"struct": Struct, "struct": Struct,
"Offset": Table, "Offset": Table,
"LOffset": LTable, "LOffset": LTable,
"ValueRecord": ValueRecord, "ValueRecord": ValueRecord,
"DeltaValue": DeltaValue, "DeltaValue": DeltaValue,
"VarIdxMapValue": VarIdxMapValue,
"VarDataValue": VarDataValue,
# "Template" types
"OffsetTo": lambda C: partial(Table, tableClass=C),
"LOffsetTo": lambda C: partial(LTable, tableClass=C),
} }

View File

@ -134,7 +134,7 @@ otData = [
('uint16', 'StartSize', None, None, 'Smallest size to correct-in ppem'), ('uint16', 'StartSize', None, None, 'Smallest size to correct-in ppem'),
('uint16', 'EndSize', None, None, 'Largest size to correct-in ppem'), ('uint16', 'EndSize', None, None, 'Largest size to correct-in ppem'),
('uint16', 'DeltaFormat', None, None, 'Format of DeltaValue array data: 1, 2, or 3'), ('uint16', 'DeltaFormat', None, None, 'Format of DeltaValue array data: 1, 2, or 3'),
('DeltaValue', 'DeltaValue', '', 0, 'Array of compressed data'), ('DeltaValue', 'DeltaValue', '', 'DeltaFormat in (1,2,3)', 'Array of compressed data'),
]), ]),
@ -143,10 +143,11 @@ otData = [
# #
('GPOS', [ ('GPOS', [
('Version', 'Version', None, None, 'Version of the GPOS table-initially = 0x00010000'), ('Version', 'Version', None, None, 'Version of the GPOS table- 0x00010000 or 0x00010001'),
('Offset', 'ScriptList', None, None, 'Offset to ScriptList table-from beginning of GPOS table'), ('Offset', 'ScriptList', None, None, 'Offset to ScriptList table-from beginning of GPOS table'),
('Offset', 'FeatureList', None, None, 'Offset to FeatureList table-from beginning of GPOS table'), ('Offset', 'FeatureList', None, None, 'Offset to FeatureList table-from beginning of GPOS table'),
('Offset', 'LookupList', None, None, 'Offset to LookupList table-from beginning of GPOS table'), ('Offset', 'LookupList', None, None, 'Offset to LookupList table-from beginning of GPOS table'),
('LOffset', 'FeatureVariations', None, 'Version >= 0x00010001', 'Offset to FeatureVariations table-from beginning of GPOS table'),
]), ]),
('SinglePosFormat1', [ ('SinglePosFormat1', [
@ -443,10 +444,11 @@ otData = [
# #
('GSUB', [ ('GSUB', [
('Version', 'Version', None, None, 'Version of the GSUB table-initially set to 0x00010000'), ('Version', 'Version', None, None, 'Version of the GSUB table- 0x00010000 or 0x00010001'),
('Offset', 'ScriptList', None, None, 'Offset to ScriptList table-from beginning of GSUB table'), ('Offset', 'ScriptList', None, None, 'Offset to ScriptList table-from beginning of GSUB table'),
('Offset', 'FeatureList', None, None, 'Offset to FeatureList table-from beginning of GSUB table'), ('Offset', 'FeatureList', None, None, 'Offset to FeatureList table-from beginning of GSUB table'),
('Offset', 'LookupList', None, None, 'Offset to LookupList table-from beginning of GSUB table'), ('Offset', 'LookupList', None, None, 'Offset to LookupList table-from beginning of GSUB table'),
('LOffset', 'FeatureVariations', None, 'Version >= 0x00010001', 'Offset to FeatureVariations table-from beginning of GSUB table'),
]), ]),
('SingleSubstFormat1', [ ('SingleSubstFormat1', [
@ -639,12 +641,13 @@ otData = [
# #
('GDEF', [ ('GDEF', [
('Version', 'Version', None, None, 'Version of the GDEF table-initially 0x00010000'), ('Version', 'Version', None, None, 'Version of the GDEF table- 0x00010000, 0x00010002, or 0x00010003'),
('Offset', 'GlyphClassDef', None, None, 'Offset to class definition table for glyph type-from beginning of GDEF header (may be NULL)'), ('Offset', 'GlyphClassDef', None, None, 'Offset to class definition table for glyph type-from beginning of GDEF header (may be NULL)'),
('Offset', 'AttachList', None, None, 'Offset to list of glyphs with attachment points-from beginning of GDEF header (may be NULL)'), ('Offset', 'AttachList', None, None, 'Offset to list of glyphs with attachment points-from beginning of GDEF header (may be NULL)'),
('Offset', 'LigCaretList', None, None, 'Offset to list of positioning points for ligature carets-from beginning of GDEF header (may be NULL)'), ('Offset', 'LigCaretList', None, None, 'Offset to list of positioning points for ligature carets-from beginning of GDEF header (may be NULL)'),
('Offset', 'MarkAttachClassDef', None, None, 'Offset to class definition table for mark attachment type-from beginning of GDEF header (may be NULL)'), ('Offset', 'MarkAttachClassDef', None, None, 'Offset to class definition table for mark attachment type-from beginning of GDEF header (may be NULL)'),
('Offset', 'MarkGlyphSetsDef', None, 'int(round(Version*0x10000)) >= 0x00010002', 'Offset to the table of mark set definitions-from beginning of GDEF header (may be NULL)'), ('Offset', 'MarkGlyphSetsDef', None, 'Version >= 0x00010002', 'Offset to the table of mark set definitions-from beginning of GDEF header (may be NULL)'),
('LOffset', 'VarStore', None, 'Version >= 0x00010003', 'Offset to variation store (may be NULL)'),
]), ]),
('AttachList', [ ('AttachList', [
@ -836,6 +839,107 @@ otData = [
('Offset', 'Lookup', 'LookupCount', 0, 'Array of offsets to GPOS-type lookup tables-from beginning of JstfMax table-in design order'), ('Offset', 'Lookup', 'LookupCount', 0, 'Array of offsets to GPOS-type lookup tables-from beginning of JstfMax table-in design order'),
]), ]),
#
# Variation fonts
#
# GSUB/GPOS FeatureVariations
('FeatureVariations', [
('Version', 'Version', None, None, 'Version of the table-initially set to 0x00010000'),
('uint32', 'FeatureVariationCount', None, None, 'Number of records in the FeatureVariationRecord array'),
('struct', 'FeatureVariationRecord', 'FeatureVariationCount', 0, 'Array of FeatureVariationRecord'),
]),
('FeatureVariationRecord', [
('LOffset', 'ConditionSet', None, None, 'Offset to a ConditionSet table, from beginning of the FeatureVariations table.'),
('LOffset', 'FeatureTableSubstitution', None, None, 'Offset to a FeatureTableSubstitution table, from beginning of the FeatureVariations table'),
]),
('ConditionSet', [
('uint16', 'ConditionCount', None, None, 'Number of condition tables in the ConditionTable array'),
('LOffset', 'ConditionTable', 'ConditionCount', 0, 'Array of condition tables.'),
]),
('ConditionTableFormat1', [
('uint16', 'Format', None, None, 'Format, = 1'),
('uint16', 'AxisIndex', None, None, 'Index for the variation axis within the fvar table, base 0.'),
('F2Dot14', 'FilterRangeMinValue', None, None, 'Minimum normalized axis value of the font variation instances that satisfy this condition.'),
('F2Dot14', 'FilterRangeMaxValue', None, None, 'Maximum value that satisfies this condition.'),
]),
('FeatureTableSubstitution', [
('Version', 'Version', None, None, 'Version of the table-initially set to 0x00010000'),
('uint16', 'SubstitutionCount', None, None, 'Number of records in the FeatureVariationRecords array'),
('FeatureTableSubstitutionRecord', 'SubstitutionRecord', 'SubstitutionCount', 0, 'Array of FeatureTableSubstitutionRecord'),
]),
('FeatureTableSubstitutionRecord', [
('uint16', 'FeatureIndex', None, None, 'The feature table index to match.'),
('LOffset', 'Feature', None, None, 'Offset to an alternate feature table, from start of the FeatureTableSubstitution table.'),
]),
# VariationStore
('VarRegionAxis', [
('F2Dot14', 'StartCoord', None, None, ''),
('F2Dot14', 'PeakCoord', None, None, ''),
('F2Dot14', 'EndCoord', None, None, ''),
]),
('VarRegion', [
('struct', 'VarRegionAxis', 'RegionAxisCount', 0, ''),
]),
('VarRegionList', [
('uint16', 'RegionAxisCount', None, None, ''),
('uint16', 'RegionCount', None, None, ''),
('VarRegion', 'Region', 'RegionCount', 0, ''),
]),
('VarData', [
('uint16', 'ItemCount', None, None, ''),
('uint16', 'NumShorts', None, None, ''), # Automatically computed
('uint16', 'VarRegionCount', None, None, ''),
('uint16', 'VarRegionIndex', 'VarRegionCount', 0, ''),
('VarDataValue', 'Item', 'ItemCount', 0, ''),
]),
('VarStore', [
('uint16', 'Format', None, None, 'Set to 1.'),
('LOffset', 'VarRegionList', None, None, ''),
('uint16', 'VarDataCount', None, None, ''),
('LOffset', 'VarData', 'VarDataCount', 0, ''),
]),
# Variation helpers
('VarIdxMap', [
('uint16', 'EntryFormat', None, None, ''), # Automatically computed
('uint16', 'MappingCount', None, None, ''), # Automatically computed
('VarIdxMapValue', 'mapping', '', 0, 'Array of compressed data'),
]),
# Glyph advance variations
('HVAR', [
('Version', 'Version', None, None, 'Version of the HVAR table-initially = 0x00010000'),
('LOffset', 'VarStore', None, None, ''),
('LOffsetTo(VarIdxMap)', 'AdvWidthMap', None, None, ''),
('LOffsetTo(VarIdxMap)', 'LsbMap', None, None, ''),
('LOffsetTo(VarIdxMap)', 'RsbMap', None, None, ''),
]),
('VVAR', [
('Version', 'Version', None, None, 'Version of the VVAR table-initially = 0x00010000'),
('LOffset', 'VarStore', None, None, ''),
('LOffsetTo(VarIdxMap)', 'AdvHeightMap', None, None, ''),
('LOffsetTo(VarIdxMap)', 'TsbMap', None, None, ''),
('LOffsetTo(VarIdxMap)', 'BsbMap', None, None, ''),
('LOffsetTo(VarIdxMap)', 'VOrgMap', None, None, 'Vertical origin mapping.'),
]),
# #
# math # math
# #

View File

@ -6,6 +6,7 @@ converter objects from otConverters.py.
""" """
from __future__ import print_function, division, absolute_import from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
from fontTools.misc.textTools import safeEval
from .otBase import BaseTable, FormatSwitchingBaseTable from .otBase import BaseTable, FormatSwitchingBaseTable
import operator import operator
import logging import logging
@ -133,6 +134,91 @@ class Coverage(FormatSwitchingBaseTable):
glyphs.append(attrs["value"]) glyphs.append(attrs["value"])
class VarIdxMap(BaseTable):
def postRead(self, rawTable, font):
assert (rawTable['EntryFormat'] & 0xFFC0) == 0
self.mapping = rawTable['mapping']
def preWrite(self, font):
mapping = getattr(self, "mapping", None)
if mapping is None:
mapping = self.mapping = []
rawTable = { 'mapping': mapping }
rawTable['MappingCount'] = len(mapping)
# TODO Remove this abstraction/optimization and move it varLib.builder?
ored = 0
for idx in mapping:
ored |= idx
inner = ored & 0xFFFF
innerBits = 0
while inner:
innerBits += 1
inner >>= 1
innerBits = max(innerBits, 1)
assert innerBits <= 16
ored = (ored >> (16-innerBits)) | (ored & ((1<<innerBits)-1))
if ored <= 0x000000FF:
entrySize = 1
elif ored <= 0x0000FFFF:
entrySize = 2
elif ored <= 0x00FFFFFF:
entrySize = 3
else:
entrySize = 4
entryFormat = ((entrySize - 1) << 4) | (innerBits - 1)
rawTable['EntryFormat'] = entryFormat
return rawTable
def toXML2(self, xmlWriter, font):
for i, value in enumerate(getattr(self, "mapping", [])):
attrs = (
('index', i),
('outer', value >> 16),
('inner', value & 0xFFFF),
)
xmlWriter.simpletag("Map", attrs)
xmlWriter.newline()
def fromXML(self, name, attrs, content, font):
mapping = getattr(self, "mapping", None)
if mapping is None:
mapping = []
self.mapping = mapping
outer = safeEval(attrs['outer'])
inner = safeEval(attrs['inner'])
assert inner <= 0xFFFF
mapping.append((outer << 16) | inner)
class VarData(BaseTable):
def preWrite(self, font):
rawTable = self.__dict__.copy()
# TODO Remove this abstraction/optimization and move it varLib.builder?
numShorts = 0
count = len(self.VarRegionIndex)
for item in self.Item:
assert len(item) == count, ("Item length mismatch", len(item), count)
for i in range(count - 1, numShorts - 1, -1):
if not (-128 <= item[i] <= 127):
numShorts = i + 1
break
if numShorts == count:
break
rawTable['NumShorts'] = numShorts
return rawTable
class SingleSubst(FormatSwitchingBaseTable): class SingleSubst(FormatSwitchingBaseTable):
def postRead(self, rawTable, font): def postRead(self, rawTable, font):

View File

@ -3,7 +3,7 @@ Module for dealing with 'gvar'-style font variations, also known as run-time
interpolation. interpolation.
The ideas here are very similar to MutatorMath. There is even code to read The ideas here are very similar to MutatorMath. There is even code to read
MutatorMath .designspace files. MutatorMath .designspace files in the varLib.designspace module.
For now, if you run this file on a designspace file, it tries to find For now, if you run this file on a designspace file, it tries to find
ttf-interpolatable files for the masters and build a GX variation font from ttf-interpolatable files for the masters and build a GX variation font from
@ -20,284 +20,18 @@ API *will* change in near future.
""" """
from __future__ import print_function, division, absolute_import from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables._n_a_m_e import NameRecord from fontTools.ttLib.tables._n_a_m_e import NameRecord
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r, GlyphVariation from fontTools.ttLib.tables._g_v_a_r import GlyphVariation
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables import otBase as otBase
from fontTools.varLib import designspace, models, builder
from fontTools.varLib.merger import merge_tables, Merger
import warnings import warnings
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
import os.path import os.path
#
# Variation space, aka design space, model
#
def supportScalar(location, support):
"""Returns the scalar multiplier at location, for a master
with support.
>>> supportScalar({}, {})
1.0
>>> supportScalar({'wght':.2}, {})
1.0
>>> supportScalar({'wght':.2}, {'wght':(0,2,3)})
0.1
>>> supportScalar({'wght':2.5}, {'wght':(0,2,4)})
0.75
"""
scalar = 1.
for axis,(lower,peak,upper) in support.items():
if axis not in location:
scalar = 0.
break
v = location[axis]
if v == peak:
continue
if v <= lower or upper <= v:
scalar = 0.
break;
if v < peak:
scalar *= (v - lower) / (peak - lower)
else: # v > peak
scalar *= (v - upper) / (peak - upper)
return scalar
class VariationModel(object):
"""
Locations must be in normalized space. Ie. base master
is at origin (0).
>>> from pprint import pprint
>>> locations = [ \
{'wght':100}, \
{'wght':-100}, \
{'wght':-180}, \
{'wdth':+.3}, \
{'wght':+120,'wdth':.3}, \
{'wght':+120,'wdth':.2}, \
{}, \
{'wght':+180,'wdth':.3}, \
{'wght':+180}, \
]
>>> model = VariationModel(locations, axisOrder=['wght'])
>>> pprint(model.locations)
[{},
{'wght': -100},
{'wght': -180},
{'wght': 100},
{'wght': 180},
{'wdth': 0.3},
{'wdth': 0.3, 'wght': 180},
{'wdth': 0.3, 'wght': 120},
{'wdth': 0.2, 'wght': 120}]
>>> pprint(model.deltaWeights)
[{},
{0: 1.0},
{0: 1.0},
{0: 1.0},
{0: 1.0},
{0: 1.0},
{0: 1.0, 4: 1.0, 5: 1.0},
{0: 1.0, 3: 0.75, 4: 0.25, 5: 1.0, 6: 0.25},
{0: 1.0,
3: 0.75,
4: 0.25,
5: 0.6666666666666667,
6: 0.16666666666666669,
7: 0.6666666666666667}]
"""
def __init__(self, locations, axisOrder=[]):
locations = [{k:v for k,v in loc.items() if v != 0.} for loc in locations]
keyFunc = self.getMasterLocationsSortKeyFunc(locations, axisOrder=axisOrder)
axisPoints = keyFunc.axisPoints
self.locations = sorted(locations, key=keyFunc)
# TODO Assert that locations are unique.
self.mapping = [self.locations.index(l) for l in locations] # Mapping from user's master order to our master order
self.reverseMapping = [locations.index(l) for l in self.locations] # Reverse of above
self._computeMasterSupports(axisPoints)
@staticmethod
def getMasterLocationsSortKeyFunc(locations, axisOrder=[]):
assert {} in locations, "Base master not found."
axisPoints = {}
for loc in locations:
if len(loc) != 1:
continue
axis = next(iter(loc))
value = loc[axis]
if axis not in axisPoints:
axisPoints[axis] = {0}
assert value not in axisPoints[axis]
axisPoints[axis].add(value)
def getKey(axisPoints, axisOrder):
def sign(v):
return -1 if v < 0 else +1 if v > 0 else 0
def key(loc):
rank = len(loc)
onPointAxes = [axis for axis,value in loc.items() if value in axisPoints[axis]]
orderedAxes = [axis for axis in axisOrder if axis in loc]
orderedAxes.extend([axis for axis in sorted(loc.keys()) if axis not in axisOrder])
return (
rank, # First, order by increasing rank
-len(onPointAxes), # Next, by decreasing number of onPoint axes
tuple(axisOrder.index(axis) if axis in axisOrder else 0x10000 for axis in orderedAxes), # Next, by known axes
tuple(orderedAxes), # Next, by all axes
tuple(sign(loc[axis]) for axis in orderedAxes), # Next, by signs of axis values
tuple(abs(loc[axis]) for axis in orderedAxes), # Next, by absolute value of axis values
)
return key
ret = getKey(axisPoints, axisOrder)
ret.axisPoints = axisPoints
return ret
@staticmethod
def lowerBound(value, lst):
if any(v < value for v in lst):
return max(v for v in lst if v < value)
else:
return value
@staticmethod
def upperBound(value, lst):
if any(v > value for v in lst):
return min(v for v in lst if v > value)
else:
return value
def _computeMasterSupports(self, axisPoints):
supports = []
deltaWeights = []
locations = self.locations
for i,loc in enumerate(locations):
box = {}
# Account for axisPoints first
for axis,values in axisPoints.items():
if not axis in loc:
continue
locV = loc[axis]
box[axis] = (self.lowerBound(locV, values), locV, self.upperBound(locV, values))
locAxes = set(loc.keys())
# Walk over previous masters now
for j,m in enumerate(locations[:i]):
# Master with extra axes do not participte
if not set(m.keys()).issubset(locAxes):
continue
# If it's NOT in the current box, it does not participate
relevant = True
for axis, (lower,_,upper) in box.items():
if axis in m and not (lower < m[axis] < upper):
relevant = False
break
if not relevant:
continue
# Split the box for new master
for axis,val in m.items():
assert axis in box
lower,locV,upper = box[axis]
if val < locV:
lower = val
elif locV < val:
upper = val
box[axis] = (lower,locV,upper)
supports.append(box)
deltaWeight = {}
# Walk over previous masters now, populate deltaWeight
for j,m in enumerate(locations[:i]):
scalar = supportScalar(loc, supports[j])
if scalar:
deltaWeight[j] = scalar
deltaWeights.append(deltaWeight)
self.supports = supports
self.deltaWeights = deltaWeights
def getDeltas(self, masterValues):
count = len(self.locations)
assert len(masterValues) == len(self.deltaWeights)
mapping = self.reverseMapping
out = []
for i,weights in enumerate(self.deltaWeights):
delta = masterValues[mapping[i]]
for j,weight in weights.items():
delta -= out[j] * weight
out.append(delta)
return out
def interpolateFromDeltas(self, loc, deltas):
v = None
supports = self.supports
assert len(deltas) == len(supports)
for i,(delta,support) in enumerate(zip(deltas, supports)):
scalar = supportScalar(loc, support)
if not scalar: continue
contribution = delta * scalar
if i == 0:
v = contribution
else:
v += contribution
return v
def interpolateFromMasters(self, loc, masterValues):
deltas = self.getDeltas(masterValues)
return self.interpolateFromDeltas(loc, deltas)
#
# .designspace routines
#
def _xmlParseLocation(et):
loc = {}
for dim in et.find('location'):
assert dim.tag == 'dimension'
name = dim.attrib['name']
value = float(dim.attrib['xvalue'])
assert name not in loc
loc[name] = value
return loc
def _designspace_load(et):
base_idx = None
masters = []
ds = et.getroot()
for master in ds.find('sources'):
name = master.attrib['name']
filename = master.attrib['filename']
isBase = master.find('info')
if isBase is not None:
assert base_idx is None
base_idx = len(masters)
loc = _xmlParseLocation(master)
masters.append((filename, loc, name))
instances = []
for instance in ds.find('instances'):
name = master.attrib['name']
family = instance.attrib['familyname']
style = instance.attrib['stylename']
filename = instance.attrib['filename']
loc = _xmlParseLocation(instance)
instances.append((filename, loc, name, family, style))
return masters, instances, base_idx
def designspace_load(filename):
return _designspace_load(ET.parse(filename))
def designspace_loads(string):
return _designspace_load(ET.fromstring(string))
# #
# Creation routines # Creation routines
# #
@ -305,32 +39,55 @@ def designspace_loads(string):
# TODO: Move to name table proper; also, is mac_roman ok for ASCII names? # TODO: Move to name table proper; also, is mac_roman ok for ASCII names?
def _AddName(font, name): def _AddName(font, name):
"""(font, "Bold") --> NameRecord""" """(font, "Bold") --> NameRecord"""
name = tounicode(name)
nameTable = font.get("name") nameTable = font.get("name")
namerec = NameRecord() namerec = NameRecord()
namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256]) namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
namerec.string = name.encode("mac_roman") namerec.string = name
namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0) namerec.platformID, namerec.platEncID, namerec.langID = (3, 1, 0x409)
nameTable.names.append(namerec) nameTable.names.append(namerec)
return namerec return namerec
# Move to fvar table proper? # Move to fvar table proper?
def _add_fvar(font, axes, instances): # TODO how to provide axis order?
assert "fvar" not in font def _add_fvar(font, axes, instances, axis_map):
font['fvar'] = fvar = table__f_v_a_r() """
Add 'fvar' table to font.
for tag in sorted(axes.keys()): axes is a dictionary mapping axis-id to axis (min,default,max)
coordinate values.
instances is list of dictionary objects with 'location', 'stylename',
and possibly 'postscriptfontname' entries.
axisMap is dictionary mapping axis-id to (axis-tag, axis-name).
"""
assert "fvar" not in font
font['fvar'] = fvar = newTable('fvar')
for iden in sorted(axes.keys(), key=lambda k: axis_map[k][0]):
axis = Axis() axis = Axis()
axis.axisTag = tag axis.axisTag = Tag(axis_map[iden][0])
name, axis.minValue, axis.defaultValue, axis.maxValue = axes[tag] axis.minValue, axis.defaultValue, axis.maxValue = axes[iden]
axis.nameID = _AddName(font, name).nameID axis.axisNameID = _AddName(font, axis_map[iden][1]).nameID
fvar.axes.append(axis) fvar.axes.append(axis)
for name, coordinates in instances: for instance in instances:
coordinates = instance['location']
name = instance['stylename']
psname = instance.get('postscriptfontname')
inst = NamedInstance() inst = NamedInstance()
inst.nameID = _AddName(font, name).nameID inst.subfamilyNameID = _AddName(font, name).nameID
inst.coordinates = coordinates if psname:
inst.postscriptNameID = _AddName(font, psname).nameID
inst.coordinates = {axis_map[k][0]:v for k,v in coordinates.items()}
fvar.instances.append(inst) fvar.instances.append(inst)
return fvar
# TODO Move to glyf or gvar table proper # TODO Move to glyf or gvar table proper
def _GetCoordinates(font, glyphName): def _GetCoordinates(font, glyphName):
"""font, glyphName --> glyph coordinates as expected by "gvar" table """font, glyphName --> glyph coordinates as expected by "gvar" table
@ -403,42 +160,16 @@ def _SetCoordinates(font, glyphName, coord):
# XXX Remove the round when https://github.com/behdad/fonttools/issues/593 is fixed # XXX Remove the round when https://github.com/behdad/fonttools/issues/593 is fixed
font["hmtx"].metrics[glyphName] = int(round(horizontalAdvanceWidth)), int(round(leftSideBearing)) font["hmtx"].metrics[glyphName] = int(round(horizontalAdvanceWidth)), int(round(leftSideBearing))
def _add_gvar(font, axes, master_ttfs, master_locs, base_idx):
# Make copies for modification def _add_gvar(font, model, master_ttfs):
master_ttfs = master_ttfs[:]
master_locs = [l.copy() for l in master_locs]
axis_tags = axes.keys()
# Normalize master locations. TODO Move to a separate function.
for tag,(name,lower,default,upper) in axes.items():
for l in master_locs:
v = l[tag]
if v == default:
v = 0
elif v < default:
v = (v - default) / (default - lower)
else:
v = (v - default) / (upper - default)
l[tag] = v
# Locations are normalized now
print("Normalized master positions:")
print(master_locs)
print("Generating gvar") print("Generating gvar")
assert "gvar" not in font assert "gvar" not in font
gvar = font["gvar"] = table__g_v_a_r() gvar = font["gvar"] = newTable('gvar')
gvar.version = 1 gvar.version = 1
gvar.reserved = 0 gvar.reserved = 0
gvar.variations = {} gvar.variations = {}
# Assume single-model for now.
model = VariationModel(master_locs)
model_base_idx = model.mapping[base_idx]
assert 0 == model_base_idx
for glyph in font.getGlyphOrder(): for glyph in font.getGlyphOrder():
allData = [_GetCoordinates(m, glyph) for m in master_ttfs] allData = [_GetCoordinates(m, glyph) for m in master_ttfs]
@ -450,42 +181,186 @@ def _add_gvar(font, axes, master_ttfs, master_locs, base_idx):
continue continue
del allControls del allControls
# Update gvar
gvar.variations[glyph] = [] gvar.variations[glyph] = []
deltas = model.getDeltas(allCoords) deltas = model.getDeltas(allCoords)
supports = model.supports supports = model.supports
assert len(deltas) == len(supports) assert len(deltas) == len(supports)
for i,(delta,support) in enumerate(zip(deltas, supports)): for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])):
if i == model_base_idx:
continue
var = GlyphVariation(support, delta) var = GlyphVariation(support, delta)
gvar.variations[glyph].append(var) gvar.variations[glyph].append(var)
def main(args=None): def _add_HVAR(font, model, master_ttfs, axisTags):
import sys print("Generating HVAR")
if args is None:
args = sys.argv[1:]
(designspace_filename,) = args hAdvanceDeltas = {}
finder = lambda s: s.replace('master_ufo', 'master_ttf_interpolatable').replace('.ufo', '.ttf') metricses = [m["hmtx"].metrics for m in master_ttfs]
axisMap = None # dict mapping axis id to (axis tag, axis name) for glyph in font.getGlyphOrder():
outfile = os.path.splitext(designspace_filename)[0] + '-GX.ttf' hAdvances = [metrics[glyph][0] for metrics in metricses]
# TODO move round somewhere else?
hAdvanceDeltas[glyph] = tuple(round(d) for d in model.getDeltas(hAdvances)[1:])
masters, instances, base_idx = designspace_load(designspace_filename) # We only support the direct mapping right now.
supports = model.supports[1:]
varTupleList = builder.buildVarRegionList(supports, axisTags)
varTupleIndexes = list(range(len(supports)))
n = len(supports)
items = []
zeroes = [0]*n
for glyphName in font.getGlyphOrder():
items.append(hAdvanceDeltas.get(glyphName, zeroes))
while items and items[-1] is zeroes:
del items[-1]
advanceMapping = None
# Add indirect mapping to save on duplicates
uniq = set(items)
# TODO Improve heuristic
if (len(items) - len(uniq)) * len(varTupleIndexes) > len(items):
newItems = sorted(uniq)
mapper = {v:i for i,v in enumerate(newItems)}
mapping = [mapper[item] for item in items]
while len(mapping) > 1 and mapping[-1] == mapping[-2]:
del mapping[-1]
advanceMapping = builder.buildVarIdxMap(mapping)
items = newItems
del mapper, mapping, newItems
del uniq
varData = builder.buildVarData(varTupleIndexes, items)
varStore = builder.buildVarStore(varTupleList, [varData])
assert "HVAR" not in font
HVAR = font["HVAR"] = newTable('HVAR')
hvar = HVAR.table = ot.HVAR()
hvar.Version = 0x00010000
hvar.VarStore = varStore
hvar.AdvWidthMap = advanceMapping
hvar.LsbMap = hvar.RsbMap = None
def _all_equal(lst):
it = iter(lst)
v0 = next(it)
for v in it:
if v0 != v:
return False
return True
def buildVarDevTable(store_builder, master_values):
if _all_equal(master_values):
return None
varIdx = store_builder.storeMasters(master_values)
return builder.buildVarDevTable(varIdx)
class VariationMerger(Merger):
def __init__(self, model, axisTags):
self.model = model
self.store_builder = builder.OnlineVarStoreBuilder(axisTags)
self.store_builder.setModel(model)
@VariationMerger.merger(ot.Anchor)
def merge(merger, self, lst):
assert self.Format == 1
XDeviceTable = buildVarDevTable(merger.store_builder, [a.XCoordinate for a in lst])
YDeviceTable = buildVarDevTable(merger.store_builder, [a.YCoordinate for a in lst])
if XDeviceTable or YDeviceTable:
self.Format = 3
self.XDeviceTable = XDeviceTable
self.YDeviceTable = YDeviceTable
@VariationMerger.merger(ot.PairPos)
def merge(merger, self, lst):
# We need to interecept the merger processing of the object tree here so
# that we can change the PairPos value formats after the ValueRecords are
# changed.
merger.mergeObjects(self, lst)
# Now examine the list of value records, and update to the union of format values.
vf1 = self.ValueFormat1
vf2 = self.ValueFormat2
if self.Format == 1:
for pairSet in self.PairSet:
for pairValueRecord in pairSet.PairValueRecord:
pv1 = pairValueRecord.Value1
if pv1 is not None:
vf1 |= pv1.getFormat()
pv2 = pairValueRecord.Value2
if pv2 is not None:
vf2 |= pv2.getFormat()
elif self.Format == 2:
for class1Record in self.Class1Record:
for class2Record in class1Record.Class2Record:
pv1 = class2Record.Value1
if pv1 is not None:
vf1 |= pv1.getFormat()
pv2 = class2Record.Value2
if pv2 is not None:
vf2 |= pv2.getFormat()
self.ValueFormat1 = vf1
self.ValueFormat2 = vf2
@VariationMerger.merger(otBase.ValueRecord)
def merge(merger, self, lst):
for name, tableName in [('XAdvance','XAdvDevice'),
('YAdvance','YAdvDevice'),
('XPlacement','XPlaDevice'),
('YPlacement','YPlaDevice')]:
if hasattr(self, name):
deviceTable = buildVarDevTable(merger.store_builder,
[getattr(a, name) for a in lst])
if deviceTable:
setattr(self, tableName, deviceTable)
def _merge_OTL(font, model, master_fonts, axisTags, base_idx):
print("Merging OpenType Layout tables")
merger = VariationMerger(model, axisTags)
merge_tables(font, merger, master_fonts, axisTags, base_idx, ['GPOS'])
store = merger.store_builder.finish()
try:
GDEF = font['GDEF'].table
assert GDEF.Version <= 0x00010002
except KeyError:
font['GDEF']= newTable('GDEF')
GDEFTable = font["GDEF"] = newTable('GDEF')
GDEF = GDEFTable.table = ot.GDEF()
GDEF.Version = 0x00010003
GDEF.VarStore = store
def build(designspace_filename, master_finder=lambda s:s, axisMap=None):
"""
Build variation font from a designspace file.
If master_finder is set, it should be a callable that takes master
filename as found in designspace file and map it to master font
binary as to be opened (eg. .ttf or .otf).
If axisMap is set, it should be dictionary mapping axis-id to
(axis-tag, axis-name).
"""
masters, instances = designspace.load(designspace_filename)
base_idx = None
for i,m in enumerate(masters):
if 'info' in m and m['info']['copy']:
assert base_idx is None
base_idx = i
assert base_idx is not None, "Cannot find 'base' master; Add <info> element to one of the masters in the .designspace document." assert base_idx is not None, "Cannot find 'base' master; Add <info> element to one of the masters in the .designspace document."
from pprint import pprint from pprint import pprint
print("Masters:")
pprint(masters)
print("Instances:")
pprint(instances)
print("Index of base master:", base_idx) print("Index of base master:", base_idx)
print("Building GX") print("Building GX")
print("Loading TTF masters") print("Loading TTF masters")
basedir = os.path.dirname(designspace_filename) basedir = os.path.dirname(designspace_filename)
master_ttfs = [finder(os.path.join(basedir, m[0])) for m in masters] master_ttfs = [master_finder(os.path.join(basedir, m['filename'])) for m in masters]
master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs] master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs]
standard_axis_map = { standard_axis_map = {
@ -503,56 +378,72 @@ def main(args=None):
# TODO: For weight & width, use OS/2 values and setup 'avar' mapping. # TODO: For weight & width, use OS/2 values and setup 'avar' mapping.
# Set up master locations master_locs = [o['location'] for o in masters]
master_locs = []
instance_locs = []
out = []
for loc in [m[1] for m in masters+instances]:
# Apply modifications for default axes; and apply tags
l = {}
for axis,value in loc.items():
tag,name = axis_map[axis]
l[tag] = value
out.append(l)
master_locs = out[:len(masters)]
instance_locs = out[len(masters):]
axis_tags = set(master_locs[0].keys()) axis_tags = set(master_locs[0].keys())
assert all(axis_tags == set(m.keys()) for m in master_locs) assert all(axis_tags == set(m.keys()) for m in master_locs)
print("Axis tags:", axis_tags)
print("Master positions:")
pprint(master_locs)
# Set up axes # Set up axes
axes = {} axes = {}
axis_names = {}
for tag,name in axis_map.values():
if tag not in axis_tags: continue
axis_names[tag] = name
for tag in axis_tags: for tag in axis_tags:
default = master_locs[base_idx][tag] default = master_locs[base_idx][tag]
lower = min(m[tag] for m in master_locs) lower = min(m[tag] for m in master_locs)
upper = max(m[tag] for m in master_locs) upper = max(m[tag] for m in master_locs)
name = axis_names[tag] axes[tag] = (lower, default, upper)
axes[tag] = (name, lower, default, upper)
print("Axes:") print("Axes:")
pprint(axes) pprint(axes)
# Set up named instances print("Master locations:")
instance_list = [] pprint(master_locs)
for loc,instance in zip(instance_locs,instances):
style = instance[4]
instance_list.append((style, loc))
# TODO append masters as named-instances as well; needs .designspace change.
# We can use the base font straight, but it's faster to load it again since
# then we won't be recompiling the existing ('glyf', 'hmtx', ...) tables.
#gx = master_fonts[base_idx]
gx = TTFont(master_ttfs[base_idx]) gx = TTFont(master_ttfs[base_idx])
_add_fvar(gx, axes, instance_list) # TODO append masters as named-instances as well; needs .designspace change.
fvar = _add_fvar(gx, axes, instances, axis_map)
print("Setting up glyph variations")
_add_gvar(gx, axes, master_fonts, master_locs, base_idx)
print("Saving GX font", outfile) # Normalize master locations
master_locs = [models.normalizeLocation(m, axes) for m in master_locs]
print("Normalized master locations:")
pprint(master_locs)
# TODO Clean this up.
del instances
del axes
master_locs = [{axis_map[k][0]:v for k,v in loc.items()} for loc in master_locs]
#instance_locs = [{axis_map[k][0]:v for k,v in loc.items()} for loc in instance_locs]
axisTags = [axis.axisTag for axis in fvar.axes]
# Assume single-model for now.
model = models.VariationModel(master_locs)
assert 0 == model.mapping[base_idx]
print("Building variations tables")
if 'glyf' in gx:
_add_gvar(gx, model, master_fonts)
_add_HVAR(gx, model, master_fonts, axisTags)
_merge_OTL(gx, model, master_fonts, axisTags, base_idx)
return gx, model, master_ttfs
def main(args=None):
if args is None:
import sys
args = sys.argv[1:]
(designspace_filename,) = args
finder = lambda s: s.replace('master_ufo', 'master_ttf_interpolatable').replace('.ufo', '.ttf')
outfile = os.path.splitext(designspace_filename)[0] + '-GX.ttf'
gx, model, master_ttfs = build(designspace_filename, finder)
print("Saving variation font", outfile)
gx.save(outfile) gx.save(outfile)

View File

@ -0,0 +1,148 @@
from __future__ import print_function, division, absolute_import
from fontTools import ttLib
from fontTools.ttLib.tables import otTables as ot
# VariationStore
def buildVarRegionAxis(axisSupport):
self = ot.VarRegionAxis()
self.StartCoord, self.PeakCoord, self.EndCoord = axisSupport
return self
def buildVarRegion(support, axisTags):
assert all(tag in axisTags for tag in support.keys()), ("Unknown axis tag found.", support, axisTags)
self = ot.VarRegion()
self.VarRegionAxis = []
for tag in axisTags:
self.VarRegionAxis.append(buildVarRegionAxis(support.get(tag, (0,0,0))))
self.VarRegionAxisCount = len(self.VarRegionAxis)
return self
def buildVarRegionList(supports, axisTags):
self = ot.VarRegionList()
self.AxisCount = len(axisTags)
self.Region = []
for support in supports:
self.Region.append(buildVarRegion(support, axisTags))
self.RegionCount = len(self.Region)
return self
def _reorderItem(lst, narrows):
out = []
count = len(lst)
for i in range(count):
if i not in narrows:
out.append(lst[i])
for i in range(count):
if i in narrows:
out.append(lst[i])
return out
def optimizeVarData(self):
# Reorder columns such that all SHORT columns come before UINT8
count = self.VarRegionCount
items = self.Item
narrows = set(range(count))
for item in items:
for i in narrows:
if not (-128 <= item[i] <= 127):
narrows.remove(i)
break
if not narrows:
break
self.VarRegionIndex = _reorderItem(self.VarRegionIndex, narrows)
for i in range(self.ItemCount):
items[i] = _reorderItem(items[i], narrows)
return self
def buildVarData(varRegionIndices, items, optimize=True):
self = ot.VarData()
self.VarRegionIndex = list(varRegionIndices)
regionCount = self.VarRegionCount = len(self.VarRegionIndex)
records = self.Item = []
if items:
for item in items:
assert len(item) == regionCount
records.append(list(item))
self.ItemCount = len(self.Item)
if items and optimize:
optimizeVarData(self)
return self
def buildVarStore(varRegionList, varDataList):
self = ot.VarStore()
self.Format = 1
self.VarRegionList = varRegionList
self.VarData = list(varDataList)
self.VarDataCount = len(self.VarData)
return self
def _getLocationKey(loc):
return tuple(sorted(loc.items(), key=lambda kv: kv[0]))
class OnlineVarStoreBuilder(object):
def __init__(self, axisTags):
self._axisTags = axisTags
self._regionMap = {}
self._regionList = buildVarRegionList([], axisTags)
self._store = buildVarStore(self._regionList, [])
def setModel(self, model):
self._model = model
regionMap = self._regionMap
regionList = self._regionList
regions = model.supports[1:]
regionIndices = []
for region in regions:
key = _getLocationKey(region)
idx = regionMap.get(key)
if idx is None:
varRegion = buildVarRegion(region, self._axisTags)
idx = regionMap[key] = len(regionList.Region)
regionList.Region.append(varRegion)
regionIndices.append(idx)
data = self._data = buildVarData(regionIndices, [], optimize=False)
self._outer = len(self._store.VarData)
self._store.VarData.append(data)
def finish(self, optimize=True):
self._regionList.RegionCount = len(self._regionList.Region)
self._store.VarDataCount = len(self._store.VarData)
for data in self._store.VarData:
data.ItemCount = len(data.Item)
if optimize:
optimizeVarData(data)
return self._store
def storeMasters(self, master_values):
deltas = [int(round(d)) for d in self._model.getDeltas(master_values)[1:]]
inner = len(self._data.Item)
self._data.Item.append(deltas)
# TODO Check for full data array?
return (self._outer << 16) + inner
# Variation helpers
def buildVarIdxMap(varIdxes):
# TODO Change VarIdxMap mapping to hold separate outer,inner indices
self = ot.VarIdxMap()
self.mapping = list(varIdxes)
return self
def buildVarDevTable(varIdx):
self = ot.Device()
self.DeltaFormat = 0x8000
self.StartSize = varIdx >> 16
self.EndSize = varIdx & 0xFFFF
return self

View File

@ -0,0 +1,53 @@
"""Rudimentary support for loading MutatorMath .designspace files."""
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
__all__ = ['load', 'loads']
def _xmlParseLocation(et):
loc = {}
for dim in et.find('location'):
assert dim.tag == 'dimension'
name = dim.attrib['name']
value = float(dim.attrib['xvalue'])
assert name not in loc
loc[name] = value
return loc
def _loadItem(et):
item = dict(et.attrib)
for elt in et:
if elt.tag == 'location':
value = _xmlParseLocation(et)
else:
value = {}
if 'copy' in elt.attrib:
value['copy'] = bool(int(elt.attrib['copy']))
# TODO load more?!
item[elt.tag] = value
return item
def _load(et):
masters = []
ds = et.getroot()
for et in ds.find('sources'):
masters.append(_loadItem(et))
instances = []
for et in ds.find('instances'):
instances.append(_loadItem(et))
return masters, instances
def load(filename):
"""Load designspace from a file name or object. Returns two items:
list of masters (aka sources) and list of instances."""
return _load(ET.parse(filename))
def loads(string):
"""Load designspace from a string."""
return _load(ET.fromstring(string))

View File

@ -1,6 +1,6 @@
from __future__ import print_function, division, absolute_import from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from fontTools.varLib import designspace_load from fontTools.varLib import designspace
import os import os
import unittest import unittest
@ -8,16 +8,22 @@ import unittest
class DesignspaceTest(unittest.TestCase): class DesignspaceTest(unittest.TestCase):
def test_load(self): def test_load(self):
self.assertEqual( self.assertEqual(
designspace_load(_getpath("VarLibTest.designspace")), designspace.load(_getpath("VarLibTest.designspace")),
( ([{'filename': 'VarLibTest-Light.ufo',
[ 'groups': {'copy': True},
('VarLibTest-Light.ufo', {'weight': 0.0}, 'master_1'), 'info': {'copy': True},
('VarLibTest-Bold.ufo', {'weight': 1.0}, 'master_2') 'lib': {'copy': True},
], 'location': {'weight': 0.0},
[('instance/VarLibTest-Medium.ufo', {'weight': 0.5}, 'name': 'master_1'},
'master_2', 'VarLibTest', 'Medium')], {'filename': 'VarLibTest-Bold.ufo',
0 'location': {'weight': 1.0},
) 'name': 'master_2'}],
[{'filename': 'instance/VarLibTest-Medium.ufo',
'location': {'weight': 0.5},
'familyname': 'VarLibTest',
'stylename': 'Medium',
'info': {},
'kerning': {}}])
) )

View File

@ -0,0 +1,112 @@
"""
Interpolate OpenType Layout tables (GDEF / GPOS / GSUB).
"""
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.DefaultTable import DefaultTable
from fontTools.varLib import designspace, models, builder
from fontTools.varLib.merger import merge_tables, Merger
import os.path
class InstancerMerger(Merger):
def __init__(self, model, location):
self.model = model
self.location = location
@InstancerMerger.merger(ot.Anchor)
def merge(merger, self, lst):
XCoords = [a.XCoordinate for a in lst]
YCoords = [a.YCoordinate for a in lst]
model = merger.model
location = merger.location
self.XCoordinate = round(model.interpolateFromMasters(location, XCoords))
self.YCoordinate = round(model.interpolateFromMasters(location, YCoords))
def main(args=None):
import sys
if args is None:
args = sys.argv[1:]
designspace_filename = args[0]
locargs = args[1:]
outfile = os.path.splitext(designspace_filename)[0] + '-instance.ttf'
finder = lambda s: s.replace('master_ufo', 'master_ttf_interpolatable').replace('.ufo', '.ttf')
loc = {}
for arg in locargs:
tag,val = arg.split('=')
loc[tag] = float(val)
masters, instances = designspace.load(designspace_filename)
base_idx = None
for i,m in enumerate(masters):
if 'info' in m and m['info']['copy']:
assert base_idx is None
base_idx = i
assert base_idx is not None, "Cannot find 'base' master; Add <info> element to one of the masters in the .designspace document."
from pprint import pprint
print("Index of base master:", base_idx)
print("Building GX")
print("Loading TTF masters")
basedir = os.path.dirname(designspace_filename)
master_ttfs = [finder(os.path.join(basedir, m['filename'])) for m in masters]
master_fonts = [TTFont(ttf_path) for ttf_path in master_ttfs]
#font = master_fonts[base_idx]
font = TTFont(master_ttfs[base_idx])
master_locs = [o['location'] for o in masters]
axis_tags = set(master_locs[0].keys())
assert all(axis_tags == set(m.keys()) for m in master_locs)
# Set up axes
axes = {}
for tag in axis_tags:
default = master_locs[base_idx][tag]
lower = min(m[tag] for m in master_locs)
upper = max(m[tag] for m in master_locs)
axes[tag] = (lower, default, upper)
print("Axes:")
pprint(axes)
print("Location:", loc)
print("Master locations:")
pprint(master_locs)
# Normalize locations
loc = models.normalizeLocation(loc, axes)
master_locs = [models.normalizeLocation(m, axes) for m in master_locs]
print("Normalized location:", loc)
print("Normalized master locations:")
pprint(master_locs)
# Assume single-model for now.
model = models.VariationModel(master_locs)
assert 0 == model.mapping[base_idx]
merger = InstancerMerger(model, loc)
print("Building variations tables")
merge_tables(font, merger, master_fonts, axes, base_idx, ['GPOS'])
print("Saving font", outfile)
font.save(outfile)
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
main()
#sys.exit(0)
import doctest, sys
sys.exit(doctest.testmod().failed)

View File

@ -0,0 +1,61 @@
"""
Merge OpenType Layout tables (GDEF / GPOS / GSUB).
"""
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.DefaultTable import DefaultTable
class Merger(object):
mergers = None
@classmethod
def merger(celf, *clazzes):
assert celf != Merger, 'Subclass Merger instead.'
if celf.mergers is None:
celf.mergers = {}
def wrapper(method):
assert method.__name__ == 'merge'
done = []
for clazz in clazzes:
if clazz in done: continue # Support multiple names of a clazz
done.append(clazz)
assert clazz not in celf.mergers, \
"Oops, class '%s' has merge function defined already." % (clazz.__name__)
celf.mergers[clazz] = method
return None
return wrapper
def mergeObjects(self, out, lst):
keys = vars(out).keys()
assert all(vars(table).keys() == keys for table in lst)
for key in keys:
value = getattr(out, key)
values = [getattr(table, key) for table in lst]
self.mergeThings(value, values)
def mergeLists(self, out, lst):
count = len(out)
assert all(count == len(v) for v in lst), (count, [len(v) for v in lst])
for value,values in zip(out, zip(*lst)):
self.mergeThings(value, values)
def mergeThings(self, out, lst):
clazz = type(out)
assert all(type(item) == clazz for item in lst), lst
mergerFunc = self.mergers.get(type(out), None)
if mergerFunc is not None:
mergerFunc(self, out, lst)
elif hasattr(out, '__dict__'):
self.mergeObjects(out, lst)
elif isinstance(out, list):
self.mergeLists(out, lst)
else:
assert all(out == v for v in lst), lst
def merge_tables(font, merger, master_ttfs, axes, base_idx, tables):
for tag in tables:
print('Merging', tag)
merger.mergeThings(font[tag], [m[tag] for m in master_ttfs])

View File

@ -0,0 +1,234 @@
"""Variation fonts interpolation models."""
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
__all__ = ['normalizeLocation', 'supportScalar', 'VariationModel']
def normalizeLocation(location, axes):
"""Normalizes location based on axis min/default/max values from axes."""
out = {}
for tag,(lower,default,upper) in axes.items():
v = location.get(tag, default)
if v == default:
v = 0
elif v < default:
v = (max(v, lower) - default) / (default - lower)
else:
v = (min(v, upper) - default) / (upper - default)
out[tag] = v
return out
def supportScalar(location, support):
"""Returns the scalar multiplier at location, for a master
with support.
>>> supportScalar({}, {})
1.0
>>> supportScalar({'wght':.2}, {})
1.0
>>> supportScalar({'wght':.2}, {'wght':(0,2,3)})
0.1
>>> supportScalar({'wght':2.5}, {'wght':(0,2,4)})
0.75
"""
scalar = 1.
for axis,(lower,peak,upper) in support.items():
if axis not in location:
scalar = 0.
break
v = location[axis]
if v == peak:
continue
if v <= lower or upper <= v:
scalar = 0.
break;
if v < peak:
scalar *= (v - lower) / (peak - lower)
else: # v > peak
scalar *= (v - upper) / (peak - upper)
return scalar
class VariationModel(object):
"""
Locations must be in normalized space. Ie. base master
is at origin (0).
>>> from pprint import pprint
>>> locations = [ \
{'wght':100}, \
{'wght':-100}, \
{'wght':-180}, \
{'wdth':+.3}, \
{'wght':+120,'wdth':.3}, \
{'wght':+120,'wdth':.2}, \
{}, \
{'wght':+180,'wdth':.3}, \
{'wght':+180}, \
]
>>> model = VariationModel(locations, axisOrder=['wght'])
>>> pprint(model.locations)
[{},
{'wght': -100},
{'wght': -180},
{'wght': 100},
{'wght': 180},
{'wdth': 0.3},
{'wdth': 0.3, 'wght': 180},
{'wdth': 0.3, 'wght': 120},
{'wdth': 0.2, 'wght': 120}]
>>> pprint(model.deltaWeights)
[{},
{0: 1.0},
{0: 1.0},
{0: 1.0},
{0: 1.0},
{0: 1.0},
{0: 1.0, 4: 1.0, 5: 1.0},
{0: 1.0, 3: 0.75, 4: 0.25, 5: 1.0, 6: 0.25},
{0: 1.0,
3: 0.75,
4: 0.25,
5: 0.6666666666666667,
6: 0.16666666666666669,
7: 0.6666666666666667}]
"""
def __init__(self, locations, axisOrder=[]):
locations = [{k:v for k,v in loc.items() if v != 0.} for loc in locations]
keyFunc = self.getMasterLocationsSortKeyFunc(locations, axisOrder=axisOrder)
axisPoints = keyFunc.axisPoints
self.locations = sorted(locations, key=keyFunc)
# TODO Assert that locations are unique.
self.mapping = [self.locations.index(l) for l in locations] # Mapping from user's master order to our master order
self.reverseMapping = [locations.index(l) for l in self.locations] # Reverse of above
self._computeMasterSupports(axisPoints)
@staticmethod
def getMasterLocationsSortKeyFunc(locations, axisOrder=[]):
assert {} in locations, "Base master not found."
axisPoints = {}
for loc in locations:
if len(loc) != 1:
continue
axis = next(iter(loc))
value = loc[axis]
if axis not in axisPoints:
axisPoints[axis] = {0}
assert value not in axisPoints[axis]
axisPoints[axis].add(value)
def getKey(axisPoints, axisOrder):
def sign(v):
return -1 if v < 0 else +1 if v > 0 else 0
def key(loc):
rank = len(loc)
onPointAxes = [axis for axis,value in loc.items() if value in axisPoints[axis]]
orderedAxes = [axis for axis in axisOrder if axis in loc]
orderedAxes.extend([axis for axis in sorted(loc.keys()) if axis not in axisOrder])
return (
rank, # First, order by increasing rank
-len(onPointAxes), # Next, by decreasing number of onPoint axes
tuple(axisOrder.index(axis) if axis in axisOrder else 0x10000 for axis in orderedAxes), # Next, by known axes
tuple(orderedAxes), # Next, by all axes
tuple(sign(loc[axis]) for axis in orderedAxes), # Next, by signs of axis values
tuple(abs(loc[axis]) for axis in orderedAxes), # Next, by absolute value of axis values
)
return key
ret = getKey(axisPoints, axisOrder)
ret.axisPoints = axisPoints
return ret
@staticmethod
def lowerBound(value, lst):
if any(v < value for v in lst):
return max(v for v in lst if v < value)
else:
return value
@staticmethod
def upperBound(value, lst):
if any(v > value for v in lst):
return min(v for v in lst if v > value)
else:
return value
def _computeMasterSupports(self, axisPoints):
supports = []
deltaWeights = []
locations = self.locations
for i,loc in enumerate(locations):
box = {}
# Account for axisPoints first
for axis,values in axisPoints.items():
if not axis in loc:
continue
locV = loc[axis]
box[axis] = (self.lowerBound(locV, values), locV, self.upperBound(locV, values))
locAxes = set(loc.keys())
# Walk over previous masters now
for j,m in enumerate(locations[:i]):
# Master with extra axes do not participte
if not set(m.keys()).issubset(locAxes):
continue
# If it's NOT in the current box, it does not participate
relevant = True
for axis, (lower,_,upper) in box.items():
if axis in m and not (lower < m[axis] < upper):
relevant = False
break
if not relevant:
continue
# Split the box for new master
for axis,val in m.items():
assert axis in box
lower,locV,upper = box[axis]
if val < locV:
lower = val
elif locV < val:
upper = val
box[axis] = (lower,locV,upper)
supports.append(box)
deltaWeight = {}
# Walk over previous masters now, populate deltaWeight
for j,m in enumerate(locations[:i]):
scalar = supportScalar(loc, supports[j])
if scalar:
deltaWeight[j] = scalar
deltaWeights.append(deltaWeight)
self.supports = supports
self.deltaWeights = deltaWeights
def getDeltas(self, masterValues):
count = len(self.locations)
assert len(masterValues) == len(self.deltaWeights)
mapping = self.reverseMapping
out = []
for i,weights in enumerate(self.deltaWeights):
delta = masterValues[mapping[i]]
for j,weight in weights.items():
delta -= out[j] * weight
out.append(delta)
return out
def interpolateFromDeltas(self, loc, deltas):
v = None
supports = self.supports
assert len(deltas) == len(supports)
for i,(delta,support) in enumerate(zip(deltas, supports)):
scalar = supportScalar(loc, support)
if not scalar: continue
contribution = delta * scalar
if i == 0:
v = contribution
else:
v += contribution
return v
def interpolateFromMasters(self, loc, masterValues):
deltas = self.getDeltas(masterValues)
return self.interpolateFromDeltas(loc, deltas)

View File

@ -7,13 +7,14 @@ from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import * from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.varLib import VariationModel, supportScalar, _GetCoordinates, _SetCoordinates from fontTools.varLib import _GetCoordinates, _SetCoordinates
from fontTools.varLib.models import VariationModel, supportScalar, normalizeLocation
import os.path import os.path
def main(args=None): def main(args=None):
import sys
if args is None: if args is None:
import sys
args = sys.argv[1:] args = sys.argv[1:]
varfilename = args[0] varfilename = args[0]
@ -22,29 +23,18 @@ def main(args=None):
loc = {} loc = {}
for arg in locargs: for arg in locargs:
tag,valstr = arg.split('=') tag,val = arg.split('=')
while len(tag) < 4:
tag += ' '
assert len(tag) <= 4 assert len(tag) <= 4
loc[tag] = float(valstr) loc[tag.ljust(4)] = float(val)
print("Location:", loc) print("Location:", loc)
print("Loading GX font") print("Loading GX font")
varfont = TTFont(varfilename) varfont = TTFont(varfilename)
fvar = varfont['fvar'] fvar = varfont['fvar']
for axis in fvar.axes: axes = {a.axisTag:(a.minValue,a.defaultValue,a.maxValue) for a in fvar.axes}
lower, default, upper = axis.minValue, axis.defaultValue, axis.maxValue # TODO Round to F2Dot14?
v = loc.get(axis.axisTag, default) loc = normalizeLocation(loc, axes)
if v < lower: v = lower
if v > upper: v = upper
if v == default:
v = 0
elif v < default:
v = (v - default) / (default - lower)
else:
v = (v - default) / (upper - default)
loc[axis.axisTag] = v
# Location is normalized now # Location is normalized now
print("Normalized location:", loc) print("Normalized location:", loc)

View File

@ -76,11 +76,11 @@ These additional options include:
The following tables are currently supported: The following tables are currently supported:
<!-- begin table list --> <!-- begin table list -->
BASE, CBDT, CBLC, CFF, COLR, CPAL, DSIG, EBDT, EBLC, FFTM, GDEF, BASE, CBDT, CBLC, CFF, COLR, CPAL, DSIG, EBDT, EBLC, FFTM, GDEF,
GMAP, GPKG, GPOS, GSUB, JSTF, LTSH, MATH, META, OS/2, SING, SVG, GMAP, GPKG, GPOS, GSUB, HVAR, JSTF, LTSH, MATH, META, OS/2, SING,
TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, SVG, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS,
VDMX, VORG, avar, cmap, cvt, feat, fpgm, fvar, gasp, glyf, gvar, TSIV, VDMX, VORG, VVAR, avar, cmap, cvt, feat, fpgm, fvar, gasp,
hdmx, head, hhea, hmtx, kern, loca, ltag, maxp, meta, name, post, glyf, gvar, hdmx, head, hhea, hmtx, kern, loca, ltag, maxp, meta,
prep, sbix, trak, vhea and vmtx name, post, prep, sbix, trak, vhea and vmtx
<!-- end table list --> <!-- end table list -->
Other tables are dumped as hexadecimal data. Other tables are dumped as hexadecimal data.