Problems with Custom Annotation Coordinates and Click Behavior in PDFTron

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&apos;00&apos;\" date=\"D:20200107202417+00&apos;00&apos;\" 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&apos;00&apos;\" date=\"D:20200107202417+00&apos;00&apos;\" 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.

Hello, I’m AiRon, an automated tech support bot :robot:
While you wait for one of our customer support representatives, I can help you with your question.


It looks like you’re facing issues with custom annotation coordinates and click behavior in Apryse WebViewer. Let’s break down the issues and explore some potential solutions.

Some things you can try:

  • Coordinate Issue: Ensure that the coordinate system used for your annotations aligns with the PDF coordinate system. PDF coordinates start from the bottom-left, whereas screen coordinates start from the top-left. Make sure to adjust your coordinates accordingly.

  • Manual Offsets: Instead of applying manual offsets, ensure the calculations for coordinates are correct. You may need to convert the PDF coordinates to screen coordinates using the page’s transformation matrix.

  • Custom Click Handling: To prevent the default popup while maintaining custom click handling, you can intercept the click event and stop the propagation. Use the annotationManager to listen for annotation clicks and execute your custom logic.

  • Popup/Tooltip Issue: Disable the default popup by setting the annotation’s NoView property to true. This will prevent it from being displayed, but you need to ensure your custom data handling logic is intact.



Here are some resources that might help you:


Hopefully, this helps you solve your problem while you wait for a human to get back to you.

Hello @kevenjesus,

Thank you for posting on our forum.

Regarding “Incorrect positioning of hotspots”, how are you currently getting the rect that are being applied to these annotations (parseHotspotStamp)? If the source PDF where these stamps were has different dimensions than the destination PDF, there may be a difference in transformation matrices of the page which could be leading to this behavior.

If you have not already, please see our guide regarding coordinates, as there is a different system used by PDF Page, Viewer, and Pages: https://docs.apryse.com/web/guides/coordinates

Along with the information requested above, please feel free to provide the PDF used in your screenshot, along with the images being used for stamps and we can investigate further on our end.

Regarding “hide this popup entirely”, see our guide here for more information on how to hide particular elements, such as the AnnotationPopup in your screenshot: https://docs.apryse.com/web/guides/hiding-elements

Let us know if you need further clarification!

Best Regards,
Jacob Romano Carlsen
Web Development Support Engineer
Apryse Software Inc.