For the complete documentation index, see llms.txt. This page is also available as Markdown.

Render citation highlights in your own viewer

Outcome: clicking a citation scrolls your PDF viewer to the exact sentence and draws a highlight over it.

The locator contract

Each Citation location gives page (1-indexed) and a bbox in fractions of page width and height, origin top-left.

Text
left = bbox.x * renderedWidth top = bbox.y * renderedHeight width = bbox.width * renderedWidth height = bbox.height * renderedHeight

Because the values are fractions, the same numbers work at any zoom and any DPI.

Walkthrough

Take one real citation — the encoder-layers claim from the Transformer paper, page 7:

json
{ "index": 1, "quote": "The encoder is composed of a stack of N = 6 identical layers.", "source": { "type": "document", "id": "doc_8ba2f01c47" }, "locations": [ { "page": 7, "bbox": { "x": 0.252, "y": 0.426, "width": 0.495, "height": 0.041 } } ] }

bbox values are fractions of the page, origin top-left — not pixels, not PDF points. That is deliberate: they stay correct at any zoom, and you never need to know the source page's dimensions.

Positioning the overlay

Multiply each fraction by the rendered page box. Your page element must be position: relative.

TypeScript
type BBox = { x: number; y: number; width: number; height: number }; function highlight(pageEl: HTMLElement, bbox: BBox, index: number) { const { width: pw, height: ph } = pageEl.getBoundingClientRect(); const el = document.createElement("mark"); el.dataset.citationIndex = String(index); Object.assign(el.style, { position: "absolute", left: `${bbox.x * pw}px`, top: `${bbox.y * ph}px`, width: `${bbox.width * pw}px`, height: `${bbox.height * ph}px`, background: "rgba(234, 88, 12, 0.24)", borderRadius: "2px", pointerEvents: "none", }); pageEl.appendChild(el); return el; }

For the citation above on a page rendered 816 × 1056, that lands the box at left: 205.6px, top: 449.9px, width: 403.9px, height: 43.3px.

Scrolling to it

Render first, then scroll — a zero-size element scrolls to the wrong place.

TypeScript
function revealCitation(pageEl: HTMLElement, bbox: BBox, index: number) { const el = highlight(pageEl, bbox, index); requestAnimationFrame(() => { el.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" }); }); }

Two things that will bite you

Re-measure on resize. The fractions do not change; the pixels do. Recompute on ResizeObserver, not on window.resize alone.

A citation can carry several locations. A claim spanning a page break returns one entry per page. Render all of them and scroll to the first:

TypeScript
citation.locations.forEach((loc, i) => { const pageEl = pages[loc.page - 1]; // page is 1-indexed if (i === 0) revealCitation(pageEl, loc.bbox, citation.index); else highlight(pageEl, loc.bbox, citation.index); });

page is 1-indexed

page is 1-indexed Your page array almost certainly is not. Off-by-one here shows the reader a confidently wrong passage, which is worse than showing none.

Handling the hard parts

  • Rotated pages. Check page rotation before mapping coordinates.

  • Multi-box quotes. locations can hold several boxes for one quote — draw all of them.

  • Missing locations. Corpus papers whose PDF we do not hold return a quote but no boxes. Fall back to a text search within the page, then to the paper page.

citation · grounded-answers · documents

Last updated