# -*- coding: ISO-8859-1 -*-
#
#
# Copyright (C) 2002-2006 Jrg Lehmann <joergl@users.sourceforge.net>
# Copyright (C) 2003-2004,2006,2007 Michael Schindler <m-schindler@users.sourceforge.net>
# Copyright (C) 2002-2006 Andr Wobst <wobsta@users.sourceforge.net>
#
# This file is part of PyX (http://pyx.sourceforge.net/).
#
# PyX is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PyX 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PyX; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
import cStringIO, exceptions, re, struct, string, sys, warnings, math
import unit, epsfile, bbox, canvas, color, trafo, path, pykpathsea, type1font
class binfile:
def __init__(self, filename, mode="r"):
self.file = open(filename, mode)
def close(self):
self.file.close()
def tell(self):
return self.file.tell()
def eof(self):
return self.file.eof()
def read(self, bytes):
return self.file.read(bytes)
def readint(self, bytes=4, signed=0):
first = 1
result = 0
while bytes:
value = ord(self.file.read(1))
if first and signed and value > 127:
value -= 256
first = 0
result = 256 * result + value
bytes -= 1
return result
def readint32(self):
return struct.unpack(">l", self.file.read(4))[0]
def readuint32(self):
return struct.unpack(">L", self.file.read(4))[0]
def readint24(self):
# XXX: checkme
return struct.unpack(">l", "\0"+self.file.read(3))[0]
def readuint24(self):
# XXX: checkme
return struct.unpack(">L", "\0"+self.file.read(3))[0]
def readint16(self):
return struct.unpack(">h", self.file.read(2))[0]
def readuint16(self):
return struct.unpack(">H", self.file.read(2))[0]
def readchar(self):
return struct.unpack("b", self.file.read(1))[0]
def readuchar(self):
return struct.unpack("B", self.file.read(1))[0]
def readstring(self, bytes):
l = self.readuchar()
assert l <= bytes-1, "inconsistency in file: string too long"
return self.file.read(bytes-1)[:l]
class stringbinfile(binfile):
def __init__(self, s):
self.file = cStringIO.StringIO(s)
##############################################################################
# TFM file handling
##############################################################################
class TFMError(exceptions.Exception): pass
class char_info_word:
def __init__(self, word):
self.width_index = int((word & 0xFF000000L) >> 24) #make sign-safe
self.height_index = (word & 0x00F00000) >> 20
self.depth_index = (word & 0x000F0000) >> 16
self.italic_index = (word & 0x0000FC00) >> 10
self.tag = (word & 0x00000300) >> 8
self.remainder = (word & 0x000000FF)
class tfmfile:
def __init__(self, name, debug=0):
self.file = binfile(name, "rb")
#
# read pre header
#
self.lf = self.file.readint16()
self.lh = self.file.readint16()
self.bc = self.file.readint16()
self.ec = self.file.readint16()
self.nw = self.file.readint16()
self.nh = self.file.readint16()
self.nd = self.file.readint16()
self.ni = self.file.readint16()
self.nl = self.file.readint16()
self.nk = self.file.readint16()
self.ne = self.file.readint16()
self.np = self.file.readint16()
if not (self.bc-1 <= self.ec <= 255 and
self.ne <= 256 and
self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
+self.ni+self.nl+self.nk+self.ne+self.np):
raise TFMError, "error in TFM pre-header"
if debug:
print "lh=%d" % self.lh
#
# read header
#
self.checksum = self.file.readint32()
self.designsize = self.file.readint32()
assert self.designsize > 0, "invald design size"
if self.lh > 2:
assert self.lh > 11, "inconsistency in TFM file: incomplete field"
self.charcoding = self.file.readstring(40)
else:
self.charcoding = None
if self.lh > 12:
assert self.lh > 16, "inconsistency in TFM file: incomplete field"
self.fontfamily = self.file.readstring(20)
else:
self.fontfamily = None
if debug:
print "(FAMILY %s)" % self.fontfamily
print "(CODINGSCHEME %s)" % self.charcoding
print "(DESINGSIZE R %f)" % 16.0*self.designsize/16777216L
if self.lh > 17:
self.sevenbitsave = self.file.readuchar()
# ignore the following two bytes
self.file.readint16()
facechar = self.file.readuchar()
# decode ugly face specification into the Knuth suggested string
if facechar < 18:
if facechar >= 12:
self.face = "E"
facechar -= 12
elif facechar >= 6:
self.face = "C"
facechar -= 6
else:
self.face = "R"
if facechar >= 4:
self.face = "L" + self.face
facechar -= 4
elif facechar >= 2:
self.face = "B" + self.face
facechar -= 2
else:
self.face = "M" + self.face
if facechar == 1:
self.face = self.face[0] + "I" + self.face[1]
else:
self.face = self.face[0] + "R" + self.face[1]
else:
self.face = None
else:
self.sevenbitsave = self.face = None
if self.lh > 18:
# just ignore the rest
print self.file.read((self.lh-18)*4)
#
# read char_info
#
self.char_info = [None]*(self.ec+1)
for charcode in range(self.bc, self.ec+1):
self.char_info[charcode] = char_info_word(self.file.readint32())
if self.char_info[charcode].width_index == 0:
# disable character if width_index is zero
self.char_info[charcode] = None
#
# read widths
#
self.width = [None for width_index in range(self.nw)]
for width_index in range(self.nw):
self.width[width_index] = self.file.readint32()
#
# read heights
#
self.height = [None for height_index in range(self.nh)]
for height_index in range(self.nh):
self.height[height_index] = self.file.readint32()
#
# read depths
#
self.depth = [None for depth_index in range(self.nd)]
for depth_index in range(self.nd):
self.depth[depth_index] = self.file.readint32()
#
# read italic
#
self.italic = [None for italic_index in range(self.ni)]
for italic_index in range(self.ni):
self.italic[italic_index] = self.file.readint32()
#
# read lig_kern
#
# XXX decode to lig_kern_command
self.lig_kern = [None for lig_kern_index in range(self.nl)]
for lig_kern_index in range(self.nl):
self.lig_kern[lig_kern_index] = self.file.readint32()
#
# read kern
#
self.kern = [None for kern_index in range(self.nk)]
for kern_index in range(self.nk):
self.kern[kern_index] = self.file.readint32()
#
# read exten
#
# XXX decode to extensible_recipe
self.exten = [None for exten_index in range(self.ne)]
for exten_index in range(self.ne):
self.exten[exten_index] = self.file.readint32()
#
# read param
#
# XXX decode
self.param = [None for param_index in range(self.np)]
for param_index in range(self.np):
self.param[param_index] = self.file.readint32()
self.file.close()
##############################################################################
# Font handling
##############################################################################
#
# PostScript font selection and output primitives
#
class UnsupportedFontFormat(Exception):
pass
class UnsupportedPSFragment(Exception):
pass
class fontmapping:
tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
def __init__(self, s):
""" construct font mapping from line s of font mapping file """
self.texname = self.basepsname = self.fontfile = None
# standard encoding
self.encodingfile = None
# supported postscript fragments occuring in psfonts.map
self.reencodefont = self.extendfont = self.slantfont = None
tokens = []
while len(s):
match = self.tokenpattern.match(s)
if match:
if match.groups()[0] is not None:
tokens.append('"%s"' % match.groups()[0])
else:
tokens.append(match.groups()[2])
s = s[match.end():]
else:
raise RuntimeError("Cannot tokenize string '%s'" % s)
for token in tokens:
if token.startswith("<"):
if token.startswith("<<"):
# XXX: support non-partial download here
self.fontfile = token[2:]
elif token.startswith("<["):
self.encodingfile = token[2:]
elif token.endswith(".pfa") or token.endswith(".pfb"):
self.fontfile = token[1:]
elif token.endswith(".enc"):
self.encodingfile = token[1:]
elif token.endswith(".ttf"):
raise UnsupportedFontFormat("TrueType font")
else:
raise RuntimeError("Unknown token '%s'" % token)
elif token.startswith('"'):
pscode = token[1:-1].split()
# parse standard postscript code fragments
while pscode:
try:
arg, cmd = pscode[:2]
except:
raise UnsupportedPSFragment("Unsupported Postscript fragment '%s'" % pscode)
pscode = pscode[2:]
if cmd == "ReEncodeFont":
self.reencodefont = arg
elif cmd == "ExtendFont":
self.extendfont = arg
elif cmd == "SlantFont":
self.slantfont = arg
else:
raise UnsupportedPSFragment("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
else:
if self.texname is None:
self.texname = token
else:
self.basepsname = token
if self.basepsname is None:
self.basepsname = self.texname
def __str__(self):
return ("'%s' is '%s' read from '%s' encoded as '%s'" %
(self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
# generate fontmap
def readfontmap(filenames):
""" read font map from filename (without path) """
fontmap = {}
for filename in filenames:
mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
# try also the oft-used registration as dvips config file
if not mappath:
mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
if not mappath:
raise RuntimeError("cannot find font mapping file '%s'" % filename)
mapfile = open(mappath, "rU")
lineno = 0
for line in mapfile.readlines():
lineno += 1
line = line.rstrip()
if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
try:
fm = fontmapping(line)
except (RuntimeError, UnsupportedPSFragment), e:
warnings.warn("Ignoring line %i in mapping file '%s': %s" % (lineno, mappath, e))
except UnsupportedFontFormat, e:
pass
else:
fontmap[fm.texname] = fm
mapfile.close()
return fontmap
class font:
def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
self.name = name
self.q = q # desired size of font (fix_word) in TeX points
self.d = d # design size of font (fix_word) in TeX points
self.tfmconv = tfmconv # conversion factor from tfm units to dvi units
self.pyxconv = pyxconv # conversion factor from dvi units to PostScript points
self.fontmap = fontmap
tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
if not tfmpath:
raise TFMError("cannot find %s.tfm" % self.name)
self.tfmfile = tfmfile(tfmpath, debug)
# We only check for equality of font checksums if none of them
# is zero. The case c == 0 happend in some VF files and
# according to the VFtoVP documentation, paragraph 40, a check
# is only performed if tfmfile.checksum > 0. Anyhow, being
# more generous here seems to be reasonable
if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c != 0:
raise DVIError("check sums do not agree: %d vs. %d" %
(self.tfmfile.checksum, c))
# Check whether the given design size matches the one defined in the tfm file
if abs(self.tfmfile.designsize - d) > 2:
raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsize, d))
#if q < 0 or q > 134217728:
# raise DVIError("font '%s' not loaded: bad scale" % self.name)
if d < 0 or d > 134217728:
raise DVIError("font '%s' not loaded: bad design size" % self.name)
self.scale = 1.0*q/d
def fontinfo(self):
class fontinfo:
pass
# The following code is a very crude way to obtain the information
# required for the PDF font descritor. (TODO: The correct way would
# be to read the information from the AFM file.)
fontinfo = fontinfo()
try:
fontinfo.fontbbox = (0,
-self.getdepth_ds(ord("y")),
self.getwidth_ds(ord("W")),
self.getheight_ds(ord("H")))
except:
fontinfo.fontbbox = (0, -10, 100, 100)
try:
fontinfo.italicangle = -180/math.pi*math.atan(self.tfmfile.param[0]/65536.0)
except IndexError:
fontinfo.italicangle = 0
fontinfo.ascent = fontinfo.fontbbox[3]
fontinfo.descent = fontinfo.fontbbox[1]
try:
fontinfo.capheight = self.getheight_ds(ord("h"))
except:
fontinfo.capheight = 100
try:
fontinfo.vstem = self.getwidth_ds(ord("."))/3
except:
fontinfo.vstem = 5
return fontinfo
def __str__(self):
return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name,
16.0*self.d/16777216L,
16.0*self.q/16777216L)
__repr__ = __str__
def getsize_pt(self):
""" return size of font in (PS) points """
# The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
# to the corresponding float. Furthermore, we have to convert from TeX
# points to points, hence the factor 72/72.27.
return 16L*self.q/16777216L*72/72.27
def _convert_tfm_to_dvi(self, length):
# doing the integer math with long integers will lead to different roundings
# return 16*length*int(round(self.q*self.tfmconv))/16777216
# Knuth instead suggests the following algorithm based on 4 byte integer logic only
# z = int(round(self.q*self.tfmconv))
# b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
# assert b0 == 0 or b0 == 255
# shift = 4
# while z >= 8388608:
# z >>= 1
# shift -= 1
# assert shift >= 0
# result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
# if b0 == 255:
# result = result - (z << (8-shift))
# however, we can simplify this using a single long integer multiplication,
# but take into account the transformation of z
z = int(round(self.q*self.tfmconv))
assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
assert z < 134217728 # 1 << 27
shift = 20 # 1 << 20
while z >= 8388608: # 1 << 23
z >>= 1
shift -= 1
# length*z is a long integer, but the result will be a regular integer
return int(length*long(z) >> shift)
def _convert_tfm_to_ds(self, length):
return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv * 1000 / self.getsize_pt()
def _convert_tfm_to_pt(self, length):
return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv
# routines returning lengths as integers in dvi units
def getwidth_dvi(self, charcode):
return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
def getheight_dvi(self, charcode):
return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
def getdepth_dvi(self, charcode):
return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
def getitalic_dvi(self, charcode):
return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
# routines returning lengths as integers in design size (AFM) units
def getwidth_ds(self, charcode):
return self._convert_tfm_to_ds(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
def getheight_ds(self, charcode):
return self._convert_tfm_to_ds(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
def getdepth_ds(self, charcode):
return self._convert_tfm_to_ds(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
def getitalic_ds(self, charcode):
return self._convert_tfm_to_ds(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
# routines returning lengths as floats in PostScript points
def getwidth_pt(self, charcode):
return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
def getheight_pt(self, charcode):
return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
def getdepth_pt(self, charcode):
return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
def getitalic_pt(self, charcode):
return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
class virtualfont(font):
def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
if fontpath is None or not len(fontpath):
raise RuntimeError
font.__init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug)
self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
def getfonts(self):
""" return fonts used in virtual font itself """
return self.vffile.getfonts()
def getchar(self, cc):
""" return dvi chunk corresponding to char code cc """
return self.vffile.getchar(cc)
##############################################################################
# DVI file handling
##############################################################################
_DVI_CHARMIN = 0 # typeset a character and move right (range min)
_DVI_CHARMAX = 127 # typeset a character and move right (range max)
_DVI_SET1234 = 128 # typeset a character and move right
_DVI_SETRULE = 132 # typeset a rule and move right
_DVI_PUT1234 = 133 # typeset a character
_DVI_PUTRULE = 137 # typeset a rule
_DVI_NOP = 138 # no operation
_DVI_BOP = 139 # beginning of page
_DVI_EOP = 140 # ending of page
_DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
_DVI_POP = 142 # restore positions (h, v, w, x, y, z)
_DVI_RIGHT1234 = 143 # move right
_DVI_W0 = 147 # move right by w
_DVI_W1234 = 148 # move right and set w
_DVI_X0 = 152 # move right by x
_DVI_X1234 = 153 # move right and set x
_DVI_DOWN1234 = 157 # move down
_DVI_Y0 = 161 # move down by y
_DVI_Y1234 = 162 # move down and set y
_DVI_Z0 = 166 # move down by z
_DVI_Z1234 = 167 # move down and set z
_DVI_FNTNUMMIN = 171 # set current font (range min)
_DVI_FNTNUMMAX = 234 # set current font (range max)
_DVI_FNT1234 = 235 # set current font
_DVI_SPECIAL1234 = 239 # special (dvi extention)
_DVI_FNTDEF1234 = 243 # define the meaning of a font number
_DVI_PRE = 247 # preamble
_DVI_POST = 248 # postamble beginning
_DVI_POSTPOST = 249 # postamble ending
_DVI_VERSION = 2 # dvi version
# position variable indices
_POS_H = 0
_POS_V = 1
_POS_W = 2
_POS_X = 3
_POS_Y = 4
_POS_Z = 5
# reader states
_READ_PRE = 1
_READ_NOPAGE = 2
_READ_PAGE = 3
_READ_POST = 4 # XXX not used
_READ_POSTPOST = 5 # XXX not used
_READ_DONE = 6
class DVIError(exceptions.Exception): pass
# save and restore colors
class _savecolor(canvas.canvasitem):
def processPS(self, file, writer, context, registry, bbox):
file.write("currentcolor currentcolorspace\n")
def processPDF(self, file, writer, context, registry, bbox):
file.write("q\n")
class _restorecolor(canvas.canvasitem):
def processPS(self, file, writer, context, registry, bbox):
file.write("setcolorspace setcolor\n")
def processPDF(self, file, writer, context, registry, bbox):
file.write("Q\n")
class _savetrafo(canvas.canvasitem):
def processPS(self, file, writer, context, registry, bbox):
file.write("matrix currentmatrix\n")
def processPDF(self, file, writer, context, registry, bbox):
file.write("q\n")
class _restoretrafo(canvas.canvasitem):
def processPS(self, file, writer, context, registry, bbox):
file.write("setmatrix\n")
def processPDF(self, file, writer, context, registry, bbox):
file.write("Q\n")
class dvifile:
def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
""" opens the dvi file and reads the preamble """
self.filename = filename
self.fontmap = fontmap
self.debug = debug
self.debugfile = debugfile
self.debugstack = []
self.fonts = {}
self.activefont = None
# stack of fonts and fontscale currently used (used for VFs)
self.fontstack = []
self.stack = []
# pointer to currently active page
self.actpage = None
# stack for self.file, self.fonts and self.stack, needed for VF inclusion
self.statestack = []
self.file = binfile(self.filename, "rb")
# currently read byte in file (for debugging output)
self.filepos = None
self._read_pre()
# helper routines
def flushtext(self):
""" finish currently active text object """
if self.debug and self.activetext:
self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activetext.chars]))
self.activetext = None
def putrule(self, height, width, advancepos=1):
self.flushtext()
x1 = self.pos[_POS_H] * self.pyxconv
y1 = -self.pos[_POS_V] * self.pyxconv
w = width * self.pyxconv
h = height * self.pyxconv
if height > 0 and width > 0:
if self.debug:
self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
(self.filepos, advancepos and "set" or "put", height, width))
self.actpage.fill(path.rect_pt(x1, y1, w, h))
else:
if self.debug:
self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
(self.filepos, advancepos and "set" or "put", height, width))
if advancepos:
if self.debug:
self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
(self.pos[_POS_H], width, self.pos[_POS_H]+width))
self.pos[_POS_H] += width
def putchar(self, char, advancepos=1, id1234=0):
dx = advancepos and self.activefont.getwidth_dvi(char) or 0
if self.debug:
self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
(self.filepos,
advancepos and "set" or "put",
id1234 and "%i " % id1234 or "char",
char,
self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
if isinstance(self.activefont, virtualfont):
# virtual font handling
afterpos = list(self.pos)
afterpos[_POS_H] += dx
self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
self.activefont.getsize_pt())
else:
if self.activetext is None:
if not self.fontmap.has_key(self.activefont.name):
raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self.activefont.name)
fontmapinfo = self.fontmap[self.activefont.name]
encodingname = fontmapinfo.reencodefont
if encodingname is not None:
encodingfilename = pykpathsea.find_file(fontmapinfo.encodingfile, pykpathsea.kpse_tex_ps_header_format)
if not encodingfilename:
raise RuntimeError("cannot find font encoding file %s" % fontmapinfo.encodingfile)
fontencoding = type1font.encoding(encodingname, encodingfilename)
else:
fontencoding = None
fontbasefontname = fontmapinfo.basepsname
if fontmapinfo.fontfile is not None:
fontfilename = pykpathsea.find_file(fontmapinfo.fontfile, pykpathsea.kpse_type1_format)
if not fontfilename:
raise RuntimeError("cannot find type 1 font %s" % fontmapinfo.fontfile)
else:
fontfilename = None
fontslant = fontmapinfo.slantfont
if fontslant is not None:
fontslant = float(fontslant)
# XXX we currently misuse use self.activefont as metric
font = type1font.font(fontbasefontname, fontfilename, fontencoding, fontslant, self.activefont)
self.activetext = type1font.text_pt(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv, font)
self.actpage.insert(self.activetext)
self.activetext.addchar(char)
self.pos[_POS_H] += dx
if not advancepos:
self.flushtext()
def usefont(self, fontnum, id1234=0):
self.flushtext()
self.activefont = self.fonts[fontnum]
if self.debug:
self.debugfile.write("%d: fnt%s%i current font is %s\n" %
(self.filepos,
id1234 and "%i " % id1234 or "num",
fontnum,
self.fonts[fontnum].name))
def definefont(self, cmdnr, num, c, q, d, fontname):
# cmdnr: type of fontdef command (only used for debugging output)
# c: checksum
# q: scaling factor (fix_word)
# Note that q is actually s in large parts of the documentation.
# d: design size (fix_word)
try:
afont = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
except (TypeError, RuntimeError):
afont = font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
self.fonts[num] = afont
if self.debug:
self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
# scale = round((1000.0*self.conv*q)/(self.trueconv*d))
# m = 1.0*q/d
# scalestring = scale!=1000 and " scaled %d" % scale or ""
# print ("Font %i: %s%s---loaded at size %d DVI units" %
# (num, fontname, scalestring, q))
# if scale!=1000:
# print " (this font is magnified %d%%)" % round(scale/10)
def special(self, s):
x = self.pos[_POS_H] * self.pyxconv
y = -self.pos[_POS_V] * self.pyxconv
if self.debug:
self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
if not s.startswith("PyX:"):
warnings.warn("ignoring special '%s'" % s)
return
# it is in general not safe to continue using the currently active font because
# the specials may involve some gsave/grestore operations
self.flushtext()
command, args = s[4:].split()[0], s[4:].split()[1:]
if command == "color_begin":
if args[0] == "cmyk":
c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
elif args[0] == "gray":
c = color.gray(float(args[1]))
elif args[0] == "hsb":
c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
elif args[0] == "rgb":
c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
elif args[0] == "RGB":
c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
elif args[0] == "texnamed":
try:
c = getattr(color.cmyk, args[1])
except AttributeError:
raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
elif args[0] == "pyxcolor":
# pyx.color.cmyk.PineGreen or
# pyx.color.cmyk(0,0,0,0.0)
pat = re.compile(r"(pyx\.)?(color\.)?(?P<model>(cmyk)|(rgb)|(grey)|(gray)|(hsb))[\.]?(?P<arg>.*)")
sd = pat.match(" ".join(args[1:]))
if sd:
sd = sd.groupdict()
if sd["arg"][0] == "(":
numpat = re.compile(r"[+-]?((\d+\.\d*)|(\d*\.\d+)|(\d+))([eE][+-]\d+)?")
arg = tuple([float(x[0]) for x in numpat.findall(sd["arg"])])
try:
c = getattr(color, sd["model"])(*arg)
except TypeError or AttributeError:
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
else:
try:
c = getattr(getattr(color, sd["model"]), sd["arg"])
except AttributeError:
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
else:
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
else:
raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
self.actpage.insert(_savecolor())
self.actpage.insert(c)
elif command == "color_end":
self.actpage.insert(_restorecolor())
elif command == "rotate_begin":
self.actpage.insert(_savetrafo())
self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
elif command == "rotate_end":
self.actpage.insert(_restoretrafo())
elif command == "scale_begin":
self.actpage.insert(_savetrafo())
self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
elif command == "scale_end":
self.actpage.insert(_restoretrafo())
elif command == "epsinclude":
# parse arguments
argdict = {}
for arg in args:
name, value = arg.split("=")
argdict[name] = value
# construct kwargs for epsfile constructor
epskwargs = {}
epskwargs["filename"] = argdict["file"]
epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
float(argdict["urx"]), float(argdict["ury"]))
if argdict.has_key("width"):
epskwargs["width"] = float(argdict["width"]) * unit.t_pt
if argdict.has_key("height"):
epskwargs["height"] = float(argdict["height"]) * unit.t_pt
if argdict.has_key("clip"):
epskwargs["clip"] = int(argdict["clip"])
self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
elif command == "marker":
if len(args) != 1:
raise RuntimeError("marker contains spaces")
for c in args[0]:
if c not in string.digits + string.letters + "@":
raise RuntimeError("marker contains invalid characters")
if self.actpage.markers.has_key(args[0]):
raise RuntimeError("marker name occurred several times")
self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
else:
raise RuntimeError("unknown PyX special '%s', aborting" % command)
# routines for pushing and popping different dvi chunks on the reader
def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
""" push dvi string with defined fonts on top of reader
stack. Every positions gets scaled relatively by the factor
scale. After the interpreting of the dvi chunk has been finished,
continue with self.pos=afterpos. The designsize of the virtual
font is passed as a fix_word
"""
#if self.debug:
# self.debugfile.write("executing new dvi chunk\n")
self.debugstack.append(self.debug)
self.debug = 0
self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
# units in vf files are relative to the size of the font and given as fix_words
# which can be converted to floats by diving by 2**20
oldpyxconv = self.pyxconv
self.pyxconv = fontsize/2**20
rescale = self.pyxconv/oldpyxconv
self.file = stringbinfile(dvi)
self.fonts = fonts
self.stack = []
self.filepos = 0
# rescale self.pos in order to be consistent with the new scaling
self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
# since tfmconv converts from tfm units to dvi units, rescale it as well
self.tfmconv /= rescale
self.usefont(0)
def _pop_dvistring(self):
self.flushtext()
#if self.debug:
# self.debugfile.write("finished executing dvi chunk\n")
self.debug = self.debugstack.pop()
self.file.close()
self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
# routines corresponding to the different reader states of the dvi maschine
def _read_pre(self):
afile = self.file
while 1:
self.filepos = afile.tell()
cmd = afile.readuchar()
if cmd == _DVI_NOP:
pass
elif cmd == _DVI_PRE:
if afile.readuchar() != _DVI_VERSION: raise DVIError
num = afile.readuint32()
den = afile.readuint32()
self.mag = afile.readuint32()
# For the interpretation of the lengths in dvi and tfm files,
# three conversion factors are relevant:
# - self.tfmconv: tfm units -> dvi units
# - self.pyxconv: dvi units -> (PostScript) points
# - self.conv: dvi units -> pixels
self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
# calculate conv as described in the DVIType docu using
# a given resolution in dpi
self.resolution = 300.0
self.conv = (num/254000.0)*(self.resolution/den)
# self.pyxconv is the conversion factor from the dvi units
# to (PostScript) points. It consists of
# - self.mag/1000.0: magstep scaling
# - self.conv: conversion from dvi units to pixels
# - 1/self.resolution: conversion from pixels to inch
# - 72 : conversion from inch to points
self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
comment = afile.read(afile.readuchar())
return
else:
raise DVIError
def readpage(self, pageid=None):
""" reads a page from the dvi file
This routine reads a page from the dvi file which is
returned as a canvas. When there is no page left in the
dvifile, None is returned and the file is closed properly."""
while 1:
self.filepos = self.file.tell()
cmd = self.file.readuchar()
if cmd == _DVI_NOP:
pass
elif cmd == _DVI_BOP:
ispageid = [self.file.readuint32() for i in range(10)]
if pageid is not None and ispageid != pageid:
raise DVIError("invalid pageid")
if self.debug:
self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
self.file.readuint32()
break
elif cmd == _DVI_POST:
self.file.close()
return None # nothing left
else:
raise DVIError
self.actpage = canvas.canvas()
self.actpage.markers = {}
self.pos = [0, 0, 0, 0, 0, 0]
# currently active output: text instance currently used
self.activetext = None
while 1:
afile = self.file
self.filepos = afile.tell()
try:
cmd = afile.readuchar()
except struct.error:
# we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
# so we have to continue with the rest of the dvi file
self._pop_dvistring()
continue
if cmd == _DVI_NOP:
pass
if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
self.putchar(cmd)
elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
elif cmd == _DVI_SETRULE:
self.putrule(afile.readint32(), afile.readint32())
elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
elif cmd == _DVI_PUTRULE:
self.putrule(afile.readint32(), afile.readint32(), 0)
elif cmd == _DVI_EOP:
self.flushtext()
if self.debug:
self.debugfile.write("%d: eop\n \n" % self.filepos)
return self.actpage
elif cmd == _DVI_PUSH:
self.stack.append(list(self.pos))
if self.debug:
self.debugfile.write("%s: push\n"
"level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
((self.filepos, len(self.stack)-1) + tuple(self.pos)))
elif cmd == _DVI_POP:
self.flushtext()
self.pos = self.stack.pop()
if self.debug:
self.debugfile.write("%s: pop\n"
"level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
((self.filepos, len(self.stack)) + tuple(self.pos)))
elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
self.flushtext()
dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
if self.debug:
self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
(self.filepos,
cmd - _DVI_RIGHT1234 + 1,
dh,
self.pos[_POS_H],
dh,
self.pos[_POS_H]+dh))
self.pos[_POS_H] += dh
elif cmd == _DVI_W0:
self.flushtext()
if self.debug:
self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
(self.filepos,
self.pos[_POS_W],
self.pos[_POS_H],
self.pos[_POS_W],
self.pos[_POS_H]+self.pos[_POS_W]))
self.pos[_POS_H] += self.pos[_POS_W]
elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
self.flushtext()
self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
if self.debug:
self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
(self.filepos,
cmd - _DVI_W1234 + 1,
self.pos[_POS_W],
self.pos[_POS_H],
self.pos[_POS_W],
self.pos[_POS_H]+self.pos[_POS_W]))
self.pos[_POS_H] += self.pos[_POS_W]
elif cmd == _DVI_X0:
self.flushtext()
if self.debug:
self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
(self.filepos,
self.pos[_POS_X],
self.pos[_POS_H],
self.pos[_POS_X],
self.pos[_POS_H]+self.pos[_POS_X]))
self.pos[_POS_H] += self.pos[_POS_X]
elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
self.flushtext()
self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
if self.debug:
self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
(self.filepos,
cmd - _DVI_X1234 + 1,
self.pos[_POS_X],
self.pos[_POS_H],
self.pos[_POS_X],
self.pos[_POS_H]+self.pos[_POS_X]))
self.pos[_POS_H] += self.pos[_POS_X]
elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
self.flushtext()
dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
if self.debug:
self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
(self.filepos,
cmd - _DVI_DOWN1234 + 1,
dv,
self.pos[_POS_V],
dv,
self.pos[_POS_V]+dv))
self.pos[_POS_V] += dv
elif cmd == _DVI_Y0:
self.flushtext()
if self.debug:
self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
(self.filepos,
self.pos[_POS_Y],
self.pos[_POS_V],
self.pos[_POS_Y],
self.pos[_POS_V]+self.pos[_POS_Y]))
self.pos[_POS_V] += self.pos[_POS_Y]
elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
self.flushtext()
self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
if self.debug:
self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
(self.filepos,
cmd - _DVI_Y1234 + 1,
self.pos[_POS_Y],
self.pos[_POS_V],
self.pos[_POS_Y],
self.pos[_POS_V]+self.pos[_POS_Y]))
self.pos[_POS_V] += self.pos[_POS_Y]
elif cmd == _DVI_Z0:
self.flushtext()
if self.debug:
self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
(self.filepos,
self.pos[_POS_Z],
self.pos[_POS_V],
self.pos[_POS_Z],
self.pos[_POS_V]+self.pos[_POS_Z]))
self.pos[_POS_V] += self.pos[_POS_Z]
elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
self.flushtext()
self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
if self.debug:
self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
(self.filepos,
cmd - _DVI_Z1234 + 1,
self.pos[_POS_Z],
self.pos[_POS_V],
self.pos[_POS_Z],
self.pos[_POS_V]+self.pos[_POS_Z]))
self.pos[_POS_V] += self.pos[_POS_Z]
elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
self.usefont(cmd - _DVI_FNTNUMMIN, 0)
elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
# note that according to the DVI docs, for four byte font numbers,
# the font number is signed. Don't ask why!
fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
if cmd == _DVI_FNTDEF1234:
num = afile.readuchar()
elif cmd == _DVI_FNTDEF1234+1:
num = afile.readuint16()
elif cmd == _DVI_FNTDEF1234+2:
num = afile.readuint24()
elif cmd == _DVI_FNTDEF1234+3:
# Cool, here we have according to docu a signed int. Why?
num = afile.readint32()
self.definefont(cmd-_DVI_FNTDEF1234+1,
num,
afile.readint32(),
afile.readint32(),
afile.readint32(),
afile.read(afile.readuchar()+afile.readuchar()))
else:
raise DVIError
##############################################################################
# VF file handling
##############################################################################
_VF_LONG_CHAR = 242 # character packet (long version)
_VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
_VF_PRE = _DVI_PRE # preamble
_VF_POST = _DVI_POST # postamble
_VF_ID = 202 # VF id byte
class VFError(exceptions.Exception): pass
class vffile:
def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
self.filename = filename
self.scale = scale
self.tfmconv = tfmconv
self.pyxconv = pyxconv
self.fontmap = fontmap
self.debug = debug
self.fonts = {} # used fonts
self.widths = {} # widths of defined chars
self.chardefs = {} # dvi chunks for defined chars
afile = binfile(self.filename, "rb")
cmd = afile.readuchar()
if cmd == _VF_PRE:
if afile.readuchar() != _VF_ID: raise VFError
comment = afile.read(afile.readuchar())
self.cs = afile.readuint32()
self.ds = afile.readuint32()
else:
raise VFError
while 1:
cmd = afile.readuchar()
if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
# font definition
if cmd == _VF_FNTDEF1234:
num = afile.readuchar()
elif cmd == _VF_FNTDEF1234+1:
num = afile.readuint16()
elif cmd == _VF_FNTDEF1234+2:
num = afile.readuint24()
elif cmd == _VF_FNTDEF1234+3:
num = afile.readint32()
c = afile.readint32()
s = afile.readint32() # relative scaling used for font (fix_word)
d = afile.readint32() # design size of font
fontname = afile.read(afile.readuchar()+afile.readuchar())
# rescaled size of font: s is relative to the scaling
# of the virtual font itself. Note that realscale has
# to be a fix_word (like s)
# XXX: check rounding
reals = int(round(self.scale * (16*self.ds/16777216L) * s))
# print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
# (fontname, self.scale, self.ds, s, reals)
# )
# XXX allow for virtual fonts here too
self.fonts[num] = font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
elif cmd == _VF_LONG_CHAR:
# character packet (long form)
pl = afile.readuint32() # packet length
cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
tfm = afile.readuint24() # character width
dvi = afile.read(pl) # dvi code of character
self.widths[cc] = tfm
self.chardefs[cc] = dvi
elif cmd < _VF_LONG_CHAR:
# character packet (short form)
cc = afile.readuchar() # char code
tfm = afile.readuint24() # character width
dvi = afile.read(cmd)
self.widths[cc] = tfm
self.chardefs[cc] = dvi
elif cmd == _VF_POST:
break
else:
raise VFError
afile.close()
def getfonts(self):
return self.fonts
def getchar(self, cc):
return self.chardefs[cc]
|