| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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: |
| | |
| | pass |
| | return False |
| |
|
| |
|
| | def Ghostscript(tile, size, fp, scale=1, transparency=False): |
| | """Render an image using Ghostscript""" |
| |
|
| | |
| | decoder, tile, offset, data = tile[0] |
| | length, bbox = data |
| |
|
| | |
| | scale = int(scale) or 1 |
| | |
| | |
| | size = (size[0] * scale, size[1] * scale) |
| | |
| | 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 |
| |
|
| | |
| | |
| | |
| | with open(infile_temp, "wb") as f: |
| | |
| | fp.seek(0, io.SEEK_END) |
| | fsize = fp.tell() |
| | |
| | |
| | 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" |
| |
|
| | |
| | command = [ |
| | "gs", |
| | "-q", |
| | "-g%dx%d" % size, |
| | "-r%fx%f" % res, |
| | "-dBATCH", |
| | "-dNOPAUSE", |
| | "-dSAFER", |
| | f"-sDEVICE={device}", |
| | f"-sOutputFile={outfile}", |
| | |
| | "-c", |
| | f"{-bbox[0]} {-bbox[1]} translate", |
| | "-f", |
| | infile, |
| | |
| | "-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 |
| |
|
| | |
| | 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) |
| | |
| | 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) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | 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) |
| |
|
| | |
| | |
| | fp = PSFile(self.fp) |
| |
|
| | |
| | fp.seek(offset) |
| |
|
| | box = None |
| |
|
| | self.mode = "RGB" |
| | self._size = 1, 1 |
| |
|
| | |
| | |
| |
|
| | 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: |
| | |
| | |
| | |
| | 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] == "%": |
| | |
| | |
| | pass |
| | else: |
| | raise OSError("bad EPS header") |
| |
|
| | s_raw = fp.readline() |
| | s = s_raw.strip("\r\n") |
| |
|
| | if s and s[:1] != "%": |
| | break |
| |
|
| | |
| | |
| |
|
| | while s[:1] == "%": |
| |
|
| | if len(s) > 255: |
| | raise SyntaxError("not an EPS file") |
| |
|
| | if s[:11] == "%ImageData:": |
| | |
| | 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": |
| | |
| | fp.seek(0, io.SEEK_END) |
| | length = fp.tell() |
| | offset = 0 |
| | elif i32(s, 0) == 0xC6D3D0C5: |
| | |
| | |
| | |
| | |
| | |
| | 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): |
| | |
| | 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): |
| | |
| | |
| | pass |
| |
|
| |
|
| | |
| | |
| |
|
| |
|
| | def _save(im, fp, filename, eps=1): |
| | """EPS Writer for the Python Imaging Library.""" |
| |
|
| | |
| | |
| | im.load() |
| |
|
| | |
| | |
| | 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: |
| | |
| | |
| | fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n") |
| | fp.write(b"%%Creator: PIL 0.1 EpsEncode\n") |
| | |
| | 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) |
| |
|
| | |
| | |
| | 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) |
| | 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") |
| |
|