2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2005 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import copy
, cStringIO
, exceptions
, re
, struct
, string
, sys
, warnings
, math
26 import unit
, epsfile
, bbox
, canvas
, color
, trafo
, path
, pykpathsea
, pswriter
, pdfwriter
, type1font
31 def __init__(self
, filename
, mode
="r"):
32 self
.file = open(filename
, mode
)
38 return self
.file.tell()
41 return self
.file.eof()
43 def read(self
, bytes
):
44 return self
.file.read(bytes
)
46 def readint(self
, bytes
=4, signed
=0):
50 value
= ord(self
.file.read(1))
51 if first
and signed
and value
> 127:
54 result
= 256 * result
+ value
59 return struct
.unpack(">l", self
.file.read(4))[0]
62 return struct
.unpack(">L", self
.file.read(4))[0]
66 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
70 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
73 return struct
.unpack(">h", self
.file.read(2))[0]
76 return struct
.unpack(">H", self
.file.read(2))[0]
79 return struct
.unpack("b", self
.file.read(1))[0]
82 return struct
.unpack("B", self
.file.read(1))[0]
84 def readstring(self
, bytes
):
86 assert l
<= bytes
-1, "inconsistency in file: string too long"
87 return self
.file.read(bytes
-1)[:l
]
89 class stringbinfile(binfile
):
91 def __init__(self
, s
):
92 self
.file = cStringIO
.StringIO(s
)
97 ##############################################################################
99 ##############################################################################
101 class TFMError(exceptions
.Exception): pass
104 class char_info_word
:
105 def __init__(self
, word
):
106 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
107 self
.height_index
= (word
& 0x00F00000) >> 20
108 self
.depth_index
= (word
& 0x000F0000) >> 16
109 self
.italic_index
= (word
& 0x0000FC00) >> 10
110 self
.tag
= (word
& 0x00000300) >> 8
111 self
.remainder
= (word
& 0x000000FF)
115 def __init__(self
, name
, debug
=0):
116 self
.file = binfile(name
, "rb")
122 self
.lf
= self
.file.readint16()
123 self
.lh
= self
.file.readint16()
124 self
.bc
= self
.file.readint16()
125 self
.ec
= self
.file.readint16()
126 self
.nw
= self
.file.readint16()
127 self
.nh
= self
.file.readint16()
128 self
.nd
= self
.file.readint16()
129 self
.ni
= self
.file.readint16()
130 self
.nl
= self
.file.readint16()
131 self
.nk
= self
.file.readint16()
132 self
.ne
= self
.file.readint16()
133 self
.np
= self
.file.readint16()
135 if not (self
.bc
-1 <= self
.ec
<= 255 and
137 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
138 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
139 raise TFMError
, "error in TFM pre-header"
142 print "lh=%d" % self
.lh
148 self
.checksum
= self
.file.readint32()
149 self
.designsize
= self
.file.readint32()
150 assert self
.designsize
> 0, "invald design size"
152 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
153 self
.charcoding
= self
.file.readstring(40)
155 self
.charcoding
= None
158 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
159 self
.fontfamily
= self
.file.readstring(20)
161 self
.fontfamily
= None
164 print "(FAMILY %s)" % self
.fontfamily
165 print "(CODINGSCHEME %s)" % self
.charcoding
166 print "(DESINGSIZE R %f)" % 16.0*self
.designsize
/16777216L
169 self
.sevenbitsave
= self
.file.readuchar()
170 # ignore the following two bytes
171 self
.file.readint16()
172 facechar
= self
.file.readuchar()
173 # decode ugly face specification into the Knuth suggested string
185 self
.face
= "L" + self
.face
188 self
.face
= "B" + self
.face
191 self
.face
= "M" + self
.face
194 self
.face
= self
.face
[0] + "I" + self
.face
[1]
196 self
.face
= self
.face
[0] + "R" + self
.face
[1]
201 self
.sevenbitsave
= self
.face
= None
204 # just ignore the rest
205 print self
.file.read((self
.lh
-18)*4)
211 self
.char_info
= [None]*(self
.ec
+1)
212 for charcode
in range(self
.bc
, self
.ec
+1):
213 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
214 if self
.char_info
[charcode
].width_index
== 0:
215 # disable character if width_index is zero
216 self
.char_info
[charcode
] = None
222 self
.width
= [None for width_index
in range(self
.nw
)]
223 for width_index
in range(self
.nw
):
224 self
.width
[width_index
] = self
.file.readint32()
230 self
.height
= [None for height_index
in range(self
.nh
)]
231 for height_index
in range(self
.nh
):
232 self
.height
[height_index
] = self
.file.readint32()
238 self
.depth
= [None for depth_index
in range(self
.nd
)]
239 for depth_index
in range(self
.nd
):
240 self
.depth
[depth_index
] = self
.file.readint32()
246 self
.italic
= [None for italic_index
in range(self
.ni
)]
247 for italic_index
in range(self
.ni
):
248 self
.italic
[italic_index
] = self
.file.readint32()
254 # XXX decode to lig_kern_command
256 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
257 for lig_kern_index
in range(self
.nl
):
258 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
264 self
.kern
= [None for kern_index
in range(self
.nk
)]
265 for kern_index
in range(self
.nk
):
266 self
.kern
[kern_index
] = self
.file.readint32()
272 # XXX decode to extensible_recipe
274 self
.exten
= [None for exten_index
in range(self
.ne
)]
275 for exten_index
in range(self
.ne
):
276 self
.exten
[exten_index
] = self
.file.readint32()
284 self
.param
= [None for param_index
in range(self
.np
)]
285 for param_index
in range(self
.np
):
286 self
.param
[param_index
] = self
.file.readint32()
292 ##############################################################################
294 ##############################################################################
297 # PostScript font selection and output primitives
304 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
306 def __init__(self
, s
):
307 """ construct font mapping from line s of font mapping file """
308 self
.texname
= self
.basepsname
= self
.fontfile
= None
311 self
.encodingfile
= None
313 # supported postscript fragments occuring in psfonts.map
314 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
318 match
= self
.tokenpattern
.match(s
)
320 if match
.groups()[0] is not None:
321 tokens
.append('"%s"' % match
.groups()[0])
323 tokens
.append(match
.groups()[2])
326 raise RuntimeError("wrong syntax")
329 if token
.startswith("<"):
330 if token
.startswith("<<"):
331 # XXX: support non-partial download here
332 self
.fontfile
= token
[2:]
333 elif token
.startswith("<["):
334 self
.encodingfile
= token
[2:]
335 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
336 self
.fontfile
= token
[1:]
337 elif token
.endswith(".enc"):
338 self
.encodingfile
= token
[1:]
340 raise RuntimeError("wrong syntax")
341 elif token
.startswith('"'):
342 pscode
= token
[1:-1].split()
343 # parse standard postscript code fragments
346 arg
, cmd
= pscode
[:2]
348 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode
)
350 if cmd
== "ReEncodeFont":
351 self
.reencodefont
= arg
352 elif cmd
== "ExtendFont":
353 self
.extendfont
= arg
354 elif cmd
== "SlantFont":
357 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
359 if self
.texname
is None:
362 self
.basepsname
= token
363 if self
.basepsname
is None:
364 self
.basepsname
= self
.texname
367 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
368 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
372 def readfontmap(filenames
):
373 """ read font map from filename (without path) """
375 for filename
in filenames
:
376 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_fontmap_format
)
377 # try also the oft-used registration as dvips config file
379 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
381 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
382 mapfile
= open(mappath
, "r")
384 for line
in mapfile
.readlines():
387 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
389 fm
= fontmapping(line
)
390 except RuntimeError, e
:
391 warnings
.warn("Ignoring line %i in mapping file '%s': %s" % (lineno
, filename
, e
))
393 fontmap
[fm
.texname
] = fm
400 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
402 self
.q
= q
# desired size of font (fix_word) in TeX points
403 self
.d
= d
# design size of font (fix_word) in TeX points
404 self
.tfmconv
= tfmconv
# conversion factor from tfm units to dvi units
405 self
.pyxconv
= pyxconv
# conversion factor from dvi units to PostScript points
406 self
.fontmap
= fontmap
407 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
409 raise TFMError("cannot find %s.tfm" % self
.name
)
410 self
.tfmfile
= tfmfile(tfmpath
, debug
)
412 # We only check for equality of font checksums if none of them
413 # is zero. The case c == 0 happend in some VF files and
414 # according to the VFtoVP documentation, paragraph 40, a check
415 # is only performed if tfmfile.checksum > 0. Anyhow, being
416 # more generous here seems to be reasonable
417 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!= 0:
418 raise DVIError("check sums do not agree: %d vs. %d" %
419 (self
.tfmfile
.checksum
, c
))
421 # Check whether the given design size matches the one defined in the tfm file
422 if abs(self
.tfmfile
.designsize
- d
) > 2:
423 raise DVIError("design sizes do not agree: %d vs. %d" % (self
.tfmfile
.designsize
, d
))
424 #if q < 0 or q > 134217728:
425 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
426 if d
< 0 or d
> 134217728:
427 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
431 # The following code is a very crude way to obtain the information
432 # required for the PDF font descritor. (TODO: The correct way would
433 # be to read the information from the AFM file.)
435 -self
.getdepth_ds(ord("y")),
436 self
.getwidth_ds(ord("W")),
437 self
.getheight_ds(ord("H")))
438 self
.italicangle
= -180/math
.pi
*math
.atan(self
.tfmfile
.param
[0]/65536.0)
439 self
.ascent
= self
.fontbbox
[3]
440 self
.descent
= self
.fontbbox
[1]
441 self
.capheight
= self
.getheight_ds(ord("h"))
442 self
.vstem
= self
.getwidth_ds(ord("."))/3
445 return "font %s designed at %g TeX pts used at %g TeX pts" % (self
.name
,
446 16.0*self
.d
/16777216L,
447 16.0*self
.q
/16777216L)
450 def getsize_pt(self
):
451 """ return size of font in (PS) points """
452 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
453 # to the corresponding float. Furthermore, we have to convert from TeX
454 # points to points, hence the factor 72/72.27.
455 return 16L*self
.q
/16777216L*72/72.27
457 def _convert_tfm_to_dvi(self
, length
):
458 # doing the integer math with long integers will lead to different roundings
459 # return 16*length*int(round(self.q*self.tfmconv))/16777216
461 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
462 # z = int(round(self.q*self.tfmconv))
463 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
464 # assert b0 == 0 or b0 == 255
466 # while z >= 8388608:
470 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
472 # result = result - (z << (8-shift))
474 # however, we can simplify this using a single long integer multiplication,
475 # but take into account the transformation of z
476 z
= int(round(self
.q
*self
.tfmconv
))
477 assert -16777216 <= length
< 16777216 # -(1 << 24) <= length < (1 << 24)
478 assert z
< 134217728 # 1 << 27
480 while z
>= 8388608: # 1 << 23
483 # length*z is a long integer, but the result will be a regular integer
484 return int(length
*long(z
) >> shift
)
486 def _convert_tfm_to_ds(self
, length
):
487 return (16*long(round(length
*self
.q
*self
.tfmconv
))/16777216) * self
.pyxconv
* 1000 / self
.getsize_pt()
489 def _convert_tfm_to_pt(self
, length
):
490 return (16*long(round(length
*self
.q
*self
.tfmconv
))/16777216) * self
.pyxconv
492 # routines returning lengths as integers in dvi units
494 def getwidth_dvi(self
, charcode
):
495 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
497 def getheight_dvi(self
, charcode
):
498 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
500 def getdepth_dvi(self
, charcode
):
501 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
503 def getitalic_dvi(self
, charcode
):
504 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
506 # routines returning lengths as integers in design size (AFM) units
508 def getwidth_ds(self
, charcode
):
509 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
511 def getheight_ds(self
, charcode
):
512 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
514 def getdepth_ds(self
, charcode
):
515 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
517 def getitalic_ds(self
, charcode
):
518 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
520 # routines returning lengths as floats in PostScript points
522 def getwidth_pt(self
, charcode
):
523 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
525 def getheight_pt(self
, charcode
):
526 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
528 def getdepth_pt(self
, charcode
):
529 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
531 def getitalic_pt(self
, charcode
):
532 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
535 class virtualfont(font
):
536 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
537 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
)
538 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
539 if fontpath
is None or not len(fontpath
):
541 self
.vffile
= vffile(fontpath
, self
.scale
, tfmconv
, pyxconv
, fontmap
, debug
> 1)
544 """ return fonts used in virtual font itself """
545 return self
.vffile
.getfonts()
547 def getchar(self
, cc
):
548 """ return dvi chunk corresponding to char code cc """
549 return self
.vffile
.getchar(cc
)
552 ##############################################################################
554 ##############################################################################
556 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
557 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
558 _DVI_SET1234
= 128 # typeset a character and move right
559 _DVI_SETRULE
= 132 # typeset a rule and move right
560 _DVI_PUT1234
= 133 # typeset a character
561 _DVI_PUTRULE
= 137 # typeset a rule
562 _DVI_NOP
= 138 # no operation
563 _DVI_BOP
= 139 # beginning of page
564 _DVI_EOP
= 140 # ending of page
565 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
566 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
567 _DVI_RIGHT1234
= 143 # move right
568 _DVI_W0
= 147 # move right by w
569 _DVI_W1234
= 148 # move right and set w
570 _DVI_X0
= 152 # move right by x
571 _DVI_X1234
= 153 # move right and set x
572 _DVI_DOWN1234
= 157 # move down
573 _DVI_Y0
= 161 # move down by y
574 _DVI_Y1234
= 162 # move down and set y
575 _DVI_Z0
= 166 # move down by z
576 _DVI_Z1234
= 167 # move down and set z
577 _DVI_FNTNUMMIN
= 171 # set current font (range min)
578 _DVI_FNTNUMMAX
= 234 # set current font (range max)
579 _DVI_FNT1234
= 235 # set current font
580 _DVI_SPECIAL1234
= 239 # special (dvi extention)
581 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
582 _DVI_PRE
= 247 # preamble
583 _DVI_POST
= 248 # postamble beginning
584 _DVI_POSTPOST
= 249 # postamble ending
586 _DVI_VERSION
= 2 # dvi version
588 # position variable indices
600 _READ_POST
= 4 # XXX not used
601 _READ_POSTPOST
= 5 # XXX not used
605 class DVIError(exceptions
.Exception): pass
607 # save and restore colors
609 class _savecolor(canvas
.canvasitem
):
610 def outputPS(self
, file, writer
, context
):
611 file.write("currentcolor currentcolorspace\n")
613 def outputPDF(self
, file, writer
, context
):
617 class _restorecolor(canvas
.canvasitem
):
618 def outputPS(self
, file, writer
, context
):
619 file.write("setcolorspace setcolor\n")
621 def outputPDF(self
, file, writer
, context
):
624 class _savetrafo(canvas
.canvasitem
):
625 def outputPS(self
, file, writer
, context
):
626 file.write("matrix currentmatrix\n")
628 def outputPDF(self
, file, writer
, context
):
632 class _restoretrafo(canvas
.canvasitem
):
633 def outputPS(self
, file, writer
, context
):
634 file.write("setmatrix\n")
636 def outputPDF(self
, file, writer
, context
):
642 def __init__(self
, filename
, fontmap
, debug
=0, debugfile
=sys
.stdout
):
643 """ opens the dvi file and reads the preamble """
644 self
.filename
= filename
645 self
.fontmap
= fontmap
647 self
.debugfile
= debugfile
651 self
.activefont
= None
653 # stack of fonts and fontscale currently used (used for VFs)
657 # pointer to currently active page
660 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
663 self
.file = binfile(self
.filename
, "rb")
665 # currently read byte in file (for debugging output)
673 """ finish currently active text object """
674 if self
.debug
and self
.activetext
:
675 self
.debugfile
.write("[%s]\n" % "".join([chr(char
) for char
in self
.activetext
.chars
]))
676 self
.activetext
= None
678 def putrule(self
, height
, width
, advancepos
=1):
680 x1
= self
.pos
[_POS_H
] * self
.pyxconv
681 y1
= -self
.pos
[_POS_V
] * self
.pyxconv
682 w
= width
* self
.pyxconv
683 h
= height
* self
.pyxconv
685 if height
> 0 and width
> 0:
687 self
.debugfile
.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
688 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
689 self
.actpage
.fill(path
.rect_pt(x1
, y1
, w
, h
))
692 self
.debugfile
.write("%d: %srule height %d, width %d (invisible)\n" %
693 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
697 self
.debugfile
.write(" h:=%d+%d=%d, hh:=???\n" %
698 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
))
699 self
.pos
[_POS_H
] += width
701 def putchar(self
, char
, advancepos
=1, id1234
=0):
702 dx
= advancepos
and self
.activefont
.getwidth_dvi(char
) or 0
705 self
.debugfile
.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
707 advancepos
and "set" or "put",
708 id1234
and "%i " % id1234
or "char",
710 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
))
712 if isinstance(self
.activefont
, virtualfont
):
713 # virtual font handling
714 afterpos
= list(self
.pos
)
715 afterpos
[_POS_H
] += dx
716 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
717 self
.activefont
.getsize_pt())
719 if self
.activetext
is None:
720 fontmapinfo
= self
.fontmap
.get(self
.activefont
.name
)
722 encodingname
= fontmapinfo
.reencodefont
723 if encodingname
is not None:
724 encodingfilename
= pykpathsea
.find_file(fontmapinfo
.encodingfile
, pykpathsea
.kpse_tex_ps_header_format
)
725 if not encodingfilename
:
726 raise RuntimeError("cannot find font encoding file %s" % fontmapinfo
.encodingfile
)
727 fontencoding
= type1font
.encoding(encodingname
, encodingfilename
)
731 fontbasefontname
= fontmapinfo
.basepsname
732 if fontmapinfo
.fontfile
is not None:
733 fontfilename
= pykpathsea
.find_file(fontmapinfo
.fontfile
, pykpathsea
.kpse_type1_format
)
735 raise RuntimeError("cannot find type 1 font %s" % fontmapinfo
.fontfile
)
739 # XXX we currently misuse use self.activefont as metric
740 font
= type1font
.font(fontbasefontname
, fontfilename
, fontencoding
, self
.activefont
)
742 self
.activetext
= type1font
.text_pt(self
.pos
[_POS_H
] * self
.pyxconv
, -self
.pos
[_POS_V
] * self
.pyxconv
, font
)
743 self
.actpage
.insert(self
.activetext
)
744 self
.activetext
.addchar(char
)
745 self
.pos
[_POS_H
] += dx
750 def usefont(self
, fontnum
, id1234
=0):
752 self
.activefont
= self
.fonts
[fontnum
]
754 self
.debugfile
.write("%d: fnt%s%i current font is %s\n" %
756 id1234
and "%i " % id1234
or "num",
758 self
.fonts
[fontnum
].name
))
761 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
762 # cmdnr: type of fontdef command (only used for debugging output)
764 # q: scaling factor (fix_word)
765 # Note that q is actually s in large parts of the documentation.
766 # d: design size (fix_word)
769 afont
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
770 except (TypeError, RuntimeError):
771 afont
= font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
773 self
.fonts
[num
] = afont
776 self
.debugfile
.write("%d: fntdef%d %i: %s\n" % (self
.filepos
, cmdnr
, num
, fontname
))
778 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
780 # scalestring = scale!=1000 and " scaled %d" % scale or ""
781 # print ("Font %i: %s%s---loaded at size %d DVI units" %
782 # (num, fontname, scalestring, q))
784 # print " (this font is magnified %d%%)" % round(scale/10)
786 def special(self
, s
):
787 x
= self
.pos
[_POS_H
] * self
.pyxconv
788 y
= -self
.pos
[_POS_V
] * self
.pyxconv
790 self
.debugfile
.write("%d: xxx '%s'\n" % (self
.filepos
, s
))
791 if not s
.startswith("PyX:"):
792 warnings
.warn("ignoring special '%s'" % s
)
795 # it is in general not safe to continue using the currently active font because
796 # the specials may involve some gsave/grestore operations
799 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
800 if command
== "color_begin":
801 if args
[0] == "cmyk":
802 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
803 elif args
[0] == "gray":
804 c
= color
.gray(float(args
[1]))
805 elif args
[0] == "hsb":
806 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
807 elif args
[0] == "rgb":
808 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
809 elif args
[0] == "RGB":
810 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
811 elif args
[0] == "texnamed":
813 c
= getattr(color
.cmyk
, args
[1])
814 except AttributeError:
815 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
817 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
818 self
.actpage
.insert(_savecolor())
819 self
.actpage
.insert(c
)
820 elif command
== "color_end":
821 self
.actpage
.insert(_restorecolor())
822 elif command
== "rotate_begin":
823 self
.actpage
.insert(_savetrafo())
824 self
.actpage
.insert(trafo
.rotate_pt(float(args
[0]), x
, y
))
825 elif command
== "rotate_end":
826 self
.actpage
.insert(_restoretrafo())
827 elif command
== "scale_begin":
828 self
.actpage
.insert(_savetrafo())
829 self
.actpage
.insert(trafo
.scale_pt(float(args
[0]), float(args
[1]), x
, y
))
830 elif command
== "scale_end":
831 self
.actpage
.insert(_restoretrafo())
832 elif command
== "epsinclude":
836 name
, value
= arg
.split("=")
837 argdict
[name
] = value
839 # construct kwargs for epsfile constructor
841 epskwargs
["filename"] = argdict
["file"]
842 epskwargs
["bbox"] = bbox
.bbox_pt(float(argdict
["llx"]), float(argdict
["lly"]),
843 float(argdict
["urx"]), float(argdict
["ury"]))
844 if argdict
.has_key("width"):
845 epskwargs
["width"] = float(argdict
["width"]) * unit
.t_pt
846 if argdict
.has_key("height"):
847 epskwargs
["height"] = float(argdict
["height"]) * unit
.t_pt
848 if argdict
.has_key("clip"):
849 epskwargs
["clip"] = int(argdict
["clip"])
850 self
.actpage
.insert(epsfile
.epsfile(x
* unit
.t_pt
, y
* unit
.t_pt
, **epskwargs
))
851 elif command
== "marker":
853 raise RuntimeError("marker contains spaces")
855 if c
not in string
.digits
+ string
.letters
+ "@":
856 raise RuntimeError("marker contains invalid characters")
857 if self
.actpage
.markers
.has_key(args
[0]):
858 raise RuntimeError("marker name occurred several times")
859 self
.actpage
.markers
[args
[0]] = x
* unit
.t_pt
, y
* unit
.t_pt
861 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
863 # routines for pushing and popping different dvi chunks on the reader
865 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
866 """ push dvi string with defined fonts on top of reader
867 stack. Every positions gets scaled relatively by the factor
868 scale. After the interpreting of the dvi chunk has been finished,
869 continue with self.pos=afterpos. The designsize of the virtual
870 font is passed as a fix_word
875 # self.debugfile.write("executing new dvi chunk\n")
876 self
.debugstack
.append(self
.debug
)
879 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.pyxconv
, self
.tfmconv
))
881 # units in vf files are relative to the size of the font and given as fix_words
882 # which can be converted to floats by diving by 2**20
883 oldpyxconv
= self
.pyxconv
884 self
.pyxconv
= fontsize
/2**20
885 rescale
= self
.pyxconv
/oldpyxconv
887 self
.file = stringbinfile(dvi
)
892 # rescale self.pos in order to be consistent with the new scaling
893 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
895 # since tfmconv converts from tfm units to dvi units, rescale it as well
896 self
.tfmconv
/= rescale
900 def _pop_dvistring(self
):
903 # self.debugfile.write("finished executing dvi chunk\n")
904 self
.debug
= self
.debugstack
.pop()
907 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.pyxconv
, self
.tfmconv
= self
.statestack
.pop()
909 # routines corresponding to the different reader states of the dvi maschine
914 self
.filepos
= afile
.tell()
915 cmd
= afile
.readuchar()
918 elif cmd
== _DVI_PRE
:
919 if afile
.readuchar() != _DVI_VERSION
: raise DVIError
920 num
= afile
.readuint32()
921 den
= afile
.readuint32()
922 self
.mag
= afile
.readuint32()
924 # For the interpretation of the lengths in dvi and tfm files,
925 # three conversion factors are relevant:
926 # - self.tfmconv: tfm units -> dvi units
927 # - self.pyxconv: dvi units -> (PostScript) points
928 # - self.conv: dvi units -> pixels
929 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
931 # calculate conv as described in the DVIType docu using
932 # a given resolution in dpi
933 self
.resolution
= 300.0
934 self
.conv
= (num
/254000.0)*(self
.resolution
/den
)
936 # self.pyxconv is the conversion factor from the dvi units
937 # to (PostScript) points. It consists of
938 # - self.mag/1000.0: magstep scaling
939 # - self.conv: conversion from dvi units to pixels
940 # - 1/self.resolution: conversion from pixels to inch
941 # - 72 : conversion from inch to points
942 self
.pyxconv
= self
.mag
/1000.0*self
.conv
/self
.resolution
*72
944 comment
= afile
.read(afile
.readuchar())
949 def readpage(self
, pageid
=None):
950 """ reads a page from the dvi file
952 This routine reads a page from the dvi file which is
953 returned as a canvas. When there is no page left in the
954 dvifile, None is returned and the file is closed properly."""
957 self
.filepos
= self
.file.tell()
958 cmd
= self
.file.readuchar()
961 elif cmd
== _DVI_BOP
:
962 ispageid
= [self
.file.readuint32() for i
in range(10)]
963 if pageid
is not None and ispageid
!= pageid
:
964 raise DVIError("invalid pageid")
966 self
.debugfile
.write("%d: beginning of page %i\n" % (self
.filepos
, ispageid
[0]))
967 self
.file.readuint32()
969 elif cmd
== _DVI_POST
:
971 return None # nothing left
975 self
.actpage
= canvas
.canvas()
976 self
.actpage
.markers
= {}
977 self
.pos
= [0, 0, 0, 0, 0, 0]
979 # currently active output: text instance currently used
980 self
.activetext
= None
984 self
.filepos
= afile
.tell()
986 cmd
= afile
.readuchar()
988 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
989 # so we have to continue with the rest of the dvi file
990 self
._pop
_dvistring
()
994 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
996 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
997 self
.putchar(afile
.readint(cmd
- _DVI_SET1234
+ 1), id1234
=cmd
-_DVI_SET1234
+1)
998 elif cmd
== _DVI_SETRULE
:
999 self
.putrule(afile
.readint32(), afile
.readint32())
1000 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1001 self
.putchar(afile
.readint(cmd
- _DVI_PUT1234
+ 1), advancepos
=0, id1234
=cmd
-_DVI_SET1234
+1)
1002 elif cmd
== _DVI_PUTRULE
:
1003 self
.putrule(afile
.readint32(), afile
.readint32(), 0)
1004 elif cmd
== _DVI_EOP
:
1007 self
.debugfile
.write("%d: eop\n \n" % self
.filepos
)
1009 elif cmd
== _DVI_PUSH
:
1010 self
.stack
.append(list(self
.pos
))
1012 self
.debugfile
.write("%s: push\n"
1013 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1014 ((self
.filepos
, len(self
.stack
)-1) + tuple(self
.pos
)))
1015 elif cmd
== _DVI_POP
:
1017 self
.pos
= self
.stack
.pop()
1019 self
.debugfile
.write("%s: pop\n"
1020 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1021 ((self
.filepos
, len(self
.stack
)) + tuple(self
.pos
)))
1022 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1024 dh
= afile
.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1026 self
.debugfile
.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1028 cmd
- _DVI_RIGHT1234
+ 1,
1032 self
.pos
[_POS_H
]+dh
))
1033 self
.pos
[_POS_H
] += dh
1034 elif cmd
== _DVI_W0
:
1037 self
.debugfile
.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1042 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1043 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1044 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1046 self
.pos
[_POS_W
] = afile
.readint(cmd
- _DVI_W1234
+ 1, 1)
1048 self
.debugfile
.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1050 cmd
- _DVI_W1234
+ 1,
1054 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1055 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1056 elif cmd
== _DVI_X0
:
1059 self
.debugfile
.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1064 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1065 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1066 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1068 self
.pos
[_POS_X
] = afile
.readint(cmd
- _DVI_X1234
+ 1, 1)
1070 self
.debugfile
.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1072 cmd
- _DVI_X1234
+ 1,
1076 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1077 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1078 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1080 dv
= afile
.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1082 self
.debugfile
.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1084 cmd
- _DVI_DOWN1234
+ 1,
1088 self
.pos
[_POS_V
]+dv
))
1089 self
.pos
[_POS_V
] += dv
1090 elif cmd
== _DVI_Y0
:
1093 self
.debugfile
.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1098 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1099 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1100 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1102 self
.pos
[_POS_Y
] = afile
.readint(cmd
- _DVI_Y1234
+ 1, 1)
1104 self
.debugfile
.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1106 cmd
- _DVI_Y1234
+ 1,
1110 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1111 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1112 elif cmd
== _DVI_Z0
:
1115 self
.debugfile
.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1120 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1121 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1122 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1124 self
.pos
[_POS_Z
] = afile
.readint(cmd
- _DVI_Z1234
+ 1, 1)
1126 self
.debugfile
.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1128 cmd
- _DVI_Z1234
+ 1,
1132 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1133 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1134 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1135 self
.usefont(cmd
- _DVI_FNTNUMMIN
, 0)
1136 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1137 # note that according to the DVI docs, for four byte font numbers,
1138 # the font number is signed. Don't ask why!
1139 fntnum
= afile
.readint(cmd
- _DVI_FNT1234
+ 1, cmd
== _DVI_FNT1234
+ 3)
1140 self
.usefont(fntnum
, id1234
=cmd
-_DVI_FNT1234
+1)
1141 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1142 self
.special(afile
.read(afile
.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1143 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1144 if cmd
== _DVI_FNTDEF1234
:
1145 num
= afile
.readuchar()
1146 elif cmd
== _DVI_FNTDEF1234
+1:
1147 num
= afile
.readuint16()
1148 elif cmd
== _DVI_FNTDEF1234
+2:
1149 num
= afile
.readuint24()
1150 elif cmd
== _DVI_FNTDEF1234
+3:
1151 # Cool, here we have according to docu a signed int. Why?
1152 num
= afile
.readint32()
1153 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1158 afile
.read(afile
.readuchar()+afile
.readuchar()))
1163 ##############################################################################
1165 ##############################################################################
1167 _VF_LONG_CHAR
= 242 # character packet (long version)
1168 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1169 _VF_PRE
= _DVI_PRE
# preamble
1170 _VF_POST
= _DVI_POST
# postamble
1172 _VF_ID
= 202 # VF id byte
1174 class VFError(exceptions
.Exception): pass
1177 def __init__(self
, filename
, scale
, tfmconv
, pyxconv
, fontmap
, debug
=0):
1178 self
.filename
= filename
1180 self
.tfmconv
= tfmconv
1181 self
.pyxconv
= pyxconv
1182 self
.fontmap
= fontmap
1184 self
.fonts
= {} # used fonts
1185 self
.widths
= {} # widths of defined chars
1186 self
.chardefs
= {} # dvi chunks for defined chars
1188 afile
= binfile(self
.filename
, "rb")
1190 cmd
= afile
.readuchar()
1192 if afile
.readuchar() != _VF_ID
: raise VFError
1193 comment
= afile
.read(afile
.readuchar())
1194 self
.cs
= afile
.readuint32()
1195 self
.ds
= afile
.readuint32()
1200 cmd
= afile
.readuchar()
1201 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1203 if cmd
== _VF_FNTDEF1234
:
1204 num
= afile
.readuchar()
1205 elif cmd
== _VF_FNTDEF1234
+1:
1206 num
= afile
.readuint16()
1207 elif cmd
== _VF_FNTDEF1234
+2:
1208 num
= afile
.readuint24()
1209 elif cmd
== _VF_FNTDEF1234
+3:
1210 num
= afile
.readint32()
1211 c
= afile
.readint32()
1212 s
= afile
.readint32() # relative scaling used for font (fix_word)
1213 d
= afile
.readint32() # design size of font
1214 fontname
= afile
.read(afile
.readuchar()+afile
.readuchar())
1216 # rescaled size of font: s is relative to the scaling
1217 # of the virtual font itself. Note that realscale has
1218 # to be a fix_word (like s)
1219 # XXX: check rounding
1220 reals
= int(round(self
.scale
* (16*self
.ds
/16777216L) * s
))
1222 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1223 # (fontname, self.scale, self.ds, s, reals)
1226 # XXX allow for virtual fonts here too
1227 self
.fonts
[num
] = font(fontname
, c
, reals
, d
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
1228 elif cmd
== _VF_LONG_CHAR
:
1229 # character packet (long form)
1230 pl
= afile
.readuint32() # packet length
1231 cc
= afile
.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1232 tfm
= afile
.readuint24() # character width
1233 dvi
= afile
.read(pl
) # dvi code of character
1234 self
.widths
[cc
] = tfm
1235 self
.chardefs
[cc
] = dvi
1236 elif cmd
< _VF_LONG_CHAR
:
1237 # character packet (short form)
1238 cc
= afile
.readuchar() # char code
1239 tfm
= afile
.readuint24() # character width
1240 dvi
= afile
.read(cmd
)
1241 self
.widths
[cc
] = tfm
1242 self
.chardefs
[cc
] = dvi
1243 elif cmd
== _VF_POST
:
1253 def getchar(self
, cc
):
1254 return self
.chardefs
[cc
]