Created a new library directory called "FreeLib". All OpenSource RFMKII components will reside there, fontTools being the flagship.

git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@2 4cde692c-a291-49d1-8350-778aa11640f8
This commit is contained in:
Just 1999-12-16 21:34:53 +00:00
parent a8f3feacb0
commit 7842e56b97
54 changed files with 18319 additions and 0 deletions

View File

265
Lib/fontTools/afmLib.py Normal file
View File

@ -0,0 +1,265 @@
"""Module for reading and writing AFM files."""
# XXX reads AFM's generated by Fog, not tested with much else.
# It does not implement the full spec (Adobe Technote 5004, Adobe Font Metrics
# File Format Specification). Still, it should read most "common" AFM files.
import re
import string
import types
__version__ = "$Id: afmLib.py,v 1.1 1999-12-16 21:34:51 Just Exp $"
# every single line starts with a "word"
identifierRE = re.compile("^([A-Za-z]+).*")
# regular expression to parse char lines
charRE = re.compile(
"(-?\d+)" # charnum
"\s*;\s*WX\s+" # ; WX
"(\d+)" # width
"\s*;\s*N\s+" # ; N
"(\.?[A-Za-z0-9_]+)" # charname
"\s*;\s*B\s+" # ; B
"(-?\d+)" # left
"\s+" #
"(-?\d+)" # bottom
"\s+" #
"(-?\d+)" # right
"\s+" #
"(-?\d+)" # top
"\s*;\s*" # ;
)
# regular expression to parse kerning lines
kernRE = re.compile(
"([.A-Za-z0-9_]+)" # leftchar
"\s+" #
"([.A-Za-z0-9_]+)" # rightchar
"\s+" #
"(-?\d+)" # value
"\s*" #
)
error = "AFM.error"
class AFM:
_keywords = ['StartFontMetrics',
'EndFontMetrics',
'StartCharMetrics',
'EndCharMetrics',
'StartKernData',
'StartKernPairs',
'EndKernPairs',
'EndKernData', ]
def __init__(self, path = None):
self._attrs = {}
self._chars = {}
self._kerning = {}
self._index = {}
self._comments = []
if path is not None:
self.read(path)
def read(self, path):
lines = readlines(path)
for line in lines:
if not string.strip(line):
continue
m = identifierRE.match(line)
if m is None:
raise error, "syntax error in AFM file: " + `line`
pos = m.regs[1][1]
word = line[:pos]
rest = string.strip(line[pos:])
if word in self._keywords:
continue
if word == 'C':
self.parsechar(rest)
elif word == "KPX":
self.parsekernpair(rest)
else:
self.parseattr(word, rest)
def parsechar(self, rest):
m = charRE.match(rest)
if m is None:
raise error, "syntax error in AFM file: " + `rest`
things = []
for fr, to in m.regs[1:]:
things.append(rest[fr:to])
charname = things[2]
del things[2]
charnum, width, l, b, r, t = map(string.atoi, things)
self._chars[charname] = charnum, width, (l, b, r, t)
def parsekernpair(self, rest):
m = kernRE.match(rest)
if m is None:
raise error, "syntax error in AFM file: " + `rest`
things = []
for fr, to in m.regs[1:]:
things.append(rest[fr:to])
leftchar, rightchar, value = things
value = string.atoi(value)
self._kerning[(leftchar, rightchar)] = value
def parseattr(self, word, rest):
if word == "FontBBox":
l, b, r, t = map(string.atoi, string.split(rest))
self._attrs[word] = l, b, r, t
elif word == "Comment":
self._comments.append(rest)
else:
try:
value = string.atoi(rest)
except (ValueError, OverflowError):
self._attrs[word] = rest
else:
self._attrs[word] = value
def write(self, path, sep = '\r'):
import time
lines = [ "StartFontMetrics 2.0",
"Comment Generated by afmLib, version %s; at %s" %
(string.split(__version__)[2],
time.strftime("%m/%d/%Y %H:%M:%S",
time.localtime(time.time())))]
# write attributes
items = self._attrs.items()
items.sort() # XXX proper ordering???
for attr, value in items:
if attr == "FontBBox":
value = string.join(map(str, value), " ")
lines.append(attr + " " + str(value))
# write char metrics
lines.append("StartCharMetrics " + `len(self._chars)`)
items = map(lambda (charname, (charnum, width, box)):
(charnum, (charname, width, box)),
self._chars.items())
def myCmp(a, b):
"""Custom compare function to make sure unencoded chars (-1)
end up at the end of the list after sorting."""
if a[0] == -1:
a = (0xffff,) + a[1:] # 0xffff is an arbitrary large number
if b[0] == -1:
b = (0xffff,) + b[1:]
return cmp(a, b)
items.sort(myCmp)
for charnum, (charname, width, (l, b, r, t)) in items:
lines.append("C %d ; WX %d ; N %s ; B %d %d %d %d ;" %
(charnum, width, charname, l, b, r, t))
lines.append("EndCharMetrics")
# write kerning info
lines.append("StartKernData")
lines.append("StartKernPairs " + `len(self._kerning)`)
items = self._kerning.items()
items.sort() # XXX is order important?
for (leftchar, rightchar), value in items:
lines.append("KPX %s %s %d" % (leftchar, rightchar, value))
lines.append("EndKernPairs")
lines.append("EndKernData")
lines.append("EndFontMetrics")
writelines(path, lines, sep)
def has_kernpair(self, pair):
return self._kerning.has_key(pair)
def kernpairs(self):
return self._kerning.keys()
def has_char(self, char):
return self._chars.has_key(char)
def chars(self):
return self._chars.keys()
def comments(self):
return self._comments
def __getattr__(self, attr):
if self._attrs.has_key(attr):
return self._attrs[attr]
else:
raise AttributeError, attr
def __setattr__(self, attr, value):
# all attrs *not* starting with "_" are consider to be AFM keywords
if attr[:1] == "_":
self.__dict__[attr] = value
else:
self._attrs[attr] = value
def __getitem__(self, key):
if type(key) == types.TupleType:
# key is a tuple, return the kernpair
if self._kerning.has_key(key):
return self._kerning[key]
else:
raise KeyError, "no kerning pair: " + str(key)
else:
# return the metrics instead
if self._chars.has_key(key):
return self._chars[key]
else:
raise KeyError, "metrics index " + str(key) + " out of range"
def __repr__(self):
if hasattr(self, "FullName"):
return '<AFM object for %s>' % self.FullName
else:
return '<AFM object at %x>' % id(self)
def readlines(path):
f = open(path, 'rb')
data = f.read()
f.close()
# read any text file, regardless whether it's formatted for Mac, Unix or Dos
sep = ""
if '\r' in data:
sep = sep + '\r' # mac or dos
if '\n' in data:
sep = sep + '\n' # unix or dos
return string.split(data, sep)
def writelines(path, lines, sep = '\r'):
f = open(path, 'wb')
for line in lines:
f.write(line + sep)
f.close()
if __name__ == "__main__":
import macfs
fss, ok = macfs.StandardGetFile('TEXT')
if ok:
path = fss.as_pathname()
afm = AFM(path)
char = 'A'
if afm.has_char(char):
print afm[char] # print charnum, width and boundingbox
pair = ('A', 'V')
if afm.has_kernpair(pair):
print afm[pair] # print kerning value for pair
print afm.Version # various other afm entries have become attributes
print afm.Weight
# afm.comments() returns a list of all Comment lines found in the AFM
print afm.comments()
#print afm.chars()
#print afm.kernpairs()
print afm
afm.write(path + ".xxx")

1189
Lib/fontTools/agl.py Normal file

File diff suppressed because it is too large Load Diff

459
Lib/fontTools/cffLib.py Normal file
View File

@ -0,0 +1,459 @@
"""cffLib.py -- read/write tools for Adobe CFF fonts."""
__version__ = "1.0b1"
__author__ = "jvr"
import struct, sstruct
import string
import types
import psCharStrings
cffHeaderFormat = """
major: B
minor: B
hdrSize: B
offSize: B
"""
class CFFFontSet:
def __init__(self):
self.fonts = {}
def decompile(self, data):
sstruct.unpack(cffHeaderFormat, data[:4], self)
assert self.major == 1 and self.minor == 0, \
"unknown CFF format: %d.%d" % (self.major, self.minor)
restdata = data[self.hdrSize:]
self.fontNames, restdata = readINDEX(restdata)
topDicts, restdata = readINDEX(restdata)
strings, restdata = readINDEX(restdata)
strings = IndexedStrings(strings)
globalSubrs, restdata = readINDEX(restdata)
self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs)
for i in range(len(topDicts)):
font = self.fonts[self.fontNames[i]] = CFFFont()
font.GlobalSubrs = self.GlobalSubrs # Hmm.
font.decompile(data, topDicts[i], strings, self) # maybe only 'on demand'?
def compile(self):
strings = IndexedStrings()
XXXX
def toXML(self, xmlWriter, progress=None):
xmlWriter.newline()
for fontName in self.fontNames:
xmlWriter.begintag("CFFFont", name=fontName)
xmlWriter.newline()
font = self.fonts[fontName]
font.toXML(xmlWriter, progress)
xmlWriter.endtag("CFFFont")
xmlWriter.newline()
xmlWriter.newline()
xmlWriter.begintag("GlobalSubrs")
xmlWriter.newline()
for i in range(len(self.GlobalSubrs)):
xmlWriter.newline()
xmlWriter.begintag("CharString", id=i)
xmlWriter.newline()
self.GlobalSubrs[i].toXML(xmlWriter)
xmlWriter.endtag("CharString")
xmlWriter.newline()
xmlWriter.newline()
xmlWriter.endtag("GlobalSubrs")
xmlWriter.newline()
xmlWriter.newline()
def fromXML(self, (name, attrs, content)):
xxx
class IndexedStrings:
def __init__(self, strings=None):
if strings is None:
strings = []
self.strings = strings
def __getitem__(self, SID):
if SID < cffStandardStringCount:
return cffStandardStrings[SID]
else:
return self.strings[SID - cffStandardStringCount]
def getSID(self, s):
if not hasattr(self, "stringMapping"):
self.buildStringMapping()
if cffStandardStringMapping.has_key(s):
SID = cffStandardStringMapping[s]
if self.stringMapping.has_key(s):
SID = self.stringMapping[s]
else:
SID = len(self.strings) + cffStandardStringCount
self.strings.append(s)
self.stringMapping[s] = SID
return SID
def getStrings(self):
return self.strings
def buildStringMapping(self):
self.stringMapping = {}
for index in range(len(self.strings)):
self.stringMapping[self.strings[index]] = index + cffStandardStringCount
class CFFFont:
defaults = psCharStrings.topDictDefaults
def __init__(self):
pass
def __getattr__(self, attr):
if not self.defaults.has_key(attr):
raise AttributeError, attr
return self.defaults[attr]
def fromDict(self, dict):
self.__dict__.update(dict)
def decompile(self, data, topDictData, strings, fontSet):
top = psCharStrings.TopDictDecompiler(strings)
top.decompile(topDictData)
self.fromDict(top.getDict())
# get private dict
size, offset = self.Private
#print "YYY Private (size, offset):", size, offset
privateData = data[offset:offset+size]
self.Private = PrivateDict()
self.Private.decompile(data[offset:], privateData, strings)
# get raw charstrings
#print "YYYY CharStrings offset:", self.CharStrings
rawCharStrings, restdata = readINDEX(data[self.CharStrings:])
nGlyphs = len(rawCharStrings)
# get charset (or rather: get glyphNames)
charsetOffset = self.charset
if charsetOffset == 0:
xxx # standard charset
else:
#print "YYYYY charsetOffset:", charsetOffset
format = ord(data[charsetOffset])
if format == 0:
xxx
elif format == 1:
charSet = parseCharsetFormat1(nGlyphs,
data[charsetOffset+1:], strings)
elif format == 2:
charSet = parseCharsetFormat2(nGlyphs,
data[charsetOffset+1:], strings)
elif format == 3:
xxx
else:
xxx
self.charset = charSet
assert len(charSet) == nGlyphs
self.CharStrings = charStrings = {}
if self.CharstringType == 2:
# Type 2 CharStrings
charStringClass = psCharStrings.T2CharString
else:
# Type 1 CharStrings
charStringClass = psCharStrings.T1CharString
for i in range(nGlyphs):
charStrings[charSet[i]] = charStringClass(rawCharStrings[i])
assert len(charStrings) == nGlyphs
# XXX Encoding!
encoding = self.Encoding
if encoding not in (0, 1):
# encoding is an _offset_ from the beginning of 'data' to an encoding subtable
XXX
self.Encoding = encoding
def getGlyphOrder(self):
return self.charset
def setGlyphOrder(self, glyphOrder):
self.charset = glyphOrder
def decompileAllCharStrings(self):
if self.CharstringType == 2:
# Type 2 CharStrings
decompiler = psCharStrings.SimpleT2Decompiler(self.Private.Subrs, self.GlobalSubrs)
for charString in self.CharStrings.values():
if charString.needsDecompilation():
decompiler.reset()
decompiler.execute(charString)
else:
# Type 1 CharStrings
for charString in self.CharStrings.values():
charString.decompile()
def toXML(self, xmlWriter, progress=None):
xmlWriter.newline()
# first dump the simple values
self.toXMLSimpleValues(xmlWriter)
# dump charset
# XXX
# decompile all charstrings
if progress:
progress.setlabel("Decompiling CharStrings...")
self.decompileAllCharStrings()
# dump private dict
xmlWriter.begintag("Private")
xmlWriter.newline()
self.Private.toXML(xmlWriter)
xmlWriter.endtag("Private")
xmlWriter.newline()
self.toXMLCharStrings(xmlWriter, progress)
def toXMLSimpleValues(self, xmlWriter):
keys = self.__dict__.keys()
keys.remove("CharStrings")
keys.remove("Private")
keys.remove("charset")
keys.remove("GlobalSubrs")
keys.sort()
for key in keys:
value = getattr(self, key)
if key == "Encoding":
if value == 0:
# encoding is (Adobe) Standard Encoding
value = "StandardEncoding"
elif value == 1:
# encoding is Expert Encoding
value = "ExpertEncoding"
if type(value) == types.ListType:
value = string.join(map(str, value), " ")
else:
value = str(value)
xmlWriter.begintag(key)
if hasattr(value, "toXML"):
xmlWriter.newline()
value.toXML(xmlWriter)
xmlWriter.newline()
else:
xmlWriter.write(value)
xmlWriter.endtag(key)
xmlWriter.newline()
xmlWriter.newline()
def toXMLCharStrings(self, xmlWriter, progress=None):
charStrings = self.CharStrings
xmlWriter.newline()
xmlWriter.begintag("CharStrings")
xmlWriter.newline()
glyphNames = charStrings.keys()
glyphNames.sort()
for glyphName in glyphNames:
if progress:
progress.setlabel("Dumping 'CFF ' table... (%s)" % glyphName)
progress.increment()
xmlWriter.newline()
charString = charStrings[glyphName]
xmlWriter.begintag("CharString", name=glyphName)
xmlWriter.newline()
charString.toXML(xmlWriter)
xmlWriter.endtag("CharString")
xmlWriter.newline()
xmlWriter.newline()
xmlWriter.endtag("CharStrings")
xmlWriter.newline()
class PrivateDict:
defaults = psCharStrings.privateDictDefaults
def __init__(self):
pass
def decompile(self, data, privateData, strings):
p = psCharStrings.PrivateDictDecompiler(strings)
p.decompile(privateData)
self.fromDict(p.getDict())
# get local subrs
#print "YYY Private.Subrs:", self.Subrs
chunk = data[self.Subrs:]
localSubrs, restdata = readINDEX(chunk)
self.Subrs = map(psCharStrings.T2CharString, localSubrs)
def toXML(self, xmlWriter):
xmlWriter.newline()
keys = self.__dict__.keys()
keys.remove("Subrs")
for key in keys:
value = getattr(self, key)
if type(value) == types.ListType:
value = string.join(map(str, value), " ")
else:
value = str(value)
xmlWriter.begintag(key)
xmlWriter.write(value)
xmlWriter.endtag(key)
xmlWriter.newline()
# write subroutines
xmlWriter.newline()
xmlWriter.begintag("Subrs")
xmlWriter.newline()
for i in range(len(self.Subrs)):
xmlWriter.newline()
xmlWriter.begintag("CharString", id=i)
xmlWriter.newline()
self.Subrs[i].toXML(xmlWriter)
xmlWriter.endtag("CharString")
xmlWriter.newline()
xmlWriter.newline()
xmlWriter.endtag("Subrs")
xmlWriter.newline()
xmlWriter.newline()
def __getattr__(self, attr):
if not self.defaults.has_key(attr):
raise AttributeError, attr
return self.defaults[attr]
def fromDict(self, dict):
self.__dict__.update(dict)
def readINDEX(data):
count, = struct.unpack(">H", data[:2])
count = int(count)
offSize = ord(data[2])
data = data[3:]
offsets = []
for index in range(count+1):
chunk = data[index * offSize: (index+1) * offSize]
chunk = '\0' * (4 - offSize) + chunk
offset, = struct.unpack(">L", chunk)
offset = int(offset)
offsets.append(offset)
data = data[(count+1) * offSize:]
prev = offsets[0]
stuff = []
for next in offsets[1:]:
chunk = data[prev-1:next-1]
assert len(chunk) == next - prev
stuff.append(chunk)
prev = next
data = data[next-1:]
return stuff, data
def parseCharsetFormat1(nGlyphs, data, strings):
charSet = ['.notdef']
count = 1
while count < nGlyphs:
first = int(struct.unpack(">H", data[:2])[0])
nLeft = ord(data[2])
data = data[3:]
for SID in range(first, first+nLeft+1):
charSet.append(strings[SID])
count = count + nLeft + 1
return charSet
def parseCharsetFormat2(nGlyphs, data, strings):
charSet = ['.notdef']
count = 1
while count < nGlyphs:
first = int(struct.unpack(">H", data[:2])[0])
nLeft = int(struct.unpack(">H", data[2:4])[0])
data = data[4:]
for SID in range(first, first+nLeft+1):
charSet.append(strings[SID])
count = count + nLeft + 1
return charSet
# The 391 Standard Strings as used in the CFF format.
# from Adobe Technical None #5176, version 1.0, 18 March 1998
cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
'Semibold'
]
cffStandardStringCount = 391
assert len(cffStandardStrings) == cffStandardStringCount
# build reverse mapping
cffStandardStringMapping = {}
for _i in range(cffStandardStringCount):
cffStandardStringMapping[cffStandardStrings[_i]] = _i

553
Lib/fontTools/fondLib.py Normal file
View File

@ -0,0 +1,553 @@
import os
import Res
import struct, sstruct
import string
__version__ = "1.0b2"
__author__ = "jvr"
error = "fondLib.error"
DEBUG = 0
headerformat = """
ffFlags: h
ffFamID: h
ffFirstChar: h
ffLastChar: h
ffAscent: h
ffDescent: h
ffLeading: h
ffWidMax: h
ffWTabOff: l
ffKernOff: l
ffStylOff: l
"""
FONDheadersize = 52
class FontFamily:
def __init__(self, theRes, mode = 'r'):
self.ID, type, self.name = theRes.GetResInfo()
if type <> 'FOND':
raise ValueError, "FOND resource required"
self.FOND = theRes
self.mode = mode
self.changed = 0
if DEBUG:
self.parsedthings = []
def parse(self):
self._getheader()
self._getfontassociationtable()
self._getoffsettable()
self._getboundingboxtable()
self._getglyphwidthtable()
self._getstylemappingtable()
self._getglyphencodingsubtable()
self._getkerningtables()
def minimalparse(self):
self._getheader()
self._getglyphwidthtable()
self._getstylemappingtable()
def __repr__(self):
return "<FontFamily instance of %s>" % self.name
def getflags(self):
return self.fondClass
def setflags(self, flags):
self.changed = 1
self.fondClass = flags
def save(self, destresfile = None):
if self.mode <> 'w':
raise error, "can't save font: no write permission"
self._buildfontassociationtable()
self._buildoffsettable()
self._buildboundingboxtable()
self._buildglyphwidthtable()
self._buildkerningtables()
self._buildstylemappingtable()
self._buildglyphencodingsubtable()
rawnames = [ "_rawheader",
"_rawfontassociationtable",
"_rawoffsettable",
"_rawglyphwidthtable",
"_rawstylemappingtable",
"_rawglyphencodingsubtable",
"_rawkerningtables"
]
for name in rawnames[1:]: # skip header
data = getattr(self, name)
if len(data) & 1:
setattr(self, name, data + '\0')
self.ffWTabOff = FONDheadersize + len(self._rawfontassociationtable) + len(self._rawoffsettable)
self.ffStylOff = self.ffWTabOff + len(self._rawglyphwidthtable)
self.ffKernOff = self.ffStylOff + len(self._rawstylemappingtable) + len(self._rawglyphencodingsubtable)
self.glyphTableOffset = len(self._rawstylemappingtable)
if not self._rawglyphwidthtable:
self.ffWTabOff = 0
if not self._rawstylemappingtable:
self.ffStylOff = 0
if not self._rawglyphencodingsubtable:
self.glyphTableOffset = 0
if not self._rawkerningtables:
self.ffKernOff = 0
self._buildheader()
# glyphTableOffset has only just been calculated
self._updatestylemappingtable()
newdata = ""
for name in rawnames:
newdata = newdata + getattr(self, name)
if destresfile is None:
self.FOND.data = newdata
self.FOND.ChangedResource()
self.FOND.WriteResource()
else:
ID, type, name = self.FOND.GetResInfo()
self.FOND.DetachResource()
self.FOND.data = newdata
saveref = Res.CurResFile()
Res.UseResFile(destresfile)
self.FOND.AddResource(type, ID, name)
Res.UseResFile(saveref)
self.changed = 0
def _getheader(self):
data = self.FOND.data
sstruct.unpack(headerformat, data[:28], self)
self.ffProperty = struct.unpack("9h", data[28:46])
self.ffIntl = struct.unpack("hh", data[46:50])
self.ffVersion, = struct.unpack("h", data[50:FONDheadersize])
if DEBUG:
self._rawheader = data[:FONDheadersize]
self.parsedthings.append((0, FONDheadersize, 'header'))
def _buildheader(self):
header = sstruct.pack(headerformat, self)
header = header + apply(struct.pack, ("9h",) + self.ffProperty)
header = header + apply(struct.pack, ("hh",) + self.ffIntl)
header = header + struct.pack("h", self.ffVersion)
if DEBUG:
print "header is the same?", self._rawheader == header and 'yes.' or 'no.'
if self._rawheader <> header:
print len(self._rawheader), len(header)
self._rawheader = header
def _getfontassociationtable(self):
data = self.FOND.data
offset = FONDheadersize
numberofentries, = struct.unpack("h", data[offset:offset+2])
numberofentries = numberofentries + 1
size = numberofentries * 6
self.fontAssoc = []
for i in range(offset + 2, offset + size, 6):
self.fontAssoc.append(struct.unpack("3h", data[i:i+6]))
self._endoffontassociationtable = offset + size + 2
if DEBUG:
self._rawfontassociationtable = data[offset:self._endoffontassociationtable]
self.parsedthings.append((offset, self._endoffontassociationtable, 'fontassociationtable'))
def _buildfontassociationtable(self):
data = struct.pack("h", len(self.fontAssoc) - 1)
for size, stype, ID in self.fontAssoc:
data = data + struct.pack("3h", size, stype, ID)
if DEBUG:
print "font association table is the same?", self._rawfontassociationtable == data and 'yes.' or 'no.'
if self._rawfontassociationtable <> data:
print len(self._rawfontassociationtable), len(data)
self._rawfontassociationtable = data
def _getoffsettable(self):
if self.ffWTabOff == 0:
self._rawoffsettable = ""
return
data = self.FOND.data
# Quick'n'Dirty. What's the spec anyway? Can't find it...
offset = self._endoffontassociationtable
count = self.ffWTabOff
self._rawoffsettable = data[offset:count]
if DEBUG:
self.parsedthings.append((offset, count, 'offsettable&bbtable'))
def _buildoffsettable(self):
if not hasattr(self, "_rawoffsettable"):
self._rawoffsettable = ""
def _getboundingboxtable(self):
self.boundingBoxes = None
if self._rawoffsettable[:6] <> '\0\0\0\0\0\6': # XXX ????
return
boxes = {}
data = self._rawoffsettable[6:]
numstyles = struct.unpack("h", data[:2])[0] + 1
data = data[2:]
for i in range(numstyles):
style, l, b, r, t = struct.unpack("hhhhh", data[:10])
boxes[style] = (l, b, r, t)
data = data[10:]
self.boundingBoxes = boxes
def _buildboundingboxtable(self):
if self.boundingBoxes and self._rawoffsettable[:6] == '\0\0\0\0\0\6':
boxes = self.boundingBoxes.items()
boxes.sort()
data = '\0\0\0\0\0\6' + struct.pack("h", len(boxes) - 1)
for style, (l, b, r, t) in boxes:
data = data + struct.pack("hhhhh", style, l, b, r, t)
self._rawoffsettable = data
def _getglyphwidthtable(self):
self.widthTables = {}
if self.ffWTabOff == 0:
return
data = self.FOND.data
offset = self.ffWTabOff
numberofentries, = struct.unpack("h", data[offset:offset+2])
numberofentries = numberofentries + 1
count = offset + 2
for i in range(numberofentries):
stylecode, = struct.unpack("h", data[count:count+2])
widthtable = self.widthTables[stylecode] = []
count = count + 2
for j in range(3 + self.ffLastChar - self.ffFirstChar):
width, = struct.unpack("h", data[count:count+2])
widthtable.append(width)
count = count + 2
if DEBUG:
self._rawglyphwidthtable = data[offset:count]
self.parsedthings.append((offset, count, 'glyphwidthtable'))
def _buildglyphwidthtable(self):
if not self.widthTables:
self._rawglyphwidthtable = ""
return
numberofentries = len(self.widthTables)
data = struct.pack('h', numberofentries - 1)
tables = self.widthTables.items()
tables.sort()
for stylecode, table in tables:
data = data + struct.pack('h', stylecode)
if len(table) <> (3 + self.ffLastChar - self.ffFirstChar):
raise error, "width table has wrong length"
for width in table:
data = data + struct.pack('h', width)
if DEBUG:
print "glyph width table is the same?", self._rawglyphwidthtable == data and 'yes.' or 'no.'
self._rawglyphwidthtable = data
def _getkerningtables(self):
self.kernTables = {}
if self.ffKernOff == 0:
return
data = self.FOND.data
offset = self.ffKernOff
numberofentries, = struct.unpack("h", data[offset:offset+2])
numberofentries = numberofentries + 1
count = offset + 2
for i in range(numberofentries):
stylecode, = struct.unpack("h", data[count:count+2])
count = count + 2
numberofpairs, = struct.unpack("h", data[count:count+2])
count = count + 2
kerntable = self.kernTables[stylecode] = []
for j in range(numberofpairs):
firstchar, secondchar, kerndistance = struct.unpack("cch", data[count:count+4])
kerntable.append((ord(firstchar), ord(secondchar), kerndistance))
count = count + 4
if DEBUG:
self._rawkerningtables = data[offset:count]
self.parsedthings.append((offset, count, 'kerningtables'))
def _buildkerningtables(self):
if self.kernTables == {}:
self._rawkerningtables = ""
self.ffKernOff = 0
return
numberofentries = len(self.kernTables)
data = [struct.pack('h', numberofentries - 1)]
tables = self.kernTables.items()
tables.sort()
for stylecode, table in tables:
data.append(struct.pack('h', stylecode))
data.append(struct.pack('h', len(table))) # numberofpairs
for firstchar, secondchar, kerndistance in table:
data.append(struct.pack("cch", chr(firstchar), chr(secondchar), kerndistance))
data = string.join(data, '')
if DEBUG:
print "kerning table is the same?", self._rawkerningtables == data and 'yes.' or 'no.'
if self._rawkerningtables <> data:
print len(self._rawkerningtables), len(data)
self._rawkerningtables = data
def _getstylemappingtable(self):
offset = self.ffStylOff
self.styleStrings = []
self.styleIndices = ()
self.glyphTableOffset = 0
self.fondClass = 0
if offset == 0:
return
data = self.FOND.data
self.fondClass, self.glyphTableOffset, self.styleMappingReserved, = \
struct.unpack("hll", data[offset:offset+10])
self.styleIndices = struct.unpack('48b', data[offset + 10:offset + 58])
stringcount, = struct.unpack('h', data[offset+58:offset+60])
count = offset + 60
for i in range(stringcount):
str_len = ord(data[count])
self.styleStrings.append(data[count + 1:count + 1 + str_len])
count = count + 1 + str_len
self._unpackstylestrings()
data = data[offset:count]
if len(data) % 2:
data = data + '\0'
if DEBUG:
self._rawstylemappingtable = data
self.parsedthings.append((offset, count, 'stylemappingtable'))
def _buildstylemappingtable(self):
if not self.styleIndices:
self._rawstylemappingtable = ""
return
data = struct.pack("hll", self.fondClass, self.glyphTableOffset,
self.styleMappingReserved)
self._packstylestrings()
data = data + apply(struct.pack, ("48b",) + self.styleIndices)
stringcount = len(self.styleStrings)
data = data + struct.pack("h", stringcount)
for string in self.styleStrings:
data = data + chr(len(string)) + string
if len(data) % 2:
data = data + '\0'
if DEBUG:
print "style mapping table is the same?", self._rawstylemappingtable == data and 'yes.' or 'no.'
self._rawstylemappingtable = data
def _unpackstylestrings(self):
psNames = {}
self.ffFamilyName = self.styleStrings[0]
for i in self.widthTables.keys():
index = self.styleIndices[i]
if index == 1:
psNames[i] = self.styleStrings[0]
else:
style = self.styleStrings[0]
codes = map(ord, self.styleStrings[index - 1])
for code in codes:
style = style + self.styleStrings[code - 1]
psNames[i] = style
self.psNames = psNames
def _packstylestrings(self):
nameparts = {}
splitnames = {}
for style, name in self.psNames.items():
split = splitname(name, self.ffFamilyName)
splitnames[style] = split
for part in split:
nameparts[part] = None
del nameparts[self.ffFamilyName]
nameparts = nameparts.keys()
nameparts.sort()
items = splitnames.items()
items.sort()
numindices = 0
for style, split in items:
if len(split) > 1:
numindices = numindices + 1
styleStrings = [self.ffFamilyName] + numindices * [None] + nameparts
# XXX the next bit goes wrong for MM fonts.
for style, split in items:
if len(split) == 1:
continue
indices = ""
for part in split[1:]:
indices = indices + chr(nameparts.index(part) + numindices + 2)
styleStrings[self.styleIndices[style] - 1] = indices
self.styleStrings = styleStrings
def _updatestylemappingtable(self):
# Update the glyphTableOffset field.
# This is neccesary since we have to build this table to
# know what the glyphTableOffset will be.
# And we don't want to build it twice, do we?
data = self._rawstylemappingtable
if not data:
return
data = data[:2] + struct.pack("l", self.glyphTableOffset) + data[6:]
self._rawstylemappingtable = data
def _getglyphencodingsubtable(self):
glyphEncoding = self.glyphEncoding = {}
if not self.glyphTableOffset:
return
offset = self.ffStylOff + self.glyphTableOffset
data = self.FOND.data
numberofentries, = struct.unpack("h", data[offset:offset+2])
count = offset + 2
for i in range(numberofentries):
glyphcode = ord(data[count])
count = count + 1
strlen = ord(data[count])
count = count + 1
glyphname = data[count:count+strlen]
glyphEncoding[glyphcode] = glyphname
count = count + strlen
if DEBUG:
self._rawglyphencodingsubtable = data[offset:count]
self.parsedthings.append((offset, count, 'glyphencodingsubtable'))
def _buildglyphencodingsubtable(self):
if not self.glyphEncoding:
self._rawglyphencodingsubtable = ""
return
numberofentries = len(self.glyphEncoding)
data = struct.pack("h", numberofentries)
items = self.glyphEncoding.items()
items.sort()
for glyphcode, glyphname in items:
data = data + chr(glyphcode) + chr(len(glyphname)) + glyphname
self._rawglyphencodingsubtable = data
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
def splitname(name, famname = None):
# XXX this goofs up MM font names: but how should it be done??
if famname:
if name[:len(famname)] <> famname:
raise error, "first part of name should be same as family name"
name = name[len(famname):]
split = [famname]
else:
split = []
current = ""
for c in name:
if c == '-' or c in uppercase:
if current:
split.append(current)
current = ""
current = current + c
if current:
split.append(current)
return split
def makeLWFNfilename(name):
split = splitname(name)
lwfnname = split[0][:5]
for part in split[1:]:
if part <> '-':
lwfnname = lwfnname + part[:3]
return lwfnname
class BitmapFontFile:
def __init__(self, path, mode = 'r'):
import macfs
if mode == 'r':
permission = 1 # read only
elif mode == 'w':
permission = 3 # exclusive r/w
else:
raise error, 'mode should be either "r" or "w"'
self.mode = mode
fss = macfs.FSSpec(path)
self.resref = Res.FSpOpenResFile(fss, permission)
Res.UseResFile(self.resref)
self.path = path
self.fonds = []
self.getFONDs()
def getFONDs(self):
FONDcount = Res.Count1Resources('FOND')
for i in range(FONDcount):
fond = FontFamily(Res.Get1IndResource('FOND', i + 1), self.mode)
self.fonds.append(fond)
def parse(self):
self.fondsbyname = {}
for fond in self.fonds:
fond.parse()
if hasattr(fond, "psNames") and fond.psNames:
psNames = fond.psNames.values()
psNames.sort()
self.fondsbyname[psNames[0]] = fond
def minimalparse(self):
for fond in self.fonds:
fond.minimalparse()
def close(self):
if self.resref <> None:
try:
Res.CloseResFile(self.resref)
except Res.Error:
pass
self.resref = None
class FondSelector:
def __init__(self, fondlist):
import W
if not fondlist:
raise ValueError, "expected at least one FOND entry"
if len(fondlist) == 1:
self.choice = 0
return
fonds = []
for fond in fondlist:
fonds.append(fond.name)
self.w = W.ModalDialog((200, 200), "aaa")
self.w.donebutton = W.Button((-70, -26, 60, 16), "Done", self.close)
self.w.l = W.List((10, 10, -10, -36), fonds, self.listhit)
self.w.setdefaultbutton(self.w.donebutton)
self.w.l.setselection([0])
self.w.open()
def close(self):
self.checksel()
sel = self.w.l.getselection()
self.choice = sel[0]
self.w.close()
def listhit(self, isDbl):
if isDbl:
self.w.donebutton.push()
else:
self.checksel()
def checksel(self):
sel = self.w.l.getselection()
if not sel:
self.w.l.setselection([0])
elif len(sel) <> 1:
self.w.l.setselection([sel[0]])

View File

View File

@ -0,0 +1,89 @@
"""fontTools.misc.textTools.py -- miscelaneous routines."""
import string
def safeEval(data, eval=eval):
"""A safe replacement for eval."""
return eval(data, {"__builtins__":{}}, {})
def readHex(content):
"""Convert a list of hex strings to binary data."""
hexdata = ""
for chunk in content:
if type(chunk) == type(""):
hexdata = hexdata + chunk
return deHexStr(hexdata)
def deHexStr(hexdata):
"""Convert a hex string to binary data."""
parts = string.split(hexdata)
hexdata = string.join(parts, "")
if len(hexdata) % 2:
hexdata = hexdata + "0"
data = ""
for i in range(0, len(hexdata), 2):
data = data + chr(string.atoi(hexdata[i:i+2], 16))
return data
def hexStr(data):
"""Convert binary data to a hex string."""
h = string.hexdigits
r = ''
for c in data:
i = ord(c)
r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
return r
def num2binary(l, bits=32):
all = []
bin = ""
for i in range(bits):
if l & 0x1:
bin = "1" + bin
else:
bin = "0" + bin
l = l >> 1
if not ((i+1) % 8):
all.append(bin)
bin = ""
all.reverse()
assert l in (0, -1), "number doesn't fit in number of bits"
return string.join(all, " ")
def binary2num(bin):
bin = string.join(string.split(bin), "")
l = 0
for digit in bin:
l = l << 1
if digit <> "0":
l = l | 0x1
return l
def caselessSort(alist):
"""Return a sorted copy of a list. If there are only strings
in the list, it will not consider case.
"""
try:
# turn ['FOO', 'aaBc', 'ABcD'] into
# [('foo', 'FOO'), ('aabc', 'aaBc'), ('abcd', 'ABcD')],
# but only if all elements are strings
tupledlist = map(lambda item, lower = string.lower:
(lower(item), item), alist)
except TypeError:
# at least one element in alist is not a string, proceed the normal way...
alist = alist[:]
alist.sort()
return alist
else:
tupledlist.sort()
# turn [('aabc', 'aaBc'), ('abcd', 'ABcD'), ('foo', 'FOO')] into
# ['aaBc', 'ABcD', 'FOO']
return map(lambda x: x[1], tupledlist)

144
Lib/fontTools/nfntLib.py Normal file
View File

@ -0,0 +1,144 @@
import Res
import macfs
import struct
import Qd
from types import *
class NFNT:
def __init__(self, nfnt, name = "", _type = 'NFNT'):
if type(nfnt) == type(Res.Resource("")):
theID, theType, name = nfnt.GetResInfo()
if theType <> _type:
raise TypeError, 'resource of wrong type; expected ' + _type
data = nfnt.data
elif type(nfnt) == StringType:
fss = macfs.FSSpec(nfnt)
data = readnfntresource(nfnt, name, _type)
elif type(nfnt) == type(macfs.FSSpec(':')):
data = readnfntresource(nfnt, name, _type)
else:
raise TypeError, 'expected resource, string or fss; found ' + type(nfnt).__name__
self.parse_nfnt(data)
def parse_nfnt(self, data):
# header; FontRec
( self.fontType,
self.firstChar,
self.lastChar,
self.widMax,
self.kernMax,
self.nDescent,
fRectWidth,
self.fRectHeight,
owTLoc,
self.ascent,
self.descent,
self.leading,
self.rowWords ) = struct.unpack("13h", data[:26])
if owTLoc < 0:
owTLoc = owTLoc + 0x8000 # unsigned short
# rest
tablesize = 2 * (self.lastChar - self.firstChar + 3)
bitmapsize = 2 * self.rowWords * self.fRectHeight
self.bits = data[26:26 + bitmapsize]
self.bitImage = Qd.BitMap(self.bits, 2 * self.rowWords, (0, 0, self.rowWords * 16, self.fRectHeight))
owTable = data[26 + bitmapsize + tablesize:26 + bitmapsize + 2 * tablesize]
if len(owTable) <> tablesize:
raise ValueError, 'invalid NFNT resource'
locTable = data[26 + bitmapsize:26 + bitmapsize + tablesize]
if len(locTable) <> tablesize:
raise ValueError, 'invalid NFNT resource'
# fill tables
self.offsettable = []
self.widthtable = []
self.locationtable = []
for i in range(0, tablesize, 2):
self.offsettable.append(ord(owTable[i]))
self.widthtable.append(ord(owTable[i+1]))
loc, = struct.unpack('h', locTable[i:i+2])
self.locationtable.append(loc)
def drawstring(self, astring, destbits, xoffset = 0, yoffset = 0):
drawchar = self.drawchar
for ch in astring:
xoffset = drawchar(ch, destbits, xoffset, yoffset)
return xoffset
def drawchar(self, ch, destbits, xoffset, yoffset = 0):
width, bounds, destbounds = self.getcharbounds(ch)
destbounds = Qd.OffsetRect(destbounds, xoffset, yoffset)
Qd.CopyBits(self.bitImage, destbits, bounds, destbounds, 1, None)
return xoffset + width
def stringwidth(self, astring):
charwidth = self.charwidth
width = 0
for ch in astring:
width = width + charwidth(ch)
return width
def charwidth(self, ch):
cindex = ord(ch) - self.firstChar
if cindex > self.lastChar or \
(self.offsettable[cindex] == 255 and self.widthtable[cindex] == 255):
cindex = -2 # missing char
return self.widthtable[cindex]
def getcharbounds(self, ch):
cindex = ord(ch) - self.firstChar
if cindex > self.lastChar or \
(self.offsettable[cindex] == 255 and self.widthtable[cindex] == 255):
return self.getcharboundsindex(-2) # missing char
return self.getcharboundsindex(cindex)
def getcharboundsindex(self, cindex):
offset = self.offsettable[cindex]
width = self.widthtable[cindex]
if offset == 255 and width == 255:
raise ValueError, "character not defined"
location0 = self.locationtable[cindex]
location1 = self.locationtable[cindex + 1]
srcbounds = (location0, 0, location1, self.fRectHeight)
destbounds = ( offset + self.kernMax,
0,
offset + self.kernMax + location1 - location0,
self.fRectHeight )
return width, srcbounds, destbounds
def readnfntresource(fss, name, _type = 'NFNT'):
resref = Res.FSpOpenResFile(fss, 1) # readonly
Res.UseResFile(resref)
try:
if name:
res = Res.Get1NamedResource(_type, name)
else:
# just take the first in the file
res = Res.Get1IndResource(_type, 1)
theID, theType, name = res.GetResInfo()
if theType <> _type:
raise TypeError, 'resource of wrong type; expected ' + _type
data = res.data
finally:
Res.CloseResFile(resref)
return data
if 0:
import Win
fss, ok = macfs.StandardGetFile('FFIL')
if ok:
n = NFNT(fss)
s = "!!!ABCDEFGHIJKLMN01234 hemeltje lief...x.."
x = 10
y = 40
destbits = Win.FrontWindow().GetWindowPort().portBits
n.drawstring(s, destbits, x, y)
print n.stringwidth(s)

View File

@ -0,0 +1,974 @@
"""psCharStrings.py -- module implementing various kinds of CharStrings:
CFF dictionary data and Type1/Type2 CharStrings.
"""
__version__ = "1.0b1"
__author__ = "jvr"
import types
import struct
import string
t1OperandEncoding = [None] * 256
t1OperandEncoding[0:32] = (32) * ["do_operator"]
t1OperandEncoding[32:247] = (247 - 32) * ["read_byte"]
t1OperandEncoding[247:251] = (251 - 247) * ["read_smallInt1"]
t1OperandEncoding[251:255] = (255 - 251) * ["read_smallInt2"]
t1OperandEncoding[255] = "read_longInt"
assert len(t1OperandEncoding) == 256
t2OperandEncoding = t1OperandEncoding[:]
t2OperandEncoding[28] = "read_shortInt"
cffDictOperandEncoding = t2OperandEncoding[:]
cffDictOperandEncoding[29] = "read_longInt"
cffDictOperandEncoding[30] = "read_realNumber"
cffDictOperandEncoding[255] = "reserved"
realNibbles = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'.', 'E', 'E-', None, '-']
class ByteCodeDecompilerBase:
def read_byte(self, b0, data, index):
return b0 - 139, index
def read_smallInt1(self, b0, data, index):
b1 = ord(data[index])
return (b0-247)*256 + b1 + 108, index+1
def read_smallInt2(self, b0, data, index):
b1 = ord(data[index])
return -(b0-251)*256 - b1 - 108, index+1
def read_shortInt(self, b0, data, index):
bin = data[index] + data[index+1]
value, = struct.unpack(">h", bin)
return value, index+2
def read_longInt(self, b0, data, index):
bin = data[index] + data[index+1] + data[index+2] + data[index+3]
value, = struct.unpack(">l", bin)
return value, index+4
def read_realNumber(self, b0, data, index):
number = ''
while 1:
b = ord(data[index])
index = index + 1
nibble0 = (b & 0xf0) >> 4
nibble1 = b & 0x0f
if nibble0 == 0xf:
break
number = number + realNibbles[nibble0]
if nibble1 == 0xf:
break
number = number + realNibbles[nibble1]
return string.atof(number), index
def _buildOperatorDict(operatorList):
dict = {}
for item in operatorList:
if len(item) == 2:
dict[item[0]] = item[1]
else:
dict[item[0]] = item[1:]
return dict
t2Operators = [
# opcode name
(1, 'hstem'),
(3, 'vstem'),
(4, 'vmoveto'),
(5, 'rlineto'),
(6, 'hlineto'),
(7, 'vlineto'),
(8, 'rrcurveto'),
(10, 'callsubr'),
(11, 'return'),
(14, 'endchar'),
(16, 'blend'),
(18, 'hstemhm'),
(19, 'hintmask'),
(20, 'cntrmask'),
(21, 'rmoveto'),
(22, 'hmoveto'),
(23, 'vstemhm'),
(24, 'rcurveline'),
(25, 'rlinecurve'),
(26, 'vvcurveto'),
(27, 'hhcurveto'),
# (28, 'shortint'), # not really an operator
(29, 'callgsubr'),
(30, 'vhcurveto'),
(31, 'hvcurveto'),
((12, 3), 'and'),
((12, 4), 'or'),
((12, 5), 'not'),
((12, 8), 'store'),
((12, 9), 'abs'),
((12, 10), 'add'),
((12, 11), 'sub'),
((12, 12), 'div'),
((12, 13), 'load'),
((12, 14), 'neg'),
((12, 15), 'eq'),
((12, 18), 'drop'),
((12, 20), 'put'),
((12, 21), 'get'),
((12, 22), 'ifelse'),
((12, 23), 'random'),
((12, 24), 'mul'),
((12, 26), 'sqrt'),
((12, 27), 'dup'),
((12, 28), 'exch'),
((12, 29), 'index'),
((12, 30), 'roll'),
((12, 34), 'hflex'),
((12, 35), 'flex'),
((12, 36), 'hflex1'),
((12, 37), 'flex1'),
]
class T2CharString(ByteCodeDecompilerBase):
operandEncoding = t2OperandEncoding
operators = _buildOperatorDict(t2Operators)
def __init__(self, bytecode=None, program=None):
if program is None:
program = []
self.bytecode = bytecode
self.program = program
def __repr__(self):
if self.bytecode is None:
return "<%s (source) at %x>" % (self.__class__.__name__, id(self))
else:
return "<%s (bytecode) at %x>" % (self.__class__.__name__, id(self))
def needsDecompilation(self):
return self.bytecode is not None
def setProgram(self, program):
self.program = program
self.bytecode = None
def getToken(self, index,
len=len, ord=ord, getattr=getattr, type=type, StringType=types.StringType):
if self.bytecode is not None:
if index >= len(self.bytecode):
return None, 0, 0
b0 = ord(self.bytecode[index])
index = index + 1
code = self.operandEncoding[b0]
handler = getattr(self, code)
token, index = handler(b0, self.bytecode, index)
else:
if index >= len(self.program):
return None, 0, 0
token = self.program[index]
index = index + 1
isOperator = type(token) == StringType
return token, isOperator, index
def getBytes(self, index, nBytes):
if self.bytecode is not None:
newIndex = index + nBytes
bytes = self.bytecode[index:newIndex]
index = newIndex
else:
bytes = self.program[index]
index = index + 1
assert len(bytes) == nBytes
return bytes, index
def do_operator(self, b0, data, index):
if b0 == 12:
op = (b0, ord(data[index]))
index = index+1
else:
op = b0
operator = self.operators[op]
return operator, index
def toXML(self, xmlWriter):
from misc.textTools import num2binary
if self.bytecode is not None:
xmlWriter.dumphex(self.bytecode)
else:
index = 0
args = []
while 1:
token, isOperator, index = self.getToken(index)
if token is None:
break
if isOperator:
args = map(str, args)
if token in ('hintmask', 'cntrmask'):
hintMask, isOperator, index = self.getToken(index)
bits = []
for byte in hintMask:
bits.append(num2binary(ord(byte), 8))
hintMask = repr(string.join(bits, ""))
line = string.join(args + [token, hintMask], " ")
else:
line = string.join(args + [token], " ")
xmlWriter.write(line)
xmlWriter.newline()
args = []
else:
args.append(token)
t1Operators = [
# opcode name
(1, 'hstem'),
(3, 'vstem'),
(4, 'vmoveto'),
(5, 'rlineto'),
(6, 'hlineto'),
(7, 'vlineto'),
(8, 'rrcurveto'),
(9, 'closepath'),
(10, 'callsubr'),
(11, 'return'),
(13, 'hsbw'),
(14, 'endchar'),
(21, 'rmoveto'),
(22, 'hmoveto'),
(30, 'vhcurveto'),
(31, 'hvcurveto'),
((12, 0), 'dotsection'),
((12, 1), 'vstem3'),
((12, 2), 'hstem3'),
((12, 6), 'seac'),
((12, 7), 'sbw'),
((12, 12), 'div'),
((12, 16), 'callothersubr'),
((12, 17), 'pop'),
((12, 33), 'setcurrentpoint'),
]
class T1CharString(T2CharString):
operandEncoding = t1OperandEncoding
operators = _buildOperatorDict(t1Operators)
def decompile(self):
if hasattr(self, "program"):
return
program = []
index = 0
while 1:
token, isOperator, index = self.getToken(index)
if token is None:
break
program.append(token)
self.setProgram(program)
class SimpleT2Decompiler:
def __init__(self, localSubrs, globalSubrs):
self.localSubrs = localSubrs
self.localBias = calcSubrBias(localSubrs)
self.globalSubrs = globalSubrs
self.globalBias = calcSubrBias(globalSubrs)
self.reset()
def reset(self):
self.callingStack = []
self.operandStack = []
self.hintCount = 0
self.hintMaskBytes = 0
def execute(self, charString):
self.callingStack.append(charString)
needsDecompilation = charString.needsDecompilation()
if needsDecompilation:
program = []
pushToProgram = program.append
else:
pushToProgram = lambda x: None
pushToStack = self.operandStack.append
index = 0
while 1:
token, isOperator, index = charString.getToken(index)
if token is None:
break # we're done!
pushToProgram(token)
if isOperator:
handlerName = "op_" + token
if hasattr(self, handlerName):
handler = getattr(self, handlerName)
rv = handler(index)
if rv:
hintMaskBytes, index = rv
pushToProgram(hintMaskBytes)
else:
self.popall()
else:
pushToStack(token)
if needsDecompilation:
charString.setProgram(program)
assert program[-1] in ("endchar", "return", "callsubr", "callgsubr", "seac")
del self.callingStack[-1]
def pop(self):
value = self.operandStack[-1]
del self.operandStack[-1]
return value
def popall(self):
stack = self.operandStack[:]
self.operandStack[:] = []
return stack
def push(self, value):
self.operandStack.append(value)
def op_return(self, index):
if self.operandStack:
pass
def op_endchar(self, index):
pass
def op_callsubr(self, index):
subrIndex = self.pop()
subr = self.localSubrs[subrIndex+self.localBias]
self.execute(subr)
def op_callgsubr(self, index):
subrIndex = self.pop()
subr = self.globalSubrs[subrIndex+self.globalBias]
self.execute(subr)
def op_hstemhm(self, index):
self.countHints()
op_vstemhm = op_hstemhm
def op_hintmask(self, index):
if not self.hintMaskBytes:
self.countHints()
self.hintMaskBytes = (self.hintCount + 7) / 8
hintMaskBytes, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes)
return hintMaskBytes, index
op_cntrmask = op_hintmask
def countHints(self):
assert self.hintMaskBytes == 0
args = self.popall()
self.hintCount = self.hintCount + len(args) / 2
class T2OutlineExtractor(SimpleT2Decompiler):
def __init__(self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX):
SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs)
self.nominalWidthX = nominalWidthX
self.defaultWidthX = defaultWidthX
def reset(self):
import Numeric
SimpleT2Decompiler.reset(self)
self.hints = []
self.gotWidth = 0
self.width = 0
self.currentPoint = Numeric.array((0, 0), Numeric.Int16)
self.contours = []
def getContours(self):
return self.contours
def newPath(self):
self.contours.append([[], [], 0])
def closePath(self):
if self.contours and self.contours[-1][2] == 0:
self.contours[-1][2] = 1
def appendPoint(self, point, isPrimary):
import Numeric
point = self.currentPoint + Numeric.array(point, Numeric.Int16)
self.currentPoint = point
points, flags, isClosed = self.contours[-1]
points.append(point)
flags.append(isPrimary)
def popallWidth(self, evenOdd=0):
args = self.popall()
if not self.gotWidth:
if evenOdd ^ (len(args) % 2):
self.width = self.nominalWidthX + args[0]
args = args[1:]
else:
self.width = self.defaultWidthX
self.gotWidth = 1
return args
def countHints(self):
assert self.hintMaskBytes == 0
args = self.popallWidth()
self.hintCount = self.hintCount + len(args) / 2
#
# hint operators
#
def op_hstem(self, index):
self.popallWidth() # XXX
def op_vstem(self, index):
self.popallWidth() # XXX
def op_hstemhm(self, index):
self.countHints()
#XXX
def op_vstemhm(self, index):
self.countHints()
#XXX
#def op_hintmask(self, index):
# self.countHints()
#def op_cntrmask(self, index):
# self.countHints()
#
# path constructors, moveto
#
def op_rmoveto(self, index):
self.closePath()
self.newPath()
self.appendPoint(self.popallWidth(), 1)
def op_hmoveto(self, index):
self.closePath()
self.newPath()
self.appendPoint((self.popallWidth(1)[0], 0), 1)
def op_vmoveto(self, index):
self.closePath()
self.newPath()
self.appendPoint((0, self.popallWidth(1)[0]), 1)
def op_endchar(self, index):
self.closePath()
#
# path constructors, lines
#
def op_rlineto(self, index):
args = self.popall()
for i in range(0, len(args), 2):
point = args[i:i+2]
self.appendPoint(point, 1)
def op_hlineto(self, index):
self.alternatingLineto(1)
def op_vlineto(self, index):
self.alternatingLineto(0)
#
# path constructors, curves
#
def op_rrcurveto(self, index):
"""{dxa dya dxb dyb dxc dyc}+ rrcurveto"""
args = self.popall()
for i in range(0, len(args), 6):
dxa, dya, dxb, dyb, dxc, dyc, = args[i:i+6]
self.rrcurveto((dxa, dya), (dxb, dyb), (dxc, dyc))
def op_rcurveline(self, index):
"""{dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline"""
args = self.popall()
for i in range(0, len(args)-2, 6):
dxb, dyb, dxc, dyc, dxd, dyd = args[i:i+6]
self.rrcurveto((dxb, dyb), (dxc, dyc), (dxd, dyd))
self.appendPoint(args[-2:], 1)
def op_rlinecurve(self, index):
"""{dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve"""
args = self.popall()
lineArgs = args[:-6]
for i in range(0, len(lineArgs), 2):
self.appendPoint(lineArgs[i:i+2], 1)
dxb, dyb, dxc, dyc, dxd, dyd = args[-6:]
self.rrcurveto((dxb, dyb), (dxc, dyc), (dxd, dyd))
def op_vvcurveto(self, index):
"dx1? {dya dxb dyb dyc}+ vvcurveto"
args = self.popall()
if len(args) % 2:
dx1 = args[0]
args = args[1:]
else:
dx1 = 0
for i in range(0, len(args), 4):
dya, dxb, dyb, dyc = args[i:i+4]
self.rrcurveto((dx1, dya), (dxb, dyb), (0, dyc))
dx1 = 0
def op_hhcurveto(self, index):
"""dy1? {dxa dxb dyb dxc}+ hhcurveto"""
args = self.popall()
if len(args) % 2:
dy1 = args[0]
args = args[1:]
else:
dy1 = 0
for i in range(0, len(args), 4):
dxa, dxb, dyb, dxc = args[i:i+4]
self.rrcurveto((dxa, dy1), (dxb, dyb), (dxc, 0))
dy1 = 0
def op_vhcurveto(self, index):
"""dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto (30)
{dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto
"""
args = self.popall()
while args:
args = self.vcurveto(args)
if args:
args = self.hcurveto(args)
def op_hvcurveto(self, index):
"""dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
{dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
"""
args = self.popall()
while args:
args = self.hcurveto(args)
if args:
args = self.vcurveto(args)
#
# path constructors, flex
#
def op_hflex(self, index):
XXX
def op_flex(self, index):
XXX
def op_hflex1(self, index):
XXX
def op_flex1(self, index):
XXX
#
# MultipleMaster. Well...
#
def op_blend(self, index):
XXX
# misc
def op_and(self, index):
XXX
def op_or(self, index):
XXX
def op_not(self, index):
XXX
def op_store(self, index):
XXX
def op_abs(self, index):
XXX
def op_add(self, index):
XXX
def op_sub(self, index):
XXX
def op_div(self, index):
num2 = self.pop()
num1 = self.pop()
d1 = num1/num2
d2 = float(num1)/num2
if d1 == d2:
self.push(d1)
else:
self.push(d2)
def op_load(self, index):
XXX
def op_neg(self, index):
XXX
def op_eq(self, index):
XXX
def op_drop(self, index):
XXX
def op_put(self, index):
XXX
def op_get(self, index):
XXX
def op_ifelse(self, index):
XXX
def op_random(self, index):
XXX
def op_mul(self, index):
XXX
def op_sqrt(self, index):
XXX
def op_dup(self, index):
XXX
def op_exch(self, index):
XXX
def op_index(self, index):
XXX
def op_roll(self, index):
XXX
#
# miscelaneous helpers
#
def alternatingLineto(self, isHorizontal):
args = self.popall()
for arg in args:
if isHorizontal:
point = (arg, 0)
else:
point = (0, arg)
self.appendPoint(point, 1)
isHorizontal = not isHorizontal
def rrcurveto(self, p1, p2, p3):
self.appendPoint(p1, 0)
self.appendPoint(p2, 0)
self.appendPoint(p3, 1)
def vcurveto(self, args):
dya, dxb, dyb, dxc = args[:4]
args = args[4:]
if len(args) == 1:
dyc = args[0]
args = []
else:
dyc = 0
self.rrcurveto((0, dya), (dxb, dyb), (dxc, dyc))
return args
def hcurveto(self, args):
dxa, dxb, dyb, dyc = args[:4]
args = args[4:]
if len(args) == 1:
dxc = args[0]
args = []
else:
dxc = 0
self.rrcurveto((dxa, 0), (dxb, dyb), (dxc, dyc))
return args
class T1OutlineExtractor(T2OutlineExtractor):
def __init__(self, subrs):
self.subrs = subrs
self.reset()
def reset(self):
self.flexing = 0
self.width = 0
self.sbx = 0
T2OutlineExtractor.reset(self)
def popallWidth(self, evenOdd=0):
return self.popall()
def exch(self):
stack = self.operandStack
stack[-1], stack[-2] = stack[-2], stack[-1]
#
# path constructors
#
def op_rmoveto(self, index):
if self.flexing:
return
self.newPath()
self.appendPoint(self.popall(), 1)
def op_hmoveto(self, index):
if self.flexing:
# We must add a parameter to the stack if we are flexing
self.push(0)
return
self.newPath()
self.appendPoint((self.popall()[0], 0), 1)
def op_vmoveto(self, index):
if self.flexing:
# We must add a parameter to the stack if we are flexing
self.push(0)
self.exch()
return
self.newPath()
self.appendPoint((0, self.popall()[0]), 1)
def op_closepath(self, index):
self.closePath()
def op_setcurrentpoint(self, index):
args = self.popall()
x, y = args
self.currentPoint[0] = x
self.currentPoint[1] = y
def op_endchar(self, index):
self.closePath()
def op_hsbw(self, index):
sbx, wx = self.popall()
self.width = wx
self.sbx = sbx
self.currentPoint[0] = sbx
def op_sbw(self, index):
self.popall() # XXX
#
def op_callsubr(self, index):
subrIndex = self.pop()
subr = self.subrs[subrIndex]
self.execute(subr)
def op_callothersubr(self, index):
subrIndex = self.pop()
nArgs = self.pop()
#print nArgs, subrIndex, "callothersubr"
if subrIndex == 0 and nArgs == 3:
self.doFlex()
self.flexing = 0
elif subrIndex == 1 and nArgs == 0:
self.flexing = 1
# ignore...
def op_pop(self, index):
pass # ignore...
def doFlex(self):
finaly = self.pop()
finalx = self.pop()
self.pop() # flex height is unused
p3y = self.pop()
p3x = self.pop()
bcp4y = self.pop()
bcp4x = self.pop()
bcp3y = self.pop()
bcp3x = self.pop()
p2y = self.pop()
p2x = self.pop()
bcp2y = self.pop()
bcp2x = self.pop()
bcp1y = self.pop()
bcp1x = self.pop()
rpy = self.pop()
rpx = self.pop()
# call rrcurveto
self.push(bcp1x+rpx)
self.push(bcp1y+rpy)
self.push(bcp2x)
self.push(bcp2y)
self.push(p2x)
self.push(p2y)
self.op_rrcurveto(None)
# call rrcurveto
self.push(bcp3x)
self.push(bcp3y)
self.push(bcp4x)
self.push(bcp4y)
self.push(p3x)
self.push(p3y)
self.op_rrcurveto(None)
# Push back final coords so subr 0 can find them
self.push(finalx)
self.push(finaly)
def op_dotsection(self, index):
self.popall() # XXX
def op_hstem3(self, index):
self.popall() # XXX
def op_seac(self, index):
"asb adx ady bchar achar seac"
asb, adx, ady, bchar, achar = self.popall() # XXX
self.contours.append([(asb, adx, ady, bchar, achar), None, -1])
def op_vstem3(self, index):
self.popall() # XXX
class DictDecompiler(ByteCodeDecompilerBase):
operandEncoding = cffDictOperandEncoding
dictDefaults = {}
def __init__(self, strings):
self.stack = []
self.strings = strings
self.dict = {}
def getDict(self):
assert len(self.stack) == 0, "non-empty stack"
return self.dict
def decompile(self, data):
index = 0
lenData = len(data)
push = self.stack.append
while index < lenData:
b0 = ord(data[index])
index = index + 1
code = self.operandEncoding[b0]
handler = getattr(self, code)
value, index = handler(b0, data, index)
if value is not None:
push(value)
def pop(self):
value = self.stack[-1]
del self.stack[-1]
return value
def popall(self):
all = self.stack[:]
del self.stack[:]
return all
def do_operator(self, b0, data, index):
if b0 == 12:
op = (b0, ord(data[index]))
index = index+1
else:
op = b0
operator, argType = self.operators[op]
self.handle_operator(operator, argType)
return None, index
def handle_operator(self, operator, argType):
if type(argType) == type(()):
value = ()
for arg in argType:
arghandler = getattr(self, "arg_" + arg)
value = (arghandler(operator),) + value
else:
arghandler = getattr(self, "arg_" + argType)
value = arghandler(operator)
self.dict[operator] = value
def arg_number(self, name):
return self.pop()
def arg_SID(self, name):
return self.strings[self.pop()]
def arg_array(self, name):
return self.popall()
topDictOperators = [
# opcode name argument type
(0, 'version', 'SID'),
(1, 'Notice', 'SID'),
(2, 'FullName', 'SID'),
(3, 'FamilyName', 'SID'),
(4, 'Weight', 'SID'),
(5, 'FontBBox', 'array'),
(13, 'UniqueID', 'number'),
(14, 'XUID', 'array'),
(15, 'charset', 'number'),
(16, 'Encoding', 'number'),
(17, 'CharStrings', 'number'),
(18, 'Private', ('number', 'number')),
((12, 0), 'Copyright', 'SID'),
((12, 1), 'isFixedPitch', 'number'),
((12, 2), 'ItalicAngle', 'number'),
((12, 3), 'UnderlinePosition', 'number'),
((12, 4), 'UnderlineThickness', 'number'),
((12, 5), 'PaintType', 'number'),
((12, 6), 'CharstringType', 'number'),
((12, 7), 'FontMatrix', 'array'),
((12, 8), 'StrokeWidth', 'number'),
((12, 20), 'SyntheticBase', 'number'),
((12, 21), 'PostScript', 'SID'),
((12, 22), 'BaseFontName', 'SID'),
# CID additions
((12, 30), 'ROS', ('SID', 'SID', 'number')),
((12, 31), 'CIDFontVersion', 'number'),
((12, 32), 'CIDFontRevision', 'number'),
((12, 33), 'CIDFontType', 'number'),
((12, 34), 'CIDCount', 'number'),
((12, 35), 'UIDBase', 'number'),
((12, 36), 'FDArray', 'number'),
((12, 37), 'FDSelect', 'number'),
((12, 38), 'FontName', 'SID'),
# MM, Chameleon. Pft.
]
topDictDefaults = {
'isFixedPitch': 0,
'ItalicAngle': 0,
'UnderlineThickness': 50,
'PaintType': 0,
'CharstringType': 2,
'FontMatrix': [0.001, 0, 0, 0.001, 0, 0],
'FontBBox': [0, 0, 0, 0],
'StrokeWidth': 0,
'charset': 0,
'Encoding': 0,
# CID defaults
'CIDFontVersion': 0,
'CIDFontRevision': 0,
'CIDFontType': 0,
'CIDCount': 8720,
}
class TopDictDecompiler(DictDecompiler):
operators = _buildOperatorDict(topDictOperators)
dictDefaults = topDictDefaults
privateDictOperators = [
# opcode name argument type
(6, 'BlueValues', 'array'),
(7, 'OtherBlues', 'array'),
(8, 'FamilyBlues', 'array'),
(9, 'FamilyOtherBlues', 'array'),
(10, 'StdHW', 'number'),
(11, 'StdVW', 'number'),
(19, 'Subrs', 'number'),
(20, 'defaultWidthX', 'number'),
(21, 'nominalWidthX', 'number'),
((12, 9), 'BlueScale', 'number'),
((12, 10), 'BlueShift', 'number'),
((12, 11), 'BlueFuzz', 'number'),
((12, 12), 'StemSnapH', 'array'),
((12, 13), 'StemSnapV', 'array'),
((12, 14), 'ForceBold', 'number'),
((12, 15), 'ForceBoldThreshold', 'number'),
((12, 16), 'lenIV', 'number'),
((12, 17), 'LanguageGroup', 'number'),
((12, 18), 'ExpansionFactor', 'number'),
((12, 19), 'initialRandomSeed', 'number'),
]
privateDictDefaults = {
'defaultWidthX': 0,
'nominalWidthX': 0,
'BlueScale': 0.039625,
'BlueShift': 7,
'BlueFuzz': 1,
'ForceBold': 0,
'ForceBoldThreshold': 0,
'lenIV': -1,
'LanguageGroup': 0,
'ExpansionFactor': 0.06,
'initialRandomSeed': 0,
}
class PrivateDictDecompiler(DictDecompiler):
operators = _buildOperatorDict(privateDictOperators)
dictDefaults = privateDictDefaults
def calcSubrBias(subrs):
nSubrs = len(subrs)
if nSubrs < 1240:
bias = 107
elif nSubrs < 33900:
bias = 1131
else:
bias = 32768
return bias

346
Lib/fontTools/psLib.py Normal file
View File

@ -0,0 +1,346 @@
import StringIO
import regex
import string
import eexec
import types
from psOperators import *
ps_special = '()<>[]{}%' # / is one too, but we take care of that one differently
whitespace = string.whitespace
skipwhiteRE = regex.compile("[%s]*" % whitespace)
endofthingPat = "[^][(){}<>/%s%s]*" % ('%', whitespace)
endofthingRE = regex.compile(endofthingPat)
commentRE = regex.compile("%[^\n\r]*")
# XXX This not entirely correct:
stringPat = """
(
\(
\(
[^()]* \\\\ [()]
\)
\|
\(
[^()]* ( [^()]* )
\)
\)*
[^()]*
)
"""
stringPat = string.join(string.split(stringPat), '')
stringRE = regex.compile(stringPat)
hexstringRE = regex.compile("<[%s0-9A-Fa-f]*>" % whitespace)
ps_tokenerror = 'ps_tokenerror'
ps_error = 'ps_error'
class PSTokenizer(StringIO.StringIO):
def getnexttoken(self,
# localize some stuff, for performance
len = len,
ps_special = ps_special,
stringmatch = stringRE.match,
hexstringmatch = hexstringRE.match,
commentmatch = commentRE.match,
endmatch = endofthingRE.match,
whitematch = skipwhiteRE.match):
self.pos = self.pos + whitematch(self.buf, self.pos)
if self.pos >= self.len:
return None, None
pos = self.pos
buf = self.buf
char = buf[pos]
if char in ps_special:
if char in '{}[]':
tokentype = 'do_special'
token = char
elif char == '%':
tokentype = 'do_comment'
commentlen = commentmatch(buf, pos)
token = buf[pos:pos+commentlen]
elif char == '(':
tokentype = 'do_string'
strlen = stringmatch(buf, pos)
if strlen < 0:
raise ps_tokenerror, 'bad string at character %d' % pos
token = buf[pos:pos+strlen]
elif char == '<':
tokentype = 'do_hexstring'
strlen = hexstringmatch(buf, pos)
if strlen < 0:
raise ps_tokenerror, 'bad hexstring at character %d' % pos
token = buf[pos:pos+strlen]
else:
raise ps_tokenerror, 'bad token at character %d' % pos
else:
if char == '/':
tokentype = 'do_literal'
endofthing = endmatch(buf, pos + 1) + 1
else:
tokentype = ''
endofthing = endmatch(buf, pos)
if endofthing <= 0:
raise ps_tokenerror, 'bad token at character %d' % pos
token = buf[pos:pos + endofthing]
self.pos = pos + len(token)
return tokentype, token
def skipwhite(self, whitematch = skipwhiteRE.match):
self.pos = self.pos + whitematch(self.buf, self.pos)
def starteexec(self):
self.pos = self.pos + 1
#self.skipwhite()
self.dirtybuf = self.buf[self.pos:]
self.buf, R = eexec.Decrypt(self.dirtybuf, 55665)
self.len = len(self.buf)
self.pos = 4
def stopeexec(self):
if not hasattr(self, 'dirtybuf'):
return
self.buf = self.dirtybuf
del self.dirtybuf
def flush(self):
if self.buflist:
self.buf = self.buf + string.join(self.buflist, '')
self.buflist = []
class PSInterpreter(PSOperators):
def __init__(self):
systemdict = {}
userdict = {}
self.dictstack = [systemdict, userdict]
self.stack = []
self.proclevel = 0
self.procmark = ps_procmark()
self.fillsystemdict()
def fillsystemdict(self):
systemdict = self.dictstack[0]
systemdict['['] = systemdict['mark'] = self.mark = ps_mark()
systemdict[']'] = ps_operator(']', self.do_makearray)
systemdict['true'] = ps_boolean(1)
systemdict['false'] = ps_boolean(0)
systemdict['StandardEncoding'] = ps_array(ps_StandardEncoding)
systemdict['FontDirectory'] = ps_dict({})
self.suckoperators(systemdict, self.__class__)
def suckoperators(self, systemdict, klass):
for name in dir(klass):
attr = getattr(self, name)
if callable(attr) and name[:3] == 'ps_':
name = name[3:]
systemdict[name] = ps_operator(name, attr)
for baseclass in klass.__bases__:
self.suckoperators(systemdict, baseclass)
def interpret(self, data, getattr = getattr):
tokenizer = self.tokenizer = PSTokenizer(data)
getnexttoken = tokenizer.getnexttoken
do_token = self.do_token
handle_object = self.handle_object
try:
while 1:
tokentype, token = getnexttoken()
#print token
if not token:
break
if tokentype:
handler = getattr(self, tokentype)
object = handler(token)
else:
object = do_token(token)
if object is not None:
handle_object(object)
tokenizer.close()
self.tokenizer = None
finally:
if self.tokenizer is not None:
print 'ps error:\n- - - - - - -'
print self.tokenizer.buf[self.tokenizer.pos-50:self.tokenizer.pos]
print '>>>'
print self.tokenizer.buf[self.tokenizer.pos:self.tokenizer.pos+50]
print '- - - - - - -'
def handle_object(self, object):
if not (self.proclevel or object.literal or object.type == 'proceduretype'):
if object.type <> 'operatortype':
object = self.resolve_name(object.value)
if object.literal:
self.push(object)
else:
if object.type == 'proceduretype':
self.call_procedure(object)
else:
object.function()
else:
self.push(object)
def call_procedure(self, proc):
handle_object = self.handle_object
for item in proc.value:
handle_object(item)
def resolve_name(self, name):
dictstack = self.dictstack
for i in range(len(dictstack)-1, -1, -1):
if dictstack[i].has_key(name):
return dictstack[i][name]
raise ps_error, 'name error: ' + str(name)
def do_token(self, token,
atoi = string.atoi,
atof = string.atof,
ps_name = ps_name,
ps_integer = ps_integer,
ps_real = ps_real):
try:
num = atoi(token)
except (ValueError, OverflowError):
try:
num = atof(token)
except (ValueError, OverflowError):
if '#' in token:
hashpos = string.find(token, '#')
try:
base = string.atoi(token[:hashpos])
num = string.atoi(token[hashpos+1:], base)
except (ValueError, OverflowError):
return ps_name(token)
else:
return ps_integer(num)
else:
return ps_name(token)
else:
return ps_real(num)
else:
return ps_integer(num)
def do_comment(self, token):
pass
def do_literal(self, token):
return ps_literal(token[1:])
def do_string(self, token):
return ps_string(token[1:-1])
def do_hexstring(self, token):
hexStr = string.join(string.split(token[1:-1]), '')
if len(hexStr) % 2:
hexStr = hexStr + '0'
cleanstr = []
for i in range(0, len(hexStr), 2):
cleanstr.append(chr(string.atoi(hexStr[i:i+2], 16)))
cleanstr = string.join(cleanstr, '')
return ps_string(cleanstr)
def do_special(self, token):
if token == '{':
self.proclevel = self.proclevel + 1
return self.procmark
elif token == '}':
proc = []
while 1:
topobject = self.pop()
if topobject == self.procmark:
break
proc.append(topobject)
self.proclevel = self.proclevel - 1
proc.reverse()
return ps_procedure(proc)
elif token == '[':
return self.mark
elif token == ']':
return ps_name(']')
else:
raise ps_tokenerror, 'huh?'
def push(self, object):
self.stack.append(object)
def pop(self, *types):
stack = self.stack
if not stack:
raise ps_error, 'stack underflow'
object = stack[-1]
if types:
if object.type not in types:
raise ps_error, 'typecheck, expected %s, found %s' % (`types`, object.type)
del stack[-1]
return object
def do_makearray(self):
array = []
while 1:
topobject = self.pop()
if topobject == self.mark:
break
array.append(topobject)
array.reverse()
self.push(ps_array(array))
def close(self):
"""Remove circular references."""
del self.stack
del self.dictstack
def unpack_item(item):
tp = type(item.value)
if tp == types.DictionaryType:
newitem = {}
for key, value in item.value.items():
newitem[key] = unpack_item(value)
elif tp == types.ListType:
newitem = [None] * len(item.value)
for i in range(len(item.value)):
newitem[i] = unpack_item(item.value[i])
if item.type == 'proceduretype':
newitem = tuple(newitem)
else:
newitem = item.value
return newitem
def suckfont(data):
import re
m = re.search(r"/FontName\s+/([^ \t\n\r]+)\s+def", data)
if m:
fontName = m.group(1)
else:
fontName = None
interpreter = PSInterpreter()
interpreter.interpret("/Helvetica 4 dict dup /Encoding StandardEncoding put definefont pop")
interpreter.interpret(data)
fontdir = interpreter.dictstack[0]['FontDirectory'].value
if fontdir.has_key(fontName):
rawfont = fontdir[fontName]
else:
# fall back, in case fontName wasn't found
fontNames = fontdir.keys()
if len(fontNames) > 1:
fontNames.remove("Helvetica")
fontNames.sort()
rawfont = fontdir[fontNames[0]]
interpreter.close()
return unpack_item(rawfont)
if __name__ == "__main__":
import macfs
fss, ok = macfs.StandardGetFile("LWFN")
if ok:
import t1Lib
data, kind = t1Lib.read(fss.as_pathname())
font = suckfont(data)

View File

@ -0,0 +1,580 @@
import string
_accessstrings = {0: "", 1: "readonly", 2: "executeonly", 3: "noaccess"}
class ps_object:
literal = 1
access = 0
value = None
def __init__(self, value):
self.value = value
self.type = self.__class__.__name__[3:] + "type"
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__[3:], repr(self.value))
class ps_operator(ps_object):
literal = 0
def __init__(self, name, function):
self.name = name
self.function = function
self.type = self.__class__.__name__[3:] + "type"
def __repr__(self):
return "<operator %s>" % self.name
class ps_procedure(ps_object):
literal = 0
def __repr__(self):
return "<procedure>"
def __str__(self):
psstring = '{'
for i in range(len(self.value)):
if i:
psstring = psstring + ' ' + str(self.value[i])
else:
psstring = psstring + str(self.value[i])
return psstring + '}'
class ps_name(ps_object):
literal = 0
def __str__(self):
if self.literal:
return '/' + self.value
else:
return self.value
class ps_literal(ps_object):
def __str__(self):
return '/' + self.value
class ps_array(ps_object):
def __str__(self):
psstring = '['
for i in range(len(self.value)):
item = self.value[i]
access = _accessstrings[item.access]
if access:
access = ' ' + access
if i:
psstring = psstring + ' ' + str(item) + access
else:
psstring = psstring + str(item) + access
return psstring + ']'
def __repr__(self):
return "<array>"
_type1_pre_eexec_order = [
"FontInfo",
"FontName",
"Encoding",
"PaintType",
"FontType",
"FontMatrix",
"FontBBox",
"UniqueID",
"Metrics",
"StrokeWidth"
]
_type1_fontinfo_order = [
"version",
"Notice",
"FullName",
"FamilyName",
"Weight",
"ItalicAngle",
"isFixedPitch",
"UnderlinePosition",
"UnderlineThickness"
]
_type1_post_eexec_order = [
"Private",
"CharStrings",
"FID"
]
def _type1_item_repr(key, value):
psstring = ""
access = _accessstrings[value.access]
if access:
access = access + ' '
if key == 'CharStrings':
psstring = psstring + "/%s %s def\n" % (key, _type1_CharString_repr(value.value))
elif key == 'Encoding':
psstring = psstring + _type1_Encoding_repr(value, access)
else:
psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
return psstring
def _type1_Encoding_repr(encoding, access):
encoding = encoding.value
psstring = "/Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n"
for i in range(256):
name = encoding[i].value
if name <> '.notdef':
psstring = psstring + "dup %d /%s put\n" % (i, name)
return psstring + access + "def\n"
def _type1_CharString_repr(charstrings):
items = charstrings.items()
items.sort()
return 'xxx'
class ps_font(ps_object):
def __str__(self):
psstring = "%d dict dup begin\n" % len(self.value)
for key in _type1_pre_eexec_order:
try:
value = self.value[key]
except KeyError:
pass
else:
psstring = psstring + _type1_item_repr(key, value)
items = self.value.items()
items.sort()
for key, value in items:
if key not in _type1_pre_eexec_order + _type1_post_eexec_order:
psstring = psstring + _type1_item_repr(key, value)
psstring = psstring + "currentdict end\ncurrentfile eexec\ndup "
for key in _type1_post_eexec_order:
try:
value = self.value[key]
except KeyError:
pass
else:
psstring = psstring + _type1_item_repr(key, value)
return psstring + 'dup/FontName get exch definefont pop\nmark currentfile closefile\n' + \
8 * (64 * '0' + '\n') + 'cleartomark' + '\n'
def __repr__(self):
return '<font>'
class ps_file(ps_object):
pass
class ps_dict(ps_object):
def __str__(self):
psstring = "%d dict dup begin\n" % len(self.value)
items = self.value.items()
items.sort()
dictrepr = "%d dict dup begin\n" % len(items)
for key, value in items:
access = _accessstrings[value.access]
if access:
access = access + ' '
psstring = psstring + "/%s %s %sdef\n" % (str(key), str(value), access)
return psstring + 'end '
def __repr__(self):
return "<dict>"
class ps_mark(ps_object):
def __init__(self):
self.value = 'mark'
self.type = self.__class__.__name__[3:] + "type"
class ps_procmark(ps_object):
def __init__(self):
self.value = 'procmark'
self.type = self.__class__.__name__[3:] + "type"
class ps_null(ps_object):
def __init__(self):
self.type = self.__class__.__name__[3:] + "type"
class ps_boolean(ps_object):
def __str__(self):
if self.value:
return 'true'
else:
return 'false'
class ps_string(ps_object):
def __str__(self):
return "(%s)" % `self.value`[1:-1]
class ps_integer(ps_object):
def __str__(self):
return `self.value`
class ps_real(ps_object):
def __str__(self):
return `self.value`
class PSOperators:
def ps_def(self):
object = self.pop()
name = self.pop()
self.dictstack[-1][name.value] = object
def ps_bind(self):
proc = self.pop('proceduretype')
self.proc_bind(proc)
self.push(proc)
def proc_bind(self, proc):
for i in range(len(proc.value)):
item = proc.value[i]
if item.type == 'proceduretype':
self.proc_bind(item)
else:
if not item.literal:
try:
object = self.resolve_name(item.value)
except:
pass
else:
if object.type == 'operatortype':
proc.value[i] = object
def ps_exch(self):
if len(self.stack) < 2:
raise RuntimeError, 'stack underflow'
obj1 = self.pop()
obj2 = self.pop()
self.push(obj1)
self.push(obj2)
def ps_dup(self):
if not self.stack:
raise RuntimeError, 'stack underflow'
self.push(self.stack[-1])
def ps_exec(self):
object = self.pop()
if object.type == 'proceduretype':
self.call_procedure(object)
else:
self.handle_object(object)
def ps_count(self):
self.push(ps_integer(len(self.stack)))
def ps_eq(self):
any1 = self.pop()
any2 = self.pop()
self.push(ps_boolean(any1.value == any2.value))
def ps_ne(self):
any1 = self.pop()
any2 = self.pop()
self.push(ps_boolean(any1.value <> any2.value))
def ps_cvx(self):
obj = self.pop()
obj.literal = 0
self.push(obj)
def ps_matrix(self):
matrix = [ps_real(1.0), ps_integer(0), ps_integer(0), ps_real(1.0), ps_integer(0), ps_integer(0)]
self.push(ps_array(matrix))
def ps_string(self):
num = self.pop('integertype').value
self.push(ps_string('\0' * num))
def ps_type(self):
obj = self.pop()
self.push(ps_string(obj.type))
def ps_store(self):
value = self.pop()
key = self.pop()
name = key.value
for i in range(len(self.dictstack)-1, -1, -1):
if self.dictstack[i].has_key(name):
self.dictstack[i][name] = value
break
self.dictstack[-1][name] = value
def ps_where(self):
name = self.pop()
# XXX
self.push(ps_boolean(0))
def ps_systemdict(self):
self.push(ps_dict(self.dictstack[0]))
def ps_userdict(self):
self.push(ps_dict(self.dictstack[1]))
def ps_currentdict(self):
self.push(ps_dict(self.dictstack[-1]))
def ps_currentfile(self):
self.push(ps_file(self.tokenizer))
def ps_eexec(self):
file = self.pop('filetype').value
file.starteexec()
def ps_closefile(self):
file = self.pop('filetype').value
file.skipwhite()
file.stopeexec()
def ps_cleartomark(self):
obj = self.pop()
while obj <> self.mark:
obj = self.pop()
def ps_readstring(self,
ps_boolean = ps_boolean,
len = len):
string = self.pop('stringtype')
oldstr = string.value
file = self.pop('filetype')
#pad = file.value.read(1)
# for StringIO, this is faster
file.value.pos = file.value.pos + 1
newstr = file.value.read(len(oldstr))
string.value = newstr
self.push(string)
self.push(ps_boolean(len(oldstr) == len(newstr)))
def ps_known(self):
key = self.pop()
dict = self.pop('dicttype', 'fonttype')
self.push(ps_boolean(dict.value.has_key(key.value)))
def ps_if(self):
proc = self.pop('proceduretype')
bool = self.pop('booleantype')
if bool.value:
self.call_procedure(proc)
def ps_ifelse(self):
proc2 = self.pop('proceduretype')
proc1 = self.pop('proceduretype')
bool = self.pop('booleantype')
if bool.value:
self.call_procedure(proc1)
else:
self.call_procedure(proc2)
def ps_readonly(self):
obj = self.pop()
if obj.access < 1:
obj.access = 1
self.push(obj)
def ps_executeonly(self):
obj = self.pop()
if obj.access < 2:
obj.access = 2
self.push(obj)
def ps_noaccess(self):
obj = self.pop()
if obj.access < 3:
obj.access = 3
self.push(obj)
def ps_not(self):
obj = self.pop('booleantype', 'integertype')
if obj.type == 'booleantype':
self.push(ps_boolean(not obj.value))
else:
self.push(ps_integer(~obj.value))
def ps_print(self):
str = self.pop('stringtype')
print 'PS output --->', str.value
def ps_anchorsearch(self):
seek = self.pop('stringtype')
string = self.pop('stringtype')
seeklen = len(seek.value)
if string.value[:seeklen] == seek.value:
self.push(ps_string(string.value[seeklen:]))
self.push(seek)
self.push(ps_boolean(1))
else:
self.push(string)
self.push(ps_boolean(0))
def ps_array(self):
num = self.pop('integertype')
array = ps_array([None] * num.value)
self.push(array)
def ps_astore(self):
array = self.pop('arraytype')
for i in range(len(array.value)-1, -1, -1):
array.value[i] = self.pop()
self.push(array)
def ps_load(self):
name = self.pop()
object = self.resolve_name(name.value)
self.push(object)
def ps_put(self):
obj1 = self.pop()
obj2 = self.pop()
obj3 = self.pop('arraytype', 'dicttype', 'stringtype', 'proceduretype')
tp = obj3.type
if tp == 'arraytype' or tp == 'proceduretype':
obj3.value[obj2.value] = obj1
elif tp == 'dicttype':
obj3.value[obj2.value] = obj1
elif tp == 'stringtype':
index = obj2.value
obj3.value = obj3.value[:index] + chr(obj1.value) + obj3.value[index+1:]
def ps_get(self):
obj1 = self.pop()
if obj1.value == "Encoding":
pass
obj2 = self.pop('arraytype', 'dicttype', 'stringtype', 'proceduretype', 'fonttype')
tp = obj2.type
if tp in ('arraytype', 'proceduretype'):
self.push(obj2.value[obj1.value])
elif tp in ('dicttype', 'fonttype'):
self.push(obj2.value[obj1.value])
elif tp == 'stringtype':
self.push(ps_integer(ord(obj2.value[obj1.value])))
else:
assert 0, "shouldn't get here"
def ps_getinterval(self):
obj1 = self.pop('integertype')
obj2 = self.pop('integertype')
obj3 = self.pop('arraytype', 'stringtype')
tp = obj3.type
if tp == 'arraytype':
self.push(ps_array(obj3.value[obj2.value:obj2.value + obj1.value]))
elif tp == 'stringtype':
self.push(ps_string(obj3.value[obj2.value:obj2.value + obj1.value]))
def ps_putinterval(self):
obj1 = self.pop('arraytype', 'stringtype')
obj2 = self.pop('integertype')
obj3 = self.pop('arraytype', 'stringtype')
tp = obj3.type
if tp == 'arraytype':
obj3.value[obj2.value:obj2.value + len(obj1.value)] = obj1.value
elif tp == 'stringtype':
newstr = obj3.value[:obj2.value]
newstr = newstr + obj1.value
newstr = newstr + obj3.value[obj2.value + len(obj1.value):]
obj3.value = newstr
def ps_cvn(self):
str = self.pop('stringtype')
self.push(ps_name(str.value))
def ps_index(self):
n = self.pop('integertype').value
if n < 0:
raise RuntimeError, 'index may not be negative'
self.push(self.stack[-1-n])
def ps_for(self):
proc = self.pop('proceduretype')
limit = self.pop('integertype', 'realtype').value
increment = self.pop('integertype', 'realtype').value
i = self.pop('integertype', 'realtype').value
while 1:
if increment > 0:
if i > limit:
break
else:
if i < limit:
break
if type(i) == type(0.0):
self.push(ps_real(i))
else:
self.push(ps_integer(i))
self.call_procedure(proc)
i = i + increment
def ps_forall(self):
proc = self.pop('proceduretype')
obj = self.pop('arraytype', 'stringtype', 'dicttype')
tp = obj.type
if tp == 'arraytype':
for item in obj.value:
self.push(item)
self.call_procedure(proc)
elif tp == 'stringtype':
for item in obj.value:
self.push(ps_integer(ord(item)))
self.call_procedure(proc)
elif tp == 'dicttype':
for key, value in obj.value.items():
self.push(ps_name(key))
self.push(value)
self.call_procedure(proc)
def ps_definefont(self):
font = self.pop('dicttype')
name = self.pop()
font = ps_font(font.value)
self.dictstack[0]['FontDirectory'].value[name.value] = font
self.push(font)
def ps_findfont(self):
name = self.pop()
font = self.dictstack[0]['FontDirectory'].value[name.value]
self.push(font)
def ps_pop(self):
self.pop()
def ps_dict(self):
num = self.pop('integertype')
dict = ps_dict({})
self.push(dict)
def ps_begin(self):
dict = self.pop('dicttype')
self.dictstack.append(dict.value)
def ps_end(self):
if len(self.dictstack) > 2:
del self.dictstack[-1]
else:
raise RuntimeError, 'dictstack underflow'
notdef = '.notdef'
StandardEncoding = [ notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma',
'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six',
'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question',
'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'braceleft', 'bar', 'braceright', 'asciitilde', notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, 'exclamdown',
'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle',
'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', notdef,
'endash', 'dagger', 'daggerdbl', 'periodcentered', notdef, 'paragraph', 'bullet',
'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis',
'perthousand', notdef, 'questiondown', notdef, 'grave', 'acute', 'circumflex',
'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', notdef, 'ring', 'cedilla',
notdef, 'hungarumlaut', 'ogonek', 'caron', 'emdash', notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, notdef, notdef, notdef,
notdef, notdef, notdef, notdef, notdef, 'AE', notdef, 'ordfeminine',
notdef, notdef, notdef, notdef, 'Lslash', 'Oslash', 'OE', 'ordmasculine',
notdef, notdef, notdef, notdef, notdef, 'ae', notdef, notdef,
notdef, 'dotlessi', notdef, notdef, 'lslash', 'oslash', 'oe', 'germandbls',
notdef, notdef, notdef, notdef ]
ps_StandardEncoding = map(ps_name, StandardEncoding)

347
Lib/fontTools/t1Lib.py Normal file
View File

@ -0,0 +1,347 @@
"""fontTools.t1Lib.py -- Tools for PostScript Type 1 fonts
Functions for reading and writing raw Type 1 data:
read(path)
reads any Type 1 font file, returns the raw data and a type indicator:
'LWFN', 'PFB' or 'OTHER', depending on the format of the file pointed
to by 'path'.
Raises an error when the file does not contain valid Type 1 data.
write(path, data, kind = 'OTHER', dohex = 0)
writes raw Type 1 data to the file pointed to by 'path'.
'kind' can be one of 'LWFN', 'PFB' or 'OTHER'; it defaults to 'OTHER'.
'dohex' is a flag which determines whether the eexec encrypted
part should be written as hexadecimal or binary, but only if kind
is 'LWFN' or 'PFB'.
"""
__author__ = "jvr"
__version__ = "1.0b2"
DEBUG = 0
import eexec
import string
import re
import os
if os.name == 'mac':
import Res
import macfs
error = 't1Lib.error'
# work in progress
class T1Font:
"""Type 1 font class.
XXX This is work in progress! For now just use the read()
and write() functions as described above, they are stable.
"""
def __init__(self, path=None):
if path is not None:
self.data, type = read(path)
else:
pass # XXX
def saveAs(self, path, type):
self.write(path, self.getData(), type)
def getData(self):
return self.data
def __getitem__(self, key):
if not hasattr(self, "font"):
self.parse()
return self.font[key]
else:
return self.font[key]
def parse(self):
import psLib
import psCharStrings
self.font = psLib.suckfont(self.data)
charStrings = self.font["CharStrings"]
lenIV = self.font["Private"].get("lenIV", 4)
assert lenIV >= 0
for glyphName, charString in charStrings.items():
charString, R = eexec.Decrypt(charString, 4330)
charStrings[glyphName] = psCharStrings.T1CharString(charString[lenIV:])
subrs = self.font["Private"]["Subrs"]
for i in range(len(subrs)):
charString, R = eexec.Decrypt(subrs[i], 4330)
subrs[i] = psCharStrings.T1CharString(charString[lenIV:])
del self.data
# public functions
def read(path):
"""reads any Type 1 font file, returns raw data"""
normpath = string.lower(path)
if os.name == 'mac':
fss = macfs.FSSpec(path)
creator, type = fss.GetCreatorType()
if type == 'LWFN':
return readlwfn(path), 'LWFN'
if normpath[-4:] == '.pfb':
return readpfb(path), 'PFB'
else:
return readother(path), 'OTHER'
def write(path, data, kind='OTHER', dohex=0):
asserttype1(data)
kind = string.upper(kind)
try:
os.remove(path)
except os.error:
pass
err = 1
try:
if kind == 'LWFN':
writelwfn(path, data)
elif kind == 'PFB':
writepfb(path, data)
else:
writeother(path, data, dohex)
err = 0
finally:
if err and not DEBUG:
try:
os.remove(path)
except os.error:
pass
# -- internal --
LWFNCHUNKSIZE = 2000
HEXLINELENGTH = 80
def readlwfn(path):
"""reads an LWFN font file, returns raw data"""
resref = Res.OpenResFile(path)
try:
Res.UseResFile(resref)
n = Res.Count1Resources('POST')
data = []
for i in range(501, 501 + n):
res = Res.Get1Resource('POST', i)
code = ord(res.data[0])
if ord(res.data[1]) <> 0:
raise error, 'corrupt LWFN file'
if code in [1, 2]:
data.append(res.data[2:])
elif code in [3, 5]:
break
elif code == 4:
f = open(path, "rb")
data.append(f.read())
f.close()
elif code == 0:
pass # comment, ignore
else:
raise error, 'bad chunk code: ' + `code`
finally:
Res.CloseResFile(resref)
data = string.join(data, '')
asserttype1(data)
return data
def readpfb(path):
"""reads a PFB font file, returns raw data"""
f = open(path, "rb")
data = []
while 1:
if f.read(1) <> chr(128):
raise error, 'corrupt PFB file'
code = ord(f.read(1))
if code in [1, 2]:
chunklen = string2long(f.read(4))
data.append(f.read(chunklen))
elif code == 3:
break
else:
raise error, 'bad chunk code: ' + `code`
f.close()
data = string.join(data, '')
asserttype1(data)
return data
def readother(path):
"""reads any (font) file, returns raw data"""
f = open(path, "rb")
data = f.read()
f.close()
asserttype1(data)
chunks = findencryptedchunks(data)
data = []
for isencrypted, chunk in chunks:
if isencrypted and ishex(chunk[:4]):
data.append(dehexstring(chunk))
else:
data.append(chunk)
return string.join(data, '')
# file writing tools
def writelwfn(path, data):
Res.CreateResFile(path)
fss = macfs.FSSpec(path)
fss.SetCreatorType('just', 'LWFN')
resref = Res.OpenResFile(path)
try:
Res.UseResFile(resref)
resID = 501
chunks = findencryptedchunks(data)
for isencrypted, chunk in chunks:
if isencrypted:
code = 2
else:
code = 1
while chunk:
res = Res.Resource(chr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2])
res.AddResource('POST', resID, '')
chunk = chunk[LWFNCHUNKSIZE - 2:]
resID = resID + 1
res = Res.Resource(chr(5) + '\0')
res.AddResource('POST', resID, '')
finally:
Res.CloseResFile(resref)
def writepfb(path, data):
chunks = findencryptedchunks(data)
f = open(dstpath, "wb")
try:
for isencrypted, chunk in chunks:
if isencrypted:
code = 2
else:
code = 1
f.write(chr(128) + chr(code))
f.write(long2string(len(chunk)))
f.write(chunk)
f.write(chr(128) + chr(3))
finally:
f.close()
if os.name == 'mac':
fss = macfs.FSSpec(dstpath)
fss.SetCreatorType('mdos', 'BINA')
def writeother(path, data, dohex = 0):
chunks = findencryptedchunks(data)
f = open(path, "wb")
try:
hexlinelen = HEXLINELENGTH / 2
for isencrypted, chunk in chunks:
if isencrypted:
code = 2
else:
code = 1
if code == 2 and dohex:
while chunk:
f.write(eexec.hexstring(chunk[:hexlinelen]))
f.write('\r')
chunk = chunk[hexlinelen:]
else:
f.write(chunk)
finally:
f.close()
if os.name == 'mac':
fss = macfs.FSSpec(path)
fss.SetCreatorType('R*ch', 'TEXT') # BBEdit text file
# decryption tools
EEXECBEGIN = "currentfile eexec"
EEXECEND = '0' * 64
EEXECINTERNALEND = "currentfile closefile"
EEXECBEGINMARKER = "%-- eexec start\r"
EEXECENDMARKER = "%-- eexec end\r"
_ishexRE = re.compile('[0-9A-Fa-f]*$')
def ishex(text):
return _ishexRE.match(text) is not None
def decrypttype1(data):
chunks = findencryptedchunks(data)
data = []
for isencrypted, chunk in chunks:
if isencrypted:
if ishex(chunk[:4]):
chunk = dehexstring(chunk)
decrypted, R = eexec.Decrypt(chunk, 55665)
decrypted = decrypted[4:]
if decrypted[-len(EEXECINTERNALEND)-1:-1] <> EEXECINTERNALEND \
and decrypted[-len(EEXECINTERNALEND)-2:-2] <> EEXECINTERNALEND:
raise error, "invalid end of eexec part"
decrypted = decrypted[:-len(EEXECINTERNALEND)-2] + '\r'
data.append(EEXECBEGINMARKER + decrypted + EEXECENDMARKER)
else:
if chunk[-len(EEXECBEGIN)-1:-1] == EEXECBEGIN:
data.append(chunk[:-len(EEXECBEGIN)-1])
else:
data.append(chunk)
return string.join(data, '')
def findencryptedchunks(data):
chunks = []
while 1:
ebegin = string.find(data, EEXECBEGIN)
if ebegin < 0:
break
eend = string.find(data, EEXECEND, ebegin)
if eend < 0:
raise error, "can't find end of eexec part"
chunks.append((0, data[:ebegin + len(EEXECBEGIN) + 1]))
chunks.append((1, data[ebegin + len(EEXECBEGIN) + 1:eend]))
data = data[eend:]
chunks.append((0, data))
return chunks
def dehexstring(hexstring):
return eexec.dehexstring(string.join(string.split(hexstring), ""))
# Type 1 assertion
_fontType1RE = re.compile(r"/FontType\s+1\s+def")
def asserttype1(data):
for head in ['%!PS-AdobeFont', '%!FontType1-1.0']:
if data[:len(head)] == head:
break
else:
raise error, "not a PostScript font"
if not _fontType1RE.search(data):
raise error, "not a Type 1 font"
if string.find(data, "currentfile eexec") < 0:
raise error, "not an encrypted Type 1 font"
# XXX what else?
return data
# pfb helpers
def long2string(long):
str = ""
for i in range(4):
str = str + chr((long & (0xff << (i * 8))) >> i * 8)
return str
def string2long(str):
if len(str) <> 4:
raise ValueError, 'string must be 4 bytes long'
long = 0
for i in range(4):
long = long + (ord(str[i]) << (i * 8))
return long

View File

@ -0,0 +1,555 @@
"""ttLib -- a package for dealing with TrueType fonts.
This package offers translators to convert TrueType fonts to Python
objects and vice versa, and additionally from Python to XML and vice versa.
Example interactive session:
Python 1.5.2c1 (#43, Mar 9 1999, 13:06:43) [CW PPC w/GUSI w/MSL]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> from fontTools import ttLib
>>> tt = ttLib.TTFont("afont.ttf")
>>> tt['maxp'].numGlyphs
242
>>> tt['OS/2'].achVendID
'B&H\000'
>>> tt['head'].unitsPerEm
2048
>>> tt.saveXML("afont.xml")
Dumping 'LTSH' table...
Dumping 'OS/2' table...
Dumping 'VDMX' table...
Dumping 'cmap' table...
Dumping 'cvt ' table...
Dumping 'fpgm' table...
Dumping 'glyf' table...
Dumping 'hdmx' table...
Dumping 'head' table...
Dumping 'hhea' table...
Dumping 'hmtx' table...
Dumping 'loca' table...
Dumping 'maxp' table...
Dumping 'name' table...
Dumping 'post' table...
Dumping 'prep' table...
>>> tt2 = ttLib.TTFont()
>>> tt2.importXML("afont.xml")
>>> tt2['maxp'].numGlyphs
242
>>>
"""
__author__ = "Just van Rossum, just@letterror.com"
__version__ = "1.0a5"
import os
import stat
import types
class TTLibError(Exception): pass
class TTFont:
"""The main font object. It manages file input and output, and offers
a convenient way of accessing tables.
Tables will be only decompiled when neccesary, ie. when they're actually
accessed. This means that simple operations can be extremely fast.
"""
def __init__(self, file=None, res_name_or_index=None,
sfntVersion="\000\001\000\000", checkchecksums=0, verbose=0):
"""The constructor can be called with a few different arguments.
When reading a font from disk, 'file' should be either a pathname
pointing to a file, or a readable file object.
It we're running on a Macintosh, 'res_name_or_index' maybe an sfnt
resource name or an sfnt resource index number or zero. The latter
case will cause TTLib to autodetect whether the file is a flat file
or a suitcase. (If it's a suitcase, only the first 'sfnt' resource
will be read!)
The 'checkchecksums' argument is used to specify how sfnt
checksums are treated upon reading a file from disk:
0: don't check (default)
1: check, print warnings if a wrong checksum is found (default)
2: check, raise an exception if a wrong checksum is found.
The TTFont constructor can also be called without a 'file'
argument: this is the way to create a new empty font.
In this case you can optionally supply the 'sfntVersion' argument.
"""
import sfnt
self.verbose = verbose
self.tables = {}
self.reader = None
if not file:
self.sfntVersion = sfntVersion
return
if type(file) == types.StringType:
if os.name == "mac" and res_name_or_index is not None:
# on the mac, we deal with sfnt resources as well as flat files
import macUtils
if res_name_or_index == 0:
if macUtils.getSFNTResIndices(file):
# get the first available sfnt font.
file = macUtils.SFNTResourceReader(file, 1)
else:
file = open(file, "rb")
else:
file = macUtils.SFNTResourceReader(file, res_name_or_index)
else:
file = open(file, "rb")
else:
pass # assume "file" is a readable file object
self.reader = sfnt.SFNTReader(file, checkchecksums)
self.sfntVersion = self.reader.sfntVersion
def close(self):
"""If we still have a reader object, close it."""
if self.reader is not None:
self.reader.close()
def save(self, file, make_suitcase=0):
"""Save the font to disk. Similarly to the constructor,
the 'file' argument can be either a pathname or a writable
file object.
On the Mac, if make_suitcase is non-zero, a suitcase file will
we made instead of a flat .ttf file.
"""
import sfnt
if type(file) == types.StringType:
if os.name == "mac" and make_suitcase:
import macUtils
file = macUtils.SFNTResourceWriter(file, self)
else:
file = open(file, "wb")
if os.name == "mac":
import macfs
fss = macfs.FSSpec(file.name)
fss.SetCreatorType('mdos', 'BINA')
else:
pass # assume "file" is a writable file object
tags = self.keys()
numTables = len(tags)
writer = sfnt.SFNTWriter(file, numTables, self.sfntVersion)
done = []
for tag in tags:
self._writeTable(tag, writer, done)
writer.close()
def saveXML(self, file, progress=None, tables=None):
"""Export the font as an XML-based text file.
"""
import xmlWriter
writer = xmlWriter.XMLWriter(file)
writer.begintag("ttFont", sfntVersion=`self.sfntVersion`[1:-1],
ttlibVersion=__version__)
writer.newline()
writer.newline()
if not tables:
tables = self.keys()
numTables = len(tables)
numGlyphs = self['maxp'].numGlyphs
if progress:
progress.set(0, numTables * numGlyphs)
for i in range(numTables):
tag = tables[i]
table = self[tag]
report = "Dumping '%s' table..." % tag
if progress:
progress.setlabel(report)
elif self.verbose:
debugmsg(report)
else:
print report
xmltag = tag2xmltag(tag)
writer.begintag(xmltag)
writer.newline()
if tag == "glyf":
table.toXML(writer, self, progress)
elif tag == "CFF ":
table.toXML(writer, self, progress)
else:
table.toXML(writer, self)
writer.endtag(xmltag)
writer.newline()
writer.newline()
if progress:
progress.set(i * numGlyphs, numTables * numGlyphs)
writer.endtag("ttFont")
writer.newline()
writer.close()
if self.verbose:
debugmsg("Done dumping XML")
def importXML(self, file, progress=None):
"""Import an XML-based text file, so as to recreate
a font object.
"""
if self.tables:
raise error, "Can't import XML into existing font."
import xmlImport
from xml.parsers.xmlproc import xmlproc
builder = xmlImport.XMLApplication(self, progress)
if progress:
progress.set(0, os.stat(file)[stat.ST_SIZE] / 100 or 1)
proc = xmlImport.UnicodeProcessor()
proc.set_application(builder)
proc.set_error_handler(xmlImport.XMLErrorHandler(proc))
dir, filename = os.path.split(file)
if dir:
olddir = os.getcwd()
os.chdir(dir)
try:
proc.parse_resource(filename)
root = builder.root
finally:
if dir:
os.chdir(olddir)
# remove circular references
proc.deref()
del builder.progress
def isLoaded(self, tag):
"""Return true if the table identified by 'tag' has been
decompiled and loaded into memory."""
return self.tables.has_key(tag)
def has_key(self, tag):
"""Pretend we're a dictionary."""
if self.isLoaded(tag):
return 1
elif self.reader and self.reader.has_key(tag):
return 1
else:
return 0
def keys(self):
"""Pretend we're a dictionary."""
keys = self.tables.keys()
if self.reader:
for key in self.reader.keys():
if key not in keys:
keys.append(key)
keys.sort()
return keys
def __len__(self):
"""Pretend we're a dictionary."""
return len(self.keys())
def __getitem__(self, tag):
"""Pretend we're a dictionary."""
try:
return self.tables[tag]
except KeyError:
if self.reader is not None:
if self.verbose:
debugmsg("reading '%s' table from disk" % tag)
data = self.reader[tag]
tableclass = getTableClass(tag)
table = tableclass(tag)
self.tables[tag] = table
if self.verbose:
debugmsg("decompiling '%s' table" % tag)
table.decompile(data, self)
return table
else:
raise KeyError, "'%s' table not found" % tag
def __setitem__(self, tag, table):
"""Pretend we're a dictionary."""
self.tables[tag] = table
def __delitem__(self, tag):
"""Pretend we're a dictionary."""
del self.tables[tag]
def setGlyphOrder(self, glyphOrder):
self.glyphOrder = glyphOrder
if self.has_key('CFF '):
self['CFF '].setGlyphOrder(glyphOrder)
if self.has_key('glyf'):
self['glyf'].setGlyphOrder(glyphOrder)
def getGlyphOrder(self):
if not hasattr(self, "glyphOrder"):
if self.has_key('CFF '):
# CFF OpenType font
self.glyphOrder = self['CFF '].getGlyphOrder()
else:
# TrueType font
glyphOrder = self['post'].getGlyphOrder()
if glyphOrder is None:
#
# No names found in the 'post' table.
# Try to create glyph names from the unicode cmap (if available)
# in combination with the Adobe Glyph List (AGL).
#
self._getGlyphNamesFromCmap()
else:
self.glyphOrder = glyphOrder
# XXX what if a font contains 'glyf'/'post' table *and* CFF?
return self.glyphOrder
def _getGlyphNamesFromCmap(self):
# Make up glyph names based on glyphID, which will be used
# in case we don't find a unicode cmap.
numGlyphs = int(self['maxp'].numGlyphs)
glyphOrder = [None] * numGlyphs
glyphOrder[0] = ".notdef"
for i in range(1, numGlyphs):
glyphOrder[i] = "glyph%.5d" % i
# Set the glyph order, so the cmap parser has something
# to work with
self.glyphOrder = glyphOrder
# Get the temporary cmap (based on the just invented names)
tempcmap = self['cmap'].getcmap(3, 1)
if tempcmap is not None:
# we have a unicode cmap
import agl, string
cmap = tempcmap.cmap
# create a reverse cmap dict
reversecmap = {}
for unicode, name in cmap.items():
reversecmap[name] = unicode
assert len(reversecmap) == len(cmap)
for i in range(numGlyphs):
tempName = glyphOrder[i]
if reversecmap.has_key(tempName):
unicode = reversecmap[tempName]
if agl.UV2AGL.has_key(unicode):
# get name from the Adobe Glyph List
glyphOrder[i] = agl.UV2AGL[unicode]
else:
# create uni<CODE> name
glyphOrder[i] = "uni" + string.upper(string.zfill(hex(unicode)[2:], 4))
# Delete the cmap table from the cache, so it can be
# parsed again with the right names.
del self.tables['cmap']
else:
pass # no unicode cmap available, stick with the invented names
self.glyphOrder = glyphOrder
def getGlyphNames(self):
"""Get a list of glyph names, sorted alphabetically."""
glyphNames = self.getGlyphOrder()[:]
glyphNames.sort()
return glyphNames
def getGlyphNames2(self):
"""Get a list of glyph names, sorted alphabetically, but not case sensitive."""
from fontTools.misc import textTools
return textTools.caselessSort(self.getGlyphOrder())
def getGlyphName(self, glyphID):
return self.getGlyphOrder()[glyphID]
def getGlyphID(self, glyphName):
if not hasattr(self, "_reverseGlyphOrderDict"):
self._buildReverseGlyphOrderDict()
glyphOrder = self.getGlyphOrder()
d = self._reverseGlyphOrderDict
if not d.has_key(glyphName):
if glyphName in glyphOrder:
self._buildReverseGlyphOrderDict()
return self.getGlyphID(glyphName)
else:
raise KeyError, glyphName
glyphID = d[glyphName]
if glyphName <> glyphOrder[glyphID]:
self._buildReverseGlyphOrderDict()
return self.getGlyphID(glyphName)
return glyphID
def _buildReverseGlyphOrderDict(self):
self._reverseGlyphOrderDict = d = {}
glyphOrder = self.getGlyphOrder()
for glyphID in range(len(glyphOrder)):
d[glyphOrder[glyphID]] = glyphID
def _writeTable(self, tag, writer, done):
"""Internal helper function for self.save(). Keeps track of
inter-table dependencies.
"""
if tag in done:
return
tableclass = getTableClass(tag)
for masterTable in tableclass.dependencies:
if masterTable not in done:
if self.has_key(masterTable):
self._writeTable(masterTable, writer, done)
else:
done.append(masterTable)
tabledata = self._getTableData(tag)
if self.verbose:
debugmsg("writing '%s' table to disk" % tag)
writer[tag] = tabledata
done.append(tag)
def _getTableData(self, tag):
"""Internal helper function. Returns raw table data,
whether compiled or directly read from disk.
"""
if self.isLoaded(tag):
if self.verbose:
debugmsg("compiling '%s' table" % tag)
return self.tables[tag].compile(self)
elif self.reader and self.reader.has_key(tag):
if self.verbose:
debugmsg("reading '%s' table from disk" % tag)
return self.reader[tag]
else:
raise KeyError, tag
def _test_endianness():
"""Test the endianness of the machine. This is crucial to know
since TrueType data is always big endian, even on little endian
machines. There are quite a few situations where we explicitly
need to swap some bytes.
"""
import struct
data = struct.pack("h", 0x01)
if data == "\000\001":
return "big"
elif data == "\001\000":
return "little"
else:
assert 0, "endian confusion!"
endian = _test_endianness()
def getTableModule(tag):
"""Fetch the packer/unpacker module for a table.
Return None when no module is found.
"""
import imp
import tables
py_tag = tag2identifier(tag)
try:
f, path, kind = imp.find_module(py_tag, tables.__path__)
if f:
f.close()
except ImportError:
return None
else:
module = __import__("fontTools.ttLib.tables." + py_tag)
return getattr(tables, py_tag)
def getTableClass(tag):
"""Fetch the packer/unpacker class for a table.
Return None when no class is found.
"""
module = getTableModule(tag)
if module is None:
from tables.DefaultTable import DefaultTable
return DefaultTable
py_tag = tag2identifier(tag)
tableclass = getattr(module, "table_" + py_tag)
return tableclass
def newtable(tag):
"""Return a new instance of a table."""
tableclass = getTableClass(tag)
return tableclass(tag)
def _escapechar(c):
"""Helper function for tag2identifier()"""
import re
if re.match("[a-z0-9]", c):
return "_" + c
elif re.match("[A-Z]", c):
return c + "_"
else:
return hex(ord(c))[2:]
def tag2identifier(tag):
"""Convert a table tag to a valid (but UGLY) python identifier,
as well as a filename that's guaranteed to be unique even on a
caseless file system. Each character is mapped to two characters.
Lowercase letters get an underscore before the letter, uppercase
letters get an underscore after the letter. Trailing spaces are
trimmed. Illegal characters are escaped as two hex bytes. If the
result starts with a number (as the result of a hex escape), an
extra underscore is prepended. Examples:
'glyf' -> '_g_l_y_f'
'cvt ' -> '_c_v_t'
'OS/2' -> 'O_S_2f_2'
"""
import re
assert len(tag) == 4, "tag should be 4 characters long"
while len(tag) > 1 and tag[-1] == ' ':
tag = tag[:-1]
ident = ""
for c in tag:
ident = ident + _escapechar(c)
if re.match("[0-9]", ident):
ident = "_" + ident
return ident
def identifier2tag(ident):
"""the opposite of tag2identifier()"""
import string
if len(ident) % 2 and ident[0] == "_":
ident = ident[1:]
assert not (len(ident) % 2)
tag = ""
for i in range(0, len(ident), 2):
if ident[i] == "_":
tag = tag + ident[i+1]
elif ident[i+1] == "_":
tag = tag + ident[i]
else:
# assume hex
tag = tag + chr(string.atoi(ident[i:i+2], 16))
# append trailing spaces
tag = tag + (4 - len(tag)) * ' '
return tag
def tag2xmltag(tag):
"""Similarly to tag2identifier(), this converts a TT tag
to a valid XML element name. Since XML element names are
case sensitive, this is a fairly simple/readable translation.
"""
import string, re
if tag == "OS/2":
return "OS_2"
if re.match("[A-Za-z_][A-Za-z_0-9]* *$", tag):
return string.strip(tag)
else:
return tag2identifier(tag)
def xmltag2tag(tag):
"""The opposite of tag2xmltag()"""
if tag == "OS_2":
return "OS/2"
if len(tag) == 8:
return identifier2tag(tag)
else:
return tag + " " * (4 - len(tag))
return tag
def debugmsg(msg):
import time
print msg + time.strftime(" (%H:%M:%S)", time.localtime(time.time()))

View File

@ -0,0 +1,212 @@
"""ttLib.macUtils.py -- Various Mac-specific stuff."""
import os
if os.name <> "mac":
raise ImportError, "This module is Mac-only!"
import Res, macfs
import cStringIO
def getSFNTResIndices(path):
"""Determine whether a file has a resource fork or not."""
fss = macfs.FSSpec(path)
try:
resref = Res.FSpOpenResFile(fss, 1) # read only
except Res.Error:
return []
Res.UseResFile(resref)
numSFNTs = Res.Count1Resources('sfnt')
Res.CloseResFile(resref)
return range(1, numSFNTs + 1)
def openTTFonts(path):
"""Given a pathname, return a list of TTFont objects. In the case
of a flat TTF/OTF file, the list will contain just one font object;
but in the case of a Mac font suitcase it will contain as many
font objects as there are sfnt resources in the file.
"""
from fontTools import ttLib
fonts = []
sfnts = getSFNTResIndices(path)
if not sfnts:
fonts.append(ttLib.TTFont(path))
else:
for index in sfnts:
fonts.append(ttLib.TTFont(path, index))
if not fonts:
raise ttLib.TTLibError, "no fonts found in file '%s'" % path
return fonts
class ProgressBar:
def __init__(self, title, maxval=100):
import EasyDialogs
self.bar = EasyDialogs.ProgressBar(title, maxval=maxval)
def set(self, val, maxval=None):
if maxval <> None:
self.bar.maxval = maxval
self.bar.set(val)
def increment(self, val=1):
self.bar.inc(val)
def setlabel(self, text):
self.bar.label(text)
def close(self):
self.bar.d.HideWindow()
del self.bar
class SFNTResourceReader:
"""Simple (Mac-only) read-only file wrapper for 'sfnt' resources."""
def __init__(self, path, res_name_or_index):
fss = macfs.FSSpec(path)
resref = Res.FSpOpenResFile(fss, 1) # read-only
Res.UseResFile(resref)
if type(res_name_or_index) == type(""):
res = Res.Get1NamedResource('sfnt', res_name_or_index)
else:
res = Res.Get1IndResource('sfnt', res_name_or_index)
self.file = cStringIO.StringIO(res.data)
Res.CloseResFile(resref)
self.name = path
def __getattr__(self, attr):
# cheap inheritance
return getattr(self.file, attr)
class SFNTResourceWriter:
"""Simple (Mac-only) file wrapper for 'sfnt' resources."""
def __init__(self, path, ttFont, res_id=None):
self.file = cStringIO.StringIO()
self.name = path
self.closed = 0
fullname = ttFont['name'].getname(4, 1, 0) # Full name, mac, default encoding
familyname = ttFont['name'].getname(1, 1, 0) # Fam. name, mac, default encoding
psname = ttFont['name'].getname(6, 1, 0) # PostScript name, etc.
if fullname is None or fullname is None or psname is None:
from fontTools import ttLib
raise ttLib.TTLibError, "can't make 'sfnt' resource, no Macintosh 'name' table found"
self.fullname = fullname.string
self.familyname = familyname.string
self.psname = psname.string
if self.familyname <> self.psname[:len(self.familyname)]:
# ugh. force fam name to be the same as first part of ps name,
# fondLib otherwise barfs.
for i in range(min(len(self.psname), len(self.familyname))):
if self.familyname[i] <> self.psname[i]:
break
self.familyname = self.psname[:i]
self.ttFont = ttFont
self.res_id = res_id
fss = macfs.FSSpec(self.name)
if os.path.exists(self.name):
os.remove(self.name)
Res.FSpCreateResFile(fss, 'DMOV', 'FFIL', 0)
self.resref = Res.FSpOpenResFile(fss, 3) # exclusive read/write permission
def close(self):
if self.closed:
return
Res.UseResFile(self.resref)
try:
res = Res.Get1NamedResource('sfnt', self.fullname)
except Res.Error:
pass
else:
res.RemoveResource()
res = Res.Resource(self.file.getvalue())
if self.res_id is None:
self.res_id = Res.Unique1ID('sfnt')
res.AddResource('sfnt', self.res_id, self.fullname)
res.ChangedResource()
self.createFond()
del self.ttFont
Res.CloseResFile(self.resref)
self.file.close()
self.closed = 1
def createFond(self):
fond_res = Res.Resource("")
fond_res.AddResource('FOND', self.res_id, self.fullname)
from fontTools import fondLib
fond = fondLib.FontFamily(fond_res, "w")
fond.ffFirstChar = 0
fond.ffLastChar = 255
fond.fondClass = 0
fond.fontAssoc = [(0, 0, self.res_id)]
fond.ffFlags = 20480 # XXX ???
fond.ffIntl = (0, 0)
fond.ffLeading = 0
fond.ffProperty = (0, 0, 0, 0, 0, 0, 0, 0, 0)
fond.ffVersion = 0
fond.glyphEncoding = {}
if self.familyname == self.psname:
fond.styleIndices = (1,) * 48 # uh-oh, fondLib is too dumb.
else:
fond.styleIndices = (2,) * 48
fond.styleStrings = []
fond.boundingBoxes = None
fond.ffFamID = self.res_id
fond.changed = 1
fond.glyphTableOffset = 0
fond.styleMappingReserved = 0
# calc:
scale = 4096.0 / self.ttFont['head'].unitsPerEm
fond.ffAscent = scale * self.ttFont['hhea'].ascent
fond.ffDescent = scale * self.ttFont['hhea'].descent
fond.ffWidMax = scale * self.ttFont['hhea'].advanceWidthMax
fond.ffFamilyName = self.familyname
fond.psNames = {0: self.psname}
fond.widthTables = {}
fond.kernTables = {}
cmap = self.ttFont['cmap'].getcmap(1, 0)
if cmap:
names = {}
for code, name in cmap.cmap.items():
names[name] = code
if self.ttFont.has_key('kern'):
kern = self.ttFont['kern'].getkern(0)
if kern:
fondkerning = []
for (left, right), value in kern.kernTable.items():
if names.has_key(left) and names.has_key(right):
fondkerning.append((names[left], names[right], scale * value))
fondkerning.sort()
fond.kernTables = {0: fondkerning}
if self.ttFont.has_key('hmtx'):
hmtx = self.ttFont['hmtx']
fondwidths = [2048] * 256 + [0, 0] # default width, + plus two zeros.
for name, (width, lsb) in hmtx.metrics.items():
if names.has_key(name):
fondwidths[names[name]] = scale * width
fond.widthTables = {0: fondwidths}
fond.save()
def __del__(self):
if not self.closed:
self.close()
def __getattr__(self, attr):
# cheap inheritance
return getattr(self.file, attr)

230
Lib/fontTools/ttLib/sfnt.py Normal file
View File

@ -0,0 +1,230 @@
"""ttLib/sfnt.py -- low-level module to deal with the sfnt file format.
Defines two public classes:
SFNTReader
SFNTWriter
(Normally you don't have to use these classes explicitly; they are
used automatically by ttLib.TTFont.)
The reading and writing of sfnt files is separated in two distinct
classes, since whenever to number of tables changes or whenever
a table's length chages you need to rewrite the whole file anyway.
"""
import struct, sstruct
import Numeric
import os
class SFNTReader:
def __init__(self, file, checkchecksums=1):
self.file = file
self.checkchecksums = checkchecksums
data = self.file.read(sfntDirectorySize)
if len(data) <> sfntDirectorySize:
from fontTools import ttLib
raise ttLib.TTLibError, "Not a TrueType or OpenType font (not enough data)"
sstruct.unpack(sfntDirectoryFormat, data, self)
if self.sfntVersion not in ("\000\001\000\000", "OTTO", "true"):
from fontTools import ttLib
raise ttLib.TTLibError, "Not a TrueType or OpenType font (bad sfntVersion)"
self.tables = {}
for i in range(self.numTables):
entry = SFNTDirectoryEntry()
entry.fromfile(self.file)
self.tables[entry.tag] = entry
def has_key(self, tag):
return self.tables.has_key(tag)
def keys(self):
return self.tables.keys()
def __getitem__(self, tag):
"""Fetch the raw table data."""
entry = self.tables[tag]
self.file.seek(entry.offset)
data = self.file.read(entry.length)
if self.checkchecksums:
if tag == 'head':
# Beh: we have to special-case the 'head' table.
checksum = calcchecksum(data[:8] + '\0\0\0\0' + data[12:])
else:
checksum = calcchecksum(data)
if self.checkchecksums > 1:
# Be obnoxious, and barf when it's wrong
assert checksum == entry.checksum, "bad checksum for '%s' table" % tag
elif checksum <> entry.checkSum:
# Be friendly, and just print a warning.
print "bad checksum for '%s' table" % tag
return data
def close(self):
self.file.close()
class SFNTWriter:
def __init__(self, file, numTables, sfntVersion="\000\001\000\000"):
self.file = file
self.numTables = numTables
self.sfntVersion = sfntVersion
self.searchRange, self.entrySelector, self.rangeShift = getsearchrange(numTables)
self.nextTableOffset = sfntDirectorySize + numTables * sfntDirectoryEntrySize
# clear out directory area
self.file.seek(self.nextTableOffset)
# make sure we're actually where we want to be. (XXX old cStringIO bug)
self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
self.tables = {}
def __setitem__(self, tag, data):
"""Write raw table data to disk."""
if self.tables.has_key(tag):
# We've written this table to file before. If the length
# of the data is still the same, we allow overwritng it.
entry = self.tables[tag]
if len(data) <> entry.length:
from fontTools import ttLib
raise ttLib.TTLibError, "cannot rewrite '%s' table: length does not match directory entry" % tag
else:
entry = SFNTDirectoryEntry()
entry.tag = tag
entry.offset = self.nextTableOffset
entry.length = len(data)
self.nextTableOffset = self.nextTableOffset + ((len(data) + 3) & ~3)
self.file.seek(entry.offset)
self.file.write(data)
self.file.seek(self.nextTableOffset)
# make sure we're actually where we want to be. (XXX old cStringIO bug)
self.file.write('\0' * (self.nextTableOffset - self.file.tell()))
if tag == 'head':
entry.checkSum = calcchecksum(data[:8] + '\0\0\0\0' + data[12:])
else:
entry.checkSum = calcchecksum(data)
self.tables[tag] = entry
def close(self):
"""All tables must have been written to disk. Now write the
directory.
"""
tables = self.tables.items()
tables.sort()
if len(tables) <> self.numTables:
from fontTools import ttLib
raise ttLib.TTLibError, "wrong number of tables; expected %d, found %d" % (self.numTables, len(tables))
directory = sstruct.pack(sfntDirectoryFormat, self)
self.file.seek(sfntDirectorySize)
for tag, entry in tables:
directory = directory + entry.tostring()
self.calcmasterchecksum(directory)
self.file.seek(0)
self.file.write(directory)
self.file.close()
def calcmasterchecksum(self, directory):
# calculate checkSumAdjustment
tags = self.tables.keys()
checksums = Numeric.zeros(len(tags)+1)
for i in range(len(tags)):
checksums[i] = self.tables[tags[i]].checkSum
directory_end = sfntDirectorySize + len(self.tables) * sfntDirectoryEntrySize
assert directory_end == len(directory)
checksums[-1] = calcchecksum(directory)
checksum = Numeric.add.reduce(checksums)
# BiboAfba!
checksumadjustment = Numeric.array(0xb1b0afba) - checksum
# write the checksum to the file
self.file.seek(self.tables['head'].offset + 8)
self.file.write(struct.pack("l", checksumadjustment))
# -- sfnt directory helpers and cruft
sfntDirectoryFormat = """
> # big endian
sfntVersion: 4s
numTables: H # number of tables
searchRange: H # (max2 <= numTables)*16
entrySelector: H # log2(max2 <= numTables)
rangeShift: H # numTables*16-searchRange
"""
sfntDirectorySize = sstruct.calcsize(sfntDirectoryFormat)
sfntDirectoryEntryFormat = """
> # big endian
tag: 4s
checkSum: l
offset: l
length: l
"""
sfntDirectoryEntrySize = sstruct.calcsize(sfntDirectoryEntryFormat)
class SFNTDirectoryEntry:
def fromfile(self, file):
sstruct.unpack(sfntDirectoryEntryFormat,
file.read(sfntDirectoryEntrySize), self)
def fromstring(self, str):
sstruct.unpack(sfntDirectoryEntryFormat, str, self)
def tostring(self):
return sstruct.pack(sfntDirectoryEntryFormat, self)
def __repr__(self):
if hasattr(self, "tag"):
return "<SFNTDirectoryEntry '%s' at %x>" % (self.tag, id(self))
else:
return "<SFNTDirectoryEntry at %x>" % id(self)
def calcchecksum(data, start=0):
"""Calculate the checksum for an arbitrary block of data.
Optionally takes a 'start' argument, which allows you to
calculate a checksum in chunks by feeding it a previous
result.
If the data length is not a multiple of four, it assumes
it is to be padded with null byte.
"""
from fontTools import ttLib
remainder = len(data) % 4
if remainder:
data = data + '\0' * (4-remainder)
a = Numeric.fromstring(struct.pack(">l", start) + data, Numeric.Int32)
if ttLib.endian <> "big":
a = a.byteswapped()
return Numeric.add.reduce(a)
def maxpoweroftwo(x):
"""Return the highest exponent of two, so that
(2 ** exponent) <= x
"""
exponent = 0
while x:
x = x >> 1
exponent = exponent + 1
return exponent - 1
def getsearchrange(n):
"""Calculate searchRange, entrySelector, rangeShift for the
sfnt directory. 'n' is the number of tables.
"""
# This stuff needs to be stored in the file, because?
import math
exponent = maxpoweroftwo(n)
searchRange = (2 ** exponent) * 16
entrySelector = exponent
rangeShift = n * 16 - searchRange
return searchRange, entrySelector, rangeShift

View File

@ -0,0 +1,263 @@
# XXX is this *really* correct?
standardGlyphOrder = [
".notdef", # 0
".null", # 1
"nonmarkingreturn", # 2
"space", # 3
"exclam", # 4
"quotedbl", # 5
"numbersign", # 6
"dollar", # 7
"percent", # 8
"ampersand", # 9
"quotesingle", # 10
"parenleft", # 11
"parenright", # 12
"asterisk", # 13
"plus", # 14
"comma", # 15
"hyphen", # 16
"period", # 17
"slash", # 18
"zero", # 19
"one", # 20
"two", # 21
"three", # 22
"four", # 23
"five", # 24
"six", # 25
"seven", # 26
"eight", # 27
"nine", # 28
"colon", # 29
"semicolon", # 30
"less", # 31
"equal", # 32
"greater", # 33
"question", # 34
"at", # 35
"A", # 36
"B", # 37
"C", # 38
"D", # 39
"E", # 40
"F", # 41
"G", # 42
"H", # 43
"I", # 44
"J", # 45
"K", # 46
"L", # 47
"M", # 48
"N", # 49
"O", # 50
"P", # 51
"Q", # 52
"R", # 53
"S", # 54
"T", # 55
"U", # 56
"V", # 57
"W", # 58
"X", # 59
"Y", # 60
"Z", # 61
"bracketleft", # 62
"backslash", # 63
"bracketright", # 64
"asciicircum", # 65
"underscore", # 66
"grave", # 67
"a", # 68
"b", # 69
"c", # 70
"d", # 71
"e", # 72
"f", # 73
"g", # 74
"h", # 75
"i", # 76
"j", # 77
"k", # 78
"l", # 79
"m", # 80
"n", # 81
"o", # 82
"p", # 83
"q", # 84
"r", # 85
"s", # 86
"t", # 87
"u", # 88
"v", # 89
"w", # 90
"x", # 91
"y", # 92
"z", # 93
"braceleft", # 94
"bar", # 95
"braceright", # 96
"asciitilde", # 97
"Adieresis", # 98
"Aring", # 99
"Ccedilla", # 100
"Eacute", # 101
"Ntilde", # 102
"Odieresis", # 103
"Udieresis", # 104
"aacute", # 105
"agrave", # 106
"acircumflex", # 107
"adieresis", # 108
"atilde", # 109
"aring", # 110
"ccedilla", # 111
"eacute", # 112
"egrave", # 113
"ecircumflex", # 114
"edieresis", # 115
"iacute", # 116
"igrave", # 117
"icircumflex", # 118
"idieresis", # 119
"ntilde", # 120
"oacute", # 121
"ograve", # 122
"ocircumflex", # 123
"odieresis", # 124
"otilde", # 125
"uacute", # 126
"ugrave", # 127
"ucircumflex", # 128
"udieresis", # 129
"dagger", # 130
"degree", # 131
"cent", # 132
"sterling", # 133
"section", # 134
"bullet", # 135
"paragraph", # 136
"germandbls", # 137
"registered", # 138
"copyright", # 139
"trademark", # 140
"acute", # 141
"dieresis", # 142
"notequal", # 143
"AE", # 144
"Oslash", # 145
"infinity", # 146
"plusminus", # 147
"lessequal", # 148
"greaterequal", # 149
"yen", # 150
"mu", # 151
"partialdiff", # 152
"summation", # 153
"product", # 154
"pi", # 155
"integral", # 156
"ordfeminine", # 157
"ordmasculine", # 158
"Omega", # 159
"ae", # 160
"oslash", # 161
"questiondown", # 162
"exclamdown", # 163
"logicalnot", # 164
"radical", # 165
"florin", # 166
"approxequal", # 167
"Delta", # 168
"guillemotleft", # 169
"guillemotright", # 170
"ellipsis", # 171
"nonbreakingspace", # 172
"Agrave", # 173
"Atilde", # 174
"Otilde", # 175
"OE", # 176
"oe", # 177
"endash", # 178
"emdash", # 179
"quotedblleft", # 180
"quotedblright", # 181
"quoteleft", # 182
"quoteright", # 183
"divide", # 184
"lozenge", # 185
"ydieresis", # 186
"Ydieresis", # 187
"fraction", # 188
"currency", # 189
"guilsinglleft", # 190
"guilsinglright", # 191
"fi", # 192
"fl", # 193
"daggerdbl", # 194
"periodcentered", # 195
"quotesinglbase", # 196
"quotedblbase", # 197
"perthousand", # 198
"Acircumflex", # 199
"Ecircumflex", # 200
"Aacute", # 201
"Edieresis", # 202
"Egrave", # 203
"Iacute", # 204
"Icircumflex", # 205
"Idieresis", # 206
"Igrave", # 207
"Oacute", # 208
"Ocircumflex", # 209
"apple", # 210
"Ograve", # 211
"Uacute", # 212
"Ucircumflex", # 213
"Ugrave", # 214
"dotlessi", # 215
"circumflex", # 216
"tilde", # 217
"macron", # 218
"breve", # 219
"dotaccent", # 220
"ring", # 221
"cedilla", # 222
"hungarumlaut", # 223
"ogonek", # 224
"caron", # 225
"Lslash", # 226
"lslash", # 227
"Scaron", # 228
"scaron", # 229
"Zcaron", # 230
"zcaron", # 231
"brokenbar", # 232
"Eth", # 233
"eth", # 234
"Yacute", # 235
"yacute", # 236
"Thorn", # 237
"thorn", # 238
"minus", # 239
"multiply", # 240
"onesuperior", # 241
"twosuperior", # 242
"threesuperior", # 243
"onehalf", # 244
"onequarter", # 245
"threequarters", # 246
"franc", # 247
"Gbreve", # 248
"gbreve", # 249
"Idotaccent", # 250
"Scedilla", # 251
"scedilla", # 252
"Cacute", # 253
"cacute", # 254
"Ccaron", # 255
"ccaron", # 256
"dcroat" # 257
]

View File

@ -0,0 +1,35 @@
import DefaultTable
from fontTools import cffLib
class table_C_F_F_(DefaultTable.DefaultTable, cffLib.CFFFontSet):
def __init__(self, tag):
DefaultTable.DefaultTable.__init__(self, tag)
cffLib.CFFFontSet.__init__(self)
self._gaveGlyphOrder = 0
def decompile(self, data, otFont):
self.data = data # XXX while work is in progress...
cffLib.CFFFontSet.decompile(self, data)
assert len(self.fonts) == 1, "can't deal with multi-font CFF tables."
#def compile(self, otFont):
# xxx
def getGlyphOrder(self):
if self._gaveGlyphOrder:
from fontTools import ttLib
raise ttLib.TTLibError, "illegal use of getGlyphOrder()"
self._gaveGlyphOrder = 1
return self.fonts[self.fontNames[0]].getGlyphOrder()
def setGlyphOrder(self, glyphOrder):
self.fonts[self.fontNames[0]].setGlyphOrder(glyphOrder)
def toXML(self, writer, otFont, progress=None):
cffLib.CFFFontSet.toXML(self, writer, progress)
#def fromXML(self, (name, attrs, content), otFont):
# xxx

View File

@ -0,0 +1,12 @@
import DefaultTable
class table_D_S_I_G_(DefaultTable.DefaultTable):
def toXML(self, xmlWriter, ttFont):
xmlWriter.comment("note that the Digital Signature will be invalid after recompilation!")
xmlWriter.newline()
xmlWriter.begintag("hexdata")
xmlWriter.newline()
xmlWriter.dumphex(self.compile(ttFont))
xmlWriter.endtag("hexdata")
xmlWriter.newline()

View File

@ -0,0 +1,36 @@
import string
import sys
class DefaultTable:
dependencies = []
def __init__(self, tag):
self.tableTag = tag
def decompile(self, data, ttFont):
self.data = data
def compile(self, ttFont):
return self.data
def toXML(self, writer, ttFont):
writer.begintag("hexdata")
writer.newline()
writer.dumphex(self.compile(ttFont))
writer.endtag("hexdata")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
from fontTools.misc.textTools import readHex
from fontTools import ttLib
if name <> "hexdata":
raise ttLib.TTLibError, "can't handle '%s' element" % name
self.decompile(readHex(content), ttFont)
def __repr__(self):
return "<'%s' table at %x>" % (self.tableTag, id(self))
def __cmp__(self, other):
return cmp(self.__dict__, other.__dict__)

View File

@ -0,0 +1,294 @@
import otCommon
class table_G_P_O_S_(otCommon.base_GPOS_GSUB):
def getLookupTypeClass(self, lookupType):
return lookupTypeClasses[lookupType]
class SinglePos:
def decompile(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
class PairPos:
def decompile(self, reader, otFont):
self.format = reader.readUShort()
if self.format == 1:
self.decompileFormat1(reader, otFont)
elif self.format == 2:
self.decompileFormat2(reader, otFont)
else:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown PairPos format: %d" % self.format
def decompileFormat1(self, reader, otFont):
coverage = reader.readTable(otCommon.CoverageTable, otFont)
glyphNames = coverage.glyphNames
valueFactory1 = ValueRecordFactory(reader.readUShort())
valueFactory2 = ValueRecordFactory(reader.readUShort())
self.pairs = pairs = {}
for i in range(reader.readUShort()):
firstGlyphName = glyphNames[i]
offset = reader.readOffset()
setData = reader.getSubString(offset)
set = PairSet()
set.decompile(setData, otFont, valueFactory1, valueFactory2)
pairs[firstGlyphName] = set.values
def decompileFormat2(self, reader, otFont):
coverage = reader.readTable(otCommon.CoverageTable, otFont)
glyphNames = coverage.glyphNames
valueFactory1 = ValueRecordFactory(reader.readUShort())
valueFactory2 = ValueRecordFactory(reader.readUShort())
self.classDef1 = reader.readTable(otCommon.ClassDefinitionTable, otFont)
self.classDef2 = reader.readTable(otCommon.ClassDefinitionTable, otFont)
class1Count = reader.readUShort()
class2Count = reader.readUShort()
self.pairs = pairs = {} # sparse matrix
for i in range(class1Count):
row = {}
for j in range(class2Count):
value1 = valueFactory1.getValueRecord(reader)
value2 = valueFactory2.getValueRecord(reader)
if value1 or value2:
row[j] = (value1, value2)
if row:
pairs[i] = row
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
if self.format == 1:
self.toXMLFormat1(xmlWriter, otFont)
elif self.format == 2:
self.toXMLFormat2(xmlWriter, otFont)
else:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown PairPos format: %d" % self.format
def toXMLFormat1(self, xmlWriter, otFont):
pairs = self.pairs.items()
pairs.sort()
for firstGlyph, secondGlyphs in pairs:
for secondGlyph, value1, value2 in secondGlyphs:
#XXXXXXXXX
xmlWriter.begintag("Pair", first=firstGlyph, second=secondGlyph)
xmlWriter.newline()
if value1:
value1.toXML(xmlWriter, otFont)
if value2:
value2.toXML(xmlWriter, otFont)
xmlWriter.endtag("Pair")
xmlWriter.newline()
def toXMLFormat2(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
class PairSet:
def decompile(self, reader, otFont, valueFactory1, valueFactory2):
pairValueCount = reader.readUShort()
self.values = values = []
for j in range(pairValueCount):
secondGlyphID = reader.readUShort()
secondGlyphName = otFont.getGlyphName(secondGlyphID)
value1 = valueFactory1.getValueRecord(reader)
value2 = valueFactory2.getValueRecord(reader)
values.append((secondGlyphName, value1, value2))
def compile(self, otFont):
xxx
#
# ------------------
#
class CursivePos:
def decompile(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
class MarkBasePos:
def decompile(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
class MarkLigPos:
def decompile(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
class MarkMarkPos:
def decompile(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
class ContextPos:
def decompile(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
class ChainContextPos:
def decompile(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
lookupTypeClasses = {
1: SinglePos,
2: PairPos,
3: CursivePos,
4: MarkBasePos,
5: MarkLigPos,
6: MarkMarkPos,
7: ContextPos,
8: ChainContextPos,
}
valueRecordFormat = [
# Mask Name struct format char
(0x0001, "XPlacement", "h"),
(0x0002, "YPlacement", "h"),
(0x0004, "XAdvance", "h"),
(0x0008, "YAdvance", "h"),
(0x0010, "XPlaDevice", "H"),
(0x0020, "YPlaDevice", "H"),
(0x0040, "XAdvDevice", "H"),
(0x0080, "YAdvDevice", "H"),
# reserved:
(0x0100, "Reserved1", "H"),
(0x0200, "Reserved2", "H"),
(0x0400, "Reserved3", "H"),
(0x0800, "Reserved4", "H"),
(0x1000, "Reserved5", "H"),
(0x2000, "Reserved6", "H"),
(0x4000, "Reserved7", "H"),
(0x8000, "Reserved8", "H"),
]
class ValueRecordFactory:
def __init__(self, valueFormat):
format = ">"
names = []
for mask, name, formatChar in valueRecordFormat:
if valueFormat & mask:
names.append(name)
format = format + formatChar
self.names, self.format = names, format
self.size = 2 * len(names)
def getValueRecord(self, reader):
names = self.names
if not names:
return None
values = reader.readStruct(self.format, self.size)
values = map(int, values)
valueRecord = ValueRecord()
items = map(None, names, values)
for name, value in items:
setattr(valueRecord, name, value)
return valueRecord
class ValueRecord:
# see ValueRecordFactory
def __nonzero__(self):
for value in self.__dict__.values():
if value:
return 1
return 0
def toXML(self, xmlWriter, otFont):
simpleItems = []
for mask, name, format in valueRecordFormat[:4]: # "simple" values
if hasattr(self, name):
simpleItems.append((name, getattr(self, name)))
deviceItems = []
for mask, name, format in valueRecordFormat[4:8]: # device records
if hasattr(self, name):
deviceItems.append((name, getattr(self, name)))
if deviceItems:
xmlWriter.begintag("ValueRecord", simpleItems)
xmlWriter.newline()
for name, deviceRecord in deviceItems:
xxx
xmlWriter.endtag("ValueRecord")
xmlWriter.newline()
else:
xmlWriter.simpletag("ValueRecord", simpleItems)
xmlWriter.newline()
def __repr__(self):
return "<ValueRecord>"

View File

@ -0,0 +1,283 @@
import otCommon
class table_G_S_U_B_(otCommon.base_GPOS_GSUB):
def getLookupTypeClass(self, lookupType):
return lookupTypeClasses[lookupType]
class SingleSubst:
def decompile(self, reader, otFont):
self.format = reader.readUShort()
if self.format == 1:
self.decompileFormat1(reader, otFont)
elif self.format == 2:
self.decompileFormat2(reader, otFont)
else:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown SingleSub format: %d" % self.format
def decompileFormat1(self, reader, otFont):
coverage = reader.readTable(otCommon.CoverageTable, otFont)
glyphIDs = coverage.getGlyphIDs()
glyphNames = coverage.getGlyphNames()
self.substitutions = substitutions = {}
deltaGlyphID = reader.readShort()
for i in range(len(glyphIDs)):
input = glyphNames[i]
output = otFont.getGlyphName(glyphIDs[i] + deltaGlyphID)
substitutions[input] = output
def decompileFormat2(self, reader, otFont):
coverage = reader.readTable(otCommon.CoverageTable, otFont)
glyphNames = coverage.getGlyphNames()
glyphCount = reader.readUShort()
self.substitutions = substitutions = {}
for i in range(glyphCount):
glyphID = reader.readUShort()
output = otFont.getGlyphName(glyphID)
input = glyphNames[i]
substitutions[input] = output
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
substitutions = self.substitutions.items()
substitutions.sort()
for input, output in substitutions:
xmlWriter.simpletag("Subst", [("in", input), ("out", output)])
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
class MultipleSubst:
def decompile(self, reader, otFont):
format = reader.readUShort()
if format <> 1:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown MultipleSubst format: %d" % format
glyphNames = reader.readTable(otCommon.CoverageTable, otFont).getGlyphNames()
sequenceCount = reader.readUShort()
self.substitutions = substitutions = {}
for i in range(sequenceCount):
sequence = reader.readTable(Sequence, otFont)
substitutions[glyphNames[i]] = sequence.glyphs
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
import string
items = self.substitutions.items()
items.sort()
for input, output in items:
xmlWriter.simpletag("Subst", [("in", input), ("out", string.join(output, ","))])
xmlWriter.newline()
class Sequence:
def decompile(self, reader, otFont):
self.glyphs = []
for i in range(reader.readUShort()):
self.glyphs.append(otFont.getGlyphName(reader.readUShort()))
def compile(self, otFont):
xxx
class AlternateSubst:
def decompile(self, reader, otFont):
format = reader.readUShort()
if format <> 1:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown AlternateSubst format: %d" % format
coverage = reader.readTable(otCommon.CoverageTable, otFont)
glyphNames = coverage.getGlyphNames()
alternateSetCount = reader.readUShort()
self.alternateSet = alternateSet = {}
for i in range(alternateSetCount):
set = reader.readTable(AlternateSet, otFont)
alternateSet[glyphNames[i]] = set.glyphs
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
alternates = self.alternateSet.items()
alternates.sort()
for input, substList in alternates:
xmlWriter.begintag("AlternateSet", [("in", input)])
xmlWriter.newline()
for output in substList:
xmlWriter.simpletag("Subst", out=output)
xmlWriter.newline()
xmlWriter.endtag("AlternateSet")
xmlWriter.newline()
class AlternateSet:
def decompile(self, reader, otFont):
glyphCount = reader.readUShort()
glyphIDs = reader.readUShortArray(glyphCount)
self.glyphs = map(otFont.getGlyphName, glyphIDs)
def compile(self, otFont):
xxx
class LigatureSubst:
def decompile(self, reader, otFont):
format = reader.readUShort()
if format <> 1:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown LigatureSubst format: %d" % format
coverage = reader.readTable(otCommon.CoverageTable, otFont)
glyphNames = coverage.getGlyphNames()
ligSetCount = reader.readUShort()
self.ligatures = ligatures = []
for i in range(ligSetCount):
firstGlyph = glyphNames[i]
ligSet = reader.readTable(LigatureSet, otFont)
for ligatureGlyph, components in ligSet.ligatures:
ligatures.append(((firstGlyph,) + tuple(components)), ligatureGlyph)
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
import string
for input, output in self.ligatures:
xmlWriter.simpletag("Subst", [("in", string.join(input, ",")), ("out", output)])
xmlWriter.newline()
class LigatureSet:
def decompile(self, reader, otFont):
ligatureCount = reader.readUShort()
self.ligatures = ligatures = []
for i in range(ligatureCount):
lig = reader.readTable(Ligature, otFont)
ligatures.append((lig.ligatureGlyph, lig.components))
def compile(self, otFont):
xxx
class Ligature:
def decompile(self, reader, otFont):
self.ligatureGlyph = otFont.getGlyphName(reader.readUShort())
compCount = reader.readUShort()
self.components = components = []
for i in range(compCount-1):
components.append(otFont.getGlyphName(reader.readUShort()))
def compile(self, otFont):
xxx
class ContextSubst:
def decompile(self, reader, otFont):
format = reader.readUShort()
if format == 1:
self.decompileFormat1(reader, otFont)
elif format == 2:
self.decompileFormat2(reader, otFont)
elif format == 3:
self.decompileFormat3(reader, otFont)
else:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown ContextSubst format: %d" % format
def decompileFormat1(self, reader, otFont):
xxx
def decompileFormat2(self, reader, otFont):
xxx
def decompileFormat3(self, reader, otFont):
glyphCount = reader.readUShort()
substCount = reader.readUShort()
coverage = []
for i in range(glyphCount):
coverage.append(reader.readTable(otCommon.CoverageTable, otFont))
self.substitutions = substitutions = []
for i in range(substCount):
lookupRecord = SubstLookupRecord()
lookupRecord.decompile(reader, otFont)
substitutions.append((coverage[i].getGlyphNames(), lookupRecord))
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
class ChainContextSubst:
def decompile(self, reader, otFont):
self.format = reader.readUShort()
if self.format == 1:
self.decompileFormat1(reader, otFont)
elif self.format == 2:
self.decompileFormat2(reader, otFont)
elif self.format == 3:
self.decompileFormat3(reader, otFont)
else:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown ChainContextSubst format: %d" % self.format
def decompileFormat1(self, reader, otFont):
pass
def decompileFormat2(self, reader, otFont):
pass
def decompileFormat3(self, reader, otFont):
pass
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.comment("XXX")
xmlWriter.newline()
lookupTypeClasses = {
1: SingleSubst,
2: MultipleSubst,
3: AlternateSubst,
4: LigatureSubst,
5: ContextSubst,
6: ChainContextSubst,
}
#
# Shared classes
#
class SubstLookupRecord:
def decompile(self, reader, otFont):
self.sequenceIndex = reader.readUShort()
self.lookupListIndex = reader.readUShort()
def compile(self, otFont):
xxx

View File

@ -0,0 +1,50 @@
import DefaultTable
import array
import struct
from fontTools.misc.textTools import safeEval
# XXX I've lowered the strictness, to make sure Apple's own Chicago
# XXX gets through. They're looking into it, I hope to raise the standards
# XXX back to normal eventually.
class table_L_T_S_H_(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
version, numGlyphs = struct.unpack(">HH", data[:4])
data = data[4:]
assert version == 0
assert len(data) == numGlyphs
# ouch: the assertion is not true in Chicago!
#assert numGlyphs == ttFont['maxp'].numGlyphs
yPels = array.array("B")
yPels.fromstring(data)
self.yPels = {}
for i in range(numGlyphs):
self.yPels[ttFont.getGlyphName(i)] = yPels[i]
def compile(self, ttFont):
version = 0
names = self.yPels.keys()
numGlyphs = len(names)
yPels = [0] * numGlyphs
# ouch: the assertion is not true in Chicago!
#assert len(self.yPels) == ttFont['maxp'].numGlyphs == numGlyphs
for name in names:
yPels[ttFont.getGlyphID(name)] = self.yPels[name]
yPels = array.array("B", yPels)
return struct.pack(">HH", version, numGlyphs) + yPels.tostring()
def toXML(self, writer, ttFont):
names = self.yPels.keys()
names.sort()
for name in names:
writer.simpletag("yPel", name=name, value=self.yPels[name])
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if not hasattr(self, "yPels"):
self.yPels = {}
if name <> "yPel":
return # ignore unknown tags
self.yPels[attrs["name"]] = safeEval(attrs["value"])

View File

@ -0,0 +1,164 @@
import DefaultTable
import sstruct
from fontTools.misc.textTools import safeEval, num2binary, binary2num
# panose classification
panoseFormat = """
bFamilyType: B
bSerifStyle: B
bWeight: B
bProportion: B
bContrast: B
bStrokeVariation: B
bArmStyle: B
bLetterForm: B
bMidline: B
bXHeight: B
"""
class Panose:
def toXML(self, writer, ttFont):
formatstring, names, fixes = sstruct.getformat(panoseFormat)
for name in names:
writer.simpletag(name, value=getattr(self, name))
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
setattr(self, name, safeEval(attrs["value"]))
# 'sfnt' OS/2 and Windows Metrics table - 'OS/2'
OS2_format_0 = """
> # big endian
version: H # version
xAvgCharWidth: h # average character width
usWeightClass: H # degree of thickness of strokes
usWidthClass: H # aspect ratio
fsType: h # type flags
ySubscriptXSize: h # subscript horizontal font size
ySubscriptYSize: h # subscript vertical font size
ySubscriptXOffset: h # subscript x offset
ySubscriptYOffset: h # subscript y offset
ySuperscriptXSize: h # superscript horizontal font size
ySuperscriptYSize: h # superscript vertical font size
ySuperscriptXOffset: h # superscript x offset
ySuperscriptYOffset: h # superscript y offset
yStrikeoutSize: h # strikeout size
yStrikeoutPosition: h # strikeout position
sFamilyClass: h # font family class and subclass
panose: 10s # panose classification number
ulUnicodeRange1: l # character range
ulUnicodeRange2: l # character range
ulUnicodeRange3: l # character range
ulUnicodeRange4: l # character range
achVendID: 4s # font vendor identification
fsSelection: H # font selection flags
fsFirstCharIndex: H # first unicode character index
fsLastCharIndex: H # last unicode character index
usTypoAscender: H # typographic ascender
usTypoDescender: H # typographic descender
usTypoLineGap: H # typographic line gap
usWinAscent: H # Windows ascender
usWinDescent: H # Windows descender
"""
OS2_format_1_addition = """
ulCodePageRange1: l
ulCodePageRange2: l
"""
OS2_format_2_addition = OS2_format_1_addition + """
sxHeight: h
sCapHeight: h
usDefaultChar: H
usBreakChar: H
usMaxContex: H
"""
bigendian = " > # big endian\n"
OS2_format_1 = OS2_format_0 + OS2_format_1_addition
OS2_format_2 = OS2_format_0 + OS2_format_2_addition
OS2_format_1_addition = bigendian + OS2_format_1_addition
OS2_format_2_addition = bigendian + OS2_format_2_addition
class table_O_S_2f_2(DefaultTable.DefaultTable):
"""the OS/2 table"""
def decompile(self, data, ttFont):
dummy, data = sstruct.unpack2(OS2_format_0, data, self)
if self.version == 1:
sstruct.unpack(OS2_format_1_addition, data, self)
elif self.version == 2:
sstruct.unpack(OS2_format_2_addition, data, self)
elif self.version <> 0:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown format for OS/2 table: version %s" % self.version
self.panose = sstruct.unpack(panoseFormat, self.panose, Panose())
def compile(self, ttFont):
panose = self.panose
self.panose = sstruct.pack(panoseFormat, self.panose)
if self.version == 0:
data = sstruct.pack(OS2_format_0, self)
elif self.version == 1:
data = sstruct.pack(OS2_format_1, self)
elif self.version == 2:
data = sstruct.pack(OS2_format_2, self)
else:
from fontTools import ttLib
raise ttLib.TTLibError, "unknown format for OS/2 table: version %s" % self.version
self.panose = panose
return data
def toXML(self, writer, ttFont):
if self.version == 1:
format = OS2_format_1
elif self.version == 2:
format = OS2_format_2
else:
format = OS2_format_0
formatstring, names, fixes = sstruct.getformat(format)
for name in names:
value = getattr(self, name)
if type(value) == type(0L):
value = int(value)
if name=="panose":
writer.begintag("panose")
writer.newline()
value.toXML(writer, ttFont)
writer.endtag("panose")
elif name in ("ulUnicodeRange1", "ulUnicodeRange2",
"ulUnicodeRange3", "ulUnicodeRange4",
"ulCodePageRange1", "ulCodePageRange2"):
writer.simpletag(name, value=num2binary(value))
elif name in ("fsType", "fsSelection"):
writer.simpletag(name, value=num2binary(value, 16))
elif name == "achVendID":
writer.simpletag(name, value=repr(value)[1:-1])
else:
writer.simpletag(name, value=value)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if name == "panose":
self.panose = panose = Panose()
for element in content:
if type(element) == type(()):
panose.fromXML(element, ttFont)
elif name in ("ulUnicodeRange1", "ulUnicodeRange2",
"ulUnicodeRange3", "ulUnicodeRange4",
"ulCodePageRange1", "ulCodePageRange2",
"fsType", "fsSelection"):
setattr(self, name, binary2num(attrs["value"]))
elif name == "achVendID":
setattr(self, name, safeEval("'''" + attrs["value"] + "'''"))
else:
setattr(self, name, safeEval(attrs["value"]))

View File

@ -0,0 +1,45 @@
import DefaultTable
import struct
tsi0Format = '>HHl'
def fixlongs((glyphID, textLength, textOffset)):
return int(glyphID), int(textLength), textOffset
class table_T_S_I__0(DefaultTable.DefaultTable):
dependencies = ["TSI1"]
def decompile(self, data, ttFont):
numGlyphs = ttFont['maxp'].numGlyphs
indices = []
size = struct.calcsize(tsi0Format)
for i in range(numGlyphs + 5):
glyphID, textLength, textOffset = fixlongs(struct.unpack(tsi0Format, data[:size]))
indices.append((glyphID, textLength, textOffset))
data = data[size:]
assert len(data) == 0
assert indices[-5] == (0XFFFE, 0, 0xABFC1F34), "bad magic number"
self.indices = indices[:-5]
self.extra_indices = indices[-4:]
def compile(self, ttFont):
data = ""
size = struct.calcsize(tsi0Format)
for index, textLength, textOffset in self.indices:
data = data + struct.pack(tsi0Format, index, textLength, textOffset)
data = data + struct.pack(tsi0Format, 0XFFFE, 0, 0xABFC1F34)
for index, textLength, textOffset in self.extra_indices:
data = data + struct.pack(tsi0Format, index, textLength, textOffset)
return data
def set(self, indices, extra_indices):
# gets called by 'TSI1' or 'TSI3'
self.indices = indices
self.extra_indices = extra_indices
def toXML(self, writer, ttFont):
writer.comment("This table will be calculated by the compiler")
writer.newline()

View File

@ -0,0 +1,116 @@
import DefaultTable
import string
class table_T_S_I__1(DefaultTable.DefaultTable):
extras = {0xfffa: "ppgm", 0xfffb: "cvt", 0xfffc: "reserved", 0xfffd: "fpgm"}
indextable = "TSI0"
def decompile(self, data, ttFont):
indextable = ttFont[self.indextable]
self.glyphPrograms = {}
for i in range(len(indextable.indices)):
glyphID, textLength, textOffset = indextable.indices[i]
if textLength == 0x8000:
# Ugh. Hi Beat!
textLength = indextable.indices[i+1][1]
if textLength > 0x8000:
pass # XXX Hmmm.
text = data[textOffset:textOffset+textLength]
assert len(text) == textLength
if text:
self.glyphPrograms[ttFont.getGlyphName(glyphID)] = text
self.extraPrograms = {}
for i in range(len(indextable.extra_indices)):
extraCode, textLength, textOffset = indextable.extra_indices[i]
if textLength == 0x8000:
if extraName == "fpgm": # this is the last one
textLength = len(data) - textOffset
else:
textLength = indextable.extra_indices[i+1][1]
text = data[textOffset:textOffset+textLength]
assert len(text) == textLength
if text:
self.extraPrograms[self.extras[extraCode]] = text
def compile(self, ttFont):
data = ''
indextable = ttFont[self.indextable]
glyphNames = ttFont.getGlyphOrder()
indices = []
for i in range(len(glyphNames)):
if len(data) % 2:
data = data + "\015" # align on 2-byte boundaries, fill with return chars. Yum.
name = glyphNames[i]
if self.glyphPrograms.has_key(name):
text = self.glyphPrograms[name]
else:
text = ""
textLength = len(text)
if textLength >= 0x8000:
textLength = 0x8000 # XXX ???
indices.append((i, textLength, len(data)))
data = data + text
extra_indices = []
codes = self.extras.items()
codes.sort()
for i in range(len(codes)):
if len(data) % 2:
data = data + "\015" # align on 2-byte boundaries, fill with return chars.
code, name = codes[i]
if self.extraPrograms.has_key(name):
text = self.extraPrograms[name]
else:
text = ""
textLength = len(text)
if textLength >= 0x8000:
textLength = 0x8000 # XXX ???
extra_indices.append((code, textLength, len(data)))
data = data + text
indextable.set(indices, extra_indices)
return data
def toXML(self, writer, ttFont):
names = self.glyphPrograms.keys()
names.sort()
writer.newline()
for name in names:
text = self.glyphPrograms[name]
if not text:
continue
writer.begintag("glyphProgram", name=name)
writer.newline()
writer.write_noindent(string.replace(text, "\r", "\n"))
writer.newline()
writer.endtag("glyphProgram")
writer.newline()
writer.newline()
extra_names = self.extraPrograms.keys()
extra_names.sort()
for name in extra_names:
text = self.extraPrograms[name]
if not text:
continue
writer.begintag("extraProgram", name=name)
writer.newline()
writer.write_noindent(string.replace(text, "\r", "\n"))
writer.newline()
writer.endtag("extraProgram")
writer.newline()
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if not hasattr(self, "glyphPrograms"):
self.glyphPrograms = {}
self.extraPrograms = {}
lines = string.split(string.replace(string.join(content, ""), "\r", "\n"), "\n")
text = string.join(lines[1:-1], "\r")
if name == "glyphProgram":
self.glyphPrograms[attrs["name"]] = text
elif name == "extraProgram":
self.extraPrograms[attrs["name"]] = text

View File

@ -0,0 +1,8 @@
from fontTools import ttLib
superclass = ttLib.getTableClass("TSI0")
class table_T_S_I__2(superclass):
dependencies = ["TSI3"]

View File

@ -0,0 +1,11 @@
from fontTools import ttLib
superclass = ttLib.getTableClass("TSI1")
class table_T_S_I__3(superclass):
extras = {0xfffa: "reserved0", 0xfffb: "reserved1", 0xfffc: "reserved2", 0xfffd: "reserved3"}
indextable = "TSI2"

View File

@ -0,0 +1,42 @@
import DefaultTable
import array
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
class table_T_S_I__5(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
numGlyphs = ttFont['maxp'].numGlyphs
assert len(data) == 2 * numGlyphs
a = array.array("H")
a.fromstring(data)
if ttLib.endian <> "big":
a.byteswap()
self.glyphGrouping = {}
for i in range(numGlyphs):
self.glyphGrouping[ttFont.getGlyphName(i)] = a[i]
def compile(self, ttFont):
glyphNames = ttFont.getGlyphOrder()
a = array.array("H")
for i in range(len(glyphNames)):
a.append(self.glyphGrouping[glyphNames[i]])
if ttLib.endian <> "big":
a.byteswap()
return a.tostring()
def toXML(self, writer, ttFont):
names = self.glyphGrouping.keys()
names.sort()
for glyphName in names:
writer.simpletag("glyphgroup", name=glyphName, value=self.glyphGrouping[glyphName])
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if not hasattr(self, "glyphGrouping"):
self.glyphGrouping = {}
if name <> "glyphgroup":
return
self.glyphGrouping[attrs["name"]] = safeEval(attrs["value"])

View File

@ -0,0 +1 @@
# dummy file, so Python knows ttLib.tables is a subpackage

View File

@ -0,0 +1,397 @@
import DefaultTable
import struct
import string
import array
from fontTools import ttLib
from fontTools.misc.textTools import safeEval, readHex
class table__c_m_a_p(DefaultTable.DefaultTable):
def getcmap(self, platformID, platEncID):
for subtable in self.tables:
if (subtable.platformID == platformID and
subtable.platEncID == platEncID):
return subtable
return None # not found
def decompile(self, data, ttFont):
tableVersion, numSubTables = struct.unpack(">HH", data[:4])
self.tableVersion = int(tableVersion)
self.tables = tables = []
for i in range(numSubTables):
platformID, platEncID, offset = struct.unpack(
">HHl", data[4+i*8:4+(i+1)*8])
platformID, platEncID = int(platformID), int(platEncID)
format, length = struct.unpack(">HH", data[offset:offset+4])
if not cmap_classes.has_key(format):
table = cmap_format_unknown(format)
else:
table = cmap_classes[format](format)
table.platformID = platformID
table.platEncID = platEncID
table.decompile(data[offset:offset+int(length)], ttFont)
tables.append(table)
def compile(self, ttFont):
self.tables.sort() # sort according to the spec; see CmapSubtable.__cmp__()
numSubTables = len(self.tables)
totalOffset = 4 + 8 * numSubTables
data = struct.pack(">HH", self.tableVersion, numSubTables)
tableData = ""
done = {} # remember the data so we can reuse the "pointers"
for table in self.tables:
chunk = table.compile(ttFont)
if done.has_key(chunk):
offset = done[chunk]
else:
offset = done[chunk] = totalOffset + len(tableData)
tableData = tableData + chunk
data = data + struct.pack(">HHl", table.platformID, table.platEncID, offset)
return data + tableData
def toXML(self, writer, ttFont):
writer.simpletag("tableVersion", version=self.tableVersion)
writer.newline()
for table in self.tables:
table.toXML(writer, ttFont)
def fromXML(self, (name, attrs, content), ttFont):
if name == "tableVersion":
self.tableVersion = safeEval(attrs["version"])
return
if name[:12] <> "cmap_format_":
return
if not hasattr(self, "tables"):
self.tables = []
format = safeEval(name[12])
if not cmap_classes.has_key(format):
table = cmap_format_unknown(format)
else:
table = cmap_classes[format](format)
table.platformID = safeEval(attrs["platformID"])
table.platEncID = safeEval(attrs["platEncID"])
table.fromXML((name, attrs, content), ttFont)
self.tables.append(table)
class CmapSubtable:
def __init__(self, format):
self.format = format
def toXML(self, writer, ttFont):
writer.begintag(self.__class__.__name__, [
("platformID", self.platformID),
("platEncID", self.platEncID),
])
writer.newline()
writer.dumphex(self.compile(ttFont))
writer.endtag(self.__class__.__name__)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.decompile(readHex(content), ttFont)
def __cmp__(self, other):
# implemented so that list.sort() sorts according to the cmap spec.
selfTuple = (
self.platformID,
self.platEncID,
self.version,
self.__dict__)
otherTuple = (
other.platformID,
other.platEncID,
other.version,
other.__dict__)
return cmp(selfTuple, otherTuple)
class cmap_format_0(CmapSubtable):
def decompile(self, data, ttFont):
format, length, version = struct.unpack(">HHH", data[:6])
self.version = int(version)
assert len(data) == 262 == length
glyphIdArray = array.array("B")
glyphIdArray.fromstring(data[6:])
self.cmap = cmap = {}
for charCode in range(len(glyphIdArray)):
cmap[charCode] = ttFont.getGlyphName(glyphIdArray[charCode])
def compile(self, ttFont):
charCodes = self.cmap.keys()
charCodes.sort()
assert charCodes == range(256) # charCodes[charCode] == charCode
for charCode in charCodes:
# reusing the charCodes list!
charCodes[charCode] = ttFont.getGlyphID(self.cmap[charCode])
glyphIdArray = array.array("B", charCodes)
data = struct.pack(">HHH", 0, 262, self.version) + glyphIdArray.tostring()
assert len(data) == 262
return data
def toXML(self, writer, ttFont):
writer.begintag(self.__class__.__name__, [
("platformID", self.platformID),
("platEncID", self.platEncID),
("version", self.version),
])
writer.newline()
items = self.cmap.items()
items.sort()
for code, name in items:
writer.simpletag("map", code=hex(code), name=name)
writer.newline()
writer.endtag(self.__class__.__name__)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.version = safeEval(attrs["version"])
self.cmap = {}
for element in content:
if type(element) <> type(()):
continue
name, attrs, content = element
if name <> "map":
continue
self.cmap[safeEval(attrs["code"])] = attrs["name"]
class cmap_format_2(CmapSubtable):
def decompile(self, data, ttFont):
format, length, version = struct.unpack(">HHH", data[:6])
self.version = int(version)
self.data = data
def compile(self, ttFont):
return self.data
cmap_format_4_format = ">7H"
#uint16 endCode[segCount] # Ending character code for each segment, last = 0xFFFF.
#uint16 reservedPad # This value should be zero
#uint16 startCode[segCount] # Starting character code for each segment
#uint16 idDelta[segCount] # Delta for all character codes in segment
#uint16 idRangeOffset[segCount] # Offset in bytes to glyph indexArray, or 0
#uint16 glyphIndexArray[variable] # Glyph index array
class cmap_format_4(CmapSubtable):
def decompile(self, data, ttFont):
(format, length, self.version, segCountX2,
searchRange, entrySelector, rangeShift) = \
struct.unpack(cmap_format_4_format, data[:14])
assert len(data) == length, "corrupt cmap table (%d, %d)" % (len(data), length)
data = data[14:]
segCountX2 = int(segCountX2)
segCount = segCountX2 / 2
allcodes = array.array("H")
allcodes.fromstring(data)
if ttLib.endian <> "big":
allcodes.byteswap()
# divide the data
endCode = allcodes[:segCount]
allcodes = allcodes[segCount+1:]
startCode = allcodes[:segCount]
allcodes = allcodes[segCount:]
idDelta = allcodes[:segCount]
allcodes = allcodes[segCount:]
idRangeOffset = allcodes[:segCount]
glyphIndexArray = allcodes[segCount:]
# build 2-byte character mapping
cmap = {}
for i in range(len(startCode) - 1): # don't do 0xffff!
for charCode in range(startCode[i], endCode[i] + 1):
rangeOffset = idRangeOffset[i]
if rangeOffset == 0:
glyphID = charCode + idDelta[i]
else:
# *someone* needs to get killed.
index = idRangeOffset[i] / 2 + (charCode - startCode[i]) + i - len(idRangeOffset)
if glyphIndexArray[index] <> 0: # if not missing glyph
glyphID = glyphIndexArray[index] + idDelta[i]
else:
glyphID = 0 # missing glyph
cmap[charCode] = ttFont.getGlyphName(glyphID % 0x10000)
self.cmap = cmap
def compile(self, ttFont):
from fontTools.ttLib.sfnt import maxpoweroftwo
codes = self.cmap.items()
codes.sort()
# build startCode and endCode lists
last = codes[0][0]
endCode = []
startCode = [last]
for charCode, glyphName in codes[1:]: # skip the first code, it's the first start code
if charCode == last + 1:
last = charCode
continue
endCode.append(last)
startCode.append(charCode)
last = charCode
endCode.append(last)
startCode.append(0xffff)
endCode.append(0xffff)
# build up rest of cruft.
idDelta = []
idRangeOffset = []
glyphIndexArray = []
for i in range(len(endCode)-1): # skip the closing codes (0xffff)
indices = []
for charCode in range(startCode[i], endCode[i]+1):
indices.append(ttFont.getGlyphID(self.cmap[charCode]))
if indices == range(indices[0], indices[0] + len(indices)):
idDelta.append((indices[0] - startCode[i]) % 0x10000)
idRangeOffset.append(0)
else:
# someone *definitely* needs to get killed.
idDelta.append(0)
idRangeOffset.append(2 * (len(endCode) + len(glyphIndexArray) - i))
glyphIndexArray = glyphIndexArray + indices
idDelta.append(1) # 0xffff + 1 == (tadaa!) 0. So this end code maps to .notdef
idRangeOffset.append(0)
# Insane.
segCount = len(endCode)
segCountX2 = segCount * 2
maxexponent = maxpoweroftwo(segCount)
searchRange = 2 * (2 ** maxexponent)
entrySelector = maxexponent
rangeShift = 2 * segCount - searchRange
allcodes = array.array("H",
endCode + [0] + startCode + idDelta + idRangeOffset + glyphIndexArray)
if ttLib.endian <> "big":
allcodes.byteswap()
data = allcodes.tostring()
length = struct.calcsize(cmap_format_4_format) + len(data)
header = struct.pack(cmap_format_4_format, self.format, length, self.version,
segCountX2, searchRange, entrySelector, rangeShift)
return header + data
def toXML(self, writer, ttFont):
from fontTools.unicode import Unicode
codes = self.cmap.items()
codes.sort()
writer.begintag(self.__class__.__name__, [
("platformID", self.platformID),
("platEncID", self.platEncID),
("version", self.version),
])
writer.newline()
for code, name in codes:
writer.simpletag("map", code=hex(code), name=name)
writer.comment(Unicode[code])
writer.newline()
writer.endtag(self.__class__.__name__)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.version = safeEval(attrs["version"])
self.cmap = {}
for element in content:
if type(element) <> type(()):
continue
name, attrs, content = element
if name <> "map":
continue
self.cmap[safeEval(attrs["code"])] = attrs["name"]
class cmap_format_6(CmapSubtable):
def decompile(self, data, ttFont):
format, length, version, firstCode, entryCount = struct.unpack(
">HHHHH", data[:10])
self.version = int(version)
firstCode = int(firstCode)
self.version = int(version)
data = data[10:]
assert len(data) == 2 * entryCount
glyphIndexArray = array.array("H")
glyphIndexArray.fromstring(data)
if ttLib.endian <> "big":
glyphIndexArray.byteswap()
self.cmap = cmap = {}
for i in range(len(glyphIndexArray)):
glyphID = glyphIndexArray[i]
glyphName = ttFont.getGlyphName(glyphID)
cmap[i+firstCode] = glyphName
def compile(self, ttFont):
codes = self.cmap.keys()
codes.sort()
assert codes == range(codes[0], codes[0] + len(codes))
glyphIndexArray = array.array("H", [0] * len(codes))
firstCode = codes[0]
for i in range(len(codes)):
code = codes[i]
glyphIndexArray[code-firstCode] = ttFont.getGlyphID(self.cmap[code])
if ttLib.endian <> "big":
glyphIndexArray.byteswap()
data = glyphIndexArray.tostring()
header = struct.pack(">HHHHH",
6, len(data) + 10, self.version, firstCode, len(self.cmap))
return header + data
def toXML(self, writer, ttFont):
codes = self.cmap.items()
codes.sort()
writer.begintag(self.__class__.__name__, [
("platformID", self.platformID),
("platEncID", self.platEncID),
("version", self.version),
])
writer.newline()
for code, name in codes:
writer.simpletag("map", code=hex(code), name=name)
writer.newline()
writer.endtag(self.__class__.__name__)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.version = safeEval(attrs["version"])
self.cmap = {}
for element in content:
if type(element) <> type(()):
continue
name, attrs, content = element
if name <> "map":
continue
self.cmap[safeEval(attrs["code"])] = attrs["name"]
class cmap_format_unknown(CmapSubtable):
def decompile(self, data, ttFont):
self.data = data
def compile(self, ttFont):
return self.data
cmap_classes = {
0: cmap_format_0,
2: cmap_format_2,
4: cmap_format_4,
6: cmap_format_6,
}

View File

@ -0,0 +1,48 @@
import DefaultTable
import array
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
class table__c_v_t(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
values = array.array("h")
values.fromstring(data)
if ttLib.endian <> "big":
values.byteswap()
self.values = values
def compile(self, ttFont):
values = self.values[:]
if ttLib.endian <> "big":
values.byteswap()
return values.tostring()
def toXML(self, writer, ttFont):
for i in range(len(self.values)):
value = self.values[i]
writer.simpletag("cv", value=value, index=i)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if not hasattr(self, "values"):
self.values = array.array("h")
if name == "cv":
index = safeEval(attrs["index"])
value = safeEval(attrs["value"])
for i in range(1 + index - len(self.values)):
self.values.append(0)
self.values[index] = value
def __len__(self):
return len(self.values)
def __getitem__(self, index):
return self.values[index]
def __setitem__(self, index, value):
self.values[index] = value
def __delitem__(self, index):
del self.values[index]

View File

@ -0,0 +1,14 @@
import DefaultTable
import array
class table__f_p_g_m(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
self.fpgm = data
def compile(self, ttFont):
return self.fpgm
def __len__(self):
return len(self.fpgm)

View File

@ -0,0 +1,46 @@
import DefaultTable
import struct
from fontTools.misc.textTools import safeEval
GASP_DOGRAY = 0x0002
GASP_GRIDFIT = 0x0001
class table__g_a_s_p(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
self.version, numRanges = struct.unpack(">HH", data[:4])
assert self.version == 0, "unknown 'gasp' format: %s" % self.version
data = data[4:]
self.gaspRange = {}
for i in range(numRanges):
rangeMaxPPEM, rangeGaspBehavior = struct.unpack(">HH", data[:4])
self.gaspRange[int(rangeMaxPPEM)] = int(rangeGaspBehavior)
data = data[4:]
assert not data, "too much data"
def compile(self, ttFont):
numRanges = len(self.gaspRange)
data = struct.pack(">HH", 0, numRanges)
items = self.gaspRange.items()
items.sort()
for rangeMaxPPEM, rangeGaspBehavior in items:
data = data + struct.pack(">HH", rangeMaxPPEM, rangeGaspBehavior)
return data
def toXML(self, writer, ttFont):
items = self.gaspRange.items()
items.sort()
for rangeMaxPPEM, rangeGaspBehavior in items:
writer.simpletag("gaspRange", [
("rangeMaxPPEM", rangeMaxPPEM),
("rangeGaspBehavior", rangeGaspBehavior)])
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if name <> "gaspRange":
return
if not hasattr(self, "gaspRange"):
self.gaspRange = {}
self.gaspRange[safeEval(attrs["rangeMaxPPEM"])] = safeEval(attrs["rangeGaspBehavior"])

View File

@ -0,0 +1,778 @@
"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
#
# The Apple and MS rasterizers behave differently for
# scaled composite components: one does scale first and then translate
# and the other does it vice versa. MS defined some flags to indicate
# the difference, but it seems nobody actually _sets_ those flags.
#
# Funny thing: Apple seems to _only_ do their thing in the
# WE_HAVE_A_SCALE (eg. Chicago) case, and not when it's WE_HAVE_AN_X_AND_Y_SCALE
# (eg. Charcoal)...
#
SCALE_COMPONENT_OFFSET_DEFAULT = 0 # 0 == MS, 1 == Apple
import struct, sstruct
import DefaultTable
from fontTools import ttLib
from fontTools.misc.textTools import safeEval, readHex
import array
import Numeric
import types
class table__g_l_y_f(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
loca = ttFont['loca']
last = loca[0]
self.glyphs = {}
self.glyphOrder = []
self.glyphOrder = glyphOrder = ttFont.getGlyphOrder()
for i in range(0, len(loca)-1):
glyphName = glyphOrder[i]
next = loca[i+1]
glyphdata = data[last:next]
if len(glyphdata) <> (next - last):
raise ttLib.TTLibError, "not enough 'glyf' table data"
glyph = Glyph(glyphdata)
self.glyphs[glyphName] = glyph
last = next
if len(data) > next:
raise ttLib.TTLibError, "too much 'glyf' table data"
def compile(self, ttFont):
import string
locations = []
currentLocation = 0
dataList = []
for glyphName in ttFont.getGlyphOrder():
glyph = self[glyphName]
glyphData = glyph.compile(self)
locations.append(currentLocation)
currentLocation = currentLocation + len(glyphData)
dataList.append(glyphData)
locations.append(currentLocation)
data = string.join(dataList, "")
ttFont['loca'].set(locations)
ttFont['maxp'].numGlyphs = len(self.glyphs)
return data
def toXML(self, writer, ttFont, progress=None, compactGlyphs=0):
writer.newline()
glyphOrder = ttFont.getGlyphOrder()
writer.begintag("GlyphOrder")
writer.newline()
for i in range(len(glyphOrder)):
glyphName = glyphOrder[i]
writer.simpletag("GlyphID", id=i, name=glyphName)
writer.newline()
writer.endtag("GlyphOrder")
writer.newline()
writer.newline()
glyphNames = ttFont.getGlyphNames()
writer.comment("The xMin, yMin, xMax and yMax values\nwill be recalculated by the compiler.")
writer.newline()
writer.newline()
for glyphName in glyphNames:
if progress:
progress.setlabel("Dumping 'glyf' table... (%s)" % glyphName)
progress.increment()
glyph = self[glyphName]
if glyph.numberOfContours:
writer.begintag('TTGlyph', [
("name", glyphName),
("xMin", glyph.xMin),
("yMin", glyph.yMin),
("xMax", glyph.xMax),
("yMax", glyph.yMax),
])
writer.newline()
glyph.toXML(writer, ttFont)
if compactGlyphs:
glyph.compact(self)
writer.endtag('TTGlyph')
writer.newline()
else:
writer.simpletag('TTGlyph', name=glyphName)
writer.comment("contains no outline data")
writer.newline()
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if name == "GlyphOrder":
glyphOrder = []
for element in content:
if type(element) == types.StringType:
continue
name, attrs, content = element
if name == "GlyphID":
index = safeEval(attrs["id"])
glyphName = attrs["name"]
glyphOrder = glyphOrder + (1 + index - len(glyphOrder)) * [".notdef"]
glyphOrder[index] = glyphName
ttFont.setGlyphOrder(glyphOrder)
elif name == "TTGlyph":
if not hasattr(self, "glyphs"):
self.glyphs = {}
glyphName = attrs["name"]
if ttFont.verbose:
ttLib.debugmsg("unpacking glyph '%s'" % glyphName)
glyph = Glyph()
for attr in ['xMin', 'yMin', 'xMax', 'yMax']:
setattr(glyph, attr, safeEval(attrs.get(attr, '0')))
self.glyphs[glyphName] = glyph
for element in content:
if type(element) == types.StringType:
continue
glyph.fromXML(element, ttFont)
def setGlyphOrder(self, glyphOrder):
self.glyphOrder = glyphOrder
def getGlyphName(self, glyphID):
return self.glyphOrder[glyphID]
def getGlyphID(self, glyphName):
# XXX optimize with reverse dict!!!
return self.glyphOrder.index(glyphName)
#def keys(self):
# return self.glyphOrder[:]
#
#def has_key(self, glyphName):
# return self.glyphs.has_key(glyphName)
#
def __getitem__(self, glyphName):
glyph = self.glyphs[glyphName]
glyph.expand(self)
return glyph
def __setitem__(self, glyphName, glyph):
self.glyphs[glyphName] = glyph
if glyphName not in self.glyphOrder:
self.glyphOrder.append(glyphName)
def __delitem__(self, glyphName):
del self.glyphs[glyphName]
self.glyphOrder.remove(glyphName)
def __len__(self):
assert len(self.glyphOrder) == len(self.glyphs)
return len(self.glyphs)
glyphHeaderFormat = """
> # big endian
numberOfContours: h
xMin: h
yMin: h
xMax: h
yMax: h
"""
# flags
flagOnCurve = 0x01
flagXShort = 0x02
flagYShort = 0x04
flagRepeat = 0x08
flagXsame = 0x10
flagYsame = 0x20
flagReserved1 = 0x40
flagReserved2 = 0x80
ARG_1_AND_2_ARE_WORDS = 0x0001 # if set args are words otherwise they are bytes
ARGS_ARE_XY_VALUES = 0x0002 # if set args are xy values, otherwise they are points
ROUND_XY_TO_GRID = 0x0004 # for the xy values if above is true
WE_HAVE_A_SCALE = 0x0008 # Sx = Sy, otherwise scale == 1.0
NON_OVERLAPPING = 0x0010 # set to same value for all components (obsolete!)
MORE_COMPONENTS = 0x0020 # indicates at least one more glyph after this one
WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Sx, Sy
WE_HAVE_A_TWO_BY_TWO = 0x0080 # t00, t01, t10, t11
WE_HAVE_INSTRUCTIONS = 0x0100 # instructions follow
USE_MY_METRICS = 0x0200 # apply these metrics to parent glyph
OVERLAP_COMPOUND = 0x0400 # used by Apple in GX fonts
SCALED_COMPONENT_OFFSET = 0x0800 # composite designed to have the component offset scaled (designed for Apple)
UNSCALED_COMPONENT_OFFSET = 0x1000 # composite designed not to have the component offset scaled (designed for MS)
class Glyph:
def __init__(self, data=""):
if not data:
# empty char
self.numberOfContours = 0
return
self.data = data
def compact(self, glyfTable):
data = self.compile(glyfTable)
self.__dict__.clear()
self.data = data
def expand(self, glyfTable):
if not hasattr(self, "data"):
# already unpacked
return
dummy, data = sstruct.unpack2(glyphHeaderFormat, self.data, self)
del self.data
if self.numberOfContours == -1:
self.decompileComponents(data, glyfTable)
else:
self.decompileCoordinates(data)
def compile(self, glyfTable):
if hasattr(self, "data"):
return self.data
if self.numberOfContours == 0:
return ""
self.recalcBounds(glyfTable)
data = sstruct.pack(glyphHeaderFormat, self)
if self.numberOfContours == -1:
data = data + self.compileComponents(glyfTable)
else:
data = data + self.compileCoordinates()
# from the spec: "Note that the local offsets should be word-aligned"
if len(data) % 2:
# ...so if the length of the data is odd, append a null byte
data = data + "\0"
return data
def toXML(self, writer, ttFont):
if self.numberOfContours == -1:
for compo in self.components:
compo.toXML(writer, ttFont)
if hasattr(self, "instructions"):
writer.begintag("instructions")
writer.newline()
writer.dumphex(self.instructions)
writer.endtag("instructions")
writer.newline()
else:
last = 0
for i in range(self.numberOfContours):
writer.begintag("contour")
writer.newline()
for j in range(last, self.endPtsOfContours[i] + 1):
writer.simpletag("pt", [
("x", self.coordinates[j][0]),
("y", self.coordinates[j][1]),
("on", self.flags[j] & flagOnCurve)])
writer.newline()
last = self.endPtsOfContours[i] + 1
writer.endtag("contour")
writer.newline()
if self.numberOfContours:
writer.begintag("instructions")
writer.newline()
writer.dumphex(self.instructions)
writer.endtag("instructions")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if name == "contour":
self.numberOfContours = self.numberOfContours + 1
if self.numberOfContours < 0:
raise ttLib.TTLibError, "can't mix composites and contours in glyph"
coordinates = []
flags = []
for element in content:
if type(element) == types.StringType:
continue
name, attrs, content = element
if name <> "pt":
continue # ignore anything but "pt"
coordinates.append([safeEval(attrs["x"]), safeEval(attrs["y"])])
flags.append(not not safeEval(attrs["on"]))
coordinates = Numeric.array(coordinates, Numeric.Int16)
flags = Numeric.array(flags, Numeric.Int8)
if not hasattr(self, "coordinates"):
self.coordinates = coordinates
self.flags = flags
self.endPtsOfContours = [len(coordinates)-1]
else:
self.coordinates = Numeric.concatenate((self.coordinates, coordinates))
self.flags = Numeric.concatenate((self.flags, flags))
self.endPtsOfContours.append(len(self.coordinates)-1)
elif name == "component":
if self.numberOfContours > 0:
raise ttLib.TTLibError, "can't mix composites and contours in glyph"
self.numberOfContours = -1
if not hasattr(self, "components"):
self.components = []
component = GlyphComponent()
self.components.append(component)
component.fromXML((name, attrs, content), ttFont)
elif name == "instructions":
self.instructions = readHex(content)
def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
assert self.numberOfContours == -1
nContours = 0
nPoints = 0
for compo in self.components:
baseGlyph = glyfTable[compo.glyphName]
if baseGlyph.numberOfContours == 0:
continue
elif baseGlyph.numberOfContours > 0:
nP, nC = baseGlyph.getMaxpValues()
else:
nP, nC, maxComponentDepth = baseGlyph.getCompositeMaxpValues(
glyfTable, maxComponentDepth + 1)
nPoints = nPoints + nP
nContours = nContours + nC
return nPoints, nContours, maxComponentDepth
def getMaxpValues(self):
assert self.numberOfContours > 0
return len(self.coordinates), len(self.endPtsOfContours)
def decompileComponents(self, data, glyfTable):
self.components = []
more = 1
haveInstructions = 0
while more:
component = GlyphComponent()
more, haveInstr, data = component.decompile(data, glyfTable)
haveInstructions = haveInstructions | haveInstr
self.components.append(component)
if haveInstructions:
numInstructions, = struct.unpack(">h", data[:2])
data = data[2:]
self.instructions = data[:numInstructions]
data = data[numInstructions:]
assert len(data) in (0, 1), "bad composite data"
def decompileCoordinates(self, data):
endPtsOfContours = array.array("h")
endPtsOfContours.fromstring(data[:2*self.numberOfContours])
if ttLib.endian <> "big":
endPtsOfContours.byteswap()
self.endPtsOfContours = endPtsOfContours.tolist()
data = data[2*self.numberOfContours:]
instructionLength, = struct.unpack(">h", data[:2])
data = data[2:]
self.instructions = data[:instructionLength]
data = data[instructionLength:]
nCoordinates = self.endPtsOfContours[-1] + 1
flags, xCoordinates, yCoordinates = \
self.decompileCoordinatesRaw(nCoordinates, data)
# fill in repetitions and apply signs
coordinates = Numeric.zeros((nCoordinates, 2), Numeric.Int16)
xIndex = 0
yIndex = 0
for i in range(nCoordinates):
flag = flags[i]
# x coordinate
if flag & flagXShort:
if flag & flagXsame:
x = xCoordinates[xIndex]
else:
x = -xCoordinates[xIndex]
xIndex = xIndex + 1
elif flag & flagXsame:
x = 0
else:
x = xCoordinates[xIndex]
xIndex = xIndex + 1
# y coordinate
if flag & flagYShort:
if flag & flagYsame:
y = yCoordinates[yIndex]
else:
y = -yCoordinates[yIndex]
yIndex = yIndex + 1
elif flag & flagYsame:
y = 0
else:
y = yCoordinates[yIndex]
yIndex = yIndex + 1
coordinates[i] = (x, y)
assert xIndex == len(xCoordinates)
assert yIndex == len(yCoordinates)
# convert relative to absolute coordinates
self.coordinates = Numeric.add.accumulate(coordinates)
# discard all flags but for "flagOnCurve"
if hasattr(Numeric, "__version__"):
self.flags = Numeric.bitwise_and(flags, flagOnCurve).astype(Numeric.Int8)
else:
self.flags = Numeric.boolean_and(flags, flagOnCurve).astype(Numeric.Int8)
def decompileCoordinatesRaw(self, nCoordinates, data):
# unpack flags and prepare unpacking of coordinates
flags = Numeric.array([0] * nCoordinates, Numeric.Int8)
# Warning: deep Python trickery going on. We use the struct module to unpack
# the coordinates. We build a format string based on the flags, so we can
# unpack the coordinates in one struct.unpack() call.
xFormat = ">" # big endian
yFormat = ">" # big endian
i = j = 0
while 1:
flag = ord(data[i])
i = i + 1
repeat = 1
if flag & flagRepeat:
repeat = ord(data[i]) + 1
i = i + 1
for k in range(repeat):
if flag & flagXShort:
xFormat = xFormat + 'B'
elif not (flag & flagXsame):
xFormat = xFormat + 'h'
if flag & flagYShort:
yFormat = yFormat + 'B'
elif not (flag & flagYsame):
yFormat = yFormat + 'h'
flags[j] = flag
j = j + 1
if j >= nCoordinates:
break
assert j == nCoordinates, "bad glyph flags"
data = data[i:]
# unpack raw coordinates, krrrrrr-tching!
xDataLen = struct.calcsize(xFormat)
yDataLen = struct.calcsize(yFormat)
if (len(data) - (xDataLen + yDataLen)) not in (0, 1):
raise ttLib.TTLibError, "bad glyph record"
xCoordinates = struct.unpack(xFormat, data[:xDataLen])
yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
return flags, xCoordinates, yCoordinates
def compileComponents(self, glyfTable):
data = ""
lastcomponent = len(self.components) - 1
more = 1
haveInstructions = 0
for i in range(len(self.components)):
if i == lastcomponent:
haveInstructions = hasattr(self, "instructions")
more = 0
compo = self.components[i]
data = data + compo.compile(more, haveInstructions, glyfTable)
if haveInstructions:
data = data + struct.pack(">h", len(self.instructions)) + self.instructions
return data
def compileCoordinates(self):
assert len(self.coordinates) == len(self.flags)
data = ""
endPtsOfContours = array.array("h", self.endPtsOfContours)
if ttLib.endian <> "big":
endPtsOfContours.byteswap()
data = data + endPtsOfContours.tostring()
data = data + struct.pack(">h", len(self.instructions))
data = data + self.instructions
nCoordinates = len(self.coordinates)
# make a copy
coordinates = self.coordinates.astype(self.coordinates.typecode())
# absolute to relative coordinates
coordinates[1:] = Numeric.subtract(coordinates[1:], coordinates[:-1])
flags = self.flags
compressedflags = []
xPoints = []
yPoints = []
xFormat = ">"
yFormat = ">"
lastflag = None
repeat = 0
for i in range(len(coordinates)):
# Oh, the horrors of TrueType
flag = self.flags[i]
x, y = coordinates[i]
# do x
if x == 0:
flag = flag | flagXsame
elif -255 <= x <= 255:
flag = flag | flagXShort
if x > 0:
flag = flag | flagXsame
else:
x = -x
xPoints.append(x)
xFormat = xFormat + 'B'
else:
xPoints.append(x)
xFormat = xFormat + 'h'
# do y
if y == 0:
flag = flag | flagYsame
elif -255 <= y <= 255:
flag = flag | flagYShort
if y > 0:
flag = flag | flagYsame
else:
y = -y
yPoints.append(y)
yFormat = yFormat + 'B'
else:
yPoints.append(y)
yFormat = yFormat + 'h'
# handle repeating flags
if flag == lastflag:
repeat = repeat + 1
if repeat == 1:
compressedflags.append(flag)
elif repeat > 1:
compressedflags[-2] = flag | flagRepeat
compressedflags[-1] = repeat
else:
compressedflags[-1] = repeat
else:
repeat = 0
compressedflags.append(flag)
lastflag = flag
data = data + array.array("B", compressedflags).tostring()
data = data + apply(struct.pack, (xFormat,)+tuple(xPoints))
data = data + apply(struct.pack, (yFormat,)+tuple(yPoints))
return data
def recalcBounds(self, glyfTable):
coordinates, endPts, flags = self.getCoordinates(glyfTable)
self.xMin, self.yMin = Numeric.minimum.reduce(coordinates)
self.xMax, self.yMax = Numeric.maximum.reduce(coordinates)
def getCoordinates(self, glyfTable):
if self.numberOfContours > 0:
return self.coordinates, self.endPtsOfContours, self.flags
elif self.numberOfContours == -1:
# it's a composite
allCoords = None
allFlags = None
allEndPts = None
for compo in self.components:
g = glyfTable[compo.glyphName]
coordinates, endPts, flags = g.getCoordinates(glyfTable)
if hasattr(compo, "firstpt"):
# move according to two reference points
move = allCoords[compo.firstpt] - coordinates[compo.secondpt]
else:
move = compo.x, compo.y
if not hasattr(compo, "transform"):
coordinates = coordinates + move # I love NumPy!
else:
apple_way = compo.flags & SCALED_COMPONENT_OFFSET
ms_way = compo.flags & UNSCALED_COMPONENT_OFFSET
assert not (apple_way and ms_way)
if not (apple_way or ms_way):
scale_component_offset = SCALE_COMPONENT_OFFSET_DEFAULT # see top of this file
else:
scale_component_offset = apple_way
if scale_component_offset:
# the Apple way: first move, then scale (ie. scale the component offset)
coordinates = coordinates + move
coordinates = Numeric.dot(coordinates, compo.transform)
else:
# the MS way: first scale, then move
coordinates = Numeric.dot(coordinates, compo.transform)
coordinates = coordinates + move
# due to the transformation the coords. are now floats;
# round them off nicely, and cast to short
coordinates = Numeric.floor(coordinates + 0.5).astype(Numeric.Int16)
if allCoords is None:
allCoords = coordinates
allEndPts = endPts
allFlags = flags
else:
allEndPts = allEndPts + (Numeric.array(endPts) + len(allCoords)).tolist()
allCoords = Numeric.concatenate((allCoords, coordinates))
allFlags = Numeric.concatenate((allFlags, flags))
return allCoords, allEndPts, allFlags
else:
return Numeric.array([], Numeric.Int16), [], Numeric.array([], Numeric.Int8)
def __cmp__(self, other):
if self.numberOfContours <= 0:
return cmp(self.__dict__, other.__dict__)
else:
if cmp(len(self.coordinates), len(other.coordinates)):
return 1
ctest = Numeric.alltrue(Numeric.alltrue(Numeric.equal(self.coordinates, other.coordinates)))
ftest = Numeric.alltrue(Numeric.equal(self.flags, other.flags))
if not ctest or not ftest:
return 1
return (
cmp(self.endPtsOfContours, other.endPtsOfContours) or
cmp(self.instructions, other.instructions)
)
class GlyphComponent:
def __init__(self):
pass
def decompile(self, data, glyfTable):
flags, glyphID = struct.unpack(">HH", data[:4])
self.flags = int(flags)
glyphID = int(glyphID)
self.glyphName = glyfTable.getGlyphName(int(glyphID))
#print ">>", reprflag(self.flags)
data = data[4:]
if self.flags & ARG_1_AND_2_ARE_WORDS:
if self.flags & ARGS_ARE_XY_VALUES:
self.x, self.y = struct.unpack(">hh", data[:4])
else:
x, y = struct.unpack(">HH", data[:4])
self.firstpt, self.secondpt = int(x), int(y)
data = data[4:]
else:
if self.flags & ARGS_ARE_XY_VALUES:
self.x, self.y = struct.unpack(">bb", data[:2])
else:
x, y = struct.unpack(">BB", data[:4])
self.firstpt, self.secondpt = int(x), int(y)
data = data[2:]
if self.flags & WE_HAVE_A_SCALE:
scale, = struct.unpack(">h", data[:2])
self.transform = Numeric.array(
[[scale, 0], [0, scale]]) / float(0x4000) # fixed 2.14
data = data[2:]
elif self.flags & WE_HAVE_AN_X_AND_Y_SCALE:
xscale, yscale = struct.unpack(">hh", data[:4])
self.transform = Numeric.array(
[[xscale, 0], [0, yscale]]) / float(0x4000) # fixed 2.14
data = data[4:]
elif self.flags & WE_HAVE_A_TWO_BY_TWO:
(xscale, scale01,
scale10, yscale) = struct.unpack(">hhhh", data[:8])
self.transform = Numeric.array(
[[xscale, scale01], [scale10, yscale]]) / float(0x4000) # fixed 2.14
data = data[8:]
more = self.flags & MORE_COMPONENTS
haveInstructions = self.flags & WE_HAVE_INSTRUCTIONS
self.flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
NON_OVERLAPPING)
return more, haveInstructions, data
def compile(self, more, haveInstructions, glyfTable):
data = ""
# reset all flags we will calculate ourselves
flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS |
SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET |
NON_OVERLAPPING)
if more:
flags = flags | MORE_COMPONENTS
if haveInstructions:
flags = flags | WE_HAVE_INSTRUCTIONS
if hasattr(self, "firstpt"):
if (0 <= self.firstpt <= 255) and (0 <= self.secondpt <= 255):
data = data + struct.pack(">BB", self.firstpt, self.secondpt)
else:
data = data + struct.pack(">HH", self.firstpt, self.secondpt)
flags = flags | ARG_1_AND_2_ARE_WORDS
else:
flags = flags | ARGS_ARE_XY_VALUES
if (-128 <= self.x <= 127) and (-128 <= self.y <= 127):
data = data + struct.pack(">bb", self.x, self.y)
else:
data = data + struct.pack(">hh", self.x, self.y)
flags = flags | ARG_1_AND_2_ARE_WORDS
if hasattr(self, "transform"):
# XXX needs more testing
transform = Numeric.floor(self.transform * 0x4000 + 0.5)
if transform[0][1] or transform[1][0]:
flags = flags | WE_HAVE_A_TWO_BY_TWO
data = data + struct.pack(">hhhh",
transform[0][0], transform[0][1],
transform[1][0], transform[1][1])
elif transform[0][0] <> transform[1][1]:
flags = flags | WE_HAVE_AN_X_AND_Y_SCALE
data = data + struct.pack(">hh",
transform[0][0], transform[1][1])
else:
flags = flags | WE_HAVE_A_SCALE
data = data + struct.pack(">h",
transform[0][0])
glyphID = glyfTable.getGlyphID(self.glyphName)
return struct.pack(">HH", flags, glyphID) + data
def toXML(self, writer, ttFont):
attrs = [("glyphName", self.glyphName)]
if not hasattr(self, "firstpt"):
attrs = attrs + [("x", self.x), ("y", self.y)]
else:
attrs = attrs + [("firstpt", self.firstpt), ("secondpt", self.secondpt)]
if hasattr(self, "transform"):
# XXX needs more testing
transform = self.transform
if transform[0][1] or transform[1][0]:
attrs = attrs + [
("scalex", transform[0][0]), ("scale01", transform[0][1]),
("scale10", transform[1][0]), ("scaley", transform[1][1]),
]
elif transform[0][0] <> transform[1][1]:
attrs = attrs + [
("scalex", transform[0][0]), ("scaley", transform[1][1]),
]
else:
attrs = attrs + [("scale", transform[0][0])]
attrs = attrs + [("flags", hex(self.flags))]
writer.simpletag("component", attrs)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.glyphName = attrs["glyphName"]
if attrs.has_key("firstpt"):
self.firstpt = safeEval(attrs["firstpt"])
self.secondpt = safeEval(attrs["secondpt"])
else:
self.x = safeEval(attrs["x"])
self.y = safeEval(attrs["y"])
if attrs.has_key("scale01"):
scalex = safeEval(attrs["scalex"])
scale01 = safeEval(attrs["scale01"])
scale10 = safeEval(attrs["scale10"])
scaley = safeEval(attrs["scaley"])
self.transform = Numeric.array([[scalex, scale01], [scale10, scaley]])
elif attrs.has_key("scalex"):
scalex = safeEval(attrs["scalex"])
scaley = safeEval(attrs["scaley"])
self.transform = Numeric.array([[scalex, 0], [0, scaley]])
elif attrs.has_key("scale"):
scale = safeEval(attrs["scale"])
self.transform = Numeric.array([[scale, 0], [0, scale]])
self.flags = safeEval(attrs["flags"])
def __cmp__(self, other):
if hasattr(self, "transform"):
if Numeric.alltrue(Numeric.equal(self.transform, other.transform)):
selfdict = self.__dict__.copy()
otherdict = other.__dict__.copy()
del selfdict["transform"]
del otherdict["transform"]
return cmp(selfdict, otherdict)
else:
return 1
else:
return cmp(self.__dict__, other.__dict__)
def reprflag(flag):
bin = ""
if type(flag) == types.StringType:
flag = ord(flag)
while flag:
if flag & 0x01:
bin = "1" + bin
else:
bin = "0" + bin
flag = flag >> 1
bin = (14 - len(bin)) * "0" + bin
return bin

View File

@ -0,0 +1,95 @@
import DefaultTable
import sstruct
import string
hdmxHeaderFormat = """
version: H
numRecords: H
recordSize: l
"""
class table__h_d_m_x(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
numGlyphs = ttFont['maxp'].numGlyphs
glyphOrder = ttFont.getGlyphOrder()
dummy, data = sstruct.unpack2(hdmxHeaderFormat, data, self)
self.hdmx = {}
for i in range(self.numRecords):
ppem = ord(data[0])
maxSize = ord(data[1])
widths = {}
for glyphID in range(numGlyphs):
widths[glyphOrder[glyphID]] = ord(data[glyphID+2])
self.hdmx[ppem] = widths
data = data[self.recordSize:]
assert len(data) == 0, "too much hdmx data"
def compile(self, ttFont):
self.version = 0
numGlyphs = ttFont['maxp'].numGlyphs
glyphOrder = ttFont.getGlyphOrder()
self.recordSize = 4 * ((2 + numGlyphs + 3) / 4)
pad = (self.recordSize - 2 - numGlyphs) * "\0"
self.numRecords = len(self.hdmx)
data = sstruct.pack(hdmxHeaderFormat, self)
items = self.hdmx.items()
items.sort()
for ppem, widths in items:
data = data + chr(ppem) + chr(max(widths.values()))
for glyphID in range(len(glyphOrder)):
width = widths[glyphOrder[glyphID]]
data = data + chr(width)
data = data + pad
return data
def toXML(self, writer, ttFont):
writer.begintag("hdmxData")
writer.newline()
ppems = self.hdmx.keys()
ppems.sort()
records = []
format = ""
for ppem in ppems:
widths = self.hdmx[ppem]
records.append(widths)
format = format + "%4d"
glyphNames = ttFont.getGlyphOrder()[:]
glyphNames.sort()
maxNameLen = max(map(len, glyphNames))
format = "%" + `maxNameLen` + 's:' + format + ' ;'
writer.write(format % (("ppem",) + tuple(ppems)))
writer.newline()
writer.newline()
for glyphName in glyphNames:
row = []
for ppem in ppems:
widths = self.hdmx[ppem]
row.append(widths[glyphName])
writer.write(format % ((glyphName,) + tuple(row)))
writer.newline()
writer.endtag("hdmxData")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if name <> "hdmxData":
return
content = string.join(content, " ")
lines = string.split(content, ";")
topRow = string.split(lines[0])
assert topRow[0] == "ppem:", "illegal hdmx format"
ppems = map(int, topRow[1:])
self.hdmx = hdmx = {}
for ppem in ppems:
hdmx[ppem] = {}
lines = map(string.split, lines[1:])
for line in lines:
if not line:
continue
assert line[0][-1] == ":", "illegal hdmx format"
glyphName = line[0][:-1]
line = map(int, line[1:])
assert len(line) == len(ppems), "illegal hdmx format"
for i in range(len(ppems)):
hdmx[ppems[i]][glyphName] = line[i]

View File

@ -0,0 +1,136 @@
import DefaultTable
import sstruct
import time
import string
import calendar
from fontTools.misc.textTools import safeEval, num2binary, binary2num
headFormat = """
> # big endian
tableVersion: 16.16F
fontRevision: 16.16F
checkSumAdjustment: l
magicNumber: l
x # pad byte
flags: b
unitsPerEm: H
created: 8s
modified: 8s
xMin: h
yMin: h
xMax: h
yMax: h
macStyle: H
lowestRecPPEM: H
fontDirectionHint: h
indexToLocFormat: h
glyphDataFormat: h
"""
class table__h_e_a_d(DefaultTable.DefaultTable):
dependencies = ['maxp', 'loca']
def decompile(self, data, ttFont):
sstruct.unpack(headFormat, data, self)
self.unitsPerEm = int(self.unitsPerEm)
self.strings2dates()
def compile(self, ttFont):
self.modified = long(time.time() - mac_epoch_diff)
self.dates2strings()
data = sstruct.pack(headFormat, self)
self.strings2dates()
return data
def strings2dates(self):
self.created = bin2long(self.created)
self.modified = bin2long(self.modified)
def dates2strings(self):
self.created = long2bin(self.created)
self.modified = long2bin(self.modified)
def toXML(self, writer, ttFont):
writer.comment("Most of this table will be recalculated by the compiler")
writer.newline()
formatstring, names, fixes = sstruct.getformat(headFormat)
for name in names:
value = getattr(self, name)
if name in ("created", "modified"):
value = time.asctime(time.gmtime(max(0, value + mac_epoch_diff)))
if type(value) == type(0L):
value=int(value)
if name in ("magicNumber", "checkSumAdjustment"):
value = hex(value)
elif name == "flags":
value = num2binary(value, 16)
writer.simpletag(name, value=value)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
value = attrs["value"]
if name in ("created", "modified"):
value = parse_date(value) - mac_epoch_diff
elif name == "flags":
value = binary2num(value)
else:
value = safeEval(value)
setattr(self, name, value)
def __cmp__(self, other):
selfdict = self.__dict__.copy()
otherdict = other.__dict__.copy()
# for testing purposes, compare without the modified and checkSumAdjustment
# fields, since they are allowed to be different.
for key in ["modified", "checkSumAdjustment"]:
del selfdict[key]
del otherdict[key]
return cmp(selfdict, otherdict)
def calc_mac_epoch_diff():
"""calculate the difference between the original Mac epoch (1904)
to the epoch on this machine.
"""
safe_epoch_t = (1971, 1, 1, 0, 0, 0, 0, 0, 0)
safe_epoch = time.mktime(safe_epoch_t) - time.timezone
assert time.gmtime(safe_epoch)[:6] == safe_epoch_t[:6]
seconds1904to1971 = 60 * 60 * 24 * (365 * (1971-1904) + 17) # thanks, Laurence!
return long(safe_epoch - seconds1904to1971)
mac_epoch_diff = calc_mac_epoch_diff()
_months = map(string.lower, calendar.month_abbr)
_weekdays = map(string.lower, calendar.day_abbr)
def parse_date(datestring):
datestring = string.lower(datestring)
weekday, month, day, tim, year = string.split(datestring)
weekday = _weekdays.index(weekday)
month = _months.index(month)
year = int(year)
day = int(day)
hour, minute, second = map(int, string.split(tim, ":"))
t = (year, month, day, hour, minute, second, weekday, 0, 0)
return long(time.mktime(t) - time.timezone)
def bin2long(data):
# thanks </F>!
v = 0L
for i in map(ord, data):
v = v<<8 | i
return v
def long2bin(v, bytes=8):
data = ""
while v:
data = chr(v & 0xff) + data
v = v >> 8
data = (bytes - len(data)) * "\0" + data
assert len(data) == 8, "long too long"
return data

View File

@ -0,0 +1,78 @@
import DefaultTable
import sstruct
from fontTools.misc.textTools import safeEval
hheaFormat = """
> # big endian
tableVersion: 16.16F
ascent: h
descent: h
lineGap: h
advanceWidthMax: H
minLeftSideBearing: h
minRightSideBearing: h
xMaxExtent: h
caretSlopeRise: h
caretSlopeRun: h
reserved0: h
reserved1: h
reserved2: h
reserved3: h
reserved4: h
metricDataFormat: h
numberOfHMetrics: H
"""
class table__h_h_e_a(DefaultTable.DefaultTable):
dependencies = ['hmtx', 'glyf']
def decompile(self, data, ttFont):
sstruct.unpack(hheaFormat, data, self)
def compile(self, ttFont):
self.recalc(ttFont)
return sstruct.pack(hheaFormat, self)
def recalc(self, ttFont):
hmtxTable = ttFont['hmtx']
if ttFont.has_key('glyf'):
if not ttFont.isLoaded('glyf'):
return
glyfTable = ttFont['glyf']
advanceWidthMax = -100000 # arbitrary big negative number
minLeftSideBearing = 100000 # arbitrary big number
minRightSideBearing = 100000 # arbitrary big number
xMaxExtent = -100000 # arbitrary big negative number
for name in ttFont.getGlyphOrder():
width, lsb = hmtxTable[name]
g = glyfTable[name]
if g.numberOfContours <= 0:
continue
advanceWidthMax = max(advanceWidthMax, width)
minLeftSideBearing = min(minLeftSideBearing, lsb)
rsb = width - lsb - (g.xMax - g.xMin)
minRightSideBearing = min(minRightSideBearing, rsb)
extent = lsb + (g.xMax - g.xMin)
xMaxExtent = max(xMaxExtent, extent)
self.advanceWidthMax = advanceWidthMax
self.minLeftSideBearing = minLeftSideBearing
self.minRightSideBearing = minRightSideBearing
self.xMaxExtent = xMaxExtent
else:
# XXX CFF recalc...
pass
def toXML(self, writer, ttFont):
formatstring, names, fixes = sstruct.getformat(hheaFormat)
for name in names:
value = getattr(self, name)
if type(value) == type(0L):
value = int(value)
writer.simpletag(name, value=value)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
setattr(self, name, safeEval(attrs["value"]))

View File

@ -0,0 +1,94 @@
import DefaultTable
import Numeric
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
class table__h_m_t_x(DefaultTable.DefaultTable):
headerTag = 'hhea'
advanceName = 'width'
sideBearingName = 'lsb'
numberOfMetricsName = 'numberOfHMetrics'
def decompile(self, data, ttFont):
numberOfMetrics = int(getattr(ttFont[self.headerTag], self.numberOfMetricsName))
metrics = Numeric.fromstring(data[:4 * numberOfMetrics],
Numeric.Int16)
if ttLib.endian <> "big":
metrics = metrics.byteswapped()
metrics.shape = (numberOfMetrics, 2)
data = data[4 * numberOfMetrics:]
numberOfSideBearings = ttFont['maxp'].numGlyphs - numberOfMetrics
numberOfSideBearings = int(numberOfSideBearings)
if numberOfSideBearings:
assert numberOfSideBearings > 0, "bad hmtx/vmtx table"
lastAdvance = metrics[-1][0]
advances = Numeric.array([lastAdvance] * numberOfSideBearings,
Numeric.Int16)
sideBearings = Numeric.fromstring(data[:2 * numberOfSideBearings],
Numeric.Int16)
if ttLib.endian <> "big":
sideBearings = sideBearings.byteswapped()
data = data[2 * numberOfSideBearings:]
additionalMetrics = Numeric.array([advances, sideBearings],
Numeric.Int16)
metrics = Numeric.concatenate((metrics,
Numeric.transpose(additionalMetrics)))
if data:
raise ttLib.TTLibError, "too much data for hmtx/vmtx table"
metrics = metrics.tolist()
self.metrics = {}
for i in range(len(metrics)):
glyphName = ttFont.getGlyphName(i)
self.metrics[glyphName] = metrics[i]
def compile(self, ttFont):
metrics = []
for glyphName in ttFont.getGlyphOrder():
metrics.append(self.metrics[glyphName])
lastAdvance = metrics[-1][0]
lastIndex = len(metrics)
while metrics[lastIndex-2][0] == lastAdvance:
lastIndex = lastIndex - 1
additionalMetrics = metrics[lastIndex:]
additionalMetrics = map(lambda (advance, sb): sb, additionalMetrics)
metrics = metrics[:lastIndex]
setattr(ttFont[self.headerTag], self.numberOfMetricsName, len(metrics))
metrics = Numeric.array(metrics, Numeric.Int16)
if ttLib.endian <> "big":
metrics = metrics.byteswapped()
data = metrics.tostring()
additionalMetrics = Numeric.array(additionalMetrics, Numeric.Int16)
if ttLib.endian <> "big":
additionalMetrics = additionalMetrics.byteswapped()
data = data + additionalMetrics.tostring()
return data
def toXML(self, writer, ttFont):
names = self.metrics.keys()
names.sort()
for glyphName in names:
advance, sb = self.metrics[glyphName]
writer.simpletag("mtx", [
("name", glyphName),
(self.advanceName, advance),
(self.sideBearingName, sb),
])
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if not hasattr(self, "metrics"):
self.metrics = {}
if name == "mtx":
self.metrics[attrs["name"]] = [safeEval(attrs[self.advanceName]),
safeEval(attrs[self.sideBearingName])]
def __getitem__(self, glyphName):
return self.metrics[glyphName]
def __setitem__(self, glyphName, (advance, sb)):
self.metrics[glyphName] = advance, sb

View File

@ -0,0 +1,186 @@
import DefaultTable
import struct
import ttLib.sfnt
from fontTools.misc.textTools import safeEval, readHex
class table__k_e_r_n(DefaultTable.DefaultTable):
def getkern(self, format):
for subtable in self.kernTables:
if subtable.version == format:
return subtable
return None # not found
def decompile(self, data, ttFont):
version, nTables = struct.unpack(">HH", data[:4])
if version == 1:
# Apple's new format. Hm.
version, nTables = struct.unpack(">ll", data[:8])
self.version = version / float(0x10000)
data = data[8:]
else:
self.version = version
data = data[4:]
tablesIndex = []
self.kernTables = []
for i in range(nTables):
version, length = struct.unpack(">HH", data[:4])
length = int(length)
if not kern_classes.has_key(version):
subtable = KernTable_format_unkown()
else:
subtable = kern_classes[version]()
subtable.decompile(data[:length], ttFont)
self.kernTables.append(subtable)
data = data[length:]
def compile(self, ttFont):
nTables = len(self.kernTables)
if self.version == 1.0:
# Apple's new format.
data = struct.pack(">ll", self.version * 0x1000, nTables)
else:
data = struct.pack(">HH", self.version, nTables)
for subtable in self.kernTables:
data = data + subtable.compile(ttFont)
return data
def toXML(self, writer, ttFont):
writer.simpletag("version", value=self.version)
writer.newline()
for subtable in self.kernTables:
subtable.toXML(writer, ttFont)
def fromXML(self, (name, attrs, content), ttFont):
if name == "version":
self.version = safeEval(attrs["value"])
return
if name <> "kernsubtable":
return
if not hasattr(self, "kernTables"):
self.kernTables = []
format = safeEval(attrs["format"])
if not kern_classes.has_key(format):
subtable = KernTable_format_unkown()
else:
subtable = kern_classes[format]()
self.kernTables.append(subtable)
subtable.fromXML((name, attrs, content), ttFont)
class KernTable_format_0:
def decompile(self, data, ttFont):
version, length, coverage = struct.unpack(">HHH", data[:6])
self.version, self.coverage = int(version), int(coverage)
data = data[6:]
self.kernTable = kernTable = {}
nPairs, searchRange, entrySelector, rangeShift = struct.unpack(">HHHH", data[:8])
data = data[8:]
for k in range(nPairs):
left, right, value = struct.unpack(">HHh", data[:6])
data = data[6:]
left, right = int(left), int(right)
kernTable[(ttFont.getGlyphName(left), ttFont.getGlyphName(right))] = value
assert len(data) == 0
def compile(self, ttFont):
nPairs = len(self.kernTable)
entrySelector = ttLib.sfnt.maxpoweroftwo(nPairs)
searchRange = (2 ** entrySelector) * 6
rangeShift = (nPairs - (2 ** entrySelector)) * 6
data = struct.pack(">HHHH", nPairs, searchRange, entrySelector, rangeShift)
# yeehee! (I mean, turn names into indices)
kernTable = map(lambda ((left, right), value), getGlyphID=ttFont.getGlyphID:
(getGlyphID(left), getGlyphID(right), value),
self.kernTable.items())
kernTable.sort()
for left, right, value in kernTable:
data = data + struct.pack(">HHh", left, right, value)
return struct.pack(">HHH", self.version, len(data) + 6, self.coverage) + data
def toXML(self, writer, ttFont):
writer.begintag("kernsubtable", coverage=self.coverage, format=0)
writer.newline()
items = self.kernTable.items()
items.sort()
for (left, right), value in items:
writer.simpletag("pair", [
("l", left),
("r", right),
("v", value)
])
writer.newline()
writer.endtag("kernsubtable")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.coverage = safeEval(attrs["coverage"])
self.version = safeEval(attrs["format"])
if not hasattr(self, "kernTable"):
self.kernTable = {}
for element in content:
if type(element) <> type(()):
continue
name, attrs, content = element
self.kernTable[(attrs["l"], attrs["r"])] = safeEval(attrs["v"])
def __getitem__(self, pair):
return self.kernTable[pair]
def __setitem__(self, pair, value):
self.kernTable[pair] = value
def __delitem__(self, pair):
del self.kernTable[pair]
def __cmp__(self, other):
return cmp(self.__dict__, other.__dict__)
class KernTable_format_2:
def decompile(self, data, ttFont):
self.data = data
def compile(self, ttFont, ttFont):
return data
def toXML(self, writer):
writer.begintag("kernsubtable", format=2)
writer.newline()
writer.dumphex(self.data)
writer.endtag("kernsubtable")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.decompile(readHex(content))
class KernTable_format_unkown:
def decompile(self, data, ttFont):
self.data = data
def compile(self, ttFont):
return data
def toXML(self, writer, ttFont):
writer.begintag("kernsubtable", format="-1")
writer.newline()
writer.comment("unknown 'kern' subtable format")
writer.dumphex(self.data)
writer.endtag("kernsubtable")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.decompile(readHex(content))
kern_classes = {0: KernTable_format_0, 1: KernTable_format_2}

View File

@ -0,0 +1,55 @@
import DefaultTable
import array
import Numeric
from fontTools import ttLib
import struct
class table__l_o_c_a(DefaultTable.DefaultTable):
dependencies = ['glyf']
def decompile(self, data, ttFont):
longFormat = ttFont['head'].indexToLocFormat
if longFormat:
format = "l"
else:
format = "H"
locations = array.array(format)
locations.fromstring(data)
if ttLib.endian <> "big":
locations.byteswap()
locations = Numeric.array(locations, Numeric.Int32)
if not longFormat:
locations = locations * 2
if len(locations) <> (ttFont['maxp'].numGlyphs + 1):
raise ttLib.TTLibError, "corrupt 'loca' table"
self.locations = locations
def compile(self, ttFont):
locations = self.locations
if max(locations) < 0x20000:
locations = locations / 2
locations = locations.astype(Numeric.Int16)
ttFont['head'].indexToLocFormat = 0
else:
ttFont['head'].indexToLocFormat = 1
if ttLib.endian <> "big":
locations = locations.byteswapped()
return locations.tostring()
def set(self, locations):
self.locations = Numeric.array(locations, Numeric.Int32)
def toXML(self, writer, ttFont):
writer.comment("The 'loca' table will be calculated by the compiler")
writer.newline()
def __getitem__(self, index):
return self.locations[index]
def __len__(self):
return len(self.locations)
def __cmp__(self, other):
return cmp(len(self), len(other)) or not Numeric.alltrue(Numeric.equal(self.locations, other.locations))

View File

@ -0,0 +1,138 @@
import DefaultTable
import sstruct
from fontTools.misc.textTools import safeEval
maxpFormat_0_5 = """
> # big endian
tableVersion: i
numGlyphs: H
"""
maxpFormat_1_0_add = """
> # big endian
maxPoints: H
maxContours: H
maxCompositePoints: H
maxCompositeContours: H
maxZones: H
maxTwilightPoints: H
maxStorage: H
maxFunctionDefs: H
maxInstructionDefs: H
maxStackElements: H
maxSizeOfInstructions: H
maxComponentElements: H
maxComponentDepth: H
"""
class table__m_a_x_p(DefaultTable.DefaultTable):
dependencies = ['glyf']
def decompile(self, data, ttFont):
dummy, data = sstruct.unpack2(maxpFormat_0_5, data, self)
self.numGlyphs = int(self.numGlyphs)
if self.tableVersion == 0x00010000:
dummy, data = sstruct.unpack2(maxpFormat_1_0_add, data, self)
else:
assert self.tableVersion == 0x00005000, "unknown 'maxp' format: %x" % self.tableVersion
assert len(data) == 0
def compile(self, ttFont):
if ttFont.has_key('glyf'):
if ttFont.isLoaded('glyf'):
self.recalc(ttFont)
else:
pass # XXX CFF!!!
data = sstruct.pack(maxpFormat_0_5, self)
if self.tableVersion == 0x00010000:
data = data + sstruct.pack(maxpFormat_1_0_add, self)
else:
assert self.tableVersion == 0x00005000, "unknown 'maxp' format: %f" % self.tableVersion
return data
def recalc(self, ttFont):
"""Recalculate the font bounding box, and most other maxp values except
for the TT instructions values. Also recalculate the value of bit 1
of the flags field of the 'head' table.
"""
glyfTable = ttFont['glyf']
hmtxTable = ttFont['hmtx']
headTable = ttFont['head']
self.numGlyphs = len(glyfTable)
xMin = 100000
yMin = 100000
xMax = -100000
yMax = -100000
maxPoints = 0
maxContours = 0
maxCompositePoints = 0
maxCompositeContours = 0
maxComponentElements = 0
maxComponentDepth = 0
allXMaxIsLsb = 1
for glyphName in ttFont.getGlyphOrder():
g = glyfTable[glyphName]
if g.numberOfContours:
if hmtxTable[glyphName][1] <> g.xMin:
allXMaxIsLsb = 0
xMin = min(xMin, g.xMin)
yMin = min(yMin, g.yMin)
xMax = max(xMax, g.xMax)
yMax = max(yMax, g.yMax)
if g.numberOfContours > 0:
nPoints, nContours = g.getMaxpValues()
maxPoints = max(maxPoints, nPoints)
maxContours = max(maxContours, nContours)
else:
nPoints, nContours, componentDepth = g.getCompositeMaxpValues(glyfTable)
maxCompositePoints = max(maxCompositePoints, nPoints)
maxCompositeContours = max(maxCompositeContours, nContours)
maxComponentElements = max(maxComponentElements, len(g.components))
maxComponentDepth = max(maxComponentDepth, componentDepth)
self.xMin = xMin
self.yMin = yMin
self.xMax = xMax
self.yMax = yMax
self.maxPoints = maxPoints
self.maxContours = maxContours
self.maxCompositePoints = maxCompositePoints
self.maxCompositeContours = maxCompositeContours
self.maxComponentDepth = maxComponentDepth
if allXMaxIsLsb:
headTable.flags = headTable.flags | 0x2
else:
headTable.flags = headTable.flags & ~0x2
def testrepr(self):
items = self.__dict__.items()
items.sort()
print ". . . . . . . . ."
for combo in items:
print " %s: %s" % combo
print ". . . . . . . . ."
def toXML(self, writer, ttFont):
if self.tableVersion <> 0x00005000:
writer.comment("Most of this table will be recalculated by the compiler")
writer.newline()
formatstring, names, fixes = sstruct.getformat(maxpFormat_0_5)
if self.tableVersion == 0x00010000:
formatstring, names_1_0, fixes = sstruct.getformat(maxpFormat_1_0_add)
names = names + names_1_0
else:
assert self.tableVersion == 0x00005000, "unknown 'maxp' format: %f" % self.tableVersion
for name in names:
value = getattr(self, name)
if type(value) == type(0L):
value=int(value)
if name == "tableVersion":
value = hex(value)
writer.simpletag(name, value=value)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
setattr(self, name, safeEval(attrs["value"]))

View File

@ -0,0 +1,136 @@
import DefaultTable
import struct, sstruct
from fontTools.misc.textTools import safeEval
import string
import types
nameRecordFormat = """
> # big endian
platformID: H
platEncID: H
langID: H
nameID: H
length: H
offset: H
"""
class table__n_a_m_e(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
format, n, stringoffset = struct.unpack(">HHH", data[:6])
stringoffset = int(stringoffset)
stringData = data[stringoffset:]
data = data[6:stringoffset]
self.names = []
for i in range(n):
name, data = sstruct.unpack2(nameRecordFormat, data, NameRecord())
name.fixlongs()
name.string = stringData[name.offset:name.offset+name.length]
del name.offset, name.length
self.names.append(name)
def compile(self, ttFont):
self.names.sort() # sort according to the spec; see NameRecord.__cmp__()
stringData = ""
format = 0
n = len(self.names)
stringoffset = 6 + n * sstruct.calcsize(nameRecordFormat)
data = struct.pack(">HHH", format, n, stringoffset)
lastoffset = 0
done = {} # remember the data so we can reuse the "pointers"
for name in self.names:
if done.has_key(name.string):
name.offset, name.length = done[name.string]
else:
name.offset, name.length = done[name.string] = len(stringData), len(name.string)
stringData = stringData + name.string
data = data + sstruct.pack(nameRecordFormat, name)
return data + stringData
def toXML(self, writer, ttFont):
for name in self.names:
name.toXML(writer, ttFont)
def fromXML(self, (name, attrs, content), ttFont):
if name <> "namerecord":
return # ignore unknown tags
if not hasattr(self, "names"):
self.names = []
name = NameRecord()
self.names.append(name)
name.fromXML((name, attrs, content), ttFont)
def getname(self, nameID, platformID, platEncID, langID=None):
for namerecord in self.names:
if ( namerecord.nameID == nameID and
namerecord.platformID == platformID and
namerecord.platEncID == platEncID):
if langID is None or namerecord.langID == langID:
return namerecord
return None # not found
def __cmp__(self, other):
return cmp(self.names, other.names)
class NameRecord:
def toXML(self, writer, ttFont):
writer.begintag("namerecord", [
("nameID", self.nameID),
("platformID", self.platformID),
("platEncID", self.platEncID),
("langID", hex(self.langID)),
])
writer.newline()
if self.platformID == 0 or (self.platformID == 3 and self.platEncID == 1):
writer.write16bit(self.string)
else:
writer.write8bit(self.string)
writer.newline()
writer.endtag("namerecord")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
self.nameID = safeEval(attrs["nameID"])
self.platformID = safeEval(attrs["platformID"])
self.platEncID = safeEval(attrs["platEncID"])
self.langID = safeEval(attrs["langID"])
if self.platformID == 0 or (self.platformID == 3 and self.platEncID == 1):
from fontTools.ttLib.xmlImport import UnicodeString
str = UnicodeString("")
for element in content:
str = str + element
self.string = str.stripped().tostring()
else:
self.string = string.strip(string.join(content, ""))
def __cmp__(self, other):
"""Compare method, so a list of NameRecords can be sorted
according to the spec by just sorting it..."""
selftuple = (self.platformID,
self.platEncID,
self.langID,
self.nameID,
self.string)
othertuple = (other.platformID,
other.platEncID,
other.langID,
other.nameID,
other.string)
return cmp(selftuple, othertuple)
def __repr__(self):
return "<NameRecord NameID=%d; PlatformID=%d; LanguageID=%d>" % (
self.nameID, self.platformID, self.langID)
def fixlongs(self):
"""correct effects from bug in Python 1.5.1, where "H"
returns a Python Long int.
This has been fixed in Python 1.5.2.
"""
for attr in dir(self):
val = getattr(self, attr)
if type(val) == types.LongType:
setattr(self, attr, int(val))

View File

@ -0,0 +1,214 @@
from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
import DefaultTable
import struct, sstruct
import array
from fontTools import ttLib
from fontTools.misc.textTools import safeEval, readHex
postFormat = """
>
formatType: 16.16F
italicAngle: 16.16F # italic angle in degrees
underlinePosition: h
underlineThickness: h
isFixedPitch: l
minMemType42: l # minimum memory if TrueType font is downloaded
maxMemType42: l # maximum memory if TrueType font is downloaded
minMemType1: l # minimum memory if Type1 font is downloaded
maxMemType1: l # maximum memory if Type1 font is downloaded
"""
postFormatSize = sstruct.calcsize(postFormat)
class table__p_o_s_t(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
sstruct.unpack(postFormat, data[:postFormatSize], self)
data = data[postFormatSize:]
if self.formatType == 1.0:
self.decode_format_1_0(data, ttFont)
elif self.formatType == 2.0:
self.decode_format_2_0(data, ttFont)
elif self.formatType == 3.0:
self.decode_format_3_0(data, ttFont)
else:
# supported format
raise ttLib.TTLibError, "'post' table format %f not supported" % self.formatType
def compile(self, ttFont):
data = sstruct.pack(postFormat, self)
if self.formatType == 1.0:
pass # we're done
elif self.formatType == 2.0:
data = data + self.encode_format_2_0(ttFont)
elif self.formatType == 3.0:
pass # we're done
else:
# supported format
raise ttLib.TTLibError, "'post' table format %f not supported" % self.formatType
return data
def getGlyphOrder(self):
"""This function will get called by a ttLib.TTFont instance.
Do not call this function yourself, use TTFont().getGlyphOrder()
or its relatives instead!
"""
if not hasattr(self, "glyphOrder"):
raise ttLib.TTLibError, "illegal use of getGlyphOrder()"
glyphOrder = self.glyphOrder
del self.glyphOrder
return glyphOrder
def decode_format_1_0(self, data, ttFont):
self.glyphOrder = standardGlyphOrder[:]
def decode_format_2_0(self, data, ttFont):
numGlyphs, = struct.unpack(">H", data[:2])
numGlyphs = int(numGlyphs)
data = data[2:]
indices = array.array("H")
indices.fromstring(data[:2*numGlyphs])
if ttLib.endian <> "big":
indices.byteswap()
data = data[2*numGlyphs:]
self.extraNames = extraNames = unpackPStrings(data)
self.glyphOrder = glyphOrder = [None] * int(ttFont['maxp'].numGlyphs)
for glyphID in range(numGlyphs):
index = indices[glyphID]
if index > 257:
name = extraNames[index-258]
else:
# fetch names from standard list
name = standardGlyphOrder[index]
glyphOrder[glyphID] = name
#AL990511: code added to handle the case of new glyphs without
# entries into the 'post' table
if numGlyphs < ttFont['maxp'].numGlyphs:
for i in range(numGlyphs, ttFont['maxp'].numGlyphs):
glyphOrder[i] = "glyph#%.5d" % i
self.extraNames.append(glyphOrder[i])
self.build_psNameMapping(ttFont)
def build_psNameMapping(self, ttFont):
mapping = {}
allNames = {}
for i in range(ttFont['maxp'].numGlyphs):
glyphName = psName = self.glyphOrder[i]
if allNames.has_key(glyphName):
# make up a new glyphName that's unique
n = 1
while allNames.has_key(glyphName + "#" + `n`):
n = n + 1
glyphName = glyphName + "#" + `n`
self.glyphOrder[i] = glyphName
mapping[glyphName] = psName
allNames[glyphName] = psName
self.mapping = mapping
def decode_format_3_0(self, data, ttFont):
# Setting self.glyphOrder to None will cause the TTFont object
# try and construct glyph names from a Unicode cmap table.
self.glyphOrder = None
def encode_format_2_0(self, ttFont):
numGlyphs = ttFont['maxp'].numGlyphs
glyphOrder = ttFont.getGlyphOrder()
assert len(glyphOrder) == numGlyphs
indices = array.array("H")
for glyphID in range(numGlyphs):
glyphName = glyphOrder[glyphID]
if self.mapping.has_key(glyphName):
psName = self.mapping[glyphName]
else:
psName = glyphName
if psName in self.extraNames:
index = 258 + self.extraNames.index(psName)
elif psName in standardGlyphOrder:
index = standardGlyphOrder.index(psName)
else:
index = 258 + len(self.extraNames)
extraNames.append(psName)
indices.append(index)
if ttLib.endian <> "big":
indices.byteswap()
return struct.pack(">H", numGlyphs) + indices.tostring() + packPStrings(self.extraNames)
def toXML(self, writer, ttFont):
formatstring, names, fixes = sstruct.getformat(postFormat)
for name in names:
value = getattr(self, name)
writer.simpletag(name, value=value)
writer.newline()
if hasattr(self, "mapping"):
writer.begintag("psNames")
writer.newline()
writer.comment("This file uses unique glyph names based on the information\n"
"found in the 'post' table. Since these names might not be unique,\n"
"we have to invent artificial names in case of clashes. In order to\n"
"be able to retain the original information, we need a name to\n"
"ps name mapping for those cases where they differ. That's what\n"
"you see below.\n")
writer.newline()
items = self.mapping.items()
items.sort()
for name, psName in items:
writer.simpletag("psName", name=name, psName=psName)
writer.newline()
writer.endtag("psNames")
writer.newline()
if hasattr(self, "extraNames"):
writer.begintag("extraNames")
writer.newline()
writer.comment("following are the name that are not taken from the standard Mac glyph order")
writer.newline()
for name in self.extraNames:
writer.simpletag("psName", name=name)
writer.newline()
writer.endtag("extraNames")
writer.newline()
if hasattr(self, "data"):
writer.begintag("hexdata")
writer.newline()
writer.dumphex(self.data)
writer.endtag("hexdata")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if name not in ("psNames", "extraNames", "hexdata"):
setattr(self, name, safeEval(attrs["value"]))
elif name == "psNames":
self.mapping = {}
for element in content:
if type(element) <> type(()):
continue
name, attrs, content = element
if name == "psName":
self.mapping[attrs["name"]] = attrs["psName"]
elif name == "extraNames":
self.extraNames = []
for element in content:
if type(element) <> type(()):
continue
name, attrs, content = element
if name == "psName":
self.extraNames.append(attrs["name"])
else:
self.data = readHex(content)
def unpackPStrings(data):
strings = []
while data:
length = ord(data[0])
strings.append(data[1:1+length])
data = data[1+length:]
return strings
def packPStrings(strings):
data = ""
for s in strings:
data = data + chr(len(s)) + s
return data

View File

@ -0,0 +1,14 @@
import DefaultTable
import array
class table__p_r_e_p(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
self.prep = data
def compile(self, ttFont):
return self.prep
def __len__(self):
return len(self.prep)

View File

@ -0,0 +1,78 @@
import DefaultTable
import sstruct
from fontTools.misc.textTools import safeEval
vheaFormat = """
> # big endian
tableVersion: 16.16F
ascent: h
descent: h
lineGap: h
advanceHeightMax: H
minTopSideBearing: h
minBottomSideBearing: h
yMaxExtent: h
caretSlopeRise: h
caretSlopeRun: h
reserved0: h
reserved1: h
reserved2: h
reserved3: h
reserved4: h
metricDataFormat: h
numberOfVMetrics: H
"""
class table__v_h_e_a(DefaultTable.DefaultTable):
dependencies = ['vmtx', 'glyf']
def decompile(self, data, ttFont):
sstruct.unpack(vheaFormat, data, self)
def compile(self, ttFont):
self.recalc(ttFont)
return sstruct.pack(vheaFormat, self)
def recalc(self, ttFont):
vtmxTable = ttFont['vmtx']
if ttFont.has_key('glyf'):
if not ttFont.isLoaded('glyf'):
return
glyfTable = ttFont['glyf']
advanceHeightMax = -100000 # arbitrary big negative number
minTopSideBearing = 100000 # arbitrary big number
minBottomSideBearing = 100000 # arbitrary big number
yMaxExtent = -100000 # arbitrary big negative number
for name in ttFont.getGlyphOrder():
height, tsb = vtmxTable[name]
g = glyfTable[name]
if g.numberOfContours <= 0:
continue
advanceHeightMax = max(advanceHeightMax, height)
minTopSideBearing = min(minTopSideBearing, tsb)
rsb = height - tsb - (g.yMax - g.yMin)
minBottomSideBearing = min(minBottomSideBearing, rsb)
extent = tsb + (g.yMax - g.yMin)
yMaxExtent = max(yMaxExtent, extent)
self.advanceHeightMax = advanceHeightMax
self.minTopSideBearing = minTopSideBearing
self.minBottomSideBearing = minBottomSideBearing
self.yMaxExtent = yMaxExtent
else:
# XXX CFF recalc...
pass
def toXML(self, writer, ttFont):
formatstring, names, fixes = sstruct.getformat(vheaFormat)
for name in names:
value = getattr(self, name)
if type(value) == type(0L):
value = int(value)
writer.simpletag(name, value=value)
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
setattr(self, name, safeEval(attrs["value"]))

View File

@ -0,0 +1,14 @@
import DefaultTable
import Numeric
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
superclass = ttLib.getTableClass("hmtx")
class table__v_m_t_x(superclass):
headerTag = 'vhea'
advanceName = 'height'
sideBearingName = 'tsb'
numberOfMetricsName = 'numberOfVMetrics'

View File

@ -0,0 +1,605 @@
"""ttLib.tables.otCommon.py -- Various data structures used by various OpenType tables.
"""
import struct, sstruct
import DefaultTable
from fontTools import ttLib
class base_GPOS_GSUB(DefaultTable.DefaultTable):
"""Base class for GPOS and GSUB tables; they share the same high-level structure."""
version = 0x00010000
def decompile(self, data, otFont):
reader = OTTableReader(data)
self.version = reader.readLong()
if self.version <> 0x00010000:
raise ttLib.TTLibError, "unknown table version: 0x%8x" % self.version
self.scriptList = reader.readTable(ScriptList, otFont, self.tableTag)
self.featureList = reader.readTable(FeatureList, otFont, self.tableTag)
self.lookupList = reader.readTable(LookupList, otFont, self.tableTag)
def compile(self, otFont):
XXXXXX
def toXML(self, xmlWriter, otFont):
names = [("ScriptList", "scriptList"),
("FeatureList", "featureList"),
("LookupList", "lookupList")]
for name, attr in names:
xmlWriter.newline()
xmlWriter.begintag(name)
xmlWriter.newline()
table = getattr(self, attr)
table.toXML(xmlWriter, otFont)
xmlWriter.endtag(name)
xmlWriter.newline()
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
#
# Script List and subtables
#
class ScriptList:
def __init__(self, parentTag):
self.parentTag = parentTag
def decompile(self, reader, otFont):
scriptCount = reader.readUShort()
self.scripts = reader.readTagList(scriptCount, Script, otFont)
def compile(self, otFont):
XXXXXX
def toXML(self, xmlWriter, otFont):
for tag, script in self.scripts:
xmlWriter.begintag("Script", tag=tag)
xmlWriter.newline()
script.toXML(xmlWriter, otFont)
xmlWriter.endtag("Script")
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
class Script:
def decompile(self, reader, otFont):
self.defaultLangSystem = None
self.defaultLangSystem = reader.readTable(LanguageSystem, otFont)
langSysCount = reader.readUShort()
self.languageSystems = reader.readTagList(langSysCount, LanguageSystem, otFont)
def compile(self, otFont):
XXXXX
def toXML(self, xmlWriter, otFont):
xmlWriter.begintag("DefaultLanguageSystem")
xmlWriter.newline()
self.defaultLangSystem.toXML(xmlWriter, otFont)
xmlWriter.endtag("DefaultLanguageSystem")
xmlWriter.newline()
for tag, langSys in self.languageSystems:
xmlWriter.begintag("LanguageSystem", tag=tag)
xmlWriter.newline()
langSys.toXML(xmlWriter, otFont)
xmlWriter.endtag("LanguageSystem")
xmlWriter.newline()
class LanguageSystem:
def decompile(self, reader, otFont):
self.lookupOrder = reader.readUShort()
self.reqFeatureIndex = reader.readUShort()
featureCount = reader.readUShort()
self.featureIndex = reader.readUShortArray(featureCount)
def compile(self, otFont):
xxx
def toXML(self, xmlWriter, otFont):
xmlWriter.simpletag("LookupOrder", value=self.lookupOrder)
xmlWriter.newline()
xmlWriter.simpletag("ReqFeature", index=hex(self.reqFeatureIndex))
xmlWriter.newline()
for index in self.featureIndex:
xmlWriter.simpletag("Feature", index=index)
xmlWriter.newline()
#
# Feature List and subtables
#
class FeatureList:
def __init__(self, parentTag):
self.parentTag = parentTag
def decompile(self, reader, otFont):
featureCount = reader.readUShort()
self.features = reader.readTagList(featureCount, Feature, otFont)
def compile(self, otFont):
XXXXX
def toXML(self, xmlWriter, otFont):
for index in range(len(self.features)):
tag, feature = self.features[index]
xmlWriter.begintag("Feature", index=index, tag=tag)
xmlWriter.newline()
feature.toXML(xmlWriter, otFont)
xmlWriter.endtag("Feature")
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
class Feature:
def decompile(self, reader, otFont):
self.featureParams = reader.readUShort()
lookupCount = reader.readUShort()
self.lookupListIndex = reader.readUShortArray(lookupCount)
def compile(self, otFont):
XXXXX
def toXML(self, xmlWriter, otFont):
xmlWriter.simpletag("FeatureParams", value=hex(self.featureParams))
xmlWriter.newline()
for lookupIndex in self.lookupListIndex:
xmlWriter.simpletag("LookupTable", index=lookupIndex)
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
#
# Lookup List and subtables
#
class LookupList:
def __init__(self, parentTag):
self.parentTag = parentTag
def decompile(self, reader, otFont):
lookupCount = reader.readUShort()
self.lookup = lookup = []
for i in range(lookupCount):
lookup.append(reader.readTable(LookupTable, otFont, self.parentTag))
def compile(self, otFont):
XXXXX
def toXML(self, xmlWriter, otFont):
for i in range(len(self.lookup)):
xmlWriter.newline()
lookupTable = self.lookup[i]
xmlWriter.begintag("LookupTable", index=i)
xmlWriter.newline()
lookupTable.toXML(xmlWriter, otFont)
xmlWriter.endtag("LookupTable")
xmlWriter.newline()
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
class LookupTable:
def __init__(self, parentTag):
self.parentTag = parentTag
def decompile(self, reader, otFont):
parentTable = otFont[self.parentTag]
self.lookupType = reader.readUShort()
self.lookupFlag = reader.readUShort()
subTableCount = reader.readUShort()
self.subTables = subTables = []
lookupTypeClass = parentTable.getLookupTypeClass(self.lookupType)
for i in range(subTableCount):
subTables.append(reader.readTable(lookupTypeClass, otFont))
def compile(self, otFont):
XXXXXX
def __repr__(self):
if not hasattr(self, "lookupTypeName"):
m = ttLib.getTableModule(self.parentTag)
self.lookupTypeName = m.lookupTypeClasses[self.lookupType].__name__
return "<%s LookupTable at %x>" % (self.lookupTypeName, id(self))
def toXML(self, xmlWriter, otFont):
xmlWriter.simpletag("LookupFlag", value=hex(self.lookupFlag))
xmlWriter.newline()
for subTable in self.subTables:
name = subTable.__class__.__name__
xmlWriter.begintag(name)
xmlWriter.newline()
subTable.toXML(xmlWriter, otFont)
xmlWriter.endtag(name)
xmlWriter.newline()
def fromXML(self, (name, attrs, content), otFont):
xxx
#
# Other common formats
#
class CoverageTable:
def getGlyphIDs(self):
return self.glyphIDs
def getGlyphNames(self):
return self.glyphNames
def makeGlyphNames(self, otFont):
self.glyphNames = map(lambda i, o=otFont.getGlyphOrder(): o[i], self.glyphIDs)
def decompile(self, reader, otFont):
format = reader.readUShort()
if format == 1:
self.decompileFormat1(reader, otFont)
elif format == 2:
self.decompileFormat2(reader, otFont)
else:
raise ttLib.TTLibError, "unknown Coverage table format: %d" % format
self.makeGlyphNames(otFont)
def decompileFormat1(self, reader, otFont):
glyphCount = reader.readUShort()
self.glyphIDs = glyphIDs = []
for i in range(glyphCount):
glyphID = reader.readUShort()
glyphIDs.append(glyphID)
def decompileFormat2(self, reader, otFont):
rangeCount = reader.readUShort()
self.glyphIDs = glyphIDs = []
for i in range(rangeCount):
startID = reader.readUShort()
endID = reader.readUShort()
startCoverageIndex = reader.readUShort()
for glyphID in range(startID, endID + 1):
glyphIDs.append(glyphID)
def compile(self, otFont):
# brute force ;-)
data1 = self.compileFormat1(otFont)
data2 = self.compileFormat2(otFont)
if len(data1) <= len(data2):
format = 1
reader = data1
else:
format = 2
reader = data2
return struct.pack(">H", format) + reader
def compileFormat1(self, otFont):
xxxxx
glyphIDs = map(otFont.getGlyphID, self.glyphNames)
data = pack(">H", len(glyphIDs))
pack = struct.pack
for glyphID in glyphIDs:
data = data + pack(">H", glyphID)
return data
def compileFormat2(self, otFont):
xxxxx
glyphIDs = map(otFont.getGlyphID, self.glyphNames)
ranges = []
lastID = startID = glyphIDs[0]
startCoverageIndex = 0
glyphCount = len(glyphIDs)
for i in range(1, glyphCount+1):
if i == glyphCount:
glyphID = 0x1ffff # arbitrary, but larger than 0x10000
else:
glyphID = glyphIDs[i]
if glyphID <> (lastID + 1):
ranges.append((startID, lastID, startCoverageIndex))
startCoverageIndex = i
startID = glyphID
lastID = glyphID
ranges.sort() # sort by startID
rangeData = ""
for startID, endID, startCoverageIndex in ranges:
rangeData = rangeData + struct.pack(">HHH", startID, endID, startCoverageIndex)
return pack(">H", len(ranges)) + rangeData
class ClassDefinitionTable:
def decompile(self, reader, otFont):
format = reader.readUShort()
if format == 1:
self.decompileFormat1(reader, otFont)
elif format == 2:
self.decompileFormat2(reader, otFont)
else:
raise ttLib.TTLibError, "unknown Class table format: %d" % format
self.reverse()
def reverse(self):
classDefs = {}
for glyphName, classCode in self.classDefs:
try:
classDefs[classCode].append(glyphName)
except KeyError:
classDefs[classCode] = [glyphName]
self.classDefs = classDefs
def decompileFormat1(self, reader, otFont):
self.classDefs = classDefs = []
startGlyphID = reader.readUShort()
glyphCount = reader.readUShort()
for i in range(glyphCount):
glyphName = otFont.getglyphName(startGlyphID + i)
classValue = reader.readUShort()
if classValue:
classDefs.append((glyphName, classValue))
def decompileFormat2(self, reader, otFont):
self.classDefs = classDefs = []
classRangeCount = reader.readUShort()
for i in range(classRangeCount):
startID = reader.readUShort()
endID = reader.readUShort()
classValue = reader.readUShort()
for glyphID in range(startID, endID + 1):
if classValue:
glyphName = otFont.getGlyphName(glyphID)
classDefs.append((glyphName, classValue))
def compile(self, otFont):
# brute force again
data1 = self.compileFormat1(otFont)
data2 = self.compileFormat2(otFont)
if len(data1) <= len(data2):
format = 1
data = data1
else:
format = 2
data = data2
return struct.pack(">H", format) + data
def compileFormat1(self, otFont):
items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID:
(getGlyphID(glyphName), classValue), self.glyphs.items())
items.sort()
startGlyphID = items[0][0]
endGlyphID = items[-1][0]
data = ""
lastID = startGlyphID
for glyphID, classValue in items:
for i in range(lastID + 1, glyphID - 1):
data = data + "\0\0" # 0 == default class
data = data + struct.pack(">H", classValue)
lastID = glyphID
return struct.pack(">H", endGlyphID - startGlyphID + 1) + data
def compileFormat2(self, otFont):
items = map(lambda (glyphName, classValue), getGlyphID=otFont.getGlyphID:
(getGlyphID(glyphName), classValue), self.glyphs.items())
items.sort()
ranges = []
lastID, lastClassValue = items[0][0]
startID = lastID
itemCount = len(items)
for i in range(1, itemCount+1):
if i == itemCount:
glyphID = 0x1ffff # arbitrary, but larger than 0x10000
classValue = 0
else:
glyphID, classValue = items[i]
if glyphID <> (lastID + 1) or lastClassValue <> classValue:
ranges.append((startID, lastID, lastClassValue))
startID = glyphID
lastClassValue = classValue
lastID = glyphID
lastClassValue = classValue
rangeData = ""
for startID, endID, classValue in ranges:
rangeData = rangeData + struct.pack(">HHH", startID, endID, classValue)
return pack(">H", len(ranges)) + rangeData
def __getitem__(self, glyphName):
if self.glyphs.has_key(glyphName):
return self.glyphs[glyphName]
else:
return 0 # default class
class DeviceTable:
def decompile(self, reader, otFont):
xxxxxx
self.startSize = unpack_uint16(reader[:2])
endSize = unpack_uint16(reader[2:4])
deltaFormat = unpack_uint16(reader[4:6])
reader = reader[6:]
if deltaFormat == 1:
bits = 2
elif deltaFormat == 2:
bits = 4
elif deltaFormat == 3:
bits = 8
else:
raise ttLib.TTLibError, "unknown Device table delta format: %d" % deltaFormat
numCount = 16 / bits
deltaCount = endSize - self.startSize + 1
deltaValues = []
mask = (1 << bits) - 1
threshold = (1 << bits) / 2
shift = 1 << bits
for i in range(0, deltaCount, numCount):
offset = 2*i/numCount
chunk = unpack_uint16(reader[offset:offset+2])
deltas = []
for j in range(numCount):
delta = chunk & mask
if delta >= threshold:
delta = delta - shift
deltas.append(delta)
chunk = chunk >> bits
deltas.reverse()
deltaValues = deltaValues + deltas
self.deltaValues = deltaValues[:deltaCount]
def compile(self, otFont):
deltaValues = self.deltaValues
startSize = self.startSize
endSize = startSize + len(deltaValues) - 1
smallestDelta = min(deltas)
largestDelta = ma(deltas)
if smallestDelta >= -2 and largestDelta < 2:
deltaFormat = 1
bits = 2
elif smallestDelta >= -8 and largestDelta < 8:
deltaFormat = 2
bits = 4
elif smallestDelta >= -128 and largestDelta < 128:
deltaFormat = 3
bits = 8
else:
raise ttLib.TTLibError, "delta value too large: min=%d, max=%d" % (smallestDelta, largestDelta)
data = struct.pack(">HHH", startSize, endSize, deltaFormat)
numCount = 16 / bits
# pad the list to a multiple of numCount values
remainder = len(deltaValues) % numCount
if remainder:
deltaValues = deltaValues + [0] * (numCount - remainder)
deltaData = ""
for i in range(0, len(deltaValues), numCount):
chunk = 0
for j in range(numCount):
chunk = chunk << bits
chunk = chunk | deltaValues[i+j]
deltaData = deltaData + struct.pack(">H", chunk)
return data + deltaData
#
# Miscelaneous helper routines and classes
#
class OTTableReader:
"""Data wrapper, mostly designed to make reading OT data less cumbersome."""
def __init__(self, data, offset=0):
self.data = data
self.offset = offset
self.pos = offset
def readUShort(self):
pos = self.pos
newpos = pos + 2
value = int(struct.unpack(">H", self.data[pos:newpos])[0])
self.pos = newpos
return value
readOffset = readUShort
def readShort(self):
pos = self.pos
newpos = pos + 2
value = int(struct.unpack(">h", self.data[pos:newpos])[0])
self.pos = newpos
return value
def readLong(self):
pos = self.pos
newpos = pos + 4
value = int(struct.unpack(">l", self.data[pos:newpos])[0])
self.pos = newpos
return value
def readTag(self):
pos = self.pos
newpos = pos + 4
value = self.data[pos:newpos]
assert len(value) == 4
self.pos = newpos
return value
def readUShortArray(self, count):
return self.readArray(count, "H")
readOffsetArray = readUShortArray
def readShortArray(self, count):
return self.readArray(count, "h")
def readArray(self, count, format):
assert format in "Hh"
from array import array
pos = self.pos
newpos = pos + 2 * count
a = array(format)
a.fromstring(self.data[pos:newpos])
if ttLib.endian <> 'big':
a.byteswap()
self.pos = newpos
return a.tolist()
def readTable(self, tableClass, otFont, *args):
offset = self.readOffset()
if offset == 0:
return None
newReader = self.getSubString(offset)
table = apply(tableClass, args)
table.decompile(newReader, otFont)
return table
def readTableArray(self, count, tableClass, otFont, *args):
list = []
for i in range(count):
list.append(apply(self.readTable, (tableClass, otFont) + args))
return list
def readTagList(self, count, tableClass, otFont, *args):
list = []
for i in range(count):
tag = self.readTag()
table = apply(self.readTable, (tableClass, otFont) + args)
list.append((tag, table))
return list
def readStruct(self, format, size=None):
if size is None:
size = struct.calcsize(format)
else:
assert size == struct.calcsize(format)
pos = self.pos
newpos = pos + size
values = struct.unpack(format, self.data[pos:newpos])
self.pos = newpos
return values
def getSubString(self, offset):
return self.__class__(self.data, self.offset+offset)
def seek(self, n):
"""Relative seek."""
self.pos = self.pos + n

View File

@ -0,0 +1,91 @@
This folder is a subpackage of ttLib. Each module here is a
specialized TT/OT table converter: they can convert raw data
to Python objects and vice versa. Usually you don't need to
use the modules directly: they are imported and used
automatically when needed by ttLib.
If you are writing you own table converter the following is
important.
The modules here have pretty strange names: this is due to the
fact that we need to map TT table tags (which are case sensitive)
to filenames (which on Mac and Win aren't case sensitive) as well
as to Python identifiers. The latter means it can only contain
[A-Za-z0-9_] and cannot start with a number.
ttLib provides functions to expand a tag into the format used here:
>>> from fontTools import ttLib
>>> ttLib.tag2identifier("FOO ")
'F_O_O_'
>>> ttLib.tag2identifier("cvt ")
'_c_v_t'
>>> ttLib.tag2identifier("OS/2")
'O_S_2f_2'
>>> ttLib.tag2identifier("glyf")
'_g_l_y_f'
>>>
And vice versa:
>>> ttLib.identifier2tag("F_O_O_")
'FOO '
>>> ttLib.identifier2tag("_c_v_t")
'cvt '
>>> ttLib.identifier2tag("O_S_2f_2")
'OS/2'
>>> ttLib.identifier2tag("_g_l_y_f")
'glyf'
>>>
Eg. the 'glyf' table converter lives in a Python file called:
_g_l_y_f.py
The converter itself is a class, named "table_" + expandedtag. Eg:
class table__g_l_y_f:
etc.
Note that if you _do_ need to use such modules or classes manually,
there are two convenient API functions that let you find them by tag:
>>> ttLib.getTableModule('glyf')
<module 'ttLib.tables._g_l_y_f'>
>>> ttLib.getTableClass('glyf')
<class ttLib.tables._g_l_y_f.table__g_l_y_f at 645f400>
>>>
You must subclass from DefaultTable.DefaultTable. It provides some default
behavior, as well as a constructor method (__init__) that you don't need to
override.
Your converter should minimally provide two methods:
class table_F_O_O_(DefaultTable.DefaultTable): # converter for table 'FOO '
def decompile(self, data, ttFont):
# 'data' is the raw table data. Unpack it into a
# Python data structure.
# 'ttFont' is a ttLib.TTfile instance, enabling you to
# refer to other tables. Do ***not*** keep a reference to
# it: it will cause a circular reference (ttFont saves
# a reference to us), and that means we'll be leaking
# memory. If you need to use it in other methods, just
# pass it around as a method argument.
def compile(self, ttFont):
# Return the raw data, as converted from the Python
# data structure.
# Again, 'ttFont' is there so you can access other tables.
# Same warning applies.
If you want to support XML import/export as well, you need to provide two
additional methods:
def toXML(self, writer, ttFont):
# XXX
def fromXML(self, (name, attrs, content), ttFont):
# XXX

View File

@ -0,0 +1,249 @@
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
import array
# first, the list of instructions that eat bytes or words from the instruction stream
streamInstructions = [
# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
# opcode mnemonic argbits descriptive name pops pushes eats from instruction stream pushes
# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
(0x40, 'NPUSHB', 0, 'PushNBytes', 0, -1), # n, b1, b2,...bn b1,b2...bn
(0x41, 'NPUSHW', 0, 'PushNWords', 0, -1), # n, w1, w2,...w w1,w2...wn
(0xb0, 'PUSHB', 3, 'PushBytes', 0, -1), # b0, b1,..bn b0, b1, ...,bn
(0xb8, 'PUSHW', 3, 'PushWords', 0, -1), # w0,w1,..wn w0 ,w1, ...wn
# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
]
# next, the list of "normal" instructions
instructions = [
# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
# opcode mnemonic argbits descriptive name pops pushes pops pushes
# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
(0x7f, 'AA', 0, 'AdjustAngle', 1, 0), # p -
(0x64, 'ABS', 0, 'Absolute', 1, 1), # n |n|
(0x60, 'ADD', 0, 'Add', 2, 1), # n2, n1 (n1 + n2)
(0x27, 'ALIGNPTS', 0, 'AlignPts', 2, 0), # p2, p1 -
(0x3c, 'ALIGNRP', 0, 'AlignRelativePt', -1, 0), # p1, p2, ... , ploopvalue -
(0x5a, 'AND', 0, 'LogicalAnd', 2, 1), # e2, e1 b
(0x2b, 'CALL', 0, 'CallFunction', 1, 0), # f -
(0x67, 'CEILING', 0, 'Ceiling', 1, 1), # n ceil(n)
(0x25, 'CINDEX', 0, 'CopyXToTopStack', 1, 1), # k ek
(0x22, 'CLEAR', 0, 'ClearStack', -1, 0), # all items on the stack -
(0x4f, 'DEBUG', 0, 'DebugCall', 1, 0), # n -
(0x73, 'DELTAC1', 0, 'DeltaExceptionC1', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
(0x74, 'DELTAC2', 0, 'DeltaExceptionC2', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
(0x75, 'DELTAC3', 0, 'DeltaExceptionC3', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
(0x5d, 'DELTAP1', 0, 'DeltaExceptionP1', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
(0x71, 'DELTAP2', 0, 'DeltaExceptionP2', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
(0x72, 'DELTAP3', 0, 'DeltaExceptionP3', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
(0x24, 'DEPTH', 0, 'GetDepthStack', 0, 1), # - n
(0x62, 'DIV', 0, 'Divide', 2, 1), # n2, n1 (n1 * 64)/ n2
(0x20, 'DUP', 0, 'DuplicateTopStack', 1, 2), # e e, e
(0x59, 'EIF', 0, 'EndIf', 0, 0), # - -
(0x1b, 'ELSE', 0, 'Else', 0, 0), # - -
(0x2d, 'ENDF', 0, 'EndFunctionDefinition', 0, 0), # - -
(0x54, 'EQ', 0, 'Equal', 2, 1), # e2, e1 b
(0x57, 'EVEN', 0, 'Even', 1, 1), # e b
(0x2c, 'FDEF', 0, 'FunctionDefinition', 1, 0), # f -
(0x4e, 'FLIPOFF', 0, 'SetAutoFlipOff', 0, 0), # - -
(0x4d, 'FLIPON', 0, 'SetAutoFlipOn', 0, 0), # - -
(0x80, 'FLIPPT', 0, 'FlipPoint', -1, 0), # p1, p2, ..., ploopvalue -
(0x82, 'FLIPRGOFF', 0, 'FlipRangeOff', 2, 0), # h, l -
(0x81, 'FLIPRGON', 0, 'FlipRangeOn', 2, 0), # h, l -
(0x66, 'FLOOR', 0, 'Floor', 1, 1), # n floor(n)
(0x46, 'GC', 1, 'GetCoordOnPVector', 1, 1), # p c
(0x88, 'GETINFO', 0, 'GetInfo', 1, 1), # selector result
(0x0d, 'GFV', 0, 'GetFVector', 0, 2), # - px, py
(0x0c, 'GPV', 0, 'GetPVector', 0, 2), # - px, py
(0x52, 'GT', 0, 'GreaterThan', 2, 1), # e2, e1 b
(0x53, 'GTEQ', 0, 'GreaterThanOrEqual', 2, 1), # e2, e1 b
(0x89, 'IDEF', 0, 'InstructionDefinition', 1, 0), # f -
(0x58, 'IF', 0, 'If', 1, 0), # e -
(0x8e, 'INSTCTRL', 0, 'SetInstrExecControl', 2, 0), # s, v -
(0x39, 'IP', 0, 'InterpolatePts', -1, 0), # p1, p2, ... , ploopvalue -
(0x0f, 'ISECT', 0, 'MovePtToIntersect', 5, 0), # a1, a0, b1, b0, p -
(0x30, 'IUP', 1, 'InterpolateUntPts', 0, 0), # - -
(0x1c, 'JMPR', 0, 'Jump', 1, 0), # offset -
(0x79, 'JROF', 0, 'JumpRelativeOnFalse', 2, 0), # e, offset -
(0x78, 'JROT', 0, 'JumpRelativeOnTrue', 2, 0), # e, offset -
(0x2a, 'LOOPCALL', 0, 'LoopAndCallFunction', 2, 0), # f, count -
(0x50, 'LT', 0, 'LessThan', 2, 1), # e2, e1 b
(0x51, 'LTEQ', 0, 'LessThenOrEqual', 2, 1), # e2, e1 b
(0x8b, 'MAX', 0, 'Maximum', 2, 1), # e2, e1 max(e1, e2)
(0x49, 'MD', 1, 'MeasureDistance', 2, 1), # p2,p1 d
(0x2e, 'MDAP', 1, 'MoveDirectAbsPt', 1, 0), # p -
(0xc0, 'MDRP', 5, 'MoveDirectRelPt', 1, 0), # p -
(0x3e, 'MIAP', 1, 'MoveIndirectAbsPt', 2, 0), # n, p -
(0x8c, 'MIN', 0, 'Minimum', 2, 1), # e2, e1 min(e1, e2)
(0x26, 'MINDEX', 0, 'MoveXToTopStack', 2, 1), # k ek
(0xe0, 'MIRP', 5, 'MoveIndirectRelPt', 1, 0), # n, p -
(0x4b, 'MPPEM', 0, 'MeasurePixelPerEm', 0, 1), # - ppem
(0x4c, 'MPS', 0, 'MeasurePointSize', 0, 1), # - pointSize
(0x3a, 'MSIRP', 1, 'MoveStackIndirRelPt', 2, 0), # d, p -
(0x63, 'MUL', 0, 'Multiply', 2, 1), # n2, n1 (n1 * n2)/64
(0x65, 'NEG', 0, 'Negate', 1, 1), # n -n
(0x55, 'NEQ', 0, 'NotEqual', 2, 1), # e2, e1 b
(0x5c, 'NOT', 0, 'LogicalNot', 1, 1), # e ( not e )
(0x6c, 'NROUND', 2, 'NoRound', 1, 1), # n1 n2
(0x56, 'ODD', 0, 'Odd', 1, 1), # e b
(0x5b, 'OR', 0, 'LogicalOr', 2, 1), # e2, e1 b
(0x21, 'POP', 0, 'PopTopStack', 1, 0), # e -
(0x45, 'RCVT', 0, 'ReadCVT', 1, 1), # location value
(0x7d, 'RDTG', 0, 'RoundDownToGrid', 0, 0), # - -
(0x7a, 'ROFF', 0, 'RoundOff', 0, 0), # - -
(0x8a, 'ROLL', 0, 'RollTopThreeStack', 3, 3), # a,b,c b,a,c
(0x68, 'ROUND', 2, 'Round', 1, 1), # n1 n2
(0x43, 'RS', 0, 'ReadStore', 1, 1), # n v
(0x3d, 'RTDG', 0, 'RoundToDoubleGrid', 0, 0), # - -
(0x18, 'RTG', 0, 'RoundToGrid', 0, 0), # - -
(0x19, 'RTHG', 0, 'RoundToHalfGrid', 0, 0), # - -
(0x7c, 'RUTG', 0, 'RoundUpToGrid', 0, 0), # - -
(0x77, 'S45ROUND', 0, 'SuperRound45Degrees', 1, 0), # n -
(0x7e, 'SANGW', 0, 'SetAngleWeight', 1, 0), # weight -
(0x85, 'SCANCTRL', 0, 'ScanConversionControl', 1, 0), # n -
(0x8d, 'SCANTYPE', 0, 'ScanType', 1, 0), # n -
(0x48, 'SCFS', 0, 'SetCoordFromStackFP', 2, 0), # c, p -
(0x1d, 'SCVTCI', 0, 'SetCVTCutIn', 1, 0), # n -
(0x5e, 'SDB', 0, 'SetDeltaBaseInGState', 1, 0), # n -
(0x86, 'SDPVTL', 1, 'SetDualPVectorToLine', 2, 0), # p2, p1 -
(0x5f, 'SDS', 0, 'SetDeltaShiftInGState', 1, 0), # n -
(0x0b, 'SFVFS', 0, 'SetFVectorFromStack', 2, 0), # y, x -
(0x04, 'SFVTCA', 1, 'SetFVectorToAxis', 0, 0), # - -
(0x08, 'SFVTL', 1, 'SetFVectorToLine', 2, 0), # p2, p1 -
(0x0e, 'SFVTPV', 0, 'SetFVectorToPVector', 0, 0), # - -
(0x34, 'SHC', 1, 'ShiftContourByLastPt', 1, 0), # c -
(0x32, 'SHP', 1, 'ShiftPointByLastPoint', -1, 0), # p1, p2, ..., ploopvalue -
(0x38, 'SHPIX', 0, 'ShiftZoneByPixel', -1, 0), # d, p1, p2, ..., ploopvalue -
(0x36, 'SHZ', 1, 'ShiftZoneByLastPoint', 1, 0), # e -
(0x17, 'SLOOP', 0, 'SetLoopVariable', 1, 0), # n -
(0x1a, 'SMD', 0, 'SetMinimumDistance', 1, 0), # distance -
(0x0a, 'SPVFS', 0, 'SetPVectorFromStack', 2, 0), # y, x -
(0x02, 'SPVTCA', 1, 'SetPVectorToAxis', 0, 0), # - -
(0x06, 'SPVTL', 1, 'SetPVectorToLine', 2, 0), # p2, p1 -
(0x76, 'SROUND', 0, 'SuperRound', 1, 0), # n -
(0x10, 'SRP0', 0, 'SetRefPoint0', 1, 0), # p -
(0x11, 'SRP1', 0, 'SetRefPoint1', 1, 0), # p -
(0x12, 'SRP2', 0, 'SetRefPoint2', 1, 0), # p -
(0x1f, 'SSW', 0, 'SetSingleWidth', 1, 0), # n -
(0x1e, 'SSWCI', 0, 'SetSingleWidthCutIn', 1, 0), # n -
(0x61, 'SUB', 0, 'Subtract', 2, 1), # n2, n1 (n1 - n2)
(0x00, 'SVTCA', 1, 'SetFPVectorToAxis', 0, 0), # - -
(0x23, 'SWAP', 0, 'SwapTopStack', 2, 2), # e2, e1 e1, e2
(0x13, 'SZP0', 0, 'SetZonePointer0', 1, 0), # n -
(0x14, 'SZP1', 0, 'SetZonePointer1', 1, 0), # n -
(0x15, 'SZP2', 0, 'SetZonePointer2', 1, 0), # n -
(0x16, 'SZPS', 0, 'SetZonePointerS', 1, 0), # n -
(0x29, 'UTP', 0, 'UnTouchPt', 1, 0), # p -
(0x70, 'WCVTF', 0, 'WriteCVTInFUnits', 2, 0), # n, l -
(0x44, 'WCVTP', 0, 'WriteCVTInPixels', 2, 0), # v, l -
(0x42, 'WS', 0, 'WriteStore', 2, 0), # v, l -
# ------ ----------- ----- ------------------------ --- ------ ---------------------------------- --------------
]
def bitRepr(value, bits):
s = ""
for i in range(bits):
s = "01"[value & 0x1] + s
value = value >> 1
return s
def makeOpcodeDict(instructionList):
opcodeDict = {}
for op, mnemonic, argbits, name, pops, pushes in instructionList:
if argbits:
argoffset = op
for i in range(1 << argbits):
opcodeDict[op+i] = mnemonic, argbits, argoffset, name
else:
opcodeDict[op] = mnemonic, 0, 0, name
return opcodeDict
streamOpcodeDict = makeOpcodeDict(streamInstructions)
opcodeDict = makeOpcodeDict(instructions)
tt_instructions_error = "TT instructions error"
class Program:
def __init__(self):
pass
def fromBytecode(self, bytecode):
self.bytecode = array.array("B")
self.bytecode.fromstring(bytecode)
def fromAssembly(self, assembly):
self.assembly = assembly
def getBytecode(self):
if not hasattr(self, "bytecode"):
self._assemble()
return self.bytecode.tostring()
def getAssembly(self):
if not hasattr(self, "assembly"):
self._disassemble()
return self.assembly
def _assemble(self):
xxx
def _disassemble(self):
assembly = []
i = 0
bytecode = self.bytecode
numBytecode = len(bytecode)
while i < numBytecode:
op = bytecode[i]
arg = 0
try:
mnemonic, argbits, argoffset, name = opcodeDict[op]
except KeyError:
try:
mnemonic, argbits, argoffset, name = streamOpcodeDict[op]
except KeyError:
raise tt_instructions_error, "illegal opcode: 0x%.2x" % op
pushbytes = pushwords = 0
if argbits:
if mnemonic == "PUSHB":
pushbytes = op - argoffset + 1
else:
pushwords = op - argoffset + 1
else:
i = i + 1
if mnemonic == "NPUSHB":
pushbytes = bytecode[i]
else:
pushwords = bytecode[i]
i = i + 1
assembly.append(mnemonic + "[ ]")
for j in range(pushbytes):
assembly.append(`bytecode[i]`)
i = i + 1
for j in range(0, pushwords, 2):
assembly.append(`(bytecode[i] << 8) + bytecode[i+1]`)
i = i + 2
else:
if argbits:
assembly.append(mnemonic + "[%s]" % bitRepr(op - argoffset, argbits))
else:
assembly.append(mnemonic + "[ ]")
i = i + 1
self.assembly = assembly
del self.bytecode
fpgm = '@\01476&%\037\023\022\015\014\005\004\002, \260\003%E#E#ah\212 Eh \212#D`D-,KRXED\033!!Y-, EhD \260\001` E\260Fvh\030\212E`D-,\260\022+\260\002%E\260\002%Ej\260@\213`\260\002%#D!!!-,\260\023+\260\002%E\260\002%Ej\270\377\300\214`\260\002%#D!!!-,\261\000\003%EhTX\260\003%E\260\003%E`h \260\004%#D\260\004%#D\033\260\003% Eh \212#D\260\003%Eh`\260\003%#DY-,\260\003% Eh \212#D\260\003%Eh`\260\003%#D-,KRXED\033!!Y-,F#F`\212\212F# F\212`\212a\270\377\200b# \020#\212\261KK\212pE` \260\000PX\260\001a\270\377\272\213\033\260F\214Y\260\020`h\001:-, E\260\003%FRX?\033!\021Y-,KS#KQZX E\212`D\033!!Y-,KS#KQZX8\033!!Y-'
gpgm = '@\022\011\003\207@\005\200\004\207\000\010\007\202\001\010\004\202\000\010\000\020\320\355\020\336\355\001\020\336\375\032}\336\032\030\375\31610'
p = Program()
p.fromBytecode(fpgm)
for line in p.getAssembly():
print line

View File

@ -0,0 +1,332 @@
from fontTools import ttLib
from fontTools.ttLib import macUtils
import macfs
import PyBrowser
import W, Lists
import os
import ATM
import Numeric
import Qd
from rf.views.wGlyphList import GlyphList
class TableBrowser:
def __init__(self, path=None, ttFont=None, res_index=None):
W.SetCursor('watch')
if path is None:
self.ttFont = ttFont
self.filename = "????"
else:
self.ttFont = ttLib.TTFont(path, res_index)
if res_index is None:
self.filename = os.path.basename(path)
else:
self.filename = os.path.basename(path) + " - " + str(res_index)
self.currentglyph = None
self.glyphs = {}
self.buildinterface()
def buildinterface(self):
buttonwidth = 120
glyphlistwidth = 150
hmargin = 10
vmargin = 8
title = self.filename
tables = self.ttFont.keys()
tables.sort()
self.w = w = W.Window((500, 300), title, minsize = (400, 200))
w.browsetablebutton = W.Button((hmargin, 32, buttonwidth, 16), "Browse tableŠ",
self.browsetable)
w.browsefontbutton = W.Button((hmargin, vmargin, buttonwidth, 16), "Browse fontŠ",
self.browsefont)
w.tablelist = W.List((hmargin, 56, buttonwidth, -128), tables, self.tablelisthit)
w.divline1 = W.VerticalLine((buttonwidth + 2 * hmargin, vmargin, 1, -vmargin))
gleft = buttonwidth + 3 * hmargin + 1
hasGlyfTable = self.ttFont.has_key('glyf')
glyphnames = self.ttFont.getGlyphNames2() # caselessly sorted glyph names
if hasGlyfTable:
w.glyphlist = GlyphList((gleft, 56, glyphlistwidth, -vmargin),
glyphnames, self.glyphlisthit)
w.divline2 = W.VerticalLine((buttonwidth + glyphlistwidth + 4 * hmargin + 2,
vmargin, 1, -vmargin))
yMin = self.ttFont['head'].yMin
yMax = self.ttFont['head'].yMax
w.gviewer = GlyphViewer((buttonwidth + glyphlistwidth + 5 * hmargin + 3,
vmargin, -hmargin, -vmargin), yMin, yMax)
w.showpoints = W.CheckBox((gleft, vmargin, glyphlistwidth, 16), "Show points",
self.w.gviewer.toggleshowpoints)
w.showpoints.set(self.w.gviewer.showpoints)
w.showlines = W.CheckBox((gleft, vmargin + 24, glyphlistwidth, 16), "Show lines",
self.w.gviewer.toggleshowlines)
w.showlines.set(self.w.gviewer.showlines)
else:
w.glyphlist = GlyphList((gleft, 56, glyphlistwidth, -vmargin),
glyphnames)
w.noGlyphTable = W.TextBox((gleft, vmargin, -20, 20), "no 'glyf' table found")
w.setdefaultbutton(w.browsetablebutton)
w.tocurrentfont = W.Button((hmargin, -120, buttonwidth, 16), "Copy to current font", self.copytocurrentfont)
w.fromcurrentfont = W.Button((hmargin, -96, buttonwidth, 16), "Copy from current font", self.copyfromcurrentfont)
w.saveflat = W.Button((hmargin, -72, buttonwidth, 16), "Save as flat fileŠ", self.saveflat)
w.savesuitcasebutton = W.Button((hmargin, -48, buttonwidth, 16), "Save as suitcaseŠ", self.savesuitcase)
w.savexmlbutton = W.Button((hmargin, -24, buttonwidth, 16), "Save as XMLŠ", self.saveXML)
w.open()
w.browsetablebutton.enable(0)
def browsetable(self):
self.tablelisthit(1)
def browsefont(self):
PyBrowser.Browser(self.ttFont)
def copytocurrentfont(self):
pass
def copyfromcurrentfont(self):
pass
def saveflat(self):
path = putfile("Save font as flat file:", self.filename, ".TTF")
if path:
W.SetCursor('watch')
self.ttFont.save(path)
def savesuitcase(self):
path = putfile("Save font as suitcase:", self.filename, ".suit")
if path:
W.SetCursor('watch')
self.ttFont.save(path, 1)
def saveXML(self):
path = putfile("Save font as XML text file:", self.filename, ".xml")
if path:
W.SetCursor('watch')
pb = macUtils.ProgressBar("Saving %s as XMLŠ" % self.filename)
try:
self.ttFont.saveXML(path, pb)
finally:
pb.close()
def glyphlisthit(self, isDbl):
sel = self.w.glyphlist.getselectedobjects()
if not sel or sel[0] == self.currentglyph:
return
self.currentglyph = sel[0]
if self.glyphs.has_key(self.currentglyph):
g = self.glyphs[self.currentglyph]
else:
g = Glyph(self.ttFont, self.currentglyph)
self.glyphs[self.currentglyph] = g
self.w.gviewer.setglyph(g)
def tablelisthit(self, isdbl):
if isdbl:
for tag in self.w.tablelist.getselectedobjects():
table = self.ttFont[tag]
if tag == 'glyf':
W.SetCursor('watch')
for glyphname in self.ttFont.getGlyphOrder():
try:
glyph = table[glyphname]
except KeyError:
pass # incomplete font, oh well.
PyBrowser.Browser(table)
else:
sel = self.w.tablelist.getselection()
if sel:
self.w.browsetablebutton.enable(1)
else:
self.w.browsetablebutton.enable(0)
class Glyph:
def __init__(self, ttFont, glyphName):
ttglyph = ttFont['glyf'][glyphName]
self.iscomposite = ttglyph.numberOfContours == -1
self.width, self.lsb = ttFont['hmtx'][glyphName]
if ttglyph.numberOfContours == 0:
self.xMin = 0
self.contours = []
return
self.xMin = ttglyph.xMin
coordinates, endPts, flags = ttglyph.getCoordinates(ttFont['glyf'])
self.contours = []
self.flags = []
startpt = 0
for endpt in endPts:
self.contours.append(Numeric.array(coordinates[startpt:endpt+1]))
self.flags.append(flags[startpt:endpt+1])
startpt = endpt + 1
def getcontours(self, scale, move):
contours = []
for i in range(len(self.contours)):
contours.append((self.contours[i] * Numeric.array(scale) + move), self.flags[i])
return contours
class GlyphViewer(W.Widget):
def __init__(self, possize, yMin, yMax):
W.Widget.__init__(self, possize)
self.glyph = None
extra = 0.02 * (yMax-yMin)
self.yMin, self.yMax = yMin - extra, yMax + extra
self.showpoints = 1
self.showlines = 1
def toggleshowpoints(self, onoff):
self.showpoints = onoff
self.SetPort()
self.draw()
def toggleshowlines(self, onoff):
self.showlines = onoff
self.SetPort()
self.draw()
def setglyph(self, glyph):
self.glyph = glyph
self.SetPort()
self.draw()
def draw(self, visRgn=None):
# This a HELL of a routine, but it's pretty damn fast...
import Qd
if not self._visible:
return
Qd.EraseRect(Qd.InsetRect(self._bounds, 1, 1))
cliprgn = Qd.NewRgn()
savergn = Qd.NewRgn()
Qd.RectRgn(cliprgn, self._bounds)
Qd.GetClip(savergn)
Qd.SetClip(cliprgn)
try:
if self.glyph:
l, t, r, b = Qd.InsetRect(self._bounds, 1, 1)
height = b - t
scale = float(height) / (self.yMax - self.yMin)
topoffset = t + scale * self.yMax
width = scale * self.glyph.width
lsb = scale * self.glyph.lsb
xMin = scale * self.glyph.xMin
# XXXX this is not correct when USE_MY_METRICS is set in component!
leftoffset = l + 0.5 * (r - l - width)
gleftoffset = leftoffset - xMin + lsb
if self.showlines:
Qd.RGBForeColor((0xafff, 0xafff, 0xafff))
# left sidebearing
Qd.MoveTo(leftoffset, t)
Qd.LineTo(leftoffset, b - 1)
# right sidebearing
Qd.MoveTo(leftoffset + width, t)
Qd.LineTo(leftoffset + width, b - 1)
# baseline
Qd.MoveTo(l, topoffset)
Qd.LineTo(r - 1, topoffset)
# origin
Qd.RGBForeColor((0x5fff, 0, 0))
Qd.MoveTo(gleftoffset, topoffset - 16)
Qd.LineTo(gleftoffset, topoffset + 16)
# reset color
Qd.RGBForeColor((0, 0, 0))
if self.glyph.iscomposite:
Qd.RGBForeColor((0x7fff, 0x7fff, 0x7fff))
ATM.startFillATM()
contours = self.glyph.getcontours((scale, -scale), (gleftoffset, topoffset))
for contour, flags in contours:
currentpoint = None
done_moveto = 0
i = 0
nPoints = len(contour)
while i < nPoints:
pt = contour[i]
if flags[i]:
# onCurve
currentpoint = lineto(pt, done_moveto)
else:
if not currentpoint:
if not flags[i-1]:
currentpoint = 0.5 * (contour[i-1] + pt)
else:
currentpoint = contour[i-1]
if not flags[(i+1) % nPoints]:
endPt = 0.5 * (pt + contour[(i+1) % nPoints])
else:
endPt = contour[(i+1) % nPoints]
i = i + 1
# offCurve
currentpoint = qcurveto(currentpoint,
pt, endPt, done_moveto)
done_moveto = 1
i = i + 1
ATM.fillClosePathATM()
ATM.endFillATM()
# draw point markers
if self.showpoints:
for contour, flags in contours:
Qd.RGBForeColor((0, 0xffff, 0))
for i in range(len(contour)):
(x, y) = contour[i]
onCurve = flags[i] & 0x1
if onCurve:
Qd.PaintRect(Qd.InsetRect((x, y, x, y), -2, -2))
else:
Qd.PaintOval(Qd.InsetRect((x, y, x, y), -2, -2))
Qd.RGBForeColor((0xffff, 0, 0))
Qd.RGBForeColor((0, 0, 0))
Qd.FrameRect(self._bounds)
finally:
Qd.SetClip(savergn)
Qd.DisposeRgn(cliprgn)
Qd.DisposeRgn(savergn)
extensions = [".suit", ".xml", ".TTF", ".ttf"]
def putfile(prompt, filename, newextension):
for ext in extensions:
if filename[-len(ext):] == ext:
filename = filename[:-len(ext)] + newextension
break
else:
filename = filename + newextension
fss, ok = macfs.StandardPutFile(prompt, filename)
if ok:
return fss.as_pathname()
def lineto(pt, done_moveto):
x, y = pt
if done_moveto:
ATM.fillLineToATM((x, y))
else:
ATM.fillMoveToATM((x, y))
return pt
def qcurveto(pt0, pt1, pt2, done_moveto):
if not done_moveto:
x0, y0 = pt0
ATM.fillMoveToATM((x0, y0))
x1a, y1a = pt0 + 0.6666666666667 * (pt1 - pt0)
x1b, y1b = pt2 + 0.6666666666667 * (pt1 - pt2)
x2, y2 = pt2
ATM.fillCurveToATM((x1a, y1a), (x1b, y1b), (x2, y2))
return pt2

View File

@ -0,0 +1,196 @@
from fontTools import ttLib
from fontTools.misc.textTools import safeEval
import types
import string
import Numeric, array
from xml.parsers.xmlproc import xmlproc
xmlerror = "xmlerror"
xml_parse_error = "XML parse error"
class UnicodeString:
def __init__(self, value):
if isinstance(value, UnicodeString):
self.value = value.value
else:
if type(value) == types.StringType:
# Since Numeric interprets char codes as *signed*,
# we feed it through the array module.
value = array.array("B", value)
self.value = Numeric.array(value, Numeric.Int16)
def __len__(self):
return len(self.value)
#def __hash__(self):
# return hash(self.value.tostring())
#
#def __cmp__(self, other):
# if not isinstance(other, UnicodeString):
# return 1
# else:
# return not Numeric.alltrue(
# Numeric.equal(self.value, other.value))
def __add__(self, other):
if not isinstance(other, UnicodeString):
other = self.__class__(other)
return self.__class__(Numeric.concatenate((self.value, other.value)))
def __radd__(self, other):
if not isinstance(other, UnicodeString):
other = self.__class__(other)
return self.__class__(Numeric.concatenate((other.value, self.value)))
def __getslice__(self, i, j):
return self.__class__(self.value[i:j])
def __getitem__(self, i):
return self.__class__(self.value[i:i+1])
def tostring(self):
value = self.value
if ttLib.endian <> "big":
value = value.byteswapped()
return value.tostring()
def stripped(self):
value = self.value
i = 0
for i in range(len(value)):
if value[i] not in (0xa, 0xd, 0x9, 0x20):
break
value = value[i:]
i = 0
for i in range(len(value)-1, -1, -1):
if value[i] not in (0xa, 0xd, 0x9, 0x20):
break
value = value[:i+1]
return self.__class__(value)
def __repr__(self):
return "<%s %s at %x>" % (self.__class__.__name__, `self.value.tostring()`, id(self))
class UnicodeProcessor(xmlproc.XMLProcessor):
def parse_charref(self):
"Parses a character reference."
if self.now_at("x"):
digs=unhex(self.get_match(xmlproc.reg_hex_digits))
else:
try:
digs=string.atoi(self.get_match(xmlproc.reg_digits))
except ValueError,e:
self.report_error(3027)
digs=None
if digs == 169:
pass
if not self.now_at(";"): self.report_error(3005,";")
if digs==None: return
if not (digs==9 or digs==10 or digs==13 or \
(digs>=32 and digs<=255)):
if digs>255:
self.app.handle_data(UnicodeString([digs]),0,1)
else:
# hrm, I need to let some null bytes go through...
self.app.handle_data(chr(digs),0,1)
#self.report_error(3018,digs)
else:
if self.stack==[]:
self.report_error(3028)
self.app.handle_data(chr(digs),0,1)
class XMLErrorHandler(xmlproc.ErrorHandler):
def fatal(self, msg):
"Handles a fatal error message."
# we don't want no stinkin' sys.exit(1)
raise xml_parse_error, msg
class XMLApplication(xmlproc.Application):
def __init__(self, ttFont, progress=None):
self.ttFont = ttFont
self.progress = progress
self.root = None
self.content_stack = []
self.lastpos = 0
def handle_start_tag(self, name, attrs):
if self.progress:
pos = self.locator.pos + self.locator.block_offset
if (pos - self.lastpos) > 4000:
self.progress.set(pos / 100)
self.lastpos = pos
stack = self.locator.stack
stacksize = len(stack)
if not stacksize:
if name <> "ttFont":
raise xml_parse_error, "illegal root tag: %s" % name
sfntVersion = attrs.get("sfntVersion", "\000\001\000\000")
if len(sfntVersion) <> 4:
sfntVersion = safeEval('"' + sfntVersion + '"')
self.ttFont.sfntVersion = sfntVersion
self.content_stack.append([])
elif stacksize == 1:
msg = "Parsing '%s' table..." % ttLib.xmltag2tag(name)
if self.progress:
self.progress.setlabel(msg)
elif self.ttFont.verbose:
ttLib.debugmsg(msg)
else:
print msg
tag = ttLib.xmltag2tag(name)
tableclass = ttLib.getTableClass(tag)
if tableclass is None:
from fontTools.ttLib.tables.DefaultTable import DefaultTable
tableclass = DefaultTable
self.current_table = tableclass(tag)
self.ttFont[tag] = self.current_table
self.content_stack.append([])
elif stacksize == 2:
self.content_stack.append([])
self.root = (name, attrs, self.content_stack[-1])
else:
list = []
self.content_stack[-1].append(name, attrs, list)
self.content_stack.append(list)
def handle_data(self, data, start, end):
if len(self.locator.stack) > 1:
self.content_stack[-1].append(data[start:end])
def handle_end_tag(self, name):
del self.content_stack[-1]
stack = self.locator.stack
stacksize = len(stack)
if stacksize == 1:
self.root = None
elif stacksize == 2:
self.current_table.fromXML(self.root, self.ttFont)
self.root = None
class ProgressPrinter:
def __init__(self, title, maxval=100):
print title
def set(self, val, maxval=None):
pass
def increment(self, val=1):
pass
def setlabel(self, text):
print text

6646
Lib/fontTools/unicode.py Normal file

File diff suppressed because it is too large Load Diff

204
Lib/sstruct.py Normal file
View File

@ -0,0 +1,204 @@
"""sstruct.py -- SuperStruct
Higher level layer on top of the struct module, enabling to
bind names to struct elements. The interface is similar to
struct, except the objects passed and returned are not tuples
(or argument lists), but dictionaries or instances.
Just like struct, we use format strings to describe a data
structure, except we use one line per element. Lines are
separated by newlines or semi-colons. Each line contains
either one of the special struct characters ('@', '=', '<',
'>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f').
Repetitions, like the struct module offers them are not useful
in this context, except for fixed length strings (eg. 'myInt:5h'
is not allowed but 'myString:5s' is). The 'x' format character
(pad byte) is treated as 'special', since it is by definition
anonymous. Extra whitespace is allowed everywhere.
The sstruct module offers one feature that the "normal" struct
module doesn't: support for fixed point numbers. These are spelled
as "n.mF", where n is the number of bits before the point, and m
the number of bits after the point. Fixed point numbers get
converted to floats.
pack(format, object):
'object' is either a dictionary or an instance (or actually
anything that has a __dict__ attribute). If it is a dictionary,
its keys are used for names. If it is an instance, it's
attributes are used to grab struct elements from. Returns
a string containing the data.
unpack(format, data, object=None)
If 'object' is omitted (or None), a new dictionary will be
returned. If 'object' is a dictionary, it will be used to add
struct elements to. If it is an instance (or in fact anything
that has a __dict__ attribute), an attribute will be added for
each struct element. In the latter two cases, 'object' itself
is returned.
unpack2(format, data, object=None)
Convenience function. Same as unpack, except data may be longer
than needed. The returned value is a tuple: (object, leftoverdata).
calcsize(format)
like struct.calcsize(), but uses our own format strings:
it returns the size of the data in bytes.
"""
# XXX I would like to support pascal strings, too, but I'm not
# sure if that's wise. Would be nice if struct supported them
# "properly", but that would certainly break calcsize()...
__version__ = "1.2"
__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>"
import struct
import re
import types
error = "sstruct.error"
def pack(format, object):
formatstring, names, fixes = getformat(format)
elements = []
if type(object) is not types.DictType:
object = object.__dict__
for name in names:
value = object[name]
if fixes.has_key(name):
# fixed point conversion
value = int(round(value*fixes[name]))
elements.append(value)
data = apply(struct.pack, (formatstring,) + tuple(elements))
return data
def unpack(format, data, object=None):
if object is None:
object = {}
formatstring, names, fixes = getformat(format)
if type(object) is types.DictType:
dict = object
else:
dict = object.__dict__
elements = struct.unpack(formatstring, data)
for i in range(len(names)):
name = names[i]
value = elements[i]
if fixes.has_key(name):
# fixed point conversion
value = value / fixes[name]
dict[name] = value
return object
def unpack2(format, data, object=None):
length = calcsize(format)
return unpack(format, data[:length], object), data[length:]
def calcsize(format):
formatstring, names, fixes = getformat(format)
return struct.calcsize(formatstring)
# matches "name:formatchar" (whitespace is allowed)
_elementRE = re.compile(
"\s*" # whitespace
"([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier)
"\s*:\s*" # whitespace : whitespace
"([cbBhHiIlLfd]|[0-9]+[ps]|" # formatchar...
"([0-9]+)\.([0-9]+)(F))" # ...formatchar
"\s*" # whitespace
"(#.*)?$" # [comment] + end of string
)
# matches the special struct format chars and 'x' (pad byte)
_extraRE = re.compile("\s*([x@=<>!])\s*(#.*)?$")
# matches an "empty" string, possibly containing whitespace and/or a comment
_emptyRE = re.compile("\s*(#.*)?$")
_fixedpointmappings = {
8: "b",
16: "h",
32: "l"}
_formatcache = {}
def getformat(format):
try:
formatstring, names, fixes = _formatcache[format]
except KeyError:
lines = re.split("[\n;]", format)
formatstring = ""
names = []
fixes = {}
for line in lines:
if _emptyRE.match(line):
continue
m = _extraRE.match(line)
if m:
formatchar = m.group(1)
if formatchar <> 'x' and formatstring:
raise error, "a special format char must be first"
else:
m = _elementRE.match(line)
if not m:
raise error, "syntax error in format: '%s'" % line
name = m.group(1)
names.append(name)
formatchar = m.group(2)
if m.group(3):
# fixed point
before = int(m.group(3))
after = int(m.group(4))
bits = before + after
if bits not in [8, 16, 32]:
raise error, "fixed point must be 8, 16 or 32 bits long"
formatchar = _fixedpointmappings[bits]
assert m.group(5) == "F"
fixes[name] = float(1 << after)
formatstring = formatstring + formatchar
_formatcache[format] = formatstring, names, fixes
return formatstring, names, fixes
def _test():
format = """
# comments are allowed
> # big endian (see documentation for struct)
# empty lines are allowed:
ashort: h
along: l
abyte: b # a byte
achar: c
astr: 5s
afloat: f; adouble: d # multiple "statements" are allowed
afixed: 16.16F
"""
print 'size:', calcsize(format)
class foo:
pass
i = foo()
i.ashort = 0x7fff
i.along = 0x7fffffff
i.abyte = 0x7f
i.achar = "a"
i.astr = "12345"
i.afloat = 0.5
i.adouble = 0.5
i.afixed = 1.5
data = pack(format, i)
print 'data:', `data`
print unpack(format, data)
i2 = foo()
unpack(format, data, i2)
print vars(i2)
if __name__ == "__main__":
_test()

172
Lib/xmlWriter.py Normal file
View File

@ -0,0 +1,172 @@
"""xmlWriter.py -- Simple XML authoring class"""
__author__ = "jvr"
__version__ = "0.9"
import string
import struct
INDENT = " "
class XMLWriter:
def __init__(self, file, dtd=None, indentwhite=INDENT):
if type(file) == type(""):
self.file = open(file, "w")
else:
# assume writable file object
self.file = file
self.dtd = dtd
self.indentwhite = indentwhite
self.indentlevel = 0
self.stack = []
self.needindent = 1
self.writeraw("<?xml version='1.0'?>")
self.newline()
if self.dtd:
# DOCTYPE???
self.newline()
def close(self):
self.file.close()
def write(self, data):
self.writeraw(escape(data))
def write_noindent(self, data):
self.file.write(escape(data))
def write8bit(self, data):
self.writeraw(escape8bit(data))
def write16bit(self, data):
self.writeraw(escape16bit(data))
def writeraw(self, data):
if self.needindent:
self.file.write(self.indentlevel * self.indentwhite)
self.needindent = 0
self.file.write(data)
def newline(self):
self.file.write("\n")
self.needindent = 1
def comment(self, data):
data = escape(data)
lines = string.split(data, "\n")
self.writeraw("<!-- " + lines[0])
for line in lines[1:]:
self.newline()
self.writeraw(" " + line)
self.writeraw(" -->")
def simpletag(self, _TAG_, *args, **kwargs):
attrdata = apply(self.stringifyattrs, args, kwargs)
data = "<%s%s/>" % (_TAG_, attrdata)
self.writeraw(data)
def begintag(self, _TAG_, *args, **kwargs):
attrdata = apply(self.stringifyattrs, args, kwargs)
data = "<%s%s>" % (_TAG_, attrdata)
self.writeraw(data)
self.stack.append(_TAG_)
self.indent()
def endtag(self, _TAG_):
assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
del self.stack[-1]
self.dedent()
data = "</%s>" % _TAG_
self.writeraw(data)
def dumphex(self, data):
linelength = 16
hexlinelength = linelength * 2
chunksize = 8
for i in range(0, len(data), linelength):
hexline = hexStr(data[i:i+linelength])
line = ""
white = ""
for j in range(0, hexlinelength, chunksize):
line = line + white + hexline[j:j+chunksize]
white = " "
self.writeraw(line)
self.newline()
def indent(self):
self.indentlevel = self.indentlevel + 1
def dedent(self):
assert self.indentlevel > 0
self.indentlevel = self.indentlevel - 1
def stringifyattrs(self, *args, **kwargs):
if kwargs:
assert not args
attributes = kwargs.items()
attributes.sort()
elif args:
assert len(args) == 1
attributes = args[0]
else:
return ""
data = ""
for attr, value in attributes:
data = data + ' %s="%s"' % (attr, escapeattr(str(value)))
return data
def escape(data):
data = string.replace(data, "&", "&amp;")
data = string.replace(data, "<", "&lt;")
return data
def escapeattr(data):
data = string.replace(data, "&", "&amp;")
data = string.replace(data, "<", "&lt;")
data = string.replace(data, '"', "&quot;")
return data
def escape8bit(data):
def escapechar(c):
n = ord(c)
if c in "<&":
if c == "&":
return "&amp;"
else:
return "&lt;"
elif 32 <= n <= 127:
return c
else:
return "&#" + `n` + ";"
return string.join(map(escapechar, data), "")
needswap = struct.pack("h", 1) == "\001\000"
def escape16bit(data):
import array
a = array.array("H")
a.fromstring(data)
if needswap:
a.byteswap()
def escapenum(n, amp=ord("&"), lt=ord("<")):
if n == amp:
return "&amp;"
elif n == lt:
return "&lt;"
elif 32 <= n <= 127:
return chr(n)
else:
return "&#" + `n` + ";"
return string.join(map(escapenum, a), "")
def hexStr(s):
h = string.hexdigits
r = ''
for c in s:
i = ord(c)
r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
return r