#!/usr/pkg/bin/python2.2
# combine zefflores and zeicbl into one virtual TeX font
# (could/should be generalised for others)
import string
class PLLexer:
# rough-n-ready intake for font propety-list files
def __init__(self, file):
self.currlex = None
self.char = None
self.file=file
# get the next lexeme
def lexeme(self):
self._fill()
x = self.currlex
self.currlex = None
return x
# peek at the next lexeme (but don't advance)
def peek(self):
self._fill()
return self.currlex
def peekbyte(self):
if not self.char:
self.char = self.file.read(1)
if ""==self.char:
self.char=None
return self.char
def byte(self):
x = self.peekbyte();
self.char=None
return x
# internal
def _fill(self):
if not self.currlex:
self.currlex=self._lexeme()
# internal
def _lexeme(self):
while 1:
x = self.byte()
if not x:
return None
if x not in string.whitespace:
break
if x in ")(":
return x
if x in "+-0123456789.":
while self.peekbyte() in "0123456789.":
x=x+self.byte()
else:
while self.peekbyte() not in (")("+string.whitespace):
x=x+self.byte()
return x
def readexpr(self):
x = self.lexeme()
if not x:
return None
if "("==x:
y = list();
while ")"!=self.peek():
y.append(self.readexpr())
self.lexeme()
return y
lx=string.lower(x)
z = None
try:
# i want to downcase keywords and reduce the numeric
# codes; but something that looks like a numeric intro
# might not actually be one... try it and see
if lx=="c" and self.peek() not in ")(":
z=ord(self.peek())
if lx=="d":
z=int(self.peek(),10)
if lx=="o":
z=int(self.peek(),8)
if lx=="r":
z=float(self.peek())
except:
pass
if type(None)!=type(z):
# it worked out as a numeric intro,
# so return the number and eat its token
lx = z
self.lexeme()
return lx
class PLFont:
all = range(0,256)
dims = ["slant","space","stretch","shrink","xheight","quad"]
def __init__(self):
self.font = list() # imported fonts
self.slot = list() # character data
self.metrics = {} # dimension data
for i in PLFont.all:
self.slot.append(PLCharacter())
# copy of character c from font n
def take(self, f, c):
x = PLCharacter(self.font[f].slot[c])
x.mapto=(f,c)
return x
# read the file stream as a TeX VPL
def read(self, file):
lex = PLLexer(file)
while 1:
la = lex.readexpr()
if not la:
break
if "ligtable"==la[0]:
ch = None
for lb in la[1:]:
if "label"==lb[0]:
ch = self.slot[lb[1]]
if "krn"==lb[0] or "lig"==lb[0]:
ch.liquor.append((lb[1],lb[2]))
if "stop"==lb[0]:
ch = None
elif "character"==la[0]:
ch = self.slot[la[1]]
for lb in la[2:]:
if "charwd"==lb[0]:
ch.width=lb[1]
if "charht"==lb[0]:
ch.height=lb[1]
if "chardp"==lb[0]:
ch.depth=lb[1]
elif "fontdimen"==la[0]:
for lb in la[1:]:
if lb[0] in PLFont.dims and type(lb[1])==type(0.):
self.metrics[lb[0]]=lb[1]
else:
pass
# return a tring of TeX VPL
def output(self):
str="(comment VPL for `eiffle' by Kwantus' Python pgm)"
str+="\n(fontdimen"
for i in self.metrics.keys():
str+="\n (%s r %f)"%(i,self.metrics[i])
str+=")"
for i in range(0,len(self.font)):
str+="\n(mapfont d "+`i`
str+="\n (fontname "+self.font[i].name+")"
str+=")"
# ligature/kerning info
str+="\n(ligtable"
for ch in self.slot:
str+=ch.liquorout()
str+=")"
# collect character info
for ch in self.slot:
str+=ch.output()
return str
# handy tightening agent; give it a string and list of numbers
def tighten(font, str, *lst):
for i in range(0,len(lst)):
font.slot[ord(str[i])].tighten(ord(str[i+1]),lst[i])
class PLCharacter:
def __init__(self, x=None):
if type(x)==type(self):
self.width=x.width
self.height=x.height
self.depth=x.depth
self.shift=x.shift
self.liquor=x.liquor[:]
else:
self.liquor=list() # kerning/ligature info
self.width=0 # origin increment
self.height=0 # amount glyph rises above baseline
self.depth=0 # amount glyph descends below baseline
self.shift=(0,0) # amount to shift glyph (right, up)
# self.mapto=(0,0) # font & slot from which to draw glyph
# self.slot # slot the character occupies (slippery but improves much)
def movedown(self, r):
self.height-=r
self.depth+=r
self.shift = (self.shift[0],self.shift[1]-r)
return self
# reduce the kerning
def tighten(self, n, r):
c=-1
L=len(self.liquor)
for i in range(0,L):
(c,x)=self.liquor[i]
if c==n:
break
if c!=n:
c=n
x=0.
self.liquor.append(None)
i=L
if type(x)!=type(0.):
error("was a ligature")
x-=r
self.liquor[i]=(c,x)
def liquorout(ch):
lk = ch.liquor
if lk:
str="\n (label d "+`ch.slot`+")"
for (c,x) in lk:
str+=((type(x)==type(0.) and "\n (krn d %d r %f)") or "\n (lig d %d d %d)")%(c,x)
str+="\n (stop)"
else:
str=""
return str
def output(self):
x="\n(character d "+`self.slot`
x+="\n (charwd r %f)"%self.width
x+="\n (charht r %f)"%self.height
if self.depth>0:
x+="\n (chardp r %f)"%self.depth
x+="\n (map\n (selectfont d %d)"%self.mapto[0]
if self.shift[1]>0:
x+="\n (moveup r %f)"%self.shift[1]
if self.shift[1]<0:
x+="\n (movedown r %f)"%-self.shift[1]
x+="\n (setchar d %d))"%self.mapto[1]
x+=")"
return x
vf = PLFont()
# import zeffloresce
for i in ["zefflores","zeicbl"]:
x=PLFont()
f=file(i+".pl")
x.read(f)
x.name=i
f.close()
vf.font.append(x)
# copy `default'
for i in PLFont.all:
vf.slot[i]=vf.take(0,i)
vf.slot[i].slot=i
vf.metrics=vf.font[0].metrics.copy()
# replace the caps
caps=range(ord("A"),ord("Z")+1)
for i in caps:
vf.slot[i] = vf.take(1,i).movedown(.07)
vf.slot[i].width -= .05
vf.slot[i].slot = i
# these need a bit more fiddling
vf.slot[ord("I")].movedown(.018)
vf.slot[ord("M")].movedown(.012)
vf.slot[ord("O")].movedown(.015)
vf.slot[ord("L")].movedown(.021)
vf.slot[ord("D")].movedown(-.02)
vf.slot[ord("X")].movedown(.01)
vf.slot[ord("Z")].movedown(.015)
# delete inter-face kerns and ligatures
#fixme: i'm knowing too much about the data structures here,
# add some abstractions to hide this
#fixme: i'm not happy, overall, with a `pairlist' for liquor
# i need either to abstract pairlists or use a dictionary or other mapping type
for i in PLFont.all:
kn = list()
f = i in caps
for j in vf.slot[i].liquor:
if f==(j[0] in caps):
kn.append(j)
vf.slot[i].liquor=kn
# tighten some kerns
# (some of these need to be advanced to zefflores)
vf.tighten("ted", .06, .05)
vf.tighten("rch", .039, .03)
vf.tighten("Ba", .04)
vf.tighten("Xa", .03)
vf.tighten("vavecce", .04, .02, .04, .03, .05, .04)
vf.tighten("fawa", .07, .02, .04)
vf.tighten("fo", .06)
vf.tighten("fe", .08)
vf.tighten("co", .03)
for i in ["b", .03, "o", .02, "e", .02, "p", .02, "r", .12, "f", .07, "v", .08, "w", .08, "y", .08]:
if type(i)==type(0.):
vf.tighten(x+".", i)
vf.tighten(x+",", i)
else:
x=i
# a rarity: these were too close
vf.tighten("ib", -.01)
vf.tighten("ir", -.01)
vf.tighten("mu", -.007)
vf.tighten("Or", -.012)
# write the VPL
print vf.output()
#eof