Skip to content

Add citation/reference hover preview#5611

Open
koppor wants to merge 11 commits intosumatrapdfreader:masterfrom
koppor:add-reference-hovering
Open

Add citation/reference hover preview#5611
koppor wants to merge 11 commits intosumatrapdfreader:masterfrom
koppor:add-reference-hovering

Conversation

@koppor
Copy link
Copy Markdown

@koppor koppor commented May 6, 2026

Unfortunately it's too complicated. I don't see us implementing that.

#128 (comment)

11 years later, Claude can help.

This update is entirely written by claude - "only" driven by me in a code-and-fix mannger

Screenshot:

grafik

Summary

Hovering an internal-document link (citation, figure reference, footnote marker, etc.) shows a small popup rendering the destination region of the target page — so a [1] citation, a "Figure 2.3" reference, or a footnote can be inspected without leaving the current page. Inspired by PDFRefPreview.

Fixes #128
Fixes #4221

Demo

Hover any in-text reference for ~300 ms. The popup shows just the relevant entry / figure / footnote, cropped to its actual text box. Works for both 1-column and 2-column layouts, hanging-indent and numeric [N] bibliographies, and figure references (where the strip falls back to a generous region around the anchor).

Implementation

Detection (src/Canvas.cpp + src/RefHover.cpp):

  • IsCitationLink accepts any internal kindPageElementDest link.
  • DetectEntryBox does a per-glyph text+coords scan starting at destY, with several boundary signals: [N at the entry's first-line X, indent change back to that X, an X that matches neither first-line nor continuation X, vertical paragraph break, single-line-entry pattern, and column wrap.
  • Falls back to a fixed strip when text scan finds nothing near destY, and to a taller strip when the detected box is suspiciously small (figure / diagram fragment).
  • The detected region is rendered via engine->RenderPage and blitted into a popup, letterboxed to fit max bounds.

Engine API change (would appreciate a quick eyeball on this part):

  • New virtual IPageDestination::GetDestPoint2() in src/EngineBase.h, default returns GetRect2() (so existing implementations are unaffected).
  • New helper PageDestGetDestPoint().
  • PageDestinationMupdf (in src/EngineMupdf.cpp) now stores destX/destY resolved via fz_resolve_link at construction; previously the resolved (x, y) from ResolveLink was discarded after the page number was extracted.

Plumbing:

  • New files src/RefHover.cpp / src/RefHover.h.
  • MainWindow gets a refHover member, destroyed in dtor.
  • Canvas::OnMouseMove schedules / hides the popup; OnTimer renders.
  • Both vs2022/*.vcxproj and *.vcxproj.filters updated.

Setting: EnableCitationHover advanced setting (default true, version 3.7), defined in cmd/gen-settings.ts. Generated files updated.

Test plan

  • bun ./cmd/build.ts clean — 0 warnings, 0 errors.
  • 2-column academic paper, numeric [N] citations: tight popup of the right column entry only.
  • 1-column paper, author-year hanging-indent bibliography (Bischoff and Küchlin, 2017 → bib entry Daniel Bischoff and Wolfgang Küchlin. Adapting…): single entry, correctly cropped.
  • Last bibliography entry (no [N] follows): stops before author bios / footers thanks to the indent-change signal.
  • Footnote markers (single-line stacked footnotes ¹url \n ²url \n …): just the hovered footnote.
  • Figure / table reference (Figure 2.2): falls back to a generous strip around the anchor so the diagram is visible.
  • Toggle EnableCitationHover to false: existing popup is hidden, no further popups scheduled.

Notes

  • Test PDF used during development: tests-manual/extract-references/paper1/main.pdf from the JabRef repo. Not included in this PR.
  • The RefHover.cpp text-scan boundary heuristics are defensive (multiple signals), but they're heuristics — happy to iterate based on PDFs that don't crop cleanly.

🤖 Generated with Claude Code

Hovering an internal-document link (citation, figure, footnote, etc.)
now shows a small popup that renders the destination region of the
target page, so the bibliography entry / figure / footnote is visible
without leaving the current page. Inspired by PDFRefPreview.

Detection:
- IsCitationLink: any internal kindPageElementDest link.
- DetectEntryBox: per-glyph text+coords scan from the destination
  anchor, ending at one of: "[N" at the entry's first-line X, indent
  change back to that X, an X that matches neither first-line nor
  continuation X, vertical paragraph break, single-line-entry pattern,
  or column wrap.
- Falls back to a fixed strip when text-based detection finds nothing
  near destY, and to a taller strip when the detected box is too small
  (figure / diagram fragment).
- Result is rendered via engine->RenderPage and blitted into a yellow
  popup, letterboxed to fit max bounds.

Engine API:
- IPageDestination::GetDestPoint2() (default returns GetRect2()).
- PageDestGetDestPoint() helper.
- PageDestinationMupdf caches resolved (destX, destY) from
  fz_resolve_link at construction.

Plumbing:
- New RefHover.cpp/.h.
- MainWindow gets a refHover member, destroyed in dtor.
- Canvas OnMouseMove schedules / hides the popup; OnTimer renders.

Configurable via the EnableCitationHover advanced setting (default
true).

fixes sumatrapdfreader#128
fixes sumatrapdfreader#4221

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@GitHubRulesOK
Copy link
Copy Markdown
Collaborator

Looks very usefull do you have a compiled exe for me to play with while testing some other files

@koppor
Copy link
Copy Markdown
Author

koppor commented May 6, 2026

Looks very usefull

Thank you! I was searching for that feature for a long time - but none of the existing apps convinced me. Sioyek kind of looked nice, but it has other issues (e.g., ahrm/sioyek#1350)

do you have a compiled exe for me to play with while testing some other files

Sure! I put it at https://builds.jabref.org/main/SumatraPDF-dll.exe .

@GitHubRulesOK
Copy link
Copy Markdown
Collaborator

GitHubRulesOK commented May 6, 2026

Thanks so not your fault the first file I opened is another new oddity where fonts are big and thus window content is limited which begs the question what do other apps have such as is there a zoomin out function
image
image

Initial render uses page zoom so popup text height matches visible page
text. Wheel on the citation link re-renders a region sized to fill the
popup at the new zoom (anchored at detection top-left, clamped to page).
Wheeling out brings in new page content; wheeling in crops to detail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@koppor
Copy link
Copy Markdown
Author

koppor commented May 7, 2026

Good question — I just added mouse-wheel zoom for the popup. Hover a citation link to bring up the popup, then scroll the wheel while still hovering the link to zoom the preview in/out:

  • Wheel down (zoom out): the popup re-renders a larger region of the page, so previously-empty space fills with new content from below/right of the detected entry. Useful when the destination has large fonts or when the auto-detected region was too small.
  • Wheel up (zoom in): re-renders a smaller region at higher detail, cropping the trailing whitespace.

The popup window keeps its initial size; only the rendered content changes. The initial zoom now matches the document's current page zoom, so popup text height is comparable to the visible page text.

New build: https://builds.jabref.org/main/SumatraPDF-dll-2.exe

@GitHubRulesOK
Copy link
Copy Markdown
Collaborator

GitHubRulesOK commented May 7, 2026

@koppor Fantastic improvement though took me a while to grasp the concept of zoom the source box but once tried out it makes sense as an alternative to try to zoom the quick viewbox (I have had issues with that when attempting a magnifier box as mouse focus is then relocated). The last core issue I see is the "window" is highly varied depending on sources own desired target. Take this silly example, where the target is a wide set of goto with a topbar target. It would be better if the quickview is a fixed ratio.
image

koppor and others added 2 commits May 7, 2026 16:07
Reviewer noted that internal links pointing to non-bibliography destinations
(TOC entries, topbar goto targets, page anchors) produced ugly wide-thin
popups because the entry-detection fallback returned a strip that ran from
destX to the right page margin. Replace those fallback paths with a
fixed-pt box anchored on the destination so every non-reference target
gets the same popup shape. Bibliography entries that the detector can
identify keep their existing (content-shaped) box.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@koppor
Copy link
Copy Markdown
Author

koppor commented May 7, 2026

@GitHubRulesOK Good catch — pushed 77b017f to address that.

Now the popup shape depends on what the link actually points to:

  • Reference (bibliography entry): detection finds a [N]/author-year-shaped block — popup is sized to that block (as before).
  • Non-reference (TOC, topbar, generic goto): detection's text scan doesn't find a bibliography-shaped entry — popup falls back to a fixed-ratio box (≈ 360×260 pt) anchored on the destination, regardless of what's nearby.

So your wide-thin "topbar goto" example now gets the same compact box as any other non-reference target, rather than stretching to the right page margin.

Build with this fix: https://builds.jabref.org/main/SumatraPDF-dll-3.exe

@GitHubRulesOK
Copy link
Copy Markdown
Collaborator

@koppor I am not trying to be a killjoy this IS a good improvement
image
but it may just be that the examples I have to hand are wildly variable so in the case shown below the target is top of table but user would expect a box much higher than the target zone more like a landscape view of page width or half page width for 2 column content. I understand it cannot always be known what users desire but the qwickview overlay could be as wide as the current viewport page width and half the current source page height as it is temporary.
image

koppor and others added 2 commits May 8, 2026 10:27
The atIndentX check used a ±5pt tolerance to decide whether a new line
was still part of the current bibliography entry. That's too tight when
a continuation line switches from roman to italic (or vice versa) — the
per-glyph bbox left edge shifts with the new side-bearings, even though
both lines actually start at the same pen position. The result was that
3+ line entries with italic continuation (e.g. titles followed by an
italicized journal/conference name on a new line) got truncated at the
italic line's first glyph, with only the 6pt of padding leaking the top
of that line into the popup. Bumping tolerance to ±25pt covers any
realistic side-bearing variance while still catching truly external
content (footers, author bios) which sit at a meaningfully different X.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removed the rule that terminated entry detection when a continuation
line's first glyph X drifted from the captured indentX. Even with the
recently bumped tolerance, font-metric variance on continuation lines
(especially when one continuation line starts in italic and another
in roman/digit) could still trigger premature termination, cutting off
the last line of long bibliography entries.

The remaining rules already cover all the realistic next-entry signals:
- "[N" at firstLineLeftX (numeric bibs)
- new line back at firstLineLeftX after a continuation (hanging-indent)
- vertical paragraph break
- single-line-entry case

If a future PDF has external content following a bibliography entry
without any of those signals, we'd over-include — but that's vanishingly
rare and clearly preferable to silently truncating real entries.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@koppor
Copy link
Copy Markdown
Author

koppor commented May 8, 2026

@koppor I am not trying to be a killjoy

The thing is that with the AI helper, it is much easier to adress user comments and wishes.

I only hope that the code style is good enough. 😅

koppor and others added 2 commits May 8, 2026 11:33
Reviewer noted that the small fixed-ratio popup (360×260pt) used for
non-reference targets often shows too little context — e.g. a 'Table N'
link surfaces only the caption text, not the table itself; a section-
heading link shows only the heading; an image-only PDF link shows a
narrow patch of art.

Replace it with a landscape view: full page width × half page height,
anchored at the destination. Capped by kMaxPopupWidth/Height (the latter
already enforced; this commit also wires the width cap into the auto-
fit). Also recognise single-line detected entries with no continuation
indent as ambiguous (likely caption / heading rather than a real
bibliography entry) and route them through the same landscape view.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related fixes:

1. Relax rule (a): a "[" at firstLineLeftX is now treated as a next-entry
   marker regardless of what follows. Previously it required a digit
   ("[1]", "[2]"), which missed alphanumeric description-list styles
   like "[Nyg11]", "[Foo+09]", "[KAZ18]".

2. The "single-line + no indent → landscape view" heuristic was firing on
   single-line description-list bibliography entries, treating them as
   non-references and replacing the fitted entry box with a half-page
   landscape slice (which then included the next several entries below).
   Skip that redirect when the detected entry starts with "[" — that's a
   strong bibliography signal even on a one-line entry.

The destY-at-page-top case (where the citation link's destination has no
specific Y, just page-level) still falls back to the landscape view —
that one needs source-text extraction on the source page to identify the
citation key, which is a larger change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@GitHubRulesOK
Copy link
Copy Markdown
Collaborator

GitHubRulesOK commented May 8, 2026

@kjk can the PR be adjusted to your expectation of common user desires my test with others suggest they use a fixed window size so click figure1 will show a large area around that goto
image
image
image

Reshape the citation-hover popup so it picks the right region for non-
bibliography destinations:

- "Figure / Table / Listing / Algorithm N.M" caption anywhere below the
  detected box: the destination is figure / table / listing body. Show
  landscape view so the popup includes the caption. Catches code/console
  listings whose lines start with "[TAG]" (linter output, etc.) that
  would otherwise short-circuit through the bracket-bib check.
- Code-listing detector via brace/semicolon/paren density: figures whose
  body is example code route to landscape view.
- Numbered / labeled headings ("6.2 Foo", "Figure 2.2 ...", "Section 7"):
  routed to landscape so the popup includes the heading + content below.
- Tabular indent (continuation X far right of firstLineLeftX): routed to
  landscape to show the whole table.

Description-list bibliography improvements:

- Track a descListSibling flag on rule (a) "[" at firstLineLeftX, rule
  (b) indent-change-back, rule (c) vertical paragraph break with the
  next glyph at firstLineLeftX, and rule (d) single-line case. When set,
  the post-loop returns the fitted box even for single-line entries with
  no continuation indent — fixes abbreviation-list hovers (JVM, AKM,
  ADR, OMT, ...) where blank lines separate sibling entries.
- Recover startIdx via leftmost-on-line walk after the y-band scan: a
  poorly-authored link destX can land startIdx mid-line on hanging-
  indent layouts, dropping the "[KOS06]" / "Philippe Kruchten" leading
  portion from the popup. Walking to the line's actual leftmost glyph
  recovers it.
- Trim trailing blank page margin in LandscapeBox so the popup ends just
  below the last text glyph instead of including the full page footer.
- Cap popup height by monitor work-area (~90%, max 1400px) and clamp
  popup position to the work-area edges so it doesn't get clipped at
  the top/left when the cursor is near a screen corner.

Resolve page-level link destinations using the source link's text:

- PageDestGetDestPoint returns {0,0,0,0} when the link has no specific
  anchor — common for body-text abbreviation / glossary links. Plumb
  the source page + source rect through RefHoverSchedule, then on
  destY <= 0 extract the longest alphanumeric run from the source rect
  (preferring "(XYZ)"-flanked tokens over bracketed citation keys) and
  search the destination page for it. The matching glyph's Y becomes
  destY, so DetectEntryBox can crop to the matching entry. Hovering
  "(AKM)" body text now yields just the AKM entry, not the whole
  abbreviations page.

Re-render guard tightened: the popup short-circuit now also compares
destX/destY, so two adjacent links that share a destination page but
land at different positions (e.g. "Section 7" link + a bib ref both on
page 41) each render their own content rather than reuse a stale popup.

A manual-test checklist comment at the top of RefHover.cpp documents the
case classes covered and the remaining known limits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@koppor
Copy link
Copy Markdown
Author

koppor commented May 8, 2026

Pushed 5915fb57f. Test build: https://builds.jabref.org/main/SumatraPDF-dll-4.exe

Iterated through several non-bibliography hover targets and edge cases in description-list bibliographies. The detector now handles:

Figures / tables / code listings / headings

  • Figure / Table / Listing / Algorithm N.M caption anywhere below the detected entry box → landscape view, popup includes the caption. Catches code listings whose lines start with [TAG] (e.g. linter output) that would otherwise look like a description-list bib.
  • High brace/semicolon/paren density inside the entry box → code-listing destination → landscape view (caption follows below).
  • Numbered (6.2 Foo) or label-prefixed (Figure 2.2, Section 7) entries → landscape view so the heading + content below land in the popup.

Description-list bibliographies and abbreviation lists

  • descListSibling flag set on rule (a) [-at-firstLineLeftX, rule (b) indent-change-back, rule (c) vertical paragraph break with the next glyph at firstLineLeftX, and rule (d) single-line case. Fixes single-line abbreviation entries (JVM, AKM, ADR, OMT, ...) where blank lines separate sibling entries — rule (c) used to fire first and route to landscape view.
  • Walk startIdx to the line's leftmost glyph after the y-band scan. PDF link destX is unreliable (often carries the source-page X), which would land startIdx mid-line on hanging-indent layouts and drop the leading [KOS06] / "Philippe Kruchten" portion from the popup.

Page-level link destinations

  • PageDestGetDestPoint returns {0,0,0,0} when the link has no specific anchor — common for body-text abbreviation links. Plumb the source page + source rect through RefHoverSchedule; on destY <= 0 extract the longest alphanumeric run from the source rect (preferring (XYZ)-flanked tokens over [citationKey]-style tokens) and search the destination page for it. The matched glyph's Y becomes destY. Hovering (AKM) body text now crops to just the AKM entry instead of showing the whole abbreviations page.

Popup positioning / re-render

  • Cap popup height by monitor work-area (~90%, max 1400px); clamp position to work-area edges so the popup isn't clipped at the top/left when the cursor is near a screen corner.
  • Re-render guard now also compares destX/destY: two adjacent links sharing a destination page but landing at different positions each render their own content (no stale popup).
  • LandscapeBox trims trailing blank page margin so the popup ends just below the last text glyph instead of including the page footer.

Top-of-file checklist comment in src/RefHover.cpp documents the case classes covered and remaining known limits (link destinations whose source rect doesn't isolate a unique key still fall back to the full-page landscape view).

@koppor
Copy link
Copy Markdown
Author

koppor commented May 8, 2026

The new build is available at https://builds.jabref.org/main/SumatraPDF-dll-4.exe

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.

Can we have a snapshot feature like Skim in MacOS Popup preview of PDF links

2 participants