python-docx-oss 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. docx/__init__.py +71 -0
  2. docx/api.py +37 -0
  3. docx/blkcntnr.py +101 -0
  4. docx/comments.py +163 -0
  5. docx/dml/__init__.py +0 -0
  6. docx/dml/color.py +112 -0
  7. docx/document.py +285 -0
  8. docx/drawing/__init__.py +59 -0
  9. docx/enum/__init__.py +0 -0
  10. docx/enum/base.py +150 -0
  11. docx/enum/dml.py +103 -0
  12. docx/enum/section.py +86 -0
  13. docx/enum/shape.py +19 -0
  14. docx/enum/style.py +452 -0
  15. docx/enum/table.py +136 -0
  16. docx/enum/text.py +367 -0
  17. docx/exceptions.py +18 -0
  18. docx/image/__init__.py +32 -0
  19. docx/image/bmp.py +43 -0
  20. docx/image/constants.py +172 -0
  21. docx/image/emf.py +46 -0
  22. docx/image/exceptions.py +13 -0
  23. docx/image/gif.py +38 -0
  24. docx/image/helpers.py +86 -0
  25. docx/image/image.py +237 -0
  26. docx/image/jpeg.py +469 -0
  27. docx/image/png.py +253 -0
  28. docx/image/svg.py +118 -0
  29. docx/image/tiff.py +289 -0
  30. docx/opc/__init__.py +0 -0
  31. docx/opc/constants.py +307 -0
  32. docx/opc/coreprops.py +142 -0
  33. docx/opc/customprops.py +59 -0
  34. docx/opc/exceptions.py +12 -0
  35. docx/opc/oxml.py +247 -0
  36. docx/opc/package.py +254 -0
  37. docx/opc/packuri.py +109 -0
  38. docx/opc/part.py +267 -0
  39. docx/opc/parts/__init__.py +0 -0
  40. docx/opc/parts/coreprops.py +48 -0
  41. docx/opc/parts/customprops.py +61 -0
  42. docx/opc/phys_pkg.py +119 -0
  43. docx/opc/pkgreader.py +254 -0
  44. docx/opc/pkgwriter.py +115 -0
  45. docx/opc/rel.py +169 -0
  46. docx/opc/shared.py +31 -0
  47. docx/opc/spec.py +24 -0
  48. docx/oxml/__init__.py +252 -0
  49. docx/oxml/comments.py +124 -0
  50. docx/oxml/coreprops.py +298 -0
  51. docx/oxml/customprops.py +141 -0
  52. docx/oxml/dml/CT_ExtensionList.py +48 -0
  53. docx/oxml/document.py +88 -0
  54. docx/oxml/drawing.py +11 -0
  55. docx/oxml/exceptions.py +10 -0
  56. docx/oxml/ns.py +111 -0
  57. docx/oxml/numbering.py +109 -0
  58. docx/oxml/parser.py +62 -0
  59. docx/oxml/section.py +537 -0
  60. docx/oxml/settings.py +138 -0
  61. docx/oxml/shape.py +426 -0
  62. docx/oxml/shared.py +52 -0
  63. docx/oxml/simpletypes.py +434 -0
  64. docx/oxml/styles.py +320 -0
  65. docx/oxml/table.py +977 -0
  66. docx/oxml/text/__init__.py +0 -0
  67. docx/oxml/text/font.py +358 -0
  68. docx/oxml/text/hyperlink.py +45 -0
  69. docx/oxml/text/pagebreak.py +278 -0
  70. docx/oxml/text/paragraph.py +106 -0
  71. docx/oxml/text/parfmt.py +392 -0
  72. docx/oxml/text/run.py +316 -0
  73. docx/oxml/xmlchemy.py +711 -0
  74. docx/package.py +119 -0
  75. docx/parts/__init__.py +0 -0
  76. docx/parts/comments.py +51 -0
  77. docx/parts/customxml.py +67 -0
  78. docx/parts/document.py +193 -0
  79. docx/parts/hdrftr.py +53 -0
  80. docx/parts/image.py +80 -0
  81. docx/parts/numbering.py +51 -0
  82. docx/parts/settings.py +50 -0
  83. docx/parts/story.py +113 -0
  84. docx/parts/styles.py +42 -0
  85. docx/py.typed +0 -0
  86. docx/section.py +479 -0
  87. docx/settings.py +35 -0
  88. docx/shape.py +103 -0
  89. docx/shared.py +382 -0
  90. docx/styles/__init__.py +40 -0
  91. docx/styles/latent.py +198 -0
  92. docx/styles/style.py +254 -0
  93. docx/styles/styles.py +136 -0
  94. docx/table.py +537 -0
  95. docx/templates/default-comments.xml +12 -0
  96. docx/templates/default-docx-template/[Content_Types].xml +17 -0
  97. docx/templates/default-docx-template/_rels/.rels +7 -0
  98. docx/templates/default-docx-template/customXml/_rels/item1.xml.rels +4 -0
  99. docx/templates/default-docx-template/customXml/item1.xml +2 -0
  100. docx/templates/default-docx-template/customXml/itemProps1.xml +6 -0
  101. docx/templates/default-docx-template/docProps/app.xml +36 -0
  102. docx/templates/default-docx-template/docProps/core.xml +13 -0
  103. docx/templates/default-docx-template/docProps/thumbnail.jpeg +0 -0
  104. docx/templates/default-docx-template/word/_rels/document.xml.rels +11 -0
  105. docx/templates/default-docx-template/word/document.xml +11 -0
  106. docx/templates/default-docx-template/word/fontTable.xml +61 -0
  107. docx/templates/default-docx-template/word/numbering.xml +201 -0
  108. docx/templates/default-docx-template/word/settings.xml +53 -0
  109. docx/templates/default-docx-template/word/styles.xml +11844 -0
  110. docx/templates/default-docx-template/word/stylesWithEffects.xml +11800 -0
  111. docx/templates/default-docx-template/word/theme/theme1.xml +318 -0
  112. docx/templates/default-docx-template/word/webSettings.xml +5 -0
  113. docx/templates/default-footer.xml +27 -0
  114. docx/templates/default-header.xml +27 -0
  115. docx/templates/default-numbering.xml +20 -0
  116. docx/templates/default-settings.xml +26 -0
  117. docx/templates/default-styles.xml +190 -0
  118. docx/templates/default.docx +0 -0
  119. docx/text/__init__.py +0 -0
  120. docx/text/font.py +466 -0
  121. docx/text/hyperlink.py +121 -0
  122. docx/text/pagebreak.py +104 -0
  123. docx/text/paragraph.py +173 -0
  124. docx/text/parfmt.py +286 -0
  125. docx/text/run.py +272 -0
  126. docx/text/tabstops.py +123 -0
  127. docx/types.py +34 -0
  128. python_docx_oss-0.1.0.dist-info/METADATA +57 -0
  129. python_docx_oss-0.1.0.dist-info/RECORD +132 -0
  130. python_docx_oss-0.1.0.dist-info/WHEEL +5 -0
  131. python_docx_oss-0.1.0.dist-info/licenses/LICENSE +21 -0
  132. python_docx_oss-0.1.0.dist-info/top_level.txt +1 -0
docx/image/jpeg.py ADDED
@@ -0,0 +1,469 @@
1
+ """Objects related to parsing headers of JPEG image streams.
2
+
3
+ Includes both JFIF and Exif sub-formats.
4
+ """
5
+
6
+ import io
7
+
8
+ from docx.image.constants import JPEG_MARKER_CODE, MIME_TYPE
9
+ from docx.image.helpers import BIG_ENDIAN, StreamReader
10
+ from docx.image.image import BaseImageHeader
11
+ from docx.image.tiff import Tiff
12
+
13
+
14
+ class Jpeg(BaseImageHeader):
15
+ """Base class for JFIF and EXIF subclasses."""
16
+
17
+ @property
18
+ def content_type(self):
19
+ """MIME content type for this image, unconditionally `image/jpeg` for JPEG
20
+ images."""
21
+ return MIME_TYPE.JPEG
22
+
23
+ @property
24
+ def default_ext(self):
25
+ """Default filename extension, always 'jpg' for JPG images."""
26
+ return "jpg"
27
+
28
+ DEFAULT_DPI = 72
29
+
30
+ @classmethod
31
+ def from_stream(cls, stream):
32
+ """If APP0 or APP1 is missing, the DPI values will be set to the default value of
33
+ |DEFAULT_DPI|(72)."""
34
+ markers = _JfifMarkers.from_stream(stream)
35
+ px_width = markers.sof.px_width
36
+ px_height = markers.sof.px_height
37
+ horz_dpi, vert_dpi = cls._get_horz_dpi_and_vert_dpi(markers)
38
+ return cls(px_width, px_height, horz_dpi, vert_dpi)
39
+
40
+ @classmethod
41
+ def _get_horz_dpi_and_vert_dpi(cls, markers):
42
+ horz_dpi, vert_dpi = cls._try_get_dpi_from_app0(markers)
43
+ if horz_dpi is None or vert_dpi is None:
44
+ horz_dpi, vert_dpi = cls._try_get_dpi_from_app1(markers)
45
+ if horz_dpi is None or vert_dpi is None:
46
+ horz_dpi = vert_dpi = cls.DEFAULT_DPI
47
+ # DPI values in APP0 or APP1 may be 0, which is invalid. should use default value.
48
+ if horz_dpi == 0:
49
+ horz_dpi = cls.DEFAULT_DPI
50
+ if vert_dpi == 0:
51
+ vert_dpi = cls.DEFAULT_DPI
52
+ return horz_dpi, vert_dpi
53
+
54
+ @staticmethod
55
+ def _try_get_dpi_from_app0(markers):
56
+ try:
57
+ horz_dpi = markers.app0.horz_dpi
58
+ vert_dpi = markers.app0.vert_dpi
59
+ return horz_dpi, vert_dpi
60
+ except KeyError:
61
+ return None, None
62
+
63
+ @staticmethod
64
+ def _try_get_dpi_from_app1(markers):
65
+ try:
66
+ horz_dpi = markers.app1.horz_dpi
67
+ vert_dpi = markers.app1.vert_dpi
68
+ return horz_dpi, vert_dpi
69
+ except KeyError:
70
+ return None, None
71
+
72
+
73
+ class Exif(Jpeg):
74
+ """Image header parser for Exif image format."""
75
+
76
+ @classmethod
77
+ def from_stream(cls, stream):
78
+ """Return |Exif| instance having header properties parsed from Exif image in
79
+ `stream`."""
80
+ markers = _JfifMarkers.from_stream(stream)
81
+ # print('\n%s' % markers)
82
+
83
+ px_width = markers.sof.px_width
84
+ px_height = markers.sof.px_height
85
+ horz_dpi = markers.app1.horz_dpi
86
+ vert_dpi = markers.app1.vert_dpi
87
+
88
+ return cls(px_width, px_height, horz_dpi, vert_dpi)
89
+
90
+
91
+ class Jfif(Jpeg):
92
+ """Image header parser for JFIF image format."""
93
+
94
+ @classmethod
95
+ def from_stream(cls, stream):
96
+ """Return a |Jfif| instance having header properties parsed from image in
97
+ `stream`."""
98
+ markers = _JfifMarkers.from_stream(stream)
99
+
100
+ px_width = markers.sof.px_width
101
+ px_height = markers.sof.px_height
102
+ horz_dpi = markers.app0.horz_dpi
103
+ vert_dpi = markers.app0.vert_dpi
104
+
105
+ return cls(px_width, px_height, horz_dpi, vert_dpi)
106
+
107
+
108
+ class _JfifMarkers:
109
+ """Sequence of markers in a JPEG file, perhaps truncated at first SOS marker for
110
+ performance reasons."""
111
+
112
+ def __init__(self, markers):
113
+ super(_JfifMarkers, self).__init__()
114
+ self._markers = list(markers)
115
+
116
+ def __str__(self): # pragma: no cover
117
+ """Returns a tabular listing of the markers in this instance, which can be handy
118
+ for debugging and perhaps other uses."""
119
+ header = " offset seglen mc name\n======= ====== == ====="
120
+ tmpl = "%7d %6d %02X %s"
121
+ rows = []
122
+ for marker in self._markers:
123
+ rows.append(
124
+ tmpl
125
+ % (
126
+ marker.offset,
127
+ marker.segment_length,
128
+ ord(marker.marker_code),
129
+ marker.name,
130
+ )
131
+ )
132
+ lines = [header] + rows
133
+ return "\n".join(lines)
134
+
135
+ @classmethod
136
+ def from_stream(cls, stream):
137
+ """Return a |_JfifMarkers| instance containing a |_JfifMarker| subclass instance
138
+ for each marker in `stream`."""
139
+ marker_parser = _MarkerParser.from_stream(stream)
140
+ markers = []
141
+ for marker in marker_parser.iter_markers():
142
+ markers.append(marker)
143
+ if marker.marker_code == JPEG_MARKER_CODE.SOS:
144
+ break
145
+ return cls(markers)
146
+
147
+ @property
148
+ def app0(self):
149
+ """First APP0 marker in image markers."""
150
+ for m in self._markers:
151
+ if m.marker_code == JPEG_MARKER_CODE.APP0:
152
+ return m
153
+ raise KeyError("no APP0 marker in image")
154
+
155
+ @property
156
+ def app1(self):
157
+ """First APP1 marker in image markers."""
158
+ for m in self._markers:
159
+ if m.marker_code == JPEG_MARKER_CODE.APP1:
160
+ return m
161
+ raise KeyError("no APP1 marker in image")
162
+
163
+ @property
164
+ def sof(self):
165
+ """First start of frame (SOFn) marker in this sequence."""
166
+ for m in self._markers:
167
+ if m.marker_code in JPEG_MARKER_CODE.SOF_MARKER_CODES:
168
+ return m
169
+ raise KeyError("no start of frame (SOFn) marker in image")
170
+
171
+
172
+ class _MarkerParser:
173
+ """Service class that knows how to parse a JFIF stream and iterate over its
174
+ markers."""
175
+
176
+ def __init__(self, stream_reader):
177
+ super(_MarkerParser, self).__init__()
178
+ self._stream = stream_reader
179
+
180
+ @classmethod
181
+ def from_stream(cls, stream):
182
+ """Return a |_MarkerParser| instance to parse JFIF markers from `stream`."""
183
+ stream_reader = StreamReader(stream, BIG_ENDIAN)
184
+ return cls(stream_reader)
185
+
186
+ def iter_markers(self):
187
+ """Generate a (marker_code, segment_offset) 2-tuple for each marker in the JPEG
188
+ `stream`, in the order they occur in the stream."""
189
+ marker_finder = _MarkerFinder.from_stream(self._stream)
190
+ start = 0
191
+ marker_code = None
192
+ while marker_code != JPEG_MARKER_CODE.EOI:
193
+ marker_code, segment_offset = marker_finder.next(start)
194
+ marker = _MarkerFactory(marker_code, self._stream, segment_offset)
195
+ yield marker
196
+ start = segment_offset + marker.segment_length
197
+
198
+
199
+ class _MarkerFinder:
200
+ """Service class that knows how to find the next JFIF marker in a stream."""
201
+
202
+ def __init__(self, stream):
203
+ super(_MarkerFinder, self).__init__()
204
+ self._stream = stream
205
+
206
+ @classmethod
207
+ def from_stream(cls, stream):
208
+ """Return a |_MarkerFinder| instance to find JFIF markers in `stream`."""
209
+ return cls(stream)
210
+
211
+ def next(self, start):
212
+ """Return a (marker_code, segment_offset) 2-tuple identifying and locating the
213
+ first marker in `stream` occuring after offset `start`.
214
+
215
+ The returned `segment_offset` points to the position immediately following the
216
+ 2-byte marker code, the start of the marker segment, for those markers that have
217
+ a segment.
218
+ """
219
+ position = start
220
+ while True:
221
+ # skip over any non-\xFF bytes
222
+ position = self._offset_of_next_ff_byte(start=position)
223
+ # skip over any \xFF padding bytes
224
+ position, byte_ = self._next_non_ff_byte(start=position + 1)
225
+ # 'FF 00' sequence is not a marker, start over if found
226
+ if byte_ == b"\x00":
227
+ continue
228
+ # this is a marker, gather return values and break out of scan
229
+ marker_code, segment_offset = byte_, position + 1
230
+ break
231
+ return marker_code, segment_offset
232
+
233
+ def _next_non_ff_byte(self, start):
234
+ """Return an offset, byte 2-tuple for the next byte in `stream` that is not
235
+ '\xff', starting with the byte at offset `start`.
236
+
237
+ If the byte at offset `start` is not '\xff', `start` and the returned `offset`
238
+ will be the same.
239
+ """
240
+ self._stream.seek(start)
241
+ byte_ = self._read_byte()
242
+ while byte_ == b"\xff":
243
+ byte_ = self._read_byte()
244
+ offset_of_non_ff_byte = self._stream.tell() - 1
245
+ return offset_of_non_ff_byte, byte_
246
+
247
+ def _offset_of_next_ff_byte(self, start):
248
+ """Return the offset of the next '\xff' byte in `stream` starting with the byte
249
+ at offset `start`.
250
+
251
+ Returns `start` if the byte at that offset is a hex 255; it does not necessarily
252
+ advance in the stream.
253
+ """
254
+ self._stream.seek(start)
255
+ byte_ = self._read_byte()
256
+ while byte_ != b"\xff":
257
+ byte_ = self._read_byte()
258
+ offset_of_ff_byte = self._stream.tell() - 1
259
+ return offset_of_ff_byte
260
+
261
+ def _read_byte(self):
262
+ """Return the next byte read from stream.
263
+
264
+ Raise Exception if stream is at end of file.
265
+ """
266
+ byte_ = self._stream.read(1)
267
+ if not byte_: # pragma: no cover
268
+ raise Exception("unexpected end of file")
269
+ return byte_
270
+
271
+
272
+ def _MarkerFactory(marker_code, stream, offset):
273
+ """Return |_Marker| or subclass instance appropriate for marker at `offset` in
274
+ `stream` having `marker_code`."""
275
+ if marker_code == JPEG_MARKER_CODE.APP0:
276
+ marker_cls = _App0Marker
277
+ elif marker_code == JPEG_MARKER_CODE.APP1:
278
+ marker_cls = _App1Marker
279
+ elif marker_code in JPEG_MARKER_CODE.SOF_MARKER_CODES:
280
+ marker_cls = _SofMarker
281
+ else:
282
+ marker_cls = _Marker
283
+ return marker_cls.from_stream(stream, marker_code, offset)
284
+
285
+
286
+ class _Marker:
287
+ """Base class for JFIF marker classes.
288
+
289
+ Represents a marker and its segment occuring in a JPEG byte stream.
290
+ """
291
+
292
+ def __init__(self, marker_code, offset, segment_length):
293
+ super(_Marker, self).__init__()
294
+ self._marker_code = marker_code
295
+ self._offset = offset
296
+ self._segment_length = segment_length
297
+
298
+ @classmethod
299
+ def from_stream(cls, stream, marker_code, offset):
300
+ """Return a generic |_Marker| instance for the marker at `offset` in `stream`
301
+ having `marker_code`."""
302
+ if JPEG_MARKER_CODE.is_standalone(marker_code):
303
+ segment_length = 0
304
+ else:
305
+ segment_length = stream.read_short(offset)
306
+ return cls(marker_code, offset, segment_length)
307
+
308
+ @property
309
+ def marker_code(self):
310
+ """The single-byte code that identifies the type of this marker, e.g. ``'\xe0'``
311
+ for start of image (SOI)."""
312
+ return self._marker_code
313
+
314
+ @property
315
+ def name(self): # pragma: no cover
316
+ return JPEG_MARKER_CODE.marker_names[self._marker_code]
317
+
318
+ @property
319
+ def offset(self): # pragma: no cover
320
+ return self._offset
321
+
322
+ @property
323
+ def segment_length(self):
324
+ """The length in bytes of this marker's segment."""
325
+ return self._segment_length
326
+
327
+
328
+ class _App0Marker(_Marker):
329
+ """Represents a JFIF APP0 marker segment."""
330
+
331
+ def __init__(self, marker_code, offset, length, density_units, x_density, y_density):
332
+ super(_App0Marker, self).__init__(marker_code, offset, length)
333
+ self._density_units = density_units
334
+ self._x_density = x_density
335
+ self._y_density = y_density
336
+
337
+ @property
338
+ def horz_dpi(self):
339
+ """Horizontal dots per inch specified in this marker, defaults to 72 if not
340
+ specified."""
341
+ return self._dpi(self._x_density)
342
+
343
+ @property
344
+ def vert_dpi(self):
345
+ """Vertical dots per inch specified in this marker, defaults to 72 if not
346
+ specified."""
347
+ return self._dpi(self._y_density)
348
+
349
+ def _dpi(self, density):
350
+ """Return dots per inch corresponding to `density` value."""
351
+ if self._density_units == 1:
352
+ dpi = density
353
+ elif self._density_units == 2:
354
+ dpi = int(round(density * 2.54))
355
+ else:
356
+ dpi = 72
357
+ return dpi
358
+
359
+ @classmethod
360
+ def from_stream(cls, stream, marker_code, offset):
361
+ """Return an |_App0Marker| instance for the APP0 marker at `offset` in
362
+ `stream`."""
363
+ # field off type notes
364
+ # ------------------ --- ----- -------------------
365
+ # segment length 0 short
366
+ # JFIF identifier 2 5 chr 'JFIF\x00'
367
+ # major JPEG version 7 byte typically 1
368
+ # minor JPEG version 8 byte typically 1 or 2
369
+ # density units 9 byte 1=inches, 2=cm
370
+ # horz dots per unit 10 short
371
+ # vert dots per unit 12 short
372
+ # ------------------ --- ----- -------------------
373
+ segment_length = stream.read_short(offset)
374
+ density_units = stream.read_byte(offset, 9)
375
+ x_density = stream.read_short(offset, 10)
376
+ y_density = stream.read_short(offset, 12)
377
+ return cls(marker_code, offset, segment_length, density_units, x_density, y_density)
378
+
379
+
380
+ class _App1Marker(_Marker):
381
+ """Represents a JFIF APP1 (Exif) marker segment."""
382
+
383
+ def __init__(self, marker_code, offset, length, horz_dpi, vert_dpi):
384
+ super(_App1Marker, self).__init__(marker_code, offset, length)
385
+ self._horz_dpi = horz_dpi
386
+ self._vert_dpi = vert_dpi
387
+
388
+ @classmethod
389
+ def from_stream(cls, stream, marker_code, offset):
390
+ """Extract the horizontal and vertical dots-per-inch value from the APP1 header
391
+ at `offset` in `stream`."""
392
+ # field off len type notes
393
+ # -------------------- --- --- ----- ----------------------------
394
+ # segment length 0 2 short
395
+ # Exif identifier 2 6 6 chr 'Exif\x00\x00'
396
+ # TIFF byte order 8 2 2 chr 'II'=little 'MM'=big endian
397
+ # meaning of universe 10 2 2 chr '*\x00' or '\x00*' depending
398
+ # IFD0 off fr/II or MM 10 16 long relative to ...?
399
+ # -------------------- --- --- ----- ----------------------------
400
+ segment_length = stream.read_short(offset)
401
+ if cls._is_non_Exif_APP1_segment(stream, offset):
402
+ return cls(marker_code, offset, segment_length, 72, 72)
403
+ tiff = cls._tiff_from_exif_segment(stream, offset, segment_length)
404
+ return cls(marker_code, offset, segment_length, tiff.horz_dpi, tiff.vert_dpi)
405
+
406
+ @property
407
+ def horz_dpi(self):
408
+ """Horizontal dots per inch specified in this marker, defaults to 72 if not
409
+ specified."""
410
+ return self._horz_dpi
411
+
412
+ @property
413
+ def vert_dpi(self):
414
+ """Vertical dots per inch specified in this marker, defaults to 72 if not
415
+ specified."""
416
+ return self._vert_dpi
417
+
418
+ @classmethod
419
+ def _is_non_Exif_APP1_segment(cls, stream, offset):
420
+ """Return True if the APP1 segment at `offset` in `stream` is NOT an Exif
421
+ segment, as determined by the ``'Exif\x00\x00'`` signature at offset 2 in the
422
+ segment."""
423
+ stream.seek(offset + 2)
424
+ exif_signature = stream.read(6)
425
+ return exif_signature != b"Exif\x00\x00"
426
+
427
+ @classmethod
428
+ def _tiff_from_exif_segment(cls, stream, offset, segment_length):
429
+ """Return a |Tiff| instance parsed from the Exif APP1 segment of
430
+ `segment_length` at `offset` in `stream`."""
431
+ # wrap full segment in its own stream and feed to Tiff()
432
+ stream.seek(offset + 8)
433
+ segment_bytes = stream.read(segment_length - 8)
434
+ substream = io.BytesIO(segment_bytes)
435
+ return Tiff.from_stream(substream)
436
+
437
+
438
+ class _SofMarker(_Marker):
439
+ """Represents a JFIF start of frame (SOFx) marker segment."""
440
+
441
+ def __init__(self, marker_code, offset, segment_length, px_width, px_height):
442
+ super(_SofMarker, self).__init__(marker_code, offset, segment_length)
443
+ self._px_width = px_width
444
+ self._px_height = px_height
445
+
446
+ @classmethod
447
+ def from_stream(cls, stream, marker_code, offset):
448
+ """Return an |_SofMarker| instance for the SOFn marker at `offset` in stream."""
449
+ # field off type notes
450
+ # ------------------ --- ----- ----------------------------
451
+ # segment length 0 short
452
+ # Data precision 2 byte
453
+ # Vertical lines 3 short px_height
454
+ # Horizontal lines 5 short px_width
455
+ # ------------------ --- ----- ----------------------------
456
+ segment_length = stream.read_short(offset)
457
+ px_height = stream.read_short(offset, 3)
458
+ px_width = stream.read_short(offset, 5)
459
+ return cls(marker_code, offset, segment_length, px_width, px_height)
460
+
461
+ @property
462
+ def px_height(self):
463
+ """Image height in pixels."""
464
+ return self._px_height
465
+
466
+ @property
467
+ def px_width(self):
468
+ """Image width in pixels."""
469
+ return self._px_width