~ubuntu-branches/ubuntu/trusty/pyx/trusty

« back to all changes in this revision

Viewing changes to pyx/dvifile.py

  • Committer: Bazaar Package Importer
  • Author(s): Stuart Prescott
  • Date: 2011-05-20 00:13:52 UTC
  • mfrom: (1.1.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20110520001352-7jfc1unuvduxji05
Tags: 0.11.1-1
* New upstream release.
* Change documentation build system to sphinx in line with new upstream docs.
* Rebuild lfs files as part of build process.
* Refresh patchs for new version:
  - drop manual-latex-define.patch no longer needed with sphinx.
  - drop siteconfig-static.patch: not needed with new build system
  - drop pyx-text-warnings26.patch: warnings fixed upstream
* Add patch sphinx-mathjax.patch to use pngmath in sphinx rather than mathjax
  which is not yet in Debian.
* Add patch createlfs-no-foiltex.patch to skip generation of foiltex-based
  lfs files that would require non-free components.
* Switch to dpkg-source 3.0 (quilt) format.
* Switch from python-support to dh_python2 as build helper.
* Update copyright format to newer DEP-5 specification.
* Add new files to copyright file.
* Bump standards version to 3.9.2 (no changes required).
* Set DM-Upload-Allowed: yes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: ISO-8859-1 -*-
2
 
#
3
 
#
4
 
# Copyright (C) 2002-2006 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-2006 Andr� Wobst <wobsta@users.sourceforge.net>
7
 
#
8
 
# This file is part of PyX (http://pyx.sourceforge.net/).
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
 
 
24
 
import cStringIO, exceptions, re, struct, string, sys, warnings, math
25
 
import unit, epsfile, bbox, canvas, color, trafo, path, pykpathsea, type1font
26
 
 
27
 
 
28
 
class binfile:
29
 
 
30
 
    def __init__(self, filename, mode="r"):
31
 
        self.file = open(filename, mode)
32
 
 
33
 
    def close(self):
34
 
        self.file.close()
35
 
 
36
 
    def tell(self):
37
 
        return self.file.tell()
38
 
 
39
 
    def eof(self):
40
 
        return self.file.eof()
41
 
 
42
 
    def read(self, bytes):
43
 
        return self.file.read(bytes)
44
 
 
45
 
    def readint(self, bytes=4, signed=0):
46
 
        first = 1
47
 
        result = 0
48
 
        while bytes:
49
 
            value = ord(self.file.read(1))
50
 
            if first and signed and value > 127:
51
 
                value -= 256
52
 
            first = 0
53
 
            result = 256 * result + value
54
 
            bytes -= 1
55
 
        return result
56
 
 
57
 
    def readint32(self):
58
 
        return struct.unpack(">l", self.file.read(4))[0]
59
 
 
60
 
    def readuint32(self):
61
 
        return struct.unpack(">L", self.file.read(4))[0]
62
 
 
63
 
    def readint24(self):
64
 
        # XXX: checkme
65
 
        return struct.unpack(">l", "\0"+self.file.read(3))[0]
66
 
 
67
 
    def readuint24(self):
68
 
        # XXX: checkme
69
 
        return struct.unpack(">L", "\0"+self.file.read(3))[0]
70
 
 
71
 
    def readint16(self):
72
 
        return struct.unpack(">h", self.file.read(2))[0]
73
 
 
74
 
    def readuint16(self):
75
 
        return struct.unpack(">H", self.file.read(2))[0]
76
 
 
77
 
    def readchar(self):
78
 
        return struct.unpack("b", self.file.read(1))[0]
79
 
 
80
 
    def readuchar(self):
81
 
        return struct.unpack("B", self.file.read(1))[0]
82
 
 
83
 
    def readstring(self, bytes):
84
 
        l = self.readuchar()
85
 
        assert l <= bytes-1, "inconsistency in file: string too long"
86
 
        return self.file.read(bytes-1)[:l]
87
 
 
88
 
class stringbinfile(binfile):
89
 
 
90
 
    def __init__(self, s):
91
 
        self.file = cStringIO.StringIO(s)
92
 
 
93
 
 
94
 
 
95
 
 
96
 
##############################################################################
97
 
# TFM file handling
98
 
##############################################################################
99
 
 
100
 
class TFMError(exceptions.Exception): pass
101
 
 
102
 
 
103
 
class char_info_word:
104
 
    def __init__(self, word):
105
 
        self.width_index  = int((word & 0xFF000000L) >> 24) #make sign-safe
106
 
        self.height_index = (word & 0x00F00000) >> 20
107
 
        self.depth_index  = (word & 0x000F0000) >> 16
108
 
        self.italic_index = (word & 0x0000FC00) >> 10
109
 
        self.tag          = (word & 0x00000300) >> 8
110
 
        self.remainder    = (word & 0x000000FF)
111
 
 
112
 
 
113
 
class tfmfile:
114
 
    def __init__(self, name, debug=0):
115
 
        self.file = binfile(name, "rb")
116
 
 
117
 
        #
118
 
        # read pre header
119
 
        #
120
 
 
121
 
        self.lf = self.file.readint16()
122
 
        self.lh = self.file.readint16()
123
 
        self.bc = self.file.readint16()
124
 
        self.ec = self.file.readint16()
125
 
        self.nw = self.file.readint16()
126
 
        self.nh = self.file.readint16()
127
 
        self.nd = self.file.readint16()
128
 
        self.ni = self.file.readint16()
129
 
        self.nl = self.file.readint16()
130
 
        self.nk = self.file.readint16()
131
 
        self.ne = self.file.readint16()
132
 
        self.np = self.file.readint16()
133
 
 
134
 
        if not (self.bc-1 <= self.ec <= 255 and
135
 
                self.ne <= 256 and
136
 
                self.lf == 6+self.lh+(self.ec-self.bc+1)+self.nw+self.nh+self.nd
137
 
                +self.ni+self.nl+self.nk+self.ne+self.np):
138
 
            raise TFMError, "error in TFM pre-header"
139
 
 
140
 
        if debug:
141
 
            print "lh=%d" % self.lh
142
 
 
143
 
        #
144
 
        # read header
145
 
        #
146
 
 
147
 
        self.checksum = self.file.readint32()
148
 
        self.designsize = self.file.readint32()
149
 
        assert self.designsize > 0, "invald design size"
150
 
        if self.lh > 2:
151
 
            assert self.lh > 11, "inconsistency in TFM file: incomplete field"
152
 
            self.charcoding = self.file.readstring(40)
153
 
        else:
154
 
            self.charcoding = None
155
 
 
156
 
        if self.lh > 12:
157
 
            assert self.lh > 16, "inconsistency in TFM file: incomplete field"
158
 
            self.fontfamily = self.file.readstring(20)
159
 
        else:
160
 
            self.fontfamily = None
161
 
 
162
 
        if debug:
163
 
            print "(FAMILY %s)" % self.fontfamily
164
 
            print "(CODINGSCHEME %s)" % self.charcoding
165
 
            print "(DESINGSIZE R %f)" % 16.0*self.designsize/16777216L
166
 
 
167
 
        if self.lh > 17:
168
 
            self.sevenbitsave = self.file.readuchar()
169
 
            # ignore the following two bytes
170
 
            self.file.readint16()
171
 
            facechar = self.file.readuchar()
172
 
            # decode ugly face specification into the Knuth suggested string
173
 
            if facechar < 18:
174
 
                if facechar >= 12:
175
 
                    self.face = "E"
176
 
                    facechar -= 12
177
 
                elif facechar >= 6:
178
 
                    self.face = "C"
179
 
                    facechar -= 6
180
 
                else:
181
 
                    self.face = "R"
182
 
 
183
 
                if facechar >= 4:
184
 
                    self.face = "L" + self.face
185
 
                    facechar -= 4
186
 
                elif facechar >= 2:
187
 
                    self.face = "B" + self.face
188
 
                    facechar -= 2
189
 
                else:
190
 
                    self.face = "M" + self.face
191
 
 
192
 
                if facechar == 1:
193
 
                    self.face = self.face[0] + "I" + self.face[1]
194
 
                else:
195
 
                    self.face = self.face[0] + "R" + self.face[1]
196
 
 
197
 
            else:
198
 
                self.face = None
199
 
        else:
200
 
            self.sevenbitsave = self.face = None
201
 
 
202
 
        if self.lh > 18:
203
 
            # just ignore the rest
204
 
            print self.file.read((self.lh-18)*4)
205
 
 
206
 
        #
207
 
        # read char_info
208
 
        #
209
 
 
210
 
        self.char_info = [None]*(self.ec+1)
211
 
        for charcode in range(self.bc, self.ec+1):
212
 
            self.char_info[charcode] = char_info_word(self.file.readint32())
213
 
            if self.char_info[charcode].width_index == 0:
214
 
                # disable character if width_index is zero
215
 
                self.char_info[charcode] = None
216
 
 
217
 
        #
218
 
        # read widths
219
 
        #
220
 
 
221
 
        self.width = [None for width_index in range(self.nw)]
222
 
        for width_index in range(self.nw):
223
 
            self.width[width_index] = self.file.readint32()
224
 
 
225
 
        #
226
 
        # read heights
227
 
        #
228
 
 
229
 
        self.height = [None for height_index in range(self.nh)]
230
 
        for height_index in range(self.nh):
231
 
            self.height[height_index] = self.file.readint32()
232
 
 
233
 
        #
234
 
        # read depths
235
 
        #
236
 
 
237
 
        self.depth = [None for depth_index in range(self.nd)]
238
 
        for depth_index in range(self.nd):
239
 
            self.depth[depth_index] = self.file.readint32()
240
 
 
241
 
        #
242
 
        # read italic
243
 
        #
244
 
 
245
 
        self.italic = [None for italic_index in range(self.ni)]
246
 
        for italic_index in range(self.ni):
247
 
            self.italic[italic_index] = self.file.readint32()
248
 
 
249
 
        #
250
 
        # read lig_kern
251
 
        #
252
 
 
253
 
        # XXX decode to lig_kern_command
254
 
 
255
 
        self.lig_kern = [None for lig_kern_index in range(self.nl)]
256
 
        for lig_kern_index in range(self.nl):
257
 
            self.lig_kern[lig_kern_index] = self.file.readint32()
258
 
 
259
 
        #
260
 
        # read kern
261
 
        #
262
 
 
263
 
        self.kern = [None for kern_index in range(self.nk)]
264
 
        for kern_index in range(self.nk):
265
 
            self.kern[kern_index] = self.file.readint32()
266
 
 
267
 
        #
268
 
        # read exten
269
 
        #
270
 
 
271
 
        # XXX decode to extensible_recipe
272
 
 
273
 
        self.exten = [None for exten_index in range(self.ne)]
274
 
        for exten_index in range(self.ne):
275
 
            self.exten[exten_index] = self.file.readint32()
276
 
 
277
 
        #
278
 
        # read param
279
 
        #
280
 
 
281
 
        # XXX decode
282
 
 
283
 
        self.param = [None for param_index in range(self.np)]
284
 
        for param_index in range(self.np):
285
 
            self.param[param_index] = self.file.readint32()
286
 
 
287
 
        self.file.close()
288
 
 
289
 
 
290
 
 
291
 
##############################################################################
292
 
# Font handling
293
 
##############################################################################
294
 
 
295
 
#
296
 
# PostScript font selection and output primitives
297
 
#
298
 
 
299
 
class UnsupportedFontFormat(Exception):
300
 
    pass
301
 
 
302
 
class UnsupportedPSFragment(Exception):
303
 
    pass
304
 
 
305
 
class fontmapping:
306
 
 
307
 
    tokenpattern = re.compile(r'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
308
 
 
309
 
    def __init__(self, s):
310
 
        """ construct font mapping from line s of font mapping file """
311
 
        self.texname = self.basepsname = self.fontfile = None
312
 
 
313
 
        # standard encoding
314
 
        self.encodingfile = None
315
 
 
316
 
        # supported postscript fragments occuring in psfonts.map
317
 
        self.reencodefont = self.extendfont = self.slantfont = None
318
 
 
319
 
        tokens = []
320
 
        while len(s):
321
 
            match = self.tokenpattern.match(s)
322
 
            if match:
323
 
                if match.groups()[0] is not None:
324
 
                    tokens.append('"%s"' % match.groups()[0])
325
 
                else:
326
 
                    tokens.append(match.groups()[2])
327
 
                s = s[match.end():]
328
 
            else:
329
 
                raise RuntimeError("Cannot tokenize string '%s'" % s)
330
 
 
331
 
        for token in tokens:
332
 
            if token.startswith("<"):
333
 
                if token.startswith("<<"):
334
 
                    # XXX: support non-partial download here
335
 
                    self.fontfile = token[2:]
336
 
                elif token.startswith("<["):
337
 
                    self.encodingfile = token[2:]
338
 
                elif token.endswith(".pfa") or token.endswith(".pfb"):
339
 
                    self.fontfile = token[1:]
340
 
                elif token.endswith(".enc"):
341
 
                    self.encodingfile = token[1:]
342
 
                elif token.endswith(".ttf"):
343
 
                    raise UnsupportedFontFormat("TrueType font")
344
 
                else:
345
 
                    raise RuntimeError("Unknown token '%s'" % token)
346
 
            elif token.startswith('"'):
347
 
                pscode = token[1:-1].split()
348
 
                # parse standard postscript code fragments
349
 
                while pscode:
350
 
                    try:
351
 
                        arg, cmd = pscode[:2]
352
 
                    except:
353
 
                        raise UnsupportedPSFragment("Unsupported Postscript fragment '%s'" % pscode)
354
 
                    pscode = pscode[2:]
355
 
                    if cmd == "ReEncodeFont":
356
 
                        self.reencodefont = arg
357
 
                    elif cmd == "ExtendFont":
358
 
                        self.extendfont = arg
359
 
                    elif cmd == "SlantFont":
360
 
                        self.slantfont = arg
361
 
                    else:
362
 
                        raise UnsupportedPSFragment("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
363
 
            else:
364
 
                if self.texname is None:
365
 
                    self.texname = token
366
 
                else:
367
 
                    self.basepsname = token
368
 
        if self.basepsname is None:
369
 
            self.basepsname = self.texname
370
 
 
371
 
    def __str__(self):
372
 
        return ("'%s' is '%s' read from '%s' encoded as '%s'" %
373
 
                (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
374
 
 
375
 
# generate fontmap
376
 
 
377
 
def readfontmap(filenames):
378
 
    """ read font map from filename (without path) """
379
 
    fontmap = {}
380
 
    for filename in filenames:
381
 
        mappath = pykpathsea.find_file(filename, pykpathsea.kpse_fontmap_format)
382
 
        # try also the oft-used registration as dvips config file
383
 
        if not mappath:
384
 
            mappath = pykpathsea.find_file(filename, pykpathsea.kpse_dvips_config_format)
385
 
        if not mappath:
386
 
            raise RuntimeError("cannot find font mapping file '%s'" % filename)
387
 
        mapfile = open(mappath, "rU")
388
 
        lineno = 0
389
 
        for line in mapfile.readlines():
390
 
            lineno += 1
391
 
            line = line.rstrip()
392
 
            if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
393
 
                try:
394
 
                    fm = fontmapping(line)
395
 
                except (RuntimeError, UnsupportedPSFragment), e:
396
 
                    warnings.warn("Ignoring line %i in mapping file '%s': %s" % (lineno, mappath, e))
397
 
                except UnsupportedFontFormat, e:
398
 
                    pass
399
 
                else:
400
 
                    fontmap[fm.texname] = fm
401
 
        mapfile.close()
402
 
    return fontmap
403
 
 
404
 
 
405
 
class font:
406
 
 
407
 
    def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
408
 
        self.name = name
409
 
        self.q = q                  # desired size of font (fix_word) in TeX points
410
 
        self.d = d                  # design size of font (fix_word) in TeX points
411
 
        self.tfmconv = tfmconv      # conversion factor from tfm units to dvi units
412
 
        self.pyxconv = pyxconv      # conversion factor from dvi units to PostScript points
413
 
        self.fontmap = fontmap
414
 
        tfmpath = pykpathsea.find_file("%s.tfm" % self.name, pykpathsea.kpse_tfm_format)
415
 
        if not tfmpath:
416
 
            raise TFMError("cannot find %s.tfm" % self.name)
417
 
        self.tfmfile = tfmfile(tfmpath, debug)
418
 
 
419
 
        # We only check for equality of font checksums if none of them
420
 
        # is zero. The case c == 0 happend in some VF files and
421
 
        # according to the VFtoVP documentation, paragraph 40, a check
422
 
        # is only performed if tfmfile.checksum > 0. Anyhow, being
423
 
        # more generous here seems to be reasonable
424
 
        if self.tfmfile.checksum != c and self.tfmfile.checksum != 0 and c != 0:
425
 
            raise DVIError("check sums do not agree: %d vs. %d" %
426
 
                           (self.tfmfile.checksum, c))
427
 
 
428
 
        # Check whether the given design size matches the one defined in the tfm file
429
 
        if abs(self.tfmfile.designsize - d) > 2:
430
 
            raise DVIError("design sizes do not agree: %d vs. %d" % (self.tfmfile.designsize, d))
431
 
        #if q < 0 or q > 134217728:
432
 
        #    raise DVIError("font '%s' not loaded: bad scale" % self.name)
433
 
        if d < 0 or d > 134217728:
434
 
            raise DVIError("font '%s' not loaded: bad design size" % self.name)
435
 
 
436
 
        self.scale = 1.0*q/d
437
 
 
438
 
    def fontinfo(self):
439
 
        class fontinfo:
440
 
            pass
441
 
 
442
 
        # The following code is a very crude way to obtain the information
443
 
        # required for the PDF font descritor. (TODO: The correct way would
444
 
        # be to read the information from the AFM file.)
445
 
        fontinfo = fontinfo()
446
 
        try:
447
 
            fontinfo.fontbbox = (0,
448
 
                                 -self.getdepth_ds(ord("y")),
449
 
                                 self.getwidth_ds(ord("W")),
450
 
                                 self.getheight_ds(ord("H")))
451
 
        except:
452
 
            fontinfo.fontbbox = (0, -10, 100, 100)
453
 
        try:
454
 
            fontinfo.italicangle = -180/math.pi*math.atan(self.tfmfile.param[0]/65536.0)
455
 
        except IndexError:
456
 
            fontinfo.italicangle = 0
457
 
        fontinfo.ascent = fontinfo.fontbbox[3]
458
 
        fontinfo.descent = fontinfo.fontbbox[1]
459
 
        try:
460
 
            fontinfo.capheight = self.getheight_ds(ord("h"))
461
 
        except:
462
 
            fontinfo.capheight = 100
463
 
        try:
464
 
            fontinfo.vstem = self.getwidth_ds(ord("."))/3
465
 
        except:
466
 
            fontinfo.vstem = 5
467
 
        return fontinfo
468
 
 
469
 
    def __str__(self):
470
 
        return "font %s designed at %g TeX pts used at %g TeX pts" % (self.name, 
471
 
                                                                      16.0*self.d/16777216L,
472
 
                                                                      16.0*self.q/16777216L)
473
 
    __repr__ = __str__
474
 
 
475
 
    def getsize_pt(self):
476
 
        """ return size of font in (PS) points """
477
 
        # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
478
 
        # to the corresponding float. Furthermore, we have to convert from TeX
479
 
        # points to points, hence the factor 72/72.27.
480
 
        return 16L*self.q/16777216L*72/72.27
481
 
 
482
 
    def _convert_tfm_to_dvi(self, length):
483
 
        # doing the integer math with long integers will lead to different roundings
484
 
        # return 16*length*int(round(self.q*self.tfmconv))/16777216
485
 
 
486
 
        # Knuth instead suggests the following algorithm based on 4 byte integer logic only
487
 
        # z = int(round(self.q*self.tfmconv))
488
 
        # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
489
 
        # assert b0 == 0 or b0 == 255
490
 
        # shift = 4
491
 
        # while z >= 8388608:
492
 
        #     z >>= 1
493
 
        #     shift -= 1
494
 
        # assert shift >= 0
495
 
        # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
496
 
        # if b0 == 255:
497
 
        #     result = result - (z << (8-shift))
498
 
 
499
 
        # however, we can simplify this using a single long integer multiplication,
500
 
        # but take into account the transformation of z
501
 
        z = int(round(self.q*self.tfmconv))
502
 
        assert -16777216 <= length < 16777216 # -(1 << 24) <= length < (1 << 24)
503
 
        assert z < 134217728 # 1 << 27
504
 
        shift = 20 # 1 << 20
505
 
        while z >= 8388608: # 1 << 23
506
 
            z >>= 1
507
 
            shift -= 1
508
 
        # length*z is a long integer, but the result will be a regular integer
509
 
        return int(length*long(z) >> shift)
510
 
 
511
 
    def _convert_tfm_to_ds(self, length):
512
 
        return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv * 1000 / self.getsize_pt()
513
 
    
514
 
    def _convert_tfm_to_pt(self, length):
515
 
        return (16*long(round(length*float(self.q)*self.tfmconv))/16777216) * self.pyxconv
516
 
 
517
 
    # routines returning lengths as integers in dvi units
518
 
 
519
 
    def getwidth_dvi(self, charcode):
520
 
        return self._convert_tfm_to_dvi(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
521
 
 
522
 
    def getheight_dvi(self, charcode):
523
 
        return self._convert_tfm_to_dvi(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
524
 
 
525
 
    def getdepth_dvi(self, charcode):
526
 
        return self._convert_tfm_to_dvi(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
527
 
 
528
 
    def getitalic_dvi(self, charcode):
529
 
        return self._convert_tfm_to_dvi(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
530
 
 
531
 
    # routines returning lengths as integers in design size (AFM) units 
532
 
 
533
 
    def getwidth_ds(self, charcode):
534
 
        return self._convert_tfm_to_ds(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
535
 
 
536
 
    def getheight_ds(self, charcode):
537
 
        return self._convert_tfm_to_ds(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
538
 
 
539
 
    def getdepth_ds(self, charcode):
540
 
        return self._convert_tfm_to_ds(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
541
 
 
542
 
    def getitalic_ds(self, charcode):
543
 
        return self._convert_tfm_to_ds(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
544
 
 
545
 
    # routines returning lengths as floats in PostScript points
546
 
 
547
 
    def getwidth_pt(self, charcode):
548
 
        return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
549
 
 
550
 
    def getheight_pt(self, charcode):
551
 
        return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
552
 
 
553
 
    def getdepth_pt(self, charcode):
554
 
        return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
555
 
 
556
 
    def getitalic_pt(self, charcode):
557
 
        return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
558
 
 
559
 
 
560
 
class virtualfont(font):
561
 
    def __init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug=0):
562
 
        fontpath = pykpathsea.find_file(name, pykpathsea.kpse_vf_format)
563
 
        if fontpath is None or not len(fontpath):
564
 
            raise RuntimeError
565
 
        font.__init__(self, name, c, q, d, tfmconv, pyxconv, fontmap, debug)
566
 
        self.vffile = vffile(fontpath, self.scale, tfmconv, pyxconv, fontmap, debug > 1)
567
 
 
568
 
    def getfonts(self):
569
 
        """ return fonts used in virtual font itself """
570
 
        return self.vffile.getfonts()
571
 
 
572
 
    def getchar(self, cc):
573
 
        """ return dvi chunk corresponding to char code cc """
574
 
        return self.vffile.getchar(cc)
575
 
 
576
 
 
577
 
##############################################################################
578
 
# DVI file handling
579
 
##############################################################################
580
 
 
581
 
_DVI_CHARMIN     =   0 # typeset a character and move right (range min)
582
 
_DVI_CHARMAX     = 127 # typeset a character and move right (range max)
583
 
_DVI_SET1234     = 128 # typeset a character and move right
584
 
_DVI_SETRULE     = 132 # typeset a rule and move right
585
 
_DVI_PUT1234     = 133 # typeset a character
586
 
_DVI_PUTRULE     = 137 # typeset a rule
587
 
_DVI_NOP         = 138 # no operation
588
 
_DVI_BOP         = 139 # beginning of page
589
 
_DVI_EOP         = 140 # ending of page
590
 
_DVI_PUSH        = 141 # save the current positions (h, v, w, x, y, z)
591
 
_DVI_POP         = 142 # restore positions (h, v, w, x, y, z)
592
 
_DVI_RIGHT1234   = 143 # move right
593
 
_DVI_W0          = 147 # move right by w
594
 
_DVI_W1234       = 148 # move right and set w
595
 
_DVI_X0          = 152 # move right by x
596
 
_DVI_X1234       = 153 # move right and set x
597
 
_DVI_DOWN1234    = 157 # move down
598
 
_DVI_Y0          = 161 # move down by y
599
 
_DVI_Y1234       = 162 # move down and set y
600
 
_DVI_Z0          = 166 # move down by z
601
 
_DVI_Z1234       = 167 # move down and set z
602
 
_DVI_FNTNUMMIN   = 171 # set current font (range min)
603
 
_DVI_FNTNUMMAX   = 234 # set current font (range max)
604
 
_DVI_FNT1234     = 235 # set current font
605
 
_DVI_SPECIAL1234 = 239 # special (dvi extention)
606
 
_DVI_FNTDEF1234  = 243 # define the meaning of a font number
607
 
_DVI_PRE         = 247 # preamble
608
 
_DVI_POST        = 248 # postamble beginning
609
 
_DVI_POSTPOST    = 249 # postamble ending
610
 
 
611
 
_DVI_VERSION     = 2   # dvi version
612
 
 
613
 
# position variable indices
614
 
_POS_H           = 0
615
 
_POS_V           = 1
616
 
_POS_W           = 2
617
 
_POS_X           = 3
618
 
_POS_Y           = 4
619
 
_POS_Z           = 5
620
 
 
621
 
# reader states
622
 
_READ_PRE       = 1
623
 
_READ_NOPAGE    = 2
624
 
_READ_PAGE      = 3
625
 
_READ_POST      = 4 # XXX not used
626
 
_READ_POSTPOST  = 5 # XXX not used
627
 
_READ_DONE      = 6
628
 
 
629
 
 
630
 
class DVIError(exceptions.Exception): pass
631
 
 
632
 
# save and restore colors
633
 
 
634
 
class _savecolor(canvas.canvasitem):
635
 
    def processPS(self, file, writer, context, registry, bbox):
636
 
        file.write("currentcolor currentcolorspace\n")
637
 
 
638
 
    def processPDF(self, file, writer, context, registry, bbox):
639
 
        file.write("q\n")
640
 
 
641
 
 
642
 
class _restorecolor(canvas.canvasitem):
643
 
    def processPS(self, file, writer, context, registry, bbox):
644
 
        file.write("setcolorspace setcolor\n")
645
 
 
646
 
    def processPDF(self, file, writer, context, registry, bbox):
647
 
        file.write("Q\n")
648
 
 
649
 
class _savetrafo(canvas.canvasitem):
650
 
    def processPS(self, file, writer, context, registry, bbox):
651
 
        file.write("matrix currentmatrix\n")
652
 
 
653
 
    def processPDF(self, file, writer, context, registry, bbox):
654
 
        file.write("q\n")
655
 
 
656
 
 
657
 
class _restoretrafo(canvas.canvasitem):
658
 
    def processPS(self, file, writer, context, registry, bbox):
659
 
        file.write("setmatrix\n")
660
 
 
661
 
    def processPDF(self, file, writer, context, registry, bbox):
662
 
        file.write("Q\n")
663
 
 
664
 
 
665
 
class dvifile:
666
 
 
667
 
    def __init__(self, filename, fontmap, debug=0, debugfile=sys.stdout):
668
 
        """ opens the dvi file and reads the preamble """
669
 
        self.filename = filename
670
 
        self.fontmap = fontmap
671
 
        self.debug = debug
672
 
        self.debugfile = debugfile
673
 
        self.debugstack = []
674
 
 
675
 
        self.fonts = {}
676
 
        self.activefont = None
677
 
 
678
 
        # stack of fonts and fontscale currently used (used for VFs)
679
 
        self.fontstack = []
680
 
        self.stack = []
681
 
 
682
 
        # pointer to currently active page
683
 
        self.actpage = None
684
 
 
685
 
        # stack for self.file, self.fonts and self.stack, needed for VF inclusion
686
 
        self.statestack = []
687
 
 
688
 
        self.file = binfile(self.filename, "rb")
689
 
 
690
 
        # currently read byte in file (for debugging output)
691
 
        self.filepos = None
692
 
 
693
 
        self._read_pre()
694
 
 
695
 
    # helper routines
696
 
 
697
 
    def flushtext(self):
698
 
        """ finish currently active text object """
699
 
        if self.debug and self.activetext:
700
 
            self.debugfile.write("[%s]\n" % "".join([chr(char) for char in self.activetext.chars]))
701
 
        self.activetext = None
702
 
 
703
 
    def putrule(self, height, width, advancepos=1):
704
 
        self.flushtext()
705
 
        x1 =  self.pos[_POS_H] * self.pyxconv
706
 
        y1 = -self.pos[_POS_V] * self.pyxconv
707
 
        w = width * self.pyxconv
708
 
        h = height * self.pyxconv
709
 
 
710
 
        if height > 0 and width > 0:
711
 
            if self.debug:
712
 
                self.debugfile.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
713
 
                                     (self.filepos, advancepos and "set" or "put", height, width))
714
 
            self.actpage.fill(path.rect_pt(x1, y1, w, h))
715
 
        else:
716
 
            if self.debug:
717
 
                self.debugfile.write("%d: %srule height %d, width %d (invisible)\n" %
718
 
                                     (self.filepos, advancepos and "set" or "put", height, width))
719
 
 
720
 
        if advancepos:
721
 
            if self.debug:
722
 
                self.debugfile.write(" h:=%d+%d=%d, hh:=???\n" %
723
 
                                     (self.pos[_POS_H], width, self.pos[_POS_H]+width))
724
 
            self.pos[_POS_H] += width
725
 
 
726
 
    def putchar(self, char, advancepos=1, id1234=0):
727
 
        dx = advancepos and self.activefont.getwidth_dvi(char) or 0
728
 
 
729
 
        if self.debug:
730
 
            self.debugfile.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
731
 
                                 (self.filepos,
732
 
                                  advancepos and "set" or "put",
733
 
                                  id1234 and "%i " % id1234 or "char",
734
 
                                  char,
735
 
                                  self.pos[_POS_H], dx, self.pos[_POS_H]+dx))
736
 
 
737
 
        if isinstance(self.activefont, virtualfont):
738
 
            # virtual font handling
739
 
            afterpos = list(self.pos)
740
 
            afterpos[_POS_H] += dx
741
 
            self._push_dvistring(self.activefont.getchar(char), self.activefont.getfonts(), afterpos,
742
 
                                 self.activefont.getsize_pt())
743
 
        else:
744
 
            if self.activetext is None:
745
 
                if not self.fontmap.has_key(self.activefont.name):
746
 
                    raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self.activefont.name)
747
 
                fontmapinfo = self.fontmap[self.activefont.name]
748
 
 
749
 
                encodingname = fontmapinfo.reencodefont
750
 
                if encodingname is not None:
751
 
                    encodingfilename = pykpathsea.find_file(fontmapinfo.encodingfile, pykpathsea.kpse_tex_ps_header_format)
752
 
                    if not encodingfilename:
753
 
                        raise RuntimeError("cannot find font encoding file %s" % fontmapinfo.encodingfile)
754
 
                    fontencoding = type1font.encoding(encodingname, encodingfilename)
755
 
                else:
756
 
                    fontencoding = None
757
 
 
758
 
                fontbasefontname = fontmapinfo.basepsname
759
 
                if fontmapinfo.fontfile is not None:
760
 
                    fontfilename = pykpathsea.find_file(fontmapinfo.fontfile, pykpathsea.kpse_type1_format)
761
 
                    if not fontfilename:
762
 
                        raise RuntimeError("cannot find type 1 font %s" % fontmapinfo.fontfile)
763
 
                else:
764
 
                    fontfilename = None
765
 
 
766
 
                fontslant = fontmapinfo.slantfont
767
 
                if fontslant is not None:
768
 
                    fontslant = float(fontslant)
769
 
 
770
 
                # XXX we currently misuse use self.activefont as metric 
771
 
                font = type1font.font(fontbasefontname, fontfilename, fontencoding, fontslant, self.activefont)
772
 
 
773
 
                self.activetext = type1font.text_pt(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv, font)
774
 
                self.actpage.insert(self.activetext)
775
 
            self.activetext.addchar(char)
776
 
            self.pos[_POS_H] += dx
777
 
 
778
 
        if not advancepos:
779
 
            self.flushtext()
780
 
 
781
 
    def usefont(self, fontnum, id1234=0):
782
 
        self.flushtext()
783
 
        self.activefont = self.fonts[fontnum]
784
 
        if self.debug:
785
 
            self.debugfile.write("%d: fnt%s%i current font is %s\n" %
786
 
                                 (self.filepos,
787
 
                                  id1234 and "%i " % id1234 or "num",
788
 
                                  fontnum,
789
 
                                  self.fonts[fontnum].name))
790
 
 
791
 
 
792
 
    def definefont(self, cmdnr, num, c, q, d, fontname):
793
 
        # cmdnr: type of fontdef command (only used for debugging output)
794
 
        # c:     checksum
795
 
        # q:     scaling factor (fix_word)
796
 
        #        Note that q is actually s in large parts of the documentation.
797
 
        # d:     design size (fix_word)
798
 
 
799
 
        try:
800
 
            afont = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
801
 
        except (TypeError, RuntimeError):
802
 
            afont = font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
803
 
 
804
 
        self.fonts[num] = afont
805
 
 
806
 
        if self.debug:
807
 
            self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
808
 
 
809
 
#            scale = round((1000.0*self.conv*q)/(self.trueconv*d))
810
 
#            m = 1.0*q/d
811
 
#            scalestring = scale!=1000 and " scaled %d" % scale or ""
812
 
#            print ("Font %i: %s%s---loaded at size %d DVI units" %
813
 
#                   (num, fontname, scalestring, q))
814
 
#            if scale!=1000:
815
 
#                print " (this font is magnified %d%%)" % round(scale/10)
816
 
 
817
 
    def special(self, s):
818
 
        x =  self.pos[_POS_H] * self.pyxconv
819
 
        y = -self.pos[_POS_V] * self.pyxconv
820
 
        if self.debug:
821
 
            self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
822
 
        if not s.startswith("PyX:"):
823
 
            warnings.warn("ignoring special '%s'" % s)
824
 
            return
825
 
 
826
 
        # it is in general not safe to continue using the currently active font because
827
 
        # the specials may involve some gsave/grestore operations
828
 
        self.flushtext()
829
 
 
830
 
        command, args = s[4:].split()[0], s[4:].split()[1:]
831
 
        if command == "color_begin":
832
 
            if args[0] == "cmyk":
833
 
                c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
834
 
            elif args[0] == "gray":
835
 
                c = color.gray(float(args[1]))
836
 
            elif args[0] == "hsb":
837
 
                c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
838
 
            elif args[0] == "rgb":
839
 
                c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
840
 
            elif args[0] == "RGB":
841
 
                c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
842
 
            elif args[0] == "texnamed":
843
 
                try:
844
 
                    c = getattr(color.cmyk, args[1])
845
 
                except AttributeError:
846
 
                    raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
847
 
            elif args[0] == "pyxcolor":
848
 
                # pyx.color.cmyk.PineGreen or
849
 
                # pyx.color.cmyk(0,0,0,0.0)
850
 
                pat = re.compile(r"(pyx\.)?(color\.)?(?P<model>(cmyk)|(rgb)|(grey)|(gray)|(hsb))[\.]?(?P<arg>.*)")
851
 
                sd = pat.match(" ".join(args[1:]))
852
 
                if sd:
853
 
                    sd = sd.groupdict()
854
 
                    if sd["arg"][0] == "(":
855
 
                        numpat = re.compile(r"[+-]?((\d+\.\d*)|(\d*\.\d+)|(\d+))([eE][+-]\d+)?")
856
 
                        arg = tuple([float(x[0]) for x in numpat.findall(sd["arg"])])
857
 
                        try:
858
 
                            c = getattr(color, sd["model"])(*arg)
859
 
                        except TypeError or AttributeError:
860
 
                            raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
861
 
                    else:
862
 
                        try:
863
 
                            c = getattr(getattr(color, sd["model"]), sd["arg"])
864
 
                        except AttributeError:
865
 
                            raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
866
 
                else:
867
 
                    raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
868
 
            else:
869
 
                raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
870
 
            self.actpage.insert(_savecolor())
871
 
            self.actpage.insert(c)
872
 
        elif command == "color_end":
873
 
            self.actpage.insert(_restorecolor())
874
 
        elif command == "rotate_begin":
875
 
            self.actpage.insert(_savetrafo())
876
 
            self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
877
 
        elif command == "rotate_end":
878
 
            self.actpage.insert(_restoretrafo())
879
 
        elif command == "scale_begin":
880
 
            self.actpage.insert(_savetrafo())
881
 
            self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
882
 
        elif command == "scale_end":
883
 
            self.actpage.insert(_restoretrafo())
884
 
        elif command == "epsinclude":
885
 
            # parse arguments
886
 
            argdict = {}
887
 
            for arg in args:
888
 
                name, value = arg.split("=")
889
 
                argdict[name] = value
890
 
 
891
 
            # construct kwargs for epsfile constructor
892
 
            epskwargs = {}
893
 
            epskwargs["filename"] = argdict["file"]
894
 
            epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
895
 
                                           float(argdict["urx"]), float(argdict["ury"]))
896
 
            if argdict.has_key("width"):
897
 
                epskwargs["width"] = float(argdict["width"]) * unit.t_pt
898
 
            if argdict.has_key("height"):
899
 
                epskwargs["height"] = float(argdict["height"]) * unit.t_pt
900
 
            if argdict.has_key("clip"):
901
 
               epskwargs["clip"] = int(argdict["clip"])
902
 
            self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
903
 
        elif command == "marker":
904
 
            if len(args) != 1:
905
 
                raise RuntimeError("marker contains spaces")
906
 
            for c in args[0]:
907
 
                if c not in string.digits + string.letters + "@":
908
 
                    raise RuntimeError("marker contains invalid characters")
909
 
            if self.actpage.markers.has_key(args[0]):
910
 
                raise RuntimeError("marker name occurred several times")
911
 
            self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
912
 
        else:
913
 
            raise RuntimeError("unknown PyX special '%s', aborting" % command)
914
 
 
915
 
    # routines for pushing and popping different dvi chunks on the reader
916
 
 
917
 
    def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
918
 
        """ push dvi string with defined fonts on top of reader
919
 
        stack. Every positions gets scaled relatively by the factor
920
 
        scale. After the interpreting of the dvi chunk has been finished,
921
 
        continue with self.pos=afterpos. The designsize of the virtual
922
 
        font is passed as a fix_word
923
 
 
924
 
        """
925
 
 
926
 
        #if self.debug:
927
 
        #    self.debugfile.write("executing new dvi chunk\n")
928
 
        self.debugstack.append(self.debug)
929
 
        self.debug = 0
930
 
 
931
 
        self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
932
 
 
933
 
        # units in vf files are relative to the size of the font and given as fix_words
934
 
        # which can be converted to floats by diving by 2**20
935
 
        oldpyxconv = self.pyxconv
936
 
        self.pyxconv = fontsize/2**20
937
 
        rescale = self.pyxconv/oldpyxconv
938
 
 
939
 
        self.file = stringbinfile(dvi)
940
 
        self.fonts = fonts
941
 
        self.stack = []
942
 
        self.filepos = 0
943
 
 
944
 
        # rescale self.pos in order to be consistent with the new scaling
945
 
        self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
946
 
 
947
 
        # since tfmconv converts from tfm units to dvi units, rescale it as well
948
 
        self.tfmconv /= rescale
949
 
 
950
 
        self.usefont(0)
951
 
 
952
 
    def _pop_dvistring(self):
953
 
        self.flushtext()
954
 
        #if self.debug:
955
 
        #    self.debugfile.write("finished executing dvi chunk\n")
956
 
        self.debug = self.debugstack.pop()
957
 
 
958
 
        self.file.close()
959
 
        self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
960
 
 
961
 
    # routines corresponding to the different reader states of the dvi maschine
962
 
 
963
 
    def _read_pre(self):
964
 
        afile = self.file
965
 
        while 1:
966
 
            self.filepos = afile.tell()
967
 
            cmd = afile.readuchar()
968
 
            if cmd == _DVI_NOP:
969
 
                pass
970
 
            elif cmd == _DVI_PRE:
971
 
                if afile.readuchar() != _DVI_VERSION: raise DVIError
972
 
                num = afile.readuint32()
973
 
                den = afile.readuint32()
974
 
                self.mag = afile.readuint32()
975
 
 
976
 
                # For the interpretation of the lengths in dvi and tfm files, 
977
 
                # three conversion factors are relevant:
978
 
                # - self.tfmconv: tfm units -> dvi units
979
 
                # - self.pyxconv: dvi units -> (PostScript) points
980
 
                # - self.conv:    dvi units -> pixels
981
 
                self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
982
 
 
983
 
                # calculate conv as described in the DVIType docu using 
984
 
                # a given resolution in dpi
985
 
                self.resolution = 300.0
986
 
                self.conv = (num/254000.0)*(self.resolution/den)
987
 
 
988
 
                # self.pyxconv is the conversion factor from the dvi units
989
 
                # to (PostScript) points. It consists of
990
 
                # - self.mag/1000.0:   magstep scaling
991
 
                # - self.conv:         conversion from dvi units to pixels
992
 
                # - 1/self.resolution: conversion from pixels to inch
993
 
                # - 72               : conversion from inch to points
994
 
                self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
995
 
 
996
 
                comment = afile.read(afile.readuchar())
997
 
                return
998
 
            else:
999
 
                raise DVIError
1000
 
 
1001
 
    def readpage(self, pageid=None):
1002
 
        """ reads a page from the dvi file
1003
 
 
1004
 
        This routine reads a page from the dvi file which is
1005
 
        returned as a canvas. When there is no page left in the
1006
 
        dvifile, None is returned and the file is closed properly."""
1007
 
 
1008
 
        while 1:
1009
 
            self.filepos = self.file.tell()
1010
 
            cmd = self.file.readuchar()
1011
 
            if cmd == _DVI_NOP:
1012
 
                pass
1013
 
            elif cmd == _DVI_BOP:
1014
 
                ispageid = [self.file.readuint32() for i in range(10)]
1015
 
                if pageid is not None and ispageid != pageid:
1016
 
                    raise DVIError("invalid pageid")
1017
 
                if self.debug:
1018
 
                    self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
1019
 
                self.file.readuint32()
1020
 
                break
1021
 
            elif cmd == _DVI_POST:
1022
 
                self.file.close()
1023
 
                return None # nothing left
1024
 
            else:
1025
 
                raise DVIError
1026
 
 
1027
 
        self.actpage = canvas.canvas()
1028
 
        self.actpage.markers = {}
1029
 
        self.pos = [0, 0, 0, 0, 0, 0]
1030
 
 
1031
 
        # currently active output: text instance currently used
1032
 
        self.activetext = None
1033
 
 
1034
 
        while 1:
1035
 
            afile = self.file
1036
 
            self.filepos = afile.tell()
1037
 
            try:
1038
 
                cmd = afile.readuchar()
1039
 
            except struct.error:
1040
 
                # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1041
 
                # so we have to continue with the rest of the dvi file
1042
 
                self._pop_dvistring()
1043
 
                continue
1044
 
            if cmd == _DVI_NOP:
1045
 
                pass
1046
 
            if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1047
 
                self.putchar(cmd)
1048
 
            elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1049
 
                self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
1050
 
            elif cmd == _DVI_SETRULE:
1051
 
                self.putrule(afile.readint32(), afile.readint32())
1052
 
            elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1053
 
                self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
1054
 
            elif cmd == _DVI_PUTRULE:
1055
 
                self.putrule(afile.readint32(), afile.readint32(), 0)
1056
 
            elif cmd == _DVI_EOP:
1057
 
                self.flushtext()
1058
 
                if self.debug:
1059
 
                    self.debugfile.write("%d: eop\n \n" % self.filepos)
1060
 
                return self.actpage
1061
 
            elif cmd == _DVI_PUSH:
1062
 
                self.stack.append(list(self.pos))
1063
 
                if self.debug:
1064
 
                    self.debugfile.write("%s: push\n"
1065
 
                                         "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1066
 
                                         ((self.filepos, len(self.stack)-1) + tuple(self.pos)))
1067
 
            elif cmd == _DVI_POP:
1068
 
                self.flushtext()
1069
 
                self.pos = self.stack.pop()
1070
 
                if self.debug:
1071
 
                    self.debugfile.write("%s: pop\n"
1072
 
                                         "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1073
 
                                         ((self.filepos, len(self.stack)) + tuple(self.pos)))
1074
 
            elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1075
 
                self.flushtext()
1076
 
                dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1077
 
                if self.debug:
1078
 
                    self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1079
 
                                         (self.filepos,
1080
 
                                          cmd - _DVI_RIGHT1234 + 1,
1081
 
                                          dh,
1082
 
                                          self.pos[_POS_H],
1083
 
                                          dh,
1084
 
                                          self.pos[_POS_H]+dh))
1085
 
                self.pos[_POS_H] += dh
1086
 
            elif cmd == _DVI_W0:
1087
 
                self.flushtext()
1088
 
                if self.debug:
1089
 
                    self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1090
 
                                         (self.filepos,
1091
 
                                          self.pos[_POS_W],
1092
 
                                          self.pos[_POS_H],
1093
 
                                          self.pos[_POS_W],
1094
 
                                          self.pos[_POS_H]+self.pos[_POS_W]))
1095
 
                self.pos[_POS_H] += self.pos[_POS_W]
1096
 
            elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1097
 
                self.flushtext()
1098
 
                self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1099
 
                if self.debug:
1100
 
                    self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1101
 
                                         (self.filepos,
1102
 
                                          cmd - _DVI_W1234 + 1,
1103
 
                                          self.pos[_POS_W],
1104
 
                                          self.pos[_POS_H],
1105
 
                                          self.pos[_POS_W],
1106
 
                                          self.pos[_POS_H]+self.pos[_POS_W]))
1107
 
                self.pos[_POS_H] += self.pos[_POS_W]
1108
 
            elif cmd == _DVI_X0:
1109
 
                self.flushtext()
1110
 
                if self.debug:
1111
 
                    self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1112
 
                                         (self.filepos,
1113
 
                                          self.pos[_POS_X],
1114
 
                                          self.pos[_POS_H],
1115
 
                                          self.pos[_POS_X],
1116
 
                                          self.pos[_POS_H]+self.pos[_POS_X]))
1117
 
                self.pos[_POS_H] += self.pos[_POS_X]
1118
 
            elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1119
 
                self.flushtext()
1120
 
                self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1121
 
                if self.debug:
1122
 
                    self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1123
 
                                         (self.filepos,
1124
 
                                          cmd - _DVI_X1234 + 1,
1125
 
                                          self.pos[_POS_X],
1126
 
                                          self.pos[_POS_H],
1127
 
                                          self.pos[_POS_X],
1128
 
                                          self.pos[_POS_H]+self.pos[_POS_X]))
1129
 
                self.pos[_POS_H] += self.pos[_POS_X]
1130
 
            elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1131
 
                self.flushtext()
1132
 
                dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1133
 
                if self.debug:
1134
 
                    self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1135
 
                                         (self.filepos,
1136
 
                                          cmd - _DVI_DOWN1234 + 1,
1137
 
                                          dv,
1138
 
                                          self.pos[_POS_V],
1139
 
                                          dv,
1140
 
                                          self.pos[_POS_V]+dv))
1141
 
                self.pos[_POS_V] += dv
1142
 
            elif cmd == _DVI_Y0:
1143
 
                self.flushtext()
1144
 
                if self.debug:
1145
 
                    self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1146
 
                                         (self.filepos,
1147
 
                                          self.pos[_POS_Y],
1148
 
                                          self.pos[_POS_V],
1149
 
                                          self.pos[_POS_Y],
1150
 
                                          self.pos[_POS_V]+self.pos[_POS_Y]))
1151
 
                self.pos[_POS_V] += self.pos[_POS_Y]
1152
 
            elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1153
 
                self.flushtext()
1154
 
                self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1155
 
                if self.debug:
1156
 
                    self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1157
 
                                         (self.filepos,
1158
 
                                          cmd - _DVI_Y1234 + 1,
1159
 
                                          self.pos[_POS_Y],
1160
 
                                          self.pos[_POS_V],
1161
 
                                          self.pos[_POS_Y],
1162
 
                                          self.pos[_POS_V]+self.pos[_POS_Y]))
1163
 
                self.pos[_POS_V] += self.pos[_POS_Y]
1164
 
            elif cmd == _DVI_Z0:
1165
 
                self.flushtext()
1166
 
                if self.debug:
1167
 
                    self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1168
 
                                         (self.filepos,
1169
 
                                          self.pos[_POS_Z],
1170
 
                                          self.pos[_POS_V],
1171
 
                                          self.pos[_POS_Z],
1172
 
                                          self.pos[_POS_V]+self.pos[_POS_Z]))
1173
 
                self.pos[_POS_V] += self.pos[_POS_Z]
1174
 
            elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1175
 
                self.flushtext()
1176
 
                self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1177
 
                if self.debug:
1178
 
                    self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1179
 
                                         (self.filepos,
1180
 
                                          cmd - _DVI_Z1234 + 1,
1181
 
                                          self.pos[_POS_Z],
1182
 
                                          self.pos[_POS_V],
1183
 
                                          self.pos[_POS_Z],
1184
 
                                          self.pos[_POS_V]+self.pos[_POS_Z]))
1185
 
                self.pos[_POS_V] += self.pos[_POS_Z]
1186
 
            elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1187
 
                self.usefont(cmd - _DVI_FNTNUMMIN, 0)
1188
 
            elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1189
 
                # note that according to the DVI docs, for four byte font numbers,
1190
 
                # the font number is signed. Don't ask why!
1191
 
                fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
1192
 
                self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
1193
 
            elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1194
 
                self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1195
 
            elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1196
 
                if cmd == _DVI_FNTDEF1234:
1197
 
                    num = afile.readuchar()
1198
 
                elif cmd == _DVI_FNTDEF1234+1:
1199
 
                    num = afile.readuint16()
1200
 
                elif cmd == _DVI_FNTDEF1234+2:
1201
 
                    num = afile.readuint24()
1202
 
                elif cmd == _DVI_FNTDEF1234+3:
1203
 
                    # Cool, here we have according to docu a signed int. Why?
1204
 
                    num = afile.readint32()
1205
 
                self.definefont(cmd-_DVI_FNTDEF1234+1,
1206
 
                                num,
1207
 
                                afile.readint32(),
1208
 
                                afile.readint32(),
1209
 
                                afile.readint32(),
1210
 
                                afile.read(afile.readuchar()+afile.readuchar()))
1211
 
            else:
1212
 
                raise DVIError
1213
 
 
1214
 
 
1215
 
##############################################################################
1216
 
# VF file handling
1217
 
##############################################################################
1218
 
 
1219
 
_VF_LONG_CHAR  = 242              # character packet (long version)
1220
 
_VF_FNTDEF1234 = _DVI_FNTDEF1234  # font definition
1221
 
_VF_PRE        = _DVI_PRE         # preamble
1222
 
_VF_POST       = _DVI_POST        # postamble
1223
 
 
1224
 
_VF_ID         = 202              # VF id byte
1225
 
 
1226
 
class VFError(exceptions.Exception): pass
1227
 
 
1228
 
class vffile:
1229
 
    def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1230
 
        self.filename = filename
1231
 
        self.scale = scale
1232
 
        self.tfmconv = tfmconv
1233
 
        self.pyxconv = pyxconv
1234
 
        self.fontmap = fontmap
1235
 
        self.debug = debug
1236
 
        self.fonts = {}            # used fonts
1237
 
        self.widths = {}           # widths of defined chars
1238
 
        self.chardefs = {}         # dvi chunks for defined chars
1239
 
 
1240
 
        afile = binfile(self.filename, "rb")
1241
 
 
1242
 
        cmd = afile.readuchar()
1243
 
        if cmd == _VF_PRE:
1244
 
            if afile.readuchar() != _VF_ID: raise VFError
1245
 
            comment = afile.read(afile.readuchar())
1246
 
            self.cs = afile.readuint32()
1247
 
            self.ds = afile.readuint32()
1248
 
        else:
1249
 
            raise VFError
1250
 
 
1251
 
        while 1:
1252
 
            cmd = afile.readuchar()
1253
 
            if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1254
 
                # font definition
1255
 
                if cmd == _VF_FNTDEF1234:
1256
 
                    num = afile.readuchar()
1257
 
                elif cmd == _VF_FNTDEF1234+1:
1258
 
                    num = afile.readuint16()
1259
 
                elif cmd == _VF_FNTDEF1234+2:
1260
 
                    num = afile.readuint24()
1261
 
                elif cmd == _VF_FNTDEF1234+3:
1262
 
                    num = afile.readint32()
1263
 
                c = afile.readint32()
1264
 
                s = afile.readint32()     # relative scaling used for font (fix_word)
1265
 
                d = afile.readint32()     # design size of font
1266
 
                fontname = afile.read(afile.readuchar()+afile.readuchar())
1267
 
 
1268
 
                # rescaled size of font: s is relative to the scaling
1269
 
                # of the virtual font itself.  Note that realscale has
1270
 
                # to be a fix_word (like s)
1271
 
                # XXX: check rounding
1272
 
                reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1273
 
 
1274
 
                # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1275
 
                #        (fontname, self.scale, self.ds, s, reals)
1276
 
                #        )
1277
 
 
1278
 
                # XXX allow for virtual fonts here too
1279
 
                self.fonts[num] =  font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1280
 
            elif cmd == _VF_LONG_CHAR:
1281
 
                # character packet (long form)
1282
 
                pl = afile.readuint32()   # packet length
1283
 
                cc = afile.readuint32()   # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1284
 
                tfm = afile.readuint24()  # character width
1285
 
                dvi = afile.read(pl)      # dvi code of character
1286
 
                self.widths[cc] = tfm
1287
 
                self.chardefs[cc] = dvi
1288
 
            elif cmd < _VF_LONG_CHAR:
1289
 
                # character packet (short form)
1290
 
                cc = afile.readuchar()    # char code
1291
 
                tfm = afile.readuint24()  # character width
1292
 
                dvi = afile.read(cmd)
1293
 
                self.widths[cc] = tfm
1294
 
                self.chardefs[cc] = dvi
1295
 
            elif cmd == _VF_POST:
1296
 
                break
1297
 
            else:
1298
 
                raise VFError
1299
 
 
1300
 
        afile.close()
1301
 
 
1302
 
    def getfonts(self):
1303
 
        return self.fonts
1304
 
 
1305
 
    def getchar(self, cc):
1306
 
        return self.chardefs[cc]
1307
 
 
1308