- added new helper methods _distributeparams and _findnormpathitem to
[PyX/mjg.git] / pyx / epsfile.py
blob0bd743ecfd60f2bb6398aaa4c073a892a6b07025
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import string
25 import base, bbox, prolog, pykpathsea, unit, trafo
27 # PostScript-procedure definitions (cf. 5002.EPSF_Spec_v3.0.pdf)
28 # with important correction in EndEPSF:
29 # end operator is missing in the spec!
31 _BeginEPSF = prolog.definition("BeginEPSF", """{
32 /b4_Inc_state save def
33 /dict_count countdictstack def
34 /op_count count 1 sub def
35 userdict begin
36 /showpage { } def
37 0 setgray 0 setlinecap
38 1 setlinewidth 0 setlinejoin
39 10 setmiterlimit [ ] 0 setdash newpath
40 /languagelevel where
41 {pop languagelevel
42 1 ne
43 {false setstrokeadjust false setoverprint
44 } if
45 } if
46 } bind""")
48 _EndEPSF = prolog.definition("EndEPSF", """{
49 end
50 count op_count sub {pop} repeat
51 countdictstack dict_count sub {end} repeat
52 b4_Inc_state restore
53 } bind""")
55 def _readbbox(filename):
56 """returns bounding box of EPS file filename"""
58 file = open(filename, "r")
60 # readline
61 def readlinewithexception():
62 line = file.readline()
63 if not len(line):
64 raise IOError("unexpected end of file")
65 return line
67 # check the %! header comment
68 if not readlinewithexception().startswith("%!"):
69 raise IOError("file doesn't start with a '%!' header comment")
71 bboxatend = 0
72 # parse the header (use the first BoundingBox)
73 while 1:
74 line = readlinewithexception()
75 if line.startswith("%%BoundingBox:") and not bboxatend:
76 values = line.split(":", 1)[1].split()
77 if values == ["(atend)"]:
78 bboxatend = 1
79 else:
80 if len(values) != 4:
81 raise IOError("invalid number of bounding box values")
82 return bbox.bbox_pt(*map(int, values))
83 elif (line.rstrip() == "%%EndComments" or
84 (line[0] != "%" and line[1] not in string.whitespace)): # implicit end of comments section
85 break
86 if not bboxatend:
87 raise IOError("no bounding box information found")
89 # parse the body
90 nesting = 0 # allow for nested documents
91 while 1:
92 line = readlinewithexception()
93 if line.startswith("%%BeginData:"):
94 values = line.split(":", 1)[1].split()
95 if len(values) > 3:
96 raise IOError("invalid number of arguments")
97 if len(values) == 3:
98 if values[2] == "Lines":
99 for i in xrange(int(values[0])):
100 readlinewithexception()
101 elif values[2] != "Bytes":
102 raise IOError("invalid bytesorlines-value")
103 else:
104 file.read(int(values[0]))
105 else:
106 file.read(int(values[0]))
107 line = readlinewithexception()
108 # ignore tailing whitespace/newline for binary data
109 if (len(values) < 3 or values[2] != "Lines") and not len(line.strip()):
110 line = readlinewithexception()
111 if line.rstrip() != "%%EndData":
112 raise IOError("missing EndData")
113 elif line.startswith("%%BeginBinary:"):
114 file.read(int(line.split(":", 1)[1]))
115 line = readlinewithexception()
116 # ignore tailing whitespace/newline
117 if not len(line.strip()):
118 line = readlinewithexception()
119 if line.rstrip() != "%%EndBinary":
120 raise IOError("missing EndBinary")
121 elif line.startswith("%%BeginDocument:"):
122 nesting += 1
123 elif line.rstrip() == "%%EndDocument":
124 if nesting < 1:
125 raise IOError("unmatched EndDocument")
126 nesting -= 1
127 elif not nesting and line.rstrip() == "%%Trailer":
128 break
130 usebbox = None
131 # parse the trailer (use the last BoundingBox)
132 while 1:
133 line = file.readline()
134 if line.startswith("%%BoundingBox:"):
135 values = line.split(":", 1)[1].split()
136 if len(values) != 4:
137 raise IOError("invalid number of bounding box values")
138 usebbox = bbox.bbox_pt(*map(int, values))
139 elif not len(line):
140 break
141 if usebbox is None:
142 raise IOError("missing bounding box information in document trailer")
143 return usebbox
146 class epsfile(base.canvasitem):
148 """class for epsfiles"""
150 def __init__(self,
151 x, y, filename,
152 width=None, height=None, scale=None, align="bl",
153 clip=1, translatebbox=1, bbox=None,
154 kpsearch=0):
155 """inserts epsfile
157 Object for an EPS file named filename at position (x,y). Width, height,
158 scale and aligment can be adjusted by the corresponding parameters. If
159 clip is set, the result gets clipped to the bbox of the EPS file. If
160 translatebbox is not set, the EPS graphics is not translated to the
161 corresponding origin. If bbox is not None, it overrides the bounding
162 box in the epsfile itself. If kpsearch is set then filename is searched
163 using the kpathsea library.
166 self.x_pt = unit.topt(x)
167 self.y_pt = unit.topt(y)
168 if kpsearch:
169 self.filename = pykpathsea.find_file(filename, pykpathsea.kpse_pict_format)
170 else:
171 self.filename = filename
172 self.mybbox = bbox or _readbbox(self.filename)
174 # determine scaling in x and y direction
175 self.scalex = self.scaley = scale
177 if width is not None or height is not None:
178 if scale is not None:
179 raise ValueError("cannot set both width and/or height and scale simultaneously")
180 if height is not None:
181 self.scaley = unit.topt(height)/(self.mybbox.ury_pt-self.mybbox.lly_pt)
182 if width is not None:
183 self.scalex = unit.topt(width)/(self.mybbox.urx_pt-self.mybbox.llx_pt)
185 if self.scalex is None:
186 self.scalex = self.scaley
187 if self.scaley is None:
188 self.scaley = self.scalex
190 # set the actual width and height of the eps file (after a
191 # possible scaling)
192 self.width_pt = self.mybbox.urx_pt-self.mybbox.llx_pt
193 if self.scalex:
194 self.width_pt *= self.scalex
196 self.height_pt = self.mybbox.ury_pt-self.mybbox.lly_pt
197 if self.scaley:
198 self.height_pt *= self.scaley
200 # take alignment into account
201 self.align = align
202 if self.align[0]=="b":
203 pass
204 elif self.align[0]=="c":
205 self.y_pt -= self.height_pt/2.0
206 elif self.align[0]=="t":
207 self.y_pt -= self.height_pt
208 else:
209 raise ValueError("vertical alignment can only be b (bottom), c (center), or t (top)")
211 if self.align[1]=="l":
212 pass
213 elif self.align[1]=="c":
214 self.x_pt -= self.width_pt/2.0
215 elif self.align[1]=="r":
216 self.x_pt -= self.width_pt
217 else:
218 raise ValueError("horizontal alignment can only be l (left), c (center), or r (right)")
220 self.clip = clip
221 self.translatebbox = translatebbox
223 self.trafo = trafo.translate_pt(self.x_pt, self.y_pt)
225 if self.scalex is not None:
226 self.trafo = self.trafo * trafo.scale_pt(self.scalex, self.scaley)
228 if translatebbox:
229 self.trafo = self.trafo * trafo.translate_pt(-self.mybbox.llx_pt, -self.mybbox.lly_pt)
231 def bbox(self):
232 return self.mybbox.transformed(self.trafo)
234 def prolog(self):
235 return [_BeginEPSF, _EndEPSF]
237 def outputPS(self, file):
238 try:
239 epsfile=open(self.filename,"r")
240 except:
241 raise IOError, "cannot open EPS file '%s'" % self.filename
243 file.write("BeginEPSF\n")
245 bbrect = self.mybbox.rect().transformed(self.trafo)
247 if self.clip:
248 file.write("newpath\n")
249 bbrect.outputPS(file)
250 file.write("clip\n")
252 self.trafo.outputPS(file)
254 file.write("%%%%BeginDocument: %s\n" % self.filename)
255 file.write(epsfile.read())
256 file.write("%%EndDocument\n")
257 file.write("EndEPSF\n")