Skip to content

Commit 972bcf3

Browse files
src/ tests/: Correct links to pages with different size/rotation.
This addresses #3347 and #3400. We need to use the destination page's transformation matrix when constructing the link text.
1 parent 83b36bc commit 972bcf3

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

src/utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,10 @@ def getLinkText(page: pymupdf.Page, lnk: dict) -> str:
16431643
pno = lnk["page"]
16441644
xref = page.parent.page_xref(pno)
16451645
pnt = lnk.get("to", pymupdf.Point(0, 0)) # destination point
1646-
ipnt = pnt * ictm
1646+
dest_page = page.parent[pno]
1647+
dest_ctm = dest_page.transformation_matrix
1648+
dest_ictm = ~dest_ctm
1649+
ipnt = pnt * dest_ictm
16471650
annot = txt(xref, ipnt.x, ipnt.y, lnk.get("zoom", 0), rect)
16481651
else:
16491652
txt = pymupdf.annot_skel["goto2"] # annot_goto_n

tests/test_toc.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,120 @@ def get(doc):
157157
with pymupdf.open(file_out) as doc:
158158
print(f'4: {get(doc)}')
159159
pymupdf._log_items_clear()
160+
161+
162+
def test_3347():
163+
'''
164+
Check fix for #3347 - link destination rectangles when source/destination
165+
pages have different sizes.
166+
'''
167+
doc = pymupdf.open()
168+
doc.new_page(width=500, height=800)
169+
doc.new_page(width=800, height=500)
170+
rects = [
171+
(0, pymupdf.Rect(10, 20, 50, 40), pymupdf.utils.getColor('red')),
172+
(0, pymupdf.Rect(300, 350, 400, 450), pymupdf.utils.getColor('green')),
173+
(1, pymupdf.Rect(20, 30, 40, 50), pymupdf.utils.getColor('blue')),
174+
(1, pymupdf.Rect(350, 300, 450, 400), pymupdf.utils.getColor('black'))
175+
]
176+
177+
for page, rect, color in rects:
178+
doc[page].draw_rect(rect, color=color)
179+
180+
for (from_page, from_rect, _), (to_page, to_rect, _) in zip(rects, rects[1:] + rects[:1]):
181+
doc[from_page].insert_link({
182+
'kind': 1,
183+
'from': from_rect,
184+
'page': to_page,
185+
'to': to_rect.top_left,
186+
})
187+
188+
links_expected = [
189+
(0, {'kind': 1, 'xref': 11, 'from': pymupdf.Rect(10.0, 20.0, 50.0, 40.0), 'page': 0, 'to': pymupdf.Point(300.0, 350.0), 'zoom': 0.0, 'id': 'jorj-L0'}),
190+
(0, {'kind': 1, 'xref': 12, 'from': pymupdf.Rect(300.0, 350.0, 400.0, 450.0), 'page': 1, 'to': pymupdf.Point(20.0, 30.0), 'zoom': 0.0, 'id': 'jorj-L1'}),
191+
(1, {'kind': 1, 'xref': 13, 'from': pymupdf.Rect(20.0, 30.0, 40.0, 50.0), 'page': 1, 'to': pymupdf.Point(350.0, 300.0), 'zoom': 0.0, 'id': 'jorj-L0'}),
192+
(1, {'kind': 1, 'xref': 14, 'from': pymupdf.Rect(350.0, 300.0, 450.0, 400.0), 'page': 0, 'to': pymupdf.Point(10.0, 20.0), 'zoom': 0.0, 'id': 'jorj-L1'}),
193+
]
194+
195+
path = os.path.normpath(f'{__file__}/../../tests/test_3347_out.pdf')
196+
doc.save(path)
197+
print(f'Have saved to {path=}.')
198+
199+
links_actual = list()
200+
for page_i, page in enumerate(doc):
201+
links = page.get_links()
202+
for link_i, link in enumerate(links):
203+
print(f'{page_i=} {link_i=}: {link!r}')
204+
links_actual.append( (page_i, link) )
205+
206+
assert links_actual == links_expected
207+
208+
209+
def test_3400():
210+
'''
211+
Check fix for #3400 - link destination rectangles when source/destination
212+
pages have different rotations.
213+
'''
214+
width = 750
215+
height = 1110
216+
circle_middle_point = pymupdf.Point(height / 4, width / 4)
217+
print(f'{circle_middle_point=}')
218+
with pymupdf.open() as doc:
219+
220+
page = doc.new_page(width=width, height=height)
221+
page.set_rotation(270)
222+
# draw a circle at the middle point to facilitate debugging
223+
page.draw_circle(circle_middle_point, color=(0, 0, 1), radius=5, width=2)
224+
225+
for i in range(10):
226+
for j in range(10):
227+
x = i/10 * width
228+
y = j/10 * height
229+
page.draw_circle(pymupdf.Point(x, y), color=(0,0,0), radius=0.2, width=0.1)
230+
page.insert_htmlbox(pymupdf.Rect(x, y, x+width/10, y+height/20), f'<small><small><small><small>({x=:.1f},{y=:.1f})</small></small></small></small>', )
231+
232+
# rotate the middle point by the page rotation for the new toc entry
233+
toc_link_coords = circle_middle_point
234+
print(f'{toc_link_coords=}')
235+
236+
toc = [
237+
(
238+
1,
239+
"Link to circle",
240+
1,
241+
{
242+
"kind": pymupdf.LINK_GOTO,
243+
"page": 1,
244+
"to": toc_link_coords,
245+
"from": pymupdf.Rect(0, 0, height / 4, width / 4),
246+
},
247+
)
248+
]
249+
doc.set_toc(toc, 0) # set the toc
250+
251+
page = doc.new_page(width=200, height=300)
252+
from_rect = pymupdf.Rect(10, 10, 100, 50)
253+
page.insert_htmlbox(from_rect, 'link')
254+
link = dict()
255+
link['from'] = from_rect
256+
link['kind'] = pymupdf.LINK_GOTO
257+
link['to'] = toc_link_coords
258+
link['page'] = 0
259+
page.insert_link(link)
260+
261+
path = os.path.normpath(f'{__file__}/../../tests/test_3400.pdf')
262+
doc.save(path)
263+
print(f'Saved to {path=}.')
264+
265+
links_expected = [
266+
(1, {'kind': 1, 'xref': 1120, 'from': pymupdf.Rect(10.0, 10.0, 100.0, 50.0), 'page': 0, 'to': pymupdf.Point(187.5, 472.5), 'zoom': 0.0, 'id': 'jorj-L0'})
267+
]
268+
269+
links_actual = list()
270+
for page_i, page in enumerate(doc):
271+
links = page.get_links()
272+
for link_i, link in enumerate(links):
273+
print(f'({page_i}, {link!r})')
274+
links_actual.append( (page_i, link) )
275+
276+
assert links_actual == links_expected

0 commit comments

Comments
 (0)