Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/EpsImagePlugin.py: 12%
217 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1#
2# The Python Imaging Library.
3# $Id$
4#
5# EPS file handling
6#
7# History:
8# 1995-09-01 fl Created (0.1)
9# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
10# 1996-08-22 fl Don't choke on floating point BoundingBox values
11# 1996-08-23 fl Handle files from Macintosh (0.3)
12# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
13# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
14# 2014-05-07 e Handling of EPS with binary preview and fixed resolution
15# resizing
16#
17# Copyright (c) 1997-2003 by Secret Labs AB.
18# Copyright (c) 1995-2003 by Fredrik Lundh
19#
20# See the README file for information on usage and redistribution.
21#
23import io
24import os
25import re
26import subprocess
27import sys
28import tempfile
30from . import Image, ImageFile
31from ._binary import i32le as i32
33#
34# --------------------------------------------------------------------
36split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
37field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
39gs_windows_binary = None
40if sys.platform.startswith("win"): 40 ↛ 41line 40 didn't jump to line 41, because the condition on line 40 was never true
41 import shutil
43 for binary in ("gswin32c", "gswin64c", "gs"):
44 if shutil.which(binary) is not None:
45 gs_windows_binary = binary
46 break
47 else:
48 gs_windows_binary = False
51def has_ghostscript():
52 if gs_windows_binary:
53 return True
54 if not sys.platform.startswith("win"):
55 try:
56 subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
57 return True
58 except OSError:
59 # No Ghostscript
60 pass
61 return False
64def Ghostscript(tile, size, fp, scale=1, transparency=False):
65 """Render an image using Ghostscript"""
67 # Unpack decoder tile
68 decoder, tile, offset, data = tile[0]
69 length, bbox = data
71 # Hack to support hi-res rendering
72 scale = int(scale) or 1
73 # orig_size = size
74 # orig_bbox = bbox
75 size = (size[0] * scale, size[1] * scale)
76 # resolution is dependent on bbox and size
77 res = (
78 72.0 * size[0] / (bbox[2] - bbox[0]),
79 72.0 * size[1] / (bbox[3] - bbox[1]),
80 )
82 out_fd, outfile = tempfile.mkstemp()
83 os.close(out_fd)
85 infile_temp = None
86 if hasattr(fp, "name") and os.path.exists(fp.name):
87 infile = fp.name
88 else:
89 in_fd, infile_temp = tempfile.mkstemp()
90 os.close(in_fd)
91 infile = infile_temp
93 # Ignore length and offset!
94 # Ghostscript can read it
95 # Copy whole file to read in Ghostscript
96 with open(infile_temp, "wb") as f:
97 # fetch length of fp
98 fp.seek(0, io.SEEK_END)
99 fsize = fp.tell()
100 # ensure start position
101 # go back
102 fp.seek(0)
103 lengthfile = fsize
104 while lengthfile > 0:
105 s = fp.read(min(lengthfile, 100 * 1024))
106 if not s:
107 break
108 lengthfile -= len(s)
109 f.write(s)
111 device = "pngalpha" if transparency else "ppmraw"
113 # Build Ghostscript command
114 command = [
115 "gs",
116 "-q", # quiet mode
117 "-g%dx%d" % size, # set output geometry (pixels)
118 "-r%fx%f" % res, # set input DPI (dots per inch)
119 "-dBATCH", # exit after processing
120 "-dNOPAUSE", # don't pause between pages
121 "-dSAFER", # safe mode
122 f"-sDEVICE={device}",
123 f"-sOutputFile={outfile}", # output file
124 # adjust for image origin
125 "-c",
126 f"{-bbox[0]} {-bbox[1]} translate",
127 "-f",
128 infile, # input file
129 # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
130 "-c",
131 "showpage",
132 ]
134 if gs_windows_binary is not None:
135 if not gs_windows_binary:
136 raise OSError("Unable to locate Ghostscript on paths")
137 command[0] = gs_windows_binary
139 # push data through Ghostscript
140 try:
141 startupinfo = None
142 if sys.platform.startswith("win"):
143 startupinfo = subprocess.STARTUPINFO()
144 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
145 subprocess.check_call(command, startupinfo=startupinfo)
146 out_im = Image.open(outfile)
147 out_im.load()
148 finally:
149 try:
150 os.unlink(outfile)
151 if infile_temp:
152 os.unlink(infile_temp)
153 except OSError:
154 pass
156 im = out_im.im.copy()
157 out_im.close()
158 return im
161class PSFile:
162 """
163 Wrapper for bytesio object that treats either CR or LF as end of line.
164 """
166 def __init__(self, fp):
167 self.fp = fp
168 self.char = None
170 def seek(self, offset, whence=io.SEEK_SET):
171 self.char = None
172 self.fp.seek(offset, whence)
174 def readline(self):
175 s = [self.char or b""]
176 self.char = None
178 c = self.fp.read(1)
179 while (c not in b"\r\n") and len(c):
180 s.append(c)
181 c = self.fp.read(1)
183 self.char = self.fp.read(1)
184 # line endings can be 1 or 2 of \r \n, in either order
185 if self.char in b"\r\n":
186 self.char = None
188 return b"".join(s).decode("latin-1")
191def _accept(prefix):
192 return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
195##
196# Image plugin for Encapsulated PostScript. This plugin supports only
197# a few variants of this format.
200class EpsImageFile(ImageFile.ImageFile):
201 """EPS File Parser for the Python Imaging Library"""
203 format = "EPS"
204 format_description = "Encapsulated Postscript"
206 mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
208 def _open(self):
209 (length, offset) = self._find_offset(self.fp)
211 # Rewrap the open file pointer in something that will
212 # convert line endings and decode to latin-1.
213 fp = PSFile(self.fp)
215 # go to offset - start of "%!PS"
216 fp.seek(offset)
218 box = None
220 self.mode = "RGB"
221 self._size = 1, 1 # FIXME: huh?
223 #
224 # Load EPS header
226 s_raw = fp.readline()
227 s = s_raw.strip("\r\n")
229 while s_raw:
230 if s:
231 if len(s) > 255:
232 raise SyntaxError("not an EPS file")
234 try:
235 m = split.match(s)
236 except re.error as e:
237 raise SyntaxError("not an EPS file") from e
239 if m:
240 k, v = m.group(1, 2)
241 self.info[k] = v
242 if k == "BoundingBox":
243 try:
244 # Note: The DSC spec says that BoundingBox
245 # fields should be integers, but some drivers
246 # put floating point values there anyway.
247 box = [int(float(i)) for i in v.split()]
248 self._size = box[2] - box[0], box[3] - box[1]
249 self.tile = [
250 ("eps", (0, 0) + self.size, offset, (length, box))
251 ]
252 except Exception:
253 pass
255 else:
256 m = field.match(s)
257 if m:
258 k = m.group(1)
260 if k == "EndComments":
261 break
262 if k[:8] == "PS-Adobe":
263 self.info[k[:8]] = k[9:]
264 else:
265 self.info[k] = ""
266 elif s[0] == "%":
267 # handle non-DSC PostScript comments that some
268 # tools mistakenly put in the Comments section
269 pass
270 else:
271 raise OSError("bad EPS header")
273 s_raw = fp.readline()
274 s = s_raw.strip("\r\n")
276 if s and s[:1] != "%":
277 break
279 #
280 # Scan for an "ImageData" descriptor
282 while s[:1] == "%":
284 if len(s) > 255:
285 raise SyntaxError("not an EPS file")
287 if s[:11] == "%ImageData:":
288 # Encoded bitmapped image.
289 x, y, bi, mo = s[11:].split(None, 7)[:4]
291 if int(bi) != 8:
292 break
293 try:
294 self.mode = self.mode_map[int(mo)]
295 except ValueError:
296 break
298 self._size = int(x), int(y)
299 return
301 s = fp.readline().strip("\r\n")
302 if not s:
303 break
305 if not box:
306 raise OSError("cannot determine EPS bounding box")
308 def _find_offset(self, fp):
310 s = fp.read(160)
312 if s[:4] == b"%!PS":
313 # for HEAD without binary preview
314 fp.seek(0, io.SEEK_END)
315 length = fp.tell()
316 offset = 0
317 elif i32(s, 0) == 0xC6D3D0C5:
318 # FIX for: Some EPS file not handled correctly / issue #302
319 # EPS can contain binary data
320 # or start directly with latin coding
321 # more info see:
322 # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
323 offset = i32(s, 4)
324 length = i32(s, 8)
325 else:
326 raise SyntaxError("not an EPS file")
328 return length, offset
330 def load(self, scale=1, transparency=False):
331 # Load EPS via Ghostscript
332 if self.tile:
333 self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
334 self.mode = self.im.mode
335 self._size = self.im.size
336 self.tile = []
337 return Image.Image.load(self)
339 def load_seek(self, *args, **kwargs):
340 # we can't incrementally load, so force ImageFile.parser to
341 # use our custom load method by defining this method.
342 pass
345#
346# --------------------------------------------------------------------
349def _save(im, fp, filename, eps=1):
350 """EPS Writer for the Python Imaging Library."""
352 #
353 # make sure image data is available
354 im.load()
356 #
357 # determine PostScript image mode
358 if im.mode == "L":
359 operator = (8, 1, b"image")
360 elif im.mode == "RGB":
361 operator = (8, 3, b"false 3 colorimage")
362 elif im.mode == "CMYK":
363 operator = (8, 4, b"false 4 colorimage")
364 else:
365 raise ValueError("image mode is not supported")
367 if eps:
368 #
369 # write EPS header
370 fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n")
371 fp.write(b"%%Creator: PIL 0.1 EpsEncode\n")
372 # fp.write("%%CreationDate: %s"...)
373 fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size)
374 fp.write(b"%%Pages: 1\n")
375 fp.write(b"%%EndComments\n")
376 fp.write(b"%%Page: 1 1\n")
377 fp.write(b"%%ImageData: %d %d " % im.size)
378 fp.write(b'%d %d 0 1 1 "%s"\n' % operator)
380 #
381 # image header
382 fp.write(b"gsave\n")
383 fp.write(b"10 dict begin\n")
384 fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1]))
385 fp.write(b"%d %d scale\n" % im.size)
386 fp.write(b"%d %d 8\n" % im.size) # <= bits
387 fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
388 fp.write(b"{ currentfile buf readhexstring pop } bind\n")
389 fp.write(operator[2] + b"\n")
390 if hasattr(fp, "flush"):
391 fp.flush()
393 ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)])
395 fp.write(b"\n%%%%EndBinary\n")
396 fp.write(b"grestore end\n")
397 if hasattr(fp, "flush"):
398 fp.flush()
401#
402# --------------------------------------------------------------------
405Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
407Image.register_save(EpsImageFile.format, _save)
409Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
411Image.register_mime(EpsImageFile.format, "application/postscript")