Skip to content

Commit c946381

Browse files
committed
add EPS test for image with ImageData and BoundingBox (atend)
1 parent a4b819e commit c946381

File tree

3 files changed

+66
-40
lines changed

3 files changed

+66
-40
lines changed

Tests/images/eps/1_atend.eps

44.8 KB
Binary file not shown.

Tests/test_file_eps.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ def test_binary_header_only() -> None:
127127
EpsImagePlugin.EpsImageFile(data)
128128

129129

130+
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
131+
def test_simple_eps_file(prefix: bytes) -> None:
132+
data = io.BytesIO(prefix + b"\n".join(simple_eps_file))
133+
with Image.open(data) as img:
134+
assert img.mode == "RGB"
135+
assert img.size == (100, 100)
136+
assert img.format == "EPS"
137+
138+
130139
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
131140
def test_missing_version_comment(prefix: bytes) -> None:
132141
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_version))
@@ -142,23 +151,19 @@ def test_missing_boundingbox_comment(prefix: bytes) -> None:
142151

143152

144153
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
145-
def test_invalid_boundingbox_comment(prefix: bytes) -> None:
146-
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox))
154+
@pytest.mark.parametrize(
155+
"file_lines",
156+
(
157+
simple_eps_file_with_invalid_boundingbox,
158+
simple_eps_file_with_invalid_boundingbox_valid_imagedata,
159+
),
160+
)
161+
def test_invalid_boundingbox_comment(prefix: bytes, file_lines: list[bytes]) -> None:
162+
data = io.BytesIO(prefix + b"\n".join(file_lines))
147163
with pytest.raises(OSError, match="cannot determine EPS bounding box"):
148164
EpsImagePlugin.EpsImageFile(data)
149165

150166

151-
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
152-
def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix: bytes) -> None:
153-
data = io.BytesIO(
154-
prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox_valid_imagedata)
155-
)
156-
with Image.open(data) as img:
157-
assert img.mode == "RGB"
158-
assert img.size == (100, 100)
159-
assert img.format == "EPS"
160-
161-
162167
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
163168
def test_ascii_comment_too_long(prefix: bytes) -> None:
164169
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_ascii_comment))
@@ -247,8 +252,13 @@ def test_bytesio_object() -> None:
247252

248253

249254
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
250-
def test_1() -> None:
251-
with Image.open("Tests/images/eps/1.eps") as im:
255+
@pytest.mark.parametrize(
256+
# These images have an "ImageData" descriptor.
257+
"filename",
258+
("Tests/images/eps/1.eps", "Tests/images/eps/1_atend.eps"),
259+
)
260+
def test_1(filename: str) -> None:
261+
with Image.open(filename) as im:
252262
assert_image_equal_tofile(im, "Tests/images/eps/1.bmp")
253263

254264

src/PIL/EpsImagePlugin.py

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,11 @@ def _open(self) -> None:
193193
self.fp.seek(offset)
194194

195195
self._mode = "RGB"
196-
self._size = None
196+
197+
# When reading header comments, the first comment is used.
198+
# When reading trailer comments, the last comment is used.
199+
bounding_box: list[int] | None = None
200+
imagedata_size: tuple[int, int] | None = None
197201

198202
byte_arr = bytearray(255)
199203
bytes_mv = memoryview(byte_arr)
@@ -215,8 +219,8 @@ def check_required_header_comments() -> None:
215219
msg = 'EPS header missing "%%BoundingBox" comment'
216220
raise SyntaxError(msg)
217221

218-
def _read_comment(s: str) -> bool:
219-
nonlocal reading_trailer_comments
222+
def read_comment(s: str) -> bool:
223+
nonlocal bounding_box, reading_trailer_comments
220224
try:
221225
m = split.match(s)
222226
except re.error as e:
@@ -231,18 +235,12 @@ def _read_comment(s: str) -> bool:
231235
if k == "BoundingBox":
232236
if v == "(atend)":
233237
reading_trailer_comments = True
234-
elif not self._size or (trailer_reached and reading_trailer_comments):
238+
elif not bounding_box or (trailer_reached and reading_trailer_comments):
235239
try:
236240
# Note: The DSC spec says that BoundingBox
237241
# fields should be integers, but some drivers
238242
# put floating point values there anyway.
239-
box = [int(float(i)) for i in v.split()]
240-
self._size = box[2] - box[0], box[3] - box[1]
241-
self.tile = [
242-
ImageFile._Tile(
243-
"eps", (0, 0) + self.size, offset, (length, box)
244-
)
245-
]
243+
bounding_box = [int(float(i)) for i in v.split()]
246244
except Exception:
247245
pass
248246
return True
@@ -293,7 +291,7 @@ def _read_comment(s: str) -> bool:
293291
continue
294292

295293
s = str(bytes_mv[:bytes_read], "latin-1")
296-
if not _read_comment(s):
294+
if not read_comment(s):
297295
m = field.match(s)
298296
if m:
299297
k = m.group(1)
@@ -327,32 +325,50 @@ def _read_comment(s: str) -> bool:
327325
int(value) for value in image_data_values[:4]
328326
)
329327

330-
if bit_depth == 1:
331-
self._mode = "1"
332-
elif bit_depth == 8:
333-
try:
334-
self._mode = self.mode_map[mode_id]
335-
except ValueError:
336-
break
337-
else:
338-
break
328+
if not imagedata_size:
329+
imagedata_size = columns, rows
339330

340-
self._size = columns, rows
341-
return
331+
if bit_depth == 1:
332+
self._mode = "1"
333+
elif bit_depth == 8:
334+
try:
335+
self._mode = self.mode_map[mode_id]
336+
except ValueError:
337+
pass
342338
elif bytes_mv[:5] == b"%%EOF":
343339
break
344340
elif trailer_reached and reading_trailer_comments:
345341
# Load EPS trailer
346342
s = str(bytes_mv[:bytes_read], "latin-1")
347-
_read_comment(s)
343+
read_comment(s)
348344
elif bytes_mv[:9] == b"%%Trailer":
349345
trailer_reached = True
350346
bytes_read = 0
351347

352-
if not self._size:
348+
# A "BoundingBox" is always required,
349+
# even if an "ImageData" descriptor size exists.
350+
if not bounding_box:
353351
msg = "cannot determine EPS bounding box"
354352
raise OSError(msg)
355353

354+
# An "ImageData" size takes precedence over the "BoundingBox".
355+
if imagedata_size:
356+
self._size = imagedata_size
357+
else:
358+
self._size = (
359+
bounding_box[2] - bounding_box[0],
360+
bounding_box[3] - bounding_box[1],
361+
)
362+
363+
self.tile = [
364+
ImageFile._Tile(
365+
codec_name="eps",
366+
extents=(0, 0) + self._size,
367+
offset=offset,
368+
args=(length, bounding_box),
369+
)
370+
]
371+
356372
def _find_offset(self, fp: IO[bytes]) -> tuple[int, int]:
357373
s = fp.read(4)
358374

0 commit comments

Comments
 (0)