# Sketch - A Python-based interactive drawing program
# Copyright (C) 1998, 1999, 2003 by Bernhard Herzog
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Extract character outlines from Type1 font files...
#
import sys
from cStringIO import StringIO
from types import StringType
from string import atoi,split,find,strip
import streamfilter
from Sketch import Point
from Sketch._type1 import decode,hexdecode
from Sketch.pstokenize import PSTokenizer,OPERATOR,NAME,INT,END
def read_type1_file(filename):
data = StringIO()
file = open(filename, 'rb')
head = file.read(6)
if head[:2] == '%!':
line = file.readline()
while line:
line = file.readline()
pos = find(line, 'eexec')
if pos >= 0:
data.write(line[:pos + 5] + '\n')
line = line[pos + 5:]
break
data.write(line)
try:
buf = line + file.read(200)
buf, extra = hexdecode(buf)
buf, r = decode(buf, 55665)
data.write(buf[4:])
buf = extra + file.read(200)
while buf:
buf, extra = hexdecode(buf)
buf, r = decode(buf, r)
data.write(buf)
buf = extra + file.read(200)
except:
pass
else:
while 1:
if ord(head[0]) != 128:
raise TypeError, 'not a pfb file'
data_type = ord(head[1])
if data_type == 3:
# EOF
break
data_length = ord(head[2]) + 256 * ord(head[3]) \
+ 65536 * ord(head[4]) + 16777216 * ord(head[5])
if data_type == 1: # ASCII
data.write(file.read(data_length))
elif data_type == 2: #Binary
# decode and discard the first 4 bytes
buf = file.read(4)
if len(buf) < 4:
raise IOError, "insufficient data"
buf, r = decode(buf, 55665)
data_length = data_length - 4
if data_length < 0:
raise IOError, "invalid data"
while data_length:
buf = file.read(min(1000, data_length))
if not buf:
raise IOError, "insufficient data"
buf, r = decode(buf, r)
data.write(buf)
data_length = data_length - len(buf)
else:
raise RuntimeError, "Invalid data type"
head = file.read(6)
data = data.getvalue()
return data
def parse_type1_file(data):
subrs = char_strings = None
tokenizer = PSTokenizer(data)
next = tokenizer.next
while 1:
token, value = next()
if token == NAME:
if value == 'Subrs':
token, value = next()
if token == INT:
subrs = read_subrs(tokenizer, value)
elif value == 'CharStrings':
char_strings = read_char_strings(tokenizer)
if subrs is not None:
break
elif token == END:
break
return subrs, char_strings
def read_subrs(tokenizer, num):
if not num:
return []
subrs = [''] * num
next = tokenizer.next
read = tokenizer.read
while not subrs[-1]:
token, value = next()
if token == OPERATOR and value == 'dup':
token, index = next()
token, length = next()
next() # discard RD operator
read(1) # discard space character
data = read(length)
subrs[index] = decode(data)[0][4:]
elif token == END:
break
return subrs
def read_char_strings(tokenizer):
char_strings = {}
next = tokenizer.next
read = tokenizer.read
while 1:
token, value = next()
if token == NAME:
token, length = next()
next() # discard RD operator
read(1) # discard space character
data = read(length)
char_strings[value] = decode(data)[0][4:]
elif token == END or (token == OPERATOR and value == 'end'):
break
return char_strings
class SubrReturn(Exception):
pass
class CharStringInterpreter:
commands = {}
def __init__(self, subrs):
self.subrs = subrs
self.reset()
def print_path(self):
for closed, path in self.paths:
if closed:
print 'closed:'
else:
print 'open:'
for part in path:
print part
def reset(self):
self.stack = []
self.ps_stack = []
self.paths = ()
self.path = []
self.closed = 0
self.in_flex = 0
self.flex = []
self.cur = Point(0, 0)
def execute(self, cs):
stack = self.stack
cs = map(ord, cs)
try:
while cs:
code = cs[0]; del cs[0]
if code >= 32:
if code <= 246:
stack.append(code - 139)
elif code <= 250:
stack.append((code - 247) * 256 + cs[0] + 108)
del cs[0]
elif code <= 254:
stack.append(-(code - 251) * 256 - cs[0] - 108)
del cs[0]
else:
stack.append(cs[0] * 0x01000000 + cs[1] * 0x10000
+ cs[2] * 0x100 + cs[3])
del cs[:4]
else:
if code == 12:
code = 32 + cs[0]
del cs[0]
cmd = self.commands[code]
if cmd:
cmd(self)
except SubrReturn:
return
def new_path(self):
if self.path:
self.paths = self.paths + ((self.closed, self.path),)
self.path = []
self.closed = 0
def flush_stack(self, *rest):
del self.stack[:]
commands[32 + 0] = flush_stack # dotsection
commands[1] = flush_stack # hstem
commands[32 + 2] = flush_stack # hstem3
commands[3] = flush_stack # vstem
commands[32 + 1] = flush_stack # vstem3
def pop(self, n):
result = self.stack[-n:]
del self.stack[-n:]
if n == 1:
return result[0]
return result
def pop_all(self):
result = self.stack[:]
del self.stack[:]
if len(result) == 1:
return result[0]
return result
def endchar(self):
self.new_path()
self.flush_stack()
commands[14] = endchar
def hsbw(self):
# horizontal sidebearing and width
sbx, wx = self.pop_all()
self.cur = Point(sbx, 0)
commands[13] = hsbw
def seac(self):
# standard encoding accented character
asb, adx, ady, bchar, achar = self.pop_all()
commands[32 + 6] = seac
def sbw(self):
# sidebearing and width
sbx, sby, wx, wy = self.pop_all()
self.cur = Point(sbx, sby)
commands[32 + 7] = sbw
def closepath(self):
self.pop_all()
self.closed = 1
commands[9] = closepath
def rlineto(self):
dx, dy = self.pop_all()
self.cur = self.cur + Point(dx, dy)
self.path.append(tuple(self.cur))
commands[5] = rlineto
def hlineto(self):
dx = self.pop_all()
self.cur = self.cur + Point(dx, 0)
self.path.append(tuple(self.cur))
commands[6] = hlineto
def vlineto(self):
dy = self.pop_all()
self.cur = self.cur + Point(0, dy)
self.path.append(tuple(self.cur))
commands[7] = vlineto
def rmoveto(self):
dx, dy = self.pop_all()
self.cur = self.cur + Point(dx, dy)
if self.in_flex:
self.flex.append(self.cur)
else:
self.new_path()
self.path.append(tuple(self.cur))
commands[21] = rmoveto
def hmoveto(self):
dx = self.pop_all()
self.cur = self.cur + Point(dx, 0)
self.new_path()
self.path.append(tuple(self.cur))
commands[22] = hmoveto
def vmoveto(self):
dy = self.pop_all()
self.cur = self.cur + Point(0, dy)
self.new_path()
self.path.append(tuple(self.cur))
commands[4] = vmoveto
def rrcurveto(self):
dx1, dy1, dx2, dy2, dx3, dy3 = self.pop_all()
d1 = self.cur + Point(dx1, dy1)
d2 = d1 + Point(dx2, dy2)
d3 = d2 + Point(dx3, dy3)
self.cur = d3
self.path.append(tuple(d1) +tuple(d2) + tuple(d3))
commands[8] = rrcurveto
def hvcurveto(self):
dx1, dx2, dy2, dy3 = self.pop_all()
d1 = self.cur + Point(dx1, 0)
d2 = d1 + Point(dx2, dy2)
d3 = d2 + Point(0, dy3)
self.cur = d3
self.path.append(tuple(d1) + tuple(d2) + tuple(d3))
commands[31] = hvcurveto
def vhcurveto(self):
dy1, dx2, dy2, dx3 = self.pop_all()
d1 = self.cur + Point(0, dy1)
d2 = d1 + Point(dx2, dy2)
d3 = d2 + Point(dx3, 0)
self.cur = d3
self.path.append(tuple(d1) + tuple(d2) + tuple(d3))
commands[30] = vhcurveto
def start_flex(self):
self.in_flex = 1
self.flex = []
def end_flex(self):
size, x, y = self.pop_all()
d1, d2, d3 = self.flex[1:4]
self.path.append(tuple(d1) + tuple(d2) + tuple(d3))
d1, d2, d3 = self.flex[4:7]
self.path.append(tuple(d1) + tuple(d2) + tuple(d3))
def div(self):
num1, num2 = self.pop(2)
self.stack.append(float(num1) / num2)
commands[32 + 12] = div
def callothersubr(self):
n, sn = self.pop(2)
if n:
self.ps_stack = self.pop(n)
if sn == 3:
self.ps_stack = [3]
commands[32 + 16] = callothersubr
def callsubr(self):
num = self.pop(1)
if num == 0:
self.end_flex()
elif num == 1:
self.start_flex()
elif 2 <= num <= 3:
return
else:
self.execute(self.subrs[num])
commands[10] = callsubr
def pop_ps(self):
value = self.ps_stack[-1]
del self.ps_stack[-1]
self.stack.append(value)
commands[32 + 17] = pop_ps
def subr_return(self):
raise SubrReturn
commands[11] = subr_return
def setcurrentpoint(self):
x, y = self.pop_all()
self.cur = Point(x, y)
commands[32 + 33] = setcurrentpoint
#
# read_outlines(FILENAME)
#
# Return the outlines of the glyphs in the Type1 font stored in the file
# FILENAME as a tuple (CHAR_STRINGS, INTERPRETER). CHAR_STRINGS is a
# dictionary mapping glyph names to strings containing the outline
# description, INTERPRETER is an instance of CharStringInterpreter
# initialized with the appropriate Subrs.
def read_outlines(filename):
data = read_type1_file(filename)
data = streamfilter.StringDecode(data, None)
subrs, char_strings = parse_type1_file(data)
interpreter = CharStringInterpreter(subrs)
return char_strings, interpreter
def embed_type1_file(fontfile, outfile):
if type(fontfile) == StringType:
file = open(filename, 'rb')
else:
file = fontfile
head = file.read(6)
if head[:2] == '%!':
# PFA
outfile.write(head)
data = file.read(4000)
while data:
outfile.write(data)
data = file.read(4000)
else:
# Probably PFB
while 1: # loop over all chunks
if ord(head[0]) != 128:
raise TypeError, 'not a pfb file'
data_type = ord(head[1])
if data_type == 3:
# EOF
break
data_length = ord(head[2]) + 256 * ord(head[3]) \
+ 65536 * ord(head[4]) + 16777216 * ord(head[5])
if data_type == 1: # ASCII
outfile.write(file.read(data_length))
elif data_type == 2: #Binary
# Hex encode data
encoder = streamfilter.HexEncode(outfile)
while data_length:
if data_length > 4000:
length = 4000
else:
length = data_length
data = file.read(length)
encoder.write(data)
data_length = data_length - length
encoder.close()
head = file.read(6)
#
# some test functions...
#
def test():
filename = sys.argv[1]
data = read_type1_file(filename)
data = streamfilter.StringDecode(data, None)
subrs, char_strings = parse_type1_file(data)
items = char_strings.items()
items.sort()
interpreter = CharStringInterpreter(subrs)
for name, code in items:
print name, `code`
interpreter.execute(code)
interpreter.print_path()
interpreter.reset()
if __name__ == '__main__':
test()
|