1
# -*- coding: ISO-8859-1 -*-
4
# Copyright (C) 2002-2006 J�rg Lehmann <joergl@users.sourceforge.net>
5
# Copyright (C) 2003-2004,2006,2007 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
fontslant = fontmapinfo.slantfont
767
if fontslant is not None:
768
fontslant = float(fontslant)
770
# XXX we currently misuse use self.activefont as metric
771
font = type1font.font(fontbasefontname, fontfilename, fontencoding, fontslant, self.activefont)
773
self.activetext = type1font.text_pt(self.pos[_POS_H] * self.pyxconv, -self.pos[_POS_V] * self.pyxconv, font)
774
self.actpage.insert(self.activetext)
775
self.activetext.addchar(char)
776
self.pos[_POS_H] += dx
781
def usefont(self, fontnum, id1234=0):
783
self.activefont = self.fonts[fontnum]
785
self.debugfile.write("%d: fnt%s%i current font is %s\n" %
787
id1234 and "%i " % id1234 or "num",
789
self.fonts[fontnum].name))
792
def definefont(self, cmdnr, num, c, q, d, fontname):
793
# cmdnr: type of fontdef command (only used for debugging output)
795
# q: scaling factor (fix_word)
796
# Note that q is actually s in large parts of the documentation.
797
# d: design size (fix_word)
800
afont = virtualfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
801
except (TypeError, RuntimeError):
802
afont = font(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
804
self.fonts[num] = afont
807
self.debugfile.write("%d: fntdef%d %i: %s\n" % (self.filepos, cmdnr, num, fontname))
809
# scale = round((1000.0*self.conv*q)/(self.trueconv*d))
811
# scalestring = scale!=1000 and " scaled %d" % scale or ""
812
# print ("Font %i: %s%s---loaded at size %d DVI units" %
813
# (num, fontname, scalestring, q))
815
# print " (this font is magnified %d%%)" % round(scale/10)
817
def special(self, s):
818
x = self.pos[_POS_H] * self.pyxconv
819
y = -self.pos[_POS_V] * self.pyxconv
821
self.debugfile.write("%d: xxx '%s'\n" % (self.filepos, s))
822
if not s.startswith("PyX:"):
823
warnings.warn("ignoring special '%s'" % s)
826
# it is in general not safe to continue using the currently active font because
827
# the specials may involve some gsave/grestore operations
830
command, args = s[4:].split()[0], s[4:].split()[1:]
831
if command == "color_begin":
832
if args[0] == "cmyk":
833
c = color.cmyk(float(args[1]), float(args[2]), float(args[3]), float(args[4]))
834
elif args[0] == "gray":
835
c = color.gray(float(args[1]))
836
elif args[0] == "hsb":
837
c = color.hsb(float(args[1]), float(args[2]), float(args[3]))
838
elif args[0] == "rgb":
839
c = color.rgb(float(args[1]), float(args[2]), float(args[3]))
840
elif args[0] == "RGB":
841
c = color.rgb(int(args[1])/255.0, int(args[2])/255.0, int(args[3])/255.0)
842
elif args[0] == "texnamed":
844
c = getattr(color.cmyk, args[1])
845
except AttributeError:
846
raise RuntimeError("unknown TeX color '%s', aborting" % args[1])
847
elif args[0] == "pyxcolor":
848
# pyx.color.cmyk.PineGreen or
849
# pyx.color.cmyk(0,0,0,0.0)
850
pat = re.compile(r"(pyx\.)?(color\.)?(?P<model>(cmyk)|(rgb)|(grey)|(gray)|(hsb))[\.]?(?P<arg>.*)")
851
sd = pat.match(" ".join(args[1:]))
854
if sd["arg"][0] == "(":
855
numpat = re.compile(r"[+-]?((\d+\.\d*)|(\d*\.\d+)|(\d+))([eE][+-]\d+)?")
856
arg = tuple([float(x[0]) for x in numpat.findall(sd["arg"])])
858
c = getattr(color, sd["model"])(*arg)
859
except TypeError or AttributeError:
860
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
863
c = getattr(getattr(color, sd["model"]), sd["arg"])
864
except AttributeError:
865
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
867
raise RuntimeError("cannot access PyX color '%s' in TeX, aborting" % " ".join(args[1:]))
869
raise RuntimeError("color model '%s' cannot be handled by PyX, aborting" % args[0])
870
self.actpage.insert(_savecolor())
871
self.actpage.insert(c)
872
elif command == "color_end":
873
self.actpage.insert(_restorecolor())
874
elif command == "rotate_begin":
875
self.actpage.insert(_savetrafo())
876
self.actpage.insert(trafo.rotate_pt(float(args[0]), x, y))
877
elif command == "rotate_end":
878
self.actpage.insert(_restoretrafo())
879
elif command == "scale_begin":
880
self.actpage.insert(_savetrafo())
881
self.actpage.insert(trafo.scale_pt(float(args[0]), float(args[1]), x, y))
882
elif command == "scale_end":
883
self.actpage.insert(_restoretrafo())
884
elif command == "epsinclude":
888
name, value = arg.split("=")
889
argdict[name] = value
891
# construct kwargs for epsfile constructor
893
epskwargs["filename"] = argdict["file"]
894
epskwargs["bbox"] = bbox.bbox_pt(float(argdict["llx"]), float(argdict["lly"]),
895
float(argdict["urx"]), float(argdict["ury"]))
896
if argdict.has_key("width"):
897
epskwargs["width"] = float(argdict["width"]) * unit.t_pt
898
if argdict.has_key("height"):
899
epskwargs["height"] = float(argdict["height"]) * unit.t_pt
900
if argdict.has_key("clip"):
901
epskwargs["clip"] = int(argdict["clip"])
902
self.actpage.insert(epsfile.epsfile(x * unit.t_pt, y * unit.t_pt, **epskwargs))
903
elif command == "marker":
905
raise RuntimeError("marker contains spaces")
907
if c not in string.digits + string.letters + "@":
908
raise RuntimeError("marker contains invalid characters")
909
if self.actpage.markers.has_key(args[0]):
910
raise RuntimeError("marker name occurred several times")
911
self.actpage.markers[args[0]] = x * unit.t_pt, y * unit.t_pt
913
raise RuntimeError("unknown PyX special '%s', aborting" % command)
915
# routines for pushing and popping different dvi chunks on the reader
917
def _push_dvistring(self, dvi, fonts, afterpos, fontsize):
918
""" push dvi string with defined fonts on top of reader
919
stack. Every positions gets scaled relatively by the factor
920
scale. After the interpreting of the dvi chunk has been finished,
921
continue with self.pos=afterpos. The designsize of the virtual
922
font is passed as a fix_word
927
# self.debugfile.write("executing new dvi chunk\n")
928
self.debugstack.append(self.debug)
931
self.statestack.append((self.file, self.fonts, self.activefont, afterpos, self.stack, self.pyxconv, self.tfmconv))
933
# units in vf files are relative to the size of the font and given as fix_words
934
# which can be converted to floats by diving by 2**20
935
oldpyxconv = self.pyxconv
936
self.pyxconv = fontsize/2**20
937
rescale = self.pyxconv/oldpyxconv
939
self.file = stringbinfile(dvi)
944
# rescale self.pos in order to be consistent with the new scaling
945
self.pos = map(lambda x, rescale=rescale:1.0*x/rescale, self.pos)
947
# since tfmconv converts from tfm units to dvi units, rescale it as well
948
self.tfmconv /= rescale
952
def _pop_dvistring(self):
955
# self.debugfile.write("finished executing dvi chunk\n")
956
self.debug = self.debugstack.pop()
959
self.file, self.fonts, self.activefont, self.pos, self.stack, self.pyxconv, self.tfmconv = self.statestack.pop()
961
# routines corresponding to the different reader states of the dvi maschine
966
self.filepos = afile.tell()
967
cmd = afile.readuchar()
970
elif cmd == _DVI_PRE:
971
if afile.readuchar() != _DVI_VERSION: raise DVIError
972
num = afile.readuint32()
973
den = afile.readuint32()
974
self.mag = afile.readuint32()
976
# For the interpretation of the lengths in dvi and tfm files,
977
# three conversion factors are relevant:
978
# - self.tfmconv: tfm units -> dvi units
979
# - self.pyxconv: dvi units -> (PostScript) points
980
# - self.conv: dvi units -> pixels
981
self.tfmconv = (25400000.0/num)*(den/473628672.0)/16.0
983
# calculate conv as described in the DVIType docu using
984
# a given resolution in dpi
985
self.resolution = 300.0
986
self.conv = (num/254000.0)*(self.resolution/den)
988
# self.pyxconv is the conversion factor from the dvi units
989
# to (PostScript) points. It consists of
990
# - self.mag/1000.0: magstep scaling
991
# - self.conv: conversion from dvi units to pixels
992
# - 1/self.resolution: conversion from pixels to inch
993
# - 72 : conversion from inch to points
994
self.pyxconv = self.mag/1000.0*self.conv/self.resolution*72
996
comment = afile.read(afile.readuchar())
1001
def readpage(self, pageid=None):
1002
""" reads a page from the dvi file
1004
This routine reads a page from the dvi file which is
1005
returned as a canvas. When there is no page left in the
1006
dvifile, None is returned and the file is closed properly."""
1009
self.filepos = self.file.tell()
1010
cmd = self.file.readuchar()
1013
elif cmd == _DVI_BOP:
1014
ispageid = [self.file.readuint32() for i in range(10)]
1015
if pageid is not None and ispageid != pageid:
1016
raise DVIError("invalid pageid")
1018
self.debugfile.write("%d: beginning of page %i\n" % (self.filepos, ispageid[0]))
1019
self.file.readuint32()
1021
elif cmd == _DVI_POST:
1023
return None # nothing left
1027
self.actpage = canvas.canvas()
1028
self.actpage.markers = {}
1029
self.pos = [0, 0, 0, 0, 0, 0]
1031
# currently active output: text instance currently used
1032
self.activetext = None
1036
self.filepos = afile.tell()
1038
cmd = afile.readuchar()
1039
except struct.error:
1040
# we most probably (if the dvi file is not corrupt) hit the end of a dvi chunk,
1041
# so we have to continue with the rest of the dvi file
1042
self._pop_dvistring()
1046
if cmd >= _DVI_CHARMIN and cmd <= _DVI_CHARMAX:
1048
elif cmd >= _DVI_SET1234 and cmd < _DVI_SET1234 + 4:
1049
self.putchar(afile.readint(cmd - _DVI_SET1234 + 1), id1234=cmd-_DVI_SET1234+1)
1050
elif cmd == _DVI_SETRULE:
1051
self.putrule(afile.readint32(), afile.readint32())
1052
elif cmd >= _DVI_PUT1234 and cmd < _DVI_PUT1234 + 4:
1053
self.putchar(afile.readint(cmd - _DVI_PUT1234 + 1), advancepos=0, id1234=cmd-_DVI_SET1234+1)
1054
elif cmd == _DVI_PUTRULE:
1055
self.putrule(afile.readint32(), afile.readint32(), 0)
1056
elif cmd == _DVI_EOP:
1059
self.debugfile.write("%d: eop\n \n" % self.filepos)
1061
elif cmd == _DVI_PUSH:
1062
self.stack.append(list(self.pos))
1064
self.debugfile.write("%s: push\n"
1065
"level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1066
((self.filepos, len(self.stack)-1) + tuple(self.pos)))
1067
elif cmd == _DVI_POP:
1069
self.pos = self.stack.pop()
1071
self.debugfile.write("%s: pop\n"
1072
"level %d:(h=%d,v=%d,w=%d,x=%d,y=%d,z=%d,hh=???,vv=???)\n" %
1073
((self.filepos, len(self.stack)) + tuple(self.pos)))
1074
elif cmd >= _DVI_RIGHT1234 and cmd < _DVI_RIGHT1234 + 4:
1076
dh = afile.readint(cmd - _DVI_RIGHT1234 + 1, 1)
1078
self.debugfile.write("%d: right%d %d h:=%d%+d=%d, hh:=???\n" %
1080
cmd - _DVI_RIGHT1234 + 1,
1084
self.pos[_POS_H]+dh))
1085
self.pos[_POS_H] += dh
1086
elif cmd == _DVI_W0:
1089
self.debugfile.write("%d: w0 %d h:=%d%+d=%d, hh:=???\n" %
1094
self.pos[_POS_H]+self.pos[_POS_W]))
1095
self.pos[_POS_H] += self.pos[_POS_W]
1096
elif cmd >= _DVI_W1234 and cmd < _DVI_W1234 + 4:
1098
self.pos[_POS_W] = afile.readint(cmd - _DVI_W1234 + 1, 1)
1100
self.debugfile.write("%d: w%d %d h:=%d%+d=%d, hh:=???\n" %
1102
cmd - _DVI_W1234 + 1,
1106
self.pos[_POS_H]+self.pos[_POS_W]))
1107
self.pos[_POS_H] += self.pos[_POS_W]
1108
elif cmd == _DVI_X0:
1111
self.debugfile.write("%d: x0 %d h:=%d%+d=%d, hh:=???\n" %
1116
self.pos[_POS_H]+self.pos[_POS_X]))
1117
self.pos[_POS_H] += self.pos[_POS_X]
1118
elif cmd >= _DVI_X1234 and cmd < _DVI_X1234 + 4:
1120
self.pos[_POS_X] = afile.readint(cmd - _DVI_X1234 + 1, 1)
1122
self.debugfile.write("%d: x%d %d h:=%d%+d=%d, hh:=???\n" %
1124
cmd - _DVI_X1234 + 1,
1128
self.pos[_POS_H]+self.pos[_POS_X]))
1129
self.pos[_POS_H] += self.pos[_POS_X]
1130
elif cmd >= _DVI_DOWN1234 and cmd < _DVI_DOWN1234 + 4:
1132
dv = afile.readint(cmd - _DVI_DOWN1234 + 1, 1)
1134
self.debugfile.write("%d: down%d %d v:=%d%+d=%d, vv:=???\n" %
1136
cmd - _DVI_DOWN1234 + 1,
1140
self.pos[_POS_V]+dv))
1141
self.pos[_POS_V] += dv
1142
elif cmd == _DVI_Y0:
1145
self.debugfile.write("%d: y0 %d v:=%d%+d=%d, vv:=???\n" %
1150
self.pos[_POS_V]+self.pos[_POS_Y]))
1151
self.pos[_POS_V] += self.pos[_POS_Y]
1152
elif cmd >= _DVI_Y1234 and cmd < _DVI_Y1234 + 4:
1154
self.pos[_POS_Y] = afile.readint(cmd - _DVI_Y1234 + 1, 1)
1156
self.debugfile.write("%d: y%d %d v:=%d%+d=%d, vv:=???\n" %
1158
cmd - _DVI_Y1234 + 1,
1162
self.pos[_POS_V]+self.pos[_POS_Y]))
1163
self.pos[_POS_V] += self.pos[_POS_Y]
1164
elif cmd == _DVI_Z0:
1167
self.debugfile.write("%d: z0 %d v:=%d%+d=%d, vv:=???\n" %
1172
self.pos[_POS_V]+self.pos[_POS_Z]))
1173
self.pos[_POS_V] += self.pos[_POS_Z]
1174
elif cmd >= _DVI_Z1234 and cmd < _DVI_Z1234 + 4:
1176
self.pos[_POS_Z] = afile.readint(cmd - _DVI_Z1234 + 1, 1)
1178
self.debugfile.write("%d: z%d %d v:=%d%+d=%d, vv:=???\n" %
1180
cmd - _DVI_Z1234 + 1,
1184
self.pos[_POS_V]+self.pos[_POS_Z]))
1185
self.pos[_POS_V] += self.pos[_POS_Z]
1186
elif cmd >= _DVI_FNTNUMMIN and cmd <= _DVI_FNTNUMMAX:
1187
self.usefont(cmd - _DVI_FNTNUMMIN, 0)
1188
elif cmd >= _DVI_FNT1234 and cmd < _DVI_FNT1234 + 4:
1189
# note that according to the DVI docs, for four byte font numbers,
1190
# the font number is signed. Don't ask why!
1191
fntnum = afile.readint(cmd - _DVI_FNT1234 + 1, cmd == _DVI_FNT1234 + 3)
1192
self.usefont(fntnum, id1234=cmd-_DVI_FNT1234+1)
1193
elif cmd >= _DVI_SPECIAL1234 and cmd < _DVI_SPECIAL1234 + 4:
1194
self.special(afile.read(afile.readint(cmd - _DVI_SPECIAL1234 + 1)))
1195
elif cmd >= _DVI_FNTDEF1234 and cmd < _DVI_FNTDEF1234 + 4:
1196
if cmd == _DVI_FNTDEF1234:
1197
num = afile.readuchar()
1198
elif cmd == _DVI_FNTDEF1234+1:
1199
num = afile.readuint16()
1200
elif cmd == _DVI_FNTDEF1234+2:
1201
num = afile.readuint24()
1202
elif cmd == _DVI_FNTDEF1234+3:
1203
# Cool, here we have according to docu a signed int. Why?
1204
num = afile.readint32()
1205
self.definefont(cmd-_DVI_FNTDEF1234+1,
1210
afile.read(afile.readuchar()+afile.readuchar()))
1215
##############################################################################
1217
##############################################################################
1219
_VF_LONG_CHAR = 242 # character packet (long version)
1220
_VF_FNTDEF1234 = _DVI_FNTDEF1234 # font definition
1221
_VF_PRE = _DVI_PRE # preamble
1222
_VF_POST = _DVI_POST # postamble
1224
_VF_ID = 202 # VF id byte
1226
class VFError(exceptions.Exception): pass
1229
def __init__(self, filename, scale, tfmconv, pyxconv, fontmap, debug=0):
1230
self.filename = filename
1232
self.tfmconv = tfmconv
1233
self.pyxconv = pyxconv
1234
self.fontmap = fontmap
1236
self.fonts = {} # used fonts
1237
self.widths = {} # widths of defined chars
1238
self.chardefs = {} # dvi chunks for defined chars
1240
afile = binfile(self.filename, "rb")
1242
cmd = afile.readuchar()
1244
if afile.readuchar() != _VF_ID: raise VFError
1245
comment = afile.read(afile.readuchar())
1246
self.cs = afile.readuint32()
1247
self.ds = afile.readuint32()
1252
cmd = afile.readuchar()
1253
if cmd >= _VF_FNTDEF1234 and cmd < _VF_FNTDEF1234 + 4:
1255
if cmd == _VF_FNTDEF1234:
1256
num = afile.readuchar()
1257
elif cmd == _VF_FNTDEF1234+1:
1258
num = afile.readuint16()
1259
elif cmd == _VF_FNTDEF1234+2:
1260
num = afile.readuint24()
1261
elif cmd == _VF_FNTDEF1234+3:
1262
num = afile.readint32()
1263
c = afile.readint32()
1264
s = afile.readint32() # relative scaling used for font (fix_word)
1265
d = afile.readint32() # design size of font
1266
fontname = afile.read(afile.readuchar()+afile.readuchar())
1268
# rescaled size of font: s is relative to the scaling
1269
# of the virtual font itself. Note that realscale has
1270
# to be a fix_word (like s)
1271
# XXX: check rounding
1272
reals = int(round(self.scale * (16*self.ds/16777216L) * s))
1274
# print ("defining font %s -- VF scale: %g, VF design size: %d, relative font size: %d => real size: %d" %
1275
# (fontname, self.scale, self.ds, s, reals)
1278
# XXX allow for virtual fonts here too
1279
self.fonts[num] = font(fontname, c, reals, d, self.tfmconv, self.pyxconv, self.fontmap, self.debug > 1)
1280
elif cmd == _VF_LONG_CHAR:
1281
# character packet (long form)
1282
pl = afile.readuint32() # packet length
1283
cc = afile.readuint32() # char code (assumed unsigned, but anyhow only 0 <= cc < 255 is actually used)
1284
tfm = afile.readuint24() # character width
1285
dvi = afile.read(pl) # dvi code of character
1286
self.widths[cc] = tfm
1287
self.chardefs[cc] = dvi
1288
elif cmd < _VF_LONG_CHAR:
1289
# character packet (short form)
1290
cc = afile.readuchar() # char code
1291
tfm = afile.readuint24() # character width
1292
dvi = afile.read(cmd)
1293
self.widths[cc] = tfm
1294
self.chardefs[cc] = dvi
1295
elif cmd == _VF_POST:
1305
def getchar(self, cc):
1306
return self.chardefs[cc]