parallel deformer -- not yet finished, neither stable, but already nice
[PyX/mjg.git] / pyx / epsfile.py
blobde2f32d9c12c770fecc6d7419dad83c52740f90f
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 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(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 EOFmsgo is not None:
128 raise IOError(EOFmsg)
129 if nlpos != -1:
130 eol = nlpos + 1
131 if crpos != -1 and 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 while 1:
216 line = file.readline(EOFmsg=None)
217 if line.startswith("%%BoundingBox:"):
218 values = line.split(":", 1)[1].split()
219 if len(values) != 4:
220 raise IOError("invalid number of bounding box values")
221 usebbox = bbox.bbox_pt(*map(int, values))
222 if not usebbox:
223 raise IOError("missing bounding box information in document trailer")
224 return usebbox
227 class epsfile(canvas.canvasitem):
229 """class for epsfiles"""
231 def __init__(self,
232 x, y, filename,
233 width=None, height=None, scale=None, align="bl",
234 clip=1, translatebbox=1, bbox=None,
235 kpsearch=0):
236 """inserts epsfile
238 Object for an EPS file named filename at position (x,y). Width, height,
239 scale and aligment can be adjusted by the corresponding parameters. If
240 clip is set, the result gets clipped to the bbox of the EPS file. If
241 translatebbox is not set, the EPS graphics is not translated to the
242 corresponding origin. If bbox is not None, it overrides the bounding
243 box in the epsfile itself. If kpsearch is set then filename is searched
244 using the kpathsea library.
247 self.x_pt = unit.topt(x)
248 self.y_pt = unit.topt(y)
249 if kpsearch:
250 self.filename = pykpathsea.find_file(filename, pykpathsea.kpse_pict_format)
251 else:
252 self.filename = filename
253 self.mybbox = bbox or _readbbox(self.filename)
255 # determine scaling in x and y direction
256 self.scalex = self.scaley = scale
258 if width is not None or height is not None:
259 if scale is not None:
260 raise ValueError("cannot set both width and/or height and scale simultaneously")
261 if height is not None:
262 self.scaley = unit.topt(height)/(self.mybbox.ury_pt-self.mybbox.lly_pt)
263 if width is not None:
264 self.scalex = unit.topt(width)/(self.mybbox.urx_pt-self.mybbox.llx_pt)
266 if self.scalex is None:
267 self.scalex = self.scaley
268 if self.scaley is None:
269 self.scaley = self.scalex
271 # set the actual width and height of the eps file (after a
272 # possible scaling)
273 self.width_pt = self.mybbox.urx_pt-self.mybbox.llx_pt
274 if self.scalex:
275 self.width_pt *= self.scalex
277 self.height_pt = self.mybbox.ury_pt-self.mybbox.lly_pt
278 if self.scaley:
279 self.height_pt *= self.scaley
281 # take alignment into account
282 self.align = align
283 if self.align[0]=="b":
284 pass
285 elif self.align[0]=="c":
286 self.y_pt -= self.height_pt/2.0
287 elif self.align[0]=="t":
288 self.y_pt -= self.height_pt
289 else:
290 raise ValueError("vertical alignment can only be b (bottom), c (center), or t (top)")
292 if self.align[1]=="l":
293 pass
294 elif self.align[1]=="c":
295 self.x_pt -= self.width_pt/2.0
296 elif self.align[1]=="r":
297 self.x_pt -= self.width_pt
298 else:
299 raise ValueError("horizontal alignment can only be l (left), c (center), or r (right)")
301 self.clip = clip
302 self.translatebbox = translatebbox
304 self.trafo = trafo.translate_pt(self.x_pt, self.y_pt)
306 if self.scalex is not None:
307 self.trafo = self.trafo * trafo.scale_pt(self.scalex, self.scaley)
309 if translatebbox:
310 self.trafo = self.trafo * trafo.translate_pt(-self.mybbox.llx_pt, -self.mybbox.lly_pt)
312 def bbox(self):
313 return self.mybbox.transformed(self.trafo)
315 def registerPS(self, registry):
316 registry.add(_BeginEPSF)
317 registry.add(_EndEPSF)
319 def outputPS(self, file, writer, context):
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 bbrect = self.mybbox.rect().transformed(self.trafo)
329 if self.clip:
330 file.write("newpath\n")
331 bbrect.outputPS(file, writer, context)
332 file.write("clip\n")
334 self.trafo.outputPS(file, writer, context)
336 file.write("%%%%BeginDocument: %s\n" % self.filename)
337 file.write(epsfile.read())
338 file.write("%%EndDocument\n")
339 file.write("EndEPSF\n")