---
title: "PDF.js coordinates: Convert PDF space to screen space"
canonical_url: "https://www.nutrient.io/blog/pdfjs-coordinate-systems-pdf-to-screen/"
md_url: "https://www.nutrient.io/blog/pdfjs-coordinate-systems-pdf-to-screen.md"
last_updated: "2026-08-11T08:57:05.236Z"
description: "Convert between PDF and screen coordinate systems in PDF.js with PageViewport — convertToPdfPoint, convertToViewportRectangle, and rotation handling."
---

**TL;DR**

- PDF coordinates have a bottom-left origin in PDF points; screen coordinates have a top-left origin in CSS pixels. Store the PDF form, and render with the screen form.

- `PageViewport.convertToPdfPoint`/`convertToViewportPoint`/`convertToViewportRectangle` are the conversion APIs.

- Rederive the viewport on `scalechanging`/`rotationchanging` events and normalize rotated rectangles with `Math.min`/`Math.max`.

## Prerequisites

This guide assumes you have a working PDF.js viewer (`PDFViewer` + `EventBus` + a loaded page). If you don’t, start with the walkthrough on [how to set up a custom PDF.js viewer in React](https://www.nutrient.io/blog/pdfjs-react-viewer-setup.md).

You’ll also need:

- `pdfjs-dist` 4.x or later

- A way to access individual page views: `viewer.getPageView(pageIndex)` returns a `PageView` with a `.viewport` and `.canvas`

## The two coordinate systems

When you work with PDF.js, you’re constantly translating between two coordinate systems.

### PDF coordinates

- Origin at **bottom-left** of the page

- Y-axis goes **up**

- Units are in PDF points (1 point = 1/72 inch)

- Independent of zoom, rotation, or screen size

- This is what you store in your database

### Screen coordinates (CSS/Canvas)

- Origin at **top-left** of the rendered page

- Y-axis goes **down**

- Units are in CSS pixels

- Changes with zoom, rotation, and dots per inch (DPI)

- This is what you use for positioning HTML overlays

## PageViewport: The coordinate bridge

Every rendered page in PDF.js has a `PageViewport` object that handles conversion between these systems. Access it via the following:

```tsx

const pageView = viewer.getPageView(pageNumber - 1); // 0-indexed.
const viewport = pageView.viewport;

```

## Converting screen to PDF (for saving)

When a user draws a rectangle onscreen, you need to convert those CSS coordinates to PDF coordinates for storage:

```tsx

function screenToPdf(position, viewport) {
  const [x1, y1] = viewport.convertToPdfPoint(
    position.left,
    position.top,
  );
  const [x2, y2] = viewport.convertToPdfPoint(
    position.left + position.width,
    position.top + position.height,
  );

  const minX = Math.min(x1, x2);
  const minY = Math.min(y1, y2);
  const maxX = Math.max(x1, x2);
  const maxY = Math.max(y1, y2);

  return {
    x1: minX,
    y1: minY,
    x2: maxX,
    y2: maxY,
    width: maxX - minX,
    height: maxY - minY,
    pageNumber: position.pageNumber,
  };
}

```

**Why `Math.min`/`Math.max`?** When the page is rotated, `convertToPdfPoint` may return coordinates where x1 > x2 or y1 > y2. Normalizing ensures a consistent rectangle.

## Converting PDF to screen (for rendering)

When loading annotations from storage, convert PDF coordinates back to screen position:

```tsx

function pdfToScreen(scaled, viewport) {
  const [x1, y1, x2, y2] = viewport.convertToViewportRectangle([
    scaled.x1,
    scaled.y1,
    scaled.x2,
    scaled.y2,
  ]);

  const minX = Math.min(x1, x2);
  const minY = Math.min(y1, y2);
  const maxX = Math.max(x1, x2);
  const maxY = Math.max(y1, y2);

  return {
    left: minX,
    top: minY,
    width: maxX - minX,
    height: maxY - minY,
    pageNumber: scaled.pageNumber,
  };
}

```

## Converting a single point

For sticky notes or click positions, convert individual points:

```tsx

// Screen > PDF.
const [pdfX, pdfY] = viewport.convertToPdfPoint(canvasLeft, canvasTop);

// PDF > screen.
const [screenX, screenY] = viewport.convertToViewportPoint(pdfX, pdfY);

```

## Storing coordinates in PDF space

For persistence, store in PDF coordinate space since it’s independent of zoom/rotation:

```ts

// What you store in the database.
type NodeLocation = {
  page: number;        // 1-indexed page number.
  rect: number[];      // [x1, y1, x2, y2] in PDF coordinates.
};

// What you use for CSS rendering.
type LTWHP = {
  left: number;
  top: number;
  width: number;
  height: number;
  pageNumber: number;
};

```

## Handling scale and rotation changes

When the user zooms or rotates, you need to reconvert all coordinates. Listen for these `EventBus` events:

```tsx

React.useEffect(() => {
  const updateViewport = () => {
    const pageView = viewer.getPageView(page - 1);
    if (pageView?.viewport) {
      setViewport(pageView.viewport);
    }
  };

  eventBus.on("scalechanging", updateViewport);
  eventBus.on("rotationchanging", updateViewport);

  return () => {
    eventBus.off("scalechanging", updateViewport);
    eventBus.off("rotationchanging", updateViewport);
  };
}, [eventBus, page]);

```

When `viewport` state changes, all annotations using `pdfToScreen(stored, viewport)` automatically reposition.

## Handling rotated pages

PDF coordinates with a rotation of 180 degrees or more can flip the scroll position logic. When navigating to a location:

```tsx

function navigateToLocation(location, viewer, linkService) {
  const pdfCoords = nodeLocationToScaled(location);
  const viewport = viewer.getPageView(pdfCoords.pageNumber - 1)?.viewport;

  let scrollLeft, scrollTop;
  if (viewport && viewport.rotation >= 180) {
    scrollLeft = pdfCoords.x2;
    scrollTop = pdfCoords.y1;
  } else {
    scrollLeft = pdfCoords.x1;
    scrollTop = pdfCoords.y2;
  }

  // `PDFLinkService.goToDestination` accepts an integer page index here,
  // but the canonical PDF explicit-destination form expects a page reference
  // object `{ num, gen }` from `pdfDocument.getDestination(name)`. Use the
  // integer form for navigation, and the ref form when you’re roundtripping
  // a real PDF destination.
  linkService.goToDestination([
    pdfCoords.pageNumber - 1,
    { name: "XYZ" },
    scrollLeft,
    scrollTop,
    null,
  ]);
}

```

## Device pixel ratio

When capturing canvas regions (e.g. for area annotation screenshots), account for the canvas’s actual output scale. PDF.js caps render scale and accepts overrides, so reading `window.devicePixelRatio` directly can be wrong — derive the scale from the rendered canvas instead:

```tsx

const dpr = canvas.width / canvas.clientWidth;

context.drawImage(
  canvas,
  left * dpr,    // Source coordinates are scaled by the real DPR.
  top * dpr,
  width * dpr,
  height * dpr,
  0, 0,          // Destination at origin
  width, height, // Destination at CSS size
);

```

## Quick reference: Coordinate conversion methods

| Direction              | Method                                               | Use case                         |
| ---------------------- | ---------------------------------------------------- | -------------------------------- |
| Screen > PDF           | `viewport.convertToPdfPoint(x, y)`                   | Saving user-drawn annotations    |
| PDF > screen           | `viewport.convertToViewportPoint(x, y)`              | Rendering a single point (notes) |
| PDF rect > screen rect | `viewport.convertToViewportRectangle([x1,y1,x2,y2])` | Rendering annotation rectangles  |
| Screen rect > PDF rect | Two `convertToPdfPoint` calls                        | Saving area selections           |

Always normalize with `Math.min`/`Math.max` after conversion to handle rotation edge cases.

## Where coordinate conversion shows up in practice

Once you have `screenToPdf` and `pdfToScreen` defined, several PDF.js annotation patterns share them:

- [Sticky note annotations in PDF.js](https://www.nutrient.io/blog/pdfjs-sticky-note-annotations.md) — Single-point conversion with `convertToPdfPoint`/`convertToViewportPoint`.

- [Text highlight annotations in PDF.js](https://www.nutrient.io/blog/pdfjs-text-highlight-annotations.md) — Rectangle conversion for selected text ranges.

- [Area annotations with canvas capture in PDF.js](https://www.nutrient.io/blog/pdfjs-area-annotations-canvas-capture.md) — Bounding-box conversion plus DPR-aware canvas cropping.

- [Rendering overlays with React Portals in PDF.js](https://www.nutrient.io/blog/pdfjs-rendering-overlays-react-portals.md) — Placing React components at PDF-space coordinates.

## How Nutrient Web SDK handles this

Nutrient Web SDK ships with a simpler coordinate model than raw PDF: a top-left origin (Y-down), measured in PDF points. The viewer manages viewport transforms, zoom, rotation, and DPI internally, so you set `boundingBox` once and the SDK keeps it positioned through every change:

```js

const annotation = new NutrientViewer.Annotations.RectangleAnnotation({
  pageIndex: 0,
  boundingBox: new NutrientViewer.Geometry.Rect({
    left: 50,
    top: 100,
    width: 200,
    height: 50,
  }),
});
await instance.create(annotation);

```

Note that Nutrient’s `top` is measured from the top of the page, not the bottom — if you’re porting stored PDF-space coordinates from raw `convertToPdfPoint` output, you’ll need to flip the Y values (`pageHeight - y`) when handing them to `Geometry.Rect`.

[Learn more about Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) | [Migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) | [Contact Sales](https://www.nutrient.io/contact-sales/?=sdk)

## FAQ

#### Why store coordinates in PDF space instead of screen space?

Screen coordinates change with zoom, rotation, and device pixel ratio. If you store `{ left: 240, top: 380 }` from a user’s 125 percent zoomed view, the annotation lands somewhere else on someone else’s 75 percent view. PDF coordinates are tied to the document’s intrinsic dimensions (points from the bottom-left origin) and stay stable regardless of how the page is rendered.

#### Why do I need <code>Math.min</code>/<code>Math.max</code> after conversion?

`convertToPdfPoint` runs the screen-space rect through the viewport’s inverse transform. For rotated pages (90, 180, 270 degrees), that transform flips axes — so the converted “top-left” can end up with a larger x than the converted “bottom-right.” Normalizing the four numbers gives you a canonical rectangle regardless of rotation.

#### When do I need to recompute viewports?

On the `scalechanging` event (zoom in/out) and the `rotationchanging` event. Both invalidate the cached transform on `PageView.viewport`. The `useEffect` pattern in the handling-changes section subscribes to both and rereads the viewport from the page view.

#### What’s the difference between <code>convertToViewportPoint</code> and <code>convertToViewportRectangle</code>?

`convertToViewportPoint(x, y)` converts a single PDF point to screen coordinates — use it for sticky notes, click positions, or any single-point annotation. `convertToViewportRectangle([x1, y1, x2, y2])` converts all four corners in one call and returns a flat array — use it for highlights, area annotations, or anything with a bounding box.

#### Does <code>window.devicePixelRatio</code> work for canvas captures?

Not reliably. PDF.js caps the rendered canvas resolution on huge pages and accepts user-supplied overrides, so the canvas’s actual scale can be lower than `devicePixelRatio`. Derive it from the canvas instead: `canvas.width / canvas.clientWidth` returns the real ratio every time.

#### How do PDF coordinates relate to the PDF spec’s user space?

PDF.js’s “PDF coordinates” are PDF user space points (1 point = 1/72 inch), origin at bottom-left, Y-axis up — exactly what the PDF specification defines. A coordinate stored as `[100, 200, 300, 400]` lands at the same location whether opened in PDF.js, Acrobat, or any other compliant viewer.
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Best Document Ai Platforms](/blog/best-document-ai-platforms.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [Extract Patient Data On Premises](/blog/extract-patient-data-on-premises.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Building WCAG 2.2, Section 508, and PDF/UA-compliant PDFs with an SDK](/blog/pdf-accessibility.md)
- [Pdf Data Extraction Developer Guide](/blog/pdf-data-extraction-developer-guide.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Accessibility Structtree Printing](/blog/pdfjs-accessibility-structtree-printing.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [macOS](/blog/pdfjs-file-format-conversion-to-pdf.md)
- [macOS](/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [or](/blog/sample-blog-updated.md)
- [Sdk Product Updates Q2 2026](/blog/sdk-product-updates-q2-2026.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

