mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			390 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			390 lines
		
	
	
		
			11 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)
 | |
| #
 | |
| # 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.
 | |
| #
 | |
| 
 | |
| __version__ = "0.5"
 | |
| 
 | |
| import re
 | |
| import io
 | |
| from PIL import Image, ImageFile, _binary
 | |
| 
 | |
| #
 | |
| # --------------------------------------------------------------------
 | |
| 
 | |
| i32 = _binary.i32le
 | |
| o32 = _binary.o32le
 | |
| 
 | |
| split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
 | |
| field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
 | |
| 
 | |
| gs_windows_binary = None
 | |
| import sys
 | |
| 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 Ghostscript(tile, size, fp):
 | |
|     """Render an image using Ghostscript"""
 | |
| 
 | |
|     # Unpack decoder tile
 | |
|     decoder, tile, offset, data = tile[0]
 | |
|     length, bbox = data
 | |
| 
 | |
|     import tempfile, os
 | |
| 
 | |
|     file = tempfile.mktemp()
 | |
| 
 | |
|     # Build ghostscript command
 | |
|     command = ["gs",
 | |
|                "-q",                    # quite mode
 | |
|                "-g%dx%d" % size,        # set output geometry (pixels)
 | |
|                "-dNOPAUSE -dSAFER",     # don't pause between pages, safe mode
 | |
|                "-sDEVICE=ppmraw",       # ppm driver
 | |
|                "-sOutputFile=%s" % file,# output file
 | |
|                "- >/dev/null 2>/dev/null"]
 | |
| 
 | |
|     if gs_windows_binary is not None:
 | |
|         if gs_windows_binary is False:
 | |
|             raise WindowsError('Unable to locate Ghostscript on paths')
 | |
|         command[0] = gs_windows_binary
 | |
|         command[-1] = '- >nul 2>nul'
 | |
| 
 | |
|     command = " ".join(command)
 | |
| 
 | |
|     # push data through ghostscript
 | |
|     try:
 | |
|         gs = os.popen(command, "w")
 | |
|         # adjust for image origin
 | |
|         if bbox[0] != 0 or bbox[1] != 0:
 | |
|             gs.write("%d %d translate\n" % (-bbox[0], -bbox[1]))
 | |
|         fp.seek(offset)
 | |
|         while length > 0:
 | |
|             s = fp.read(8192)
 | |
|             if not s:
 | |
|                 break
 | |
|             length = length - len(s)
 | |
|             gs.write(s)
 | |
|         status = gs.close()
 | |
|         if status:
 | |
|             raise IOError("gs failed (status %d)" % status)
 | |
|         im = Image.core.open_ppm(file)
 | |
|     finally:
 | |
|         try: os.unlink(file)
 | |
|         except: pass
 | |
| 
 | |
|     return im
 | |
| 
 | |
| 
 | |
| class PSFile:
 | |
|     """Wrapper that treats either CR or LF as end of line."""
 | |
|     def __init__(self, fp):
 | |
|         self.fp = fp
 | |
|         self.char = None
 | |
|     def __getattr__(self, id):
 | |
|         v = getattr(self.fp, id)
 | |
|         setattr(self, id, v)
 | |
|         return v
 | |
|     def seek(self, offset, whence=0):
 | |
|         self.char = None
 | |
|         self.fp.seek(offset, whence)
 | |
|     def read(self, count):
 | |
|         return self.fp.read(count).decode('latin-1')
 | |
|     def tell(self):
 | |
|         pos = self.fp.tell()
 | |
|         if self.char:
 | |
|             pos = pos - 1
 | |
|         return pos
 | |
|     def readline(self):
 | |
|         s = b""
 | |
|         if self.char:
 | |
|             c = self.char
 | |
|             self.char = None
 | |
|         else:
 | |
|             c = self.fp.read(1)
 | |
|         while c not in b"\r\n":
 | |
|             s = s + c
 | |
|             c = self.fp.read(1)
 | |
|         if c == b"\r":
 | |
|             self.char = self.fp.read(1)
 | |
|             if self.char == b"\n":
 | |
|                 self.char = None
 | |
|         return s.decode('latin-1') + "\n"
 | |
| 
 | |
| 
 | |
| def _accept(prefix):
 | |
|     return prefix[:4] == b"%!PS" or 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"
 | |
| 
 | |
|     def _open(self):
 | |
| 
 | |
|         # FIXME: should check the first 512 bytes to see if this
 | |
|         # really is necessary (platform-dependent, though...)
 | |
| 
 | |
|         fp = PSFile(self.fp)
 | |
| 
 | |
|         # HEAD
 | |
|         s = fp.read(512)
 | |
|         if s[:4] == "%!PS":
 | |
|             offset = 0
 | |
|             fp.seek(0, 2)
 | |
|             length = fp.tell()
 | |
|         elif i32(s) == 0xC6D3D0C5:
 | |
|             offset = i32(s[4:])
 | |
|             length = i32(s[8:])
 | |
|             fp.seek(offset)
 | |
|         else:
 | |
|             raise SyntaxError("not an EPS file")
 | |
| 
 | |
|         fp.seek(offset)
 | |
| 
 | |
|         box = None
 | |
| 
 | |
|         self.mode = "RGB"
 | |
|         self.size = 1, 1 # FIXME: huh?
 | |
| 
 | |
|         #
 | |
|         # Load EPS header
 | |
| 
 | |
|         s = fp.readline()
 | |
| 
 | |
|         while s:
 | |
| 
 | |
|             if len(s) > 255:
 | |
|                 raise SyntaxError("not an EPS file")
 | |
| 
 | |
|             if s[-2:] == '\r\n':
 | |
|                 s = s[:-2]
 | |
|             elif s[-1:] == '\n':
 | |
|                 s = s[:-1]
 | |
| 
 | |
|             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(s)) for s 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:1] == '%':
 | |
|                     # handle non-DSC Postscript comments that some
 | |
|                     # tools mistakenly put in the Comments section
 | |
|                     pass
 | |
|                 else:
 | |
|                     raise IOError("bad EPS header")
 | |
| 
 | |
|             s = fp.readline()
 | |
| 
 | |
|             if s[:1] != "%":
 | |
|                 break
 | |
| 
 | |
| 
 | |
|         #
 | |
|         # Scan for an "ImageData" descriptor
 | |
| 
 | |
|         while s[0] == "%":
 | |
| 
 | |
|             if len(s) > 255:
 | |
|                 raise SyntaxError("not an EPS file")
 | |
| 
 | |
|             if s[-2:] == '\r\n':
 | |
|                 s = s[:-2]
 | |
|             elif s[-1:] == '\n':
 | |
|                 s = s[:-1]
 | |
| 
 | |
|             if s[:11] == "%ImageData:":
 | |
| 
 | |
|                 [x, y, bi, mo, z3, z4, en, id] =\
 | |
|                     s[11:].split(None, 7)
 | |
| 
 | |
|                 x = int(x); y = int(y)
 | |
| 
 | |
|                 bi = int(bi)
 | |
|                 mo = int(mo)
 | |
| 
 | |
|                 en = int(en)
 | |
| 
 | |
|                 if en == 1:
 | |
|                     decoder = "eps_binary"
 | |
|                 elif en == 2:
 | |
|                     decoder = "eps_hex"
 | |
|                 else:
 | |
|                     break
 | |
|                 if bi != 8:
 | |
|                     break
 | |
|                 if mo == 1:
 | |
|                     self.mode = "L"
 | |
|                 elif mo == 2:
 | |
|                     self.mode = "LAB"
 | |
|                 elif mo == 3:
 | |
|                     self.mode = "RGB"
 | |
|                 else:
 | |
|                     break
 | |
| 
 | |
|                 if id[:1] == id[-1:] == '"':
 | |
|                     id = id[1:-1]
 | |
| 
 | |
|                 # Scan forward to the actual image data
 | |
|                 while True:
 | |
|                     s = fp.readline()
 | |
|                     if not s:
 | |
|                         break
 | |
|                     if s[:len(id)] == id:
 | |
|                         self.size = x, y
 | |
|                         self.tile2 = [(decoder,
 | |
|                                        (0, 0, x, y),
 | |
|                                        fp.tell(),
 | |
|                                        0)]
 | |
|                         return
 | |
| 
 | |
|             s = fp.readline()
 | |
|             if not s:
 | |
|                 break
 | |
| 
 | |
|         if not box:
 | |
|             raise IOError("cannot determine EPS bounding box")
 | |
| 
 | |
|     def load(self):
 | |
|         # Load EPS via Ghostscript
 | |
|         if not self.tile:
 | |
|             return
 | |
|         self.im = Ghostscript(self.tile, self.size, self.fp)
 | |
|         self.mode = self.im.mode
 | |
|         self.size = self.im.size
 | |
|         self.tile = []
 | |
| 
 | |
| #
 | |
| # --------------------------------------------------------------------
 | |
| 
 | |
| 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:
 | |
|         def __init__(self, fp):
 | |
|             self.fp = fp
 | |
|         def __getattr__(self, name):
 | |
|             return getattr(self.fp, name)
 | |
|         def close(self):
 | |
|             pass
 | |
| 
 | |
|     base_fp = fp
 | |
|     fp = io.TextIOWrapper(NoCloseStream(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")
 | |
|     fp.flush()
 | |
| 
 | |
|     ImageFile._save(im, base_fp, [("eps", (0,0)+im.size, 0, None)])
 | |
| 
 | |
|     fp.write("\n%%%%EndBinary\n")
 | |
|     fp.write("grestore end\n")
 | |
|     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")
 |