You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 lines
12 KiB

4 years ago
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # EPS file handling
  6. #
  7. # History:
  8. # 1995-09-01 fl Created (0.1)
  9. # 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
  10. # 1996-08-22 fl Don't choke on floating point BoundingBox values
  11. # 1996-08-23 fl Handle files from Macintosh (0.3)
  12. # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
  13. # 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
  14. # 2014-05-07 e Handling of EPS with binary preview and fixed resolution
  15. # resizing
  16. #
  17. # Copyright (c) 1997-2003 by Secret Labs AB.
  18. # Copyright (c) 1995-2003 by Fredrik Lundh
  19. #
  20. # See the README file for information on usage and redistribution.
  21. #
  22. import re
  23. import io
  24. import os
  25. import sys
  26. from . import Image, ImageFile
  27. from ._binary import i32le as i32
  28. __version__ = "0.5"
  29. #
  30. # --------------------------------------------------------------------
  31. split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
  32. field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
  33. gs_windows_binary = None
  34. if sys.platform.startswith('win'):
  35. import shutil
  36. if hasattr(shutil, 'which'):
  37. which = shutil.which
  38. else:
  39. # Python 2
  40. import distutils.spawn
  41. which = distutils.spawn.find_executable
  42. for binary in ('gswin32c', 'gswin64c', 'gs'):
  43. if which(binary) is not None:
  44. gs_windows_binary = binary
  45. break
  46. else:
  47. gs_windows_binary = False
  48. def has_ghostscript():
  49. if gs_windows_binary:
  50. return True
  51. if not sys.platform.startswith('win'):
  52. import subprocess
  53. try:
  54. with open(os.devnull, 'wb') as devnull:
  55. subprocess.check_call(['gs', '--version'], stdout=devnull)
  56. return True
  57. except OSError:
  58. # No Ghostscript
  59. pass
  60. return False
  61. def Ghostscript(tile, size, fp, scale=1):
  62. """Render an image using Ghostscript"""
  63. # Unpack decoder tile
  64. decoder, tile, offset, data = tile[0]
  65. length, bbox = data
  66. # Hack to support hi-res rendering
  67. scale = int(scale) or 1
  68. # orig_size = size
  69. # orig_bbox = bbox
  70. size = (size[0] * scale, size[1] * scale)
  71. # resolution is dependent on bbox and size
  72. res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])),
  73. float((72.0 * size[1]) / (bbox[3]-bbox[1])))
  74. import subprocess
  75. import tempfile
  76. out_fd, outfile = tempfile.mkstemp()
  77. os.close(out_fd)
  78. infile_temp = None
  79. if hasattr(fp, 'name') and os.path.exists(fp.name):
  80. infile = fp.name
  81. else:
  82. in_fd, infile_temp = tempfile.mkstemp()
  83. os.close(in_fd)
  84. infile = infile_temp
  85. # Ignore length and offset!
  86. # Ghostscript can read it
  87. # Copy whole file to read in Ghostscript
  88. with open(infile_temp, 'wb') as f:
  89. # fetch length of fp
  90. fp.seek(0, 2)
  91. fsize = fp.tell()
  92. # ensure start position
  93. # go back
  94. fp.seek(0)
  95. lengthfile = fsize
  96. while lengthfile > 0:
  97. s = fp.read(min(lengthfile, 100*1024))
  98. if not s:
  99. break
  100. lengthfile -= len(s)
  101. f.write(s)
  102. # Build Ghostscript command
  103. command = ["gs",
  104. "-q", # quiet mode
  105. "-g%dx%d" % size, # set output geometry (pixels)
  106. "-r%fx%f" % res, # set input DPI (dots per inch)
  107. "-dBATCH", # exit after processing
  108. "-dNOPAUSE", # don't pause between pages
  109. "-dSAFER", # safe mode
  110. "-sDEVICE=ppmraw", # ppm driver
  111. "-sOutputFile=%s" % outfile, # output file
  112. "-c", "%d %d translate" % (-bbox[0], -bbox[1]),
  113. # adjust for image origin
  114. "-f", infile, # input file
  115. "-c", "showpage", # showpage (see: https://bugs.ghostscript.com/show_bug.cgi?id=698272)
  116. ]
  117. if gs_windows_binary is not None:
  118. if not gs_windows_binary:
  119. raise WindowsError('Unable to locate Ghostscript on paths')
  120. command[0] = gs_windows_binary
  121. # push data through Ghostscript
  122. try:
  123. with open(os.devnull, 'w+b') as devnull:
  124. startupinfo = None
  125. if sys.platform.startswith('win'):
  126. startupinfo = subprocess.STARTUPINFO()
  127. startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  128. subprocess.check_call(command, stdin=devnull, stdout=devnull,
  129. startupinfo=startupinfo)
  130. im = Image.open(outfile)
  131. im.load()
  132. finally:
  133. try:
  134. os.unlink(outfile)
  135. if infile_temp:
  136. os.unlink(infile_temp)
  137. except OSError:
  138. pass
  139. return im.im.copy()
  140. class PSFile(object):
  141. """
  142. Wrapper for bytesio object that treats either CR or LF as end of line.
  143. """
  144. def __init__(self, fp):
  145. self.fp = fp
  146. self.char = None
  147. def seek(self, offset, whence=0):
  148. self.char = None
  149. self.fp.seek(offset, whence)
  150. def readline(self):
  151. s = self.char or b""
  152. self.char = None
  153. c = self.fp.read(1)
  154. while c not in b"\r\n":
  155. s = s + c
  156. c = self.fp.read(1)
  157. self.char = self.fp.read(1)
  158. # line endings can be 1 or 2 of \r \n, in either order
  159. if self.char in b"\r\n":
  160. self.char = None
  161. return s.decode('latin-1')
  162. def _accept(prefix):
  163. return prefix[:4] == b"%!PS" or \
  164. (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
  165. ##
  166. # Image plugin for Encapsulated Postscript. This plugin supports only
  167. # a few variants of this format.
  168. class EpsImageFile(ImageFile.ImageFile):
  169. """EPS File Parser for the Python Imaging Library"""
  170. format = "EPS"
  171. format_description = "Encapsulated Postscript"
  172. mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
  173. def _open(self):
  174. (length, offset) = self._find_offset(self.fp)
  175. # Rewrap the open file pointer in something that will
  176. # convert line endings and decode to latin-1.
  177. fp = PSFile(self.fp)
  178. # go to offset - start of "%!PS"
  179. fp.seek(offset)
  180. box = None
  181. self.mode = "RGB"
  182. self._size = 1, 1 # FIXME: huh?
  183. #
  184. # Load EPS header
  185. s_raw = fp.readline()
  186. s = s_raw.strip('\r\n')
  187. while s_raw:
  188. if s:
  189. if len(s) > 255:
  190. raise SyntaxError("not an EPS file")
  191. try:
  192. m = split.match(s)
  193. except re.error:
  194. raise SyntaxError("not an EPS file")
  195. if m:
  196. k, v = m.group(1, 2)
  197. self.info[k] = v
  198. if k == "BoundingBox":
  199. try:
  200. # Note: The DSC spec says that BoundingBox
  201. # fields should be integers, but some drivers
  202. # put floating point values there anyway.
  203. box = [int(float(i)) for i in v.split()]
  204. self._size = box[2] - box[0], box[3] - box[1]
  205. self.tile = [("eps", (0, 0) + self.size, offset,
  206. (length, box))]
  207. except Exception:
  208. pass
  209. else:
  210. m = field.match(s)
  211. if m:
  212. k = m.group(1)
  213. if k == "EndComments":
  214. break
  215. if k[:8] == "PS-Adobe":
  216. self.info[k[:8]] = k[9:]
  217. else:
  218. self.info[k] = ""
  219. elif s[0] == '%':
  220. # handle non-DSC Postscript comments that some
  221. # tools mistakenly put in the Comments section
  222. pass
  223. else:
  224. raise IOError("bad EPS header")
  225. s_raw = fp.readline()
  226. s = s_raw.strip('\r\n')
  227. if s and s[:1] != "%":
  228. break
  229. #
  230. # Scan for an "ImageData" descriptor
  231. while s[:1] == "%":
  232. if len(s) > 255:
  233. raise SyntaxError("not an EPS file")
  234. if s[:11] == "%ImageData:":
  235. # Encoded bitmapped image.
  236. x, y, bi, mo = s[11:].split(None, 7)[:4]
  237. if int(bi) != 8:
  238. break
  239. try:
  240. self.mode = self.mode_map[int(mo)]
  241. except ValueError:
  242. break
  243. self._size = int(x), int(y)
  244. return
  245. s = fp.readline().strip('\r\n')
  246. if not s:
  247. break
  248. if not box:
  249. raise IOError("cannot determine EPS bounding box")
  250. def _find_offset(self, fp):
  251. s = fp.read(160)
  252. if s[:4] == b"%!PS":
  253. # for HEAD without binary preview
  254. fp.seek(0, 2)
  255. length = fp.tell()
  256. offset = 0
  257. elif i32(s[0:4]) == 0xC6D3D0C5:
  258. # FIX for: Some EPS file not handled correctly / issue #302
  259. # EPS can contain binary data
  260. # or start directly with latin coding
  261. # more info see:
  262. # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
  263. offset = i32(s[4:8])
  264. length = i32(s[8:12])
  265. else:
  266. raise SyntaxError("not an EPS file")
  267. return (length, offset)
  268. def load(self, scale=1):
  269. # Load EPS via Ghostscript
  270. if not self.tile:
  271. return
  272. self.im = Ghostscript(self.tile, self.size, self.fp, scale)
  273. self.mode = self.im.mode
  274. self._size = self.im.size
  275. self.tile = []
  276. def load_seek(self, *args, **kwargs):
  277. # we can't incrementally load, so force ImageFile.parser to
  278. # use our custom load method by defining this method.
  279. pass
  280. #
  281. # --------------------------------------------------------------------
  282. def _save(im, fp, filename, eps=1):
  283. """EPS Writer for the Python Imaging Library."""
  284. #
  285. # make sure image data is available
  286. im.load()
  287. #
  288. # determine postscript image mode
  289. if im.mode == "L":
  290. operator = (8, 1, "image")
  291. elif im.mode == "RGB":
  292. operator = (8, 3, "false 3 colorimage")
  293. elif im.mode == "CMYK":
  294. operator = (8, 4, "false 4 colorimage")
  295. else:
  296. raise ValueError("image mode is not supported")
  297. base_fp = fp
  298. wrapped_fp = False
  299. if fp != sys.stdout:
  300. if sys.version_info.major > 2:
  301. fp = io.TextIOWrapper(fp, encoding='latin-1')
  302. wrapped_fp = True
  303. try:
  304. if eps:
  305. #
  306. # write EPS header
  307. fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
  308. fp.write("%%Creator: PIL 0.1 EpsEncode\n")
  309. # fp.write("%%CreationDate: %s"...)
  310. fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
  311. fp.write("%%Pages: 1\n")
  312. fp.write("%%EndComments\n")
  313. fp.write("%%Page: 1 1\n")
  314. fp.write("%%ImageData: %d %d " % im.size)
  315. fp.write("%d %d 0 1 1 \"%s\"\n" % operator)
  316. #
  317. # image header
  318. fp.write("gsave\n")
  319. fp.write("10 dict begin\n")
  320. fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
  321. fp.write("%d %d scale\n" % im.size)
  322. fp.write("%d %d 8\n" % im.size) # <= bits
  323. fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
  324. fp.write("{ currentfile buf readhexstring pop } bind\n")
  325. fp.write(operator[2] + "\n")
  326. if hasattr(fp, "flush"):
  327. fp.flush()
  328. ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)])
  329. fp.write("\n%%%%EndBinary\n")
  330. fp.write("grestore end\n")
  331. if hasattr(fp, "flush"):
  332. fp.flush()
  333. finally:
  334. if wrapped_fp:
  335. fp.detach()
  336. #
  337. # --------------------------------------------------------------------
  338. Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
  339. Image.register_save(EpsImageFile.format, _save)
  340. Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
  341. Image.register_mime(EpsImageFile.format, "application/postscript")