Spaces:
Runtime error
Runtime error
| # | |
| # The Python Imaging Library. | |
| # $Id$ | |
| # | |
| # EPS file handling | |
| # | |
| # History: | |
| # 1995-09-01 fl Created (0.1) | |
| # 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2) | |
| # 1996-08-22 fl Don't choke on floating point BoundingBox values | |
| # 1996-08-23 fl Handle files from Macintosh (0.3) | |
| # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) | |
| # 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5) | |
| # 2014-05-07 e Handling of EPS with binary preview and fixed resolution | |
| # resizing | |
| # | |
| # Copyright (c) 1997-2003 by Secret Labs AB. | |
| # Copyright (c) 1995-2003 by Fredrik Lundh | |
| # | |
| # See the README file for information on usage and redistribution. | |
| # | |
| import io | |
| import os | |
| import re | |
| import subprocess | |
| import sys | |
| import tempfile | |
| from . import Image, ImageFile | |
| from ._binary import i32le as i32 | |
| # | |
| # -------------------------------------------------------------------- | |
| split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") | |
| field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") | |
| gs_windows_binary = None | |
| if sys.platform.startswith("win"): | |
| import shutil | |
| for binary in ("gswin32c", "gswin64c", "gs"): | |
| if shutil.which(binary) is not None: | |
| gs_windows_binary = binary | |
| break | |
| else: | |
| gs_windows_binary = False | |
| def has_ghostscript(): | |
| if gs_windows_binary: | |
| return True | |
| if not sys.platform.startswith("win"): | |
| try: | |
| subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL) | |
| return True | |
| except OSError: | |
| # No Ghostscript | |
| pass | |
| return False | |
| def Ghostscript(tile, size, fp, scale=1, transparency=False): | |
| """Render an image using Ghostscript""" | |
| # Unpack decoder tile | |
| decoder, tile, offset, data = tile[0] | |
| length, bbox = data | |
| # Hack to support hi-res rendering | |
| scale = int(scale) or 1 | |
| # orig_size = size | |
| # orig_bbox = bbox | |
| size = (size[0] * scale, size[1] * scale) | |
| # resolution is dependent on bbox and size | |
| res = ( | |
| 72.0 * size[0] / (bbox[2] - bbox[0]), | |
| 72.0 * size[1] / (bbox[3] - bbox[1]), | |
| ) | |
| out_fd, outfile = tempfile.mkstemp() | |
| os.close(out_fd) | |
| infile_temp = None | |
| if hasattr(fp, "name") and os.path.exists(fp.name): | |
| infile = fp.name | |
| else: | |
| in_fd, infile_temp = tempfile.mkstemp() | |
| os.close(in_fd) | |
| infile = infile_temp | |
| # Ignore length and offset! | |
| # Ghostscript can read it | |
| # Copy whole file to read in Ghostscript | |
| with open(infile_temp, "wb") as f: | |
| # fetch length of fp | |
| fp.seek(0, io.SEEK_END) | |
| fsize = fp.tell() | |
| # ensure start position | |
| # go back | |
| fp.seek(0) | |
| lengthfile = fsize | |
| while lengthfile > 0: | |
| s = fp.read(min(lengthfile, 100 * 1024)) | |
| if not s: | |
| break | |
| lengthfile -= len(s) | |
| f.write(s) | |
| device = "pngalpha" if transparency else "ppmraw" | |
| # Build Ghostscript command | |
| command = [ | |
| "gs", | |
| "-q", # quiet mode | |
| "-g%dx%d" % size, # set output geometry (pixels) | |
| "-r%fx%f" % res, # set input DPI (dots per inch) | |
| "-dBATCH", # exit after processing | |
| "-dNOPAUSE", # don't pause between pages | |
| "-dSAFER", # safe mode | |
| f"-sDEVICE={device}", | |
| f"-sOutputFile={outfile}", # output file | |
| # adjust for image origin | |
| "-c", | |
| f"{-bbox[0]} {-bbox[1]} translate", | |
| "-f", | |
| infile, # input file | |
| # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272) | |
| "-c", | |
| "showpage", | |
| ] | |
| if gs_windows_binary is not None: | |
| if not gs_windows_binary: | |
| raise OSError("Unable to locate Ghostscript on paths") | |
| command[0] = gs_windows_binary | |
| # push data through Ghostscript | |
| try: | |
| startupinfo = None | |
| if sys.platform.startswith("win"): | |
| startupinfo = subprocess.STARTUPINFO() | |
| startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW | |
| subprocess.check_call(command, startupinfo=startupinfo) | |
| out_im = Image.open(outfile) | |
| out_im.load() | |
| finally: | |
| try: | |
| os.unlink(outfile) | |
| if infile_temp: | |
| os.unlink(infile_temp) | |
| except OSError: | |
| pass | |
| im = out_im.im.copy() | |
| out_im.close() | |
| return im | |
| class PSFile: | |
| """ | |
| Wrapper for bytesio object that treats either CR or LF as end of line. | |
| """ | |
| def __init__(self, fp): | |
| self.fp = fp | |
| self.char = None | |
| def seek(self, offset, whence=io.SEEK_SET): | |
| self.char = None | |
| self.fp.seek(offset, whence) | |
| def readline(self): | |
| s = [self.char or b""] | |
| self.char = None | |
| c = self.fp.read(1) | |
| while (c not in b"\r\n") and len(c): | |
| s.append(c) | |
| c = self.fp.read(1) | |
| self.char = self.fp.read(1) | |
| # line endings can be 1 or 2 of \r \n, in either order | |
| if self.char in b"\r\n": | |
| self.char = None | |
| return b"".join(s).decode("latin-1") | |
| def _accept(prefix): | |
| return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5) | |
| ## | |
| # Image plugin for Encapsulated PostScript. This plugin supports only | |
| # a few variants of this format. | |
| class EpsImageFile(ImageFile.ImageFile): | |
| """EPS File Parser for the Python Imaging Library""" | |
| format = "EPS" | |
| format_description = "Encapsulated Postscript" | |
| mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"} | |
| def _open(self): | |
| (length, offset) = self._find_offset(self.fp) | |
| # Rewrap the open file pointer in something that will | |
| # convert line endings and decode to latin-1. | |
| fp = PSFile(self.fp) | |
| # go to offset - start of "%!PS" | |
| fp.seek(offset) | |
| box = None | |
| self.mode = "RGB" | |
| self._size = 1, 1 # FIXME: huh? | |
| # | |
| # Load EPS header | |
| s_raw = fp.readline() | |
| s = s_raw.strip("\r\n") | |
| while s_raw: | |
| if s: | |
| if len(s) > 255: | |
| raise SyntaxError("not an EPS file") | |
| try: | |
| m = split.match(s) | |
| except re.error as e: | |
| raise SyntaxError("not an EPS file") from e | |
| if m: | |
| k, v = m.group(1, 2) | |
| self.info[k] = v | |
| if k == "BoundingBox": | |
| try: | |
| # Note: The DSC spec says that BoundingBox | |
| # fields should be integers, but some drivers | |
| # put floating point values there anyway. | |
| box = [int(float(i)) for i in v.split()] | |
| self._size = box[2] - box[0], box[3] - box[1] | |
| self.tile = [ | |
| ("eps", (0, 0) + self.size, offset, (length, box)) | |
| ] | |
| except Exception: | |
| pass | |
| else: | |
| m = field.match(s) | |
| if m: | |
| k = m.group(1) | |
| if k == "EndComments": | |
| break | |
| if k[:8] == "PS-Adobe": | |
| self.info[k[:8]] = k[9:] | |
| else: | |
| self.info[k] = "" | |
| elif s[0] == "%": | |
| # handle non-DSC PostScript comments that some | |
| # tools mistakenly put in the Comments section | |
| pass | |
| else: | |
| raise OSError("bad EPS header") | |
| s_raw = fp.readline() | |
| s = s_raw.strip("\r\n") | |
| if s and s[:1] != "%": | |
| break | |
| # | |
| # Scan for an "ImageData" descriptor | |
| while s[:1] == "%": | |
| if len(s) > 255: | |
| raise SyntaxError("not an EPS file") | |
| if s[:11] == "%ImageData:": | |
| # Encoded bitmapped image. | |
| x, y, bi, mo = s[11:].split(None, 7)[:4] | |
| if int(bi) == 1: | |
| self.mode = "1" | |
| elif int(bi) == 8: | |
| try: | |
| self.mode = self.mode_map[int(mo)] | |
| except ValueError: | |
| break | |
| else: | |
| break | |
| self._size = int(x), int(y) | |
| return | |
| s = fp.readline().strip("\r\n") | |
| if not s: | |
| break | |
| if not box: | |
| raise OSError("cannot determine EPS bounding box") | |
| def _find_offset(self, fp): | |
| s = fp.read(160) | |
| if s[:4] == b"%!PS": | |
| # for HEAD without binary preview | |
| fp.seek(0, io.SEEK_END) | |
| length = fp.tell() | |
| offset = 0 | |
| elif i32(s, 0) == 0xC6D3D0C5: | |
| # FIX for: Some EPS file not handled correctly / issue #302 | |
| # EPS can contain binary data | |
| # or start directly with latin coding | |
| # more info see: | |
| # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf | |
| offset = i32(s, 4) | |
| length = i32(s, 8) | |
| else: | |
| raise SyntaxError("not an EPS file") | |
| return length, offset | |
| def load(self, scale=1, transparency=False): | |
| # Load EPS via Ghostscript | |
| if self.tile: | |
| self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) | |
| self.mode = self.im.mode | |
| self._size = self.im.size | |
| self.tile = [] | |
| return Image.Image.load(self) | |
| def load_seek(self, *args, **kwargs): | |
| # we can't incrementally load, so force ImageFile.parser to | |
| # use our custom load method by defining this method. | |
| pass | |
| # | |
| # -------------------------------------------------------------------- | |
| def _save(im, fp, filename, eps=1): | |
| """EPS Writer for the Python Imaging Library.""" | |
| # | |
| # make sure image data is available | |
| im.load() | |
| # | |
| # determine PostScript image mode | |
| if im.mode == "L": | |
| operator = (8, 1, b"image") | |
| elif im.mode == "RGB": | |
| operator = (8, 3, b"false 3 colorimage") | |
| elif im.mode == "CMYK": | |
| operator = (8, 4, b"false 4 colorimage") | |
| else: | |
| raise ValueError("image mode is not supported") | |
| if eps: | |
| # | |
| # write EPS header | |
| fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n") | |
| fp.write(b"%%Creator: PIL 0.1 EpsEncode\n") | |
| # fp.write("%%CreationDate: %s"...) | |
| fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size) | |
| fp.write(b"%%Pages: 1\n") | |
| fp.write(b"%%EndComments\n") | |
| fp.write(b"%%Page: 1 1\n") | |
| fp.write(b"%%ImageData: %d %d " % im.size) | |
| fp.write(b'%d %d 0 1 1 "%s"\n' % operator) | |
| # | |
| # image header | |
| fp.write(b"gsave\n") | |
| fp.write(b"10 dict begin\n") | |
| fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1])) | |
| fp.write(b"%d %d scale\n" % im.size) | |
| fp.write(b"%d %d 8\n" % im.size) # <= bits | |
| fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) | |
| fp.write(b"{ currentfile buf readhexstring pop } bind\n") | |
| fp.write(operator[2] + b"\n") | |
| if hasattr(fp, "flush"): | |
| fp.flush() | |
| ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)]) | |
| fp.write(b"\n%%%%EndBinary\n") | |
| fp.write(b"grestore end\n") | |
| if hasattr(fp, "flush"): | |
| fp.flush() | |
| # | |
| # -------------------------------------------------------------------- | |
| Image.register_open(EpsImageFile.format, EpsImageFile, _accept) | |
| Image.register_save(EpsImageFile.format, _save) | |
| Image.register_extensions(EpsImageFile.format, [".ps", ".eps"]) | |
| Image.register_mime(EpsImageFile.format, "application/postscript") | |