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
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
37 0 setgray 0 setlinecap
38 1 setlinewidth 0 setlinejoin
39 10 setmiterlimit [ ] 0 setdash newpath
43 {false setstrokeadjust false setoverprint
48 _EndEPSF
= pswriter
.PSdefinition("EndEPSF", """{
50 count op_count sub {pop} repeat
51 countdictstack dict_count sub {end} repeat
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
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")
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."""
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
:
97 result
= self
.buffer[:count
]
98 self
.buffer = self
.buffer[count
:]
101 self
.buffer += self
.file.read()
102 if EOFmsg
is not None and not len(self
.buffer):
103 raise IOError(EOFmsg
)
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."""
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
):
124 self
.buffer += newbuffer
126 eol
= len(self
.buffer)
127 if not eol
and EOFmsgo
is not None:
128 raise IOError(EOFmsg
)
131 if crpos
!= -1 and crpos
< nlpos
- 1:
133 result
= self
.buffer[:eol
]
134 self
.buffer = self
.buffer[eol
:]
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")
152 # parse the header (use the first BoundingBox)
154 line
= file.readline()
157 if line
.startswith("%%BoundingBox:") and not bboxatend
:
158 values
= line
.split(":", 1)[1].split()
159 if values
== ["(atend)"]:
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
170 raise IOError("no bounding box information found")
173 nesting
= 0 # allow for nested documents
175 line
= file.readline()
176 if line
.startswith("%%BeginData:"):
177 values
= line
.split(":", 1)[1].split()
179 raise IOError("invalid number of arguments")
181 if values
[2] == "Lines":
182 for i
in xrange(int(values
[0])):
184 elif values
[2] != "Bytes":
185 raise IOError("invalid bytesorlines-value")
187 file.read(int(values
[0]))
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:"):
206 elif line
.rstrip() == "%%EndDocument":
208 raise IOError("unmatched EndDocument")
210 elif not nesting
and line
.rstrip() == "%%Trailer":
214 # parse the trailer (use the last BoundingBox)
216 line
= file.readline(EOFmsg
=None)
217 if line
.startswith("%%BoundingBox:"):
218 values
= line
.split(":", 1)[1].split()
220 raise IOError("invalid number of bounding box values")
221 usebbox
= bbox
.bbox_pt(*map(int, values
))
223 raise IOError("missing bounding box information in document trailer")
227 class epsfile(canvas
.canvasitem
):
229 """class for epsfiles"""
233 width
=None, height
=None, scale
=None, align
="bl",
234 clip
=1, translatebbox
=1, bbox
=None,
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
)
250 self
.filename
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_pict_format
)
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
273 self
.width_pt
= self
.mybbox
.urx_pt
-self
.mybbox
.llx_pt
275 self
.width_pt
*= self
.scalex
277 self
.height_pt
= self
.mybbox
.ury_pt
-self
.mybbox
.lly_pt
279 self
.height_pt
*= self
.scaley
281 # take alignment into account
283 if self
.align
[0]=="b":
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
290 raise ValueError("vertical alignment can only be b (bottom), c (center), or t (top)")
292 if self
.align
[1]=="l":
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
299 raise ValueError("horizontal alignment can only be l (left), c (center), or r (right)")
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
)
310 self
.trafo
= self
.trafo
* trafo
.translate_pt(-self
.mybbox
.llx_pt
, -self
.mybbox
.lly_pt
)
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):
321 epsfile
=open(self
.filename
,"rb")
323 raise IOError, "cannot open EPS file '%s'" % self
.filename
325 file.write("BeginEPSF\n")
327 bbrect
= self
.mybbox
.rect().transformed(self
.trafo
)
330 file.write("newpath\n")
331 bbrect
.outputPS(file)
334 self
.trafo
.outputPS(file)
336 file.write("%%%%BeginDocument: %s\n" % self
.filename
)
337 file.write(epsfile
.read())
338 file.write("%%EndDocument\n")
339 file.write("EndEPSF\n")