#!/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
