I’m creating a basic PDF viewer in Vue.js 2 (since the customer does not want to migrate to Vue 3) using PDF.js (latest version of pdfjs-dist).
The loading and rendering of pages is working perfectly fine.
I am now trying to create a search functionality from scratch, since the latest version of PDF.js does no longer include the PDFViewerApplication and find controllers etc.
For the purpose of being able to highlight matched words, I was thinking of creating the text layers on top of the PDF’s canvas. Retrieving the text contents is alright, but the problem here is that the calculation of the desired position for the text is not correct.
I’ve tried using the transform properties from the text items and doing some calculations with it, I’ve tried scaling, doing some custom calculations but it just doesn’t work out.
My 3 latest attempts were the following (by the way I have created a repo in which the problem is reproduced, you can find it here: https://gitlab.com/dante.cassiman/pdf-viewer-bug-reproduction)
1.
span.style.left =`${item.transform[4] * scale}px`;
span.style.top = `${(viewport.height - item.transform[5]) * scale}px`;
span.style.transform = `rotate(${item.transform[0]}deg)`
span.style.fontSize = `${item.height * scale}px`;
span.style.left =${item.transform[4] * scale}px;
span.style.top = ${(viewport.height - item.transform[5]) * scale}px;
span.style.transform = rotate(${item.transform[0]}deg)
span.style.fontSize = ${item.height * scale}px;
span.style.fontFamily = fontMap[item.fontName].loadedName;
const [scaleX, , , scaleY, translateX, translateY] = item.transform;
// calculate left and top positions
const leftPosition = (translateX * scale) + (item.width * scaleX * scale) / scaleX;
const topPosition = (viewport.height - translateY * scale) - (item.height * scaleY * scale) / scaleY;
// set the calculated left and top
span.style.left = `${leftPosition}px`;
span.style.top = `${topPosition}px`;
// calculate font size using the actual item height adjusted by scale
span.style.fontSize = `${item.height * scaleY * (scale / scaleY)}px`; // Scale the font size appropriately
The scale
variable comes from the renderPage function, which calculates it like this:
const scale = canvasEl.offsetWidth / page.getViewport({ scale: 1 }).width * (window.devicePixelRatio || 1);
const viewport = page.getViewport({ scale: scale, rotation: page.rotate });
It’s all a lot more clear in the Gitlab repo, so please feel free to take a look at it. When you run the app, the red text is the what’s rendered in the text layers and has to appear on top of it’s corresponding text item in the canvas, which it does not.
This is a screenshot of my latest attempt in the repo:
I also just noticed that zooming out the page does not change the size of the canvas (which is perfect for me), but it does zoom out / scale the text layers on top…
Some help would be greatly appreciated!
EDIT: It appears that I am making it way harder than it has to be. The following calculations seem to be working perfectly fine, the only problem now is that the text seems to be rendered bottom to top (if that makes sense) instead of top to bottom. So I assume that I need some sort of transform or inversion of Y coordinate now?
Also, the zooming problem in the browser still persists.
span.style.left = `${item.transform[4] * scale / 2}px`;
span.style.top = `${(viewport.height - item.transform[5]) * scale / 2}px`;
span.style.fontSize = `${item.height * scale / 2}px`;
0