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
26 import unit
, epsfile
, bbox
, base
, canvas
, color
, trafo
, path
, prolog
, pykpathsea
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
)
96 # """ ascii file containing tokens separated by spaces.
98 # Comments beginning with % are ignored. Strings containing spaces
99 # are not handled correctly
102 # def __init__(self, filename):
103 # self.file = open(filename, "r")
106 # def gettoken(self):
107 # """ return next token or None if EOF """
108 # while not self.line:
109 # line = self.file.readline()
112 # self.line = line.split("%")[0].split()
113 # token = self.line[0]
114 # self.line = self.line[1:]
121 ##############################################################################
123 ##############################################################################
125 class TFMError(exceptions
.Exception): pass
128 class char_info_word
:
129 def __init__(self
, word
):
130 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
131 self
.height_index
= (word
& 0x00F00000) >> 20
132 self
.depth_index
= (word
& 0x000F0000) >> 16
133 self
.italic_index
= (word
& 0x0000FC00) >> 10
134 self
.tag
= (word
& 0x00000300) >> 8
135 self
.remainder
= (word
& 0x000000FF)
139 def __init__(self
, name
, debug
=0):
140 self
.file = binfile(name
, "rb")
147 self
.lf
= self
.file.readint16()
148 self
.lh
= self
.file.readint16()
149 self
.bc
= self
.file.readint16()
150 self
.ec
= self
.file.readint16()
151 self
.nw
= self
.file.readint16()
152 self
.nh
= self
.file.readint16()
153 self
.nd
= self
.file.readint16()
154 self
.ni
= self
.file.readint16()
155 self
.nl
= self
.file.readint16()
156 self
.nk
= self
.file.readint16()
157 self
.ne
= self
.file.readint16()
158 self
.np
= self
.file.readint16()
160 if not (self
.bc
-1 <= self
.ec
<= 255 and
162 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
163 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
164 raise TFMError
, "error in TFM pre-header"
167 print "lh=%d" % self
.lh
173 self
.checksum
= self
.file.readint32()
174 self
.designsize
= self
.file.readint32()
175 assert self
.designsize
> 0, "invald design size"
177 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
178 self
.charcoding
= self
.file.readstring(40)
180 self
.charcoding
= None
183 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
184 self
.fontfamily
= self
.file.readstring(20)
186 self
.fontfamily
= None
189 print "(FAMILY %s)" % self
.fontfamily
190 print "(CODINGSCHEME %s)" % self
.charcoding
191 print "(DESINGSIZE R %f)" % 16.0*self
.designsize
/16777216L
194 self
.sevenbitsave
= self
.file.readuchar()
195 # ignore the following two bytes
196 self
.file.readint16()
197 facechar
= self
.file.readuchar()
198 # decode ugly face specification into the Knuth suggested string
210 self
.face
= "L" + self
.face
213 self
.face
= "B" + self
.face
216 self
.face
= "M" + self
.face
219 self
.face
= self
.face
[0] + "I" + self
.face
[1]
221 self
.face
= self
.face
[0] + "R" + self
.face
[1]
226 self
.sevenbitsave
= self
.face
= None
229 # just ignore the rest
230 print self
.file.read((self
.lh
-18)*4)
236 self
.char_info
= [None]*(self
.ec
+1)
237 for charcode
in range(self
.bc
, self
.ec
+1):
238 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
239 if self
.char_info
[charcode
].width_index
== 0:
240 # disable character if width_index is zero
241 self
.char_info
[charcode
] = None
247 self
.width
= [None for width_index
in range(self
.nw
)]
248 for width_index
in range(self
.nw
):
249 self
.width
[width_index
] = self
.file.readint32()
255 self
.height
= [None for height_index
in range(self
.nh
)]
256 for height_index
in range(self
.nh
):
257 self
.height
[height_index
] = self
.file.readint32()
263 self
.depth
= [None for depth_index
in range(self
.nd
)]
264 for depth_index
in range(self
.nd
):
265 self
.depth
[depth_index
] = self
.file.readint32()
271 self
.italic
= [None for italic_index
in range(self
.ni
)]
272 for italic_index
in range(self
.ni
):
273 self
.italic
[italic_index
] = self
.file.readint32()
279 # XXX decode to lig_kern_command
281 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
282 for lig_kern_index
in range(self
.nl
):
283 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
289 self
.kern
= [None for kern_index
in range(self
.nk
)]
290 for kern_index
in range(self
.nk
):
291 self
.kern
[kern_index
] = self
.file.readint32()
297 # XXX decode to extensible_recipe
299 self
.exten
= [None for exten_index
in range(self
.ne
)]
300 for exten_index
in range(self
.ne
):
301 self
.exten
[exten_index
] = self
.file.readint32()
309 self
.param
= [None for param_index
in range(self
.np
)]
310 for param_index
in range(self
.np
):
311 self
.param
[param_index
] = self
.file.readint32()
316 # class FontEncoding:
318 # def __init__(self, filename):
319 # """ font encoding contained in filename """
320 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
321 # encfile = tokenfile(encpath)
324 # self.encname = encfile.gettoken()
325 # token = encfile.gettoken()
327 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
328 # self.encvector = []
329 # for i in range(256):
330 # token = encfile.gettoken()
331 # if token is None or token=="]":
332 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
333 # self.encvector.append(token)
334 # if encfile.gettoken() != "]":
335 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
336 # token = encfile.gettoken()
338 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
339 # token = encfile.gettoken()
341 # raise RuntimeError("encoding file '%s' too long" % filename)
344 # def encode(self, charcode):
345 # return self.encvector[charcode]
347 ##############################################################################
349 ##############################################################################
351 _ReEncodeFont
= prolog
.definition("ReEncodeFont", """{
354 /newencoding exch def
355 /newfontname exch def
356 /basefontname exch def
357 /basefontdict basefontname findfont def
358 /newfontdict basefontdict maxlength dict def
360 exch dup dup /FID ne exch /Encoding ne and
361 { exch newfontdict 3 1 roll put }
365 newfontdict /FontName newfontname put
366 newfontdict /Encoding newencoding put
367 newfontname newfontdict definefont pop
372 # PostScript font selection and output primitives
375 class _begintextobject(base
.canvasitem
):
376 def outputPS(self
, file):
379 def outputPDF(self
, file):
383 class _endtextobject(base
.canvasitem
):
384 def outputPS(self
, file):
387 def outputPDF(self
, file):
391 class _selectfont(base
.canvasitem
):
392 # XXX this should go away and be merged with selectfont
393 def __init__(self
, name
, size
):
397 def outputPS(self
, file):
398 file.write("/%s %f selectfont\n" % (self
.name
, self
.size
))
400 def outputPDF(self
, file):
401 file.write("/%s %f Tf\n" % (self
.name
, self
.size
))
404 class selectfont(base
.canvasitem
):
405 def __init__(self
, font
):
406 # XXX maybe we should change the calling convention here and only pass the
407 # name, size, encoding, usedchars of the font
409 self
.size
= font
.getsize_pt()
411 self
.fontid
= font
.getpsname()
414 result
= [prolog
.fontdefinition(self
.font
,
415 self
.font
.getbasepsname(),
416 self
.font
.getfontfile(),
417 self
.font
.getencodingfile(),
418 self
.font
.usedchars
)]
419 if self
.font
.getencoding():
420 result
.append(_ReEncodeFont
)
421 result
.append(prolog
.fontencoding(self
.font
.getencoding(), self
.font
.getencodingfile()))
422 result
.append(prolog
.fontreencoding(self
.font
.getpsname(), self
.font
.getbasepsname(), self
.font
.getencoding()))
425 def outputPS(self
, file):
426 file.write("/%s %f selectfont\n" % (self
.fontid
, self
.size
))
428 def outputPDF(self
, file):
429 file.write("/%s %f Tf\n" % (self
.fontid
, self
.size
))
432 class _show(base
.canvasitem
):
434 def __init__(self
, x
, y
):
442 def addchar(self
, width
, height
, depth
, char
):
444 if height
> self
.height
:
446 if depth
> self
.depth
:
448 self
.chars
.append(char
)
451 return bbox
.bbox_pt(self
.x
, self
.y
-self
.depth
, self
.x
+self
.width
, self
.y
+self
.height
)
453 def outputPS(self
, file):
455 for char
in self
.chars
:
456 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>\\":
457 ascii
= "%s" % chr(char
)
459 ascii
= "\\%03o" % char
461 file.write("%g %g moveto (%s) show\n" % (self
.x
, self
.y
, outstring
))
463 def outputPDF(self
, file):
465 for char
in self
.chars
:
466 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>\\":
467 ascii
= "%s" % chr(char
)
469 ascii
= "\\%03o" % char
471 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
472 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self
.x
, self
.y
, outstring
))
477 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
479 def __init__(self
, s
):
480 """ construct font mapping from line s of font mapping file """
481 self
.texname
= self
.basepsname
= self
.fontfile
= None
484 self
.encodingfile
= None
486 # supported postscript fragments occuring in psfonts.map
487 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
491 match
= self
.tokenpattern
.match(s
)
493 if match
.groups()[0]:
494 tokens
.append('"%s"' % match
.groups()[0])
496 tokens
.append(match
.groups()[2])
499 raise RuntimeError("wrong syntax")
502 if token
.startswith("<"):
503 if token
.startswith("<<"):
504 # XXX: support non-partial download here
505 self
.fontfile
= token
[2:]
506 elif token
.startswith("<["):
507 self
.encodingfile
= token
[2:]
508 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
509 self
.fontfile
= token
[1:]
510 elif token
.endswith(".enc"):
511 self
.encodingfile
= token
[1:]
513 raise RuntimeError("wrong syntax")
514 elif token
.startswith('"'):
515 pscode
= token
[1:-1].split()
516 # parse standard postscript code fragments
519 arg
, cmd
= pscode
[:2]
521 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode
)
523 if cmd
== "ReEncodeFont":
524 self
.reencodefont
= arg
525 elif cmd
== "ExtendFont":
526 self
.extendfont
= arg
527 elif cmd
== "SlantFont":
530 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
532 if self
.texname
is None:
535 self
.basepsname
= token
536 if self
.basepsname
is None:
537 self
.basepsname
= self
.texname
540 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
541 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
545 def readfontmap(filenames
):
546 """ read font map from filename (without path) """
548 for filename
in filenames
:
549 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_fontmap_format
)
550 # try also the oft-used registration as dvips config file
552 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
554 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
555 mapfile
= open(mappath
, "r")
557 for line
in mapfile
.readlines():
560 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
562 fm
= fontmapping(line
)
563 except RuntimeError, e
:
564 sys
.stderr
.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno
, filename
, e
))
566 fontmap
[fm
.texname
] = fm
572 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, debug
=0):
574 self
.q
= q
# desired size of font (fix_word) in TeX points
575 self
.d
= d
# design size of font (fix_word) in TeX points
576 self
.tfmconv
= tfmconv
# conversion factor from tfm units to dvi units
577 self
.pyxconv
= pyxconv
# conversion factor from dvi units to PostScript points
578 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
580 raise TFMError("cannot find %s.tfm" % self
.name
)
581 self
.tfmfile
= tfmfile(tfmpath
, debug
)
583 # We only check for equality of font checksums if none of them
584 # is zero. The case c == 0 happend in some VF files and
585 # according to the VFtoVP documentation, paragraph 40, a check
586 # is only performed if tfmfile.checksum > 0. Anyhow, being
587 # more generous here seems to be reasonable
588 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!= 0:
589 raise DVIError("check sums do not agree: %d vs. %d" %
590 (self
.tfmfile
.checksum
, c
))
592 # Check whether the given design size matches the one defined in the tfm file
593 if abs(self
.tfmfile
.designsize
- d
) > 2:
594 raise DVIError("design sizes do not agree: %d vs. %d" % (self
.tfmfile
.designsize
, d
))
595 if q
< 0 or q
> 134217728:
596 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
597 if d
< 0 or d
> 134217728:
598 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
602 # for bookkeeping of used characters
603 self
.usedchars
= [0] * 256
606 return "font %s designed at %g TeX pts used at %g TeX pts" % (self
.name
,
607 16.0*self
.d
/16777216L,
608 16.0*self
.q
/16777216L)
613 def getsize_pt(self
):
614 """ return size of font in (PS) points """
615 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
616 # to the corresponding float. Furthermore, we have to convert from TeX
617 # points to points, hence the factor 72/72.27.
618 return 16L*self
.q
/16777216L*72/72.27
620 def _convert_tfm_to_dvi(self
, length
):
621 return 16*long(round(length
*self
.q
*self
.tfmconv
))/16777216
623 # Knuth instead suggests the following algorithm based on integer logic only
624 # For z < 8388608 the result was checked and seemed to be equal.
625 # z = int(round(self.q*self.tfmconv))
626 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
627 # assert b0 == 0 or b0 == 255
629 # while z >= 8388608:
632 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> beta
634 # result = result - (z << beta)
635 # assert result == 16*int(round(length*self.q*self.tfmconv))/16777216
637 # we do not need that ...
638 # def _convert_tfm_to_pt(self, length):
639 # return (16*long(round(length*self.q*self.tfmconv))/16777216) * self.pyxconv
641 # routines returning lengths as integers in dvi units
643 def getwidth_dvi(self
, charcode
):
644 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
646 def getheight_dvi(self
, charcode
):
647 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
649 def getdepth_dvi(self
, charcode
):
650 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
652 def getitalic_dvi(self
, charcode
):
653 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
655 # routines returning lengths as floats in PostScript points
657 # def getwidth_pt(self, charcode):
658 # return self._convert_tfm_to_pt(self.tfmfile.width[self.tfmfile.char_info[charcode].width_index])
660 # def getheight_pt(self, charcode):
661 # return self._convert_tfm_to_pt(self.tfmfile.height[self.tfmfile.char_info[charcode].height_index])
663 # def getdepth_pt(self, charcode):
664 # return self._convert_tfm_to_pt(self.tfmfile.depth[self.tfmfile.char_info[charcode].depth_index])
666 # def getitalic_pt(self, charcode):
667 # return self._convert_tfm_to_pt(self.tfmfile.italic[self.tfmfile.char_info[charcode].italic_index])
669 def markcharused(self
, charcode
):
670 self
.usedchars
[charcode
] = 1
672 def mergeusedchars(self
, otherfont
):
673 for i
in range(len(self
.usedchars
)):
674 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
676 def clearusedchars(self
):
677 self
.usedchars
= [0] * 256
680 class type1font(font
):
681 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
682 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, debug
)
683 self
.fontmapping
= fontmap
.get(name
)
684 if self
.fontmapping
is None:
685 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name
)
687 def getbasepsname(self
):
688 return self
.fontmapping
.basepsname
691 if self
.fontmapping
.reencodefont
:
692 return "%s-%s" % (self
.fontmapping
.basepsname
, self
.fontmapping
.reencodefont
)
694 return self
.fontmapping
.basepsname
696 def getfontfile(self
):
697 return self
.fontmapping
.fontfile
699 def getencoding(self
):
700 return self
.fontmapping
.reencodefont
702 def getencodingfile(self
):
703 return self
.fontmapping
.encodingfile
706 class virtualfont(font
):
707 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
708 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, debug
)
709 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
710 if fontpath
is None or not len(fontpath
):
712 self
.vffile
= vffile(fontpath
, self
.scale
, tfmconv
, pyxconv
, fontmap
, debug
> 1)
715 """ return fonts used in virtual font itself """
716 return self
.vffile
.getfonts()
718 def getchar(self
, cc
):
719 """ return dvi chunk corresponding to char code cc """
720 return self
.vffile
.getchar(cc
)
723 ##############################################################################
725 ##############################################################################
727 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
728 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
729 _DVI_SET1234
= 128 # typeset a character and move right
730 _DVI_SETRULE
= 132 # typeset a rule and move right
731 _DVI_PUT1234
= 133 # typeset a character
732 _DVI_PUTRULE
= 137 # typeset a rule
733 _DVI_NOP
= 138 # no operation
734 _DVI_BOP
= 139 # beginning of page
735 _DVI_EOP
= 140 # ending of page
736 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
737 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
738 _DVI_RIGHT1234
= 143 # move right
739 _DVI_W0
= 147 # move right by w
740 _DVI_W1234
= 148 # move right and set w
741 _DVI_X0
= 152 # move right by x
742 _DVI_X1234
= 153 # move right and set x
743 _DVI_DOWN1234
= 157 # move down
744 _DVI_Y0
= 161 # move down by y
745 _DVI_Y1234
= 162 # move down and set y
746 _DVI_Z0
= 166 # move down by z
747 _DVI_Z1234
= 167 # move down and set z
748 _DVI_FNTNUMMIN
= 171 # set current font (range min)
749 _DVI_FNTNUMMAX
= 234 # set current font (range max)
750 _DVI_FNT1234
= 235 # set current font
751 _DVI_SPECIAL1234
= 239 # special (dvi extention)
752 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
753 _DVI_PRE
= 247 # preamble
754 _DVI_POST
= 248 # postamble beginning
755 _DVI_POSTPOST
= 249 # postamble ending
757 _DVI_VERSION
= 2 # dvi version
759 # position variable indices
771 _READ_POST
= 4 # XXX not used
772 _READ_POSTPOST
= 5 # XXX not used
776 class DVIError(exceptions
.Exception): pass
778 # save and restore colors
780 class _savecolor(base
.canvasitem
):
781 def outputPS(self
, file):
782 file.write("currentcolor currentcolorspace\n")
784 def outputPDF(self
, file):
788 class _restorecolor(base
.canvasitem
):
789 def outputPS(self
, file):
790 file.write("setcolorspace setcolor\n")
792 def outputPDF(self
, file):
795 class _savetrafo(base
.canvasitem
):
796 def outputPS(self
, file):
797 file.write("matrix currentmatrix\n")
799 def outputPDF(self
, file):
803 class _restoretrafo(base
.canvasitem
):
804 def outputPS(self
, file):
805 file.write("setmatrix\n")
807 def outputPDF(self
, file):
813 def __init__(self
, filename
, fontmap
, debug
=0):
814 """ opens the dvi file and reads the preamble """
815 self
.filename
= filename
816 self
.fontmap
= fontmap
820 self
.activefont
= None
822 # stack of fonts and fontscale currently used (used for VFs)
826 # pointer to currently active page
829 # currently active output: show instance currently used and
830 # the corresponding type 1 font
831 self
.activeshow
= None
832 self
.activetype1font
= None
834 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
837 self
.file = binfile(self
.filename
, "rb")
839 # currently read byte in file (for debugging output)
847 """ flush currently active string """
848 if self
.activeshow
is not None:
850 print "[%s]" % "".join([chr(char
) for char
in self
.activeshow
.chars
])
851 self
.actpage
.insert(self
.activeshow
)
852 self
.activeshow
= None
855 """ activate the font if is not yet active, closing a currently active
856 text object and flushing the output"""
857 if isinstance(self
.activefont
, type1font
):
859 if self
.activetype1font
!= self
.activefont
and self
.activefont
:
860 self
.actpage
.insert(_begintextobject())
861 self
.actpage
.insert(selectfont(self
.activefont
))
862 self
.activetype1font
= self
.activefont
866 if self
.activetype1font
:
867 self
.actpage
.insert(_endtextobject())
868 self
.activetype1font
= None
870 def putrule(self
, height
, width
, advancepos
=1):
872 x1
= self
.pos
[_POS_H
] * self
.pyxconv
873 y1
= -self
.pos
[_POS_V
] * self
.pyxconv
874 w
= width
* self
.pyxconv
875 h
= height
* self
.pyxconv
877 if height
> 0 and width
> 0:
879 print ("%d: %srule height %d, width %d (???x??? pixels)" %
880 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
881 self
.actpage
.fill(path
.rect_pt(x1
, y1
, w
, h
))
884 print ("%d: %srule height %d, width %d (invisible)" %
885 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
889 print (" h:=%d+%d=%d, hh:=???" %
890 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
))
891 self
.pos
[_POS_H
] += width
893 def putchar(self
, char
, advancepos
=1):
894 dx
= advancepos
and self
.activefont
.getwidth_dvi(char
) or 0
897 print ("%d: %schar%d h:=%d+%d=%d, hh:=???" %
899 advancepos
and "set" or "put",
901 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
))
903 if isinstance(self
.activefont
, type1font
):
904 if self
.activeshow
is None:
905 # XXX: begintext would lead to massive number of selectfonts being issued
906 # OTOH is it save to remove begintext here? I think so ...
908 self
.activeshow
= _show(self
.pos
[_POS_H
] * self
.pyxconv
, -self
.pos
[_POS_V
] * self
.pyxconv
)
909 width
= self
.activefont
.getwidth_dvi(char
) * self
.pyxconv
910 height
= self
.activefont
.getheight_dvi(char
) * self
.pyxconv
911 depth
= self
.activefont
.getdepth_dvi(char
) * self
.pyxconv
912 self
.activeshow
.addchar(width
, height
, depth
, char
)
914 self
.activefont
.markcharused(char
)
915 self
.pos
[_POS_H
] += dx
917 # virtual font handling
918 afterpos
= list(self
.pos
)
919 afterpos
[_POS_H
] += dx
920 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
921 self
.activefont
.getsize_pt())
928 def usefont(self
, fontnum
):
929 self
.activefont
= self
.fonts
[fontnum
]
932 print ("%d: fntnum%i current font is %s" %
934 fontnum
, self
.fonts
[fontnum
].name
))
937 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
938 # cmdnr: type of fontdef command (only used for debugging output)
940 # q: scaling factor (fix_word)
941 # Note that q is actually s in large parts of the documentation.
942 # d: design size (fix_word)
945 font
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
946 except (TypeError, RuntimeError):
947 font
= type1font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
949 self
.fonts
[num
] = font
952 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
954 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
956 # scalestring = scale!=1000 and " scaled %d" % scale or ""
957 # print ("Font %i: %s%s---loaded at size %d DVI units" %
958 # (num, fontname, scalestring, q))
960 # print " (this font is magnified %d%%)" % round(scale/10)
962 def special(self
, s
):
963 x
= self
.pos
[_POS_H
] * self
.pyxconv
964 y
= -self
.pos
[_POS_V
] * self
.pyxconv
966 print "%d: xxx '%s'" % (self
.filepos
, s
)
967 if not s
.startswith("PyX:"):
968 #if s.startswith("Warning:"):
969 sys
.stderr
.write("*** PyX Warning: ignoring special '%s'\n" % s
)
972 # raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s)
974 # it is in general not safe to continue using the currently active font because
975 # the specials may involve some gsave/grestore operations
978 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
979 if command
=="color_begin":
981 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
982 elif args
[0]=="gray":
983 c
= color
.gray(float(args
[1]))
985 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
987 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
989 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
990 elif args
[0]=="texnamed":
992 c
= getattr(color
.cmyk
, args
[1])
993 except AttributeError:
994 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
996 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
997 self
.actpage
.insert(_savecolor())
998 self
.actpage
.insert(c
)
999 elif command
=="color_end":
1000 self
.actpage
.insert(_restorecolor())
1001 elif command
=="rotate_begin":
1002 self
.actpage
.insert(_savetrafo())
1003 self
.actpage
.insert(trafo
.rotate_pt(float(args
[0]), x
, y
))
1004 elif command
=="rotate_end":
1005 self
.actpage
.insert(_restoretrafo())
1006 elif command
=="scale_begin":
1007 self
.actpage
.insert(_savetrafo())
1008 self
.actpage
.insert(trafo
.scale_pt(float(args
[0]), float(args
[1]), x
, y
))
1009 elif command
=="scale_end":
1010 self
.actpage
.insert(_restoretrafo())
1011 elif command
=="epsinclude":
1015 name
, value
= arg
.split("=")
1016 argdict
[name
] = value
1018 # construct kwargs for epsfile constructor
1020 epskwargs
["filename"] = argdict
["file"]
1021 epskwargs
["bbox"] = bbox
.bbox_pt(float(argdict
["llx"]), float(argdict
["lly"]),
1022 float(argdict
["urx"]), float(argdict
["ury"]))
1023 if argdict
.has_key("width"):
1024 epskwargs
["width"] = float(argdict
["width"]) * unit
.t_pt
1025 if argdict
.has_key("height"):
1026 epskwargs
["height"] = float(argdict
["height"]) * unit
.t_pt
1027 if argdict
.has_key("clip"):
1028 epskwargs
["clip"] = int(argdict
["clip"])
1029 self
.actpage
.insert(epsfile
.epsfile(x
* unit
.t_pt
, y
* unit
.t_pt
, **epskwargs
))
1030 elif command
=="marker":
1032 raise RuntimeError("marker contains spaces")
1034 if c
not in string
.digits
+ string
.letters
+ "@":
1035 raise RuntimeError("marker contains invalid characters")
1036 if self
.actpage
.markers
.has_key(args
[0]):
1037 raise RuntimeError("marker name occurred several times")
1038 self
.actpage
.markers
[args
[0]] = x
* unit
.t_pt
, y
* unit
.t_pt
1040 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
1043 # routines for pushing and popping different dvi chunks on the reader
1045 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
1046 """ push dvi string with defined fonts on top of reader
1047 stack. Every positions gets scaled relatively by the factor
1048 scale. After the interpreting of the dvi chunk has been finished,
1049 continue with self.pos=afterpos. The designsize of the virtual
1050 font is passed as a fix_word
1055 print "executing new dvi chunk"
1056 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.pyxconv
, self
.tfmconv
))
1058 # units in vf files are relative to the size of the font and given as fix_words
1059 # which can be converted to floats by diving by 2**20
1060 oldpyxconv
= self
.pyxconv
1061 self
.pyxconv
= fontsize
/2**20
1062 rescale
= self
.pyxconv
/oldpyxconv
1064 self
.file = stringbinfile(dvi
)
1069 # rescale self.pos in order to be consistent with the new scaling
1070 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
1072 # since tfmconv converts from tfm units to dvi units, rescale it as well
1073 self
.tfmconv
/= rescale
1077 def _pop_dvistring(self
):
1080 print "finished executing dvi chunk"
1082 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.pyxconv
, self
.tfmconv
= self
.statestack
.pop()
1084 # routines corresponding to the different reader states of the dvi maschine
1086 def _read_pre(self
):
1089 self
.filepos
= afile
.tell()
1090 cmd
= afile
.readuchar()
1093 elif cmd
== _DVI_PRE
:
1094 if afile
.readuchar() != _DVI_VERSION
: raise DVIError
1095 num
= afile
.readuint32()
1096 den
= afile
.readuint32()
1097 self
.mag
= afile
.readuint32()
1099 # For the interpretation of the lengths in dvi and tfm files,
1100 # three conversion factors are relevant:
1101 # - self.tfmconv: tfm units -> dvi units
1102 # - self.pyxconv: dvi units -> (PostScript) points
1103 # - self.conv: dvi units -> pixels
1104 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
1106 # calculate conv as described in the DVIType docu using
1107 # a given resolution in dpi
1108 self
.resolution
= 300.0
1109 self
.conv
= (num
/254000.0)*(self
.resolution
/den
)
1111 # self.pyxconv is the conversion factor from the dvi units
1112 # to (PostScript) points. It consists of
1113 # - self.mag/1000.0: magstep scaling
1114 # - self.conv: conversion from dvi units to pixels
1115 # - 1/self.resolution: conversion from pixels to inch
1116 # - 72 : conversion from inch to points
1117 self
.pyxconv
= self
.mag
/1000.0*self
.conv
/self
.resolution
*72
1119 comment
= afile
.read(afile
.readuchar())
1124 def readpage(self
, pageid
=None):
1125 """ reads a page from the dvi file
1127 This routine reads a page from the dvi file which is
1128 returned as a canvas. When there is no page left in the
1129 dvifile, None is returned and the file is closed properly."""
1132 self
.filepos
= self
.file.tell()
1133 cmd
= self
.file.readuchar()
1136 elif cmd
== _DVI_BOP
:
1138 ispageid
= [self
.file.readuint32() for i
in range(10)]
1139 if pageid
is not None and ispageid
!= pageid
:
1140 raise DVIError("invalid pageid")
1142 print "%d: beginning of page %i" % (self
.filepos
, ispageid
[0])
1143 self
.file.readuint32()
1145 elif cmd
== _DVI_POST
:
1147 return None # nothing left
1151 actpage
= canvas
.canvas()
1152 self
.actpage
= actpage
# XXX should be removed ...
1153 self
.actpage
.markers
= {}
1154 self
.pos
= [0, 0, 0, 0, 0, 0]
1155 self
.activetype1font
= None
1157 # Since we do not know which dvi pages the actual PS file contains later on,
1158 # we have to keep track of used char informations separately for each dvi page.
1159 # In order to do so, the already defined fonts have to be copied and their
1160 # used char informations have to be reset
1161 for nr
in self
.fonts
.keys():
1162 self
.fonts
[nr
] = copy
.copy(self
.fonts
[nr
])
1163 self
.fonts
[nr
].clearusedchars()
1167 self
.filepos
= afile
.tell()
1169 cmd
= afile
.readuchar()
1170 except struct
.error
:
1171 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1172 # so we have to continue with the rest of the dvi file
1173 self
._pop
_dvistring
()
1177 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
1179 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
1180 self
.putchar(afile
.readint(cmd
- _DVI_SET1234
+ 1))
1181 elif cmd
== _DVI_SETRULE
:
1182 self
.putrule(afile
.readint32(), afile
.readint32())
1183 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1184 self
.putchar(afile
.readint(cmd
- _DVI_PUT1234
+ 1), advancepos
=0)
1185 elif cmd
== _DVI_PUTRULE
:
1186 self
.putrule(afile
.readint32(), afile
.readint32(), 0)
1187 elif cmd
== _DVI_EOP
:
1190 print "%d: eop" % self
.filepos
1193 elif cmd
== _DVI_PUSH
:
1194 self
.stack
.append(list(self
.pos
))
1196 print "%d: push" % self
.filepos
1197 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)" %
1198 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
1199 elif cmd
== _DVI_POP
:
1201 self
.pos
= self
.stack
.pop()
1203 print "%d: pop" % self
.filepos
1204 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)" %
1205 (( len(self
.stack
),)+tuple(self
.pos
)))
1206 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1208 dh
= afile
.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1210 print ("%d: right%d %d h:=%d%+d=%d, hh:=???" %
1212 cmd
- _DVI_RIGHT1234
+ 1,
1216 self
.pos
[_POS_H
]+dh
))
1217 self
.pos
[_POS_H
] += dh
1218 elif cmd
== _DVI_W0
:
1221 print ("%d: w0 %d h:=%d%+d=%d, hh:=???" %
1226 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1227 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1228 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1230 self
.pos
[_POS_W
] = afile
.readint(cmd
- _DVI_W1234
+ 1, 1)
1232 print ("%d: w%d %d h:=%d%+d=%d, hh:=???" %
1234 cmd
- _DVI_W1234
+ 1,
1238 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1239 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1240 elif cmd
== _DVI_X0
:
1243 print ("%d: x0 %d h:=%d%+d=%d, hh:=???" %
1248 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1249 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1250 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1252 self
.pos
[_POS_X
] = afile
.readint(cmd
- _DVI_X1234
+ 1, 1)
1254 print ("%d: x%d %d h:=%d%+d=%d, hh:=???" %
1256 cmd
- _DVI_X1234
+ 1,
1260 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1261 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1262 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1264 dv
= afile
.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1266 print ("%d: down%d %d v:=%d%+d=%d, vv:=???" %
1268 cmd
- _DVI_DOWN1234
+ 1,
1272 self
.pos
[_POS_V
]+dv
))
1273 self
.pos
[_POS_V
] += dv
1274 elif cmd
== _DVI_Y0
:
1277 print ("%d: y0 %d v:=%d%+d=%d, vv:=???" %
1282 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1283 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1284 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1286 self
.pos
[_POS_Y
] = afile
.readint(cmd
- _DVI_Y1234
+ 1, 1)
1288 print ("%d: y%d %d v:=%d%+d=%d, vv:=???" %
1290 cmd
- _DVI_Y1234
+ 1,
1294 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1295 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1296 elif cmd
== _DVI_Z0
:
1298 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1299 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1301 self
.pos
[_POS_Z
] = afile
.readint(cmd
- _DVI_Z1234
+ 1, 1)
1302 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1303 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1304 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
1305 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1306 # note that according to the DVI docs, for four byte font numbers,
1307 # the font number is signed. Don't ask why!
1308 self
.usefont(afile
.readint(cmd
- _DVI_FNT1234
+ 1, cmd
== _DVI_FNT1234
+ 3))
1309 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1310 self
.special(afile
.read(afile
.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1311 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1312 if cmd
== _DVI_FNTDEF1234
:
1313 num
= afile
.readuchar()
1314 elif cmd
== _DVI_FNTDEF1234
+1:
1315 num
= afile
.readuint16()
1316 elif cmd
== _DVI_FNTDEF1234
+2:
1317 num
= afile
.readuint24()
1318 elif cmd
== _DVI_FNTDEF1234
+3:
1319 # Cool, here we have according to docu a signed int. Why?
1320 num
= afile
.readint32()
1321 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1326 afile
.read(afile
.readuchar()+afile
.readuchar()))
1331 ##############################################################################
1333 ##############################################################################
1335 _VF_LONG_CHAR
= 242 # character packet (long version)
1336 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1337 _VF_PRE
= _DVI_PRE
# preamble
1338 _VF_POST
= _DVI_POST
# postamble
1340 _VF_ID
= 202 # VF id byte
1342 class VFError(exceptions
.Exception): pass
1345 def __init__(self
, filename
, scale
, tfmconv
, pyxconv
, fontmap
, debug
=0):
1346 self
.filename
= filename
1348 self
.tfmconv
= tfmconv
1349 self
.pyxconv
= pyxconv
1350 self
.fontmap
= fontmap
1352 self
.fonts
= {} # used fonts
1353 self
.widths
= {} # widths of defined chars
1354 self
.chardefs
= {} # dvi chunks for defined chars
1356 afile
= binfile(self
.filename
, "rb")
1358 cmd
= afile
.readuchar()
1360 if afile
.readuchar() != _VF_ID
: raise VFError
1361 comment
= afile
.read(afile
.readuchar())
1362 self
.cs
= afile
.readuint32()
1363 self
.ds
= afile
.readuint32()
1368 cmd
= afile
.readuchar()
1369 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1371 if cmd
== _VF_FNTDEF1234
:
1372 num
= afile
.readuchar()
1373 elif cmd
== _VF_FNTDEF1234
+1:
1374 num
= afile
.readuint16()
1375 elif cmd
== _VF_FNTDEF1234
+2:
1376 num
= afile
.readuint24()
1377 elif cmd
== _VF_FNTDEF1234
+3:
1378 num
= afile
.readint32()
1379 c
= afile
.readint32()
1380 s
= afile
.readint32() # relative scaling used for font (fix_word)
1381 d
= afile
.readint32() # design size of font
1382 fontname
= afile
.read(afile
.readuchar()+afile
.readuchar())
1384 # rescaled size of font: s is relative to the scaling
1385 # of the virtual font itself. Note that realscale has
1386 # to be a fix_word (like s)
1387 # XXX: check rounding
1388 reals
= int(round(self
.scale
* (16*self
.ds
/16777216L) * s
))
1390 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1391 # (fontname, self.scale, self.ds, s, reals)
1394 # XXX allow for virtual fonts here too
1395 self
.fonts
[num
] = type1font(fontname
, c
, reals
, d
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
1396 elif cmd
== _VF_LONG_CHAR
:
1397 # character packet (long form)
1398 pl
= afile
.readuint32() # packet length
1399 cc
= afile
.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1400 tfm
= afile
.readuint24() # character width
1401 dvi
= afile
.read(pl
) # dvi code of character
1402 self
.widths
[cc
] = tfm
1403 self
.chardefs
[cc
] = dvi
1404 elif cmd
< _VF_LONG_CHAR
:
1405 # character packet (short form)
1406 cc
= afile
.readuchar() # char code
1407 tfm
= afile
.readuint24() # character width
1408 dvi
= afile
.read(cmd
)
1409 self
.widths
[cc
] = tfm
1410 self
.chardefs
[cc
] = dvi
1411 elif cmd
== _VF_POST
:
1421 def getchar(self
, cc
):
1422 return self
.chardefs
[cc
]