Skip to content

Commit a78fb40

Browse files
author
Steve Canny
committed
img: add image stream recognizer
Image streams are recognized by image_cls_that_can_parse(stream), which returns the Image subclass that knows how to parse the image format contained in *stream*.
1 parent a38ffd3 commit a78fb40

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

docx/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# encoding: utf-8
2+
3+
4+
class UnrecognizedImageError(Exception):
5+
"""
6+
The provided image stream could not be recognized.
7+
"""

docx/image/__init__.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,34 @@
1818
import Image as PIL_Image
1919

2020
from docx.compat import BytesIO, is_string
21+
from docx.exceptions import UnrecognizedImageError
22+
from docx.image.png import Png
2123
from docx.opc.constants import CONTENT_TYPE as CT
2224
from docx.shared import lazyproperty
2325

2426

27+
SIGNATURES = (
28+
# class, offset, signature_bytes
29+
(Png, 0, b'\x89PNG\x0D\x0A\x1A\x0A'),
30+
)
31+
32+
2533
def image_cls_that_can_parse(stream):
2634
"""
2735
Return the |Image| subclass that can parse the headers of the image file
2836
contained in *stream*.
2937
"""
30-
raise NotImplementedError
38+
def read_32(stream):
39+
stream.seek(0)
40+
return stream.read(32)
41+
42+
header = read_32(stream)
43+
for cls, offset, signature_bytes in SIGNATURES:
44+
end = offset + len(signature_bytes)
45+
found_bytes = header[offset:end]
46+
if found_bytes == signature_bytes:
47+
return cls
48+
raise UnrecognizedImageError
3149

3250

3351
class Image_OLD(object):

docx/image/png.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# encoding: utf-8
2+
3+
from __future__ import absolute_import, division, print_function
4+
5+
6+
class Png(object):
7+
"""
8+
Image header parser for PNG images
9+
"""

tests/image/test_image.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import pytest
1010

1111
from docx.compat import BytesIO
12-
from docx.image import Image_OLD
12+
from docx.exceptions import UnrecognizedImageError
13+
from docx.image import image_cls_that_can_parse, Image_OLD
1314
from docx.image.image import Image
15+
from docx.image.png import Png
1416
from docx.opc.constants import CONTENT_TYPE as CT
1517

1618
from ..unitutil import (
@@ -19,6 +21,33 @@
1921
)
2022

2123

24+
class Describe_image_cls_that_can_parse(object):
25+
26+
def it_can_recognize_an_image_stream(self, image_cls_lookup_fixture):
27+
stream, expected_class = image_cls_lookup_fixture
28+
ImageSubclass = image_cls_that_can_parse(stream)
29+
assert ImageSubclass is expected_class
30+
31+
def it_raises_on_unrecognized_image_stream(self):
32+
stream = BytesIO(b'foobar 666 not an image stream')
33+
with pytest.raises(UnrecognizedImageError):
34+
image_cls_that_can_parse(stream)
35+
36+
# fixtures -------------------------------------------------------
37+
38+
@pytest.fixture(params=[
39+
('python-icon.png', Png),
40+
])
41+
def image_cls_lookup_fixture(self, request):
42+
image_filename, expected_class = request.param
43+
image_path = test_file(image_filename)
44+
with open(image_path, 'rb') as f:
45+
blob = f.read()
46+
image_stream = BytesIO(blob)
47+
image_stream.seek(666)
48+
return image_stream, expected_class
49+
50+
2251
class DescribeImage(object):
2352

2453
def it_can_construct_from_an_image_path(self, from_path_fixture):

0 commit comments

Comments
 (0)