Replaced PSFile wrapper with standart Python open for universal line endings. DPI for non pixelbased eps files is set to 600. DPI for pixelbased eps files is calculated from file and scale is ignored.

This commit is contained in:
Karsten Wolf 2014-08-04 14:17:05 +02:00
parent 7113721ad0
commit 85ecfe9daf

View File

@ -23,6 +23,8 @@ __version__ = "0.5"
import re import re
import io import io
import unicodedata
from PIL import Image, ImageFile, _binary from PIL import Image, ImageFile, _binary
# #
@ -34,6 +36,8 @@ o32 = _binary.o32le
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
defaultDPI = 600.0
gs_windows_binary = None gs_windows_binary = None
import sys import sys
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
@ -51,6 +55,7 @@ if sys.platform.startswith('win'):
else: else:
gs_windows_binary = False gs_windows_binary = False
# UNUSED
def has_ghostscript(): def has_ghostscript():
if gs_windows_binary: if gs_windows_binary:
return True return True
@ -65,59 +70,44 @@ def has_ghostscript():
pass pass
return False return False
def makeunicode( s, enc="latin-1", normalizer='NFD'):
try:
if type(s) != unicode:
s = unicode(s, enc)
except:
pass
return unicodedata.normalize(normalizer, s)
def Ghostscript(tile, size, fp, scale=1): def Ghostscript(tile, size, fp):
"""Render an image using Ghostscript""" """Render an image using Ghostscript"""
# size is either pts or pixels
# Unpack decoder tile # Unpack decoder tile
decoder, tile, offset, data = tile[0] decoder, tile, offset, data = tile[0]
length, bbox = data length, bbox = data
#Hack to support hi-res rendering xpointsize = bbox[2] - bbox[0]
scale = int(scale) or 1 ypointsize = bbox[3] - bbox[1]
orig_size = size xdpi = size[0] / (xpointsize / 72.0)
orig_bbox = bbox ydpi = size[1] / (ypointsize / 72.0)
size = (size[0] * scale, size[1] * scale)
# resolution is dependend 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 tempfile, os, subprocess import tempfile, os, subprocess
out_fd, outfile = tempfile.mkstemp() out_fd, outfile = tempfile.mkstemp()
os.close(out_fd) os.close(out_fd)
in_fd, infile = tempfile.mkstemp()
os.close(in_fd)
# ignore length and offset!
# ghostscript can read it
# copy whole file to read in ghostscript
with open(infile, '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
length -= len(s)
f.write(s)
# Build ghostscript command # Build ghostscript command
command = ["gs", command = ["gs",
"-q", # quiet mode "-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch) "-r%fc%f" % (xdpi,ydpi), # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
"-sDEVICE=ppmraw", # ppm driver "-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file "-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]), "-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin # adjust for image origin
"-f", infile, # input file "-f", fp.name
] ]
if gs_windows_binary is not None: if gs_windows_binary is not None:
@ -136,50 +126,11 @@ def Ghostscript(tile, size, fp, scale=1):
finally: finally:
try: try:
os.unlink(outfile) os.unlink(outfile)
os.unlink(infile) except:
except: pass pass
return im 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 readbinary(self, count):
return self.fp.read(count)
def tell(self):
pos = self.fp.tell()
if self.char:
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): def _accept(prefix):
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5 return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
@ -194,55 +145,42 @@ class EpsImageFile(ImageFile.ImageFile):
format_description = "Encapsulated Postscript" format_description = "Encapsulated Postscript"
def _open(self): def _open(self):
fp = open(self.fp.name, "Ur")
fp = PSFile(self.fp) # HEAD
s = fp.read(512)
# FIX for: Some EPS file not handled correctly / issue #302
# EPS can contain binary data
# or start directly with latin coding
# read header in both ways to handle both
# file types
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
# for HEAD without binary preview
s = fp.read(4)
# for HEAD with binary preview
fp.seek(0)
sb = fp.readbinary(160)
if s[:4] == "%!PS": if s[:4] == "%!PS":
offset = 0
fp.seek(0, 2) fp.seek(0, 2)
length = fp.tell() length = fp.tell()
offset = 0 elif i32(s) == 0xC6D3D0C5:
elif i32(sb[0:4]) == 0xC6D3D0C5: offset = i32(s[4:])
offset = i32(sb[4:8]) length = i32(s[8:])
length = i32(sb[8:12]) fp.seek(offset)
else: else:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
# go to offset - start of "%!PS"
fp.seek(offset) fp.seek(offset)
box = None box = None
self.mode = "RGB" self.mode = "RGB"
self.size = 1, 1 # FIXME: huh? self.size = 1, 1 # FIXME: huh?
self.pixelsize = False
self.pointsize = False
self.bbox = False
# #
# Load EPS header # Load EPS header
s = fp.readline() s = fp.readline()
s = s.rstrip("\r\n")
s = makeunicode(s)
while s: while s:
if len(s) > 255: if len(s) > 255:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
try: try:
m = split.match(s) m = split.match(s)
except re.error as v: except re.error as v:
@ -256,10 +194,31 @@ class EpsImageFile(ImageFile.ImageFile):
# Note: The DSC spec says that BoundingBox # Note: The DSC spec says that BoundingBox
# fields should be integers, but some drivers # fields should be integers, but some drivers
# put floating point values there anyway. # put floating point values there anyway.
box = [int(float(s)) for s in v.split()]
# if self.pointsize is not False, we already have a hiresbbox
if not self.pointsize:
box = [int(float(s)) for s in v.split()]
self.bbox = box
pointsize = box[2] - box[0], box[3] - box[1]
self.size = box[2] - box[0], box[3] - box[1]
self.tile = [ ("eps",
(0,0) + self.size,
offset,
(length, box))]
self.pointsize = pointsize
except:
pass
if k == "HiResBoundingBox":
try:
box = [float(s) for s in v.split()]
self.bbox = box
pointsize = box[2] - box[0], box[3] - box[1]
self.size = box[2] - box[0], box[3] - box[1] self.size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0,0) + self.size, offset, self.tile = [ ("eps",
(0,0) + self.size,
offset,
(length, box))] (length, box))]
self.pointsize = pointsize
except: except:
pass pass
@ -284,14 +243,14 @@ class EpsImageFile(ImageFile.ImageFile):
raise IOError("bad EPS header") raise IOError("bad EPS header")
s = fp.readline() s = fp.readline()
s = s.rstrip("\r\n")
s = makeunicode(s)
if s[:1] != "%": if s[:1] != "%":
break break
# #
# Scan for an "ImageData" descriptor # Scan for an "ImageData" descriptor
while s[0] == "%": while s[0] == "%":
if len(s) > 255: if len(s) > 255:
@ -303,11 +262,11 @@ class EpsImageFile(ImageFile.ImageFile):
s = s[:-1] s = s[:-1]
if s[:11] == "%ImageData:": if s[:11] == "%ImageData:":
[x, y, bi, mo, z3, z4, en, starttag] = s[11:].split(None, 7)
[x, y, bi, mo, z3, z4, en, id] =\ x = int(x)
s[11:].split(None, 7) y = int(y)
self.pixelsize = (x,y)
x = int(x); y = int(y)
bi = int(bi) bi = int(bi)
mo = int(mo) mo = int(mo)
@ -319,46 +278,66 @@ class EpsImageFile(ImageFile.ImageFile):
elif en == 2: elif en == 2:
decoder = "eps_hex" decoder = "eps_hex"
else: else:
break pass
if bi != 8: if bi != 8:
break break
if mo == 1: if mo == 1:
self.mode = "L" self.mode = "L"
elif mo == 2: elif mo == 2:
self.mode = "LAB" self.mode = "LAB"
elif mo == 3: elif mo == 3:
self.mode = "RGB" self.mode = "RGB"
elif mo == 4:
self.mode = "CMYK"
else: else:
break pass
if id[:1] == id[-1:] == '"': bbox = (0, 0, x, y)
id = id[1:-1] if self.bbox:
bbox = self.bbox
# Scan forward to the actual image data self.tile = [("eps",
while True: (0, 0, x, y),
s = fp.readline() 0,
if not s: (length, bbox))]
break xdpi = round(x / (self.size[0] / 72.0))
if s[:len(id)] == id: ydpi = round(y / (self.size[1] / 72.0))
self.size = x, y self.info["dpi"] = (xdpi,ydpi)
self.tile2 = [(decoder, return
(0, 0, x, y),
fp.tell(),
0)]
return
s = fp.readline() s = fp.readline()
s = s.rstrip("\r\n")
s = makeunicode(s)
if not s: if not s:
break break
if not box: if not box:
raise IOError("cannot determine EPS bounding box") raise IOError("cannot determine EPS bounding box")
def load(self, scale=1): if not self.pixelsize:
self.info["dpi"] = (defaultDPI, defaultDPI)
def load(self, scale=None):
# Load EPS via Ghostscript # Load EPS via Ghostscript
if not self.tile: if not self.tile:
return return
self.im = Ghostscript(self.tile, self.size, self.fp, scale)
size = self.size
if self.pixelsize:
# pixel based eps
# size is imagesize in pixels
size = self.pixelsize
else:
# generic eps
# size is in points (== 72dpi uglyness)
if not scale:
scale = (defaultDPI/72.0)
size = ( size[0] * scale, size[1] * scale)
self.im = Ghostscript(self.tile, size, self.fp)
self.mode = self.im.mode self.mode = self.im.mode
self.size = self.im.size self.size = self.im.size
self.tile = [] self.tile = []