2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 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
29 def __init__(self
, word
):
35 self
.precomma
= abs(word
) >> 20
36 self
.postcomma
= abs(word
) & 0xFFFFF
39 return self
.sign
* (self
.precomma
+ 1.0*self
.postcomma
/0xFFFFF)
41 def __mul__(self
, other
):
45 result
.sign
= self
.sign
*other
.sign
46 c
= self
.postcomma
*other
.precomma
+ self
.precomma
*other
.postcomma
47 result
.precomma
= self
.precomma
*other
.precomma
+ (c
>> 20)
48 result
.postcomma
= c
& 0xFFFFF + ((self
.postcomma
*other
.postcomma
) >> 40)
54 def __init__(self
, filename
, mode
="r"):
55 self
.file = open(filename
, mode
)
61 return self
.file.tell()
64 return self
.file.eof()
66 def read(self
, bytes
):
67 return self
.file.read(bytes
)
69 def readint(self
, bytes
=4, signed
=0):
73 value
= ord(self
.file.read(1))
74 if first
and signed
and value
> 127:
77 result
= 256 * result
+ value
82 return struct
.unpack(">l", self
.file.read(4))[0]
85 return struct
.unpack(">L", self
.file.read(4))[0]
89 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
93 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
96 return struct
.unpack(">h", self
.file.read(2))[0]
99 return struct
.unpack(">H", self
.file.read(2))[0]
102 return struct
.unpack("b", self
.file.read(1))[0]
105 return struct
.unpack("B", self
.file.read(1))[0]
107 def readstring(self
, bytes
):
109 assert l
<= bytes
-1, "inconsistency in file: string too long"
110 return self
.file.read(bytes
-1)[:l
]
112 class stringbinfile(binfile
):
114 def __init__(self
, s
):
115 self
.file = cStringIO
.StringIO(s
)
119 # """ ascii file containing tokens separated by spaces.
121 # Comments beginning with % are ignored. Strings containing spaces
122 # are not handled correctly
125 # def __init__(self, filename):
126 # self.file = open(filename, "r")
129 # def gettoken(self):
130 # """ return next token or None if EOF """
131 # while not self.line:
132 # line = self.file.readline()
135 # self.line = line.split("%")[0].split()
136 # token = self.line[0]
137 # self.line = self.line[1:]
144 ##############################################################################
146 ##############################################################################
148 class TFMError(exceptions
.Exception): pass
151 class char_info_word
:
152 def __init__(self
, word
):
153 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
154 self
.height_index
= (word
& 0x00F00000) >> 20
155 self
.depth_index
= (word
& 0x000F0000) >> 16
156 self
.italic_index
= (word
& 0x0000FC00) >> 10
157 self
.tag
= (word
& 0x00000300) >> 8
158 self
.remainder
= (word
& 0x000000FF)
162 def __init__(self
, name
, debug
=0):
163 self
.file = binfile(name
, "rb")
170 self
.lf
= self
.file.readint16()
171 self
.lh
= self
.file.readint16()
172 self
.bc
= self
.file.readint16()
173 self
.ec
= self
.file.readint16()
174 self
.nw
= self
.file.readint16()
175 self
.nh
= self
.file.readint16()
176 self
.nd
= self
.file.readint16()
177 self
.ni
= self
.file.readint16()
178 self
.nl
= self
.file.readint16()
179 self
.nk
= self
.file.readint16()
180 self
.ne
= self
.file.readint16()
181 self
.np
= self
.file.readint16()
183 if not (self
.bc
-1 <= self
.ec
<= 255 and
185 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
186 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
187 raise TFMError
, "error in TFM pre-header"
190 print "lh=%d" % self
.lh
196 self
.checksum
= self
.file.readint32()
197 self
.designsizeraw
= self
.file.readint32()
198 assert self
.designsizeraw
> 0, "invald design size"
199 self
.designsize
= fix_word(self
.designsizeraw
)
201 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
202 self
.charcoding
= self
.file.readstring(40)
204 self
.charcoding
= None
207 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
208 self
.fontfamily
= self
.file.readstring(20)
210 self
.fontfamily
= None
213 print "(FAMILY %s)" % self
.fontfamily
214 print "(CODINGSCHEME %s)" % self
.charcoding
215 print "(DESINGSIZE R %f)" % self
.designsize
218 self
.sevenbitsave
= self
.file.readuchar()
219 # ignore the following two bytes
220 self
.file.readint16()
221 facechar
= self
.file.readuchar()
222 # decode ugly face specification into the Knuth suggested string
234 self
.face
= "L" + self
.face
237 self
.face
= "B" + self
.face
240 self
.face
= "M" + self
.face
243 self
.face
= self
.face
[0] + "I" + self
.face
[1]
245 self
.face
= self
.face
[0] + "R" + self
.face
[1]
250 self
.sevenbitsave
= self
.face
= None
253 # just ignore the rest
254 print self
.file.read((self
.lh
-18)*4)
260 self
.char_info
= [None]*(self
.ec
+1)
261 for charcode
in range(self
.bc
, self
.ec
+1):
262 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
263 if self
.char_info
[charcode
].width_index
== 0:
264 # disable character if width_index is zero
265 self
.char_info
[charcode
] = None
271 self
.width
= [None for width_index
in range(self
.nw
)]
272 for width_index
in range(self
.nw
):
273 # self.width[width_index] = fix_word(self.file.readint32())
274 self
.width
[width_index
] = self
.file.readint32()
280 self
.height
= [None for height_index
in range(self
.nh
)]
281 for height_index
in range(self
.nh
):
282 # self.height[height_index] = fix_word(self.file.readint32())
283 self
.height
[height_index
] = self
.file.readint32()
289 self
.depth
= [None for depth_index
in range(self
.nd
)]
290 for depth_index
in range(self
.nd
):
291 # self.depth[depth_index] = fix_word(self.file.readint32())
292 self
.depth
[depth_index
] = self
.file.readint32()
298 self
.italic
= [None for italic_index
in range(self
.ni
)]
299 for italic_index
in range(self
.ni
):
300 # self.italic[italic_index] = fix_word(self.file.readint32())
301 self
.italic
[italic_index
] = self
.file.readint32()
307 # XXX decode to lig_kern_command
309 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
310 for lig_kern_index
in range(self
.nl
):
311 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
317 self
.kern
= [None for kern_index
in range(self
.nk
)]
318 for kern_index
in range(self
.nk
):
319 # self.kern[kern_index] = fix_word(self.file.readint32())
320 self
.kern
[kern_index
] = self
.file.readint32()
326 # XXX decode to extensible_recipe
328 self
.exten
= [None for exten_index
in range(self
.ne
)]
329 for exten_index
in range(self
.ne
):
330 self
.exten
[exten_index
] = self
.file.readint32()
338 self
.param
= [None for param_index
in range(self
.np
)]
339 for param_index
in range(self
.np
):
340 self
.param
[param_index
] = self
.file.readint32()
345 # class FontEncoding:
347 # def __init__(self, filename):
348 # """ font encoding contained in filename """
349 # encpath = pykpathsea.find_file(filename, pykpathsea.kpse_tex_ps_header_format)
350 # encfile = tokenfile(encpath)
353 # self.encname = encfile.gettoken()
354 # token = encfile.gettoken()
356 # raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
357 # self.encvector = []
358 # for i in range(256):
359 # token = encfile.gettoken()
360 # if token is None or token=="]":
361 # raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
362 # self.encvector.append(token)
363 # if encfile.gettoken() != "]":
364 # raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
365 # token = encfile.gettoken()
367 # raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
368 # token = encfile.gettoken()
370 # raise RuntimeError("encoding file '%s' too long" % filename)
373 # def encode(self, charcode):
374 # return self.encvector[charcode]
376 ##############################################################################
378 ##############################################################################
380 _ReEncodeFont
= prolog
.definition("ReEncodeFont", """{
383 /newencoding exch def
384 /newfontname exch def
385 /basefontname exch def
386 /basefontdict basefontname findfont def
387 /newfontdict basefontdict maxlength dict def
389 exch dup dup /FID ne exch /Encoding ne and
390 { exch newfontdict 3 1 roll put }
394 newfontdict /FontName newfontname put
395 newfontdict /Encoding newencoding put
396 newfontname newfontdict definefont pop
401 # PostScript font selection and output primitives
404 class _begintextobject(base
.PSOp
):
405 def outputPS(self
, file):
408 def outputPDF(self
, file):
412 class _endtextobject(base
.PSOp
):
413 def outputPS(self
, file):
416 def outputPDF(self
, file):
420 class _selectfont(base
.PSOp
):
421 def __init__(self
, name
, size
):
425 def outputPS(self
, file):
426 file.write("/%s %f selectfont\n" % (self
.name
, self
.size
))
428 def outputPDF(self
, file):
429 file.write("/%s %f Tf\n" % (self
.name
, self
.size
))
432 class selectfont(_selectfont
):
433 def __init__(self
, font
):
434 _selectfont
.__init
__(self
, font
.getpsname(), font
.getsize())
438 result
= [prolog
.fontdefinition(self
.font
.getbasepsname(),
439 self
.font
.getfontfile(),
440 self
.font
.getencodingfile(),
441 self
.font
.usedchars
)]
442 if self
.font
.getencoding():
443 result
.append(_ReEncodeFont
)
444 result
.append(prolog
.fontencoding(self
.font
.getencoding(), self
.font
.getencodingfile()))
445 result
.append(prolog
.fontreencoding(self
.font
.getpsname(), self
.font
.getbasepsname(), self
.font
.getencoding()))
449 class _show(base
.PSCmd
):
451 def __init__(self
, x
, y
):
459 def addchar(self
, width
, height
, depth
, char
):
461 if height
> self
.height
:
463 if depth
> self
.depth
:
465 self
.chars
.append(char
)
468 return bbox
.bbox_pt(self
.x
, self
.y
-self
.depth
, self
.x
+self
.width
, self
.y
+self
.height
)
470 def outputPS(self
, file):
472 for char
in self
.chars
:
473 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>\\":
474 ascii
= "%s" % chr(char
)
476 ascii
= "\\%03o" % char
478 file.write("%g %g moveto (%s) show\n" % (self
.x
, self
.y
, outstring
))
480 def outputPDF(self
, file):
482 for char
in self
.chars
:
483 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>\\":
484 ascii
= "%s" % chr(char
)
486 ascii
= "\\%03o" % char
488 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
489 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self
.x
, self
.y
, outstring
))
494 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
496 def __init__(self
, s
):
497 """ construct font mapping from line s of font mapping file """
498 self
.texname
= self
.basepsname
= self
.fontfile
= None
501 self
.encodingfile
= None
503 # supported postscript fragments occuring in psfonts.map
504 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
508 match
= self
.tokenpattern
.match(s
)
510 if match
.groups()[0]:
511 tokens
.append('"%s"' % match
.groups()[0])
513 tokens
.append(match
.groups()[2])
516 raise RuntimeError("wrong syntax")
519 if token
.startswith("<"):
520 if token
.startswith("<<"):
521 # XXX: support non-partial download here
522 self
.fontfile
= token
[2:]
523 elif token
.startswith("<["):
524 self
.encodingfile
= token
[2:]
525 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
526 self
.fontfile
= token
[1:]
527 elif token
.endswith(".enc"):
528 self
.encodingfile
= token
[1:]
530 raise RuntimeError("wrong syntax")
531 elif token
.startswith('"'):
532 pscode
= token
[1:-1].split()
533 # parse standard postscript code fragments
536 arg
, cmd
= pscode
[:2]
538 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode
)
540 if cmd
== "ReEncodeFont":
541 self
.reencodefont
= arg
542 elif cmd
== "ExtendFont":
543 self
.extendfont
= arg
544 elif cmd
== "SlantFont":
547 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
549 if self
.texname
is None:
552 self
.basepsname
= token
553 if self
.basepsname
is None:
554 self
.basepsname
= self
.texname
557 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
558 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
562 def readfontmap(filenames
):
563 """ read font map from filename (without path) """
565 for filename
in filenames
:
566 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_fontmap_format
)
567 # try also the oft-used registration as dvips config file
569 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
571 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
572 mapfile
= open(mappath
, "r")
574 for line
in mapfile
.readlines():
577 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
579 fm
= fontmapping(line
)
580 except RuntimeError, e
:
581 sys
.stderr
.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno
, filename
, e
))
583 fontmap
[fm
.texname
] = fm
589 def __init__(self
, name
, c
, q
, d
, debug
=0):
591 self
.q
= q
# desired size of font (fix_word) in tex points
592 self
.d
= d
# design size of font (fix_word) in tex points
593 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
595 raise TFMError("cannot find %s.tfm" % self
.name
)
596 self
.tfmfile
= tfmfile(tfmpath
, debug
)
598 # We only check for equality of font checksums if none of them
599 # is zero. The case c == 0 happend in some VF files and
600 # according to the VFtoVP documentation, paragraph 40, a check
601 # is only performed if tfmfile.checksum > 0. Anyhow, being
602 # more generous here seems to be reasonable
603 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!=0:
604 raise DVIError("check sums do not agree: %d vs. %d" %
605 (self
.tfmfile
.checksum
, c
))
607 # tfmfile.designsizeraw is the design size of the font as a fix_word
608 if abs(self
.tfmfile
.designsizeraw
- d
) > 2:
609 raise DVIError("design sizes do not agree: %d vs. %d" % (self
.tfmfile
.designsizeraw
, d
))
610 if q
< 0 or q
> 134217728:
611 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
612 if d
< 0 or d
> 134217728:
613 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
617 # for bookkeeping of used characters
618 self
.usedchars
= [0] * 256
621 return "font %s designed at %g tex pts used at %g tex pts" % (self
.name
,
622 16.0*self
.d
/16777216L,
623 16.0*self
.q
/16777216L)
629 """ return size of font in (PS) points """
630 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
631 # to the corresponding float. Furthermore, we have to convert from TeX
632 # points to points, hence the factor 72/72.27.
633 return 16L*self
.q
/16777216L*72/72.27
635 def _convert(self
, width
):
636 return 16L*width
*self
.q
/16777216L
638 def getwidth(self
, charcode
):
639 return self
._convert
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
641 def getheight(self
, charcode
):
642 return self
._convert
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
644 def getdepth(self
, charcode
):
645 return self
._convert
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
647 def getitalic(self
, charcode
):
648 return self
._convert
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
650 def markcharused(self
, charcode
):
651 self
.usedchars
[charcode
] = 1
653 def mergeusedchars(self
, otherfont
):
654 for i
in range(len(self
.usedchars
)):
655 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
657 def clearusedchars(self
):
658 self
.usedchars
= [0] * 256
661 class type1font(font
):
662 def __init__(self
, name
, c
, q
, d
, fontmap
, debug
=0):
663 font
.__init
__(self
, name
, c
, q
, d
, debug
)
664 self
.fontmapping
= fontmap
.get(name
)
665 if self
.fontmapping
is None:
666 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name
)
668 def getbasepsname(self
):
669 return self
.fontmapping
.basepsname
672 if self
.fontmapping
.reencodefont
:
673 return "%s-%s" % (self
.fontmapping
.basepsname
, self
.fontmapping
.reencodefont
)
675 return self
.fontmapping
.basepsname
677 def getfontfile(self
):
678 return self
.fontmapping
.fontfile
680 def getencoding(self
):
681 return self
.fontmapping
.reencodefont
683 def getencodingfile(self
):
684 return self
.fontmapping
.encodingfile
687 class virtualfont(font
):
688 def __init__(self
, name
, c
, q
, d
, fontmap
, debug
=0):
689 font
.__init
__(self
, name
, c
, q
, d
, debug
)
690 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
691 if fontpath
is None or not len(fontpath
):
693 self
.vffile
= vffile(fontpath
, self
.scale
, fontmap
, debug
> 1)
696 """ return fonts used in virtual font itself """
697 return self
.vffile
.getfonts()
699 def getchar(self
, cc
):
700 """ return dvi chunk corresponding to char code cc """
701 return self
.vffile
.getchar(cc
)
704 ##############################################################################
706 ##############################################################################
708 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
709 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
710 _DVI_SET1234
= 128 # typeset a character and move right
711 _DVI_SETRULE
= 132 # typeset a rule and move right
712 _DVI_PUT1234
= 133 # typeset a character
713 _DVI_PUTRULE
= 137 # typeset a rule
714 _DVI_NOP
= 138 # no operation
715 _DVI_BOP
= 139 # beginning of page
716 _DVI_EOP
= 140 # ending of page
717 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
718 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
719 _DVI_RIGHT1234
= 143 # move right
720 _DVI_W0
= 147 # move right by w
721 _DVI_W1234
= 148 # move right and set w
722 _DVI_X0
= 152 # move right by x
723 _DVI_X1234
= 153 # move right and set x
724 _DVI_DOWN1234
= 157 # move down
725 _DVI_Y0
= 161 # move down by y
726 _DVI_Y1234
= 162 # move down and set y
727 _DVI_Z0
= 166 # move down by z
728 _DVI_Z1234
= 167 # move down and set z
729 _DVI_FNTNUMMIN
= 171 # set current font (range min)
730 _DVI_FNTNUMMAX
= 234 # set current font (range max)
731 _DVI_FNT1234
= 235 # set current font
732 _DVI_SPECIAL1234
= 239 # special (dvi extention)
733 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
734 _DVI_PRE
= 247 # preamble
735 _DVI_POST
= 248 # postamble beginning
736 _DVI_POSTPOST
= 249 # postamble ending
738 _DVI_VERSION
= 2 # dvi version
740 # position variable indices
752 _READ_POST
= 4 # XXX not used
753 _READ_POSTPOST
= 5 # XXX not used
757 class DVIError(exceptions
.Exception): pass
759 # save and restore colors
761 class _savecolor(base
.PSOp
):
762 def outputPS(self
, file):
763 file.write("currentcolor currentcolorspace\n")
766 class _restorecolor(base
.PSOp
):
767 def outputPS(self
, file):
768 file.write("setcolorspace setcolor\n")
770 class _savetrafo(base
.PSOp
):
771 def outputPS(self
, file):
772 file.write("matrix currentmatrix\n")
775 class _restoretrafo(base
.PSOp
):
776 def outputPS(self
, file):
777 file.write("setmatrix\n")
782 def __init__(self
, filename
, fontmap
, debug
=0):
783 """ opens the dvi file and reads the preamble """
784 self
.filename
= filename
785 self
.fontmap
= fontmap
789 self
.activefont
= None
791 # stack of fonts and fontscale currently used (used for VFs)
795 # pointer to currently active page
798 # currently active output: show instance currently used and
799 # the corresponding type 1 font
800 self
.activeshow
= None
801 self
.activetype1font
= None
803 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
806 self
.file = binfile(self
.filename
, "rb")
808 # currently read byte in file (for debugging output)
816 """ flush currently active string """
817 if self
.activeshow
is not None:
819 print "[%s]" % "".join([chr(char
) for char
in self
.activeshow
.chars
])
820 self
.actpage
.insert(self
.activeshow
)
821 self
.activeshow
= None
824 """ activate the font if is not yet active, closing a currently active
825 text object and flushing the output"""
826 if isinstance(self
.activefont
, type1font
):
828 if self
.activetype1font
!= self
.activefont
and self
.activefont
:
829 self
.actpage
.insert(_begintextobject())
830 self
.actpage
.insert(selectfont(self
.activefont
))
831 self
.activetype1font
= self
.activefont
835 if self
.activetype1font
:
836 self
.actpage
.insert(_endtextobject())
837 self
.activetype1font
= None
839 def putrule(self
, height
, width
, advancepos
=1):
841 x1
= self
.pos
[_POS_H
] * self
.conv
842 y1
= -self
.pos
[_POS_V
] * self
.conv
843 w
= width
* self
.conv
844 h
= height
* self
.conv
846 if height
> 0 and width
> 0:
848 pixelw
= int(width
*self
.trueconv
*self
.mag
/1000.0)
849 if pixelw
< width
*self
.conv
: pixelw
+= 1
850 pixelh
= int(height
*self
.trueconv
*self
.mag
/1000.0)
851 if pixelh
< height
*self
.conv
: pixelh
+= 1
853 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
854 (self
.filepos
, advancepos
and "set" or "put", height
, width
, pixelh
, pixelw
))
855 self
.actpage
.fill(path
.rect_pt(x1
, y1
, w
, h
))
858 print ("%d: %srule height %d, width %d (invisible)" %
859 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
863 print (" h:=%d+%d=%d, hh:=%d" %
864 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
, 0))
865 self
.pos
[_POS_H
] += width
867 def putchar(self
, char
, advancepos
=1):
868 dx
= advancepos
and int(round(self
.activefont
.getwidth(char
)*self
.tfmconv
)) or 0
871 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
873 advancepos
and "set" or "put",
875 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
878 if isinstance(self
.activefont
, type1font
):
879 if self
.activeshow
is None:
880 self
.activeshow
= _show(self
.pos
[_POS_H
] * self
.conv
, -self
.pos
[_POS_V
] * self
.conv
)
881 width
= self
.activefont
.getwidth(char
) * self
.tfmconv
* self
.conv
882 height
= self
.activefont
.getheight(char
) * self
.tfmconv
* self
.conv
883 depth
= self
.activefont
.getdepth(char
) * self
.tfmconv
* self
.conv
884 self
.activeshow
.addchar(width
, height
, depth
, char
)
886 self
.activefont
.markcharused(char
)
887 self
.pos
[_POS_H
] += dx
889 # virtual font handling
890 afterpos
= list(self
.pos
)
891 afterpos
[_POS_H
] += dx
892 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
893 self
.activefont
.getsize())
900 def usefont(self
, fontnum
):
902 print ("%d: fntnum%i current font is %s" %
904 fontnum
, self
.fonts
[fontnum
].name
))
906 self
.activefont
= self
.fonts
[fontnum
]
909 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
910 # cmdnr: type of fontdef command (only used for debugging output)
912 # q: scaling factor (fix_word)
913 # Note that q is actually s in large parts of the documentation.
914 # d: design size (fix_word)
917 font
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
918 except (TypeError, RuntimeError):
919 font
= type1font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.fontmap
, self
.debug
> 1)
921 self
.fonts
[num
] = font
924 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
926 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
928 # scalestring = scale!=1000 and " scaled %d" % scale or ""
929 # print ("Font %i: %s%s---loaded at size %d DVI units" %
930 # (num, fontname, scalestring, q))
932 # print " (this font is magnified %d%%)" % round(scale/10)
934 def special(self
, s
):
935 x
= self
.pos
[_POS_H
] * self
.conv
936 y
= -self
.pos
[_POS_V
] * self
.conv
938 print "%d: xxx '%s'" % (self
.filepos
, s
)
939 if not s
.startswith("PyX:"):
940 if s
.startswith("Warning:"):
941 sys
.stderr
.write("*** PyX Warning: ignoring special '%s'\n" % s
)
944 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s
)
946 # it is in general not safe to continue using the currently active font because
947 # the specials may involve some gsave/grestore operations
950 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
951 if command
=="color_begin":
953 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
954 elif args
[0]=="gray":
955 c
= color
.gray(float(args
[1]))
957 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
959 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
961 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
962 elif args
[0]=="texnamed":
964 c
= getattr(color
.cmyk
, args
[1])
965 except AttributeError:
966 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
968 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
969 self
.actpage
.insert(_savecolor())
970 self
.actpage
.insert(c
)
971 elif command
=="color_end":
972 self
.actpage
.insert(_restorecolor())
973 elif command
=="rotate_begin":
974 self
.actpage
.insert(_savetrafo())
975 self
.actpage
.insert(trafo
.rotate_pt(float(args
[0]), x
, y
))
976 elif command
=="rotate_end":
977 self
.actpage
.insert(_restoretrafo())
978 elif command
=="scale_begin":
979 self
.actpage
.insert(_savetrafo())
980 self
.actpage
.insert(trafo
.scale_pt(float(args
[0]), float(args
[1]), x
, y
))
981 elif command
=="scale_end":
982 self
.actpage
.insert(_restoretrafo())
983 elif command
=="epsinclude":
987 name
, value
= arg
.split("=")
988 argdict
[name
] = value
990 # construct kwargs for epsfile constructor
992 epskwargs
["filename"] = argdict
["file"]
993 epskwargs
["bbox"] = bbox
.bbox_pt(float(argdict
["llx"]), float(argdict
["lly"]),
994 float(argdict
["urx"]), float(argdict
["ury"]))
995 if argdict
.has_key("width"):
996 epskwargs
["width"] = float(argdict
["width"]) * unit
.t_pt
997 if argdict
.has_key("height"):
998 epskwargs
["height"] = float(argdict
["height"]) * unit
.t_pt
999 if argdict
.has_key("clip"):
1000 epskwargs
["clip"] = int(argdict
["clip"])
1001 self
.actpage
.insert(epsfile
.epsfile(x
* unit
.t_pt
, y
* unit
.t_pt
, **epskwargs
))
1002 elif command
=="marker":
1004 raise RuntimeError("marker contains spaces")
1006 if c
not in string
.digits
+ string
.letters
+ "@":
1007 raise RuntimeError("marker contains invalid characters")
1008 if self
.actpage
.markers
.has_key(args
[0]):
1009 raise RuntimeError("marker name occurred several times")
1010 self
.actpage
.markers
[args
[0]] = x
* unit
.t_pt
, y
* unit
.t_pt
1012 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
1015 # routines for pushing and popping different dvi chunks on the reader
1017 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
1018 """ push dvi string with defined fonts on top of reader
1019 stack. Every positions gets scaled relatively by the factor
1020 scale. After the interpreting of the dvi chunk has been finished,
1021 continue with self.pos=afterpos. The designsize of the virtual
1022 font is passed as a fix_word
1027 print "executing new dvi chunk"
1028 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.conv
, self
.tfmconv
))
1030 # units in vf files are relative to the size of the font and given as fix_words
1031 # which can be converted to floats by diving by 2**20
1033 self
.conv
= fontsize
/2**20
1034 rescale
= self
.conv
/oldconv
1036 self
.file = stringbinfile(dvi
)
1041 # rescale self.pos in order to be consistent with the new scaling
1042 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
1044 # since tfmconv converts from tfm units to dvi units, rescale it as well
1045 self
.tfmconv
/= rescale
1049 def _pop_dvistring(self
):
1052 print "finished executing dvi chunk"
1054 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.conv
, self
.tfmconv
= self
.statestack
.pop()
1056 # routines corresponding to the different reader states of the dvi maschine
1058 def _read_pre(self
):
1061 self
.filepos
= afile
.tell()
1062 cmd
= afile
.readuchar()
1065 elif cmd
== _DVI_PRE
:
1066 if afile
.readuchar() != _DVI_VERSION
: raise DVIError
1067 num
= afile
.readuint32()
1068 den
= afile
.readuint32()
1069 self
.mag
= afile
.readuint32()
1071 # for the interpretation of all quantities, two conversion factors
1073 # - self.tfmconv (tfm units->dvi units)
1074 # - self.conv (dvi units-> (PostScript) points)
1076 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
1078 # calculate self.conv as described in the DVIType docu
1081 self
.resolution
= 300.0
1082 # self.trueconv = conv in DVIType docu
1083 self
.trueconv
= (num
/254000.0)*(self
.resolution
/den
)
1085 # self.conv is the conversion factor from the dvi units
1086 # to (PostScript) points. It consists of
1087 # - self.mag/1000.0: magstep scaling
1088 # - self.trueconv: conversion from dvi units to pixels
1089 # - 1/self.resolution: conversion from pixels to inch
1090 # - 72 : conversion from inch to points
1091 self
.conv
= self
.mag
/1000.0*self
.trueconv
/self
.resolution
*72
1093 comment
= afile
.read(afile
.readuchar())
1098 def readpage(self
, pageid
=None):
1099 """ reads a page from the dvi file
1101 This routine reads a page from the dvi file which is
1102 returned as a canvas. When there is no page left in the
1103 dvifile, None is returned and the file is closed properly."""
1107 self
.filepos
= self
.file.tell()
1108 cmd
= self
.file.readuchar()
1111 elif cmd
== _DVI_BOP
:
1113 ispageid
= [self
.file.readuint32() for i
in range(10)]
1114 #if ispageid[:3] != [ord("P"), ord("y"), ord("X")] or ispageid[4:] != [0, 0, 0, 0, 0, 0]:
1115 if pageid
is not None and ispageid
!= pageid
:
1116 raise DVIError("invalid pageid")
1118 print "%d: beginning of page %i" % (self
.filepos
, ispageid
[0])
1119 self
.file.readuint32()
1121 elif cmd
== _DVI_POST
:
1123 return None # nothing left
1127 actpage
= canvas
.canvas()
1128 self
.actpage
= actpage
# XXX should be removed ...
1129 self
.actpage
.markers
= {}
1130 self
.pos
= [0, 0, 0, 0, 0, 0]
1131 self
.activetype1font
= None
1133 # Since we do not know which dvi pages the actual PS file contains later on,
1134 # we have to keep track of used char informations separately for each dvi page.
1135 # In order to do so, the already defined fonts have to be copied and their
1136 # used char informations have to be reset
1137 for nr
in self
.fonts
.keys():
1138 self
.fonts
[nr
] = copy
.copy(self
.fonts
[nr
])
1139 self
.fonts
[nr
].clearusedchars()
1143 self
.filepos
= afile
.tell()
1145 cmd
= afile
.readuchar()
1146 except struct
.error
:
1147 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1148 # so we have to continue with the rest of the dvi file
1149 self
._pop
_dvistring
()
1153 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
1155 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
1156 self
.putchar(afile
.readint(cmd
- _DVI_SET1234
+ 1))
1157 elif cmd
== _DVI_SETRULE
:
1158 self
.putrule(afile
.readint32(), afile
.readint32())
1159 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1160 self
.putchar(afile
.readint(cmd
- _DVI_PUT1234
+ 1), advancepos
=0)
1161 elif cmd
== _DVI_PUTRULE
:
1162 self
.putrule(afile
.readint32(), afile
.readint32(), 0)
1163 elif cmd
== _DVI_EOP
:
1166 print "%d: eop" % self
.filepos
1169 elif cmd
== _DVI_PUSH
:
1170 self
.stack
.append(list(self
.pos
))
1172 print "%d: push" % self
.filepos
1173 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1174 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
1175 elif cmd
== _DVI_POP
:
1177 self
.pos
= self
.stack
.pop()
1179 print "%d: pop" % self
.filepos
1180 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1181 (( len(self
.stack
),)+tuple(self
.pos
)))
1182 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1184 dh
= afile
.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1186 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1188 cmd
- _DVI_RIGHT1234
+ 1,
1192 self
.pos
[_POS_H
]+dh
))
1193 self
.pos
[_POS_H
] += dh
1194 elif cmd
== _DVI_W0
:
1197 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1202 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1203 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1204 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1206 self
.pos
[_POS_W
] = afile
.readint(cmd
- _DVI_W1234
+ 1, 1)
1208 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1210 cmd
- _DVI_W1234
+ 1,
1214 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1215 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1216 elif cmd
== _DVI_X0
:
1218 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1219 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1221 self
.pos
[_POS_X
] = afile
.readint(cmd
- _DVI_X1234
+ 1, 1)
1222 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1223 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1225 dv
= afile
.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1227 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1229 cmd
- _DVI_DOWN1234
+ 1,
1233 self
.pos
[_POS_V
]+dv
))
1234 self
.pos
[_POS_V
] += dv
1235 elif cmd
== _DVI_Y0
:
1238 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1243 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1244 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1245 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1247 self
.pos
[_POS_Y
] = afile
.readint(cmd
- _DVI_Y1234
+ 1, 1)
1249 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1251 cmd
- _DVI_Y1234
+ 1,
1255 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1256 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1257 elif cmd
== _DVI_Z0
:
1259 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1260 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1262 self
.pos
[_POS_Z
] = afile
.readint(cmd
- _DVI_Z1234
+ 1, 1)
1263 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1264 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1265 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
1266 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1267 self
.usefont(afile
.readint(cmd
- _DVI_FNT1234
+ 1, 1))
1268 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1269 self
.special(afile
.read(afile
.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1270 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1271 if cmd
== _DVI_FNTDEF1234
:
1272 num
= afile
.readuchar()
1273 elif cmd
== _DVI_FNTDEF1234
+1:
1274 num
= afile
.readuint16()
1275 elif cmd
== _DVI_FNTDEF1234
+2:
1276 num
= afile
.readuint24()
1277 elif cmd
== _DVI_FNTDEF1234
+3:
1278 # Cool, here we have according to docu a signed int. Why?
1279 num
= afile
.readint32()
1280 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1285 afile
.read(afile
.readuchar()+afile
.readuchar()))
1290 ##############################################################################
1292 ##############################################################################
1294 _VF_LONG_CHAR
= 242 # character packet (long version)
1295 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1296 _VF_PRE
= _DVI_PRE
# preamble
1297 _VF_POST
= _DVI_POST
# postamble
1299 _VF_ID
= 202 # VF id byte
1301 class VFError(exceptions
.Exception): pass
1304 def __init__(self
, filename
, scale
, fontmap
, debug
=0):
1305 self
.filename
= filename
1307 self
.fontmap
= fontmap
1309 self
.fonts
= {} # used fonts
1310 self
.widths
= {} # widths of defined chars
1311 self
.chardefs
= {} # dvi chunks for defined chars
1313 afile
= binfile(self
.filename
, "rb")
1315 cmd
= afile
.readuchar()
1317 if afile
.readuchar() != _VF_ID
: raise VFError
1318 comment
= afile
.read(afile
.readuchar())
1319 self
.cs
= afile
.readuint32()
1320 self
.ds
= afile
.readuint32()
1325 cmd
= afile
.readuchar()
1326 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1328 if cmd
== _VF_FNTDEF1234
:
1329 num
= afile
.readuchar()
1330 elif cmd
== _VF_FNTDEF1234
+1:
1331 num
= afile
.readuint16()
1332 elif cmd
== _VF_FNTDEF1234
+2:
1333 num
= afile
.readuint24()
1334 elif cmd
== _VF_FNTDEF1234
+3:
1335 num
= afile
.readint32()
1336 c
= afile
.readint32()
1337 s
= afile
.readint32() # relative scaling used for font (fix_word)
1338 d
= afile
.readint32() # design size of font
1339 fontname
= afile
.read(afile
.readuchar()+afile
.readuchar())
1341 # rescaled size of font: s is relative to the scaling
1342 # of the virtual font itself. Note that realscale has
1343 # to be a fix_word (like s)
1344 # Furthermore we have to correct for self.tfmconv
1346 reals
= int(self
.scale
* float(fix_word(self
.ds
))*s
)
1347 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1348 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1352 # XXX allow for virtual fonts here too
1353 self
.fonts
[num
] = type1font(fontname
, c
, reals
, d
, self
.fontmap
, self
.debug
> 1)
1354 elif cmd
== _VF_LONG_CHAR
:
1355 # character packet (long form)
1356 pl
= afile
.readuint32() # packet length
1357 cc
= afile
.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1358 tfm
= afile
.readuint24() # character width
1359 dvi
= afile
.read(pl
) # dvi code of character
1360 self
.widths
[cc
] = tfm
1361 self
.chardefs
[cc
] = dvi
1362 elif cmd
< _VF_LONG_CHAR
:
1363 # character packet (short form)
1364 cc
= afile
.readuchar() # char code
1365 tfm
= afile
.readuint24() # character width
1366 dvi
= afile
.read(cmd
)
1367 self
.widths
[cc
] = tfm
1368 self
.chardefs
[cc
] = dvi
1369 elif cmd
== _VF_POST
:
1379 def getchar(self
, cc
):
1380 return self
.chardefs
[cc
]