Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/EpsImagePlugin.py: 12%

217 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

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 

23import io 

24import os 

25import re 

26import subprocess 

27import sys 

28import tempfile 

29 

30from . import Image, ImageFile 

31from ._binary import i32le as i32 

32 

33# 

34# -------------------------------------------------------------------- 

35 

36split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$") 

37field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$") 

38 

39gs_windows_binary = None 

40if sys.platform.startswith("win"): 40 ↛ 41line 40 didn't jump to line 41, because the condition on line 40 was never true

41 import shutil 

42 

43 for binary in ("gswin32c", "gswin64c", "gs"): 

44 if shutil.which(binary) is not None: 

45 gs_windows_binary = binary 

46 break 

47 else: 

48 gs_windows_binary = False 

49 

50 

51def has_ghostscript(): 

52 if gs_windows_binary: 

53 return True 

54 if not sys.platform.startswith("win"): 

55 try: 

56 subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL) 

57 return True 

58 except OSError: 

59 # No Ghostscript 

60 pass 

61 return False 

62 

63 

64def Ghostscript(tile, size, fp, scale=1, transparency=False): 

65 """Render an image using Ghostscript""" 

66 

67 # Unpack decoder tile 

68 decoder, tile, offset, data = tile[0] 

69 length, bbox = data 

70 

71 # Hack to support hi-res rendering 

72 scale = int(scale) or 1 

73 # orig_size = size 

74 # orig_bbox = bbox 

75 size = (size[0] * scale, size[1] * scale) 

76 # resolution is dependent on bbox and size 

77 res = ( 

78 72.0 * size[0] / (bbox[2] - bbox[0]), 

79 72.0 * size[1] / (bbox[3] - bbox[1]), 

80 ) 

81 

82 out_fd, outfile = tempfile.mkstemp() 

83 os.close(out_fd) 

84 

85 infile_temp = None 

86 if hasattr(fp, "name") and os.path.exists(fp.name): 

87 infile = fp.name 

88 else: 

89 in_fd, infile_temp = tempfile.mkstemp() 

90 os.close(in_fd) 

91 infile = infile_temp 

92 

93 # Ignore length and offset! 

94 # Ghostscript can read it 

95 # Copy whole file to read in Ghostscript 

96 with open(infile_temp, "wb") as f: 

97 # fetch length of fp 

98 fp.seek(0, io.SEEK_END) 

99 fsize = fp.tell() 

100 # ensure start position 

101 # go back 

102 fp.seek(0) 

103 lengthfile = fsize 

104 while lengthfile > 0: 

105 s = fp.read(min(lengthfile, 100 * 1024)) 

106 if not s: 

107 break 

108 lengthfile -= len(s) 

109 f.write(s) 

110 

111 device = "pngalpha" if transparency else "ppmraw" 

112 

113 # Build Ghostscript command 

114 command = [ 

115 "gs", 

116 "-q", # quiet mode 

117 "-g%dx%d" % size, # set output geometry (pixels) 

118 "-r%fx%f" % res, # set input DPI (dots per inch) 

119 "-dBATCH", # exit after processing 

120 "-dNOPAUSE", # don't pause between pages 

121 "-dSAFER", # safe mode 

122 f"-sDEVICE={device}", 

123 f"-sOutputFile={outfile}", # output file 

124 # adjust for image origin 

125 "-c", 

126 f"{-bbox[0]} {-bbox[1]} translate", 

127 "-f", 

128 infile, # input file 

129 # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272) 

130 "-c", 

131 "showpage", 

132 ] 

133 

134 if gs_windows_binary is not None: 

135 if not gs_windows_binary: 

136 raise OSError("Unable to locate Ghostscript on paths") 

137 command[0] = gs_windows_binary 

138 

139 # push data through Ghostscript 

140 try: 

141 startupinfo = None 

142 if sys.platform.startswith("win"): 

143 startupinfo = subprocess.STARTUPINFO() 

144 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 

145 subprocess.check_call(command, startupinfo=startupinfo) 

146 out_im = Image.open(outfile) 

147 out_im.load() 

148 finally: 

149 try: 

150 os.unlink(outfile) 

151 if infile_temp: 

152 os.unlink(infile_temp) 

153 except OSError: 

154 pass 

155 

156 im = out_im.im.copy() 

157 out_im.close() 

158 return im 

159 

160 

161class PSFile: 

162 """ 

163 Wrapper for bytesio object that treats either CR or LF as end of line. 

164 """ 

165 

166 def __init__(self, fp): 

167 self.fp = fp 

168 self.char = None 

169 

170 def seek(self, offset, whence=io.SEEK_SET): 

171 self.char = None 

172 self.fp.seek(offset, whence) 

173 

174 def readline(self): 

175 s = [self.char or b""] 

176 self.char = None 

177 

178 c = self.fp.read(1) 

179 while (c not in b"\r\n") and len(c): 

180 s.append(c) 

181 c = self.fp.read(1) 

182 

183 self.char = self.fp.read(1) 

184 # line endings can be 1 or 2 of \r \n, in either order 

185 if self.char in b"\r\n": 

186 self.char = None 

187 

188 return b"".join(s).decode("latin-1") 

189 

190 

191def _accept(prefix): 

192 return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5) 

193 

194 

195## 

196# Image plugin for Encapsulated PostScript. This plugin supports only 

197# a few variants of this format. 

198 

199 

200class EpsImageFile(ImageFile.ImageFile): 

201 """EPS File Parser for the Python Imaging Library""" 

202 

203 format = "EPS" 

204 format_description = "Encapsulated Postscript" 

205 

206 mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"} 

207 

208 def _open(self): 

209 (length, offset) = self._find_offset(self.fp) 

210 

211 # Rewrap the open file pointer in something that will 

212 # convert line endings and decode to latin-1. 

213 fp = PSFile(self.fp) 

214 

215 # go to offset - start of "%!PS" 

216 fp.seek(offset) 

217 

218 box = None 

219 

220 self.mode = "RGB" 

221 self._size = 1, 1 # FIXME: huh? 

222 

223 # 

224 # Load EPS header 

225 

226 s_raw = fp.readline() 

227 s = s_raw.strip("\r\n") 

228 

229 while s_raw: 

230 if s: 

231 if len(s) > 255: 

232 raise SyntaxError("not an EPS file") 

233 

234 try: 

235 m = split.match(s) 

236 except re.error as e: 

237 raise SyntaxError("not an EPS file") from e 

238 

239 if m: 

240 k, v = m.group(1, 2) 

241 self.info[k] = v 

242 if k == "BoundingBox": 

243 try: 

244 # Note: The DSC spec says that BoundingBox 

245 # fields should be integers, but some drivers 

246 # put floating point values there anyway. 

247 box = [int(float(i)) for i in v.split()] 

248 self._size = box[2] - box[0], box[3] - box[1] 

249 self.tile = [ 

250 ("eps", (0, 0) + self.size, offset, (length, box)) 

251 ] 

252 except Exception: 

253 pass 

254 

255 else: 

256 m = field.match(s) 

257 if m: 

258 k = m.group(1) 

259 

260 if k == "EndComments": 

261 break 

262 if k[:8] == "PS-Adobe": 

263 self.info[k[:8]] = k[9:] 

264 else: 

265 self.info[k] = "" 

266 elif s[0] == "%": 

267 # handle non-DSC PostScript comments that some 

268 # tools mistakenly put in the Comments section 

269 pass 

270 else: 

271 raise OSError("bad EPS header") 

272 

273 s_raw = fp.readline() 

274 s = s_raw.strip("\r\n") 

275 

276 if s and s[:1] != "%": 

277 break 

278 

279 # 

280 # Scan for an "ImageData" descriptor 

281 

282 while s[:1] == "%": 

283 

284 if len(s) > 255: 

285 raise SyntaxError("not an EPS file") 

286 

287 if s[:11] == "%ImageData:": 

288 # Encoded bitmapped image. 

289 x, y, bi, mo = s[11:].split(None, 7)[:4] 

290 

291 if int(bi) != 8: 

292 break 

293 try: 

294 self.mode = self.mode_map[int(mo)] 

295 except ValueError: 

296 break 

297 

298 self._size = int(x), int(y) 

299 return 

300 

301 s = fp.readline().strip("\r\n") 

302 if not s: 

303 break 

304 

305 if not box: 

306 raise OSError("cannot determine EPS bounding box") 

307 

308 def _find_offset(self, fp): 

309 

310 s = fp.read(160) 

311 

312 if s[:4] == b"%!PS": 

313 # for HEAD without binary preview 

314 fp.seek(0, io.SEEK_END) 

315 length = fp.tell() 

316 offset = 0 

317 elif i32(s, 0) == 0xC6D3D0C5: 

318 # FIX for: Some EPS file not handled correctly / issue #302 

319 # EPS can contain binary data 

320 # or start directly with latin coding 

321 # more info see: 

322 # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf 

323 offset = i32(s, 4) 

324 length = i32(s, 8) 

325 else: 

326 raise SyntaxError("not an EPS file") 

327 

328 return length, offset 

329 

330 def load(self, scale=1, transparency=False): 

331 # Load EPS via Ghostscript 

332 if self.tile: 

333 self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) 

334 self.mode = self.im.mode 

335 self._size = self.im.size 

336 self.tile = [] 

337 return Image.Image.load(self) 

338 

339 def load_seek(self, *args, **kwargs): 

340 # we can't incrementally load, so force ImageFile.parser to 

341 # use our custom load method by defining this method. 

342 pass 

343 

344 

345# 

346# -------------------------------------------------------------------- 

347 

348 

349def _save(im, fp, filename, eps=1): 

350 """EPS Writer for the Python Imaging Library.""" 

351 

352 # 

353 # make sure image data is available 

354 im.load() 

355 

356 # 

357 # determine PostScript image mode 

358 if im.mode == "L": 

359 operator = (8, 1, b"image") 

360 elif im.mode == "RGB": 

361 operator = (8, 3, b"false 3 colorimage") 

362 elif im.mode == "CMYK": 

363 operator = (8, 4, b"false 4 colorimage") 

364 else: 

365 raise ValueError("image mode is not supported") 

366 

367 if eps: 

368 # 

369 # write EPS header 

370 fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n") 

371 fp.write(b"%%Creator: PIL 0.1 EpsEncode\n") 

372 # fp.write("%%CreationDate: %s"...) 

373 fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size) 

374 fp.write(b"%%Pages: 1\n") 

375 fp.write(b"%%EndComments\n") 

376 fp.write(b"%%Page: 1 1\n") 

377 fp.write(b"%%ImageData: %d %d " % im.size) 

378 fp.write(b'%d %d 0 1 1 "%s"\n' % operator) 

379 

380 # 

381 # image header 

382 fp.write(b"gsave\n") 

383 fp.write(b"10 dict begin\n") 

384 fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1])) 

385 fp.write(b"%d %d scale\n" % im.size) 

386 fp.write(b"%d %d 8\n" % im.size) # <= bits 

387 fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) 

388 fp.write(b"{ currentfile buf readhexstring pop } bind\n") 

389 fp.write(operator[2] + b"\n") 

390 if hasattr(fp, "flush"): 

391 fp.flush() 

392 

393 ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)]) 

394 

395 fp.write(b"\n%%%%EndBinary\n") 

396 fp.write(b"grestore end\n") 

397 if hasattr(fp, "flush"): 

398 fp.flush() 

399 

400 

401# 

402# -------------------------------------------------------------------- 

403 

404 

405Image.register_open(EpsImageFile.format, EpsImageFile, _accept) 

406 

407Image.register_save(EpsImageFile.format, _save) 

408 

409Image.register_extensions(EpsImageFile.format, [".ps", ".eps"]) 

410 

411Image.register_mime(EpsImageFile.format, "application/postscript")