Skip to content

fix(links): attach a link to every span its rect covers (#42) - #59

Open
Anai-Guo wants to merge 1 commit into
datalab-to:masterfrom
Anai-Guo:fix/link-spans-whole-anchor
Open

fix(links): attach a link to every span its rect covers (#42)#59
Anai-Guo wants to merge 1 commit into
datalab-to:masterfrom
Anai-Guo:fix/link-spans-whole-anchor

Conversation

@Anai-Guo

@Anai-Guo Anai-Guo commented Aug 9, 2026

Copy link
Copy Markdown

Fixes #42.

Problem

merge_links maps each link annotation onto exactly one span:

max_intersection = intersection_link.argmax()
...
span_link_map.setdefault(max_intersection, [])
span_link_map[max_intersection].append(link)

A linked phrase is rarely a single span, though — spans get split whenever the font
changes and at the zero-width spacers pdfium emits between words. So for a link over
"Claude 3.7 Sonnet", only the span with the largest overlap keeps the url and every
other word of the same anchor comes back with "url": "", which is what #42 reports.

Fix

Attach the link to every span it substantially covers, normalizing the overlap by
min(span_area, link_area). That keeps both directions:

  • a short span lying entirely inside the link (Sonnet) → ratio 1.0, matched;
  • a long span that swallows the whole link (only one word of a sentence is linked)
    → ratio 1.0, matched, and _reconstruct_spans then splits it char by char, exactly
    as before;
  • the sliver of the neighbouring line a link rect often reaches into → small ratio,
    ignored, so links don't bleed across lines.

The span with the largest overlap is always kept, so nothing that resolves today
stops resolving. _reconstruct_spans is unchanged and still decides per character,
so a partially covered span is split rather than tagged wholesale.

Verification

Reproducer — one link annotation over two runs in different fonts:

page.insert_text((left, baseline), "Claude ", fontname="helv", fontsize=18)
page.insert_text((left + w1, baseline), "Sonnet", fontname="hebo", fontsize=18)
page.insert_link({"kind": fitz.LINK_URI, "uri": url,
                  "from": fitz.Rect(left, baseline - 18, left + w1 + w2, baseline + 4)})

before:

span 'Claude ' url='https://example.com/anchor'
span 'Sonnet'  url=''

after:

span 'Claude ' url='https://example.com/anchor'
span 'Sonnet'  url='https://example.com/anchor'

test_link_spans_whole_anchor covers this and also asserts the unlinked line right
below stays unlinked. It fails on master (assert 'Claude' == 'Claude Sonnet') and
passes with the change. Full suite: 43 passed before, 44 passed after.

🤖 Generated with Claude Code

A linked phrase is split into several spans whenever the font changes,
but merge_links only kept the span with the largest overlap, so all the
other words of the anchor came back with an empty url.

Fixes datalab-to#42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: incomplete link extraction

1 participant