added another directory for drawings -- and two deformer examples
[PyX/mjg.git] / pyx / epsfile.py
blobb92c30d8e4952f45182582fd7ccb695ef508c8eb
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2006 Jörg Lehmann <joergl@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 string
25 import canvas, bbox, pykpathsea, unit, trafo, pswriter
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 = pswriter.PSdefinition("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 = pswriter.PSdefinition("EndEPSF", """{
49 end
50 count op_count sub {pop} repeat
51 countdictstack dict_count sub {end} repeat
52 b4_Inc_state restore
53 } bind""")
56 class linefilereader:
57 """a line by line file reader
59 This line by line file reader allows for '\n', '\r' and
60 '\r\n' as line separation characters. Line separation
61 characters are not modified (binary mode). It implements
62 a readline, a read and a close method similar to a regular
63 file object."""
65 # note: '\n\r' is not considered to be a linebreak as its documented
66 # in the DSC spec #5001, while '\n\r' *is* a *single* linebreak
67 # according to the EPSF spec #5002
69 def __init__(self, filename, typicallinelen=257):
70 """Opens the file filename for reading.
72 typicallinelen defines the default buffer increase
73 to find the next linebreak."""
75 # note: The maximal line size in an EPS is 255 plus the
76 # linebreak characters. However, we also handle
77 # lines longer than that.
79 self.file = open(filename, "rb")
80 self.buffer = ""
81 self.typicallinelen = typicallinelen
83 def read(self, count=None, EOFmsg="unexpected end of file"):
84 """read bytes from the file
86 count is the number of bytes to be read when set. Then count
87 is unset, the rest of the file is returned. EOFmsg is used
88 to raise a IOError, when the end of the file is reached while
89 reading count bytes or when the rest of the file is empty when
90 count is unset. When EOFmsg is set to None, less than the
91 requested number of bytes might be returned."""
92 if count is not None:
93 if count > len(self.buffer):
94 self.buffer += self.file.read(count - len(self.buffer))
95 if EOFmsg is not None and len(self.buffer) < count:
96 raise IOError(EOFmsg)
97 result = self.buffer[:count]
98 self.buffer = self.buffer[count:]
99 return result
100 else:
101 self.buffer += self.file.read()
102 if EOFmsg is not None and not len(self.buffer):
103 raise IOError(EOFmsg)
104 result = self.buffer
105 self.buffer = ""
106 return result
108 def readline(self, EOFmsg="unexpected end of file"):
109 """reads a line from the file
111 Lines are separated by '\n', '\r' or '\r\n'. The line separation
112 strings are included in the return value. The last line might not
113 end with an line separation string. Reading beyond the file generates
114 an IOError with the EOFmsg message. When EOFmsg is None, an empty
115 string is returned when reading beyond the end of the file."""
116 EOF = 0
117 while 1:
118 crpos = self.buffer.find("\r")
119 nlpos = self.buffer.find("\n")
120 if nlpos == -1 and (crpos == -1 or crpos == len(self.buffer) - 1) and not EOF:
121 newbuffer = self.file.read(self.typicallinelen)
122 if not len(newbuffer):
123 EOF = 1
124 self.buffer += newbuffer
125 else:
126 eol = len(self.buffer)
127 if not eol and EOFmsg is not None:
128 raise IOError(EOFmsg)
129 if nlpos != -1:
130 eol = nlpos + 1
131 if crpos != -1 and (nlpos == -1 or crpos < nlpos - 1):
132 eol = crpos + 1
133 result = self.buffer[:eol]
134 self.buffer = self.buffer[eol:]
135 return result
137 def close(self):
138 "closes the file"
139 self.file.close()
142 def _readbbox(filename):
143 """returns bounding box of EPS file filename"""
145 file = linefilereader(filename)
147 # check the %! header comment
148 if not file.readline().startswith("%!"):
149 raise IOError("file doesn't start with a '%!' header comment")
151 bboxatend = 0
152 # parse the header (use the first BoundingBox)
153 while 1:
154 line = file.readline()
155 if not line:
156 break
157 if line.startswith("%%BoundingBox:") and not bboxatend:
158 values = line.split(":", 1)[1].split()
159 if values == ["(atend)"]:
160 bboxatend = 1
161 else:
162 if len(values) != 4:
163 raise IOError("invalid number of bounding box values")
164 return bbox.bbox_pt(*map(int, values))
165 elif (line.rstrip() == "%%EndComments" or
166 (len(line) >= 2 and line[0] != "%" and line[1] not in string.whitespace)):
167 # implicit end of comments section
168 break
169 if not bboxatend:
170 raise IOError("no bounding box information found")
172 # parse the body
173 nesting = 0 # allow for nested documents
174 while 1:
175 line = file.readline()
176 if line.startswith("%%BeginData:"):
177 values = line.split(":", 1)[1].split()
178 if len(values) > 3:
179 raise IOError("invalid number of arguments")
180 if len(values) == 3:
181 if values[2] == "Lines":
182 for i in xrange(int(values[0])):
183 file.readline()
184 elif values[2] != "Bytes":
185 raise IOError("invalid bytesorlines-value")
186 else:
187 file.read(int(values[0]))
188 else:
189 file.read(int(values[0]))
190 line = file.readline()
191 # ignore tailing whitespace/newline for binary data
192 if (len(values) < 3 or values[2] != "Lines") and not len(line.strip()):
193 line = file.readline()
194 if line.rstrip() != "%%EndData":
195 raise IOError("missing EndData")
196 elif line.startswith("%%BeginBinary:"):
197 file.read(int(line.split(":", 1)[1]))
198 line = file.readline()
199 # ignore tailing whitespace/newline
200 if not len(line.strip()):
201 line = file.readline()
202 if line.rstrip() != "%%EndBinary":
203 raise IOError("missing EndBinary")
204 elif line.startswith("%%BeginDocument:"):
205 nesting += 1
206 elif line.rstrip() == "%%EndDocument":
207 if nesting < 1:
208 raise IOError("unmatched EndDocument")
209 nesting -= 1
210 elif not nesting and line.rstrip() == "%%Trailer":
211 break
213 usebbox = None
214 # parse the trailer (use the last BoundingBox)
215 line = True
216 while line:
217 line = file.readline(EOFmsg=None)
218 if line.startswith("%%BoundingBox:"):
219 values = line.split(":", 1)[1].split()
220 if len(values) != 4:
221 raise IOError("invalid number of bounding box values")
222 usebbox = bbox.bbox_pt(*map(int, values))
223 if not usebbox:
224 raise IOError("missing bounding box information in document trailer")
225 return usebbox
228 class epsfile(canvas.canvasitem):
230 """class for epsfiles"""
232 def __init__(self,
233 x, y, filename,
234 width=None, height=None, scale=None, align="bl",
235 clip=1, translatebbox=1, bbox=None,
236 kpsearch=0):
237 """inserts epsfile
239 Object for an EPS file named filename at position (x,y). Width, height,
240 scale and aligment can be adjusted by the corresponding parameters. If
241 clip is set, the result gets clipped to the bbox of the EPS file. If
242 translatebbox is not set, the EPS graphics is not translated to the
243 corresponding origin. If bbox is not None, it overrides the bounding
244 box in the epsfile itself. If kpsearch is set then filename is searched
245 using the kpathsea library.
248 self.x_pt = unit.topt(x)
249 self.y_pt = unit.topt(y)
250 if kpsearch:
251 self.filename = pykpathsea.find_file(filename, pykpathsea.kpse_pict_format)
252 else:
253 self.filename = filename
254 self.mybbox = bbox or _readbbox(self.filename)
256 # determine scaling in x and y direction
257 self.scalex = self.scaley = scale
259 if width is not None or height is not None:
260 if scale is not None:
261 raise ValueError("cannot set both width and/or height and scale simultaneously")
262 if height is not None:
263 self.scaley = unit.topt(height)/(self.mybbox.ury_pt-self.mybbox.lly_pt)
264 if width is not None:
265 self.scalex = unit.topt(width)/(self.mybbox.urx_pt-self.mybbox.llx_pt)
267 if self.scalex is None:
268 self.scalex = self.scaley
269 if self.scaley is None:
270 self.scaley = self.scalex
272 # set the actual width and height of the eps file (after a
273 # possible scaling)
274 self.width_pt = self.mybbox.urx_pt-self.mybbox.llx_pt
275 if self.scalex:
276 self.width_pt *= self.scalex
278 self.height_pt = self.mybbox.ury_pt-self.mybbox.lly_pt
279 if self.scaley:
280 self.height_pt *= self.scaley
282 # take alignment into account
283 self.align = align
284 if self.align[0]=="b":
285 pass
286 elif self.align[0]=="c":
287 self.y_pt -= self.height_pt/2.0
288 elif self.align[0]=="t":
289 self.y_pt -= self.height_pt
290 else:
291 raise ValueError("vertical alignment can only be b (bottom), c (center), or t (top)")
293 if self.align[1]=="l":
294 pass
295 elif self.align[1]=="c":
296 self.x_pt -= self.width_pt/2.0
297 elif self.align[1]=="r":
298 self.x_pt -= self.width_pt
299 else:
300 raise ValueError("horizontal alignment can only be l (left), c (center), or r (right)")
302 self.clip = clip
303 self.translatebbox = translatebbox
305 self.trafo = trafo.translate_pt(self.x_pt, self.y_pt)
307 if self.scalex is not None:
308 self.trafo = self.trafo * trafo.scale_pt(self.scalex, self.scaley)
310 if translatebbox:
311 self.trafo = self.trafo * trafo.translate_pt(-self.mybbox.llx_pt, -self.mybbox.lly_pt)
313 def bbox(self):
314 return self.mybbox.transformed(self.trafo)
316 def processPS(self, file, writer, context, registry, bbox):
317 registry.add(_BeginEPSF)
318 registry.add(_EndEPSF)
319 bbox += self.bbox()
320 try:
321 epsfile=open(self.filename,"rb")
322 except:
323 raise IOError, "cannot open EPS file '%s'" % self.filename
325 file.write("BeginEPSF\n")
327 if self.clip:
328 llx_pt, lly_pt, urx_pt, ury_pt = self.mybbox.transformed(self.trafo).highrestuple_pt()
329 file.write("%g %g %g %g rectclip\n" % (llx_pt, lly_pt, urx_pt-llx_pt, ury_pt-lly_pt))
331 self.trafo.processPS(file, writer, context, registry, bbox)
333 file.write("%%%%BeginDocument: %s\n" % self.filename)
334 file.write(epsfile.read())
335 file.write("%%EndDocument\n")
336 file.write("EndEPSF\n")
338 def processPDF(self, file, writer, context, registry, bbox):
339 raise RuntimeError("Including EPS files in PDF files not supported")