1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2006 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004,2006 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2006 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import cStringIO
, exceptions
, re
, struct
, string
, sys
, warnings
, math
25 import unit
, epsfile
, bbox
, canvas
, color
, trafo
, path
, pykpathsea
, type1font
30 def __init__(self
, filename
, mode
="r"):
31 self
.file = open(filename
, mode
)
37 return self
.file.tell()
40 return self
.file.eof()
42 def read(self
, bytes
):
43 return self
.file.read(bytes
)
45 def readint(self
, bytes
=4, signed
=0):
49 value
= ord(self
.file.read(1))
50 if first
and signed
and value
> 127:
53 result
= 256 * result
+ value
58 return struct
.unpack(">l", self
.file.read(4))[0]
61 return struct
.unpack(">L", self
.file.read(4))[0]
65 return struct
.unpack(">l", "\0"+self
.file.read(3))[0]
69 return struct
.unpack(">L", "\0"+self
.file.read(3))[0]
72 return struct
.unpack(">h", self
.file.read(2))[0]
75 return struct
.unpack(">H", self
.file.read(2))[0]
78 return struct
.unpack("b", self
.file.read(1))[0]
81 return struct
.unpack("B", self
.file.read(1))[0]
83 def readstring(self
, bytes
):
85 assert l
<= bytes
-1, "inconsistency in file: string too long"
86 return self
.file.read(bytes
-1)[:l
]
88 class stringbinfile(binfile
):
90 def __init__(self
, s
):
91 self
.file = cStringIO
.StringIO(s
)
96 ##############################################################################
98 ##############################################################################
100 class TFMError(exceptions
.Exception): pass
103 class char_info_word
:
104 def __init__(self
, word
):
105 self
.width_index
= int((word
& 0xFF000000L
) >> 24) #make sign-safe
106 self
.height_index
= (word
& 0x00F00000) >> 20
107 self
.depth_index
= (word
& 0x000F0000) >> 16
108 self
.italic_index
= (word
& 0x0000FC00) >> 10
109 self
.tag
= (word
& 0x00000300) >> 8
110 self
.remainder
= (word
& 0x000000FF)
114 def __init__(self
, name
, debug
=0):
115 self
.file = binfile(name
, "rb")
121 self
.lf
= self
.file.readint16()
122 self
.lh
= self
.file.readint16()
123 self
.bc
= self
.file.readint16()
124 self
.ec
= self
.file.readint16()
125 self
.nw
= self
.file.readint16()
126 self
.nh
= self
.file.readint16()
127 self
.nd
= self
.file.readint16()
128 self
.ni
= self
.file.readint16()
129 self
.nl
= self
.file.readint16()
130 self
.nk
= self
.file.readint16()
131 self
.ne
= self
.file.readint16()
132 self
.np
= self
.file.readint16()
134 if not (self
.bc
-1 <= self
.ec
<= 255 and
136 self
.lf
== 6+self
.lh
+(self
.ec
-self
.bc
+1)+self
.nw
+self
.nh
+self
.nd
137 +self
.ni
+self
.nl
+self
.nk
+self
.ne
+self
.np
):
138 raise TFMError
, "error in TFM pre-header"
141 print "lh=%d" % self
.lh
147 self
.checksum
= self
.file.readint32()
148 self
.designsize
= self
.file.readint32()
149 assert self
.designsize
> 0, "invald design size"
151 assert self
.lh
> 11, "inconsistency in TFM file: incomplete field"
152 self
.charcoding
= self
.file.readstring(40)
154 self
.charcoding
= None
157 assert self
.lh
> 16, "inconsistency in TFM file: incomplete field"
158 self
.fontfamily
= self
.file.readstring(20)
160 self
.fontfamily
= None
163 print "(FAMILY %s)" % self
.fontfamily
164 print "(CODINGSCHEME %s)" % self
.charcoding
165 print "(DESINGSIZE R %f)" % 16.0*self
.designsize
/16777216L
168 self
.sevenbitsave
= self
.file.readuchar()
169 # ignore the following two bytes
170 self
.file.readint16()
171 facechar
= self
.file.readuchar()
172 # decode ugly face specification into the Knuth suggested string
184 self
.face
= "L" + self
.face
187 self
.face
= "B" + self
.face
190 self
.face
= "M" + self
.face
193 self
.face
= self
.face
[0] + "I" + self
.face
[1]
195 self
.face
= self
.face
[0] + "R" + self
.face
[1]
200 self
.sevenbitsave
= self
.face
= None
203 # just ignore the rest
204 print self
.file.read((self
.lh
-18)*4)
210 self
.char_info
= [None]*(self
.ec
+1)
211 for charcode
in range(self
.bc
, self
.ec
+1):
212 self
.char_info
[charcode
] = char_info_word(self
.file.readint32())
213 if self
.char_info
[charcode
].width_index
== 0:
214 # disable character if width_index is zero
215 self
.char_info
[charcode
] = None
221 self
.width
= [None for width_index
in range(self
.nw
)]
222 for width_index
in range(self
.nw
):
223 self
.width
[width_index
] = self
.file.readint32()
229 self
.height
= [None for height_index
in range(self
.nh
)]
230 for height_index
in range(self
.nh
):
231 self
.height
[height_index
] = self
.file.readint32()
237 self
.depth
= [None for depth_index
in range(self
.nd
)]
238 for depth_index
in range(self
.nd
):
239 self
.depth
[depth_index
] = self
.file.readint32()
245 self
.italic
= [None for italic_index
in range(self
.ni
)]
246 for italic_index
in range(self
.ni
):
247 self
.italic
[italic_index
] = self
.file.readint32()
253 # XXX decode to lig_kern_command
255 self
.lig_kern
= [None for lig_kern_index
in range(self
.nl
)]
256 for lig_kern_index
in range(self
.nl
):
257 self
.lig_kern
[lig_kern_index
] = self
.file.readint32()
263 self
.kern
= [None for kern_index
in range(self
.nk
)]
264 for kern_index
in range(self
.nk
):
265 self
.kern
[kern_index
] = self
.file.readint32()
271 # XXX decode to extensible_recipe
273 self
.exten
= [None for exten_index
in range(self
.ne
)]
274 for exten_index
in range(self
.ne
):
275 self
.exten
[exten_index
] = self
.file.readint32()
283 self
.param
= [None for param_index
in range(self
.np
)]
284 for param_index
in range(self
.np
):
285 self
.param
[param_index
] = self
.file.readint32()
291 ##############################################################################
293 ##############################################################################
296 # PostScript font selection and output primitives
299 class UnsupportedFontFormat(Exception):
302 class UnsupportedPSFragment(Exception):
307 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
309 def __init__(self
, s
):
310 """ construct font mapping from line s of font mapping file """
311 self
.texname
= self
.basepsname
= self
.fontfile
= None
314 self
.encodingfile
= None
316 # supported postscript fragments occuring in psfonts.map
317 self
.reencodefont
= self
.extendfont
= self
.slantfont
= None
321 match
= self
.tokenpattern
.match(s
)
323 if match
.groups()[0] is not None:
324 tokens
.append('"%s"' % match
.groups()[0])
326 tokens
.append(match
.groups()[2])
329 raise RuntimeError("Cannot tokenize string '%s'" % s
)
332 if token
.startswith("<"):
333 if token
.startswith("<<"):
334 # XXX: support non-partial download here
335 self
.fontfile
= token
[2:]
336 elif token
.startswith("<["):
337 self
.encodingfile
= token
[2:]
338 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
339 self
.fontfile
= token
[1:]
340 elif token
.endswith(".enc"):
341 self
.encodingfile
= token
[1:]
342 elif token
.endswith(".ttf"):
343 raise UnsupportedFontFormat("TrueType font")
345 raise RuntimeError("Unknown token '%s'" % token
)
346 elif token
.startswith('"'):
347 pscode
= token
[1:-1].split()
348 # parse standard postscript code fragments
351 arg
, cmd
= pscode
[:2]
353 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s'" % pscode
)
355 if cmd
== "ReEncodeFont":
356 self
.reencodefont
= arg
357 elif cmd
== "ExtendFont":
358 self
.extendfont
= arg
359 elif cmd
== "SlantFont":
362 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
364 if self
.texname
is None:
367 self
.basepsname
= token
368 if self
.basepsname
is None:
369 self
.basepsname
= self
.texname
372 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
373 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
377 def readfontmap(filenames
):
378 """ read font map from filename (without path) """
380 for filename
in filenames
:
381 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_fontmap_format
)
382 # try also the oft-used registration as dvips config file
384 mappath
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_dvips_config_format
)
386 raise RuntimeError("cannot find font mapping file '%s'" % filename
)
387 mapfile
= open(mappath
, "rU")
389 for line
in mapfile
.readlines():
392 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
394 fm
= fontmapping(line
)
395 except (RuntimeError, UnsupportedPSFragment
), e
:
396 warnings
.warn("Ignoring line %i in mapping file '%s': %s" % (lineno
, mappath
, e
))
397 except UnsupportedFontFormat
, e
:
400 fontmap
[fm
.texname
] = fm
407 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
409 self
.q
= q
# desired size of font (fix_word) in TeX points
410 self
.d
= d
# design size of font (fix_word) in TeX points
411 self
.tfmconv
= tfmconv
# conversion factor from tfm units to dvi units
412 self
.pyxconv
= pyxconv
# conversion factor from dvi units to PostScript points
413 self
.fontmap
= fontmap
414 tfmpath
= pykpathsea
.find_file("%s.tfm" % self
.name
, pykpathsea
.kpse_tfm_format
)
416 raise TFMError("cannot find %s.tfm" % self
.name
)
417 self
.tfmfile
= tfmfile(tfmpath
, debug
)
419 # We only check for equality of font checksums if none of them
420 # is zero. The case c == 0 happend in some VF files and
421 # according to the VFtoVP documentation, paragraph 40, a check
422 # is only performed if tfmfile.checksum > 0. Anyhow, being
423 # more generous here seems to be reasonable
424 if self
.tfmfile
.checksum
!= c
and self
.tfmfile
.checksum
!= 0 and c
!= 0:
425 raise DVIError("check sums do not agree: %d vs. %d" %
426 (self
.tfmfile
.checksum
, c
))
428 # Check whether the given design size matches the one defined in the tfm file
429 if abs(self
.tfmfile
.designsize
- d
) > 2:
430 raise DVIError("design sizes do not agree: %d vs. %d" % (self
.tfmfile
.designsize
, d
))
431 #if q < 0 or q > 134217728:
432 # raise DVIError("font '%s' not loaded: bad scale" % self.name)
433 if d
< 0 or d
> 134217728:
434 raise DVIError("font '%s' not loaded: bad design size" % self
.name
)
442 # The following code is a very crude way to obtain the information
443 # required for the PDF font descritor. (TODO: The correct way would
444 # be to read the information from the AFM file.)
445 fontinfo
= fontinfo()
447 fontinfo
.fontbbox
= (0,
448 -self
.getdepth_ds(ord("y")),
449 self
.getwidth_ds(ord("W")),
450 self
.getheight_ds(ord("H")))
452 fontinfo
.fontbbox
= (0, -10, 100, 100)
454 fontinfo
.italicangle
= -180/math
.pi
*math
.atan(self
.tfmfile
.param
[0]/65536.0)
456 fontinfo
.italicangle
= 0
457 fontinfo
.ascent
= fontinfo
.fontbbox
[3]
458 fontinfo
.descent
= fontinfo
.fontbbox
[1]
460 fontinfo
.capheight
= self
.getheight_ds(ord("h"))
462 fontinfo
.capheight
= 100
464 fontinfo
.vstem
= self
.getwidth_ds(ord("."))/3
470 return "font %s designed at %g TeX pts used at %g TeX pts" % (self
.name
,
471 16.0*self
.d
/16777216L,
472 16.0*self
.q
/16777216L)
475 def getsize_pt(self
):
476 """ return size of font in (PS) points """
477 # The factor 16L/16777216L=2**(-20) converts a fix_word (here self.q)
478 # to the corresponding float. Furthermore, we have to convert from TeX
479 # points to points, hence the factor 72/72.27.
480 return 16L*self
.q
/16777216L*72/72.27
482 def _convert_tfm_to_dvi(self
, length
):
483 # doing the integer math with long integers will lead to different roundings
484 # return 16*length*int(round(self.q*self.tfmconv))/16777216
486 # Knuth instead suggests the following algorithm based on 4 byte integer logic only
487 # z = int(round(self.q*self.tfmconv))
488 # b0, b1, b2, b3 = [ord(c) for c in struct.pack(">L", length)]
489 # assert b0 == 0 or b0 == 255
491 # while z >= 8388608:
495 # result = ( ( ( ( ( b3 * z ) >> 8 ) + ( b2 * z ) ) >> 8 ) + ( b1 * z ) ) >> shift
497 # result = result - (z << (8-shift))
499 # however, we can simplify this using a single long integer multiplication,
500 # but take into account the transformation of z
501 z
= int(round(self
.q
*self
.tfmconv
))
502 assert -16777216 <= length
< 16777216 # -(1 << 24) <= length < (1 << 24)
503 assert z
< 134217728 # 1 << 27
505 while z
>= 8388608: # 1 << 23
508 # length*z is a long integer, but the result will be a regular integer
509 return int(length
*long(z
) >> shift
)
511 def _convert_tfm_to_ds(self
, length
):
512 return (16*long(round(length
*float(self
.q
)*self
.tfmconv
))/16777216) * self
.pyxconv
* 1000 / self
.getsize_pt()
514 def _convert_tfm_to_pt(self
, length
):
515 return (16*long(round(length
*float(self
.q
)*self
.tfmconv
))/16777216) * self
.pyxconv
517 # routines returning lengths as integers in dvi units
519 def getwidth_dvi(self
, charcode
):
520 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
522 def getheight_dvi(self
, charcode
):
523 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
525 def getdepth_dvi(self
, charcode
):
526 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
528 def getitalic_dvi(self
, charcode
):
529 return self
._convert
_tfm
_to
_dvi
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
531 # routines returning lengths as integers in design size (AFM) units
533 def getwidth_ds(self
, charcode
):
534 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
536 def getheight_ds(self
, charcode
):
537 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
539 def getdepth_ds(self
, charcode
):
540 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
542 def getitalic_ds(self
, charcode
):
543 return self
._convert
_tfm
_to
_ds
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
545 # routines returning lengths as floats in PostScript points
547 def getwidth_pt(self
, charcode
):
548 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.width
[self
.tfmfile
.char_info
[charcode
].width_index
])
550 def getheight_pt(self
, charcode
):
551 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.height
[self
.tfmfile
.char_info
[charcode
].height_index
])
553 def getdepth_pt(self
, charcode
):
554 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.depth
[self
.tfmfile
.char_info
[charcode
].depth_index
])
556 def getitalic_pt(self
, charcode
):
557 return self
._convert
_tfm
_to
_pt
(self
.tfmfile
.italic
[self
.tfmfile
.char_info
[charcode
].italic_index
])
560 class virtualfont(font
):
561 def __init__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
=0):
562 fontpath
= pykpathsea
.find_file(name
, pykpathsea
.kpse_vf_format
)
563 if fontpath
is None or not len(fontpath
):
565 font
.__init
__(self
, name
, c
, q
, d
, tfmconv
, pyxconv
, fontmap
, debug
)
566 self
.vffile
= vffile(fontpath
, self
.scale
, tfmconv
, pyxconv
, fontmap
, debug
> 1)
569 """ return fonts used in virtual font itself """
570 return self
.vffile
.getfonts()
572 def getchar(self
, cc
):
573 """ return dvi chunk corresponding to char code cc """
574 return self
.vffile
.getchar(cc
)
577 ##############################################################################
579 ##############################################################################
581 _DVI_CHARMIN
= 0 # typeset a character and move right (range min)
582 _DVI_CHARMAX
= 127 # typeset a character and move right (range max)
583 _DVI_SET1234
= 128 # typeset a character and move right
584 _DVI_SETRULE
= 132 # typeset a rule and move right
585 _DVI_PUT1234
= 133 # typeset a character
586 _DVI_PUTRULE
= 137 # typeset a rule
587 _DVI_NOP
= 138 # no operation
588 _DVI_BOP
= 139 # beginning of page
589 _DVI_EOP
= 140 # ending of page
590 _DVI_PUSH
= 141 # save the current positions (h, v, w, x, y, z)
591 _DVI_POP
= 142 # restore positions (h, v, w, x, y, z)
592 _DVI_RIGHT1234
= 143 # move right
593 _DVI_W0
= 147 # move right by w
594 _DVI_W1234
= 148 # move right and set w
595 _DVI_X0
= 152 # move right by x
596 _DVI_X1234
= 153 # move right and set x
597 _DVI_DOWN1234
= 157 # move down
598 _DVI_Y0
= 161 # move down by y
599 _DVI_Y1234
= 162 # move down and set y
600 _DVI_Z0
= 166 # move down by z
601 _DVI_Z1234
= 167 # move down and set z
602 _DVI_FNTNUMMIN
= 171 # set current font (range min)
603 _DVI_FNTNUMMAX
= 234 # set current font (range max)
604 _DVI_FNT1234
= 235 # set current font
605 _DVI_SPECIAL1234
= 239 # special (dvi extention)
606 _DVI_FNTDEF1234
= 243 # define the meaning of a font number
607 _DVI_PRE
= 247 # preamble
608 _DVI_POST
= 248 # postamble beginning
609 _DVI_POSTPOST
= 249 # postamble ending
611 _DVI_VERSION
= 2 # dvi version
613 # position variable indices
625 _READ_POST
= 4 # XXX not used
626 _READ_POSTPOST
= 5 # XXX not used
630 class DVIError(exceptions
.Exception): pass
632 # save and restore colors
634 class _savecolor(canvas
.canvasitem
):
635 def processPS(self
, file, writer
, context
, registry
, bbox
):
636 file.write("currentcolor currentcolorspace\n")
638 def processPDF(self
, file, writer
, context
, registry
, bbox
):
642 class _restorecolor(canvas
.canvasitem
):
643 def processPS(self
, file, writer
, context
, registry
, bbox
):
644 file.write("setcolorspace setcolor\n")
646 def processPDF(self
, file, writer
, context
, registry
, bbox
):
649 class _savetrafo(canvas
.canvasitem
):
650 def processPS(self
, file, writer
, context
, registry
, bbox
):
651 file.write("matrix currentmatrix\n")
653 def processPDF(self
, file, writer
, context
, registry
, bbox
):
657 class _restoretrafo(canvas
.canvasitem
):
658 def processPS(self
, file, writer
, context
, registry
, bbox
):
659 file.write("setmatrix\n")
661 def processPDF(self
, file, writer
, context
, registry
, bbox
):
667 def __init__(self
, filename
, fontmap
, debug
=0, debugfile
=sys
.stdout
):
668 """ opens the dvi file and reads the preamble """
669 self
.filename
= filename
670 self
.fontmap
= fontmap
672 self
.debugfile
= debugfile
676 self
.activefont
= None
678 # stack of fonts and fontscale currently used (used for VFs)
682 # pointer to currently active page
685 # stack for self.file, self.fonts and self.stack, needed for VF inclusion
688 self
.file = binfile(self
.filename
, "rb")
690 # currently read byte in file (for debugging output)
698 """ finish currently active text object """
699 if self
.debug
and self
.activetext
:
700 self
.debugfile
.write("[%s]\n" % "".join([chr(char
) for char
in self
.activetext
.chars
]))
701 self
.activetext
= None
703 def putrule(self
, height
, width
, advancepos
=1):
705 x1
= self
.pos
[_POS_H
] * self
.pyxconv
706 y1
= -self
.pos
[_POS_V
] * self
.pyxconv
707 w
= width
* self
.pyxconv
708 h
= height
* self
.pyxconv
710 if height
> 0 and width
> 0:
712 self
.debugfile
.write("%d: %srule height %d, width %d (???x??? pixels)\n" %
713 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
714 self
.actpage
.fill(path
.rect_pt(x1
, y1
, w
, h
))
717 self
.debugfile
.write("%d: %srule height %d, width %d (invisible)\n" %
718 (self
.filepos
, advancepos
and "set" or "put", height
, width
))
722 self
.debugfile
.write(" h:=%d+%d=%d, hh:=???\n" %
723 (self
.pos
[_POS_H
], width
, self
.pos
[_POS_H
]+width
))
724 self
.pos
[_POS_H
] += width
726 def putchar(self
, char
, advancepos
=1, id1234
=0):
727 dx
= advancepos
and self
.activefont
.getwidth_dvi(char
) or 0
730 self
.debugfile
.write("%d: %s%s%d h:=%d+%d=%d, hh:=???\n" %
732 advancepos
and "set" or "put",
733 id1234
and "%i " % id1234
or "char",
735 self
.pos
[_POS_H
], dx
, self
.pos
[_POS_H
]+dx
))
737 if isinstance(self
.activefont
, virtualfont
):
738 # virtual font handling
739 afterpos
= list(self
.pos
)
740 afterpos
[_POS_H
] += dx
741 self
._push
_dvistring
(self
.activefont
.getchar(char
), self
.activefont
.getfonts(), afterpos
,
742 self
.activefont
.getsize_pt())
744 if self
.activetext
is None:
745 if not self
.fontmap
.has_key(self
.activefont
.name
):
746 raise RuntimeError("missing font information for '%s'; check fontmapping file(s)" % self
.activefont
.name
)
747 fontmapinfo
= self
.fontmap
[self
.activefont
.name
]
749 encodingname
= fontmapinfo
.reencodefont
750 if encodingname
is not None:
751 encodingfilename
= pykpathsea
.find_file(fontmapinfo
.encodingfile
, pykpathsea
.kpse_tex_ps_header_format
)
752 if not encodingfilename
:
753 raise RuntimeError("cannot find font encoding file %s" % fontmapinfo
.encodingfile
)
754 fontencoding
= type1font
.encoding(encodingname
, encodingfilename
)
758 fontbasefontname
= fontmapinfo
.basepsname
759 if fontmapinfo
.fontfile
is not None:
760 fontfilename
= pykpathsea
.find_file(fontmapinfo
.fontfile
, pykpathsea
.kpse_type1_format
)
762 raise RuntimeError("cannot find type 1 font %s" % fontmapinfo
.fontfile
)
766 # XXX we currently misuse use self.activefont as metric
767 font
= type1font
.font(fontbasefontname
, fontfilename
, fontencoding
, self
.activefont
)
769 self
.activetext
= type1font
.text_pt(self
.pos
[_POS_H
] * self
.pyxconv
, -self
.pos
[_POS_V
] * self
.pyxconv
, font
)
770 self
.actpage
.insert(self
.activetext
)
771 self
.activetext
.addchar(char
)
772 self
.pos
[_POS_H
] += dx
777 def usefont(self
, fontnum
, id1234
=0):
779 self
.activefont
= self
.fonts
[fontnum
]
781 self
.debugfile
.write("%d: fnt%s%i current font is %s\n" %
783 id1234
and "%i " % id1234
or "num",
785 self
.fonts
[fontnum
].name
))
788 def definefont(self
, cmdnr
, num
, c
, q
, d
, fontname
):
789 # cmdnr: type of fontdef command (only used for debugging output)
791 # q: scaling factor (fix_word)
792 # Note that q is actually s in large parts of the documentation.
793 # d: design size (fix_word)
796 afont
= virtualfont(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
797 except (TypeError, RuntimeError):
798 afont
= font(fontname
, c
, q
/self
.tfmconv
, d
/self
.tfmconv
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
800 self
.fonts
[num
] = afont
803 self
.debugfile
.write("%d: fntdef%d %i: %s\n" % (self
.filepos
, cmdnr
, num
, fontname
))
805 # scale = round((1000.0*self.conv*q)/(self.trueconv*d))
807 # scalestring = scale!=1000 and " scaled %d" % scale or ""
808 # print ("Font %i: %s%s---loaded at size %d DVI units" %
809 # (num, fontname, scalestring, q))
811 # print " (this font is magnified %d%%)" % round(scale/10)
813 def special(self
, s
):
814 x
= self
.pos
[_POS_H
] * self
.pyxconv
815 y
= -self
.pos
[_POS_V
] * self
.pyxconv
817 self
.debugfile
.write("%d: xxx '%s'\n" % (self
.filepos
, s
))
818 if not s
.startswith("PyX:"):
819 warnings
.warn("ignoring special '%s'" % s
)
822 # it is in general not safe to continue using the currently active font because
823 # the specials may involve some gsave/grestore operations
826 command
, args
= s
[4:].split()[0], s
[4:].split()[1:]
827 if command
== "color_begin":
828 if args
[0] == "cmyk":
829 c
= color
.cmyk(float(args
[1]), float(args
[2]), float(args
[3]), float(args
[4]))
830 elif args
[0] == "gray":
831 c
= color
.gray(float(args
[1]))
832 elif args
[0] == "hsb":
833 c
= color
.hsb(float(args
[1]), float(args
[2]), float(args
[3]))
834 elif args
[0] == "rgb":
835 c
= color
.rgb(float(args
[1]), float(args
[2]), float(args
[3]))
836 elif args
[0] == "RGB":
837 c
= color
.rgb(int(args
[1])/255.0, int(args
[2])/255.0, int(args
[3])/255.0)
838 elif args
[0] == "texnamed":
840 c
= getattr(color
.cmyk
, args
[1])
841 except AttributeError:
842 raise RuntimeError("unknown TeX color '%s', aborting" % args
[1])
843 elif args
[0] == "pyxcolor":
844 # pyx.color.cmyk.PineGreen or
845 # pyx.color.cmyk(0,0,0,0.0)
846 pat
= re
.compile(r
"(pyx\.)?(color\.)?(?P<model>(cmyk)|(rgb)|(grey)|(gray)|(hsb))[\.]?(?P<arg>.*)")
847 sd
= pat
.match(" ".join(args
[1:]))
850 if sd
["arg"][0] == "(":
851 numpat
= re
.compile(r
"[+-]?((\d+\.\d*)|(\d*\.\d+)|(\d+))([eE][+-]\d+)?")
852 arg
= tuple([float(x
[0]) for x
in numpat
.findall(sd
["arg"])])
854 c
= getattr(color
, sd
["model"])(*arg
)
855 except TypeError or AttributeError:
856 raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args
[1:]))
859 c
= getattr(getattr(color
, sd
["model"]), sd
["arg"])
860 except AttributeError:
861 raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args
[1:]))
863 raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args
[1:]))
865 raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args
[0])
866 self
.actpage
.insert(_savecolor())
867 self
.actpage
.insert(c
)
868 elif command
== "color_end":
869 self
.actpage
.insert(_restorecolor())
870 elif command
== "rotate_begin":
871 self
.actpage
.insert(_savetrafo())
872 self
.actpage
.insert(trafo
.rotate_pt(float(args
[0]), x
, y
))
873 elif command
== "rotate_end":
874 self
.actpage
.insert(_restoretrafo())
875 elif command
== "scale_begin":
876 self
.actpage
.insert(_savetrafo())
877 self
.actpage
.insert(trafo
.scale_pt(float(args
[0]), float(args
[1]), x
, y
))
878 elif command
== "scale_end":
879 self
.actpage
.insert(_restoretrafo())
880 elif command
== "epsinclude":
884 name
, value
= arg
.split("=")
885 argdict
[name
] = value
887 # construct kwargs for epsfile constructor
889 epskwargs
["filename"] = argdict
["file"]
890 epskwargs
["bbox"] = bbox
.bbox_pt(float(argdict
["llx"]), float(argdict
["lly"]),
891 float(argdict
["urx"]), float(argdict
["ury"]))
892 if argdict
.has_key("width"):
893 epskwargs
["width"] = float(argdict
["width"]) * unit
.t_pt
894 if argdict
.has_key("height"):
895 epskwargs
["height"] = float(argdict
["height"]) * unit
.t_pt
896 if argdict
.has_key("clip"):
897 epskwargs
["clip"] = int(argdict
["clip"])
898 self
.actpage
.insert(epsfile
.epsfile(x
* unit
.t_pt
, y
* unit
.t_pt
, **epskwargs
))
899 elif command
== "marker":
901 raise RuntimeError("marker contains spaces")
903 if c
not in string
.digits
+ string
.letters
+ "@":
904 raise RuntimeError("marker contains invalid characters")
905 if self
.actpage
.markers
.has_key(args
[0]):
906 raise RuntimeError("marker name occurred several times")
907 self
.actpage
.markers
[args
[0]] = x
* unit
.t_pt
, y
* unit
.t_pt
909 raise RuntimeError("unknown PyX special '%s', aborting" % command
)
911 # routines for pushing and popping different dvi chunks on the reader
913 def _push_dvistring(self
, dvi
, fonts
, afterpos
, fontsize
):
914 """ push dvi string with defined fonts on top of reader
915 stack. Every positions gets scaled relatively by the factor
916 scale. After the interpreting of the dvi chunk has been finished,
917 continue with self.pos=afterpos. The designsize of the virtual
918 font is passed as a fix_word
923 # self.debugfile.write("executing new dvi chunk\n")
924 self
.debugstack
.append(self
.debug
)
927 self
.statestack
.append((self
.file, self
.fonts
, self
.activefont
, afterpos
, self
.stack
, self
.pyxconv
, self
.tfmconv
))
929 # units in vf files are relative to the size of the font and given as fix_words
930 # which can be converted to floats by diving by 2**20
931 oldpyxconv
= self
.pyxconv
932 self
.pyxconv
= fontsize
/2**20
933 rescale
= self
.pyxconv
/oldpyxconv
935 self
.file = stringbinfile(dvi
)
940 # rescale self.pos in order to be consistent with the new scaling
941 self
.pos
= map(lambda x
, rescale
=rescale
:1.0*x
/rescale
, self
.pos
)
943 # since tfmconv converts from tfm units to dvi units, rescale it as well
944 self
.tfmconv
/= rescale
948 def _pop_dvistring(self
):
951 # self.debugfile.write("finished executing dvi chunk\n")
952 self
.debug
= self
.debugstack
.pop()
955 self
.file, self
.fonts
, self
.activefont
, self
.pos
, self
.stack
, self
.pyxconv
, self
.tfmconv
= self
.statestack
.pop()
957 # routines corresponding to the different reader states of the dvi maschine
962 self
.filepos
= afile
.tell()
963 cmd
= afile
.readuchar()
966 elif cmd
== _DVI_PRE
:
967 if afile
.readuchar() != _DVI_VERSION
: raise DVIError
968 num
= afile
.readuint32()
969 den
= afile
.readuint32()
970 self
.mag
= afile
.readuint32()
972 # For the interpretation of the lengths in dvi and tfm files,
973 # three conversion factors are relevant:
974 # - self.tfmconv: tfm units -> dvi units
975 # - self.pyxconv: dvi units -> (PostScript) points
976 # - self.conv: dvi units -> pixels
977 self
.tfmconv
= (25400000.0/num
)*(den
/473628672.0)/16.0
979 # calculate conv as described in the DVIType docu using
980 # a given resolution in dpi
981 self
.resolution
= 300.0
982 self
.conv
= (num
/254000.0)*(self
.resolution
/den
)
984 # self.pyxconv is the conversion factor from the dvi units
985 # to (PostScript) points. It consists of
986 # - self.mag/1000.0: magstep scaling
987 # - self.conv: conversion from dvi units to pixels
988 # - 1/self.resolution: conversion from pixels to inch
989 # - 72 : conversion from inch to points
990 self
.pyxconv
= self
.mag
/1000.0*self
.conv
/self
.resolution
*72
992 comment
= afile
.read(afile
.readuchar())
997 def readpage(self
, pageid
=None):
998 """ reads a page from the dvi file
1000 This routine reads a page from the dvi file which is
1001 returned as a canvas. When there is no page left in the
1002 dvifile, None is returned and the file is closed properly."""
1005 self
.filepos
= self
.file.tell()
1006 cmd
= self
.file.readuchar()
1009 elif cmd
== _DVI_BOP
:
1010 ispageid
= [self
.file.readuint32() for i
in range(10)]
1011 if pageid
is not None and ispageid
!= pageid
:
1012 raise DVIError("invalid pageid")
1014 self
.debugfile
.write("%d: beginning of page %i\n" % (self
.filepos
, ispageid
[0]))
1015 self
.file.readuint32()
1017 elif cmd
== _DVI_POST
:
1019 return None # nothing left
1023 self
.actpage
= canvas
.canvas()
1024 self
.actpage
.markers
= {}
1025 self
.pos
= [0, 0, 0, 0, 0, 0]
1027 # currently active output: text instance currently used
1028 self
.activetext
= None
1032 self
.filepos
= afile
.tell()
1034 cmd
= afile
.readuchar()
1035 except struct
.error
:
1036 # we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1037 # so we have to continue with the rest of the dvi file
1038 self
._pop
_dvistring
()
1042 if cmd
>= _DVI_CHARMIN
and cmd
<= _DVI_CHARMAX
:
1044 elif cmd
>= _DVI_SET1234
and cmd
< _DVI_SET1234
+ 4:
1045 self
.putchar(afile
.readint(cmd
- _DVI_SET1234
+ 1), id1234
=cmd
-_DVI_SET1234
+1)
1046 elif cmd
== _DVI_SETRULE
:
1047 self
.putrule(afile
.readint32(), afile
.readint32())
1048 elif cmd
>= _DVI_PUT1234
and cmd
< _DVI_PUT1234
+ 4:
1049 self
.putchar(afile
.readint(cmd
- _DVI_PUT1234
+ 1), advancepos
=0, id1234
=cmd
-_DVI_SET1234
+1)
1050 elif cmd
== _DVI_PUTRULE
:
1051 self
.putrule(afile
.readint32(), afile
.readint32(), 0)
1052 elif cmd
== _DVI_EOP
:
1055 self
.debugfile
.write("%d: eop\n \n" % self
.filepos
)
1057 elif cmd
== _DVI_PUSH
:
1058 self
.stack
.append(list(self
.pos
))
1060 self
.debugfile
.write("%s: push\n"
1061 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1062 ((self
.filepos
, len(self
.stack
)-1) + tuple(self
.pos
)))
1063 elif cmd
== _DVI_POP
:
1065 self
.pos
= self
.stack
.pop()
1067 self
.debugfile
.write("%s: pop\n"
1068 "level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1069 ((self
.filepos
, len(self
.stack
)) + tuple(self
.pos
)))
1070 elif cmd
>= _DVI_RIGHT1234
and cmd
< _DVI_RIGHT1234
+ 4:
1072 dh
= afile
.readint(cmd
- _DVI_RIGHT1234
+ 1, 1)
1074 self
.debugfile
.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1076 cmd
- _DVI_RIGHT1234
+ 1,
1080 self
.pos
[_POS_H
]+dh
))
1081 self
.pos
[_POS_H
] += dh
1082 elif cmd
== _DVI_W0
:
1085 self
.debugfile
.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1090 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1091 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1092 elif cmd
>= _DVI_W1234
and cmd
< _DVI_W1234
+ 4:
1094 self
.pos
[_POS_W
] = afile
.readint(cmd
- _DVI_W1234
+ 1, 1)
1096 self
.debugfile
.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1098 cmd
- _DVI_W1234
+ 1,
1102 self
.pos
[_POS_H
]+self
.pos
[_POS_W
]))
1103 self
.pos
[_POS_H
] += self
.pos
[_POS_W
]
1104 elif cmd
== _DVI_X0
:
1107 self
.debugfile
.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1112 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1113 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1114 elif cmd
>= _DVI_X1234
and cmd
< _DVI_X1234
+ 4:
1116 self
.pos
[_POS_X
] = afile
.readint(cmd
- _DVI_X1234
+ 1, 1)
1118 self
.debugfile
.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1120 cmd
- _DVI_X1234
+ 1,
1124 self
.pos
[_POS_H
]+self
.pos
[_POS_X
]))
1125 self
.pos
[_POS_H
] += self
.pos
[_POS_X
]
1126 elif cmd
>= _DVI_DOWN1234
and cmd
< _DVI_DOWN1234
+ 4:
1128 dv
= afile
.readint(cmd
- _DVI_DOWN1234
+ 1, 1)
1130 self
.debugfile
.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1132 cmd
- _DVI_DOWN1234
+ 1,
1136 self
.pos
[_POS_V
]+dv
))
1137 self
.pos
[_POS_V
] += dv
1138 elif cmd
== _DVI_Y0
:
1141 self
.debugfile
.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1146 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1147 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1148 elif cmd
>= _DVI_Y1234
and cmd
< _DVI_Y1234
+ 4:
1150 self
.pos
[_POS_Y
] = afile
.readint(cmd
- _DVI_Y1234
+ 1, 1)
1152 self
.debugfile
.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1154 cmd
- _DVI_Y1234
+ 1,
1158 self
.pos
[_POS_V
]+self
.pos
[_POS_Y
]))
1159 self
.pos
[_POS_V
] += self
.pos
[_POS_Y
]
1160 elif cmd
== _DVI_Z0
:
1163 self
.debugfile
.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1168 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1169 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1170 elif cmd
>= _DVI_Z1234
and cmd
< _DVI_Z1234
+ 4:
1172 self
.pos
[_POS_Z
] = afile
.readint(cmd
- _DVI_Z1234
+ 1, 1)
1174 self
.debugfile
.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1176 cmd
- _DVI_Z1234
+ 1,
1180 self
.pos
[_POS_V
]+self
.pos
[_POS_Z
]))
1181 self
.pos
[_POS_V
] += self
.pos
[_POS_Z
]
1182 elif cmd
>= _DVI_FNTNUMMIN
and cmd
<= _DVI_FNTNUMMAX
:
1183 self
.usefont(cmd
- _DVI_FNTNUMMIN
, 0)
1184 elif cmd
>= _DVI_FNT1234
and cmd
< _DVI_FNT1234
+ 4:
1185 # note that according to the DVI docs, for four byte font numbers,
1186 # the font number is signed. Don't ask why!
1187 fntnum
= afile
.readint(cmd
- _DVI_FNT1234
+ 1, cmd
== _DVI_FNT1234
+ 3)
1188 self
.usefont(fntnum
, id1234
=cmd
-_DVI_FNT1234
+1)
1189 elif cmd
>= _DVI_SPECIAL1234
and cmd
< _DVI_SPECIAL1234
+ 4:
1190 self
.special(afile
.read(afile
.readint(cmd
- _DVI_SPECIAL1234
+ 1)))
1191 elif cmd
>= _DVI_FNTDEF1234
and cmd
< _DVI_FNTDEF1234
+ 4:
1192 if cmd
== _DVI_FNTDEF1234
:
1193 num
= afile
.readuchar()
1194 elif cmd
== _DVI_FNTDEF1234
+1:
1195 num
= afile
.readuint16()
1196 elif cmd
== _DVI_FNTDEF1234
+2:
1197 num
= afile
.readuint24()
1198 elif cmd
== _DVI_FNTDEF1234
+3:
1199 # Cool, here we have according to docu a signed int. Why?
1200 num
= afile
.readint32()
1201 self
.definefont(cmd
-_DVI_FNTDEF1234
+1,
1206 afile
.read(afile
.readuchar()+afile
.readuchar()))
1211 ##############################################################################
1213 ##############################################################################
1215 _VF_LONG_CHAR
= 242 # character packet (long version)
1216 _VF_FNTDEF1234
= _DVI_FNTDEF1234
# font definition
1217 _VF_PRE
= _DVI_PRE
# preamble
1218 _VF_POST
= _DVI_POST
# postamble
1220 _VF_ID
= 202 # VF id byte
1222 class VFError(exceptions
.Exception): pass
1225 def __init__(self
, filename
, scale
, tfmconv
, pyxconv
, fontmap
, debug
=0):
1226 self
.filename
= filename
1228 self
.tfmconv
= tfmconv
1229 self
.pyxconv
= pyxconv
1230 self
.fontmap
= fontmap
1232 self
.fonts
= {} # used fonts
1233 self
.widths
= {} # widths of defined chars
1234 self
.chardefs
= {} # dvi chunks for defined chars
1236 afile
= binfile(self
.filename
, "rb")
1238 cmd
= afile
.readuchar()
1240 if afile
.readuchar() != _VF_ID
: raise VFError
1241 comment
= afile
.read(afile
.readuchar())
1242 self
.cs
= afile
.readuint32()
1243 self
.ds
= afile
.readuint32()
1248 cmd
= afile
.readuchar()
1249 if cmd
>= _VF_FNTDEF1234
and cmd
< _VF_FNTDEF1234
+ 4:
1251 if cmd
== _VF_FNTDEF1234
:
1252 num
= afile
.readuchar()
1253 elif cmd
== _VF_FNTDEF1234
+1:
1254 num
= afile
.readuint16()
1255 elif cmd
== _VF_FNTDEF1234
+2:
1256 num
= afile
.readuint24()
1257 elif cmd
== _VF_FNTDEF1234
+3:
1258 num
= afile
.readint32()
1259 c
= afile
.readint32()
1260 s
= afile
.readint32() # relative scaling used for font (fix_word)
1261 d
= afile
.readint32() # design size of font
1262 fontname
= afile
.read(afile
.readuchar()+afile
.readuchar())
1264 # rescaled size of font: s is relative to the scaling
1265 # of the virtual font itself. Note that realscale has
1266 # to be a fix_word (like s)
1267 # XXX: check rounding
1268 reals
= int(round(self
.scale
* (16*self
.ds
/16777216L) * s
))
1270 # print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1271 # (fontname, self.scale, self.ds, s, reals)
1274 # XXX allow for virtual fonts here too
1275 self
.fonts
[num
] = font(fontname
, c
, reals
, d
, self
.tfmconv
, self
.pyxconv
, self
.fontmap
, self
.debug
> 1)
1276 elif cmd
== _VF_LONG_CHAR
:
1277 # character packet (long form)
1278 pl
= afile
.readuint32() # packet length
1279 cc
= afile
.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1280 tfm
= afile
.readuint24() # character width
1281 dvi
= afile
.read(pl
) # dvi code of character
1282 self
.widths
[cc
] = tfm
1283 self
.chardefs
[cc
] = dvi
1284 elif cmd
< _VF_LONG_CHAR
:
1285 # character packet (short form)
1286 cc
= afile
.readuchar() # char code
1287 tfm
= afile
.readuint24() # character width
1288 dvi
= afile
.read(cmd
)
1289 self
.widths
[cc
] = tfm
1290 self
.chardefs
[cc
] = dvi
1291 elif cmd
== _VF_POST
:
1301 def getchar(self
, cc
):
1302 return self
.chardefs
[cc
]