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
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(self
.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 EOFmsg
is not None:
128 raise IOError(EOFmsg
)
131 if crpos
!= -1 and (nlpos
== -1 or 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)
217 line
= file.readline(EOFmsg
=None)
218 if line
.startswith("%%BoundingBox:"):
219 values
= line
.split(":", 1)[1].split()
221 raise IOError("invalid number of bounding box values")
222 usebbox
= bbox
.bbox_pt(*map(int, values
))
224 raise IOError("missing bounding box information in document trailer")
228 class epsfile(canvas
.canvasitem
):
230 """class for epsfiles"""
234 width
=None, height
=None, scale
=None, align
="bl",
235 clip
=1, translatebbox
=1, bbox
=None,
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
)
251 self
.filename
= pykpathsea
.find_file(filename
, pykpathsea
.kpse_pict_format
)
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
274 self
.width_pt
= self
.mybbox
.urx_pt
-self
.mybbox
.llx_pt
276 self
.width_pt
*= self
.scalex
278 self
.height_pt
= self
.mybbox
.ury_pt
-self
.mybbox
.lly_pt
280 self
.height_pt
*= self
.scaley
282 # take alignment into account
284 if self
.align
[0]=="b":
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
291 raise ValueError("vertical alignment can only be b (bottom), c (center), or t (top)")
293 if self
.align
[1]=="l":
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
300 raise ValueError("horizontal alignment can only be l (left), c (center), or r (right)")
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
)
311 self
.trafo
= self
.trafo
* trafo
.translate_pt(-self
.mybbox
.llx_pt
, -self
.mybbox
.lly_pt
)
314 return self
.mybbox
.transformed(self
.trafo
)
316 def processPS(self
, file, writer
, context
, registry
, bbox
):
317 registry
.add(_BeginEPSF
)
318 registry
.add(_EndEPSF
)
321 epsfile
=open(self
.filename
,"rb")
323 raise IOError, "cannot open EPS file '%s'" % self
.filename
325 file.write("BeginEPSF\n")
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")