1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002-2011 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import os
, string
, tempfile
, warnings
24 from . import baseclasses
, bbox
, config
, unit
, trafo
, pswriter
26 # PostScript-procedure definitions (cf. 5002.EPSF_Spec_v3.0.pdf)
27 # with important correction in EndEPSF:
28 # end operator is missing in the spec!
30 _BeginEPSF
= pswriter
.PSdefinition("BeginEPSF", b
"""{
31 /b4_Inc_state save def
32 /dict_count countdictstack def
33 /op_count count 1 sub def
36 0 setgray 0 setlinecap
37 1 setlinewidth 0 setlinejoin
38 10 setmiterlimit [ ] 0 setdash newpath
42 {false setstrokeadjust false setoverprint
47 _EndEPSF
= pswriter
.PSdefinition("EndEPSF", b
"""{
49 count op_count sub {pop} repeat
50 countdictstack dict_count sub {end} repeat
56 """a line by line file reader
58 This line by line file reader allows for '\n', '\r' and
59 '\r\n' as line separation characters. Line separation
60 characters are not modified (binary mode). It implements
61 a readline, a read and a close method similar to a regular
64 # note: '\n\r' is not considered to be a linebreak as its documented
65 # in the DSC spec #5001, while '\n\r' *is* a *single* linebreak
66 # according to the EPSF spec #5002
68 def __init__(self
, file, typicallinelen
=257):
69 """Opens the file filename for reading.
71 typicallinelen defines the default buffer increase
72 to find the next linebreak."""
74 # note: The maximal line size in an EPS is 255 plus the
75 # linebreak characters. However, we also handle
76 # lines longer than that.
79 self
.typicallinelen
= typicallinelen
81 def read(self
, count
=None, EOFmsg
="unexpected end of file"):
82 """read bytes from the file
84 count is the number of bytes to be read when set. Then count
85 is unset, the rest of the file is returned. EOFmsg is used
86 to raise a IOError, when the end of the file is reached while
87 reading count bytes or when the rest of the file is empty when
88 count is unset. When EOFmsg is set to None, less than the
89 requested number of bytes might be returned."""
91 if count
> len(self
.buffer):
92 self
.buffer += self
.file.read(count
- len(self
.buffer))
93 if EOFmsg
is not None and len(self
.buffer) < count
:
95 result
= self
.buffer[:count
]
96 self
.buffer = self
.buffer[count
:]
99 self
.buffer += self
.file.read()
100 if EOFmsg
is not None and not len(self
.buffer):
101 raise IOError(EOFmsg
)
106 def readline(self
, EOFmsg
="unexpected end of file"):
107 """reads a line from the file
109 Lines are separated by '\n', '\r' or '\r\n'. The line separation
110 strings are included in the return value. The last line might not
111 end with an line separation string. Reading beyond the file generates
112 an IOError with the EOFmsg message. When EOFmsg is None, an empty
113 string is returned when reading beyond the end of the file."""
116 crpos
= self
.buffer.find(b
"\r")
117 nlpos
= self
.buffer.find(b
"\n")
118 if nlpos
== -1 and (crpos
== -1 or crpos
== len(self
.buffer) - 1) and not EOF
:
119 newbuffer
= self
.file.read(self
.typicallinelen
)
120 if not len(newbuffer
):
122 self
.buffer += newbuffer
124 eol
= len(self
.buffer)
125 if not eol
and EOFmsg
is not None:
126 raise IOError(EOFmsg
)
129 if crpos
!= -1 and (nlpos
== -1 or crpos
< nlpos
- 1):
131 result
= self
.buffer[:eol
]
132 self
.buffer = self
.buffer[eol
:]
140 """returns bounding box of EPS file filename"""
142 file = linefilereader(file)
144 # check the %! header comment
145 if not file.readline().startswith(b
"%!"):
146 raise IOError("file doesn't start with a '%!' header comment")
149 # parse the header (use the first BoundingBox)
151 line
= file.readline()
154 if line
.startswith(b
"%%BoundingBox:") and not bboxatend
:
155 values
= line
.split(b
":", 1)[1].split()
156 if values
== ["(atend)"]:
160 raise IOError("invalid number of bounding box values")
161 return bbox
.bbox_pt(*list(map(int, values
)))
162 elif (line
.rstrip() == b
"%%EndComments" or
163 (len(line
) >= 2 and line
[0] != "%" and line
[1] not in string
.whitespace
)):
164 # implicit end of comments section
167 raise IOError("no bounding box information found")
170 nesting
= 0 # allow for nested documents
172 line
= file.readline()
173 if line
.startswith(b
"%%BeginData:"):
174 values
= line
.split(":", 1)[1].split()
176 raise IOError("invalid number of arguments")
178 if values
[2] == b
"Lines":
179 for i
in range(int(values
[0])):
181 elif values
[2] != b
"Bytes":
182 raise IOError("invalid bytesorlines-value")
184 file.read(int(values
[0]))
186 file.read(int(values
[0]))
187 line
= file.readline()
188 # ignore tailing whitespace/newline for binary data
189 if (len(values
) < 3 or values
[2] != "Lines") and not len(line
.strip()):
190 line
= file.readline()
191 if line
.rstrip() != b
"%%EndData":
192 raise IOError("missing EndData")
193 elif line
.startswith(b
"%%BeginBinary:"):
194 file.read(int(line
.split(":", 1)[1]))
195 line
= file.readline()
196 # ignore tailing whitespace/newline
197 if not len(line
.strip()):
198 line
= file.readline()
199 if line
.rstrip() != b
"%%EndBinary":
200 raise IOError("missing EndBinary")
201 elif line
.startswith(b
"%%BeginDocument:"):
203 elif line
.rstrip() == b
"%%EndDocument":
205 raise IOError("unmatched EndDocument")
207 elif not nesting
and line
.rstrip() == b
"%%Trailer":
211 # parse the trailer (use the last BoundingBox)
214 line
= file.readline(EOFmsg
=None)
215 if line
.startswith("%%BoundingBox:"):
216 values
= line
.split(b
":", 1)[1].split()
218 raise IOError("invalid number of bounding box values")
219 usebbox
= bbox
.bbox_pt(*list(map(int, values
)))
221 raise IOError("missing bounding box information in document trailer")
225 class epsfile(baseclasses
.canvasitem
):
227 """class for epsfiles"""
231 width
=None, height
=None, scale
=None, align
="bl",
232 clip
=1, translatebbox
=1, bbox
=None,
236 Object for an EPS file named filename at position (x,y). Width, height,
237 scale and aligment can be adjusted by the corresponding parameters. If
238 clip is set, the result gets clipped to the bbox of the EPS file. If
239 translatebbox is not set, the EPS graphics is not translated to the
240 corresponding origin. If bbox is not None, it overrides the bounding
241 box in the epsfile itself. If kpsearch is set then filename is searched
242 using the kpathsea library.
245 self
.x_pt
= unit
.topt(x
)
246 self
.y_pt
= unit
.topt(y
)
247 self
.filename
= filename
248 self
.kpsearch
= kpsearch
252 epsfile
= self
.open()
253 self
.mybbox
= _readbbox(epsfile
)
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
)
315 return config
.open(self
.filename
, [config
.format
.pict
])
317 return open(self
.filename
, "rb")
320 return self
.mybbox
.transformed(self
.trafo
)
322 def processPS(self
, file, writer
, context
, registry
, bbox
):
323 registry
.add(_BeginEPSF
)
324 registry
.add(_EndEPSF
)
327 file.write("BeginEPSF\n")
330 llx_pt
, lly_pt
, urx_pt
, ury_pt
= self
.mybbox
.transformed(self
.trafo
).highrestuple_pt()
331 file.write("%g %g %g %g rectclip\n" % (llx_pt
, lly_pt
, urx_pt
-llx_pt
, ury_pt
-lly_pt
))
333 self
.trafo
.processPS(file, writer
, context
, registry
)
335 file.write("%%%%BeginDocument: %s\n" % self
.filename
)
337 epsfile
= self
.open()
338 file.write_bytes(epsfile
.read())
341 file.write("%%EndDocument\n")
342 file.write("EndEPSF\n")
344 def processPDF(self
, file, writer
, context
, registry
, bbox
):
345 warnings
.warn("EPS file is included as a bitmap created using pipeGS")
346 from pyx
import bitmap
, canvas
347 from PIL
import Image
350 i
= Image
.open(c
.pipeGS(device
="pngalpha", resolution
=600, seekable
=True))
352 b
= bitmap
.bitmap_pt(self
.bbox().llx_pt
, self
.bbox().lly_pt
, i
)
353 # we slightly shift the bitmap to re-center it, as the bitmap might contain some additional border
354 # unfortunately we need to construct another bitmap instance for that ...
355 b
= bitmap
.bitmap_pt(self
.bbox().llx_pt
+ 0.5*(self
.bbox().width_pt()-b
.bbox().width_pt()),
356 self
.bbox().lly_pt
+ 0.5*(self
.bbox().height_pt()-b
.bbox().height_pt()), i
)
357 b
.processPDF(file, writer
, context
, registry
, bbox
)