2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2005 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004,2006 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import cStringIO
, exceptions
, re
, struct
, string
, sys
, warnings
, math
26 import unit
, epsfile
, bbox
, canvas
, color
, trafo
, path
, pykpathsea
, type1font
31 def __init__(self
, filename
, mode
="r"):
32 self
.file = open(filename
, mode
)
38 return self
.file.tell()
41 return self
.file.eof()
43 def read(self
, bytes
):
44 return self
.file.read(bytes
)
46 def readint(self
, bytes
=4, signed
=0):
50 value
= ord(self
.file.read(1))
51 if first
and signed
and value
> 127:
54 result
= 256 * result
+ value
59 return struct
.unpack(">l", self
.file.read(4))[0]
62 return struct
.unpack(">L", self
.file.read(4))[0]
66 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
70 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
73 return struct
.unpack(">h", self
.file.read(2))[0]
76 return struct
.unpack(">H", self
.file.read(2))[0]
79 return struct
.unpack("b", self
.file.read(1))[0]
82 return struct
.unpack("B", self
.file.read(1))[0]
84 def readstring(self
, bytes
):
86 assert l
<= bytes
-1, "inconsistency in file: string too long"
87 return self
.file.read(bytes
-1)[:l
]
89 class stringbinfile(binfile
):
91 def __init__(self
, s
):
92 self
.file = cStringIO
.StringIO(s
)
97 ##############################################################################
99 ##############################################################################
101 class TFMError(exceptions
.Exception): pass
104 class char_info_word
:
105 def __init__(self
, word
):
106 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
107 self
.height_index
= (word
& 0x00F00000) >> 20
108 self
.depth_index
= (word
& 0x000F0000) >> 16
109 self
.italic_index
= (word
& 0x0000FC00) >> 10
110 self
.tag
= (word
& 0x00000300) >> 8
111 self
.remainder
= (word
& 0x000000FF)
115 def __init__(self
, name
, debug
=0):
116 self
.file = binfile(name
, "rb")
122 self
.lf
= self
.file.readint16()
123 self
.lh
= self
.file.readint16()
124 self
.bc
= self
.file.readint16()
125 self
.ec
= self
.file.readint16()
126 self
.nw
= self
.file.readint16()
127 self
.nh
= self
.file.readint16()
128 self
.nd
= self
.file.readint16()
129 self
.ni
= self
.file.readint16()
130 self
.nl
= self
.file.readint16()
131 self
.nk
= self
.file.readint16()
132 self
.ne
= self
.file.readint16()
133 self
.np
= self
.file.readint16()
135 if not (self
.bc
-1 <= self
.ec
<= 255 and
137 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
138 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
139 raise TFMError
, "error in TFM pre-header"
142 print "lh=%d" % self
.lh
148 self
.checksum
= self
.file.readint32()
149 self
.designsize
= self
.file.readint32()
150 assert self
.designsize
> 0, "invald design size"
152 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
153 self
.charcoding
= self
.file.readstring(40)
155 self
.charcoding
= None
158 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
159 self
.fontfamily
= self
.file.readstring(20)
161 self
.fontfamily
= None
164 print "(FAMILY %s)" % self
.fontfamily
165 print "(CODINGSCHEME %s)" % self
.charcoding
166 print "(DESINGSIZE R %f)" % 16.0*self
.designsize
/16777216L
169 self
.sevenbitsave
= self
.file.readuchar()
170 # ignore the following two bytes
171 self
.file.readint16()
172 facechar
= self
.file.readuchar()
173 # decode ugly face specification into the Knuth suggested string
185 self
.face
= "L" + self
.face
188 self
.face
= "B" + self
.face
191 self
.face
= "M" + self
.face
194 self
.face
= self
.face
[0] + "I" + self
.face
[1]
196 self
.face
= self
.face
[0] + "R" + self
.face
[1]
201 self
.sevenbitsave
= self
.face
= None
204 # just ignore the rest
205 print self
.file.read((self
.lh
-18)*4)
211 self
.char_info
= [None]*(self
.ec
+1)
212 for charcode
in range(self
.bc
, self
.ec
+1):
213 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
214 if self
.char_info
[charcode
].width_index
== 0:
215 # disable character if width_index is zero
216 self
.char_info
[charcode
] = None
222 self
.width
= [None for width_index
in range(self
.nw
)]
223 for width_index
in range(self
.nw
):
224 self
.width
[width_index
] = self
.file.readint32()
230 self
.height
= [None for height_index
in range(self
.nh
)]
231 for height_index
in range(self
.nh
):
232 self
.height
[height_index
] = self
.file.readint32()
238 self
.depth
= [None for depth_index
in range(self
.nd
)]
239 for depth_index
in range(self
.nd
):
240 self
.depth
[depth_index
] = self
.file.readint32()
246 self
.italic
= [None for italic_index
in range(self
.ni
)]
247 for italic_index
in range(self
.ni
):
248 self
.italic
[italic_index
] = self
.file.readint32()
254 # XXX decode to lig_kern_command
256 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
257 for lig_kern_index
in range(self
.nl
):
258 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
264 self
.kern
= [None for kern_index
in range(self
.nk
)]
265 for kern_index
in range(self
.nk
):
266 self
.kern
[kern_index
] = self
.file.readint32()
272 # XXX decode to extensible_recipe
274 self
.exten
= [None for exten_index
in range(self
.ne
)]
275 for exten_index
in range(self
.ne
):
276 self
.exten
[exten_index
] = self
.file.readint32()
284 self
.param
= [None for param_index
in range(self
.np
)]
285 for param_index
in range(self
.np
):
286 self
.param
[param_index
] = self
.file.readint32()
292 ##############################################################################
294 ##############################################################################
297 # PostScript font selection and output primitives
300 class UnsupportedFontFormat(Exception):
303 class UnsupportedPSFragment(Exception):
308 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
310 def __init__(self
, s
):
311 """ construct font mapping from line s of font mapping file """
312 self
.texname
= self
.basepsname
= self
.fontfile
= None
315 self
.encodingfile
= None
317 # supported postscript fragments occuring in psfonts.map
318 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
322 match
= self
.tokenpattern
.match(s
)
324 if match
.groups()[0] is not None:
325 tokens
.append('"%s"' % match
.groups()[0])
327 tokens
.append(match
.groups()[2])
330 raise RuntimeError("wrong syntax")
333 if token
.startswith("<"):
334 if token
.startswith("<<"):
335 # XXX: support non-partial download here
336 self
.fontfile
= token
[2:]
337 elif token
.startswith("<["):
338 self
.encodingfile
= token
[2:]
339 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
340 self
.fontfile
= token
[1:]
341 elif token
.endswith(".enc"):
342 self
.encodingfile
= token
[1:]
343 elif token
.endswith(".ttf"):
344 raise UnsupportedFontFormat("TrueType font")
346 raise RuntimeError("wrong syntax")
347 elif token
.startswith('"'):
348 pscode
= token
[1:-1].split()
349 # parse standard postscript code fragments
352 arg
, cmd
= pscode
[:2]
354 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s'" % pscode
)
356 if cmd
== "ReEncodeFont":
357 self
.reencodefont
= arg
358 elif cmd
== "ExtendFont":
359 self
.extendfont
= arg
360 elif cmd
== "SlantFont":
363 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
365 if self
.texname
is None:
368 self
.basepsname
= token
369 if self
.basepsname
is None:
370 self
.basepsname
= self
.texname
373 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
374 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
378 def readfontmap(filenames
):
379 """ read font map from filename (without path) """
381 for filename
in filenames
:
382 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_fontmap_format
)
383 # try also the oft-used registration as dvips config file
385 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
387 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
388 mapfile
= open(mappath
, "r")
390 for line
in mapfile
.readlines():
393 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
395 fm
= fontmapping(line
)
396 except (RuntimeError, UnsupportedPSFragment
), e
:
397 warnings
.warn("Ignoring line %i in mapping file '%s': %s" % (lineno
, filename
, e
))
398 except UnsupportedFontFormat
, e
:
401 fontmap
[fm
.texname
] = fm
408 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
410 self
.q
= q
# desired size of font (fix_word) in TeX points
411 self
.d
= d
# design size of font (fix_word) in TeX points
412 self
.tfmconv
= tfmconv
# conversion factor from tfm units to dvi units
413 self
.pyxconv
= pyxconv
# conversion factor from dvi units to PostScript points
414 self
.fontmap
= fontmap
415 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
417 raise TFMError("cannot find %s.tfm" % self
.name
)
418 self
.tfmfile
= tfmfile(tfmpath
, debug
)
420 # We only check for equality of font checksums if none of them
421 # is zero. The case c == 0 happend in some VF files and
422 # according to the VFtoVP documentation, paragraph 40, a check
423 # is only performed if tfmfile.checksum > 0. Anyhow, being
424 # more generous here seems to be reasonable
425 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!= 0:
426 raise DVIError("check sums do not agree: %d vs. %d" %
427 (self
.tfmfile
.checksum
, c
))
429 # Check whether the given design size matches the one defined in the tfm file
430 if abs(self
.tfmfile
.designsize
- d
) > 2:
431 raise DVIError("design sizes do not agree: %d vs. %d" % (self
.tfmfile
.designsize
, d
))
432 #if q < 0 or q > 134217728:
433 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
434 if d
< 0 or d
> 134217728:
435 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
443 # The following code is a very crude way to obtain the information
444 # required for the PDF font descritor. (TODO: The correct way would
445 # be to read the information from the AFM file.)
446 fontinfo
= fontinfo()
448 fontinfo
.fontbbox
= (0,
449 -self
.getdepth_ds(ord("y")),
450 self
.getwidth_ds(ord("W")),
451 self
.getheight_ds(ord("H")))
453 fontinfo
.fontbbox
= (0, -10, 100, 100)
455 fontinfo
.italicangle
= -180/math
.pi
*math
.atan(self
.tfmfile
.param
[0]/65536.0)
457 fontinfo
.italicangle
= 0
458 fontinfo
.ascent
= fontinfo
.fontbbox
[3]
459 fontinfo
.descent
= fontinfo
.fontbbox
[1]
461 fontinfo
.capheight
= self
.getheight_ds(ord("h"))
463 fontinfo
.capheight
= 100
465 fontinfo
.vstem
= self
.getwidth_ds(ord("."))/3
471 return "font %s designed at %g TeX pts used at %g TeX pts" % (self
.name
,
472 16.0*self
.d
/16777216L,
473 16.0*self
.q
/16777216L)
476 def getsize_pt(self
):
477 """ return size of font in (PS) points """
478 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
479 # to the corresponding float. Furthermore, we have to convert from TeX
480 # points to points, hence the factor 72/72.27.
481 return 16L*self
.q
/16777216L*72/72.27
483 def _convert_tfm_to_dvi(self
, length
):
484 # doing the integer math with long integers will lead to different roundings
485 # return 16*length*int(round(self.q*self.tfmconv))/16777216
487 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
488 # z = int(round(self.q*self.tfmconv))
489 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
490 # assert b0 == 0 or b0 == 255
492 # while z >= 8388608:
496 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
498 # result = result - (z << (8-shift))
500 # however, we can simplify this using a single long integer multiplication,
501 # but take into account the transformation of z
502 z
= int(round(self
.q
*self
.tfmconv
))
503 assert -16777216 <= length
< 16777216 # -(1 << 24) <= length < (1 << 24)
504 assert z
< 134217728 # 1 << 27
506 while z
>= 8388608: # 1 << 23
509 # length*z is a long integer, but the result will be a regular integer
510 return int(length
*long(z
) >> shift
)
512 def _convert_tfm_to_ds(self
, length
):
513 return (16*long(round(length
*float(self
.q
)*self
.tfmconv
))/16777216) * self
.pyxconv
* 1000 / self
.getsize_pt()
515 def _convert_tfm_to_pt(self
, length
):
516 return (16*long(round(length
*float(self
.q
)*self
.tfmconv
))/16777216) * self
.pyxconv
518 # routines returning lengths as integers in dvi units
520 def getwidth_dvi(self
, charcode
):
521 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
523 def getheight_dvi(self
, charcode
):
524 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
526 def getdepth_dvi(self
, charcode
):
527 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
529 def getitalic_dvi(self
, charcode
):
530 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
532 # routines returning lengths as integers in design size (AFM) units
534 def getwidth_ds(self
, charcode
):
535 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
537 def getheight_ds(self
, charcode
):
538 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
540 def getdepth_ds(self
, charcode
):
541 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
543 def getitalic_ds(self
, charcode
):
544 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
546 # routines returning lengths as floats in PostScript points
548 def getwidth_pt(self
, charcode
):
549 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
551 def getheight_pt(self
, charcode
):
552 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
554 def getdepth_pt(self
, charcode
):
555 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
557 def getitalic_pt(self
, charcode
):
558 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
561 class virtualfont(font
):
562 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
563 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
564 if fontpath
is None or not len(fontpath
):
566 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
)
567 self
.vffile
= vffile(fontpath
, self
.scale
, tfmconv
, pyxconv
, fontmap
, debug
> 1)
570 """ return fonts used in virtual font itself """
571 return self
.vffile
.getfonts()
573 def getchar(self
, cc
):
574 """ return dvi chunk corresponding to char code cc """
575 return self
.vffile
.getchar(cc
)
578 ##############################################################################
580 ##############################################################################
582 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
583 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
584 _DVI_SET1234
= 128 # typeset a character and move right
585 _DVI_SETRULE
= 132 # typeset a rule and move right
586 _DVI_PUT1234
= 133 # typeset a character
587 _DVI_PUTRULE
= 137 # typeset a rule
588 _DVI_NOP
= 138 # no operation
589 _DVI_BOP
= 139 # beginning of page
590 _DVI_EOP
= 140 # ending of page
591 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
592 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
593 _DVI_RIGHT1234
= 143 # move right
594 _DVI_W0
= 147 # move right by w
595 _DVI_W1234
= 148 # move right and set w
596 _DVI_X0
= 152 # move right by x
597 _DVI_X1234
= 153 # move right and set x
598 _DVI_DOWN1234
= 157 # move down
599 _DVI_Y0
= 161 # move down by y
600 _DVI_Y1234
= 162 # move down and set y
601 _DVI_Z0
= 166 # move down by z
602 _DVI_Z1234
= 167 # move down and set z
603 _DVI_FNTNUMMIN
= 171 # set current font (range min)
604 _DVI_FNTNUMMAX
= 234 # set current font (range max)
605 _DVI_FNT1234
= 235 # set current font
606 _DVI_SPECIAL1234
= 239 # special (dvi extention)
607 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
608 _DVI_PRE
= 247 # preamble
609 _DVI_POST
= 248 # postamble beginning
610 _DVI_POSTPOST
= 249 # postamble ending
612 _DVI_VERSION
= 2 # dvi version
614 # position variable indices
626 _READ_POST
= 4 # XXX not used
627 _READ_POSTPOST
= 5 # XXX not used
631 class DVIError(exceptions
.Exception): pass
633 # save and restore colors
635 class _savecolor(canvas
.canvasitem
):
636 def processPS(self
, file, writer
, context
, registry
, bbox
):
637 file.write("currentcolor currentcolorspace\n")
639 def processPDF(self
, file, writer
, context
, registry
, bbox
):
643 class _restorecolor(canvas
.canvasitem
):
644 def processPS(self
, file, writer
, context
, registry
, bbox
):
645 file.write("setcolorspace setcolor\n")
647 def processPDF(self
, file, writer
, context
, registry
, bbox
):
650 class _savetrafo(canvas
.canvasitem
):
651 def processPS(self
, file, writer
, context
, registry
, bbox
):
652 file.write("matrix currentmatrix\n")
654 def processPDF(self
, file, writer
, context
, registry
, bbox
):
658 class _restoretrafo(canvas
.canvasitem
):
659 def processPS(self
, file, writer
, context
, registry
, bbox
):
660 file.write("setmatrix\n")
662 def processPDF(self
, file, writer
, context
, registry
, bbox
):
668 def __init__(self
, filename
, fontmap
, debug
=0, debugfile
=sys
.stdout
):
669 """ opens the dvi file and reads the preamble """
670 self
.filename
= filename
671 self
.fontmap
= fontmap
673 self
.debugfile
= debugfile
677 self
.activefont
= None
679 # stack of fonts and fontscale currently used (used for VFs)
683 # pointer to currently active page
686 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
689 self
.file = binfile(self
.filename
, "rb")
691 # currently read byte in file (for debugging output)
699 """ finish currently active text object """
700 if self
.debug
and self
.activetext
:
701 self
.debugfile
.write("[%s]\n" % "".join([chr(char
) for char
in self
.activetext
.chars
]))
702 self
.activetext
= None
704 def putrule(self
, height
, width
, advancepos
=1):
706 x1
= self
.pos
[_POS_H
] * self
.pyxconv
707 y1
= -self
.pos
[_POS_V
] * self
.pyxconv
708 w
= width
* self
.pyxconv
709 h
= height
* self
.pyxconv
711 if height
> 0 and width
> 0:
713 self
.debugfile
.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
714 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
715 self
.actpage
.fill(path
.rect_pt(x1
, y1
, w
, h
))
718 self
.debugfile
.write("%d: %srule height %d, width %d (invisible)\n" %
719 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
723 self
.debugfile
.write(" h:=%d+%d=%d, hh:=???\n" %
724 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
))
725 self
.pos
[_POS_H
] += width
727 def putchar(self
, char
, advancepos
=1, id1234
=0):
728 dx
= advancepos
and self
.activefont
.getwidth_dvi(char
) or 0
731 self
.debugfile
.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
733 advancepos
and "set" or "put",
734 id1234
and "%i " % id1234
or "char",
736 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
))
738 if isinstance(self
.activefont
, virtualfont
):
739 # virtual font handling
740 afterpos
= list(self
.pos
)
741 afterpos
[_POS_H
] += dx
742 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
743 self
.activefont
.getsize_pt())
745 if self
.activetext
is None:
746 if not self
.fontmap
.has_key(self
.activefont
.name
):
747 raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self
.activefont
.name
)
748 fontmapinfo
= self
.fontmap
[self
.activefont
.name
]
750 encodingname
= fontmapinfo
.reencodefont
751 if encodingname
is not None:
752 encodingfilename
= pykpathsea
.find_file(fontmapinfo
.encodingfile
, pykpathsea
.kpse_tex_ps_header_format
)
753 if not encodingfilename
:
754 raise RuntimeError("cannot find font encoding file %s" % fontmapinfo
.encodingfile
)
755 fontencoding
= type1font
.encoding(encodingname
, encodingfilename
)
759 fontbasefontname
= fontmapinfo
.basepsname
760 if fontmapinfo
.fontfile
is not None:
761 fontfilename
= pykpathsea
.find_file(fontmapinfo
.fontfile
, pykpathsea
.kpse_type1_format
)
763 raise RuntimeError("cannot find type 1 font %s" % fontmapinfo
.fontfile
)
767 # XXX we currently misuse use self.activefont as metric
768 font
= type1font
.font(fontbasefontname
, fontfilename
, fontencoding
, self
.activefont
)
770 self
.activetext
= type1font
.text_pt(self
.pos
[_POS_H
] * self
.pyxconv
, -self
.pos
[_POS_V
] * self
.pyxconv
, font
)
771 self
.actpage
.insert(self
.activetext
)
772 self
.activetext
.addchar(char
)
773 self
.pos
[_POS_H
] += dx
778 def usefont(self
, fontnum
, id1234
=0):
780 self
.activefont
= self
.fonts
[fontnum
]
782 self
.debugfile
.write("%d: fnt%s%i current font is %s\n" %
784 id1234
and "%i " % id1234
or "num",
786 self
.fonts
[fontnum
].name
))
789 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
790 # cmdnr: type of fontdef command (only used for debugging output)
792 # q: scaling factor (fix_word)
793 # Note that q is actually s in large parts of the documentation.
794 # d: design size (fix_word)
797 afont
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
798 except (TypeError, RuntimeError):
799 afont
= font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
801 self
.fonts
[num
] = afont
804 self
.debugfile
.write("%d: fntdef%d %i: %s\n" % (self
.filepos
, cmdnr
, num
, fontname
))
806 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
808 # scalestring = scale!=1000 and " scaled %d" % scale or ""
809 # print ("Font %i: %s%s---loaded at size %d DVI units" %
810 # (num, fontname, scalestring, q))
812 # print " (this font is magnified %d%%)" % round(scale/10)
814 def special(self
, s
):
815 x
= self
.pos
[_POS_H
] * self
.pyxconv
816 y
= -self
.pos
[_POS_V
] * self
.pyxconv
818 self
.debugfile
.write("%d: xxx '%s'\n" % (self
.filepos
, s
))
819 if not s
.startswith("PyX:"):
820 warnings
.warn("ignoring special '%s'" % s
)
823 # it is in general not safe to continue using the currently active font because
824 # the specials may involve some gsave/grestore operations
827 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
828 if command
== "color_begin":
829 if args
[0] == "cmyk":
830 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
831 elif args
[0] == "gray":
832 c
= color
.gray(float(args
[1]))
833 elif args
[0] == "hsb":
834 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
835 elif args
[0] == "rgb":
836 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
837 elif args
[0] == "RGB":
838 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
839 elif args
[0] == "texnamed":
841 c
= getattr(color
.cmyk
, args
[1])
842 except AttributeError:
843 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
844 elif args
[0] == "pyxcolor":
845 # pyx.color.cmyk.PineGreen or
846 # pyx.color.cmyk(0,0,0,0.0)
847 pat
= re
.compile(r
"(pyx\.)?(color\.)?(?P<model>(cmyk)|(rgb)|(grey)|(gray)|(hsb))[\.]?(?P<arg>.*)")
848 sd
= pat
.match(" ".join(args
[1:]))
851 if sd
["arg"][0] == "(":
852 numpat
= re
.compile(r
"[+-]?((\d+\.\d*)|(\d*\.\d+)|(\d+))([eE][+-]\d+)?")
853 arg
= tuple([float(x
[0]) for x
in numpat
.findall(sd
["arg"])])
855 c
= getattr(color
, sd
["model"])(*arg
)
856 except TypeError or AttributeError:
857 raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args
[1:]))
860 c
= getattr(getattr(color
, sd
["model"]), sd
["arg"])
861 except AttributeError:
862 raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args
[1:]))
864 raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args
[1:]))
866 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
867 self
.actpage
.insert(_savecolor())
868 self
.actpage
.insert(c
)
869 elif command
== "color_end":
870 self
.actpage
.insert(_restorecolor())
871 elif command
== "rotate_begin":
872 self
.actpage
.insert(_savetrafo())
873 self
.actpage
.insert(trafo
.rotate_pt(float(args
[0]), x
, y
))
874 elif command
== "rotate_end":
875 self
.actpage
.insert(_restoretrafo())
876 elif command
== "scale_begin":
877 self
.actpage
.insert(_savetrafo())
878 self
.actpage
.insert(trafo
.scale_pt(float(args
[0]), float(args
[1]), x
, y
))
879 elif command
== "scale_end":
880 self
.actpage
.insert(_restoretrafo())
881 elif command
== "epsinclude":
885 name
, value
= arg
.split("=")
886 argdict
[name
] = value
888 # construct kwargs for epsfile constructor
890 epskwargs
["filename"] = argdict
["file"]
891 epskwargs
["bbox"] = bbox
.bbox_pt(float(argdict
["llx"]), float(argdict
["lly"]),
892 float(argdict
["urx"]), float(argdict
["ury"]))
893 if argdict
.has_key("width"):
894 epskwargs
["width"] = float(argdict
["width"]) * unit
.t_pt
895 if argdict
.has_key("height"):
896 epskwargs
["height"] = float(argdict
["height"]) * unit
.t_pt
897 if argdict
.has_key("clip"):
898 epskwargs
["clip"] = int(argdict
["clip"])
899 self
.actpage
.insert(epsfile
.epsfile(x
* unit
.t_pt
, y
* unit
.t_pt
, **epskwargs
))
900 elif command
== "marker":
902 raise RuntimeError("marker contains spaces")
904 if c
not in string
.digits
+ string
.letters
+ "@":
905 raise RuntimeError("marker contains invalid characters")
906 if self
.actpage
.markers
.has_key(args
[0]):
907 raise RuntimeError("marker name occurred several times")
908 self
.actpage
.markers
[args
[0]] = x
* unit
.t_pt
, y
* unit
.t_pt
910 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
912 # routines for pushing and popping different dvi chunks on the reader
914 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
915 """ push dvi string with defined fonts on top of reader
916 stack. Every positions gets scaled relatively by the factor
917 scale. After the interpreting of the dvi chunk has been finished,
918 continue with self.pos=afterpos. The designsize of the virtual
919 font is passed as a fix_word
924 # self.debugfile.write("executing new dvi chunk\n")
925 self
.debugstack
.append(self
.debug
)
928 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.pyxconv
, self
.tfmconv
))
930 # units in vf files are relative to the size of the font and given as fix_words
931 # which can be converted to floats by diving by 2**20
932 oldpyxconv
= self
.pyxconv
933 self
.pyxconv
= fontsize
/2**20
934 rescale
= self
.pyxconv
/oldpyxconv
936 self
.file = stringbinfile(dvi
)
941 # rescale self.pos in order to be consistent with the new scaling
942 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
944 # since tfmconv converts from tfm units to dvi units, rescale it as well
945 self
.tfmconv
/= rescale
949 def _pop_dvistring(self
):
952 # self.debugfile.write("finished executing dvi chunk\n")
953 self
.debug
= self
.debugstack
.pop()
956 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.pyxconv
, self
.tfmconv
= self
.statestack
.pop()
958 # routines corresponding to the different reader states of the dvi maschine
963 self
.filepos
= afile
.tell()
964 cmd
= afile
.readuchar()
967 elif cmd
== _DVI_PRE
:
968 if afile
.readuchar() != _DVI_VERSION
: raise DVIError
969 num
= afile
.readuint32()
970 den
= afile
.readuint32()
971 self
.mag
= afile
.readuint32()
973 # For the interpretation of the lengths in dvi and tfm files,
974 # three conversion factors are relevant:
975 # - self.tfmconv: tfm units -> dvi units
976 # - self.pyxconv: dvi units -> (PostScript) points
977 # - self.conv: dvi units -> pixels
978 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
980 # calculate conv as described in the DVIType docu using
981 # a given resolution in dpi
982 self
.resolution
= 300.0
983 self
.conv
= (num
/254000.0)*(self
.resolution
/den
)
985 # self.pyxconv is the conversion factor from the dvi units
986 # to (PostScript) points. It consists of
987 # - self.mag/1000.0: magstep scaling
988 # - self.conv: conversion from dvi units to pixels
989 # - 1/self.resolution: conversion from pixels to inch
990 # - 72 : conversion from inch to points
991 self
.pyxconv
= self
.mag
/1000.0*self
.conv
/self
.resolution
*72
993 comment
= afile
.read(afile
.readuchar())
998 def readpage(self
, pageid
=None):
999 """ reads a page from the dvi file
1001 This routine reads a page from the dvi file which is
1002 returned as a canvas. When there is no page left in the
1003 dvifile, None is returned and the file is closed properly."""
1006 self
.filepos
= self
.file.tell()
1007 cmd
= self
.file.readuchar()
1010 elif cmd
== _DVI_BOP
:
1011 ispageid
= [self
.file.readuint32() for i
in range(10)]
1012 if pageid
is not None and ispageid
!= pageid
:
1013 raise DVIError("invalid pageid")
1015 self
.debugfile
.write("%d: beginning of page %i\n" % (self
.filepos
, ispageid
[0]))
1016 self
.file.readuint32()
1018 elif cmd
== _DVI_POST
:
1020 return None # nothing left
1024 self
.actpage
= canvas
.canvas()
1025 self
.actpage
.markers
= {}
1026 self
.pos
= [0, 0, 0, 0, 0, 0]
1028 # currently active output: text instance currently used
1029 self
.activetext
= None
1033 self
.filepos
= afile
.tell()
1035 cmd
= afile
.readuchar()
1036 except struct
.error
:
1037 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1038 # so we have to continue with the rest of the dvi file
1039 self
._pop
_dvistring
()
1043 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
1045 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
1046 self
.putchar(afile
.readint(cmd
- _DVI_SET1234
+ 1), id1234
=cmd
-_DVI_SET1234
+1)
1047 elif cmd
== _DVI_SETRULE
:
1048 self
.putrule(afile
.readint32(), afile
.readint32())
1049 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1050 self
.putchar(afile
.readint(cmd
- _DVI_PUT1234
+ 1), advancepos
=0, id1234
=cmd
-_DVI_SET1234
+1)
1051 elif cmd
== _DVI_PUTRULE
:
1052 self
.putrule(afile
.readint32(), afile
.readint32(), 0)
1053 elif cmd
== _DVI_EOP
:
1056 self
.debugfile
.write("%d: eop\n \n" % self
.filepos
)
1058 elif cmd
== _DVI_PUSH
:
1059 self
.stack
.append(list(self
.pos
))
1061 self
.debugfile
.write("%s: push\n"
1062 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1063 ((self
.filepos
, len(self
.stack
)-1) + tuple(self
.pos
)))
1064 elif cmd
== _DVI_POP
:
1066 self
.pos
= self
.stack
.pop()
1068 self
.debugfile
.write("%s: pop\n"
1069 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1070 ((self
.filepos
, len(self
.stack
)) + tuple(self
.pos
)))
1071 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1073 dh
= afile
.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1075 self
.debugfile
.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1077 cmd
- _DVI_RIGHT1234
+ 1,
1081 self
.pos
[_POS_H
]+dh
))
1082 self
.pos
[_POS_H
] += dh
1083 elif cmd
== _DVI_W0
:
1086 self
.debugfile
.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1091 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1092 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1093 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1095 self
.pos
[_POS_W
] = afile
.readint(cmd
- _DVI_W1234
+ 1, 1)
1097 self
.debugfile
.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1099 cmd
- _DVI_W1234
+ 1,
1103 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1104 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1105 elif cmd
== _DVI_X0
:
1108 self
.debugfile
.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1113 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1114 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1115 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1117 self
.pos
[_POS_X
] = afile
.readint(cmd
- _DVI_X1234
+ 1, 1)
1119 self
.debugfile
.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1121 cmd
- _DVI_X1234
+ 1,
1125 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1126 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1127 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1129 dv
= afile
.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1131 self
.debugfile
.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1133 cmd
- _DVI_DOWN1234
+ 1,
1137 self
.pos
[_POS_V
]+dv
))
1138 self
.pos
[_POS_V
] += dv
1139 elif cmd
== _DVI_Y0
:
1142 self
.debugfile
.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1147 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1148 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1149 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1151 self
.pos
[_POS_Y
] = afile
.readint(cmd
- _DVI_Y1234
+ 1, 1)
1153 self
.debugfile
.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1155 cmd
- _DVI_Y1234
+ 1,
1159 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1160 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1161 elif cmd
== _DVI_Z0
:
1164 self
.debugfile
.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1169 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1170 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1171 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1173 self
.pos
[_POS_Z
] = afile
.readint(cmd
- _DVI_Z1234
+ 1, 1)
1175 self
.debugfile
.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1177 cmd
- _DVI_Z1234
+ 1,
1181 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1182 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1183 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1184 self
.usefont(cmd
- _DVI_FNTNUMMIN
, 0)
1185 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1186 # note that according to the DVI docs, for four byte font numbers,
1187 # the font number is signed. Don't ask why!
1188 fntnum
= afile
.readint(cmd
- _DVI_FNT1234
+ 1, cmd
== _DVI_FNT1234
+ 3)
1189 self
.usefont(fntnum
, id1234
=cmd
-_DVI_FNT1234
+1)
1190 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1191 self
.special(afile
.read(afile
.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1192 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1193 if cmd
== _DVI_FNTDEF1234
:
1194 num
= afile
.readuchar()
1195 elif cmd
== _DVI_FNTDEF1234
+1:
1196 num
= afile
.readuint16()
1197 elif cmd
== _DVI_FNTDEF1234
+2:
1198 num
= afile
.readuint24()
1199 elif cmd
== _DVI_FNTDEF1234
+3:
1200 # Cool, here we have according to docu a signed int. Why?
1201 num
= afile
.readint32()
1202 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1207 afile
.read(afile
.readuchar()+afile
.readuchar()))
1212 ##############################################################################
1214 ##############################################################################
1216 _VF_LONG_CHAR
= 242 # character packet (long version)
1217 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1218 _VF_PRE
= _DVI_PRE
# preamble
1219 _VF_POST
= _DVI_POST
# postamble
1221 _VF_ID
= 202 # VF id byte
1223 class VFError(exceptions
.Exception): pass
1226 def __init__(self
, filename
, scale
, tfmconv
, pyxconv
, fontmap
, debug
=0):
1227 self
.filename
= filename
1229 self
.tfmconv
= tfmconv
1230 self
.pyxconv
= pyxconv
1231 self
.fontmap
= fontmap
1233 self
.fonts
= {} # used fonts
1234 self
.widths
= {} # widths of defined chars
1235 self
.chardefs
= {} # dvi chunks for defined chars
1237 afile
= binfile(self
.filename
, "rb")
1239 cmd
= afile
.readuchar()
1241 if afile
.readuchar() != _VF_ID
: raise VFError
1242 comment
= afile
.read(afile
.readuchar())
1243 self
.cs
= afile
.readuint32()
1244 self
.ds
= afile
.readuint32()
1249 cmd
= afile
.readuchar()
1250 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1252 if cmd
== _VF_FNTDEF1234
:
1253 num
= afile
.readuchar()
1254 elif cmd
== _VF_FNTDEF1234
+1:
1255 num
= afile
.readuint16()
1256 elif cmd
== _VF_FNTDEF1234
+2:
1257 num
= afile
.readuint24()
1258 elif cmd
== _VF_FNTDEF1234
+3:
1259 num
= afile
.readint32()
1260 c
= afile
.readint32()
1261 s
= afile
.readint32() # relative scaling used for font (fix_word)
1262 d
= afile
.readint32() # design size of font
1263 fontname
= afile
.read(afile
.readuchar()+afile
.readuchar())
1265 # rescaled size of font: s is relative to the scaling
1266 # of the virtual font itself. Note that realscale has
1267 # to be a fix_word (like s)
1268 # XXX: check rounding
1269 reals
= int(round(self
.scale
* (16*self
.ds
/16777216L) * s
))
1271 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1272 # (fontname, self.scale, self.ds, s, reals)
1275 # XXX allow for virtual fonts here too
1276 self
.fonts
[num
] = font(fontname
, c
, reals
, d
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
1277 elif cmd
== _VF_LONG_CHAR
:
1278 # character packet (long form)
1279 pl
= afile
.readuint32() # packet length
1280 cc
= afile
.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1281 tfm
= afile
.readuint24() # character width
1282 dvi
= afile
.read(pl
) # dvi code of character
1283 self
.widths
[cc
] = tfm
1284 self
.chardefs
[cc
] = dvi
1285 elif cmd
< _VF_LONG_CHAR
:
1286 # character packet (short form)
1287 cc
= afile
.readuchar() # char code
1288 tfm
= afile
.readuint24() # character width
1289 dvi
= afile
.read(cmd
)
1290 self
.widths
[cc
] = tfm
1291 self
.chardefs
[cc
] = dvi
1292 elif cmd
== _VF_POST
:
1302 def getchar(self
, cc
):
1303 return self
.chardefs
[cc
]