1# -*- encoding: utf-8 -*-
2#
3#
4# Copyright (C) 2002-2011 Jörg Lehmann <joerg@pyx-project.org>
5# Copyright (C) 2003-2004,2006,2007 Michael Schindler <m-schindler@users.sourceforge.net>
6# Copyright (C) 2002-2011 André Wobst <wobsta@pyx-project.org>
7#
8# This file is part of PyX (https://pyx-project.org/).
9#
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.
14#
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.
19#
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
23
24import io, logging, math, re, string, struct, sys
25from pyx import  bbox, canvas, color, epsfile, config, path, reader, trafo, unit
26from . import texfont, tfmfile
27
28logger = logging.getLogger("pyx")
29
30
31_DVI_CHARMIN     =   0 # typeset a character and move right (range min)
32_DVI_CHARMAX     = 127 # typeset a character and move right (range max)
33_DVI_SET1234     = 128 # typeset a character and move right
34_DVI_SETRULE     = 132 # typeset a rule and move right
35_DVI_PUT1234     = 133 # typeset a character
36_DVI_PUTRULE     = 137 # typeset a rule
37_DVI_NOP         = 138 # no operation
38_DVI_BOP         = 139 # beginning of page
39_DVI_EOP         = 140 # ending of page
40_DVI_PUSH        = 141 # save the current positions (h, v, w, x, y, z)
41_DVI_POP         = 142 # restore positions (h, v, w, x, y, z)
42_DVI_RIGHT1234   = 143 # move right
43_DVI_W0          = 147 # move right by w
44_DVI_W1234       = 148 # move right and set w
45_DVI_X0          = 152 # move right by x
46_DVI_X1234       = 153 # move right and set x
47_DVI_DOWN1234    = 157 # move down
48_DVI_Y0          = 161 # move down by y
49_DVI_Y1234       = 162 # move down and set y
50_DVI_Z0          = 166 # move down by z
51_DVI_Z1234       = 167 # move down and set z
52_DVI_FNTNUMMIN   = 171 # set current font (range min)
53_DVI_FNTNUMMAX   = 234 # set current font (range max)
54_DVI_FNT1234     = 235 # set current font
55_DVI_SPECIAL1234 = 239 # special (dvi extention)
56_DVI_FNTDEF1234  = 243 # define the meaning of a font number
57_DVI_PRE         = 247 # preamble
58_DVI_POST        = 248 # postamble beginning
59_DVI_POSTPOST    = 249 # postamble ending
60
61_DVI_VERSION     = 2   # dvi version
62
63# position variable indices
64_POS_H           = 0
65_POS_V           = 1
66_POS_W           = 2
67_POS_X           = 3
68_POS_Y           = 4
69_POS_Z           = 5
70
71# reader states
72_READ_PRE       = 1
73_READ_NOPAGE    = 2
74_READ_PAGE      = 3
75_READ_POST      = 4 # XXX not used
76_READ_POSTPOST  = 5 # XXX not used
77_READ_DONE      = 6
78
79
80class DVIError(Exception): pass
81
82
83class DVIfile:
84
85    def __init__(self, filename, debug=0, debugfile=sys.stdout):
86        """ opens the dvi file and reads the preamble """
87        self.filename = filename
88        self.debug = debug
89        self.debugfile = debugfile
90        self.debugstack = []
91
92        self.fonts = {}
93        self.activefont = None
94
95        # stack of fonts and fontscale currently used (used for VFs)
96        self.fontstack = []
97        self.stack = []
98
99        # pointer to currently active page
100        self.actpage = None
101
102        # stack for self.file, self.fonts and self.stack, needed for VF inclusion
103        self.statestack = []
104
105        self.file = reader.reader(self.filename)
106
107        # currently read byte in file (for debugging output)
108        self.filepos = None
109
110        self._read_pre()
111
112    # helper routines
113
114    def beginsubpage(self, attrs):
115        c = canvas.canvas(attrs)
116        c.parent = self.actpage
117        c.markers = {}
118        self.actpage.insert(c)
119        self.actpage = c
120
121    def endsubpage(self):
122        for key, value in list(self.actpage.markers.items()):
123            self.actpage.parent.markers[key] = self.actpage.trafo.apply(*value)
124        self.actpage = self.actpage.parent
125
126    def flushtext(self, fontmap):
127        """ finish currently active text object """
128        if self.activetext:
129            x, y, charcodes = self.activetext
130            x_pt, y_pt = x * self.pyxconv, -y*self.pyxconv
131            self.actpage.insert(self.activefont.text_pt(x_pt, y_pt, charcodes, fontmap=fontmap))
132            if self.debug:
133                self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activetext[2]]))
134            self.activetext = None
135
136    def putrule(self, height, width, advancepos, fontmap):
137        self.flushtext(fontmap)
138        x1 =  self.pos[_POS_H] * self.pyxconv
139        y1 = -self.pos[_POS_V] * self.pyxconv
140        w = width * self.pyxconv
141        h = height * self.pyxconv
142
143        if height > 0 and width > 0:
144            if self.debug:
145                self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
146                                     (self.filepos, advancepos and "set" or "put", height, width))
147            self.actpage.fill(path.rect_pt(x1, y1, w, h))
148        else:
149            if self.debug:
150                self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
151                                     (self.filepos, advancepos and "set" or "put", height, width))
152
153        if advancepos:
154            if self.debug:
155                self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
156                                     (self.pos[_POS_H], width, self.pos[_POS_H]+width))
157            self.pos[_POS_H] += width * self.scale
158
159    def putchar(self, char, advancepos, id1234, fontmap):
160        dx = advancepos and self.activefont.getwidth_dvi(char) or 0
161
162        if self.debug:
163            self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
164                                 (self.filepos,
165                                  advancepos and "set" or "put",
166                                  id1234 and "%i " % id1234 or "char",
167                                  char,
168                                  self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
169
170        if isinstance(self.activefont, texfont.virtualfont):
171            # virtual font handling
172            afterpos = list(self.pos)
173            afterpos[_POS_H] += dx
174            self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
175                                 self.activefont.getsize_pt(), fontmap)
176        else:
177            if self.activetext is None:
178                self.activetext = (self.pos[_POS_H], self.pos[_POS_V], [])
179            self.activetext[2].append(char)
180            self.pos[_POS_H] += dx
181
182        if (not advancepos) or self.singlecharmode:
183            self.flushtext(fontmap)
184
185    def usefont(self, fontnum, id1234, fontmap):
186        self.flushtext(fontmap)
187        self.activefont = self.fonts[fontnum]
188        if self.debug:
189            self.debugfile.write("%d: fnt%s%i current font is %s\n" %
190                                 (self.filepos,
191                                  id1234 and "%i " % id1234 or "num",
192                                  fontnum,
193                                  self.fonts[fontnum].name))
194
195
196    def definefont(self, cmdnr, num, c, q, d, fontname):
197        # cmdnr: type of fontdef command (only used for debugging output)
198        # c:     checksum
199        # q:     scaling factor (fix_word)
200        #        Note that q is actually s in large parts of the documentation.
201        # d:     design size (fix_word)
202
203        # check whether it's a virtual font by trying to open it. if this fails, it is an ordinary TeX font
204        try:
205            with config.open(fontname, [config.format.vf]) as fontfile:
206                afont = texfont.virtualfont(fontname, fontfile, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.debug>1)
207        except EnvironmentError:
208            afont = texfont.TeXfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.debug>1)
209
210        self.fonts[num] = afont
211
212        if self.debug:
213            self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
214
215#            scale = round((1000.0*self.conv*q)/(self.trueconv*d))
216#            m = 1.0*q/d
217#            scalestring = scale!=1000 and " scaled %d" % scale or ""
218#            print ("Font %i: %s%s---loaded at size %d DVI units" %
219#                   (num, fontname, scalestring, q))
220#            if scale!=1000:
221#                print " (this font is magnified %d%%)" % round(scale/10)
222
223    def special(self, s, fontmap):
224        x =  self.pos[_POS_H] * self.pyxconv
225        y = -self.pos[_POS_V] * self.pyxconv
226        if self.debug:
227            self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
228        if not s.startswith("PyX:"):
229            logger.warning("ignoring special '%s'" % s)
230            return
231
232        # it is in general not safe to continue using the currently active font because
233        # the specials may involve some gsave/grestore operations
234        self.flushtext(fontmap)
235
236        command, args = s[4:].split()[0], s[4:].split()[1:]
237        if command == "color_begin":
238            if args[0] == "cmyk":
239                c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
240            elif args[0] == "gray":
241                c = color.gray(float(args[1]))
242            elif args[0] == "hsb":
243                c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
244            elif args[0] == "rgb":
245                c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
246            elif args[0] == "RGB":
247                c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
248            elif args[0] == "texnamed":
249                try:
250                    c = getattr(color.cmyk, args[1])
251                except AttributeError:
252                    raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
253            elif args[0] == "pyxcolor":
254                # pyx.color.cmyk.PineGreen or
255                # pyx.color.cmyk(0,0,0,0.0)
256                pat = re.compile(r"(pyx\.)?(color\.)?(?P<model>(cmyk)|(rgb)|(grey)|(gray)|(hsb))[\.]?(?P<arg>.*)")
257                sd = pat.match(" ".join(args[1:]))
258                if sd:
259                    sd = sd.groupdict()
260                    if sd["arg"][0] == "(":
261                        numpat = re.compile(r"[+-]?((\d+\.\d*)|(\d*\.\d+)|(\d+))([eE][+-]\d+)?")
262                        arg = tuple([float(x[0]) for x in numpat.findall(sd["arg"])])
263                        try:
264                            c = getattr(color, sd["model"])(*arg)
265                        except TypeError or AttributeError:
266                            raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
267                    else:
268                        try:
269                            c = getattr(getattr(color, sd["model"]), sd["arg"])
270                        except AttributeError:
271                            raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
272                else:
273                    raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
274            else:
275                raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
276
277            self.beginsubpage([c])
278        elif command == "color_end":
279            self.endsubpage()
280        elif command == "rotate_begin":
281            self.beginsubpage([trafo.rotate_pt(float(args[0]), x, y)])
282        elif command == "rotate_end":
283            self.endsubpage()
284        elif command == "scale_begin":
285            self.beginsubpage([trafo.scale_pt(float(args[0]), float(args[1]), x, y)])
286        elif command == "scale_end":
287            self.endsubpage()
288        elif command == "epsinclude":
289            # parse arguments
290            argdict = {}
291            for arg in args:
292                name, value = arg.split("=")
293                argdict[name] = value
294
295            # construct kwargs for epsfile constructor
296            epskwargs = {}
297            epskwargs["filename"] = argdict["file"]
298            epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
299                                           float(argdict["urx"]), float(argdict["ury"]))
300            if "width" in argdict:
301                epskwargs["width"] = float(argdict["width"]) * unit.t_pt
302            if "height" in argdict:
303                epskwargs["height"] = float(argdict["height"]) * unit.t_pt
304            if "clip" in argdict:
305               epskwargs["clip"] = int(argdict["clip"])
306            self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
307        elif command == "marker":
308            if len(args) != 1:
309                raise RuntimeError("marker contains spaces")
310            for c in args[0]:
311                if c not in string.ascii_letters + string.digits + "@":
312                    raise RuntimeError("marker contains invalid characters")
313            if args[0] in self.actpage.markers:
314                raise RuntimeError("marker name occurred several times")
315            self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
316        else:
317            raise RuntimeError("unknown PyX special '%s', aborting" % command)
318
319    # routines for pushing and popping different dvi chunks on the reader
320
321    def _push_dvistring(self, dvi, fonts, afterpos, fontsize, fontmap):
322        """ push dvi string with defined fonts on top of reader
323        stack. Every positions gets scaled relatively by the factor
324        scale. After interpretating the dvi chunk, continue with self.pos=afterpos.
325        The designsize of the virtual font is passed as a fix_word
326
327        """
328
329        #if self.debug:
330        #    self.debugfile.write("executing new dvi chunk\n")
331        self.debugstack.append(self.debug)
332        self.debug = 0
333
334        self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.scale))
335
336        # units in vf files are relative to the size of the font and given as fix_words
337        # which can be converted to floats by diving by 2**20.
338        # This yields the following scale factor for the height and width of rects:
339        self.scale = fontsize/2**20/self.pyxconv
340
341        self.file = reader.bytesreader(dvi)
342        self.fonts = fonts
343        self.stack = []
344        self.filepos = 0
345
346        self.usefont(0, 0, fontmap)
347
348    def _pop_dvistring(self, fontmap):
349        self.flushtext(fontmap)
350        #if self.debug:
351        #    self.debugfile.write("finished executing dvi chunk\n")
352        self.debug = self.debugstack.pop()
353
354        self.file.close()
355        self.file, self.fonts, self.activefont, self.pos, self.stack, self.scale = self.statestack.pop()
356
357    # routines corresponding to the different reader states of the dvi maschine
358
359    def _read_pre(self):
360        afile = self.file
361        while True:
362            self.filepos = afile.tell()
363            cmd = afile.readuchar()
364            if cmd == _DVI_NOP:
365                pass
366            elif cmd == _DVI_PRE:
367                if afile.readuchar() != _DVI_VERSION: raise DVIError
368                num = afile.readuint32()
369                den = afile.readuint32()
370                self.mag = afile.readuint32()
371
372                # For the interpretation of the lengths in dvi and tfm files,
373                # three conversion factors are relevant:
374                # - self.tfmconv: tfm units -> dvi units
375                # - self.pyxconv: dvi units -> (PostScript) points
376                # - self.conv:    dvi units -> pixels
377                self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
378
379                # calculate conv as described in the DVIType docu using
380                # a given resolution in dpi
381                self.resolution = 300.0
382                self.conv = (num/254000.0)*(self.resolution/den)
383
384                # self.pyxconv is the conversion factor from the dvi units
385                # to (PostScript) points. It consists of
386                # - self.mag/1000.0:   magstep scaling
387                # - self.conv:         conversion from dvi units to pixels
388                # - 1/self.resolution: conversion from pixels to inch
389                # - 72               : conversion from inch to points
390                self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
391
392                # scaling used for rules when VF chunks are interpreted
393                self.scale = 1
394
395                comment = afile.read(afile.readuchar())
396                return
397            else:
398                raise DVIError
399
400    def readpage(self, pageid=None, fontmap=None, singlecharmode=False, attrs=[]):
401        """ reads a page from the dvi file
402
403        This routine reads a page from the dvi file which is
404        returned as a canvas. When there is no page left in the
405        dvifile, None is returned and the file is closed properly."""
406
407        self.singlecharmode = singlecharmode
408
409        while True:
410            self.filepos = self.file.tell()
411            cmd = self.file.readuchar()
412            if cmd == _DVI_NOP:
413                pass
414            elif cmd == _DVI_BOP:
415                ispageid = [self.file.readuint32() for i in range(10)]
416                if pageid is not None and ispageid != pageid:
417                    raise DVIError("invalid pageid")
418                if self.debug:
419                    self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
420                self.file.readuint32()
421                break
422            elif cmd == _DVI_POST:
423                self.file.close()
424                return None # nothing left
425            else:
426                raise DVIError
427
428        self.actpage = canvas.canvas(attrs)
429        self.actpage.markers = {}
430        self.pos = [0, 0, 0, 0, 0, 0]
431
432        # tuple (hpos, vpos, codepoints) to be output, or None if no output is pending
433        self.activetext = None
434
435        while True:
436            afile = self.file
437            self.filepos = afile.tell()
438            try:
439                cmd = afile.readuchar()
440            except struct.error:
441                # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
442                # so we have to continue with the rest of the dvi file
443                self._pop_dvistring(fontmap)
444                continue
445            if cmd == _DVI_NOP:
446                pass
447            if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
448                self.putchar(cmd, True, 0, fontmap)
449            elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
450                self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), True, cmd-_DVI_SET1234+1, fontmap)
451            elif cmd == _DVI_SETRULE:
452                self.putrule(afile.readint32()*self.scale, afile.readint32()*self.scale, True, fontmap)
453            elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
454                self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), False, cmd-_DVI_SET1234+1, fontmap)
455            elif cmd == _DVI_PUTRULE:
456                self.putrule(afile.readint32()*self.scale, afile.readint32()*self.scale, False, fontmap)
457            elif cmd == _DVI_EOP:
458                self.flushtext(fontmap)
459                if self.debug:
460                    self.debugfile.write("%d: eop\n \n" % self.filepos)
461                return self.actpage
462            elif cmd == _DVI_PUSH:
463                self.stack.append(list(self.pos))
464                if self.debug:
465                    self.debugfile.write("%s: push\n"
466                                         "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
467                                         ((self.filepos, len(self.stack)-1) + tuple(self.pos)))
468            elif cmd == _DVI_POP:
469                self.flushtext(fontmap)
470                self.pos = self.stack.pop()
471                if self.debug:
472                    self.debugfile.write("%s: pop\n"
473                                         "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
474                                         ((self.filepos, len(self.stack)) + tuple(self.pos)))
475            elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
476                self.flushtext(fontmap)
477                dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1) * self.scale
478                if self.debug:
479                    self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
480                                         (self.filepos,
481                                          cmd - _DVI_RIGHT1234 + 1,
482                                          dh,
483                                          self.pos[_POS_H],
484                                          dh,
485                                          self.pos[_POS_H]+dh))
486                self.pos[_POS_H] += dh
487            elif cmd == _DVI_W0:
488                self.flushtext(fontmap)
489                if self.debug:
490                    self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
491                                         (self.filepos,
492                                          self.pos[_POS_W],
493                                          self.pos[_POS_H],
494                                          self.pos[_POS_W],
495                                          self.pos[_POS_H]+self.pos[_POS_W]))
496                self.pos[_POS_H] += self.pos[_POS_W]
497            elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
498                self.flushtext(fontmap)
499                self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1) * self.scale
500                if self.debug:
501                    self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
502                                         (self.filepos,
503                                          cmd - _DVI_W1234 + 1,
504                                          self.pos[_POS_W],
505                                          self.pos[_POS_H],
506                                          self.pos[_POS_W],
507                                          self.pos[_POS_H]+self.pos[_POS_W]))
508                self.pos[_POS_H] += self.pos[_POS_W]
509            elif cmd == _DVI_X0:
510                self.flushtext(fontmap)
511                if self.debug:
512                    self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
513                                         (self.filepos,
514                                          self.pos[_POS_X],
515                                          self.pos[_POS_H],
516                                          self.pos[_POS_X],
517                                          self.pos[_POS_H]+self.pos[_POS_X]))
518                self.pos[_POS_H] += self.pos[_POS_X]
519            elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
520                self.flushtext(fontmap)
521                self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1) * self.scale
522                if self.debug:
523                    self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
524                                         (self.filepos,
525                                          cmd - _DVI_X1234 + 1,
526                                          self.pos[_POS_X],
527                                          self.pos[_POS_H],
528                                          self.pos[_POS_X],
529                                          self.pos[_POS_H]+self.pos[_POS_X]))
530                self.pos[_POS_H] += self.pos[_POS_X]
531            elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
532                self.flushtext(fontmap)
533                dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1) * self.scale
534                if self.debug:
535                    self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
536                                         (self.filepos,
537                                          cmd - _DVI_DOWN1234 + 1,
538                                          dv,
539                                          self.pos[_POS_V],
540                                          dv,
541                                          self.pos[_POS_V]+dv))
542                self.pos[_POS_V] += dv
543            elif cmd == _DVI_Y0:
544                self.flushtext(fontmap)
545                if self.debug:
546                    self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
547                                         (self.filepos,
548                                          self.pos[_POS_Y],
549                                          self.pos[_POS_V],
550                                          self.pos[_POS_Y],
551                                          self.pos[_POS_V]+self.pos[_POS_Y]))
552                self.pos[_POS_V] += self.pos[_POS_Y]
553            elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
554                self.flushtext(fontmap)
555                self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1) * self.scale
556                if self.debug:
557                    self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
558                                         (self.filepos,
559                                          cmd - _DVI_Y1234 + 1,
560                                          self.pos[_POS_Y],
561                                          self.pos[_POS_V],
562                                          self.pos[_POS_Y],
563                                          self.pos[_POS_V]+self.pos[_POS_Y]))
564                self.pos[_POS_V] += self.pos[_POS_Y]
565            elif cmd == _DVI_Z0:
566                self.flushtext(fontmap)
567                if self.debug:
568                    self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
569                                         (self.filepos,
570                                          self.pos[_POS_Z],
571                                          self.pos[_POS_V],
572                                          self.pos[_POS_Z],
573                                          self.pos[_POS_V]+self.pos[_POS_Z]))
574                self.pos[_POS_V] += self.pos[_POS_Z]
575            elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
576                self.flushtext(fontmap)
577                self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1) * self.scale
578                if self.debug:
579                    self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
580                                         (self.filepos,
581                                          cmd - _DVI_Z1234 + 1,
582                                          self.pos[_POS_Z],
583                                          self.pos[_POS_V],
584                                          self.pos[_POS_Z],
585                                          self.pos[_POS_V]+self.pos[_POS_Z]))
586                self.pos[_POS_V] += self.pos[_POS_Z]
587            elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
588                self.usefont(cmd - _DVI_FNTNUMMIN, 0, fontmap)
589            elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
590                # note that according to the DVI docs, for four byte font numbers,
591                # the font number is signed. Don't ask why!
592                fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
593                self.usefont(fntnum, cmd-_DVI_FNT1234+1, fontmap)
594            elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
595                self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)).decode("ascii"), fontmap)
596            elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
597                if cmd == _DVI_FNTDEF1234:
598                    num = afile.readuchar()
599                elif cmd == _DVI_FNTDEF1234+1:
600                    num = afile.readuint16()
601                elif cmd == _DVI_FNTDEF1234+2:
602                    num = afile.readuint24()
603                elif cmd == _DVI_FNTDEF1234+3:
604                    # Cool, here we have according to docu a signed int. Why?
605                    num = afile.readint32()
606                self.definefont(cmd-_DVI_FNTDEF1234+1,
607                                num,
608                                afile.readint32(),
609                                afile.readint32(),
610                                afile.readint32(),
611                                afile.read(afile.readuchar()+afile.readuchar()).decode("ascii"))
612            else:
613                raise DVIError
614