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.
left = bbox.x * renderedWidth
top = bbox.y * renderedHeight
width = bbox.width * renderedWidth
height = bbox.height * renderedHeightBecause 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:
{
"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.
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.
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:
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.
locationscan 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.
Related
Last updated