| # | 
| # 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, string | 
| import Image, ImageFile | 
|   | 
| # | 
| # -------------------------------------------------------------------- | 
|   | 
| def i32(c): | 
|     return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24) | 
|   | 
| def o32(i): | 
|     return chr(i&255) + chr(i>>8&255) + chr(i>>16&255) + chr(i>>24&255) | 
|   | 
| split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") | 
| field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") | 
|   | 
| def Ghostscript(tile, size, fp): | 
|     """Render an image using Ghostscript (Unix only)""" | 
|   | 
|     # 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"] | 
|   | 
|     command = string.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 tell(self): | 
|         pos = self.fp.tell() | 
|         if self.char: | 
|             pos = pos - 1 | 
|         return pos | 
|     def readline(self): | 
|         s = "" | 
|         if self.char: | 
|             c = self.char | 
|             self.char = None | 
|         else: | 
|             c = self.fp.read(1) | 
|         while c not in "\r\n": | 
|             s = s + c | 
|             c = self.fp.read(1) | 
|         if c == "\r": | 
|             self.char = self.fp.read(1) | 
|             if self.char == "\n": | 
|                 self.char = None | 
|         return s + "\n" | 
|   | 
|   | 
| def _accept(prefix): | 
|     return prefix[:4] == "%!PS" or i32(prefix) == 0xC6D3D0C5L | 
|   | 
| ## | 
| # 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) == 0xC6D3D0C5L: | 
|             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, 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 = map(int, map(float, string.split(v))) | 
|                         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] = "" | 
|                 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] =\ | 
|                     string.split(s[11:], maxsplit=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 1: | 
|                     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" | 
|   | 
|     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("%s\n" % operator[2]) | 
|   | 
|     ImageFile._save(im, 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") |