From f2cf9c5d6d503e16ee43dc9b617d96aed38806a8 Mon Sep 17 00:00:00 2001 From: jvr Date: Thu, 23 May 2002 21:50:36 +0000 Subject: [PATCH] first working version of CFF/T2 compiler; needs cleanup/refactoring, and doesn't import from XML yet; hardly tested. git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@253 4cde692c-a291-49d1-8350-778aa11640f8 --- Lib/fontTools/cffLib.py | 430 ++++++++++++++++++++++++++-- Lib/fontTools/misc/psCharStrings.py | 99 +++++-- 2 files changed, 474 insertions(+), 55 deletions(-) diff --git a/Lib/fontTools/cffLib.py b/Lib/fontTools/cffLib.py index 739f6f29a..6ff78a74f 100644 --- a/Lib/fontTools/cffLib.py +++ b/Lib/fontTools/cffLib.py @@ -1,12 +1,12 @@ """cffLib.py -- read/write tools for Adobe CFF fonts.""" # -# $Id: cffLib.py,v 1.20 2002-05-18 20:07:01 jvr Exp $ +# $Id: cffLib.py,v 1.21 2002-05-23 21:50:36 jvr Exp $ # import struct, sstruct import string -import types +from types import FloatType, ListType, TupleType from fontTools.misc import psCharStrings @@ -30,10 +30,11 @@ class CFFFontSet: assert self.major == 1 and self.minor == 0, \ "unknown CFF format: %d.%d" % (self.major, self.minor) + file.seek(self.hdrSize) self.fontNames = list(Index(file, "fontNames")) self.topDictIndex = TopDictIndex(file) self.strings = IndexedStrings(file) - self.GlobalSubrs = CharStringIndex(file, name="GlobalSubrsIndex") + self.GlobalSubrs = GlobalSubrsIndex(file, name="GlobalSubrsIndex") self.topDictIndex.strings = self.strings self.topDictIndex.GlobalSubrs = self.GlobalSubrs @@ -53,9 +54,25 @@ class CFFFontSet: raise KeyError, name return self.topDictIndex[index] - def compile(self): + def compile(self, file): strings = IndexedStrings() - XXXX + writer = CFFWriter() + writer.add(sstruct.pack(cffHeaderFormat, self)) + fontNames = Index() + for name in self.fontNames: + fontNames.append(name) + writer.add(fontNames.getCompiler(strings, None)) + topCompiler = self.topDictIndex.getCompiler(strings, None) + writer.add(topCompiler) + writer.add(strings.getCompiler()) + writer.add(self.GlobalSubrs.getCompiler(strings, None)) + + for child in topCompiler.getChildren(strings): + writer.add(child) + + print writer.data + + writer.toFile(file) def toXML(self, xmlWriter, progress=None): xmlWriter.newline() @@ -78,13 +95,163 @@ class CFFFontSet: xxx +class CFFWriter: + + def __init__(self): + self.data = [] + + def add(self, table): + self.data.append(table) + + def toFile(self, file): + lastPosList = None + count = 1 + while 1: + print "XXX iteration", count + count += 1 + pos = 0 + posList = [pos] + for item in self.data: + if hasattr(item, "setPos"): + item.setPos(pos) + if hasattr(item, "getDataLength"): + pos = pos + item.getDataLength() + else: + pos = pos + len(item) + posList.append(pos) + if posList == lastPosList: + break + lastPosList = posList + begin = file.tell() + posList = [0] + for item in self.data: + if hasattr(item, "toFile"): + item.toFile(file) + else: + file.write(item) + posList.append(file.tell() - begin) + if posList != lastPosList: + print "++++" + print posList + print lastPosList + assert posList == lastPosList + + +def calcOffSize(largestOffset): + if largestOffset < 0x100: + offSize = 1 + elif largestOffset < 0x10000: + offSize = 2 + elif largestOffset < 0x1000000: + offSize = 3 + else: + offSize = 4 + return offSize + + +class IndexCompiler: + + def __init__(self, items, strings, parent): + self.items = self.getItems(items, strings) + self.parent = parent + + def getItems(self, items, strings): + return items + + def getOffsets(self): + pos = 1 + offsets = [pos] + for item in self.items: + if hasattr(item, "getDataLength"): + pos = pos + item.getDataLength() + else: + pos = pos + len(item) + offsets.append(pos) + return offsets + + def getDataLength(self): + lastOffset = self.getOffsets()[-1] + offSize = calcOffSize(lastOffset) + dataLength = ( + 2 + # count + 1 + # offSize + (len(self.items) + 1) * offSize + # the offsets + lastOffset - 1 # size of object data + ) + return dataLength + + def toFile(self, file): + size = self.getDataLength() + start = file.tell() + offsets = self.getOffsets() + writeCard16(file, len(self.items)) + offSize = calcOffSize(offsets[-1]) + writeCard8(file, offSize) + offSize = -offSize + pack = struct.pack + for offset in offsets: + binOffset = pack(">l", offset)[offSize:] + assert len(binOffset) == -offSize + file.write(binOffset) + for item in self.items: + if hasattr(item, "toFile"): + item.toFile(file) + else: + file.write(item) + assert start + size == file.tell() + + +class IndexedStringsCompiler(IndexCompiler): + + def getItems(self, items, strings): + return items.strings + + +class TopDictIndexCompiler(IndexCompiler): + + def getItems(self, items, strings): + out = [] + for item in items: + out.append(item.getCompiler(strings, self)) + return out + + def getChildren(self, strings): + children = [] + for topDict in self.items: + children.extend(topDict.getChildren(strings)) + return children + + +class GlobalSubrsCompiler(IndexCompiler): + def getItems(self, items, strings): + out = [] + for cs in items: + cs.compile() + out.append(cs.bytecode) + return out + +class SubrsCompiler(GlobalSubrsCompiler): + def setPos(self, pos): + offset = pos - self.parent.pos + self.parent.rawDict["Subrs"] = offset + +class CharStringsCompiler(GlobalSubrsCompiler): + def setPos(self, pos): + self.parent.rawDict["CharStrings"] = pos + + class Index: """This class represents what the CFF spec calls an INDEX.""" - def __init__(self, file, name=None): + compilerClass = IndexCompiler + + def __init__(self, file=None, name=None): if name is None: name = self.__class__.__name__ + if file is None: + self.items = [] + return if DEBUG: print "loading %s at %s" % (name, file.tell()) self.file = file @@ -92,11 +259,11 @@ class Index: self.count = count self.items = [None] * count if count == 0: - self.offsets = [] + self.items = [] return offSize = readCard8(file) if DEBUG: - print "index count: %s offSize: %s" % (count, offSize) + print " index count: %s offSize: %s" % (count, offSize) assert offSize <= 4, "offSize too large: %s" % offSize self.offsets = offsets = [] pad = '\0' * (4 - offSize) @@ -107,9 +274,11 @@ class Index: offsets.append(int(offset)) self.offsetBase = file.tell() - 1 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot + if DEBUG: + print " end of %s at %s" % (name, file.tell()) def __len__(self): - return self.count + return len(self.items) def __getitem__(self, index): item = self.items[index] @@ -127,11 +296,19 @@ class Index: def produceItem(self, index, data, file, offset, size): return data - - -class CharStringIndex(Index): - def __init__(self, file, globalSubrs=None, private=None, fdSelect=None, fdArray=None, + def append(self, item): + self.items.append(item) + + def getCompiler(self, strings, parent): + return self.compilerClass(self, strings, parent) + + +class GlobalSubrsIndex(Index): + + compilerClass = GlobalSubrsCompiler + + def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None, name=None): Index.__init__(self, file, name) self.globalSubrs = globalSubrs @@ -168,10 +345,15 @@ class CharStringIndex(Index): else: sel = fdSelect[index] return self[index], sel - + +class SubrsIndex(GlobalSubrsIndex): + compilerClass = SubrsCompiler + class TopDictIndex(Index): + compilerClass = TopDictIndexCompiler + def produceItem(self, index, data, file, offset, size): top = TopDict(self.strings, file, offset, self.GlobalSubrs) top.decompile(data) @@ -189,7 +371,7 @@ class TopDictIndex(Index): class CharStrings: def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray): - self.charStringsIndex = CharStringIndex(file, globalSubrs, private, fdSelect, fdArray) + self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray) self.nameToIndex = nameToIndex = {} for i in range(len(charset)): nameToIndex[charset[i]] = i @@ -237,12 +419,34 @@ def readCard16(file): value, = struct.unpack(">H", file.read(2)) return value +def writeCard8(file, value): + file.write(chr(value)) + +def writeCard16(file, value): + file.write(struct.pack(">H", value)) + +def packCard8(value): + return chr(value) + +def packCard16(value): + return struct.pack(">H", value) + def buildOperatorDict(table): d = {} for op, name, arg, default, conv in table: d[op] = (name, arg) return d +def buildOpcodeDict(table): + d = {} + for op, name, arg, default, conv in table: + if type(op) == TupleType: + op = chr(op[0]) + chr(op[1]) + else: + op = chr(op) + d[name] = (op, arg) + return d + def buildOrder(table): l = [] for op, name, arg, default, conv in table: @@ -266,6 +470,8 @@ def buildConverters(table): class BaseConverter: def read(self, parent, value): return value + def write(self, parent, value): + return value def xmlWrite(self, xmlWriter, name, value): xmlWriter.begintag(name) xmlWriter.newline() @@ -283,12 +489,16 @@ class PrivateDictConverter(BaseConverter): len(data) == size pr.decompile(data) return pr + def write(self, parent, value): + return (0, 0) # dummy value class SubrsConverter(BaseConverter): def read(self, parent, value): file = parent.file file.seek(parent.offset + value) # Offset(self) - return CharStringIndex(file, name="SubrsIndex") + return SubrsIndex(file, name="SubrsIndex") + def write(self, parent, value): + return 0 # dummy value class CharStringsConverter(BaseConverter): def read(self, parent, value): @@ -303,6 +513,8 @@ class CharStringsConverter(BaseConverter): private = parent.Private file.seek(value) # Offset(0) return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray) + def write(self, parent, value): + return 0 # dummy value class CharsetConverter: def read(self, parent, value): @@ -311,14 +523,18 @@ class CharsetConverter: numGlyphs = parent.numGlyphs file = parent.file file.seek(value) + if DEBUG: + print "loading charset at %s" % value format = readCard8(file) if format == 0: - raise NotImplementedError + charset =parseCharset0(numGlyphs, file, parent.strings) elif format == 1 or format == 2: charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) else: raise NotImplementedError assert len(charset) == numGlyphs + if DEBUG: + print " charset end at %s" % file.tell() else: if isCID or not hasattr(parent, "CharStrings"): assert value == 0 @@ -335,12 +551,42 @@ class CharsetConverter: # 2: ExpertSubset charset = None # return charset + def write(self, parent, value): + return 0 # dummy value def xmlWrite(self, xmlWriter, name, value): # XXX GlyphOrder needs to be stored *somewhere*, but not here... xmlWriter.simpletag("charset", value=value) xmlWriter.newline() +class CharsetCompiler: + + def __init__(self, strings, charset, parent): + assert charset[0] == '.notdef' + format = 0 # XXX! + data = [packCard8(format)] + for name in charset[1:]: + data.append(packCard16(strings.getSID(name))) + self.data = "".join(data) + self.parent = parent + + def setPos(self, pos): + self.parent.rawDict["charset"] = pos + + def getDataLength(self): + return len(self.data) + + def toFile(self, file): + file.write(self.data) + + +def parseCharset0(numGlyphs, file, strings): + charset = [".notdef"] + for i in range(numGlyphs - 1): + SID = readCard16(file) + charset.append(strings[SID]) + return charset + def parseCharset(numGlyphs, file, strings, isCID, format): charset = ['.notdef'] count = 1 @@ -405,14 +651,15 @@ class FDSelectConverter: class ROSConverter(BaseConverter): def xmlWrite(self, xmlWriter, name, value): registry, order, supplement = value - xmlWriter.simpletag(name, [('registry', registry), ('order', order), - ('supplement', supplement)]) + xmlWriter.simpletag(name, [('Registry', registry), ('Order', order), + ('Supplement', supplement)]) xmlWriter.newline() topDictOperators = [ # opcode name argument type default converter ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()), + ((12, 20), 'SyntheticBase', 'number', None, None), (0, 'version', 'SID', None, None), (1, 'Notice', 'SID', None, None), ((12, 0), 'Copyright', 'SID', None, None), @@ -432,7 +679,6 @@ topDictOperators = [ ((12, 8), 'StrokeWidth', 'number', 0, None), (14, 'XUID', 'array', None, None), (15, 'charset', 'number', 0, CharsetConverter()), - ((12, 20), 'SyntheticBase', 'number', None, None), ((12, 21), 'PostScript', 'SID', None, None), ((12, 22), 'BaseFontName', 'SID', None, None), ((12, 23), 'BaseFontBlend', 'delta', None, None), @@ -462,6 +708,8 @@ privateDictOperators = [ ((12, 12), 'StemSnapH', 'delta', None, None), ((12, 13), 'StemSnapV', 'delta', None, None), ((12, 14), 'ForceBold', 'number', 0, None), + ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated + ((12, 16), 'lenIV', 'number', None, None), # deprecated ((12, 17), 'LanguageGroup', 'number', 0, None), ((12, 18), 'ExpansionFactor', 'number', 0.06, None), ((12, 19), 'initialRandomSeed', 'number', 0, None), @@ -479,20 +727,138 @@ class PrivateDictDecompiler(psCharStrings.DictDecompiler): operators = buildOperatorDict(privateDictOperators) +class DictCompiler: + + def __init__(self, dictObj, strings, parent): + assert isinstance(strings, IndexedStrings) + self.dictObj = dictObj + self.strings = strings + self.parent = parent + rawDict = {} + for name in dictObj.order: + value = getattr(dictObj, name, None) + if value is None: + continue + conv = dictObj.converters[name] + if conv: + value = conv.write(dictObj, value) + if value == dictObj.defaults.get(name): + continue + rawDict[name] = value + self.rawDict = rawDict + + def setPos(self, pos): + pass + + def getDataLength(self): + return len(self.compile()) + + def compile(self): + rawDict = self.rawDict + data = [] + for name in self.dictObj.order: + value = rawDict.get(name) + if value is None: + continue + op, argType = self.opcodes[name] + if type(argType) == TupleType: + l = len(argType) + assert len(value) == l, "value doesn't match arg type" + for i in range(l): + arg = argType[l - i - 1] + v = value[i] + arghandler = getattr(self, "arg_" + arg) + data.append(arghandler(v)) + else: + arghandler = getattr(self, "arg_" + argType) + data.append(arghandler(value)) + data.append(op) + return "".join(data) + + def toFile(self, file): + file.write(self.compile()) + + def arg_number(self, num): + return encodeNumber(num) + def arg_SID(self, s): + return psCharStrings.encodeIntCFF(self.strings.getSID(s)) + def arg_array(self, value): + data = [] + for num in value: + data.append(encodeNumber(num)) + return "".join(data) + def arg_delta(self, value): + out = [] + last = 0 + for v in value: + out.append(v - last) + last = v + data = [] + for num in out: + data.append(encodeNumber(num)) + return "".join(data) + + +def encodeNumber(num): + if type(num) == FloatType: + return psCharStrings.encodeFloat(num) + else: + return psCharStrings.encodeIntCFF(num) + + +class TopDictCompiler(DictCompiler): + + opcodes = buildOpcodeDict(topDictOperators) + + def getChildren(self, strings): + children = [] + if hasattr(self.dictObj, "charset"): + children.append(CharsetCompiler(strings, self.dictObj.charset, self)) + if hasattr(self.dictObj, "CharStrings"): + items = [] + charStrings = self.dictObj.CharStrings + for name in self.dictObj.charset: + items.append(charStrings[name]) + charStringsComp = CharStringsCompiler(items, strings, self) + children.append(charStringsComp) + if hasattr(self.dictObj, "Private"): + privComp = self.dictObj.Private.getCompiler(strings, self) + children.append(privComp) + children.extend(privComp.getChildren(strings)) + return children + + +class PrivateDictCompiler(DictCompiler): + + opcodes = buildOpcodeDict(privateDictOperators) + + def setPos(self, pos): + size = len(self.compile()) + self.parent.rawDict["Private"] = size, pos + self.pos = pos + + def getChildren(self, strings): + children = [] + if hasattr(self.dictObj, "Subrs"): + children.append(self.dictObj.Subrs.getCompiler(strings, self)) + return children + class BaseDict: def __init__(self, strings, file, offset): self.rawDict = {} if DEBUG: - print "loading %s at %s" % (self, offset) + print "loading %s at %s" % (self.__class__.__name__, offset) self.file = file self.offset = offset self.strings = strings self.skipNames = [] def decompile(self, data): - dec = self.decompiler(self.strings) + if DEBUG: + print " length %s is %s" % (self.__class__.__name__, len(data)) + dec = self.decompilerClass(self.strings) dec.decompile(data) self.rawDict = dec.getDict() self.postDecompile() @@ -500,6 +866,9 @@ class BaseDict: def postDecompile(self): pass + def getCompiler(self, strings, parent): + return self.compilerClass(self, strings, parent) + def __getattr__(self, name): value = self.rawDict.get(name) if value is None: @@ -523,7 +892,7 @@ class BaseDict: if conv is not None: conv.xmlWrite(xmlWriter, name, value) else: - if isinstance(value, types.ListType): + if isinstance(value, ListType): value = " ".join(map(str, value)) xmlWriter.simpletag(name, value=value) xmlWriter.newline() @@ -534,7 +903,8 @@ class TopDict(BaseDict): defaults = buildDefaults(topDictOperators) converters = buildConverters(topDictOperators) order = buildOrder(topDictOperators) - decompiler = TopDictDecompiler + decompilerClass = TopDictDecompiler + compilerClass = TopDictCompiler def __init__(self, strings, file, offset, GlobalSubrs): BaseDict.__init__(self, strings, file, offset) @@ -570,7 +940,8 @@ class PrivateDict(BaseDict): defaults = buildDefaults(privateDictOperators) converters = buildConverters(privateDictOperators) order = buildOrder(privateDictOperators) - decompiler = PrivateDictDecompiler + decompilerClass = PrivateDictDecompiler + compilerClass = PrivateDictCompiler class IndexedStrings: @@ -584,6 +955,12 @@ class IndexedStrings: strings = list(Index(file, "IndexedStrings")) self.strings = strings + def getCompiler(self): + return IndexedStringsCompiler(self, None, None) + + def __len__(self): + return len(self.strings) + def __getitem__(self, SID): if SID < cffStandardStringCount: return cffStandardStrings[SID] @@ -595,7 +972,7 @@ class IndexedStrings: self.buildStringMapping() if cffStandardStringMapping.has_key(s): SID = cffStandardStringMapping[s] - if self.stringMapping.has_key(s): + elif self.stringMapping.has_key(s): SID = self.stringMapping[s] else: SID = len(self.strings) + cffStandardStringCount @@ -687,4 +1064,3 @@ cffStandardStringMapping = {} for _i in range(cffStandardStringCount): cffStandardStringMapping[cffStandardStrings[_i]] = _i - diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py index 795da46ce..ec682ef54 100644 --- a/Lib/fontTools/misc/psCharStrings.py +++ b/Lib/fontTools/misc/psCharStrings.py @@ -29,9 +29,12 @@ cffDictOperandEncoding[255] = "reserved" realNibbles = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', None, '-'] +realNibblesDict = {} +for _i in range(len(realNibbles)): + realNibblesDict[realNibbles[_i]] = _i -class ByteCodeDecompilerBase: +class ByteCodeBase: def read_byte(self, b0, data, index): return b0 - 139, index @@ -140,7 +143,64 @@ t2Operators = [ ((12, 37), 'flex1'), ] -class T2CharString(ByteCodeDecompilerBase): + +def getIntEncoder(format): + fourByteOp = chr(255) + isT1 = 0 + if format == "cff": + fourByteOp = chr(29) + elif format == "t1": + isT1 = 1 + else: + assert format == "t2" + + def encodeInt(value, fourByteOp=fourByteOp, isT1=isT1, + chr=chr, pack=struct.pack, unpack=struct.unpack): + if -107 <= value <= 107: + code = chr(value + 139) + elif 108 <= value <= 1131: + value = value - 108 + code = chr((value >> 8) + 247) + chr(value & 0xFF) + elif -1131 <= value <= -108: + value = -value - 108 + code = chr((value >> 8) + 251) + chr(value & 0xFF) + elif not isT1 and -32768 <= value <= 32767: + code = chr(28) + pack(">h", value) + else: + code = fourByteOp + pack(">l", value) + return code + + return encodeInt + + +encodeIntCFF = getIntEncoder("cff") +encodeIntT1 = getIntEncoder("t1") +encodeIntT2 = getIntEncoder("t2") + +def encodeFloat(f): + s = str(f).upper() + if s[:2] == "0.": + s = s[1:] + elif s[:3] == "-0.": + s = "-" + s[2:] + nibbles = [] + while s: + c = s[0] + s = s[1:] + if c == "E" and s[:1] == "-": + s = s[1:] + c = "E-" + nibbles.append(realNibblesDict[c]) + nibbles.append(0xf) + if len(nibbles) % 2: + nibbles.append(0xf) + d = chr(30) + for i in range(0, len(nibbles), 2): + d = d + chr(nibbles[i] << 4 | nibbles[i+1]) + return d + + +class T2CharString(ByteCodeBase): operandEncoding = t2OperandEncoding operators, opcodes = buildOperatorDict(t2Operators) @@ -178,41 +238,24 @@ class T2CharString(ByteCodeDecompilerBase): bytecode.extend(map(chr, opcodes[token])) else: bytecode.append(token) # hint mask - elif tp == types.FloatType: - # only in CFF - raise NotImplementedError elif tp == types.IntType: - # XXX factor out, is largely OK for CFF dicts, too. - if -107 <= token <= 107: - code = chr(token + 139) - elif 108 <= token <= 1131: - token = token - 108 - code = chr((token >> 8) + 247) + chr(token & 0xFF) - elif -1131 <= token <= -108: - token = -token - 108 - code = chr((token >> 8) + 251) + chr(token & 0xFF) - elif -32768 <= token <= 32767: - # XXX T2/CFF-specific: doesn't exist in T1 - code = chr(28) + struct.pack(">h", token) - else: - # XXX T1/T2-specific: different opcode in CFF - code = chr(255) + struct.pack(">l", token) - bytecode.append(code) + bytecode.append(encodeIntT2(token)) else: - assert 0 + assert 0, "unsupported type: %s" % tp bytecode = "".join(bytecode) - if DEBUG: - assert bytecode == self.__bytecode - + self.setBytecode(bytecode) + def needsDecompilation(self): return self.bytecode is not None def setProgram(self, program): self.program = program - if DEBUG: - self.__bytecode = self.bytecode self.bytecode = None + def setBytecode(self, bytecode): + self.bytecode = bytecode + self.program = None + def getToken(self, index, len=len, ord=ord, getattr=getattr, type=type, StringType=types.StringType): if self.bytecode is not None: @@ -847,7 +890,7 @@ class T1OutlineExtractor(T2OutlineExtractor): self.popall() # XXX -class DictDecompiler(ByteCodeDecompilerBase): +class DictDecompiler(ByteCodeBase): operandEncoding = cffDictOperandEncoding