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_pt())
438 result
= [prolog
.fontdefinition(self
.font
,
439 self
.font
.getbasepsname(),
440 self
.font
.getfontfile(),
441 self
.font
.getencodingfile(),
442 self
.font
.usedchars
)]
443 if self
.font
.getencoding():
444 result
.append(_ReEncodeFont
)
445 result
.append(prolog
.fontencoding(self
.font
.getencoding(), self
.font
.getencodingfile()))
446 result
.append(prolog
.fontreencoding(self
.font
.getpsname(), self
.font
.getbasepsname(), self
.font
.getencoding()))
450 class _show(base
.PSCmd
):
452 def __init__(self
, x
, y
):
460 def addchar(self
, width
, height
, depth
, char
):
462 if height
> self
.height
:
464 if depth
> self
.depth
:
466 self
.chars
.append(char
)
469 return bbox
.bbox_pt(self
.x
, self
.y
-self
.depth
, self
.x
+self
.width
, self
.y
+self
.height
)
471 def outputPS(self
, file):
473 for char
in self
.chars
:
474 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>\\":
475 ascii
= "%s" % chr(char
)
477 ascii
= "\\%03o" % char
479 file.write("%g %g moveto (%s) show\n" % (self
.x
, self
.y
, outstring
))
481 def outputPDF(self
, file):
483 for char
in self
.chars
:
484 if char
> 32 and char
< 127 and chr(char
) not in "()[]<>\\":
485 ascii
= "%s" % chr(char
)
487 ascii
= "\\%03o" % char
489 # file.write("%f %f Td (%s) Tj\n" % (self.x, self.y, outstring))
490 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self
.x
, self
.y
, outstring
))
495 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
497 def __init__(self
, s
):
498 """ construct font mapping from line s of font mapping file """
499 self
.texname
= self
.basepsname
= self
.fontfile
= None
502 self
.encodingfile
= None
504 # supported postscript fragments occuring in psfonts.map
505 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
509 match
= self
.tokenpattern
.match(s
)
511 if match
.groups()[0]:
512 tokens
.append('"%s"' % match
.groups()[0])
514 tokens
.append(match
.groups()[2])
517 raise RuntimeError("wrong syntax")
520 if token
.startswith("<"):
521 if token
.startswith("<<"):
522 # XXX: support non-partial download here
523 self
.fontfile
= token
[2:]
524 elif token
.startswith("<["):
525 self
.encodingfile
= token
[2:]
526 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
527 self
.fontfile
= token
[1:]
528 elif token
.endswith(".enc"):
529 self
.encodingfile
= token
[1:]
531 raise RuntimeError("wrong syntax")
532 elif token
.startswith('"'):
533 pscode
= token
[1:-1].split()
534 # parse standard postscript code fragments
537 arg
, cmd
= pscode
[:2]
539 raise RuntimeError("Unsupported Postscript fragment '%s'" % pscode
)
541 if cmd
== "ReEncodeFont":
542 self
.reencodefont
= arg
543 elif cmd
== "ExtendFont":
544 self
.extendfont
= arg
545 elif cmd
== "SlantFont":
548 raise RuntimeError("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
550 if self
.texname
is None:
553 self
.basepsname
= token
554 if self
.basepsname
is None:
555 self
.basepsname
= self
.texname
558 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
559 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
563 def readfontmap(filenames
):
564 """ read font map from filename (without path) """
566 for filename
in filenames
:
567 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_fontmap_format
)
568 # try also the oft-used registration as dvips config file
570 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
572 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
573 mapfile
= open(mappath
, "r")
575 for line
in mapfile
.readlines():
578 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
580 fm
= fontmapping(line
)
581 except RuntimeError, e
:
582 sys
.stderr
.write("*** PyX Warning: Ignoring line %i in mapping file '%s': %s\n" % (lineno
, filename
, e
))
584 fontmap
[fm
.texname
] = fm
590 def __init__(self
, name
, c
, q
, d
, tfmconv
, conv
, debug
=0):
592 self
.q
= q
# desired size of font (fix_word) in tex points
593 self
.d
= d
# design size of font (fix_word) in tex points
594 self
.tfmconv
= tfmconv
# conversion factor from tfm units to dvi units
595 self
.conv
= conv
# conversion factor from dvi units to PostScript points
596 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
598 raise TFMError("cannot find %s.tfm" % self
.name
)
599 self
.tfmfile
= tfmfile(tfmpath
, debug
)
601 # We only check for equality of font checksums if none of them
602 # is zero. The case c == 0 happend in some VF files and
603 # according to the VFtoVP documentation, paragraph 40, a check
604 # is only performed if tfmfile.checksum > 0. Anyhow, being
605 # more generous here seems to be reasonable
606 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!=0:
607 raise DVIError("check sums do not agree: %d vs. %d" %
608 (self
.tfmfile
.checksum
, c
))
610 # tfmfile.designsizeraw is the design size of the font as a fix_word
611 if abs(self
.tfmfile
.designsizeraw
- d
) > 2:
612 raise DVIError("design sizes do not agree: %d vs. %d" % (self
.tfmfile
.designsizeraw
, d
))
613 if q
< 0 or q
> 134217728:
614 raise DVIError("font '%s' not loaded: bad scale" % self
.name
)
615 if d
< 0 or d
> 134217728:
616 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
620 # for bookkeeping of used characters
621 self
.usedchars
= [0] * 256
624 return "font %s designed at %g tex pts used at %g tex pts" % (self
.name
,
625 16.0*self
.d
/16777216L,
626 16.0*self
.q
/16777216L)
631 def getsize_pt(self
):
632 """ return size of font in (PS) points """
633 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
634 # to the corresponding float. Furthermore, we have to convert from TeX
635 # points to points, hence the factor 72/72.27.
636 return 16L*self
.q
/16777216L*72/72.27
638 def _convert_tfm_to_dvi(self
, length
):
639 return int(round(16L*length
*self
.q
/16777216L*self
.tfmconv
))
641 def _convert_tfm_to_pt(self
, length
):
642 return int(round(16L*length
*self
.q
/16777216L*self
.tfmconv
)) * self
.conv
644 # routines returning lengths as integers in dvi units
646 def getwidth_dvi(self
, charcode
):
647 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
649 def getheight_dvi(self
, charcode
):
650 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
652 def getdepth_dvi(self
, charcode
):
653 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
655 def getitalic_dvi(self
, charcode
):
656 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
658 # routines returning lengths as floats in PostScript points
660 def getwidth_pt(self
, charcode
):
661 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
663 def getheight_pt(self
, charcode
):
664 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
666 def getdepth_pt(self
, charcode
):
667 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
669 def getitalic_pt(self
, charcode
):
670 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
672 def markcharused(self
, charcode
):
673 self
.usedchars
[charcode
] = 1
675 def mergeusedchars(self
, otherfont
):
676 for i
in range(len(self
.usedchars
)):
677 self
.usedchars
[i
] = self
.usedchars
[i
] or otherfont
.usedchars
[i
]
679 def clearusedchars(self
):
680 self
.usedchars
= [0] * 256
683 class type1font(font
):
684 def __init__(self
, name
, c
, q
, d
, tfmconv
, conv
, fontmap
, debug
=0):
685 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, conv
, debug
)
686 self
.fontmapping
= fontmap
.get(name
)
687 if self
.fontmapping
is None:
688 raise RuntimeError("no information for font '%s' found in font mapping file, aborting" % name
)
690 def getbasepsname(self
):
691 return self
.fontmapping
.basepsname
694 if self
.fontmapping
.reencodefont
:
695 return "%s-%s" % (self
.fontmapping
.basepsname
, self
.fontmapping
.reencodefont
)
697 return self
.fontmapping
.basepsname
699 def getfontfile(self
):
700 return self
.fontmapping
.fontfile
702 def getencoding(self
):
703 return self
.fontmapping
.reencodefont
705 def getencodingfile(self
):
706 return self
.fontmapping
.encodingfile
709 class virtualfont(font
):
710 def __init__(self
, name
, c
, q
, d
, tfmconv
, conv
, fontmap
, debug
=0):
711 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, conv
, debug
)
712 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
713 if fontpath
is None or not len(fontpath
):
715 self
.vffile
= vffile(fontpath
, self
.scale
, tfmconv
, conv
, fontmap
, debug
> 1)
718 """ return fonts used in virtual font itself """
719 return self
.vffile
.getfonts()
721 def getchar(self
, cc
):
722 """ return dvi chunk corresponding to char code cc """
723 return self
.vffile
.getchar(cc
)
726 ##############################################################################
728 ##############################################################################
730 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
731 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
732 _DVI_SET1234
= 128 # typeset a character and move right
733 _DVI_SETRULE
= 132 # typeset a rule and move right
734 _DVI_PUT1234
= 133 # typeset a character
735 _DVI_PUTRULE
= 137 # typeset a rule
736 _DVI_NOP
= 138 # no operation
737 _DVI_BOP
= 139 # beginning of page
738 _DVI_EOP
= 140 # ending of page
739 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
740 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
741 _DVI_RIGHT1234
= 143 # move right
742 _DVI_W0
= 147 # move right by w
743 _DVI_W1234
= 148 # move right and set w
744 _DVI_X0
= 152 # move right by x
745 _DVI_X1234
= 153 # move right and set x
746 _DVI_DOWN1234
= 157 # move down
747 _DVI_Y0
= 161 # move down by y
748 _DVI_Y1234
= 162 # move down and set y
749 _DVI_Z0
= 166 # move down by z
750 _DVI_Z1234
= 167 # move down and set z
751 _DVI_FNTNUMMIN
= 171 # set current font (range min)
752 _DVI_FNTNUMMAX
= 234 # set current font (range max)
753 _DVI_FNT1234
= 235 # set current font
754 _DVI_SPECIAL1234
= 239 # special (dvi extention)
755 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
756 _DVI_PRE
= 247 # preamble
757 _DVI_POST
= 248 # postamble beginning
758 _DVI_POSTPOST
= 249 # postamble ending
760 _DVI_VERSION
= 2 # dvi version
762 # position variable indices
774 _READ_POST
= 4 # XXX not used
775 _READ_POSTPOST
= 5 # XXX not used
779 class DVIError(exceptions
.Exception): pass
781 # save and restore colors
783 class _savecolor(base
.PSOp
):
784 def outputPS(self
, file):
785 file.write("currentcolor currentcolorspace\n")
787 def outputPDF(self
, file):
791 class _restorecolor(base
.PSOp
):
792 def outputPS(self
, file):
793 file.write("setcolorspace setcolor\n")
795 def outputPDF(self
, file):
798 class _savetrafo(base
.PSOp
):
799 def outputPS(self
, file):
800 file.write("matrix currentmatrix\n")
802 def outputPDF(self
, file):
806 class _restoretrafo(base
.PSOp
):
807 def outputPS(self
, file):
808 file.write("setmatrix\n")
810 def outputPDF(self
, file):
816 def __init__(self
, filename
, fontmap
, debug
=0):
817 """ opens the dvi file and reads the preamble """
818 self
.filename
= filename
819 self
.fontmap
= fontmap
823 self
.activefont
= None
825 # stack of fonts and fontscale currently used (used for VFs)
829 # pointer to currently active page
832 # currently active output: show instance currently used and
833 # the corresponding type 1 font
834 self
.activeshow
= None
835 self
.activetype1font
= None
837 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
840 self
.file = binfile(self
.filename
, "rb")
842 # currently read byte in file (for debugging output)
850 """ flush currently active string """
851 if self
.activeshow
is not None:
853 print "[%s]" % "".join([chr(char
) for char
in self
.activeshow
.chars
])
854 self
.actpage
.insert(self
.activeshow
)
855 self
.activeshow
= None
858 """ activate the font if is not yet active, closing a currently active
859 text object and flushing the output"""
860 if isinstance(self
.activefont
, type1font
):
862 if self
.activetype1font
!= self
.activefont
and self
.activefont
:
863 self
.actpage
.insert(_begintextobject())
864 self
.actpage
.insert(selectfont(self
.activefont
))
865 self
.activetype1font
= self
.activefont
869 if self
.activetype1font
:
870 self
.actpage
.insert(_endtextobject())
871 self
.activetype1font
= None
873 def putrule(self
, height
, width
, advancepos
=1):
875 x1
= self
.pos
[_POS_H
] * self
.conv
876 y1
= -self
.pos
[_POS_V
] * self
.conv
877 w
= width
* self
.conv
878 h
= height
* self
.conv
880 if height
> 0 and width
> 0:
882 pixelw
= int(width
*self
.trueconv
*self
.mag
/1000.0)
883 if pixelw
< width
*self
.conv
: pixelw
+= 1
884 pixelh
= int(height
*self
.trueconv
*self
.mag
/1000.0)
885 if pixelh
< height
*self
.conv
: pixelh
+= 1
887 print ("%d: %srule height %d, width %d (%dx%d pixels)" %
888 (self
.filepos
, advancepos
and "set" or "put", height
, width
, pixelh
, pixelw
))
889 self
.actpage
.fill(path
.rect_pt(x1
, y1
, w
, h
))
892 print ("%d: %srule height %d, width %d (invisible)" %
893 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
897 print (" h:=%d+%d=%d, hh:=%d" %
898 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
, 0))
899 self
.pos
[_POS_H
] += width
901 def putchar(self
, char
, advancepos
=1):
902 dx
= advancepos
and self
.activefont
.getwidth_dvi(char
) or 0
905 print ("%d: %schar%d h:=%d+%d=%d, hh:=%d" %
907 advancepos
and "set" or "put",
909 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
,
912 if isinstance(self
.activefont
, type1font
):
913 if self
.activeshow
is None:
915 self
.activeshow
= _show(self
.pos
[_POS_H
] * self
.conv
, -self
.pos
[_POS_V
] * self
.conv
)
916 width
= self
.activefont
.getwidth_dvi(char
) * self
.conv
917 height
= self
.activefont
.getheight_dvi(char
) * self
.conv
918 depth
= self
.activefont
.getdepth_dvi(char
) * self
.conv
919 self
.activeshow
.addchar(width
, height
, depth
, char
)
921 self
.activefont
.markcharused(char
)
922 self
.pos
[_POS_H
] += dx
924 # virtual font handling
925 afterpos
= list(self
.pos
)
926 afterpos
[_POS_H
] += dx
927 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
928 self
.activefont
.getsize_pt())
935 def usefont(self
, fontnum
):
937 print ("%d: fntnum%i current font is %s" %
939 fontnum
, self
.fonts
[fontnum
].name
))
941 self
.activefont
= self
.fonts
[fontnum
]
944 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
945 # cmdnr: type of fontdef command (only used for debugging output)
947 # q: scaling factor (fix_word)
948 # Note that q is actually s in large parts of the documentation.
949 # d: design size (fix_word)
952 font
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.conv
, self
.fontmap
, self
.debug
> 1)
953 except (TypeError, RuntimeError):
954 font
= type1font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.conv
, self
.fontmap
, self
.debug
> 1)
956 self
.fonts
[num
] = font
959 print "%d: fntdef%d %i: %s" % (self
.filepos
, cmdnr
, num
, fontname
)
961 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
963 # scalestring = scale!=1000 and " scaled %d" % scale or ""
964 # print ("Font %i: %s%s---loaded at size %d DVI units" %
965 # (num, fontname, scalestring, q))
967 # print " (this font is magnified %d%%)" % round(scale/10)
969 def special(self
, s
):
970 x
= self
.pos
[_POS_H
] * self
.conv
971 y
= -self
.pos
[_POS_V
] * self
.conv
973 print "%d: xxx '%s'" % (self
.filepos
, s
)
974 if not s
.startswith("PyX:"):
975 if s
.startswith("Warning:"):
976 sys
.stderr
.write("*** PyX Warning: ignoring special '%s'\n" % s
)
979 raise RuntimeError("the special '%s' cannot be handled by PyX, aborting" % s
)
981 # it is in general not safe to continue using the currently active font because
982 # the specials may involve some gsave/grestore operations
985 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
986 if command
=="color_begin":
988 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
989 elif args
[0]=="gray":
990 c
= color
.gray(float(args
[1]))
992 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
994 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
996 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
997 elif args
[0]=="texnamed":
999 c
= getattr(color
.cmyk
, args
[1])
1000 except AttributeError:
1001 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
1003 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
1004 self
.actpage
.insert(_savecolor())
1005 self
.actpage
.insert(c
)
1006 elif command
=="color_end":
1007 self
.actpage
.insert(_restorecolor())
1008 elif command
=="rotate_begin":
1009 self
.actpage
.insert(_savetrafo())
1010 self
.actpage
.insert(trafo
.rotate_pt(float(args
[0]), x
, y
))
1011 elif command
=="rotate_end":
1012 self
.actpage
.insert(_restoretrafo())
1013 elif command
=="scale_begin":
1014 self
.actpage
.insert(_savetrafo())
1015 self
.actpage
.insert(trafo
.scale_pt(float(args
[0]), float(args
[1]), x
, y
))
1016 elif command
=="scale_end":
1017 self
.actpage
.insert(_restoretrafo())
1018 elif command
=="epsinclude":
1022 name
, value
= arg
.split("=")
1023 argdict
[name
] = value
1025 # construct kwargs for epsfile constructor
1027 epskwargs
["filename"] = argdict
["file"]
1028 epskwargs
["bbox"] = bbox
.bbox_pt(float(argdict
["llx"]), float(argdict
["lly"]),
1029 float(argdict
["urx"]), float(argdict
["ury"]))
1030 if argdict
.has_key("width"):
1031 epskwargs
["width"] = float(argdict
["width"]) * unit
.t_pt
1032 if argdict
.has_key("height"):
1033 epskwargs
["height"] = float(argdict
["height"]) * unit
.t_pt
1034 if argdict
.has_key("clip"):
1035 epskwargs
["clip"] = int(argdict
["clip"])
1036 self
.actpage
.insert(epsfile
.epsfile(x
* unit
.t_pt
, y
* unit
.t_pt
, **epskwargs
))
1037 elif command
=="marker":
1039 raise RuntimeError("marker contains spaces")
1041 if c
not in string
.digits
+ string
.letters
+ "@":
1042 raise RuntimeError("marker contains invalid characters")
1043 if self
.actpage
.markers
.has_key(args
[0]):
1044 raise RuntimeError("marker name occurred several times")
1045 self
.actpage
.markers
[args
[0]] = x
* unit
.t_pt
, y
* unit
.t_pt
1047 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
1050 # routines for pushing and popping different dvi chunks on the reader
1052 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
1053 """ push dvi string with defined fonts on top of reader
1054 stack. Every positions gets scaled relatively by the factor
1055 scale. After the interpreting of the dvi chunk has been finished,
1056 continue with self.pos=afterpos. The designsize of the virtual
1057 font is passed as a fix_word
1062 print "executing new dvi chunk"
1063 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.conv
, self
.tfmconv
))
1065 # units in vf files are relative to the size of the font and given as fix_words
1066 # which can be converted to floats by diving by 2**20
1068 self
.conv
= fontsize
/2**20
1069 rescale
= self
.conv
/oldconv
1071 self
.file = stringbinfile(dvi
)
1076 # rescale self.pos in order to be consistent with the new scaling
1077 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
1079 # since tfmconv converts from tfm units to dvi units, rescale it as well
1080 self
.tfmconv
/= rescale
1084 def _pop_dvistring(self
):
1087 print "finished executing dvi chunk"
1089 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.conv
, self
.tfmconv
= self
.statestack
.pop()
1091 # routines corresponding to the different reader states of the dvi maschine
1093 def _read_pre(self
):
1096 self
.filepos
= afile
.tell()
1097 cmd
= afile
.readuchar()
1100 elif cmd
== _DVI_PRE
:
1101 if afile
.readuchar() != _DVI_VERSION
: raise DVIError
1102 num
= afile
.readuint32()
1103 den
= afile
.readuint32()
1104 self
.mag
= afile
.readuint32()
1106 # for the interpretation of all quantities, two conversion factors
1108 # - self.tfmconv (tfm units->dvi units)
1109 # - self.conv (dvi units-> (PostScript) points)
1111 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
1113 # calculate self.conv as described in the DVIType docu
1116 self
.resolution
= 300.0
1117 # self.trueconv = conv in DVIType docu
1118 self
.trueconv
= (num
/254000.0)*(self
.resolution
/den
)
1120 # self.conv is the conversion factor from the dvi units
1121 # to (PostScript) points. It consists of
1122 # - self.mag/1000.0: magstep scaling
1123 # - self.trueconv: conversion from dvi units to pixels
1124 # - 1/self.resolution: conversion from pixels to inch
1125 # - 72 : conversion from inch to points
1126 self
.conv
= self
.mag
/1000.0*self
.trueconv
/self
.resolution
*72
1128 comment
= afile
.read(afile
.readuchar())
1133 def readpage(self
, pageid
=None):
1134 """ reads a page from the dvi file
1136 This routine reads a page from the dvi file which is
1137 returned as a canvas. When there is no page left in the
1138 dvifile, None is returned and the file is closed properly."""
1142 self
.filepos
= self
.file.tell()
1143 cmd
= self
.file.readuchar()
1146 elif cmd
== _DVI_BOP
:
1148 ispageid
= [self
.file.readuint32() for i
in range(10)]
1149 #if ispageid[:3] != [ord("P"), ord("y"), ord("X")] or ispageid[4:] != [0, 0, 0, 0, 0, 0]:
1150 if pageid
is not None and ispageid
!= pageid
:
1151 raise DVIError("invalid pageid")
1153 print "%d: beginning of page %i" % (self
.filepos
, ispageid
[0])
1154 self
.file.readuint32()
1156 elif cmd
== _DVI_POST
:
1158 return None # nothing left
1162 actpage
= canvas
.canvas()
1163 self
.actpage
= actpage
# XXX should be removed ...
1164 self
.actpage
.markers
= {}
1165 self
.pos
= [0, 0, 0, 0, 0, 0]
1166 self
.activetype1font
= None
1168 # Since we do not know which dvi pages the actual PS file contains later on,
1169 # we have to keep track of used char informations separately for each dvi page.
1170 # In order to do so, the already defined fonts have to be copied and their
1171 # used char informations have to be reset
1172 for nr
in self
.fonts
.keys():
1173 self
.fonts
[nr
] = copy
.copy(self
.fonts
[nr
])
1174 self
.fonts
[nr
].clearusedchars()
1178 self
.filepos
= afile
.tell()
1180 cmd
= afile
.readuchar()
1181 except struct
.error
:
1182 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1183 # so we have to continue with the rest of the dvi file
1184 self
._pop
_dvistring
()
1188 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
1190 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
1191 self
.putchar(afile
.readint(cmd
- _DVI_SET1234
+ 1))
1192 elif cmd
== _DVI_SETRULE
:
1193 self
.putrule(afile
.readint32(), afile
.readint32())
1194 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1195 self
.putchar(afile
.readint(cmd
- _DVI_PUT1234
+ 1), advancepos
=0)
1196 elif cmd
== _DVI_PUTRULE
:
1197 self
.putrule(afile
.readint32(), afile
.readint32(), 0)
1198 elif cmd
== _DVI_EOP
:
1201 print "%d: eop" % self
.filepos
1204 elif cmd
== _DVI_PUSH
:
1205 self
.stack
.append(list(self
.pos
))
1207 print "%d: push" % self
.filepos
1208 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1209 (( len(self
.stack
)-1,)+tuple(self
.pos
)))
1210 elif cmd
== _DVI_POP
:
1212 self
.pos
= self
.stack
.pop()
1214 print "%d: pop" % self
.filepos
1215 print ("level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=,vv=)" %
1216 (( len(self
.stack
),)+tuple(self
.pos
)))
1217 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1219 dh
= afile
.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1221 print ("%d: right%d %d h:=%d%+d=%d, hh:=" %
1223 cmd
- _DVI_RIGHT1234
+ 1,
1227 self
.pos
[_POS_H
]+dh
))
1228 self
.pos
[_POS_H
] += dh
1229 elif cmd
== _DVI_W0
:
1232 print ("%d: w0 %d h:=%d%+d=%d, hh:=" %
1237 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1238 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1239 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1241 self
.pos
[_POS_W
] = afile
.readint(cmd
- _DVI_W1234
+ 1, 1)
1243 print ("%d: w%d %d h:=%d%+d=%d, hh:=" %
1245 cmd
- _DVI_W1234
+ 1,
1249 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1250 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1251 elif cmd
== _DVI_X0
:
1253 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1254 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1256 self
.pos
[_POS_X
] = afile
.readint(cmd
- _DVI_X1234
+ 1, 1)
1257 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1258 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1260 dv
= afile
.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1262 print ("%d: down%d %d v:=%d%+d=%d, vv:=" %
1264 cmd
- _DVI_DOWN1234
+ 1,
1268 self
.pos
[_POS_V
]+dv
))
1269 self
.pos
[_POS_V
] += dv
1270 elif cmd
== _DVI_Y0
:
1273 print ("%d: y0 %d v:=%d%+d=%d, vv:=" %
1278 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1279 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1280 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1282 self
.pos
[_POS_Y
] = afile
.readint(cmd
- _DVI_Y1234
+ 1, 1)
1284 print ("%d: y%d %d v:=%d%+d=%d, vv:=" %
1286 cmd
- _DVI_Y1234
+ 1,
1290 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1291 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1292 elif cmd
== _DVI_Z0
:
1294 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1295 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1297 self
.pos
[_POS_Z
] = afile
.readint(cmd
- _DVI_Z1234
+ 1, 1)
1298 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1299 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1300 self
.usefont(cmd
- _DVI_FNTNUMMIN
)
1301 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1302 self
.usefont(afile
.readint(cmd
- _DVI_FNT1234
+ 1, 1))
1303 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1304 self
.special(afile
.read(afile
.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1305 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1306 if cmd
== _DVI_FNTDEF1234
:
1307 num
= afile
.readuchar()
1308 elif cmd
== _DVI_FNTDEF1234
+1:
1309 num
= afile
.readuint16()
1310 elif cmd
== _DVI_FNTDEF1234
+2:
1311 num
= afile
.readuint24()
1312 elif cmd
== _DVI_FNTDEF1234
+3:
1313 # Cool, here we have according to docu a signed int. Why?
1314 num
= afile
.readint32()
1315 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1320 afile
.read(afile
.readuchar()+afile
.readuchar()))
1325 ##############################################################################
1327 ##############################################################################
1329 _VF_LONG_CHAR
= 242 # character packet (long version)
1330 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1331 _VF_PRE
= _DVI_PRE
# preamble
1332 _VF_POST
= _DVI_POST
# postamble
1334 _VF_ID
= 202 # VF id byte
1336 class VFError(exceptions
.Exception): pass
1339 def __init__(self
, filename
, scale
, tfmconv
, conv
, fontmap
, debug
=0):
1340 self
.filename
= filename
1342 self
.tfmconv
= tfmconv
1344 self
.fontmap
= fontmap
1346 self
.fonts
= {} # used fonts
1347 self
.widths
= {} # widths of defined chars
1348 self
.chardefs
= {} # dvi chunks for defined chars
1350 afile
= binfile(self
.filename
, "rb")
1352 cmd
= afile
.readuchar()
1354 if afile
.readuchar() != _VF_ID
: raise VFError
1355 comment
= afile
.read(afile
.readuchar())
1356 self
.cs
= afile
.readuint32()
1357 self
.ds
= afile
.readuint32()
1362 cmd
= afile
.readuchar()
1363 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1365 if cmd
== _VF_FNTDEF1234
:
1366 num
= afile
.readuchar()
1367 elif cmd
== _VF_FNTDEF1234
+1:
1368 num
= afile
.readuint16()
1369 elif cmd
== _VF_FNTDEF1234
+2:
1370 num
= afile
.readuint24()
1371 elif cmd
== _VF_FNTDEF1234
+3:
1372 num
= afile
.readint32()
1373 c
= afile
.readint32()
1374 s
= afile
.readint32() # relative scaling used for font (fix_word)
1375 d
= afile
.readint32() # design size of font
1376 fontname
= afile
.read(afile
.readuchar()+afile
.readuchar())
1378 # rescaled size of font: s is relative to the scaling
1379 # of the virtual font itself. Note that realscale has
1380 # to be a fix_word (like s)
1381 reals
= int(self
.scale
* float(fix_word(self
.ds
))*s
)
1383 # print ("defining font %s -- VF scale: %g, VF design size: %g, relative font size: %g => real size: %g" %
1384 # (fontname, self.scale, fix_word(self.ds), fix_word(s), fix_word(reals))
1388 # XXX allow for virtual fonts here too
1389 self
.fonts
[num
] = type1font(fontname
, c
, reals
, d
, self
.tfmconv
, self
.conv
, self
.fontmap
, self
.debug
> 1)
1390 elif cmd
== _VF_LONG_CHAR
:
1391 # character packet (long form)
1392 pl
= afile
.readuint32() # packet length
1393 cc
= afile
.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1394 tfm
= afile
.readuint24() # character width
1395 dvi
= afile
.read(pl
) # dvi code of character
1396 self
.widths
[cc
] = tfm
1397 self
.chardefs
[cc
] = dvi
1398 elif cmd
< _VF_LONG_CHAR
:
1399 # character packet (short form)
1400 cc
= afile
.readuchar() # char code
1401 tfm
= afile
.readuint24() # character width
1402 dvi
= afile
.read(cmd
)
1403 self
.widths
[cc
] = tfm
1404 self
.chardefs
[cc
] = dvi
1405 elif cmd
== _VF_POST
:
1415 def getchar(self
, cc
):
1416 return self
.chardefs
[cc
]