Skip to content

Commit fbc1943

Browse files
committed
added updates to fix issue #346
1 parent 1e5d891 commit fbc1943

5 files changed

Lines changed: 57 additions & 10 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ body:
4949
label: pdf2docx version
5050
options:
5151
-
52+
- 0.5.10
5253
- 0.5.9
5354
- 0.5.8
5455
- 0.5.7

pdf2docx/image/ImagesExtractor.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"""
1010

1111
import logging
12+
import math
1213
import fitz
1314
from ..common.Collection import Collection
1415
from ..common.share import BlockType
@@ -93,6 +94,24 @@ def clip_page_to_dict(
9394
)
9495
return self._to_raw_dict(pix, bbox)
9596

97+
@staticmethod
98+
def _get_image_rotation(matrix) -> int:
99+
"""Extract rotation angle (0, 90, 180, 270) from image transform matrix.
100+
101+
Handles 90-degree multiples only. Based on PyMuPDF Matrix format:
102+
fitz.Matrix(90) -> (0, 1, -1, 0, ...), Matrix(180) -> (-1, 0, 0, -1, ...).
103+
"""
104+
if matrix is None:
105+
return 0
106+
try:
107+
a, b, c, d = matrix.a, matrix.b, matrix.c, matrix.d
108+
except AttributeError:
109+
return 0
110+
# Round to nearest 90 degrees using atan2
111+
angle_rad = math.atan2(b, a)
112+
angle_deg = round(math.degrees(angle_rad) / 90) * 90
113+
return int(angle_deg % 360)
114+
96115
def extract_images(self, clip_image_res_ratio: float = 3.0):
97116
"""Extract normal images with ``Page.get_images()``.
98117
@@ -117,16 +136,23 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
117136
# extract the equivalent image by clipping the union page region for now.
118137
# https://github.com/dothinking/pdf2docx/issues/123
119138

120-
# step 1: collect images: [(bbox, item), ..., ]
139+
# step 1: collect images: [(bbox, item, image_rotation), ..., ]
121140
ic = Collection()
122141
for item in self._page.get_images(full=True):
123142
item = list(item)
124143
item[-1] = 0
144+
xref = item[0]
125145

126146
# find all occurrences referenced to this image
127147
rects = self._page.get_image_rects(item)
148+
# get per-image transform (rotation) when available (PyMuPDF 1.19+)
149+
rects_with_transform = []
150+
try:
151+
rects_with_transform = self._page.get_image_rects(xref, transform=True)
152+
except (TypeError, AttributeError):
153+
pass
128154
unrotated_page_bbox = self._page.cropbox # note the difference to page.rect
129-
for bbox in rects:
155+
for i, bbox in enumerate(rects):
130156
# ignore small images
131157
if bbox.get_area() <= 4:
132158
continue
@@ -135,8 +161,15 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
135161
if not unrotated_page_bbox.intersects(bbox):
136162
continue
137163

138-
# collect images
139-
ic.append((bbox, item))
164+
# extract per-image rotation from transform matrix
165+
image_rotation = 0
166+
if i < len(rects_with_transform):
167+
entry = rects_with_transform[i]
168+
if isinstance(entry, (list, tuple)) and len(entry) >= 2:
169+
matrix_t = entry[1]
170+
image_rotation = self._get_image_rotation(matrix_t)
171+
172+
ic.append((bbox, item, image_rotation))
140173

141174
# step 2: group by intersection
142175
fun = lambda a, b: a[0].intersects(b[0])
@@ -148,14 +181,14 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
148181
# clip page with the union bbox of all intersected images
149182
if len(group) > 1:
150183
clip_bbox = fitz.Rect()
151-
for bbox, item in group:
184+
for bbox, item, _ in group:
152185
clip_bbox |= bbox
153186
raw_dict = self.clip_page_to_dict(
154187
clip_bbox, False, clip_image_res_ratio
155188
)
156189

157190
else:
158-
bbox, item = group[0]
191+
bbox, item, image_rotation = group[0]
159192

160193
# Regarding images consist of alpha values only, the turquoise color shown in
161194
# the PDF is not part of the image, but part of PDF background.
@@ -182,10 +215,12 @@ def extract_images(self, clip_image_res_ratio: float = 3.0):
182215
# recover image, e.g., handle image with mask, or CMYK color space
183216
pix = self._recover_pixmap(doc, item)
184217

185-
# rotate image with opencv if page is rotated
218+
# rotate image: apply inverse of per-image transform, then page rotation
219+
# (PyMuPDF matrix maps image->page; correct pixmap with inverse: apply -angle)
186220
raw_dict = self._to_raw_dict(pix, bbox)
187-
if rotation:
188-
raw_dict["image"] = self._rotate_image(pix, -rotation)
221+
total_rotation = (rotation or 0) - image_rotation
222+
if total_rotation:
223+
raw_dict["image"] = self._rotate_image(pix, total_rotation)
189224

190225
images.append(raw_dict)
191226

test/samples/demo-issue-346.pdf

7.22 MB
Binary file not shown.

test/test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,17 @@ def test_multi_pages(self):
277277
# check file
278278
assert os.path.isfile(docx_file)
279279

280+
# ------------------------------------------
281+
# rotated images (issue 346)
282+
# ------------------------------------------
283+
def test_rotated_images(self):
284+
'''Test converting PDF with rotated images (issue 346).'''
285+
filename = 'demo-issue-346'
286+
self.convert(filename)
287+
docx_file = os.path.join(output_path, f'{filename}.docx')
288+
assert os.path.isfile(docx_file), f'Expected output file: {docx_file}'
289+
assert os.path.getsize(docx_file) > 0, 'Output docx should not be empty'
290+
280291

281292
# We make a separate pytest test for each sample file.
282293

version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.5.9
1+
0.5.10

0 commit comments

Comments
 (0)