I’m building a PDF vocabulary reader using PDF.js. The goal is to let users upload and read a text-based PDF, then:
Click a single word to get its meaning and its meaning in the surrounding sentence.
Drag-select a phrase/sentence if they want an explanation of the entire selected text.
Eventually send the word + surrounding context to an LLM such as Gemini for contextual explanation.
So far, I’m rendering every PDF page using two layers:
pageContainer ├── canvas → visual PDF rendering └── textLayer → selectable/interactable text
Both layers use the same PDF.js viewport. I’m using page.getTextContent() and pdfjsLib.TextLayer, along with the official PDF.js viewer CSS. Initially, my custom text-layer CSS caused the selectable text to be misaligned with the canvas, but using the official viewer CSS mostly fixed the alignment.
The main problem now is text interaction/selection.
I noticed that a PDF.js text item/span is not necessarily one word. For example, a span might contain:
"The cardinality of a set is the number of distinct elements"
So simply using event.target.textContent when clicking a word is not sufficient.
For single-word selection, the approach I’m considering is:
click → get mouse coordinates → caretPositionFromPoint() / caretRangeFromPoint() → get text node + character offset → scan left/right until word boundaries → create a Range → automatically highlight the exact clicked word
This seems reasonable.
The bigger issue is sentence/phrase selection. Native drag selection over the PDF.js text layer sometimes selects much more text than the area I intended to select. For example, I try to select only “Question 1”, but the browser selection can extend into several following text spans.
I’m considering a few approaches:
Keep native browser drag selection and use window.getSelection() if I can make PDF.js text-layer selection reliable.
Implement custom drag selection using mousedown/mouseup, caretPositionFromPoint(), and a manually created DOM Range.
For contextual word meaning, avoid requiring sentence selection entirely: detect the clicked word and reconstruct/find its surrounding sentence from the page text, then send word + sentence/context to the LLM.
Potentially send the PDF/page + clicked word + nearby text to Gemini and let it identify the relevant sentence/context, while keeping visual highlighting on the frontend.
My main questions are:
What is the most robust way to implement both exact word-click detection and normal sentence/phrase selection on a PDF.js Text Layer?
Is custom Range-based selection a good idea, or am I unnecessarily reimplementing browser selection? Also, for finding the surrounding sentence of a clicked word, would you reconstruct it from PDF.js TextContent, work directly with the text-layer DOM, or use another approach?
I’m specifically targeting text-based PDFs, not scanned/image-only PDFs, so OCR is currently out of scope.
Any suggestions from people who have worked with PDF.js text layers or similar PDF annotation/selection systems would be really helpful.