> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/edit-page-content/coordinates.md).

# Get and Convert PDF Coordinates Using JavaScript

Learn how to get & convert PDF coordinates using JavaScript. Understand the difference between PDF page coordinates and viewer page coordinates for accurate location handling. The Apryse Web SDK strea

When dealing with locations in WebViewer it can be important to understand what coordinate space they are located in. For example when you set the (x, y) value of an annotation through the WebViewer API the location is relative to the unzoomed page in viewer page coordinates, not PDF page coordinates.

## PDF page coordinates

In a PDF document the location (0, 0) is at the bottom left corner of the page. The x axis extends horizontally to the right and y axis extends vertically upward.

Here are some APIs to get the width and height of a page.

* [getPageHeight](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getPageHeight)
* [getPageWidth](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getPageWidth)

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-2b6b84ce33a96c4f31d778ad3e9dc5eb68ed67f1%2Fbc5bf0c980409d48d34b3ad56835044bc93a5f94-465x450.png?alt=media)

A page may have a rotation or translation associated with it and in this case (0, 0) may no longer correspond to the bottom left corner of the page relative to the viewer.

For example here is the same page as above but rotated 90 degrees clockwise. Notice how the coordinates have all stayed the same relative to each other but (0, 0) is now in the top left corner relative to the viewport.

Here is an API to get the rotation of a page.

* [getPageRotation](https://sdk.apryse.com/api/web/Core.Document.html#getPageRotation)

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-bce23327ddce91fdb75938224aafc290a3282634%2Fbda8e646ac52e1e02a98f120dd6b8f4e73ddb95a-450x405.png?alt=media)

Generally when you're using WebViewer you won't be dealing with PDF coordinates directly.

## Viewer page coordinates

When reading or writing annotation locations in WebViewer these values are in viewer coordinates. The (0, 0) point is located at the top left of the page. The x axis extends horizontally to the right and the y axis extends vertically downward.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-c217c0df4e7555e57bafe64a8ea63084bb9063f6%2F4acd21aac5c68a794b8fa780945b7c8e42281b75-465x450.png?alt=media)

## Converting between PDF and viewer coordinates

WebViewer uses a transformation matrix for each page to allow it to convert between PDF and viewer coordinates. The matrix takes into account the flipped y values and possibly translation or scaling.

Since XOD files are considered to be at 96 DPI and PDF files at 72 DPI the scaling factor for XOD files is 96/72 or 4/3. For PDF files there is no scaling applied, just the flipped y values.

Annotation locations inside XFDF are in PDF coordinates. When XFDF is imported into WebViewer the locations will be transformed into viewer coordinates automatically using the matrix. When exporting XFDF WebViewer reverses the process and converts back to PDF coordinates.

If you ever need to convert between PDF and viewer coordinates yourself you can use the [`getPDFCoordinates`](https://sdk.apryse.com/api/web/Core.Document.html#getPDFCoordinates__anchor) function to get PDF coordinates or the [`getXODCoordinates`](https://sdk.apryse.com/api/web/Core.Document.html#getXODCoordinates__anchor) (for XOD files) or [`getViewerCoordinates`](https://sdk.apryse.com/api/web/Core.Document.html#getViewerCoordinates__anchor) (for PDF or Office files) functions.

For example:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { documentViewer } = instance.Core;

    documentViewer.addEventListener('documentLoaded', () => {
      const doc = documentViewer.getDocument();
      // top left corner in viewer coordinates
      const x = 0;
      const y = 0;
      const pageNumber = 1;

      const pdfCoords = doc.getPDFCoordinates(pageNumber, x, y);
      // top left corner has a high y value in PDF coordinates
      // example return value { x: 0, y: 792 }

      // convert back to viewer coordinates
      const viewerCoords = doc.getViewerCoordinates(pageNumber, pdfCoords.x, pdfCoords.y);
      // { x: 0, y: 0 }
    });
  });
```

{% endcode %}

[DocumentViewer#getViewerCoordinates](https://sdk.apryse.com/api/web/Core.Document.html#getViewerCoordinates) [DocumentViewer#getPDFCoordinates](https://sdk.apryse.com/api/web/Core.Document.html#getPDFCoordinates__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;

    docViewer.on('documentLoaded', () => {
      const doc = docViewer.getDocument();
      // top left corner in viewer coordinates
      const x = 0;
      const y = 0;
      const pageNumber = 1;

      const pdfCoords = doc.getPDFCoordinates(pageNumber, x, y);
      // top left corner has a high y value in PDF coordinates
      // example return value { x: 0, y: 792 }

      // convert back to viewer coordinates
      const viewerCoords = doc.getViewerCoordinates(pageNumber, pdfCoords.x, pdfCoords.y);
      // { x: 0, y: 0 }
    });
  });
```

{% endcode %}

[DocumentViewer#getViewerCoordinates](https://sdk.apryse.com/api/web/Core.Document.html#getViewerCoordinates) [DocumentViewer#getPDFCoordinates](https://sdk.apryse.com/api/web/Core.Document.html#getPDFCoordinates__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;

    docViewer.on('documentLoaded', () => {
      const doc = docViewer.getDocument();
      // top left corner in viewer coordinates
      const x = 0;
      const y = 0;
      const pageNumber = 1;

      const pdfCoords = doc.getPDFCoordinates(pageNumber, x, y);
      // top left corner has a high y value in PDF coordinates
      // example return value { x: 0, y: 792 }

      // convert back to viewer coordinates
      const viewerCoords = doc.getViewerCoordinates(pageNumber, pdfCoords.x, pdfCoords.y);
      // { x: 0, y: 0 }
    });
  });
```

{% endcode %}

[DocumentViewer#getViewerCoordinates](https://sdk.apryse.com/api/web/Core.Document.html#getViewerCoordinates) [DocumentViewer#getPDFCoordinates](https://sdk.apryse.com/api/web/Core.Document.html#getPDFCoordinates__anchor)
{% endtab %}
{% endtabs %}

## Window coordinates

These coordinates are relative to the browser window with (0, 0) in the top left corner. The x axis extends to the right and the y axis extends downwards as you scroll through the document content.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-c8fba689f8be6a23341939e55243e44ff3f7ac0f%2Fbf0c770d7f3885ca9e6625662d9c697ef00d9013-898x569.png?alt=media)

Note that the scroll position of the viewer does not affect these coordinates. For example if the user has scrolled to page 10 the window coordinates of the first page will still be the same.

Below you can see an example of the document being scrolled downwards but the window coordinates for the pages stay the same.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-ba5286a92193e8484354222d782f84f93c139618%2F3bdb8e964c92e2cfafd02176dd36733190a29257-501x650.png?alt=media)

## Converting between window and viewer page coordinates

WebViewer provides functions on the current display mode to convert between window and page coordinates. The [`pageToWindow`](https://sdk.apryse.com/api/web/Core.DisplayMode.html#pageToWindow__anchor) and [`windowToPage`](https://sdk.apryse.com/api/web/Core.DisplayMode.html#windowToPage__anchor) functions. For example:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { documentViewer } = instance.Core;

    const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
    const pageNumber = 1;
    const pagePoint = {
      x: 0,
      y: 0
    };

    const windowPoint = displayMode.pageToWindow(pagePoint, pageNumber);
    // { x: 212, y: 46 }

    const originalPagePoint = displayMode.windowToPage(windowPoint, pageNumber);
    // { x: 0, y: 0 }
  });
```

{% endcode %}

[DisplayMode#pageToWindow](https://sdk.apryse.com/api/web/Core.DisplayMode.html#pageToWindow__anchor) [DisplayMode#windowToPage](https://sdk.apryse.com/api/web/Core.DisplayMode.html#windowToPage__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;

    const displayMode = docViewer.getDisplayModeManager().getDisplayMode();
    const pageNumber = 1;
    const pagePoint = {
      x: 0,
      y: 0
    };

    const windowPoint = displayMode.pageToWindow(pagePoint, pageNumber);
    // { x: 212, y: 46 }

    const originalPagePoint = displayMode.windowToPage(windowPoint, pageNumber);
    // { x: 0, y: 0 }
  });
```

{% endcode %}

[DisplayMode#pageToWindow](https://sdk.apryse.com/api/web/Core.DisplayMode.html#pageToWindow__anchor) [DisplayMode#windowToPage](https://sdk.apryse.com/api/web/Core.DisplayMode.html#windowToPage__anchor)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;

    const displayMode = docViewer.getDisplayModeManager().getDisplayMode();
    const pageNumber = 1;
    const pagePoint = {
      x: 0,
      y: 0
    };

    const windowPoint = displayMode.pageToWindow(pagePoint, pageNumber);
    // { x: 212, y: 46 }

    const originalPagePoint = displayMode.windowToPage(windowPoint, pageNumber);
    // { x: 0, y: 0 }
  });
```

{% endcode %}

[DisplayMode#pageToWindow](https://sdk.apryse.com/api/web/Core.DisplayMode.html#pageToWindow__anchor) [DisplayMode#windowToPage](https://sdk.apryse.com/api/web/Core.DisplayMode.html#windowToPage__anchor)
{% endtab %}
{% endtabs %}

## Converting between mouse locations and window coordinates

Mouse locations are relative to the viewport so all that's required to convert to window coordinates is scrollLeft and scrollTop values of the viewer. If you're inside of a tool (for example your own custom tool) you can use [`this.getMouseLocation(e)`](https://sdk.apryse.com/api/web/Core.Tools.Tool.html#getMouseLocation__anchor) to get window coordinates from a mouse event.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
mouseLeftUp: function(e) {
  const windowCoords = this.getMouseLocation(e);
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Or manually using the scroll values from the viewer element:

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
const getMouseLocation = e => {
  const scrollElement = documentViewer.getScrollViewElement();
  const scrollLeft = scrollElement.scrollLeft || 0;
  const scrollTop = scrollElement.scrollTop || 0;
  return {
    x: e.pageX + scrollLeft,
    y: e.pageY + scrollTop
  };
};
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}
{% code lineNumbers="true" %}

````js
const getMouseLocation = e => {
  const scrollElement = docViewer.getScrollViewElement();
  const scrollLeft = scrollElement.scrollLeft || 0;
  const scrollTop = scrollElement.scrollTop || 0;
  return {
    x: e.pageX + scrollLeft,
    y: e.pageY + scrollTop
  };
};
```6.0js
const getMouseLocation = e => {
  const scrollElement = docViewer.getScrollViewElement();
  const scrollLeft = scrollElement.scrollLeft || 0;
  const scrollTop = scrollElement.scrollTop || 0;
  return {
    x: e.pageX + scrollLeft,
    y: e.pageY + scrollTop
  };
};
````

{% endcode %}

{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}

{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;
    docViewer.on('dblClick', e => {
      // refer to getMouseLocation implementation above
      const windowCoordinates = getMouseLocation(e);
    });
  });
```

{% endcode %}

{% endtab %} {% endtabs %}

This is what happens internally in the default tools, and as we saw above the window coordinate can be transformed to a page coordinate with a function call.

## In practice

What if you want to double click on the page and add a DOM element at that location? You probably want the element to automatically scroll with the page and be able to reposition it after the zoom changes. The easiest way to do this is to position it absolutely inside the pageContainer element.

So you'll get the event object from the mouse double click event, transform that into window coordinates and convert those into page coordinates. DocumentViewer triggers a [`dblClick`](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:dblClick__anchor)[ event](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:dblClick__anchor) so we can use that to get double clicks inside the viewing area.

{% tabs %} {% tab title="JavaScript (SDK v8.0+)" %}

{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { documentViewer } = instance.Core;
    documentViewer.addEventListener('dblClick', e => {
      // refer to getMouseLocation implementation above
      const windowCoordinates = getMouseLocation(e);
    });
  });
```

{% endcode %}

{% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}

{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;
    docViewer.on('dblClick', e => {
      // refer to getMouseLocation implementation above
      const windowCoordinates = getMouseLocation(e);
    });
  });
```

{% endcode %}

{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}

{% code lineNumbers="true" %}

```js
WebViewer(...)
  .then(instance => {
    const { docViewer } = instance;
    docViewer.on('dblClick', e => {
      // refer to getMouseLocation implementation above
      const windowCoordinates = getMouseLocation(e);
    });
  });
```

{% endcode %}

{% endtab %} {% endtabs %}

You also need to figure out what page you double clicked on and you can use the [`getSelectedPages`](https://sdk.apryse.com/api/web/Core.DisplayMode.html#pageToWindow__anchor) function to do this:

{% tabs %} {% tab title="JavaScript (SDK v8.0+)" %}

{% code lineNumbers="true" %}

```js
const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
// takes a start and end point but we just want to see where a single point is located
const page = displayMode.getSelectedPages(windowCoordinates, windowCoordinates);
const clickedPage = (page.first !== null) ? page.first : docViewer.getCurrentPage();
```

{% endcode %}

{% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}

{% code lineNumbers="true" %}

```js
const displayMode = docViewer.getDisplayModeManager().getDisplayMode();
// takes a start and end point but we just want to see where a single point is located
const page = displayMode.getSelectedPages(windowCoordinates, windowCoordinates);
const clickedPage = (page.first !== null) ? page.first : docViewer.getCurrentPage();
```

{% endcode %}

{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}

{% code lineNumbers="true" %}

```js
const displayMode = docViewer.getDisplayModeManager().getDisplayMode();
// takes a start and end point but we just want to see where a single point is located
const page = displayMode.getSelectedPages(windowCoordinates, windowCoordinates);
const clickedPage = (page.first !== null) ? page.first : docViewer.getCurrentPage() - 1;
```

{% endcode %}

{% endtab %} {% endtabs %}

Once you have the page you can get the page coordinates from the window coordinates:

{% tabs %} {% tab title="JavaScript" %}

{% code lineNumbers="true" %}

```js
const pageCoordinates = displayMode.windowToPage(windowCoordinates, clickedPage);
```

{% endcode %}

{% endtab %} {% endtabs %}

Then you can create your custom element and add it to the page container element. Note that the position and size need to be scaled by the zoom level.

{% tabs %} {% tab title="JavaScript (SDK v8.0+)" %}

{% code lineNumbers="true" %}

```js
const { iframeWindow } = instance.UI;
const zoom = documentViewer.getZoom();
const customElement = document.createElement('div');
customElement.style.position = 'absolute';
customElement.style.left = pageCoordinates.x * zoom;
customElement.style.top = pageCoordinates.y * zoom;
customElement.style.width = 100 * zoom;
customElement.style.height = 25 * zoom;
customElement.style.backgroundColor = 'blue';
customElement.style.zIndex = 35;
const pageContainer = iframeWindow.document.getElementById('pageContainer' + clickedPage);
pageContainer.appendChild(customElement);
```

{% endcode %}

{% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}

{% code lineNumbers="true" %}

```js
const { iframeWindow } = instance;
const zoom = docViewer.getZoom();
const customElement = document.createElement('div');
customElement.style.position = 'absolute';
customElement.style.left = pageCoordinates.x * zoom;
customElement.style.top = pageCoordinates.y * zoom;
customElement.style.width = 100 * zoom;
customElement.style.height = 25 * zoom;
customElement.style.backgroundColor = 'blue';
customElement.style.zIndex = 35;
const pageContainer = iframeWindow.document.getElementById('pageContainer' + clickedPage);
pageContainer.appendChild(customElement);
```

{% endcode %}

{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}

{% code lineNumbers="true" %}

```js
const { iframeWindow } = instance;
const zoom = docViewer.getZoom();
const customElement = document.createElement('div');
customElement.style.position = 'absolute';
customElement.style.left = pageCoordinates.x * zoom;
customElement.style.top = pageCoordinates.y * zoom;
customElement.style.width = 100 * zoom;
customElement.style.height = 25 * zoom;
customElement.style.backgroundColor = 'blue';
customElement.style.zIndex = 35;
const pageContainer = iframeWindow.document.getElementById('pageContainer' + clickedPage);
pageContainer.appendChild(customElement);
```

{% endcode %}

{% endtab %} {% endtabs %}

You'll notice that if you change the zoom or make the page rerender the elements will disappear. This is because when a page is rerendered it is resized and re-added to the DOM.

To handle this you can keep track of your custom elements and add them to the updated pageContainer on the [`pageComplete`](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:pageComplete__anchor) event. The full code looks like this:

{% tabs %} {% tab title="JavaScript (SDK v8.0+)" %}

{% code lineNumbers="true" %}

```js
const getMouseLocation = e => {
  const scrollElement = document.getElementById('DocumentViewer');
  const scrollLeft = scrollElement.scrollLeft || 0;
  const scrollTop = scrollElement.scrollTop || 0;
  return {
    x: e.pageX + scrollLeft,
    y: e.pageY + scrollTop
  };
};
const domElements = {};
const elementWidth = 100;
const elementHeight = 25;
WebViewer(...)
  .then(instance => {
    const { iframeWindow } = instance.UI;
    const { documentViewer } = instance.Core;
    documentViewer.addEventListener('dblClick', e => {
      // refer to getMouseLocation implementation above
      const windowCoordinates = getMouseLocation(e);
      const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
      const page = displayMode.getSelectedPages(windowCoordinates, windowCoordinates);
      const clickedPage = (page.first !== null) ? page.first : documentViewer.getCurrentPage();
      const pageCoordinates = displayMode.windowToPage(windowCoordinates, clickedPage);
      const zoom = documentViewer.getZoom();
      const customElement = document.createElement('div');
      customElement.style.position = 'absolute';
      customElement.style.left = pageCoordinates.x * zoom;
      customElement.style.top = pageCoordinates.y * zoom;
      customElement.style.width = 100 * zoom;
      customElement.style.height = 25 * zoom;
      customElement.style.backgroundColor = 'blue';
      customElement.style.zIndex = 35;
      const pageContainer = iframeWindow.document.getElementById('pageContainer' + clickedPage);
      pageContainer.appendChild(customElement);
      if (!domElements[clickedPage]) {
        domElements[clickedPage] = [];
      }
      // save left and top so we can scale them when the zoom changes
      domElements[clickedPage].push({
        element: customElement,
        left: pageCoordinates.x,
        top: pageCoordinates.y
      });
    });
    documentViewer.addEventListener('pageComplete', pageNumber => {
      if (domElements[pageNumber]) {
        const zoom = documentViewer.getZoom();
        const pageContainer = iframeWindow.document.getElementById('pageContainer' + pageNumber);
        // add back and scale elements for the rerendered page
        domElements[pageNumber].forEach(elementData => {
          elementData.element.style.left = elementData.left * zoom;
          elementData.element.style.top = elementData.top * zoom;
          elementData.element.style.width = elementWidth * zoom;
          elementData.element.style.height = elementHeight * zoom;
          pageContainer.appendChild(elementData.element);
        });
      }
    });
  });
```

{% endcode %}

[DocumentViewer#pageComplete](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:pageComplete__anchor) [DocumentViewer#getZoom](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getZoom__anchor) [DisplayMode#windowToPage](https://sdk.apryse.com/api/web/Core.DisplayMode.html#windowToPage__anchor) [DisplayMode#getSelectedPages](https://sdk.apryse.com/api/web/Core.DisplayMode.html#getSelectedPages__anchor) {% endtab %}

{% tab title="JavaScript (SDK v7.0+)" %}

{% code lineNumbers="true" %}

```js
const getMouseLocation = e => {
  const scrollElement = document.getElementById('DocumentViewer');
  const scrollLeft = scrollElement.scrollLeft || 0;
  const scrollTop = scrollElement.scrollTop || 0;
  return {
    x: e.pageX + scrollLeft,
    y: e.pageY + scrollTop
  };
};
const domElements = {};
const elementWidth = 100;
const elementHeight = 25;
WebViewer(...)
  .then(instance => {
    const { docViewer, iframeWindow } = instance;
    docViewer.on('dblClick', e => {
      // refer to getMouseLocation implementation above
      const windowCoordinates = getMouseLocation(e);
      const displayMode = docViewer.getDisplayModeManager().getDisplayMode();
      const page = displayMode.getSelectedPages(windowCoordinates, windowCoordinates);
      const clickedPage = (page.first !== null) ? page.first : docViewer.getCurrentPage();
      const pageCoordinates = displayMode.windowToPage(windowCoordinates, clickedPage);
      const zoom = docViewer.getZoom();
      const customElement = document.createElement('div');
      customElement.style.position = 'absolute';
      customElement.style.left = pageCoordinates.x * zoom;
      customElement.style.top = pageCoordinates.y * zoom;
      customElement.style.width = 100 * zoom;
      customElement.style.height = 25 * zoom;
      customElement.style.backgroundColor = 'blue';
      customElement.style.zIndex = 35;
      const pageContainer = iframeWindow.document.getElementById('pageContainer' + clickedPage);
      pageContainer.appendChild(customElement);
      if (!domElements[clickedPage]) {
        domElements[clickedPage] = [];
      }
      // save left and top so we can scale them when the zoom changes
      domElements[clickedPage].push({
        element: customElement,
        left: pageCoordinates.x,
        top: pageCoordinates.y
      });
    });
    docViewer.on('pageComplete', pageNumber => {
      if (domElements[pageNumber]) {
        const zoom = docViewer.getZoom();
        const pageContainer = iframeWindow.document.getElementById('pageContainer' + pageNumber);
        // add back and scale elements for the rerendered page
        domElements[pageNumber].forEach(elementData => {
          elementData.element.style.left = elementData.left * zoom;
          elementData.element.style.top = elementData.top * zoom;
          elementData.element.style.width = elementWidth * zoom;
          elementData.element.style.height = elementHeight * zoom;
          pageContainer.appendChild(elementData.element);
        });
      }
    });
  });
```

{% endcode %}

[DocumentViewer#pageComplete](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:pageComplete__anchor) [DocumentViewer#getZoom](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getZoom__anchor) [DisplayMode#windowToPage](https://sdk.apryse.com/api/web/Core.DisplayMode.html#windowToPage__anchor) [DisplayMode#getSelectedPages](https://sdk.apryse.com/api/web/Core.DisplayMode.html#getSelectedPages__anchor) {% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}

{% code lineNumbers="true" %}

```js
const getMouseLocation = e => {
  const scrollElement = document.getElementById('DocumentViewer');
  const scrollLeft = scrollElement.scrollLeft || 0;
  const scrollTop = scrollElement.scrollTop || 0;
  return {
    x: e.pageX + scrollLeft,
    y: e.pageY + scrollTop
  };
};
const domElements = {};
const elementWidth = 100;
const elementHeight = 25;
WebViewer(...)
  .then(instance => {
    const { docViewer, iframeWindow } = instance;
    docViewer.on('dblClick', e => {
      // refer to getMouseLocation implementation above
      const windowCoordinates = getMouseLocation(e);
      const displayMode = docViewer.getDisplayModeManager().getDisplayMode();
      const page = displayMode.getSelectedPages(windowCoordinates, windowCoordinates);
      const clickedPage = (page.first !== null) ? page.first : docViewer.getCurrentPage() - 1;
      const pageCoordinates = displayMode.windowToPage(windowCoordinates, clickedPage);
      const zoom = docViewer.getZoom();
      const customElement = document.createElement('div');
      customElement.style.position = 'absolute';
      customElement.style.left = pageCoordinates.x * zoom;
      customElement.style.top = pageCoordinates.y * zoom;
      customElement.style.width = 100 * zoom;
      customElement.style.height = 25 * zoom;
      customElement.style.backgroundColor = 'blue';
      customElement.style.zIndex = 35;
      const pageContainer = iframeWindow.document.getElementById('pageContainer' + clickedPage);
      pageContainer.appendChild(customElement);
      if (!domElements[clickedPage]) {
        domElements[clickedPage] = [];
      }
      // save left and top so we can scale them when the zoom changes
      domElements[clickedPage].push({
        element: customElement,
        left: pageCoordinates.x,
        top: pageCoordinates.y
      });
    });
    docViewer.on('pageComplete', pageIndex => {
      if (domElements[pageIndex]) {
        const zoom = docViewer.getZoom();
        const pageContainer = iframeWindow.document.getElementById('pageContainer' + pageIndex);
        // add back and scale elements for the rerendered page
        domElements[pageIndex].forEach(elementData => {
          elementData.element.style.left = elementData.left * zoom;
          elementData.element.style.top = elementData.top * zoom;
          elementData.element.style.width = elementWidth * zoom;
          elementData.element.style.height = elementHeight * zoom;
          pageContainer.appendChild(elementData.element);
        });
      }
    });
  });
```

{% endcode %}

[DocumentViewer#pageComplete](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:pageComplete__anchor) [DocumentViewer#getZoom](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#getZoom__anchor) [DisplayMode#windowToPage](https://sdk.apryse.com/api/web/Core.DisplayMode.html#windowToPage__anchor) [DisplayMode#getSelectedPages](https://sdk.apryse.com/api/web/Core.DisplayMode.html#getSelectedPages__anchor)
{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/edit-page-content/coordinates.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
