1
# -*- encoding: utf-8 -*-
4
# Copyright (C) 2002-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5
# Copyright (C) 2003-2004,2006,2007 Michael Schindler <m-schindler@users.sourceforge.net>
6
# Copyright (C) 2002-2011 André Wobst <wobsta@users.sourceforge.net>
8
# This file is part of PyX (http://pyx.sourceforge.net/).
10
# PyX is free software; you can redistribute it and/or modify
11
# it under the terms of the GNU General Public License as published by
12
# the Free Software Foundation; either version 2 of the License, or
13
# (at your option) any later version.
15
# PyX is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
# GNU General Public License for more details.
20
# You should have received a copy of the GNU General Public License
21
# along with PyX; if not, write to the Free Software
22
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24
import cStringIO, math, re, string, struct, sys, warnings
25
from pyx import bbox, canvas, canvasitem, color, epsfile, filelocator, path, reader, trafo, unit
26
import texfont, tfmfile
29
_DVI_CHARMIN = 0 # typeset a character and move right (range min)
30
_DVI_CHARMAX = 127 # typeset a character and move right (range max)
31
_DVI_SET1234 = 128 # typeset a character and move right
32
_DVI_SETRULE = 132 # typeset a rule and move right
33
_DVI_PUT1234 = 133 # typeset a character
34
_DVI_PUTRULE = 137 # typeset a rule
35
_DVI_NOP = 138 # no operation
36
_DVI_BOP = 139 # beginning of page
37
_DVI_EOP = 140 # ending of page
38
_DVI_PUSH = 141 # save the current positions (h, v, w, x, y, z)
39
_DVI_POP = 142 # restore positions (h, v, w, x, y, z)
40
_DVI_RIGHT1234 = 143 # move right
41
_DVI_W0 = 147 # move right by w
42
_DVI_W1234 = 148 # move right and set w
43
_DVI_X0 = 152 # move right by x
44
_DVI_X1234 = 153 # move right and set x
45
_DVI_DOWN1234 = 157 # move down
46
_DVI_Y0 = 161 # move down by y
47
_DVI_Y1234 = 162 # move down and set y
48
_DVI_Z0 = 166 # move down by z
49
_DVI_Z1234 = 167 # move down and set z
50
_DVI_FNTNUMMIN = 171 # set current font (range min)
51
_DVI_FNTNUMMAX = 234 # set current font (range max)
52
_DVI_FNT1234 = 235 # set current font
53
_DVI_SPECIAL1234 = 239 # special (dvi extention)
54
_DVI_FNTDEF1234 = 243 # define the meaning of a font number
55
_DVI_PRE = 247 # preamble
56
_DVI_POST = 248 # postamble beginning
57
_DVI_POSTPOST = 249 # postamble ending
59
_DVI_VERSION = 2 # dvi version
61
# position variable indices
73
_READ_POST = 4 # XXX not used
74
_READ_POSTPOST = 5 # XXX not used
78
class DVIError(Exception): pass
80
# save and restore colors
82
class _savecolor(canvasitem.canvasitem):
83
def processPS(self, file, writer, context, registry, bbox):
84
file.write("currentcolor currentcolorspace\n")
86
def processPDF(self, file, writer, context, registry, bbox):
90
class _restorecolor(canvasitem.canvasitem):
91
def processPS(self, file, writer, context, registry, bbox):
92
file.write("setcolorspace setcolor\n")
94
def processPDF(self, file, writer, context, registry, bbox):
97
class _savetrafo(canvasitem.canvasitem):
98
def processPS(self, file, writer, context, registry, bbox):
99
file.write("matrix currentmatrix\n")
101
def processPDF(self, file, writer, context, registry, bbox):
105
class _restoretrafo(canvasitem.canvasitem):
106
def processPS(self, file, writer, context, registry, bbox):
107
file.write("setmatrix\n")
109
def processPDF(self, file, writer, context, registry, bbox):
115
def __init__(self, filename, debug=0, debugfile=sys.stdout):
116
""" opens the dvi file and reads the preamble """
117
self.filename = filename
119
self.debugfile = debugfile
123
self.activefont = None
125
# stack of fonts and fontscale currently used (used for VFs)
129
# pointer to currently active page
132
# stack for self.file, self.fonts and self.stack, needed for VF inclusion
135
self.file = reader.reader(self.filename)
137
# currently read byte in file (for debugging output)
144
def flushtext(self, fontmap):
145
""" finish currently active text object """
147
x, y, charcodes = self.activetext
148
x_pt, y_pt = x * self.pyxconv, -y*self.pyxconv
149
self.actpage.insert(self.activefont.text_pt(x_pt, y_pt, charcodes, fontmap=fontmap))
151
self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activetext[2]]))
152
self.activetext = None
154
def putrule(self, height, width, advancepos, fontmap):
155
self.flushtext(fontmap)
156
x1 = self.pos[_POS_H] * self.pyxconv
157
y1 = -self.pos[_POS_V] * self.pyxconv
158
w = width * self.pyxconv
159
h = height * self.pyxconv
161
if height > 0 and width > 0:
163
self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
164
(self.filepos, advancepos and "set" or "put", height, width))
165
self.actpage.fill(path.rect_pt(x1, y1, w, h))
168
self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
169
(self.filepos, advancepos and "set" or "put", height, width))
173
self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
174
(self.pos[_POS_H], width, self.pos[_POS_H]+width))
175
self.pos[_POS_H] += width * self.scale
177
def putchar(self, char, advancepos, id1234, fontmap):
178
dx = advancepos and self.activefont.getwidth_dvi(char) or 0
181
self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
183
advancepos and "set" or "put",
184
id1234 and "%i " % id1234 or "char",
186
self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
188
if isinstance(self.activefont, texfont.virtualfont):
189
# virtual font handling
190
afterpos = list(self.pos)
191
afterpos[_POS_H] += dx
192
self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
193
self.activefont.getsize_pt(), fontmap)
195
if self.activetext is None:
196
self.activetext = (self.pos[_POS_H], self.pos[_POS_V], [])
197
self.activetext[2].append(char)
198
self.pos[_POS_H] += dx
201
self.flushtext(fontmap)
203
def usefont(self, fontnum, id1234, fontmap):
204
self.flushtext(fontmap)
205
self.activefont = self.fonts[fontnum]
207
self.debugfile.write("%d: fnt%s%i current font is %s\n" %
209
id1234 and "%i " % id1234 or "num",
211
self.fonts[fontnum].name))
214
def definefont(self, cmdnr, num, c, q, d, fontname):
215
# cmdnr: type of fontdef command (only used for debugging output)
217
# q: scaling factor (fix_word)
218
# Note that q is actually s in large parts of the documentation.
219
# d: design size (fix_word)
221
# check whether it's a virtual font by trying to open it. if this fails, it is an ordinary TeX font
223
fontfile = filelocator.open(fontname, [filelocator.format.vf])
225
afont = texfont.TeXfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.debug>1)
227
afont = texfont.virtualfont(fontname, fontfile, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.debug>1)
229
self.fonts[num] = afont
232
self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
234
# scale = round((1000.0*self.conv*q)/(self.trueconv*d))
236
# scalestring = scale!=1000 and " scaled %d" % scale or ""
237
# print ("Font %i: %s%s---loaded at size %d DVI units" %
238
# (num, fontname, scalestring, q))
240
# print " (this font is magnified %d%%)" % round(scale/10)
242
def special(self, s, fontmap):
243
x = self.pos[_POS_H] * self.pyxconv
244
y = -self.pos[_POS_V] * self.pyxconv
246
self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
247
if not s.startswith("PyX:"):
248
warnings.warn("ignoring special '%s'" % s)
251
# it is in general not safe to continue using the currently active font because
252
# the specials may involve some gsave/grestore operations
253
self.flushtext(fontmap)
255
command, args = s[4:].split()[0], s[4:].split()[1:]
256
if command == "color_begin":
257
if args[0] == "cmyk":
258
c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
259
elif args[0] == "gray":
260
c = color.gray(float(args[1]))
261
elif args[0] == "hsb":
262
c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
263
elif args[0] == "rgb":
264
c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
265
elif args[0] == "RGB":
266
c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
267
elif args[0] == "texnamed":
269
c = getattr(color.cmyk, args[1])
270
except AttributeError:
271
raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
272
elif args[0] == "pyxcolor":
273
# pyx.color.cmyk.PineGreen or
274
# pyx.color.cmyk(0,0,0,0.0)
275
pat = re.compile(r"(pyx\.)?(color\.)?(?P<model>(cmyk)|(rgb)|(grey)|(gray)|(hsb))[\.]?(?P<arg>.*)")
276
sd = pat.match(" ".join(args[1:]))
279
if sd["arg"][0] == "(":
280
numpat = re.compile(r"[+-]?((\d+\.\d*)|(\d*\.\d+)|(\d+))([eE][+-]\d+)?")
281
arg = tuple([float(x[0]) for x in numpat.findall(sd["arg"])])
283
c = getattr(color, sd["model"])(*arg)
284
except TypeError or AttributeError:
285
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
288
c = getattr(getattr(color, sd["model"]), sd["arg"])
289
except AttributeError:
290
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
292
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
294
raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
295
self.actpage.insert(_savecolor())
296
self.actpage.insert(c)
297
elif command == "color_end":
298
self.actpage.insert(_restorecolor())
299
elif command == "rotate_begin":
300
self.actpage.insert(_savetrafo())
301
self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
302
elif command == "rotate_end":
303
self.actpage.insert(_restoretrafo())
304
elif command == "scale_begin":
305
self.actpage.insert(_savetrafo())
306
self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
307
elif command == "scale_end":
308
self.actpage.insert(_restoretrafo())
309
elif command == "epsinclude":
313
name, value = arg.split("=")
314
argdict[name] = value
316
# construct kwargs for epsfile constructor
318
epskwargs["filename"] = argdict["file"]
319
epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
320
float(argdict["urx"]), float(argdict["ury"]))
321
if argdict.has_key("width"):
322
epskwargs["width"] = float(argdict["width"]) * unit.t_pt
323
if argdict.has_key("height"):
324
epskwargs["height"] = float(argdict["height"]) * unit.t_pt
325
if argdict.has_key("clip"):
326
epskwargs["clip"] = int(argdict["clip"])
327
self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
328
elif command == "marker":
330
raise RuntimeError("marker contains spaces")
332
if c not in string.digits + string.letters + "@":
333
raise RuntimeError("marker contains invalid characters")
334
if self.actpage.markers.has_key(args[0]):
335
raise RuntimeError("marker name occurred several times")
336
self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
338
raise RuntimeError("unknown PyX special '%s', aborting" % command)
340
# routines for pushing and popping different dvi chunks on the reader
342
def _push_dvistring(self, dvi, fonts, afterpos, fontsize, fontmap):
343
""" push dvi string with defined fonts on top of reader
344
stack. Every positions gets scaled relatively by the factor
345
scale. After interpretating the dvi chunk, continue with self.pos=afterpos.
346
The designsize of the virtual font is passed as a fix_word
351
# self.debugfile.write("executing new dvi chunk\n")
352
self.debugstack.append(self.debug)
355
self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.scale))
357
# units in vf files are relative to the size of the font and given as fix_words
358
# which can be converted to floats by diving by 2**20.
359
# This yields the following scale factor for the height and width of rects:
360
self.scale = fontsize/2**20/self.pyxconv
362
self.file = reader.stringreader(dvi)
367
self.usefont(0, 0, fontmap)
369
def _pop_dvistring(self, fontmap):
370
self.flushtext(fontmap)
372
# self.debugfile.write("finished executing dvi chunk\n")
373
self.debug = self.debugstack.pop()
376
self.file, self.fonts, self.activefont, self.pos, self.stack, self.scale = self.statestack.pop()
378
# routines corresponding to the different reader states of the dvi maschine
383
self.filepos = afile.tell()
384
cmd = afile.readuchar()
387
elif cmd == _DVI_PRE:
388
if afile.readuchar() != _DVI_VERSION: raise DVIError
389
num = afile.readuint32()
390
den = afile.readuint32()
391
self.mag = afile.readuint32()
393
# For the interpretation of the lengths in dvi and tfm files,
394
# three conversion factors are relevant:
395
# - self.tfmconv: tfm units -> dvi units
396
# - self.pyxconv: dvi units -> (PostScript) points
397
# - self.conv: dvi units -> pixels
398
self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
400
# calculate conv as described in the DVIType docu using
401
# a given resolution in dpi
402
self.resolution = 300.0
403
self.conv = (num/254000.0)*(self.resolution/den)
405
# self.pyxconv is the conversion factor from the dvi units
406
# to (PostScript) points. It consists of
407
# - self.mag/1000.0: magstep scaling
408
# - self.conv: conversion from dvi units to pixels
409
# - 1/self.resolution: conversion from pixels to inch
410
# - 72 : conversion from inch to points
411
self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
413
# scaling used for rules when VF chunks are interpreted
416
comment = afile.read(afile.readuchar())
421
def readpage(self, pageid=None, fontmap=None):
422
""" reads a page from the dvi file
424
This routine reads a page from the dvi file which is
425
returned as a canvas. When there is no page left in the
426
dvifile, None is returned and the file is closed properly."""
429
self.filepos = self.file.tell()
430
cmd = self.file.readuchar()
433
elif cmd == _DVI_BOP:
434
ispageid = [self.file.readuint32() for i in range(10)]
435
if pageid is not None and ispageid != pageid:
436
raise DVIError("invalid pageid")
438
self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
439
self.file.readuint32()
441
elif cmd == _DVI_POST:
443
return None # nothing left
447
self.actpage = canvas.canvas()
448
self.actpage.markers = {}
449
self.pos = [0, 0, 0, 0, 0, 0]
451
# tuple (hpos, vpos, codepoints) to be output, or None if no output is pending
452
self.activetext = None
456
self.filepos = afile.tell()
458
cmd = afile.readuchar()
460
# we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
461
# so we have to continue with the rest of the dvi file
462
self._pop_dvistring(fontmap)
466
if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
467
self.putchar(cmd, True, 0, fontmap)
468
elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
469
self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), True, cmd-_DVI_SET1234+1, fontmap)
470
elif cmd == _DVI_SETRULE:
471
self.putrule(afile.readint32()*self.scale, afile.readint32()*self.scale, True, fontmap)
472
elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
473
self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), False, cmd-_DVI_SET1234+1, fontmap)
474
elif cmd == _DVI_PUTRULE:
475
self.putrule(afile.readint32()*self.scale, afile.readint32()*self.scale, False, fontmap)
476
elif cmd == _DVI_EOP:
477
self.flushtext(fontmap)
479
self.debugfile.write("%d: eop\n \n" % self.filepos)
481
elif cmd == _DVI_PUSH:
482
self.stack.append(list(self.pos))
484
self.debugfile.write("%s: push\n"
485
"level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
486
((self.filepos, len(self.stack)-1) + tuple(self.pos)))
487
elif cmd == _DVI_POP:
488
self.flushtext(fontmap)
489
self.pos = self.stack.pop()
491
self.debugfile.write("%s: pop\n"
492
"level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
493
((self.filepos, len(self.stack)) + tuple(self.pos)))
494
elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
495
self.flushtext(fontmap)
496
dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1) * self.scale
498
self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
500
cmd - _DVI_RIGHT1234 + 1,
504
self.pos[_POS_H]+dh))
505
self.pos[_POS_H] += dh
507
self.flushtext(fontmap)
509
self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
514
self.pos[_POS_H]+self.pos[_POS_W]))
515
self.pos[_POS_H] += self.pos[_POS_W]
516
elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
517
self.flushtext(fontmap)
518
self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1) * self.scale
520
self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
522
cmd - _DVI_W1234 + 1,
526
self.pos[_POS_H]+self.pos[_POS_W]))
527
self.pos[_POS_H] += self.pos[_POS_W]
529
self.flushtext(fontmap)
531
self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
536
self.pos[_POS_H]+self.pos[_POS_X]))
537
self.pos[_POS_H] += self.pos[_POS_X]
538
elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
539
self.flushtext(fontmap)
540
self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1) * self.scale
542
self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
544
cmd - _DVI_X1234 + 1,
548
self.pos[_POS_H]+self.pos[_POS_X]))
549
self.pos[_POS_H] += self.pos[_POS_X]
550
elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
551
self.flushtext(fontmap)
552
dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1) * self.scale
554
self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
556
cmd - _DVI_DOWN1234 + 1,
560
self.pos[_POS_V]+dv))
561
self.pos[_POS_V] += dv
563
self.flushtext(fontmap)
565
self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
570
self.pos[_POS_V]+self.pos[_POS_Y]))
571
self.pos[_POS_V] += self.pos[_POS_Y]
572
elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
573
self.flushtext(fontmap)
574
self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1) * self.scale
576
self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
578
cmd - _DVI_Y1234 + 1,
582
self.pos[_POS_V]+self.pos[_POS_Y]))
583
self.pos[_POS_V] += self.pos[_POS_Y]
585
self.flushtext(fontmap)
587
self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
592
self.pos[_POS_V]+self.pos[_POS_Z]))
593
self.pos[_POS_V] += self.pos[_POS_Z]
594
elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
595
self.flushtext(fontmap)
596
self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1) * self.scale
598
self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
600
cmd - _DVI_Z1234 + 1,
604
self.pos[_POS_V]+self.pos[_POS_Z]))
605
self.pos[_POS_V] += self.pos[_POS_Z]
606
elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
607
self.usefont(cmd - _DVI_FNTNUMMIN, 0, fontmap)
608
elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
609
# note that according to the DVI docs, for four byte font numbers,
610
# the font number is signed. Don't ask why!
611
fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
612
self.usefont(fntnum, cmd-_DVI_FNT1234+1, fontmap)
613
elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
614
self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)), fontmap)
615
elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
616
if cmd == _DVI_FNTDEF1234:
617
num = afile.readuchar()
618
elif cmd == _DVI_FNTDEF1234+1:
619
num = afile.readuint16()
620
elif cmd == _DVI_FNTDEF1234+2:
621
num = afile.readuint24()
622
elif cmd == _DVI_FNTDEF1234+3:
623
# Cool, here we have according to docu a signed int. Why?
624
num = afile.readint32()
625
self.definefont(cmd-_DVI_FNTDEF1234+1,
630
afile.read(afile.readuchar()+afile.readuchar()))