openlase/tools/genfont.py
2011-03-20 08:30:19 +01:00

441 lines
No EOL
11 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
import os, sys, math
import xml.sax, xml.sax.handler
import re
def pc(c):
x,y = c
if isinstance(x,int) and isinstance(y,int):
return "(%d,%d)"%(x,y)
else:
return "(%.4f, %.4f)"%(x,y)
class PathLine(object):
def __init__(self, start, end, on=True):
self.start = start
self.end = end
self.on = on
def copy(self):
return PathLine(self.start, self.end)
def transform(self, func):
self.start = func(self.start)
self.end = func(self.end)
def reverse(self):
return PathLine(self.end, self.start, self.on)
def scp(self):
return self.end
def ecp(self):
return self.start
def showinfo(self, tr=''):
print tr+'Line( %s %s )'%(pc(self.start),pc(self.end))
class PathBezier4(object):
def __init__(self, start, cp1, cp2, end):
self.start = start
self.end = end
self.cp1 = cp1
self.cp2 = cp2
def copy(self):
return PathBezier4(self.start, self.cp1, self.cp2, self.end)
def transform(self, func):
self.start = func(self.start)
self.cp1 = func(self.cp1)
self.cp2 = func(self.cp2)
self.end = func(self.end)
def reverse(self):
return PathBezier4(self.end, self.cp2, self.cp1, self.start)
def scp(self):
if self.cp1 != self.start:
return self.cp1
elif self.cp2 != self.start:
return self.cp2
else:
return self.end
def ecp(self):
if self.cp2 != self.end:
return self.cp2
elif self.cp1 != self.end:
return self.cp1
else:
return self.start
def showinfo(self, tr=''):
print tr+'Bezier( %s %s %s %s )'%(pc(self.start),pc(self.cp1),pc(self.cp2),pc(self.end))
class PathBezier3(PathBezier4):
def __init__(self, start, cp, end):
sx,sy = start
cx,cy = cp
ex,ey = end
c1x = (1.0/3) * cx + (2.0/3) * sx
c1y = (1.0/3) * cy + (2.0/3) * sy
c2x = (1.0/3) * cx + (2.0/3) * ex
c2y = (1.0/3) * cy + (2.0/3) * ey
PathBezier4.__init__(start, (c1x,c1y), (c2x,c2y), end)
class LaserPath(object):
def __init__(self):
self.segments = []
def add(self, seg):
self.segments.append(seg)
def transform(self, func):
for i in self.segments:
i.transform(func)
def startpos(self):
return self.segments[0].start
def endpos(self):
return self.segments[-1].end
def reverse(self):
lp = LaserPath()
lp.segments = [x.reverse() for x in self.segments[::-1]]
return lp
def showinfo(self, tr=''):
print tr+'LaserPath:'
for i in self.segments:
i.showinfo(tr+' ')
class LaserFrame(object):
def __init__(self):
self.objects = []
def add(self, obj):
self.objects.append(obj)
def transform(self, func):
for i in self.objects:
i.transform(func)
def showinfo(self, tr=''):
print tr+'LaserFrame:'
for i in self.objects:
i.showinfo(tr+' ')
class SVGPath(object):
def __init__(self, data=None):
self.subpaths = []
if data:
self.parse(data)
def poptok(self):
if not self.tokens:
raise ValueError("Expected token but got end of path data")
return self.tokens.pop(0)
def peektok(self):
if not self.tokens:
raise ValueError("Expected token but got end of path data")
return self.tokens[0]
def popnum(self):
tok = self.tokens.pop(0)
try:
return float(tok)
except ValueError:
raise ValueError("Invalid SVG path numerical token: %s"%tok)
def isnum(self):
if not self.tokens:
return False # no more tokens is considered a non-number
try:
float(self.peektok())
except ValueError:
return False
return True
def popcoord(self,rel=False, cur=None):
x = self.popnum()
if self.peektok() == ',':
self.poptok()
y = self.popnum()
if rel:
x += cur[0]
y += cur[1]
return x,y
def reflect(self, center, point):
cx,cy = center
px,py = point
return (2*cx-px, 2*cy-py)
def parse(self, data):
ds = re.split(r"[ \r\n\t]*([-+]?\d+\.\d+[eE][+-]?\d+|[-+]?\d+\.\d+|[-+]?\.\d+[eE][+-]?\d+|[-+]?\.\d+|[-+]?\d+\.?[eE][+-]?\d+|[-+]?\d+\.?|[MmZzLlHhVvCcSsQqTtAa])[, \r\n\t]*", data)
tokens = ds[1::2]
if any(ds[::2]) or not all(ds[1::2]):
raise ValueError("Invalid SVG path expression: %r"%data)
self.tokens = tokens
cur = (0,0)
curcpc = (0,0)
curcpq = (0,0)
sp_start = (0,0)
in_path = False
cmd = None
subpath = LaserPath()
while tokens:
if self.isnum() and cmd in "MmLlHhVvCcSsQqTtAa":
pass
elif self.peektok() in "MmZzLlHhVvCcSsQqTtAa":
cmd = tokens.pop(0)
else:
raise ValueError("Invalid SVG path token %s"%tok)
rel = cmd.upper() != cmd
ucmd = cmd.upper()
if not in_path and ucmd != 'M' :
raise ValueErorr("SVG path segment must begin with 'm' command, not '%s'"%cmd)
if ucmd == 'M':
sp_start = self.popcoord(rel, cur)
if subpath.segments:
self.subpaths.append(subpath)
subpath = LaserPath()
cmd = 'Ll'[rel]
cur = curcpc = curcpq = sp_start
in_path = True
elif ucmd == 'Z':
if sp_start != cur:
subpath.add(PathLine(cur, sp_start))
cur = curcpc = curcpq = sp_start
in_path = False
elif ucmd == 'L':
end = self.popcoord(rel, cur)
subpath.add(PathLine(cur, end))
cur = curcpc = curcpq = end
elif ucmd == 'H':
ex = self.popnum()
if rel:
ex += cur[0]
end = ex,cur[1]
subpath.add(PathLine(cur, end))
cur = curcpc = curcpq = end
elif ucmd == 'V':
ey = self.popnum()
if rel:
ey += cur[1]
end = cur[0],ey
subpath.add(PathLine(cur, end))
cur = curcpc = curcpq = end
elif ucmd in 'CS':
if ucmd == 'S':
c1 = self.reflect(cur,curcpc)
else:
c1 = self.popcoord(rel, cur)
c2 = curcpc = self.popcoord(rel, cur)
end = self.popcoord(rel, cur)
subpath.add(PathBezier4(cur, c1, c2, end))
cur = curcpq = end
elif ucmd in 'QT':
if ucmd == 'T':
cp = curcpq = self.reflect(cur,curcpq)
else:
cp = curcpq = self.popcoord(rel, cur)
end = self.popcoord(rel, cur)
subpath.add(PathBezier3(cur, cp, end))
cur = curcpc = end
elif ucmd in 'Aa':
raise ValueError("Arcs not implemented, biatch")
if subpath.segments:
self.subpaths.append(subpath)
class SVGReader(xml.sax.handler.ContentHandler):
def doctype(self, name, pubid, system):
print name,pubid,system
def startDocument(self):
self.frame = LaserFrame()
self.rects = []
self.matrix_stack = [(1,0,0,1,0,0)]
def endDocument(self):
pass
def startElement(self, name, attrs):
if name == "svg":
self.width = float(attrs['width'].replace("px",""))
self.height = float(attrs['height'].replace("px",""))
elif name == "path":
if 'transform' in attrs.keys():
self.transform(attrs['transform'])
self.addPath(attrs['d'])
if 'transform' in attrs.keys():
self.popmatrix()
elif name == "rect":
self.rects.append((attrs['id'],float(attrs['x']),float(attrs['y']),float(attrs['width']),float(attrs['height'])))
elif name == 'g':
if 'transform' in attrs.keys():
self.transform(attrs['transform'])
else:
self.pushmatrix((1,0,0,1,0,0))
def endElement(self, name):
if name == 'g':
self.popmatrix()
def mmul(self, m1, m2):
a1,b1,c1,d1,e1,f1 = m1
a2,b2,c2,d2,e2,f2 = m2
a3 = a1 * a2 + c1 * b2
b3 = b1 * a2 + d1 * b2
c3 = a1 * c2 + c1 * d2
d3 = b1 * c2 + d1 * d2
e3 = a1 * e2 + c1 * f2 + e1
f3 = b1 * e2 + d1 * f2 + f1
return (a3,b3,c3,d3,e3,f3)
def pushmatrix(self, m):
new_mat = self.mmul(self.matrix_stack[-1], m)
self.matrix_stack.append(new_mat)
def popmatrix(self):
self.matrix_stack.pop()
def tc(self,coord):
vw = vh = max(self.width, self.height) / 2.0
x,y = coord
x -= self.width / 2.0
y -= self.height / 2.0
x = x / vw
y = y / vh
return (x,y)
def ts(self,coord):
a,b,c,d,e,f = self.matrix_stack[-1]
x,y = coord
nx = a*x + c*y + e
ny = b*x + d*y + f
return (nx,ny)
def transform(self, data):
ds = re.split(r"[ \r\n\t]*([a-z]+\([^)]+\)|,)[ \r\n\t]*", data)
tokens = ds[1::2]
if any(ds[::2]) or not all(ds[1::2]):
raise ValueError("Invalid SVG transform expression: %r"%data)
if not all([x == ',' for x in tokens[1::2]]):
raise ValueError("Invalid SVG transform expression: %r"%data)
transforms = tokens[::2]
mat = (1,0,0,1,0,0)
for t in transforms:
name,rest = t.split("(")
if rest[-1] != ")":
raise ValueError("Invalid SVG transform expression: %r"%data)
args = map(float,rest[:-1].split(","))
if name == 'matrix':
mat = self.mmul(mat, args)
elif name == 'translate':
tx,ty = args
mat = self.mmul(mat, (1,0,0,1,tx,ty))
elif name == 'scale':
sx,sy = args
mat = self.mmul(mat, (sx,0,0,sy,0,0))
elif name == 'rotate':
a = args[0] / 180.0 * math.pi
if len(args) == 3:
cx,cy = args[1:]
mat = self.mmul(mat, (1,0,0,1,cx,cy))
cos = math.cos(a)
sin = math.sin(a)
mat = self.mmul(mat, (cos,sin,-sin,cos,0,0))
if len(args) == 3:
mat = self.mmul(mat, (1,0,0,1,-cx,-cy))
elif name == 'skewX':
a = args[0] / 180.0 * math.pi
mat = self.mmul(mat, (1,0,math.tan(a),1,0,0))
elif name == 'skewY':
a = args[0] / 180.0 * math.pi
mat = self.mmul(mat, (1,math.tan(a),0,1,0,0))
self.pushmatrix(mat)
def addPath(self, data):
p = SVGPath(data)
for path in p.subpaths:
path.transform(self.ts)
self.frame.add(path)
svg = sys.argv[1]
cfile = sys.argv[2]
if len(sys.argv) > 3:
font_name = sys.argv[3]
else:
font_name = "font"
handler = SVGReader()
parser = xml.sax.make_parser()
parser.setContentHandler(handler)
parser.setFeature(xml.sax.handler.feature_external_ges, False)
parser.parse(sys.argv[1])
frame = handler.frame
cdefs = []
max_h = 0
output = ""
output += "#include \"text.h\"\n"
overlap = 0
for id, x, y, w, h in handler.rects:
if id == "overlap":
overlap = w
continue
if id[:5] != "char_":
continue
cv = id[5:]
if len(cv) == 1:
chrval = ord(id[5:])
else:
chrval = int(cv,0)
if h > max_h:
max_h = h
#print >>sys.stderr, "Got rect %s"%id
char_objs = []
for obj in frame.objects:
sx,sy = obj.segments[0].start
if sx >= x and sy >= y and sx <= (x+w) and sy <= (y+h):
char_objs.append(obj)
#print >>sys.stderr, " - %d objects"%len(char_objs)
def transform(pt):
px,py = pt
px -= x
py -= y
return (px,py)
if len(char_objs) != 0:
output += "static const FontPoint fc_%02x[] = {\n"%chrval
for i,obj in enumerate(char_objs):
obj.transform(transform)
last_obj = i == len(char_objs)-1
output += "\t{0, %8.4f, %8.4f},\n"%obj.startpos()
for j,seg in enumerate(obj.segments):
last_seg = j == len(obj.segments)-1
if last_obj and last_seg:
ctl = 2
elif last_seg:
ctl = 1
else:
ctl = 0
if isinstance(seg, PathBezier4):
output += "\t{0, %8.4f, %8.4f},\n"%seg.cp1
output += "\t{0, %8.4f, %8.4f},\n"%seg.cp2
output += "\t{%d, %8.4f, %8.4f},\n"%(ctl, seg.end[0], seg.end[1])
elif isinstance(seg, PathLine):
output += "\t{0, %8.4f, %8.4f},\n"%seg.start
output += "\t{0, %8.4f, %8.4f},\n"%seg.end
output += "\t{%d, %8.4f, %8.4f},\n"%(ctl, seg.end[0], seg.end[1])
output += "};\n"
cdefs.append((chrval, w, "fc_%02x"%chrval))
else:
cdefs.append((chrval, w, "NULL"))
output += "\nstatic const FontChar font_chars[256] = {\n"
for chrval, width, sym in cdefs:
if chrval < 0x7f and chrval != 0x27:
cv = "'%s'"%chr(chrval)
else:
cv = "0x%02x"%chrval
output += "\t[%s] = {%8.4f, %s},\n"%(cv,width,sym)
output += "};\n\n"
output += "const Font %s = {%f, %f, font_chars};\n\n"%(font_name,max_h,overlap)
fd = open(cfile,"w")
fd.write(output)
fd.close()