Skip to content

Commit f017469

Browse files
author
Steve Canny
committed
img: add _IfdEntryFactory()
1 parent 3d3415e commit f017469

3 files changed

Lines changed: 144 additions & 2 deletions

File tree

docx/image/constants.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,22 @@ class TAG(object):
111111
HORZ_PX_PER_UNIT = 'horz_px_per_unit'
112112
VERT_PX_PER_UNIT = 'vert_px_per_unit'
113113
UNITS_SPECIFIER = 'units_specifier'
114+
115+
116+
class TIFF_FLD_TYPE(object):
117+
"""
118+
Tag codes for TIFF Image File Directory (IFD) entries.
119+
"""
120+
BYTE = 1
121+
ASCII = 2
122+
SHORT = 3
123+
LONG = 4
124+
RATIONAL = 5
125+
126+
field_type_names = {
127+
1: 'BYTE', 2: 'ASCII char', 3: 'SHORT', 4: 'LONG',
128+
5: 'RATIONAL'
129+
}
130+
131+
132+
TIFF_FLD = TIFF_FLD_TYPE

docx/image/tiff.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import absolute_import, division, print_function
44

5+
from .constants import TIFF_FLD
56
from .helpers import BIG_ENDIAN, LITTLE_ENDIAN, StreamReader
67
from .image import Image
78

@@ -151,14 +152,35 @@ def _IfdEntryFactory(stream_rdr, offset):
151152
Return an |_IfdEntry| subclass instance containing the value of the
152153
directory entry at *offset* in *stream_rdr*.
153154
"""
154-
raise NotImplementedError
155+
ifd_entry_classes = {
156+
TIFF_FLD.ASCII: _AsciiIfdEntry,
157+
TIFF_FLD.SHORT: _ShortIfdEntry,
158+
TIFF_FLD.LONG: _LongIfdEntry,
159+
TIFF_FLD.RATIONAL: _RationalIfdEntry,
160+
}
161+
field_type = stream_rdr.read_short(offset, 2)
162+
if field_type in ifd_entry_classes:
163+
entry_cls = ifd_entry_classes[field_type]
164+
else:
165+
entry_cls = _IfdEntry
166+
return entry_cls.from_stream(stream_rdr, offset)
155167

156168

157169
class _IfdEntry(object):
158170
"""
159171
Base class for IFD entry classes. Subclasses are differentiated by value
160172
type, e.g. ASCII, long int, etc.
161173
"""
174+
@classmethod
175+
def from_stream(cls, stream_rdr, offset):
176+
"""
177+
Return an |_IfdEntry| subclass instance parsed from *stream_rdr* at
178+
*offset*. Note this method is common to all subclasses. Override the
179+
``_parse_value()`` method to provide distinctive behavior based on
180+
field type.
181+
"""
182+
raise NotImplementedError
183+
162184
@property
163185
def tag(self):
164186
"""
@@ -172,3 +194,27 @@ def value(self):
172194
Value of this tag, its type being dependent on the tag.
173195
"""
174196
raise NotImplementedError
197+
198+
199+
class _AsciiIfdEntry(_IfdEntry):
200+
"""
201+
IFD entry having the form of a NULL-terminated ASCII string
202+
"""
203+
204+
205+
class _ShortIfdEntry(_IfdEntry):
206+
"""
207+
IFD entry expressed as a short (2-byte) integer
208+
"""
209+
210+
211+
class _LongIfdEntry(_IfdEntry):
212+
"""
213+
IFD entry expressed as a long (4-byte) integer
214+
"""
215+
216+
217+
class _RationalIfdEntry(_IfdEntry):
218+
"""
219+
IFD entry expressed as a numerator, denominator pair
220+
"""

tests/image/test_tiff.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from docx.compat import BytesIO
1414
from docx.image.helpers import BIG_ENDIAN, LITTLE_ENDIAN, StreamReader
1515
from docx.image.tiff import (
16-
_IfdEntries, _IfdEntry, _IfdParser, Tiff, _TiffParser
16+
_IfdEntries, _IfdEntry, _IfdEntryFactory, _IfdParser, Tiff, _TiffParser
1717
)
1818

1919
from ..unitutil import (
@@ -251,3 +251,80 @@ def iter_fixture(self, _IfdEntryFactory_, ifd_entry_, ifd_entry_2_):
251251
ifd_parser, _IfdEntryFactory_, stream_rdr, offsets,
252252
expected_entries
253253
)
254+
255+
256+
class Describe_IfdEntryFactory(object):
257+
258+
def it_constructs_the_right_class_for_a_given_ifd_entry(self, fixture):
259+
stream_rdr, offset, entry_cls_, ifd_entry_ = fixture
260+
ifd_entry = _IfdEntryFactory(stream_rdr, offset)
261+
entry_cls_.from_stream.assert_called_once_with(stream_rdr, offset)
262+
assert ifd_entry is ifd_entry_
263+
264+
# fixtures -------------------------------------------------------
265+
266+
@pytest.fixture(params=[
267+
(b'\x66\x66\x00\x01', 'BYTE'),
268+
(b'\x66\x66\x00\x02', 'ASCII'),
269+
(b'\x66\x66\x00\x03', 'SHORT'),
270+
(b'\x66\x66\x00\x04', 'LONG'),
271+
(b'\x66\x66\x00\x05', 'RATIONAL'),
272+
(b'\x66\x66\x00\x06', 'CUSTOM'),
273+
])
274+
def fixture(
275+
self, request, ifd_entry_, _IfdEntry_, _AsciiIfdEntry_,
276+
_ShortIfdEntry_, _LongIfdEntry_, _RationalIfdEntry_):
277+
bytes_, entry_type = request.param
278+
entry_cls_ = {
279+
'BYTE': _IfdEntry_,
280+
'ASCII': _AsciiIfdEntry_,
281+
'SHORT': _ShortIfdEntry_,
282+
'LONG': _LongIfdEntry_,
283+
'RATIONAL': _RationalIfdEntry_,
284+
'CUSTOM': _IfdEntry_,
285+
}[entry_type]
286+
stream_rdr = StreamReader(BytesIO(bytes_), BIG_ENDIAN)
287+
offset = 0
288+
return stream_rdr, offset, entry_cls_, ifd_entry_
289+
290+
@pytest.fixture
291+
def ifd_entry_(self, request):
292+
return instance_mock(request, _IfdEntry)
293+
294+
@pytest.fixture
295+
def _IfdEntry_(self, request, ifd_entry_):
296+
_IfdEntry_ = class_mock(request, 'docx.image.tiff._IfdEntry')
297+
_IfdEntry_.from_stream.return_value = ifd_entry_
298+
return _IfdEntry_
299+
300+
@pytest.fixture
301+
def _AsciiIfdEntry_(self, request, ifd_entry_):
302+
_AsciiIfdEntry_ = class_mock(
303+
request, 'docx.image.tiff._AsciiIfdEntry')
304+
_AsciiIfdEntry_.from_stream.return_value = ifd_entry_
305+
return _AsciiIfdEntry_
306+
307+
@pytest.fixture
308+
def _ShortIfdEntry_(self, request, ifd_entry_):
309+
_ShortIfdEntry_ = class_mock(
310+
request, 'docx.image.tiff._ShortIfdEntry')
311+
_ShortIfdEntry_.from_stream.return_value = ifd_entry_
312+
return _ShortIfdEntry_
313+
314+
@pytest.fixture
315+
def _LongIfdEntry_(self, request, ifd_entry_):
316+
_LongIfdEntry_ = class_mock(
317+
request, 'docx.image.tiff._LongIfdEntry')
318+
_LongIfdEntry_.from_stream.return_value = ifd_entry_
319+
return _LongIfdEntry_
320+
321+
@pytest.fixture
322+
def _RationalIfdEntry_(self, request, ifd_entry_):
323+
_RationalIfdEntry_ = class_mock(
324+
request, 'docx.image.tiff._RationalIfdEntry')
325+
_RationalIfdEntry_.from_stream.return_value = ifd_entry_
326+
return _RationalIfdEntry_
327+
328+
@pytest.fixture
329+
def offset_(self, request):
330+
return instance_mock(request, int)

0 commit comments

Comments
 (0)