Coordinate space conversion
By specification, a PDF document has its own coordinate space, which differs from the coordinate space Android views use. An Android view starts top-left, with y coordinates increasing downward. A PDF page starts bottom-left, with y coordinates increasing upward.

A crop box can also offset the PDF coordinate space from the visible bottom-left corner of a page. Page rotation can rotate it. Both are cheap: Cropping or rotating a page sets a value and leaves the content stream and the page’s annotations untouched. Both are also confusing to work with, so Nutrient exposes a normalized page coordinate space whose origin is always the visible area’s bottom-left corner.
Our Instant JSON format uses a coordinate space where the origin is the top-left corner of the page, with the y-axis increasing downward.
Nutrient provides an API for transforming between the PDF coordinate space and other coordinate spaces. The following code retrieves the onscreen coordinates of an annotation:
// 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)// Get the PDF coordinates of the annotation.final RectF annotationRect = someAnnotation.getBoundingBox();// Get the view projection for transforming between the view coordinates and PDF coordinates.ViewProjection viewProjection = fragment.getViewProjection();// Convert the annotation's PDF coordinates to view coordinates (pixels).viewProjection.toViewRect(annotationRect, someAnnotation.getPageIndex());Since every page of a PDF document has its own coordinate space, the conversion method also takes a page argument.
Understanding XFDF/PDF rects
PDFs also represent an annotation’s bounding box differently than Nutrient does. 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).
The left value carries over unchanged. Nutrient bounding boxes use width, height, left, and top, so the top value needs converting: Subtract the XFDF top value from the page height. With a page height of 800, the adjusted top value is 680 (800-120).
Thus, the equivalent Nutrient bounding box would be:
{ "top": 680, "left": 50, "width": 30, "height": 20}Conversion methods
PdfFragment#getViewProjection() returns a ViewProjection. Its methods convert between PDF and view coordinates.
toViewPoint(PointF, int)andtoPdfPoint(PointF, int)convert a single point (stored within aPointF(opens in a new tab) instance) from page coordinates to view coordinates and vice versa, respectively.toViewRect(RectF, int)andtoPdfRect(RectF, int)convert a rectangle (stored within aRectF(opens in a new tab) instance) from page coordinates to view coordinates and vice versa, respectively.
PdfDocument#getPdfProjection() returns a PdfProjection. Its methods convert between PDF and raw coordinates.
toRawPoint(PointF, int)andtoNormalizedPoint(PointF, int)convert a single point (stored within aPointF(opens in a new tab) instance) from normalized page coordinates to raw page coordinates and vice versa, respectively.toRawRect(RectF, int)andtoPdfRect(RectF, int)convert a rectangle (stored within aRectF(opens in a new tab) instance) from normalized page coordinates to raw coordinates and vice versa, respectively.
Converting between the normalized PDF coordinate space and the raw PDF coordinate space
The Nutrient API uses a normalized page coordinate space, which always puts the origin in the bottom-left corner of the visible area of the page.
You can convert from the raw PDF coordinate space to Nutrient’s normalized space using the conversion methods in PdfProjection. For example, to place a 100-point by 100-point square annotation at the page origin stored in the PDF, use code like the following:
val pageIndex = 0// Get the PDF projection for transforming between the raw coordinates and PDF coordinates.val pdfProjection = document.pdfProjectionval 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)int pageIndex = 0;// Get the PDF projection for transforming between the raw coordinates and PDF coordinates.PdfProjection pdfProjection = document.getPdfProjection();RectF rawRect = new RectF(0f, 100f, 100f, 0f);// Convert the raw PDF coordinates to normalized PDF coordinates.RectF normalizedPdfRect = pdfProjection.toPdfRect(rawRect, pageIndex);// Create a new square annotation using the normalized coordinates.SquareAnnotation annotation = new SquareAnnotation(pageIndex, normalizedPdfRect);A crop box offset can leave the annotation out of view.
To go the other way, from the normalized space to the raw PDF space, use PdfProjection#toRawPoint(PointF, int) or PdfProjection#toRawRect(RectF, int). For example, to read an annotation’s bounding box as the PDF would serialize it:
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)int pageIndex = 0;// Get the PDF projection for transforming between the raw coordinates and PDF coordinates.PdfProjection pdfProjection = document.getPdfProjection();// Get the annotation whose bounding box needs to be read.SquareAnnotation annotation = ...// Get the annotation bounding box.RectF normalizedRect = annotation.getBoundingBox();// Convert the normalized PDF coordinates to raw PDF coordinates.RectF rawRect = pdfProjection.toRawRect(normalizedRect, pageIndex);Conversion matrix
If you need more control over the coordinate conversion, you can also retrieve a Matrix(opens in a new tab) object using:
PdfProjection#getNormalizedToRawTransformation(int), which holds the normalized-to-raw transformation.PdfProjection#getRawToNormalizedTransformation(int), which holds the raw-to-normalized transformation.ViewProjection#getPageToViewTransformation(int, Matrix), which holds the page-to-view transformation. The second parameter takes a matrix object to reuse, ornullto create a new one.ViewProjection#getViewToPageTransformation(int, Matrix), which holds the view-to-page transformation. The second parameter takes a matrix object to reuse, ornullto create a new one.
How to convert between raster image pixels and points
Resolution doesn’t apply to a PDF document until it becomes a raster image whose dimensions are expressed in pixels. Nutrient returns page sizes in points. One inch is 72 points, so dividing a page’s size in points by 72 gives its inch separation — the same size expressed in inches. Resolution in DPI (dots per inch) is then the pixel count divided by that inch separation. The relations to work with:
1 inch = 72 points
Inch separation = points / 72
DPI (resolution) = pixels / inch separation
Since PDF 1.6, the page dictionary’s UserUnit entry can make the inch-to-point relationship greater than 1⁄72. See table 30 on page 79 of the PDF 1.7 specification(opens in a new tab) for more information.