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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import cStringIO
, exceptions
, re
, struct
, string
, sys
, warnings
, math
26 import unit
, epsfile
, bbox
, canvas
, color
, trafo
, path
, pykpathsea
, 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
)
435 # The following code is a very crude way to obtain the information
436 # required for the PDF font descritor. (TODO: The correct way would
437 # be to read the information from the AFM file.)
438 fontinfo
= fontinfo()
439 fontinfo
.fontbbox
= (0,
440 -self
.getdepth_ds(ord("y")),
441 self
.getwidth_ds(ord("W")),
442 self
.getheight_ds(ord("H")))
443 fontinfo
.italicangle
= -180/math
.pi
*math
.atan(self
.tfmfile
.param
[0]/65536.0)
444 fontinfo
.ascent
= fontinfo
.fontbbox
[3]
445 fontinfo
.descent
= fontinfo
.fontbbox
[1]
446 fontinfo
.capheight
= self
.getheight_ds(ord("h"))
447 fontinfo
.vstem
= self
.getwidth_ds(ord("."))/3
451 return "font %s designed at %g TeX pts used at %g TeX pts" % (self
.name
,
452 16.0*self
.d
/16777216L,
453 16.0*self
.q
/16777216L)
456 def getsize_pt(self
):
457 """ return size of font in (PS) points """
458 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
459 # to the corresponding float. Furthermore, we have to convert from TeX
460 # points to points, hence the factor 72/72.27.
461 return 16L*self
.q
/16777216L*72/72.27
463 def _convert_tfm_to_dvi(self
, length
):
464 # doing the integer math with long integers will lead to different roundings
465 # return 16*length*int(round(self.q*self.tfmconv))/16777216
467 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
468 # z = int(round(self.q*self.tfmconv))
469 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
470 # assert b0 == 0 or b0 == 255
472 # while z >= 8388608:
476 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
478 # result = result - (z << (8-shift))
480 # however, we can simplify this using a single long integer multiplication,
481 # but take into account the transformation of z
482 z
= int(round(self
.q
*self
.tfmconv
))
483 assert -16777216 <= length
< 16777216 # -(1 << 24) <= length < (1 << 24)
484 assert z
< 134217728 # 1 << 27
486 while z
>= 8388608: # 1 << 23
489 # length*z is a long integer, but the result will be a regular integer
490 return int(length
*long(z
) >> shift
)
492 def _convert_tfm_to_ds(self
, length
):
493 return (16*long(round(length
*float(self
.q
)*self
.tfmconv
))/16777216) * self
.pyxconv
* 1000 / self
.getsize_pt()
495 def _convert_tfm_to_pt(self
, length
):
496 return (16*long(round(length
*float(self
.q
)*self
.tfmconv
))/16777216) * self
.pyxconv
498 # routines returning lengths as integers in dvi units
500 def getwidth_dvi(self
, charcode
):
501 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
503 def getheight_dvi(self
, charcode
):
504 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
506 def getdepth_dvi(self
, charcode
):
507 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
509 def getitalic_dvi(self
, charcode
):
510 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
512 # routines returning lengths as integers in design size (AFM) units
514 def getwidth_ds(self
, charcode
):
515 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
517 def getheight_ds(self
, charcode
):
518 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
520 def getdepth_ds(self
, charcode
):
521 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
523 def getitalic_ds(self
, charcode
):
524 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
526 # routines returning lengths as floats in PostScript points
528 def getwidth_pt(self
, charcode
):
529 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
531 def getheight_pt(self
, charcode
):
532 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
534 def getdepth_pt(self
, charcode
):
535 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
537 def getitalic_pt(self
, charcode
):
538 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
541 class virtualfont(font
):
542 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
543 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
544 if fontpath
is None or not len(fontpath
):
546 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
)
547 self
.vffile
= vffile(fontpath
, self
.scale
, tfmconv
, pyxconv
, fontmap
, debug
> 1)
550 """ return fonts used in virtual font itself """
551 return self
.vffile
.getfonts()
553 def getchar(self
, cc
):
554 """ return dvi chunk corresponding to char code cc """
555 return self
.vffile
.getchar(cc
)
558 ##############################################################################
560 ##############################################################################
562 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
563 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
564 _DVI_SET1234
= 128 # typeset a character and move right
565 _DVI_SETRULE
= 132 # typeset a rule and move right
566 _DVI_PUT1234
= 133 # typeset a character
567 _DVI_PUTRULE
= 137 # typeset a rule
568 _DVI_NOP
= 138 # no operation
569 _DVI_BOP
= 139 # beginning of page
570 _DVI_EOP
= 140 # ending of page
571 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
572 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
573 _DVI_RIGHT1234
= 143 # move right
574 _DVI_W0
= 147 # move right by w
575 _DVI_W1234
= 148 # move right and set w
576 _DVI_X0
= 152 # move right by x
577 _DVI_X1234
= 153 # move right and set x
578 _DVI_DOWN1234
= 157 # move down
579 _DVI_Y0
= 161 # move down by y
580 _DVI_Y1234
= 162 # move down and set y
581 _DVI_Z0
= 166 # move down by z
582 _DVI_Z1234
= 167 # move down and set z
583 _DVI_FNTNUMMIN
= 171 # set current font (range min)
584 _DVI_FNTNUMMAX
= 234 # set current font (range max)
585 _DVI_FNT1234
= 235 # set current font
586 _DVI_SPECIAL1234
= 239 # special (dvi extention)
587 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
588 _DVI_PRE
= 247 # preamble
589 _DVI_POST
= 248 # postamble beginning
590 _DVI_POSTPOST
= 249 # postamble ending
592 _DVI_VERSION
= 2 # dvi version
594 # position variable indices
606 _READ_POST
= 4 # XXX not used
607 _READ_POSTPOST
= 5 # XXX not used
611 class DVIError(exceptions
.Exception): pass
613 # save and restore colors
615 class _savecolor(canvas
.canvasitem
):
616 def outputPS(self
, file, writer
, context
):
617 file.write("currentcolor currentcolorspace\n")
619 def outputPDF(self
, file, writer
, context
):
623 class _restorecolor(canvas
.canvasitem
):
624 def outputPS(self
, file, writer
, context
):
625 file.write("setcolorspace setcolor\n")
627 def outputPDF(self
, file, writer
, context
):
630 class _savetrafo(canvas
.canvasitem
):
631 def outputPS(self
, file, writer
, context
):
632 file.write("matrix currentmatrix\n")
634 def outputPDF(self
, file, writer
, context
):
638 class _restoretrafo(canvas
.canvasitem
):
639 def outputPS(self
, file, writer
, context
):
640 file.write("setmatrix\n")
642 def outputPDF(self
, file, writer
, context
):
648 def __init__(self
, filename
, fontmap
, debug
=0, debugfile
=sys
.stdout
):
649 """ opens the dvi file and reads the preamble """
650 self
.filename
= filename
651 self
.fontmap
= fontmap
653 self
.debugfile
= debugfile
657 self
.activefont
= None
659 # stack of fonts and fontscale currently used (used for VFs)
663 # pointer to currently active page
666 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
669 self
.file = binfile(self
.filename
, "rb")
671 # currently read byte in file (for debugging output)
679 """ finish currently active text object """
680 if self
.debug
and self
.activetext
:
681 self
.debugfile
.write("[%s]\n" % "".join([chr(char
) for char
in self
.activetext
.chars
]))
682 self
.activetext
= None
684 def putrule(self
, height
, width
, advancepos
=1):
686 x1
= self
.pos
[_POS_H
] * self
.pyxconv
687 y1
= -self
.pos
[_POS_V
] * self
.pyxconv
688 w
= width
* self
.pyxconv
689 h
= height
* self
.pyxconv
691 if height
> 0 and width
> 0:
693 self
.debugfile
.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
694 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
695 self
.actpage
.fill(path
.rect_pt(x1
, y1
, w
, h
))
698 self
.debugfile
.write("%d: %srule height %d, width %d (invisible)\n" %
699 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
703 self
.debugfile
.write(" h:=%d+%d=%d, hh:=???\n" %
704 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
))
705 self
.pos
[_POS_H
] += width
707 def putchar(self
, char
, advancepos
=1, id1234
=0):
708 dx
= advancepos
and self
.activefont
.getwidth_dvi(char
) or 0
711 self
.debugfile
.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
713 advancepos
and "set" or "put",
714 id1234
and "%i " % id1234
or "char",
716 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
))
718 if isinstance(self
.activefont
, virtualfont
):
719 # virtual font handling
720 afterpos
= list(self
.pos
)
721 afterpos
[_POS_H
] += dx
722 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
723 self
.activefont
.getsize_pt())
725 if self
.activetext
is None:
726 if not self
.fontmap
.has_key(self
.activefont
.name
):
727 raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self
.activefont
.name
)
728 fontmapinfo
= self
.fontmap
[self
.activefont
.name
]
730 encodingname
= fontmapinfo
.reencodefont
731 if encodingname
is not None:
732 encodingfilename
= pykpathsea
.find_file(fontmapinfo
.encodingfile
, pykpathsea
.kpse_tex_ps_header_format
)
733 if not encodingfilename
:
734 raise RuntimeError("cannot find font encoding file %s" % fontmapinfo
.encodingfile
)
735 fontencoding
= type1font
.encoding(encodingname
, encodingfilename
)
739 fontbasefontname
= fontmapinfo
.basepsname
740 if fontmapinfo
.fontfile
is not None:
741 fontfilename
= pykpathsea
.find_file(fontmapinfo
.fontfile
, pykpathsea
.kpse_type1_format
)
743 raise RuntimeError("cannot find type 1 font %s" % fontmapinfo
.fontfile
)
747 # XXX we currently misuse use self.activefont as metric
748 font
= type1font
.font(fontbasefontname
, fontfilename
, fontencoding
, self
.activefont
)
750 self
.activetext
= type1font
.text_pt(self
.pos
[_POS_H
] * self
.pyxconv
, -self
.pos
[_POS_V
] * self
.pyxconv
, font
)
751 self
.actpage
.insert(self
.activetext
)
752 self
.activetext
.addchar(char
)
753 self
.pos
[_POS_H
] += dx
758 def usefont(self
, fontnum
, id1234
=0):
760 self
.activefont
= self
.fonts
[fontnum
]
762 self
.debugfile
.write("%d: fnt%s%i current font is %s\n" %
764 id1234
and "%i " % id1234
or "num",
766 self
.fonts
[fontnum
].name
))
769 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
770 # cmdnr: type of fontdef command (only used for debugging output)
772 # q: scaling factor (fix_word)
773 # Note that q is actually s in large parts of the documentation.
774 # d: design size (fix_word)
777 afont
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
778 except (TypeError, RuntimeError):
779 afont
= font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
781 self
.fonts
[num
] = afont
784 self
.debugfile
.write("%d: fntdef%d %i: %s\n" % (self
.filepos
, cmdnr
, num
, fontname
))
786 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
788 # scalestring = scale!=1000 and " scaled %d" % scale or ""
789 # print ("Font %i: %s%s---loaded at size %d DVI units" %
790 # (num, fontname, scalestring, q))
792 # print " (this font is magnified %d%%)" % round(scale/10)
794 def special(self
, s
):
795 x
= self
.pos
[_POS_H
] * self
.pyxconv
796 y
= -self
.pos
[_POS_V
] * self
.pyxconv
798 self
.debugfile
.write("%d: xxx '%s'\n" % (self
.filepos
, s
))
799 if not s
.startswith("PyX:"):
800 warnings
.warn("ignoring special '%s'" % s
)
803 # it is in general not safe to continue using the currently active font because
804 # the specials may involve some gsave/grestore operations
807 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
808 if command
== "color_begin":
809 if args
[0] == "cmyk":
810 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
811 elif args
[0] == "gray":
812 c
= color
.gray(float(args
[1]))
813 elif args
[0] == "hsb":
814 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
815 elif args
[0] == "rgb":
816 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
817 elif args
[0] == "RGB":
818 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
819 elif args
[0] == "texnamed":
821 c
= getattr(color
.cmyk
, args
[1])
822 except AttributeError:
823 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
825 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
826 self
.actpage
.insert(_savecolor())
827 self
.actpage
.insert(c
)
828 elif command
== "color_end":
829 self
.actpage
.insert(_restorecolor())
830 elif command
== "rotate_begin":
831 self
.actpage
.insert(_savetrafo())
832 self
.actpage
.insert(trafo
.rotate_pt(float(args
[0]), x
, y
))
833 elif command
== "rotate_end":
834 self
.actpage
.insert(_restoretrafo())
835 elif command
== "scale_begin":
836 self
.actpage
.insert(_savetrafo())
837 self
.actpage
.insert(trafo
.scale_pt(float(args
[0]), float(args
[1]), x
, y
))
838 elif command
== "scale_end":
839 self
.actpage
.insert(_restoretrafo())
840 elif command
== "epsinclude":
844 name
, value
= arg
.split("=")
845 argdict
[name
] = value
847 # construct kwargs for epsfile constructor
849 epskwargs
["filename"] = argdict
["file"]
850 epskwargs
["bbox"] = bbox
.bbox_pt(float(argdict
["llx"]), float(argdict
["lly"]),
851 float(argdict
["urx"]), float(argdict
["ury"]))
852 if argdict
.has_key("width"):
853 epskwargs
["width"] = float(argdict
["width"]) * unit
.t_pt
854 if argdict
.has_key("height"):
855 epskwargs
["height"] = float(argdict
["height"]) * unit
.t_pt
856 if argdict
.has_key("clip"):
857 epskwargs
["clip"] = int(argdict
["clip"])
858 self
.actpage
.insert(epsfile
.epsfile(x
* unit
.t_pt
, y
* unit
.t_pt
, **epskwargs
))
859 elif command
== "marker":
861 raise RuntimeError("marker contains spaces")
863 if c
not in string
.digits
+ string
.letters
+ "@":
864 raise RuntimeError("marker contains invalid characters")
865 if self
.actpage
.markers
.has_key(args
[0]):
866 raise RuntimeError("marker name occurred several times")
867 self
.actpage
.markers
[args
[0]] = x
* unit
.t_pt
, y
* unit
.t_pt
869 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
871 # routines for pushing and popping different dvi chunks on the reader
873 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
874 """ push dvi string with defined fonts on top of reader
875 stack. Every positions gets scaled relatively by the factor
876 scale. After the interpreting of the dvi chunk has been finished,
877 continue with self.pos=afterpos. The designsize of the virtual
878 font is passed as a fix_word
883 # self.debugfile.write("executing new dvi chunk\n")
884 self
.debugstack
.append(self
.debug
)
887 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.pyxconv
, self
.tfmconv
))
889 # units in vf files are relative to the size of the font and given as fix_words
890 # which can be converted to floats by diving by 2**20
891 oldpyxconv
= self
.pyxconv
892 self
.pyxconv
= fontsize
/2**20
893 rescale
= self
.pyxconv
/oldpyxconv
895 self
.file = stringbinfile(dvi
)
900 # rescale self.pos in order to be consistent with the new scaling
901 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
903 # since tfmconv converts from tfm units to dvi units, rescale it as well
904 self
.tfmconv
/= rescale
908 def _pop_dvistring(self
):
911 # self.debugfile.write("finished executing dvi chunk\n")
912 self
.debug
= self
.debugstack
.pop()
915 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.pyxconv
, self
.tfmconv
= self
.statestack
.pop()
917 # routines corresponding to the different reader states of the dvi maschine
922 self
.filepos
= afile
.tell()
923 cmd
= afile
.readuchar()
926 elif cmd
== _DVI_PRE
:
927 if afile
.readuchar() != _DVI_VERSION
: raise DVIError
928 num
= afile
.readuint32()
929 den
= afile
.readuint32()
930 self
.mag
= afile
.readuint32()
932 # For the interpretation of the lengths in dvi and tfm files,
933 # three conversion factors are relevant:
934 # - self.tfmconv: tfm units -> dvi units
935 # - self.pyxconv: dvi units -> (PostScript) points
936 # - self.conv: dvi units -> pixels
937 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
939 # calculate conv as described in the DVIType docu using
940 # a given resolution in dpi
941 self
.resolution
= 300.0
942 self
.conv
= (num
/254000.0)*(self
.resolution
/den
)
944 # self.pyxconv is the conversion factor from the dvi units
945 # to (PostScript) points. It consists of
946 # - self.mag/1000.0: magstep scaling
947 # - self.conv: conversion from dvi units to pixels
948 # - 1/self.resolution: conversion from pixels to inch
949 # - 72 : conversion from inch to points
950 self
.pyxconv
= self
.mag
/1000.0*self
.conv
/self
.resolution
*72
952 comment
= afile
.read(afile
.readuchar())
957 def readpage(self
, pageid
=None):
958 """ reads a page from the dvi file
960 This routine reads a page from the dvi file which is
961 returned as a canvas. When there is no page left in the
962 dvifile, None is returned and the file is closed properly."""
965 self
.filepos
= self
.file.tell()
966 cmd
= self
.file.readuchar()
969 elif cmd
== _DVI_BOP
:
970 ispageid
= [self
.file.readuint32() for i
in range(10)]
971 if pageid
is not None and ispageid
!= pageid
:
972 raise DVIError("invalid pageid")
974 self
.debugfile
.write("%d: beginning of page %i\n" % (self
.filepos
, ispageid
[0]))
975 self
.file.readuint32()
977 elif cmd
== _DVI_POST
:
979 return None # nothing left
983 self
.actpage
= canvas
.canvas()
984 self
.actpage
.markers
= {}
985 self
.pos
= [0, 0, 0, 0, 0, 0]
987 # currently active output: text instance currently used
988 self
.activetext
= None
992 self
.filepos
= afile
.tell()
994 cmd
= afile
.readuchar()
996 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
997 # so we have to continue with the rest of the dvi file
998 self
._pop
_dvistring
()
1002 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
1004 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
1005 self
.putchar(afile
.readint(cmd
- _DVI_SET1234
+ 1), id1234
=cmd
-_DVI_SET1234
+1)
1006 elif cmd
== _DVI_SETRULE
:
1007 self
.putrule(afile
.readint32(), afile
.readint32())
1008 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1009 self
.putchar(afile
.readint(cmd
- _DVI_PUT1234
+ 1), advancepos
=0, id1234
=cmd
-_DVI_SET1234
+1)
1010 elif cmd
== _DVI_PUTRULE
:
1011 self
.putrule(afile
.readint32(), afile
.readint32(), 0)
1012 elif cmd
== _DVI_EOP
:
1015 self
.debugfile
.write("%d: eop\n \n" % self
.filepos
)
1017 elif cmd
== _DVI_PUSH
:
1018 self
.stack
.append(list(self
.pos
))
1020 self
.debugfile
.write("%s: push\n"
1021 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1022 ((self
.filepos
, len(self
.stack
)-1) + tuple(self
.pos
)))
1023 elif cmd
== _DVI_POP
:
1025 self
.pos
= self
.stack
.pop()
1027 self
.debugfile
.write("%s: pop\n"
1028 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1029 ((self
.filepos
, len(self
.stack
)) + tuple(self
.pos
)))
1030 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1032 dh
= afile
.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1034 self
.debugfile
.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1036 cmd
- _DVI_RIGHT1234
+ 1,
1040 self
.pos
[_POS_H
]+dh
))
1041 self
.pos
[_POS_H
] += dh
1042 elif cmd
== _DVI_W0
:
1045 self
.debugfile
.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1050 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1051 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1052 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1054 self
.pos
[_POS_W
] = afile
.readint(cmd
- _DVI_W1234
+ 1, 1)
1056 self
.debugfile
.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1058 cmd
- _DVI_W1234
+ 1,
1062 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1063 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1064 elif cmd
== _DVI_X0
:
1067 self
.debugfile
.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1072 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1073 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1074 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1076 self
.pos
[_POS_X
] = afile
.readint(cmd
- _DVI_X1234
+ 1, 1)
1078 self
.debugfile
.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1080 cmd
- _DVI_X1234
+ 1,
1084 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1085 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1086 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1088 dv
= afile
.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1090 self
.debugfile
.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1092 cmd
- _DVI_DOWN1234
+ 1,
1096 self
.pos
[_POS_V
]+dv
))
1097 self
.pos
[_POS_V
] += dv
1098 elif cmd
== _DVI_Y0
:
1101 self
.debugfile
.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1106 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1107 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1108 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1110 self
.pos
[_POS_Y
] = afile
.readint(cmd
- _DVI_Y1234
+ 1, 1)
1112 self
.debugfile
.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1114 cmd
- _DVI_Y1234
+ 1,
1118 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1119 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1120 elif cmd
== _DVI_Z0
:
1123 self
.debugfile
.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1128 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1129 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1130 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1132 self
.pos
[_POS_Z
] = afile
.readint(cmd
- _DVI_Z1234
+ 1, 1)
1134 self
.debugfile
.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1136 cmd
- _DVI_Z1234
+ 1,
1140 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1141 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1142 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1143 self
.usefont(cmd
- _DVI_FNTNUMMIN
, 0)
1144 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1145 # note that according to the DVI docs, for four byte font numbers,
1146 # the font number is signed. Don't ask why!
1147 fntnum
= afile
.readint(cmd
- _DVI_FNT1234
+ 1, cmd
== _DVI_FNT1234
+ 3)
1148 self
.usefont(fntnum
, id1234
=cmd
-_DVI_FNT1234
+1)
1149 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1150 self
.special(afile
.read(afile
.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1151 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1152 if cmd
== _DVI_FNTDEF1234
:
1153 num
= afile
.readuchar()
1154 elif cmd
== _DVI_FNTDEF1234
+1:
1155 num
= afile
.readuint16()
1156 elif cmd
== _DVI_FNTDEF1234
+2:
1157 num
= afile
.readuint24()
1158 elif cmd
== _DVI_FNTDEF1234
+3:
1159 # Cool, here we have according to docu a signed int. Why?
1160 num
= afile
.readint32()
1161 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1166 afile
.read(afile
.readuchar()+afile
.readuchar()))
1171 ##############################################################################
1173 ##############################################################################
1175 _VF_LONG_CHAR
= 242 # character packet (long version)
1176 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1177 _VF_PRE
= _DVI_PRE
# preamble
1178 _VF_POST
= _DVI_POST
# postamble
1180 _VF_ID
= 202 # VF id byte
1182 class VFError(exceptions
.Exception): pass
1185 def __init__(self
, filename
, scale
, tfmconv
, pyxconv
, fontmap
, debug
=0):
1186 self
.filename
= filename
1188 self
.tfmconv
= tfmconv
1189 self
.pyxconv
= pyxconv
1190 self
.fontmap
= fontmap
1192 self
.fonts
= {} # used fonts
1193 self
.widths
= {} # widths of defined chars
1194 self
.chardefs
= {} # dvi chunks for defined chars
1196 afile
= binfile(self
.filename
, "rb")
1198 cmd
= afile
.readuchar()
1200 if afile
.readuchar() != _VF_ID
: raise VFError
1201 comment
= afile
.read(afile
.readuchar())
1202 self
.cs
= afile
.readuint32()
1203 self
.ds
= afile
.readuint32()
1208 cmd
= afile
.readuchar()
1209 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1211 if cmd
== _VF_FNTDEF1234
:
1212 num
= afile
.readuchar()
1213 elif cmd
== _VF_FNTDEF1234
+1:
1214 num
= afile
.readuint16()
1215 elif cmd
== _VF_FNTDEF1234
+2:
1216 num
= afile
.readuint24()
1217 elif cmd
== _VF_FNTDEF1234
+3:
1218 num
= afile
.readint32()
1219 c
= afile
.readint32()
1220 s
= afile
.readint32() # relative scaling used for font (fix_word)
1221 d
= afile
.readint32() # design size of font
1222 fontname
= afile
.read(afile
.readuchar()+afile
.readuchar())
1224 # rescaled size of font: s is relative to the scaling
1225 # of the virtual font itself. Note that realscale has
1226 # to be a fix_word (like s)
1227 # XXX: check rounding
1228 reals
= int(round(self
.scale
* (16*self
.ds
/16777216L) * s
))
1230 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1231 # (fontname, self.scale, self.ds, s, reals)
1234 # XXX allow for virtual fonts here too
1235 self
.fonts
[num
] = font(fontname
, c
, reals
, d
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
1236 elif cmd
== _VF_LONG_CHAR
:
1237 # character packet (long form)
1238 pl
= afile
.readuint32() # packet length
1239 cc
= afile
.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1240 tfm
= afile
.readuint24() # character width
1241 dvi
= afile
.read(pl
) # dvi code of character
1242 self
.widths
[cc
] = tfm
1243 self
.chardefs
[cc
] = dvi
1244 elif cmd
< _VF_LONG_CHAR
:
1245 # character packet (short form)
1246 cc
= afile
.readuchar() # char code
1247 tfm
= afile
.readuint24() # character width
1248 dvi
= afile
.read(cmd
)
1249 self
.widths
[cc
] = tfm
1250 self
.chardefs
[cc
] = dvi
1251 elif cmd
== _VF_POST
:
1261 def getchar(self
, cc
):
1262 return self
.chardefs
[cc
]