PDF renderer library for Android
You can use Nutrient to render a PDF page into a Bitmap
object so that you can use it in your custom application views as well. To do this, use PSPDFKit
and PdfDocument
class calls.
Initializing Nutrient
Before loading or rendering a document, you have to initialize Nutrient by providing your license. Here’s how to do it.
Loading a document
The PdfDocumentLoader
class offers a variety of methods for loading a document. You can load documents from a Uri
or a DataProvider
. The following example loads a PDF document from the app’s assets. The different available document sources are described in the PdfActivity
guide:
val document : PdfDocument try { // Use this `Uri` format to access files inside your app's assets. val documentUri = Uri.parse("file:///android_asset/shopping-center-plan.pdf") // This synchronously opens the document. To keep your app UI responsive, you should do this call // on a background thread, or use the asynchronous version of this method instead. document = PdfDocumentLoader.openDocument(context, documentUri) } catch (e : IOException) { handleDocumentLoadingError(e) }
final PdfDocument document; try { // Use this `Uri` format to access files inside your app's assets. final Uri documentUri = Uri.parse("file:///android_asset/shopping-center-plan.pdf"); // This synchronously opens the document. To keep your app UI responsive, you should do this call // on a background thread, or use the asynchronous version of this method instead. document = PdfDocumentLoder.openDocument(context, documentUri); } catch (IOException e) { handleDocumentLoadingError(e); }
Now that the PdfDocument
has been loaded, you can use it to render PDF pages.
Rendering pages
The rendering of pages can be performed synchronously or asynchronously using the #renderPageToBitmap
and #renderPageToBitmapAsync
methods of the PdfDocument
, respectively.
Synchronous rendering
To synchronously render a page into a Bitmap
, use any of the available #renderPageToBitmap
methods. These methods will block until rendering has been finished, which means you should only use them on a background thread (not from the main UI thread of your app).
💡 Tip: To keep the original page aspect while rendering and prevent stretching of the resulting image, you can access the original PageSize
of the rendered page (in PDF points) and calculate your resulting bitmap size:
val pageIndex = 0 // Page size is in PDF points (not pixels). val pageSize : Size = document.getPageSize(pageIndex) // We define a target width for the resulting bitmap and use it to calculate the final height. val width = 2048 val height = (pageSize.height * (width / pageSize.width)).toInt() // This will render the first page uniformly into a bitmap with a width of 2,048 pixels. val pageBitmap : Bitmap = document.renderPageToBitmap(context, pageIndex, width, height)
final int pageIndex = 0; // Page size is in PDF points (not pixels). final Size pageSize = document.getPageSize(pageIndex); // We define a target width for the resulting bitmap and use it to calculate the final height. final int width = 2048; final int height = (int) (pageSize.height * (width / pageSize.width)); // This will render the first page uniformly into a bitmap with a width of 2,048 pixels. final Bitmap pageBitmap = document.renderPageToBitmap(context, pageIndex, width, height);
Asynchronous rendering
To render the document asynchronously in a background thread, you can use any of the available #renderPageToBitmapAsync
methods. These methods won’t block, but they will instead return an RxJava Single<Bitmap>
, which will emit the Bitmap
once rendering has finished (similar to a callback). The rendering itself is performed on a background thread by default, so you can safely call this method from your app’s main thread:
// Render the page on a background thread and return the resulting bitmap on the main thread. document.renderPageToBitmapAsync(context, pageIndex, width, height) .observeOn(AndroidSchedulers.mainThread()) .subscribe { bitmap -> // Your code can now use the bitmap. updateUserInterface(bitmap) }
// Render the page on a background thread and return the resulting bitmap on the main thread. document.renderPageToBitmapAsync(context, pageIndex, width, height) .observeOn(AndroidSchedulers.mainThread()) .subscribe(bitmap -> { // Your code can now use the bitmap. updateUserInterface(bitmap); });
Providing a render configuration
Both synchronous and asynchronous render methods allow you to specify a PageRenderConfiguration
object for defining details about the requested rendering. You can create the configuration using its Builder
class. The configuration allows you to specify several options:
-
Enabling or disabling of an in-memory render
cache
. -
Rendering of only a specific
region
on the page (e.g. the upper half). -
A
reuseBitmap
object that should be reused for rendering. Use this to optimize memory performance of your app. -
Background color, grayscale mode, or inversion of all colors.
-
Rendering of
renderedDrawables
above the page content. This can be used to add watermarks. More information can be found in our drawable API guide.
val renderConfig = PageRenderConfiguration.Builder() .toGrayscale(true) .build() val grayscaleBitmap : Bitmap = document.renderPageToBitmap(context, pageIndex, width, height, renderConfig)
final PageRenderConfiguration renderConfig = new PageRenderConfiguration.Builder() .toGrayscale(true) .build(); final Bitmap grayscaleBitmap = document.renderPageToBitmap(context, pageIndex, width, height, renderConfig);
Rendering a specific part of a page
As previously mentioned, by using PageRenderConfiguration
, you can render a specific part of a page. See the PageRenderConfiguration.Builder#region()
method for more information.
Here’s an example of rendering the part of the page that is currently zoomed (every step is described by a comment):
// You can get all visible pages by calling `PdfFragment#getVisiblePages()`. val somePageIndex = ... // In order to render just a part of the page, you first need the visible rect. val currentVisibleRect = RectF() // This saves the visible rect in PDF coordinates to `currentVisibleRect`. pdfFragment.getVisiblePdfRect(currentVisibleRect, somePageIndex) // Define a desired width for the bitmap in pixels. val bitmapWidth = 2000 // Now calculate the height, keeping the aspect ratio of the bitmap as the visible area. val ratio = bitmapWidth / currentVisibleRect.width // Invert the height so it's not negative (in PDF, the rect top has a higher value than the bottom). val bitmapHeight = (-currentVisibleRect.height * ratio) as Int // Calculate the size of the full page for the desired region bitmap size. val pageSizeInPdfPoints = document.getPageSize(somePageIndex) val fullPageWidth = (pageSizeInPdfPoints.width * ratio) as Int val fullPageHeight = (pageSizeInPdfPoints.height * ratio) as Int // Now we need to get region coordinates in relation to the full page. val x = (currentVisibleRect.left * ratio) as Int val y = ((pageSizeInPdfPoints.height - currentVisibleRect.top) * ratio) as Int // Offsets define the movement of the full page, so we move the page region in the top/left direction // by x and y and then capture it. val renderConfiguration = PageRenderConfiguration.Builder() .region(-x, -y, fullPageWidth, fullPageHeight) .build() // Captures the region and renders it to the bitmap. val regionBitmap = document.renderPageToBitmap(getContext(), 0, bitmapWidth, bitmapHeight, renderConfiguration)
// You can get all visible pages by calling `PdfFragment#getVisiblePages()`. int somePageIndex = ... // In order to render just a part of the page, we first need the visible rect. RectF currentVisibleRect = new RectF(); // This saves the visible rect in PDF coordinates to `currentVisibleRect`. pdfFragment.getVisiblePdfRect(currentVisibleRect, somePageIndex); // Define a desired width for the bitmap in pixels. int bitmapWidth = 2000; // Now calculate the height, keeping the aspect ratio of the bitmap as the visible area. float ratio = bitmapWidth / currentVisibleRect.width(); // Invert the height so it's not negative (in PDF, the rect top has a higher value than the bottom). int bitmapHeight = (int) (-currentVisibleRect.height() * ratio); // Calculate the size of the full page for the desired region bitmap size. Size pageSizeInPdfPoints = document.getPageSize(somePageIndex); int fullPageWidth = (int) (pageSizeInPdfPoints.width * ratio); int fullPageHeight = (int) (pageSizeInPdfPoints.height * ratio); // Now we need to get region coordinates in relation to the full page. int x = (int) (currentVisibleRect.left * ratio); int y = (int) ((pageSizeInPdfPoints.height - currentVisibleRect.top) * ratio); // Offsets define the movement of the full page, so we move the page region in the top/left direction // by x and y and then capture it. PageRenderConfiguration renderConfiguration = new PageRenderConfiguration.Builder() .region(-x, -y, fullPageWidth, fullPageHeight) .build(); // Captures the region and renders it to the bitmap. Bitmap regionBitmap = document.renderPageToBitmap(getContext(), 0, bitmapWidth, bitmapHeight, renderConfiguration);