mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 09:57:43 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			430 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			430 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#
 | 
						|
# 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 re
 | 
						|
import io
 | 
						|
import sys
 | 
						|
from PIL import Image, ImageFile, _binary
 | 
						|
 | 
						|
__version__ = "0.5"
 | 
						|
 | 
						|
#
 | 
						|
# --------------------------------------------------------------------
 | 
						|
 | 
						|
i32 = _binary.i32le
 | 
						|
o32 = _binary.o32le
 | 
						|
 | 
						|
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
 | 
						|
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
 | 
						|
 | 
						|
gs_windows_binary = None
 | 
						|
if sys.platform.startswith('win'):
 | 
						|
    import shutil
 | 
						|
    if hasattr(shutil, 'which'):
 | 
						|
        which = shutil.which
 | 
						|
    else:
 | 
						|
        # Python < 3.3
 | 
						|
        import distutils.spawn
 | 
						|
        which = distutils.spawn.find_executable
 | 
						|
    for binary in ('gswin32c', 'gswin64c', 'gs'):
 | 
						|
        if 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'):
 | 
						|
        import subprocess
 | 
						|
        try:
 | 
						|
            gs = subprocess.Popen(['gs', '--version'], stdout=subprocess.PIPE)
 | 
						|
            gs.stdout.read()
 | 
						|
            return True
 | 
						|
        except OSError:
 | 
						|
            # no ghostscript
 | 
						|
            pass
 | 
						|
    return False
 | 
						|
 | 
						|
 | 
						|
def Ghostscript(tile, size, fp, scale=1):
 | 
						|
    """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 = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
 | 
						|
           float((72.0 * size[1]) / (bbox[3]-bbox[1])))
 | 
						|
    # print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
 | 
						|
 | 
						|
    import os
 | 
						|
    import subprocess
 | 
						|
    import tempfile
 | 
						|
 | 
						|
    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, 2)
 | 
						|
            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)
 | 
						|
 | 
						|
    # 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)
 | 
						|
               "-dNOPAUSE",                  # don't pause between pages,
 | 
						|
               "-dSAFER",                    # safe mode
 | 
						|
               "-sDEVICE=ppmraw",            # ppm driver
 | 
						|
               "-sOutputFile=%s" % outfile,  # output file
 | 
						|
               "-c", "%d %d translate" % (-bbox[0], -bbox[1]),
 | 
						|
                                             # adjust for image origin
 | 
						|
               "-f", infile,                 # input file
 | 
						|
               ]
 | 
						|
 | 
						|
    if gs_windows_binary is not None:
 | 
						|
        if not gs_windows_binary:
 | 
						|
            raise WindowsError('Unable to locate Ghostscript on paths')
 | 
						|
        command[0] = gs_windows_binary
 | 
						|
 | 
						|
    # push data through ghostscript
 | 
						|
    try:
 | 
						|
        gs = subprocess.Popen(command, stdin=subprocess.PIPE,
 | 
						|
                              stdout=subprocess.PIPE)
 | 
						|
        gs.stdin.close()
 | 
						|
        status = gs.wait()
 | 
						|
        if status:
 | 
						|
            raise IOError("gs failed (status %d)" % status)
 | 
						|
        im = Image.open(outfile)
 | 
						|
        im.load()
 | 
						|
    finally:
 | 
						|
        try:
 | 
						|
            os.unlink(outfile)
 | 
						|
            if infile_temp:
 | 
						|
                os.unlink(infile_temp)
 | 
						|
        except OSError:
 | 
						|
            pass
 | 
						|
 | 
						|
    return im.im.copy()
 | 
						|
 | 
						|
 | 
						|
class PSFile(object):
 | 
						|
    """
 | 
						|
    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=0):
 | 
						|
        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":
 | 
						|
            s = s + 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 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.
 | 
						|
        try:
 | 
						|
            if bytes is str:
 | 
						|
                # Python2, no encoding conversion necessary
 | 
						|
                fp = open(self.fp.name, "Ur")
 | 
						|
            else:
 | 
						|
                # Python3, can use bare open command.
 | 
						|
                fp = open(self.fp.name, "Ur", encoding='latin-1')
 | 
						|
        except:
 | 
						|
            # Expect this for bytesio/stringio
 | 
						|
            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 = fp.readline().strip('\r\n')
 | 
						|
 | 
						|
        while s:
 | 
						|
            if len(s) > 255:
 | 
						|
                raise SyntaxError("not an EPS file")
 | 
						|
 | 
						|
            try:
 | 
						|
                m = split.match(s)
 | 
						|
            except re.error as v:
 | 
						|
                raise SyntaxError("not an EPS file")
 | 
						|
 | 
						|
            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:
 | 
						|
                        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 IOError("bad EPS header")
 | 
						|
 | 
						|
            s = fp.readline().strip('\r\n')
 | 
						|
 | 
						|
            if 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) != 8:
 | 
						|
                    break
 | 
						|
                try:
 | 
						|
                    self.mode = self.mode_map[int(mo)]
 | 
						|
                except ValueError:
 | 
						|
                    break
 | 
						|
 | 
						|
                self.size = int(x), int(y)
 | 
						|
                return
 | 
						|
 | 
						|
            s = fp.readline().strip('\r\n')
 | 
						|
            if not s:
 | 
						|
                break
 | 
						|
 | 
						|
        if not box:
 | 
						|
            raise IOError("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, 2)
 | 
						|
            length = fp.tell()
 | 
						|
            offset = 0
 | 
						|
        elif i32(s[0:4]) == 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:
 | 
						|
            # http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
 | 
						|
            offset = i32(s[4:8])
 | 
						|
            length = i32(s[8:12])
 | 
						|
        else:
 | 
						|
            raise SyntaxError("not an EPS file")
 | 
						|
 | 
						|
        return (length, offset)
 | 
						|
 | 
						|
    def load(self, scale=1):
 | 
						|
        # Load EPS via Ghostscript
 | 
						|
        if not self.tile:
 | 
						|
            return
 | 
						|
        self.im = Ghostscript(self.tile, self.size, self.fp, scale)
 | 
						|
        self.mode = self.im.mode
 | 
						|
        self.size = self.im.size
 | 
						|
        self.tile = []
 | 
						|
 | 
						|
    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, "image")
 | 
						|
    elif im.mode == "RGB":
 | 
						|
        operator = (8, 3, "false 3 colorimage")
 | 
						|
    elif im.mode == "CMYK":
 | 
						|
        operator = (8, 4, "false 4 colorimage")
 | 
						|
    else:
 | 
						|
        raise ValueError("image mode is not supported")
 | 
						|
 | 
						|
    class NoCloseStream(object):
 | 
						|
        def __init__(self, fp):
 | 
						|
            self.fp = fp
 | 
						|
 | 
						|
        def __getattr__(self, name):
 | 
						|
            return getattr(self.fp, name)
 | 
						|
 | 
						|
        def close(self):
 | 
						|
            pass
 | 
						|
 | 
						|
    base_fp = fp
 | 
						|
    if fp != sys.stdout:
 | 
						|
        fp = NoCloseStream(fp)
 | 
						|
        if sys.version_info[0] > 2:
 | 
						|
            fp = io.TextIOWrapper(fp, encoding='latin-1')
 | 
						|
 | 
						|
    if eps:
 | 
						|
        #
 | 
						|
        # write EPS header
 | 
						|
        fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
 | 
						|
        fp.write("%%Creator: PIL 0.1 EpsEncode\n")
 | 
						|
        # fp.write("%%CreationDate: %s"...)
 | 
						|
        fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
 | 
						|
        fp.write("%%Pages: 1\n")
 | 
						|
        fp.write("%%EndComments\n")
 | 
						|
        fp.write("%%Page: 1 1\n")
 | 
						|
        fp.write("%%ImageData: %d %d " % im.size)
 | 
						|
        fp.write("%d %d 0 1 1 \"%s\"\n" % operator)
 | 
						|
 | 
						|
    #
 | 
						|
    # image header
 | 
						|
    fp.write("gsave\n")
 | 
						|
    fp.write("10 dict begin\n")
 | 
						|
    fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
 | 
						|
    fp.write("%d %d scale\n" % im.size)
 | 
						|
    fp.write("%d %d 8\n" % im.size)  # <= bits
 | 
						|
    fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
 | 
						|
    fp.write("{ currentfile buf readhexstring pop } bind\n")
 | 
						|
    fp.write(operator[2] + "\n")
 | 
						|
    if hasattr(fp, "flush"):
 | 
						|
        fp.flush()
 | 
						|
 | 
						|
    ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
 | 
						|
 | 
						|
    fp.write("\n%%%%EndBinary\n")
 | 
						|
    fp.write("grestore end\n")
 | 
						|
    if hasattr(fp, "flush"):
 | 
						|
        fp.flush()
 | 
						|
 | 
						|
#
 | 
						|
# --------------------------------------------------------------------
 | 
						|
 | 
						|
Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
 | 
						|
 | 
						|
Image.register_save(EpsImageFile.format, _save)
 | 
						|
 | 
						|
Image.register_extension(EpsImageFile.format, ".ps")
 | 
						|
Image.register_extension(EpsImageFile.format, ".eps")
 | 
						|
 | 
						|
Image.register_mime(EpsImageFile.format, "application/postscript")
 |