This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/android/faq/coordinate-spaces.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Coordinate space conversion in Android PDF viewer | Nutrient

PDF pages and Android views use different coordinate spaces. Android views place the origin at the top-left, with y coordinates increasing downward. PDF pages place the origin at the bottom-left, with y coordinates increasing upward.

A crop box can offset PDF coordinates from the visible bottom-left corner. Page rotation can rotate the coordinate space. Cropping and rotation change page properties without modifying the content stream or annotations.

Nutrient normalizes page coordinates so the origin always matches the visible area’s bottom-left corner.

PDF page coordinates

Instant JSON places the origin at the page’s top-left, with y coordinates increasing downward. For details, refer to the Instant JSON guide.

Use Nutrient’s projection API to convert between PDF and other coordinate spaces. The following example converts an annotation’s bounds to onscreen coordinates:

// Get the PDF coordinates of the annotation.
val annotationRect = someAnnotation.boundingBox
// Get the view projection for transforming between the view coordinates and PDF coordinates.
val viewProjection = fragment.viewProjection
// Convert the annotation's PDF coordinates to view coordinates (pixels).
viewProjection.toViewRect(annotationRect, someAnnotation.pageIndex)

Each PDF page has its own coordinate space, so conversion methods require a pageIndex.

Understanding XFDF/PDF rects

XFDF and PDF files store an annotation’s bounding box as four edge coordinates. For example, export the XFDF of a rectangle annotation:

<!-- Other attributes omitted for clarity -->
<square rect="50.000000, 100.000000, 80.000000, 120.000000" />

The rect attribute contains the following information in this order:

  • The left side of the rectangle is 50 units from the left of the page.
  • The bottom side of the rectangle is 100 units from the bottom of the page.
  • The right side of the rectangle is 80 units from the left of the page.
  • The top side of the rectangle is 120 units from the bottom of the page.

The width of the rectangle annotation is 30 units (80-50) and the height is 20 units (120-100).

Nutrient Android SDK uses the same orientation. Annotation#getBoundingBox() returns a RectF(opens in a new tab) with a bottom-left origin and top greater than bottom.

Without a crop offset or rotation, no coordinate conversion is necessary. The equivalent bounding box is:

// RectF(left, top, right, bottom), in PDF points.
val boundingBox = RectF(50f, 120f, 80f, 100f)

Conversion methods

PdfFragment#getViewProjection() returns a ViewProjection for converting PDF and view coordinates. Points use PointF(opens in a new tab), and rectangles use RectF(opens in a new tab).

PdfDocument#getPdfProjection() returns a PdfProjection for converting normalized and raw PDF coordinates.

Convert between normalized and raw PDF coordinates

Nutrient’s normalized coordinates place the origin at the visible page area’s bottom-left corner. Use PdfProjection to convert raw PDF coordinates to normalized coordinates.

The following example places a 100-point square annotation at the PDF’s raw page origin:

val pageIndex = 0
// Get the PDF projection for transforming between the raw coordinates and PDF coordinates.
val pdfProjection = document.pdfProjection
val rawRect = RectF(0f, 100f, 100f, 0f)
// Convert the raw PDF coordinates to normalized PDF coordinates.
val normalizedPdfRect = pdfProjection.toPdfRect(rawRect, pageIndex)
// Create a new square annotation using the normalized coordinates.
val annotation = SquareAnnotation(pageIndex, normalizedPdfRect)

A crop box offset can leave the annotation out of view.

For the reverse conversion, use PdfProjection#toRawPoint(PointF, int) or PdfProjection#toRawRect(RectF, int). The following example retrieves an annotation’s bounding box in raw PDF coordinates:

val pageIndex = 0
// Get the PDF projection for transforming between the raw coordinates and PDF coordinates.
val pdfProjection = document.pdfProjection
// Get the annotation whose bounding box needs to be read.
val annotation : SquareAnnotation = ...
// Get the annotation bounding box.
val normalizedRect = annotation.boundingBox
// Convert the normalized PDF coordinates to raw PDF coordinates.
val rawRect = pdfProjection.toRawRect(normalizedRect, pageIndex)

Conversion matrix

Retrieve a transformation Matrix(opens in a new tab) for direct control over conversion:

For view transformations, pass an existing matrix as the second argument to reuse it. Pass null to create a new matrix.

How to convert between raster image pixels and points

Raster images have dimensions in pixels, while Nutrient returns PDF page sizes in points. By default, one inch equals 72 points. Divide the size in points by 72 to get inches. Divide the pixel count by that size in inches to calculate DPI (dots per inch).

Use these relationships:

  • One inch = 72 points
  • Size in inches = points / 72
  • DPI = pixels / size in inches

Since PDF 1.6, the page dictionary’s UserUnit entry can scale user units beyond the default 1⁄72 inch. For details, refer to table 30 on page 79 of the PDF 1.7 specification(opens in a new tab).