Create annotation/marker with custom graphics outside of rendered PDF page

Product: PDFTron WebViewer
Product Version: 8.5.0

Please give a brief summary of your issue:
Create annotation/marker with custom graphics outside of rendered PDF page

Hello!

I have some annotations (e.g. text highlight, or custom drawed boxes) within the PDF document, for which I would like to provide visual markers (e.g. arrows).
I would like to place these arrows outside of the PDF so that it does not block any part of the document. Preferably these arrows are just touching the side of the page.

I have created the below screenshot to give a visual representation of what I would like to achieve. Yellow highlights represents my annotations. I would like to draw some arrows at the same vertical position as the annotation, but outside of the rendered PDF.

Could you please help me out with some suggestions on how to achieve this?

My only guess was using popups, because I can place them outside of the PDF page. But as far as I know I can have only have one instance from them. Unfortunately I need multiple at the same time. Also this solutions feels like a huge hack to me.

Regards,
Matteo

Hi there,

Here is the code to add an arrow to a single annotation and you can extend this code to cover multiple annotations. Basically, we create HTML elements as the arrows, append them inside the viewer element, and update their positions based on the annotation’s position in the scroll view element:

const { annotationManager, documentViewer } = instance.Core;

const getAnnotationPosition = (annotation) => {
  const { left, top, right, bottom } = getAnnotationPageCoordinates(annotation);

  const pageNumber = annotation.getPageNumber();
  const topLeft = convertPageCoordinatesToWindowCoordinates(left, top, pageNumber);
  const bottomRight = convertPageCoordinatesToWindowCoordinates(right, bottom, pageNumber);

  if (annotation['NoZoom']) {
    const isNote = annotation instanceof window.Annotations.StickyAnnotation;
    const rect = annotation.getRect();
    const width = isNote ? window.Annotations.StickyAnnotation['SIZE'] : rect.getWidth();
    const height = isNote ? window.Annotations.StickyAnnotation['SIZE'] : rect.getHeight();
    const rotation = documentViewer.getCompleteRotation(annotation.PageNumber);
    if (rotation === 0) {
      bottomRight.x = topLeft.x + width;
      bottomRight.y = topLeft.y + height;
      if (isNote) {
        bottomRight.x += width * 0.2;
      }
    } else {
      if (isNote) {
        bottomRight.x = topLeft.x + width * 1.2;
        bottomRight.y = topLeft.y + height;
      } else {
        if (rotation === 1) {
          topLeft.x = bottomRight.x - height;
          bottomRight.y = topLeft.y + width;
        } else if (rotation === 2) {
          topLeft.x = bottomRight.x - width;
          topLeft.y = bottomRight.y - height;
        } else if (rotation === 3) {
          topLeft.y = bottomRight.y - width;
          bottomRight.x = topLeft.x + height;
        }
      }
    }
  }

  return { topLeft, bottomRight };
};

const getAnnotationPageCoordinates = (annotation) => {
  const rect = annotation.getRect();
  let { x1: left, y1: top, x2: right, y2: bottom } = rect;

  const isNote = annotation instanceof window.Annotations.StickyAnnotation;
  const noteAdjustment = window.Annotations.StickyAnnotation['SIZE'];

  const rotation = documentViewer.getCompleteRotation(annotation.PageNumber);
  if (rotation === 1) {
    [top, bottom] = [bottom, top];
    if (isNote) {
      top -= noteAdjustment;
      bottom -= noteAdjustment;
    }
  } else if (rotation === 2) {
    [left, right] = [right, left];
    [top, bottom] = [bottom, top];
    if (isNote) {
      top -= noteAdjustment;
      bottom -= noteAdjustment;
      left -= noteAdjustment;
      right -= noteAdjustment;
    }
  } else if (rotation === 3) {
    [left, right] = [right, left];
    if (isNote) {
      left -= noteAdjustment;
      right -= noteAdjustment;
    }
  }

  return { left, top, right, bottom };
};

const convertPageCoordinatesToWindowCoordinates = (x, y, pageNumber) => {
  const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();

  return displayMode.pageToWindow({ x, y }, pageNumber);
};


// just select an annotation and use it as our testing annotation
const annot = instance.annotManager.getSelectedAnnotations()[0];

const arrow = document.createElement('DIV')
arrow.innerText = '→'
arrow.style.fontSize = '84px'
arrow.style.fontWeight = '700'
arrow.style.color = 'red'
arrow.style.position = 'fixed'
arrow.style.left = '0px'

const movingElement = instance.docViewer.getScrollViewElement().appendChild(arrow);

instance.docViewer.getScrollViewElement().addEventListener('wheel',(e)=>{
    movingElement.style.top = `${getAnnotationPosition(annot).topLeft.y - instance.docViewer.getScrollViewElement().scrollTop}px`

})

Video:

The solution to this issue is somewhat long, but the majority of the calculation method is actually hinted in our open source UI project. The annotation note connector line is the arrow lines you see when you select an annotation and open up the right notes panel.
I hope the above can help you with your development!

Yixiao