Skip to content

feat(geometry): viewer-space conversion for citation highlighting - #15

Merged
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/hal-citation-viewport-geometry
Aug 2, 2026
Merged

feat(geometry): viewer-space conversion for citation highlighting#15
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/hal-citation-viewport-geometry

Conversation

@hallelx2

@hallelx2 hallelx2 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Makes the citation-highlight feature buildable: viewer-space coordinates so the frontend can draw an overlay on the rendered page, not just say "page 58".

Stacked on #10 (which is stacked on #9). Merge order: #9#10#11.

The good news first

The geometry was already correct. Verified rather than assumed: every coordinate the package reports — Char, Word, Line, Rect, Table.BBox, Table.CellsBBox — is already normalised. page.go translates the MediaBox origin to (0,0) and applies the /Rotate matrices, so the space matches Page.Width() x Page.Height() exactly. Rotated pages and non-zero-origin MediaBoxes are already handled, and no per-page fixups are needed.

Proved end-to-end: rendered 3M 2018 10-K page 58 at 150 DPI, projected real CellsBBox values onto the image, and the highlights land exactly on the intended rows.

What was actually missing

The contract was undocumented and untested. Nothing stopped a future change to coordinate handling from silently breaking an external consumer — the frontend would just start drawing boxes in the wrong place, with no test going red.

And there is one conversion every consumer must make and can easily invert: PDF user space is origin bottom-left, Y up; every web target (CSS, canvas, SVG, PDF.js) is origin top-left, Y down. Getting it backwards mirrors the overlay vertically — a failure that still looks plausible, because the boxes land on real rows, just the wrong ones. Better to do it once, here, with a test.

API

type ViewRect struct { Left, Top, Width, Height float64 }

func (b BBox) Viewport(pageHeight, scale float64) ViewRect
func (b BBox) Normalized(pageWidth, pageHeight float64) ViewRect

ViewRect is a distinct type from BBox on purpose — the two coordinate systems should not be mixable at a call site.

Viewport for a page rasterised at known DPI (scale = dpi/72) or a PDF.js getViewport({scale}). Normalized returns fractions of the page, which is what a resizable viewer wants: percentages stay correct at any zoom or container width, so the overlay survives a re-render without recomputation.

Tests

  • Y flip pinned in both directions (a box near the page top must yield a near-zero Top).
  • Scale factor, using the real coordinates of the "Less: Accumulated depreciation" row.
  • The two APIs agree at several scales, so they cannot drift apart.
  • Degenerate page returns the zero ViewRect rather than emitting NaN into a JSON payload.
  • End-to-end pass over every word in a real fixture: every rect lands inside the page, right way up, non-degenerate.

go build, go vet, go test ./... -count=1 -race all green.

Frontend usage

r := table.CellsBBox[row][col].Normalized(page.Width(), page.Height())
// JSON {left, top, width, height} as fractions of the page;
// multiply by the rendered canvas size at paint time.

Granularity available today: whole table (Table.BBox), cell (Table.CellsBBox[i][j]), word (Page.Words), glyph (Page.Chars).

Follow-up

A citation usually spans several words across one or more lines. A helper that merges a run of word bboxes into the minimal set of line rectangles — rather than one union box that swallows the whole paragraph — would give a tighter highlight. Not needed for v1; noted on HAL-547.

Closes HAL-547

Summary by Sourcery

Introduce viewer-space rectangle support to enable accurate citation highlighting overlays based on PDF geometry.

New Features:

  • Add ViewRect type to represent rectangles in viewer coordinates for frontend overlays.
  • Provide BBox.Viewport helper to convert PDF user-space rectangles into scaled viewer coordinates for rendered pages.
  • Provide BBox.Normalized helper to convert PDF user-space rectangles into resolution-independent viewer coordinate fractions.

Tests:

  • Add unit tests verifying Y-axis flipping behavior and scaling correctness of Viewport.
  • Add tests ensuring Normalized produces resolution-independent coordinates and handles degenerate page dimensions safely.
  • Add end-to-end tests on real document fixtures to confirm converted rectangles are within page bounds and non-degenerate.

Adds BBox.Viewport and BBox.Normalized, plus a ViewRect type, so a
frontend can draw a highlight over a rendered page instead of only
naming the page a citation came from.

The geometry was already correct and complete -- every coordinate the
package reports is normalised, with the MediaBox origin translated to
(0,0) and any /Rotate applied, so it matches Page.Width() x Page.Height()
exactly. What was missing is that this contract was undocumented and
untested, so nothing stopped a future change from breaking an external
consumer silently.

The one conversion a caller still has to make is the Y flip: PDF user
space is origin bottom-left with Y up, every web rendering target is
origin top-left with Y down. Getting that backwards mirrors the overlay
vertically, which is a failure mode that still looks plausible -- boxes
land on real rows, just the wrong ones. Doing it once here, tested, is
better than each consumer rediscovering it.

ViewRect is a distinct type from BBox rather than a second set of fields
so the two coordinate systems cannot be mixed up at a call site.

Normalized returns fractions of the page for resizable viewers, and the
tests assert the two APIs agree at several scales so they cannot drift.
Verified end-to-end by rendering 3M 2018 10-K page 58, projecting real
cell bboxes through Viewport, and confirming the boxes land on the
intended rows.
@sourcery-ai

sourcery-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a viewer-space rectangle type and conversion helpers so citation highlight coordinates can be consumed safely by web frontends, and backs the geometry contract with focused unit and end-to-end tests.

File-Level Changes

Change Details Files
Introduce a viewer-coordinate rectangle type and conversion methods from PDF user-space bounding boxes, including a normalized, resolution-independent form.
  • Define a ViewRect struct representing top-left–origin, Y-down rectangles via left/top/width/height.
  • Add BBox.Viewport(pageHeight, scale) to convert PDF user-space boxes into scaled viewer coordinates with a Y-axis flip.
  • Add BBox.Normalized(pageWidth, pageHeight) to express viewer rectangles as 0..1 fractions of page dimensions, guarding against non-positive page sizes.
geometry.go
Add unit tests that pin the coordinate conversion contract and verify correctness against real PDF fixtures.
  • Add tests for Y-axis flipping behavior at page top and bottom and for viewport scaling using known real-world coordinates.
  • Verify Normalized output is resolution-independent and consistent with Viewport across multiple scales, and that degenerate pages return a zero ViewRect instead of NaN.
  • Add an end-to-end test over words in a real PDF fixture to ensure all converted rectangles are inside the page, non-degenerate, and normalized to 0..1.
viewport_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@hallelx2, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b12f54cf-9f13-490b-bc3d-12b8df67883f

📥 Commits

Reviewing files that changed from the base of the PR and between c69feb3 and f181bd6.

📒 Files selected for processing (2)
  • geometry.go
  • viewport_test.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Consider guarding Viewport and Normalized against obviously invalid inputs (e.g., negative page dimensions or scale) to fail fast instead of silently producing inverted or nonsensical viewer rectangles.
  • You might want to offer convenience helpers that take a Page (or its width/height) directly for ViewRect conversion to reduce repetitive parameter passing and minimize call‑site mistakes with mismatched dimensions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider guarding Viewport and Normalized against obviously invalid inputs (e.g., negative page dimensions or scale) to fail fast instead of silently producing inverted or nonsensical viewer rectangles.
- You might want to offer convenience helpers that take a Page (or its width/height) directly for ViewRect conversion to reduce repetitive parameter passing and minimize call‑site mistakes with mismatched dimensions.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@hallelx2
hallelx2 merged commit a5482af into main Aug 2, 2026
5 checks passed
@hallelx2
hallelx2 deleted the halleluyaholudele/hal-citation-viewport-geometry branch August 2, 2026 09:15
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.

1 participant