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 logging
, os
, string
, tempfile
24 from . import baseclasses
, bbox
, config
, unit
, trafo
, pswriter
26 logger
= logging
.getLogger("pyx")
28 # PostScript-procedure definitions (cf. 5002.EPSF_Spec_v3.0.pdf)
29 # with important correction in EndEPSF:
30 # end operator is missing in the spec!
32 _BeginEPSF
= pswriter
.PSdefinition("BeginEPSF", b
"""{
33 /b4_Inc_state save def
34 /dict_count countdictstack def
35 /op_count count 1 sub def
38 0 setgray 0 setlinecap
39 1 setlinewidth 0 setlinejoin
40 10 setmiterlimit [ ] 0 setdash newpath
44 {false setstrokeadjust false setoverprint
49 _EndEPSF
= pswriter
.PSdefinition("EndEPSF", b
"""{
51 count op_count sub {pop} repeat
52 countdictstack dict_count sub {end} repeat
58 """a line by line file reader
60 This line by line file reader allows for '\n', '\r' and
61 '\r\n' as line separation characters. Line separation
62 characters are not modified (binary mode). It implements
63 a readline, a read and a close method similar to a regular
66 # note: '\n\r' is not considered to be a linebreak as its documented
67 # in the DSC spec #5001, while '\n\r' *is* a *single* linebreak
68 # according to the EPSF spec #5002
70 def __init__(self
, file, typicallinelen
=257):
71 """Opens the file filename for reading.
73 typicallinelen defines the default buffer increase
74 to find the next linebreak."""
76 # note: The maximal line size in an EPS is 255 plus the
77 # linebreak characters. However, we also handle
78 # lines longer than that.
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(b
"\r")
119 nlpos
= self
.buffer.find(b
"\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
:]
139 """returns bounding box of EPS file filename"""
141 file = linefilereader(file)
143 # check the %! header comment
144 if not file.readline().startswith(b
"%!"):
145 raise IOError("file doesn't start with a '%!' header comment")
148 # parse the header (use the first BoundingBox)
150 line
= file.readline()
153 if line
.startswith(b
"%%BoundingBox:") and not bboxatend
:
154 values
= line
.split(b
":", 1)[1].split()
155 if values
== ["(atend)"]:
159 raise IOError("invalid number of bounding box values")
160 return bbox
.bbox_pt(*list(map(int, values
)))
161 elif (line
.rstrip() == b
"%%EndComments" or
162 (len(line
) >= 2 and chr(line
[0]) != "%" and chr(line
[1]) not in string
.whitespace
)):
163 # implicit end of comments section
166 raise IOError("no bounding box information found")
169 nesting
= 0 # allow for nested documents
171 line
= file.readline()
172 if line
.startswith(b
"%%BeginData:"):
173 values
= line
.split(":", 1)[1].split()
175 raise IOError("invalid number of arguments")
177 if values
[2] == b
"Lines":
178 for i
in range(int(values
[0])):
180 elif values
[2] != b
"Bytes":
181 raise IOError("invalid bytesorlines-value")
183 file.read(int(values
[0]))
185 file.read(int(values
[0]))
186 line
= file.readline()
187 # ignore tailing whitespace/newline for binary data
188 if (len(values
) < 3 or values
[2] != "Lines") and not len(line
.strip()):
189 line
= file.readline()
190 if line
.rstrip() != b
"%%EndData":
191 raise IOError("missing EndData")
192 elif line
.startswith(b
"%%BeginBinary:"):
193 file.read(int(line
.split(":", 1)[1]))
194 line
= file.readline()
195 # ignore tailing whitespace/newline
196 if not len(line
.strip()):
197 line
= file.readline()
198 if line
.rstrip() != b
"%%EndBinary":
199 raise IOError("missing EndBinary")
200 elif line
.startswith(b
"%%BeginDocument:"):
202 elif line
.rstrip() == b
"%%EndDocument":
204 raise IOError("unmatched EndDocument")
206 elif not nesting
and line
.rstrip() == b
"%%Trailer":
210 # parse the trailer (use the last BoundingBox)
213 line
= file.readline(EOFmsg
=None)
214 if line
.startswith("%%BoundingBox:"):
215 values
= line
.split(b
":", 1)[1].split()
217 raise IOError("invalid number of bounding box values")
218 usebbox
= bbox
.bbox_pt(*list(map(int, values
)))
220 raise IOError("missing bounding box information in document trailer")
224 class epsfile(baseclasses
.canvasitem
):
226 """class for epsfiles"""
230 width
=None, height
=None, scale
=None, align
="bl",
231 clip
=1, translatebbox
=1, bbox
=None,
235 Object for an EPS file named filename at position (x,y). Width, height,
236 scale and aligment can be adjusted by the corresponding parameters. If
237 clip is set, the result gets clipped to the bbox of the EPS file. If
238 translatebbox is not set, the EPS graphics is not translated to the
239 corresponding origin. If bbox is not None, it overrides the bounding
240 box in the epsfile itself. If kpsearch is set then filename is searched
241 using the kpathsea library.
244 self
.x_pt
= unit
.topt(x
)
245 self
.y_pt
= unit
.topt(y
)
246 self
.filename
= filename
247 self
.kpsearch
= kpsearch
251 with self
.open() as epsfile
:
252 self
.mybbox
= _readbbox(epsfile
)
254 # determine scaling in x and y direction
255 self
.scalex
= self
.scaley
= scale
257 if width
is not None or height
is not None:
258 if scale
is not None:
259 raise ValueError("cannot set both width and/or height and scale simultaneously")
260 if height
is not None:
261 self
.scaley
= unit
.topt(height
)/(self
.mybbox
.ury_pt
-self
.mybbox
.lly_pt
)
262 if width
is not None:
263 self
.scalex
= unit
.topt(width
)/(self
.mybbox
.urx_pt
-self
.mybbox
.llx_pt
)
265 if self
.scalex
is None:
266 self
.scalex
= self
.scaley
267 if self
.scaley
is None:
268 self
.scaley
= self
.scalex
270 # set the actual width and height of the eps file (after a
272 self
.width_pt
= self
.mybbox
.urx_pt
-self
.mybbox
.llx_pt
274 self
.width_pt
*= self
.scalex
276 self
.height_pt
= self
.mybbox
.ury_pt
-self
.mybbox
.lly_pt
278 self
.height_pt
*= self
.scaley
280 # take alignment into account
282 if self
.align
[0]=="b":
284 elif self
.align
[0]=="c":
285 self
.y_pt
-= self
.height_pt
/2.0
286 elif self
.align
[0]=="t":
287 self
.y_pt
-= self
.height_pt
289 raise ValueError("vertical alignment can only be b (bottom), c (center), or t (top)")
291 if self
.align
[1]=="l":
293 elif self
.align
[1]=="c":
294 self
.x_pt
-= self
.width_pt
/2.0
295 elif self
.align
[1]=="r":
296 self
.x_pt
-= self
.width_pt
298 raise ValueError("horizontal alignment can only be l (left), c (center), or r (right)")
301 self
.translatebbox
= translatebbox
303 self
.trafo
= trafo
.translate_pt(self
.x_pt
, self
.y_pt
)
305 if self
.scalex
is not None:
306 self
.trafo
= self
.trafo
* trafo
.scale_pt(self
.scalex
, self
.scaley
)
309 self
.trafo
= self
.trafo
* trafo
.translate_pt(-self
.mybbox
.llx_pt
, -self
.mybbox
.lly_pt
)
313 return config
.open(self
.filename
, [config
.format
.pict
])
315 return open(self
.filename
, "rb")
318 return self
.mybbox
.transformed(self
.trafo
)
320 def processPS(self
, file, writer
, context
, registry
, bbox
):
321 registry
.add(_BeginEPSF
)
322 registry
.add(_EndEPSF
)
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
)
333 file.write("%%%%BeginDocument: %s\n" % self
.filename
)
335 with self
.open() as epsfile
:
336 file.write_bytes(epsfile
.read())
338 file.write("%%EndDocument\n")
339 file.write("EndEPSF\n")
341 def processPDF(self
, file, writer
, context
, registry
, bbox
):
342 logger
.warning("EPS file is included as a bitmap created using pipeGS")
343 from pyx
import bitmap
, canvas
344 from PIL
import Image
347 i
= Image
.open(c
.pipeGS(device
="pngalpha", resolution
=600))
349 b
= bitmap
.bitmap_pt(self
.bbox().llx_pt
, self
.bbox().lly_pt
, i
)
350 # we slightly shift the bitmap to re-center it, as the bitmap might contain some additional border
351 # unfortunately we need to construct another bitmap instance for that ...
352 b
= bitmap
.bitmap_pt(self
.bbox().llx_pt
+ 0.5*(self
.bbox().width_pt()-b
.bbox().width_pt()),
353 self
.bbox().lly_pt
+ 0.5*(self
.bbox().height_pt()-b
.bbox().height_pt()), i
)
354 b
.processPDF(file, writer
, context
, registry
, bbox
)