Skip to content

Commit 82ced1b

Browse files
author
Steve Canny
committed
img: add Png.from_stream()
1 parent 20ef6c5 commit 82ced1b

File tree

5 files changed

+123
-4
lines changed

5 files changed

+123
-4
lines changed

docx/image/helpers.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# encoding: utf-8
2+
3+
from __future__ import absolute_import, division, print_function
4+
5+
6+
class StreamReader(object):
7+
"""
8+
Wraps a file-like object to provide access to structured data from a
9+
binary file. Byte-order is configurable. *base_offset* is added to any
10+
base value provided to calculate actual location for reads.
11+
"""

docx/image/image.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,4 @@ def _from_stream(cls, stream, blob, filename=None):
4545
# import at execution time to avoid circular import
4646
from docx.image import image_cls_that_can_parse
4747
ImageSubclass = image_cls_that_can_parse(stream)
48-
return ImageSubclass(stream, blob, filename)
48+
return ImageSubclass.from_stream(stream, blob, filename)

docx/image/png.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,29 @@
22

33
from __future__ import absolute_import, division, print_function
44

5+
from .helpers import StreamReader
56
from .image import Image
67

78

89
class Png(Image):
910
"""
1011
Image header parser for PNG images
1112
"""
13+
@classmethod
14+
def from_stream(cls, stream, blob, filename):
15+
"""
16+
Return a |Png| instance having header properties parsed from image in
17+
*stream*.
18+
"""
19+
stream_rdr = StreamReader(stream, '>')
20+
attrs = cls._parse_png_headers(stream_rdr)
21+
cx, cy = attrs.pop('px_width'), attrs.pop('px_height')
22+
return Png(blob, filename, cx, cy, attrs)
23+
24+
@classmethod
25+
def _parse_png_headers(cls, stream):
26+
"""
27+
Return a dict of field, value pairs parsed from the PNG chunks in
28+
*stream*.
29+
"""
30+
raise NotImplementedError

tests/image/test_image.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,16 @@ def it_can_construct_from_an_image_stream(self, from_stream_fixture):
7979
image_cls_) = from_stream_fixture
8080
image = Image._from_stream(stream_, blob_, filename_)
8181
image_cls_that_can_parse_.assert_called_once_with(stream_)
82-
image_cls_.assert_called_once_with(stream_, blob_, filename_)
82+
image_cls_.from_stream.assert_called_once_with(
83+
stream_, blob_, filename_
84+
)
8385
assert image is image_
8486

8587
# fixtures -------------------------------------------------------
8688

8789
@pytest.fixture
8890
def blob_(self, request):
89-
return instance_mock(request, str)
91+
return instance_mock(request, bytes)
9092

9193
@pytest.fixture
9294
def BytesIO_(self, request, stream_):
@@ -135,7 +137,9 @@ def image_(self, request):
135137

136138
@pytest.fixture
137139
def image_cls_(self, request, image_):
138-
return loose_mock(request, return_value=image_)
140+
image_cls_ = loose_mock(request)
141+
image_cls_.from_stream.return_value = image_
142+
return image_cls_
139143

140144
@pytest.fixture
141145
def image_cls_that_can_parse_(self, request, image_cls_):

tests/image/test_png.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Test suite for docx.image.png module
5+
"""
6+
7+
from __future__ import absolute_import, print_function
8+
9+
import pytest
10+
11+
from docx.compat import BytesIO
12+
from docx.image.helpers import StreamReader
13+
from docx.image.png import Png
14+
15+
from ..unitutil import (
16+
initializer_mock, class_mock, instance_mock, method_mock
17+
)
18+
19+
20+
class DescribePng(object):
21+
22+
def it_can_construct_from_a_png_stream(self, from_stream_fixture):
23+
# fixture ----------------------
24+
(stream_, blob_, filename_, StreamReader_, _parse_png_headers_,
25+
stream_rdr_, Png__init__, cx, cy, attrs, png_) = from_stream_fixture
26+
# exercise ---------------------
27+
png = Png.from_stream(stream_, blob_, filename_)
28+
# verify -----------------------
29+
StreamReader_.assert_called_once_with(stream_, '>')
30+
_parse_png_headers_.assert_called_once_with(stream_rdr_)
31+
Png__init__.assert_called_once_with(blob_, filename_, cx, cy, attrs)
32+
assert isinstance(png, Png)
33+
34+
# fixtures -------------------------------------------------------
35+
36+
@pytest.fixture
37+
def attrs(self):
38+
return dict()
39+
40+
@pytest.fixture
41+
def blob_(self, request):
42+
return instance_mock(request, bytes)
43+
44+
@pytest.fixture
45+
def filename_(self, request):
46+
return instance_mock(request, str)
47+
48+
@pytest.fixture
49+
def from_stream_fixture(
50+
self, stream_, blob_, filename_, StreamReader_,
51+
_parse_png_headers_, stream_rdr_, Png__init__, attrs, png_):
52+
cx, cy = 42, 24
53+
attrs.update({'px_width': cx, 'px_height': cy})
54+
return (
55+
stream_, blob_, filename_, StreamReader_, _parse_png_headers_,
56+
stream_rdr_, Png__init__, cx, cy, attrs, png_
57+
)
58+
59+
@pytest.fixture
60+
def Png__init__(self, request):
61+
return initializer_mock(request, Png)
62+
63+
@pytest.fixture
64+
def _parse_png_headers_(self, request, attrs):
65+
return method_mock(
66+
request, Png, '_parse_png_headers', return_value=attrs
67+
)
68+
69+
@pytest.fixture
70+
def png_(self, request):
71+
return instance_mock(request, Png)
72+
73+
@pytest.fixture
74+
def StreamReader_(self, request, stream_rdr_):
75+
return class_mock(
76+
request, 'docx.image.png.StreamReader', return_value=stream_rdr_
77+
)
78+
79+
@pytest.fixture
80+
def stream_(self, request):
81+
return instance_mock(request, BytesIO)
82+
83+
@pytest.fixture
84+
def stream_rdr_(self, request):
85+
return instance_mock(request, StreamReader)

0 commit comments

Comments
 (0)