Hello everyone,
I’m using Apryse WebViewer version 11.3.0 (paid license) and I’m facing a couple of issues while loading hotspots as custom annotations in a PDF.
1) Incorrect positioning of hotspots (coordinates)
My hotspots are created based on a list of stamp-like data, and one of the parameters provided is the rect (coordinates).
However, when I try to position the annotations using the x and y values derived from this rect, the hotspots are not rendered in the exact expected location.
To partially mitigate this, I had to apply manual offsets, which work for most positions but still fail in some cases.
Below are some screenshots that illustrate the issue.
Position correct (with offset)
Stamp string recive:
<stamp flags=\"readonly\" page=\"4\" rect=\"849.5280000000001,409.8701144999999,910.0080000000002,349.40431050000007\" name=\"5151e21b-ac57-40b3-b8c2-05fce0d82505\" title=\"FTD\" creationdate=\"D:20200107202417+00'00'\" date=\"D:20200107202417+00'00'\" subject=\"videoLink\" icon=\"\\u25B6\" data-custom=\"1a3682f0-07d5-11ea-a237-b1a72960d2ef\" />
Position incorrect (with offset)
Stamp string recive:
<stamp flags=\"readonly\" page=\"11\" rect=\"166.3199999999999,403.73130000000003,678.2399999999999,103.8324\" name=\"78335bab-e0ee-444d-8ccb-305bc8e99bb3\" title=\"FTD\" creationdate=\"D:20200107202417+00'00'\" date=\"D:20200107202417+00'00'\" subject=\"imageLink\" icon=\"\\u26F6\" data-custom=\"/image/12_2.jpg\" />
2) Custom click handling vs default popup
These hotspots require full control over click behavior.
Currently, on click, I open a new tab and apply some custom business logic.
The problem is that after clicking the hotspot, a default popup/tooltip is displayed, and I would like to hide this popup entirely.
I’m aware of the annot.Listable property, but when I set it to false, the click mapping stops working, which prevents me from handling the custom click logic.
Is there a way to hide the default popup/tooltip while still preserving custom click handling for the annotation?
Popup after click
Below, I’m listing the main methods I’m using to:
-
Parse and calculate hotspot positions
-
Render the hotspots
-
Handle custom click events
Load hotspots method
private loadHotspots = (hotspots: HotspotType[]) => {
if (!this.viewerInstance) return;
const { Annotations, annotationManager } = this.viewerInstance.Core;
hotspots.forEach((hotspot) => {
const parsed = this.parseHotspotStamp(hotspot.value);
if (!parsed) return;
const { pageNumber, rect } = parsed;
const position = this.getHotspotPosition(pageNumber, rect, 100);
if (!position) return;
const annot = new Annotations.CustomAnnotation('hotspot');
annot.PageNumber = pageNumber;
annot.X = position.x;
annot.Y = position.y;
annot.Width = position.width;
annot.Height = position.height;
annot.ReadOnly = true;
annot.Locked = true;
annot.NoMove = true;
annot.NoResize = true;
annot.NoRotate = true;
annot.Subject = 'HOTSPOT';
annot.setCustomData('hotspot', 'true');
annot.setCustomData('type', hotspot.metadata.type);
annot.setCustomData('data_custom', hotspot.metadata.data_custom);
annot.setCustomData('title', hotspot.metadata.title);
annot.setCustomData('icon', hotspot.metadata.icon);
annotationManager.addAnnotation(annot);
if (hotspot.metadata.icon) {
const img = new Image();
img.onload = () => {
annot.draw = function (ctx: CanvasRenderingContext2D, pageMatrix: number[]){
ctx.save();
ctx.setTransform(pageMatrix[0], pageMatrix[1], pageMatrix[2], pageMatrix[3], pageMatrix[4], pageMatrix[5]);
ctx.drawImage(img, this.X, this.Y, this.Width, this.Height);
ctx.restore();
};
annotationManager.redrawAnnotation(annot);
};
img.src = hotspot.metadata.icon;
}
});
};
GetPosition hotspots method
private getHotspotPosition(
pageNumber: number,
rect: { x1: number; y1: number; x2: number; y2: number },
size: number,): { x: number; y: number; width: number; height: number } | null {
if (!this.viewerInstance) return null;
const { documentViewer } = this.viewerInstance.Core;
const pdfDoc = documentViewer.getDocument();
if (!pdfDoc) return null;
const pageInfo = pdfDoc.getPageInfo(pageNumber);
if (!pageInfo) return null;
const centerY = (rect.y1 + rect.y2) / 2;
const invertedY = pageInfo.height - centerY;
const HOTSPOT_X_OFFSET = 275;
const HOTSPOT_Y_OFFSET = 130;
return {
x: rect.x1 + HOTSPOT_X_OFFSET,
y: invertedY - size / 2 - HOTSPOT_Y_OFFSET,
width: size,
height: size,
};
}
RegisterHotSpotClick method
private registerHotspotClick() {
if (!this.viewerInstance) return;
const { annotationManager } = this.viewerInstance.Core;
annotationManager.addEventListener('annotationSelected', (annotations: any[], action: string) => {
if (action !== 'selected') return;
const annot = annotations[0];
if (!annot) return;
if (annot.getCustomData?.('hotspot') !== 'true') return;
const contentType = annot.getCustomData('type');
const dataCustom = annot.getCustomData('data_custom');
const title = annot.getCustomData('title');
console.log('Hotspot metadata:', {
type: contentType,
data_custom: dataCustom,
title: title,
url: this.fileUrl,
});
const pdfUrl = this.originalFileUrl || this.fileUrl;
if (this.hotspotHandler && pdfUrl && dataCustom && contentType) {
this.hotspotHandler.handleHotspot(dataCustom, contentType, pdfUrl, this.authValue).catch((error) => {
console.error('[PdfTronService] Erro ao processar hotspot:', error);
});
} else {
console.warn('[PdfTronService] HotspotHandler não disponível ou dados incompletos:', {
hasHandler: !!this.hotspotHandler,
hasOriginalFileUrl: !!this.originalFileUrl,
hasFileUrl: !!this.fileUrl,
hasDataCustom: !!dataCustom,
hasContentType: !!contentType,
});
}
annotationManager.deselectAnnotation(annot);
});
}
Any guidance or best practices would be greatly appreciated.
Thank you for your attention.


