package co.smartreceipts.android.workers.reports.pdf.renderer.imagex.compat;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.shockwave.pdfium.PdfDocument;
import com.shockwave.pdfium.PdfiumCore;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* <p>
* This class enables rendering a PDF document. This class is not thread safe.
* </p>
* <p>
* If you want to render a PDF, you create a renderer and for every page you want
* to render, you open the page, render it, and close the page. After you are done
* with rendering, you close the renderer. After the renderer is closed it should not
* be used anymore. Note that the pages are rendered one by one, i.e. you can have
* only a single page opened at any given time.
* </p>
* <p>
* A typical use of the APIs to render a PDF looks like this:
* </p>
* <pre>
* // create a new renderer
* PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
*
* // let us just render all pages
* final int pageCount = renderer.getPageCount();
* for (int i = 0; i < pageCount; i++) {
* Page page = renderer.openPage(i);
*
* // say we render for showing on the screen
* page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
*
* // do stuff with the bitmap
*
* // close the page
* page.close();
* }
*
* // close the renderer
* renderer.close();
* </pre>
*
* <h3>Print preview and print output</h3>
* <p>
* If you are using this class to rasterize a PDF for printing or show a print
* preview, it is recommended that you respect the following contract in order
* to provide a consistent user experience when seeing a preview and printing,
* i.e. the user sees a preview that is the same as the printout.
* </p>
* <ul>
* <li>
* Respect the property whether the document would like to be scaled for printing
* as per {@link #shouldScaleForPrinting()}.
* </li>
* <li>
* When scaling a document for printing the aspect ratio should be preserved.
* </li>
* <li>
* Do not inset the content with any margins from the {@link android.print.PrintAttributes}
* as the application is responsible to render it such that the margins are respected.
* </li>
* <li>
* If document page size is greater than the printed media size the content should
* be anchored to the upper left corner of the page for left-to-right locales and
* top right corner for right-to-left locales.
* </li>
* </ul>
*
* @see #close()
*/
public final class PdfRendererCompat implements AutoCloseable {
private final Point mTempPoint = new Point();
private final int pageCount;
/**
* Pdfium core for loading and rendering PDFs
*/
private final PdfiumCore pdfiumCore;
private PdfDocument pdfDocument;
private ParcelFileDescriptor input;
private Page mCurrentPage;
/** @hide */
@IntDef({
Page.RENDER_MODE_FOR_DISPLAY,
Page.RENDER_MODE_FOR_PRINT
})
@Retention(RetentionPolicy.SOURCE)
public @interface RenderMode {}
/**
* Creates a new instance.
* <p>
* <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
* i.e. its data being randomly accessed, e.g. pointing to a file.
* </p>
* <p>
* <strong>Note:</strong> This class takes ownership of the passed in file descriptor
* and is responsible for closing it when the renderer is closed.
* </p>
* <p>
* If the file is from an untrusted source it is recommended to run the renderer in a separate,
* isolated process with minimal permissions to limit the impact of security exploits.
* </p>
*
* @param context the context
* @param input Seekable file descriptor to read from.
*
* @throws java.io.IOException If an error occurs while reading the file.
* @throws java.lang.SecurityException If the file requires a password or
* the security scheme is not supported.
*/
public PdfRendererCompat(@NonNull Context context, @NonNull ParcelFileDescriptor input) throws IOException {
if (context == null) {
throw new NullPointerException("context cannot be null");
}
if (input == null) {
throw new NullPointerException("input cannot be null");
}
this.input = input;
this.pdfiumCore = new PdfiumCore(context);
this.pdfDocument = pdfiumCore.newDocument(input);
this.pageCount = pdfiumCore.getPageCount(pdfDocument);
}
/**
* Closes this renderer. You should not use this instance
* after this method is called.
*/
public void close() {
throwIfClosed();
throwIfPageOpened();
doClose();
}
/**
* Gets the number of pages in the document.
*
* @return The page count.
*/
public int getPageCount() {
throwIfClosed();
return pageCount;
}
/**
* Gets whether the document prefers to be scaled for printing.
* You should take this info account if the document is rendered
* for printing and the target media size differs from the page
* size.
*
* @return If to scale the document.
*/
public boolean shouldScaleForPrinting() {
throwIfClosed();
return false;
}
/**
* Opens a page for rendering.
*
* @param index The page index.
* @return A page that can be rendered.
*
* @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
*/
public Page openPage(int index) {
throwIfClosed();
throwIfPageOpened();
throwIfPageNotInDocument(index);
mCurrentPage = new Page(index);
return mCurrentPage;
}
@Override
protected void finalize() throws Throwable {
try {
if (input != null) {
doClose();
}
} finally {
super.finalize();
}
}
private void doClose() {
if (mCurrentPage != null) {
mCurrentPage.close();
}
pdfiumCore.closeDocument(pdfDocument);
try {
input.close();
} catch (IOException ioe) {
/* ignore - best effort */
}
input = null;
}
private void throwIfClosed() {
if (input == null) {
throw new IllegalStateException("Already closed");
}
}
private void throwIfPageOpened() {
if (mCurrentPage != null) {
throw new IllegalStateException("Current page not closed");
}
}
private void throwIfPageNotInDocument(int pageIndex) {
if (pageIndex < 0 || pageIndex >= pageCount) {
throw new IllegalArgumentException("Invalid page index");
}
}
/**
* This class represents a PDF document page for rendering.
*/
public final class Page implements AutoCloseable {
/**
* Mode to render the content for display on a screen.
*/
public static final int RENDER_MODE_FOR_DISPLAY = 1;
/**
* Mode to render the content for printing.
*/
public static final int RENDER_MODE_FOR_PRINT = 2;
private final int mIndex;
private final int mWidth;
private final int mHeight;
private long mNativePage;
private Page(int index) {
mNativePage = pdfiumCore.openPage(pdfDocument, index);
mIndex = index;
mWidth = pdfiumCore.getPageWidthPoint(pdfDocument, index);
mHeight = pdfiumCore.getPageHeightPoint(pdfDocument, index);
}
/**
* Gets the page index.
*
* @return The index.
*/
public int getIndex() {
return mIndex;
}
/**
* Gets the page width in points (1/72").
*
* @return The width in points.
*/
public int getWidth() {
return mWidth;
}
/**
* Gets the page height in points (1/72").
*
* @return The height in points.
*/
public int getHeight() {
return mHeight;
}
/**
* Renders a page to a bitmap.
* <p>
* You may optionally specify a rectangular clip in the bitmap bounds. No rendering
* outside the clip will be performed, hence it is your responsibility to initialize
* the bitmap outside the clip.
* </p>
* <p>
* You may optionally specify a matrix to transform the content from page coordinates
* which are in points (1/72") to bitmap coordinates which are in pixels. If this
* matrix is not provided this method will apply a transformation that will fit the
* whole page to the destination clip if provided or the destination bitmap if no
* clip is provided.
* </p>
* <p>
* The clip and transformation are useful for implementing tile rendering where the
* destination bitmap contains a portion of the image, for example when zooming.
* Another useful application is for printing where the size of the bitmap holding
* the page is too large and a client can render the page in stripes.
* </p>
* <p>
* <strong>Note: </strong> The destination bitmap format must be
* {@link Bitmap.Config#ARGB_8888 ARGB}.
* </p>
* <p>
* <strong>Note: </strong> The optional transformation matrix must be affine as per
* {@link Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
* rotation, scaling, translation but not a perspective transformation.
* </p>
*
* @param destination Destination bitmap to which to render.
* @param destClip Optional clip in the bitmap bounds.
* @param transform Optional transformation to apply when rendering.
* @param renderMode The render mode.
*
* @see #RENDER_MODE_FOR_DISPLAY
* @see #RENDER_MODE_FOR_PRINT
*/
public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
@Nullable Matrix transform, @RenderMode int renderMode) {
if (destination.getConfig() != Bitmap.Config.ARGB_8888) {
throw new IllegalArgumentException("Unsupported pixel format");
}
if (destClip != null) {
if (destClip.left < 0 || destClip.top < 0
|| destClip.right > destination.getWidth()
|| destClip.bottom > destination.getHeight()) {
throw new IllegalArgumentException("destBounds not in destination");
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (transform != null && !transform.isAffine()) {
throw new IllegalArgumentException("transform not affine");
}
}
if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
throw new IllegalArgumentException("Unsupported render mode");
}
if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
throw new IllegalArgumentException("Only single render mode supported");
}
final int contentLeft = (destClip != null) ? destClip.left : 0;
final int contentTop = (destClip != null) ? destClip.top : 0;
final int contentRight = (destClip != null) ? destClip.right
: destination.getWidth();
final int contentBottom = (destClip != null) ? destClip.bottom
: destination.getHeight();
pdfiumCore.renderPageBitmap(pdfDocument, destination, mIndex, contentLeft, contentTop, contentRight, contentBottom);
}
/**
* Closes this page.
*
* @see android.graphics.pdf.PdfRenderer#openPage(int)
*/
@Override
public void close() {
throwIfClosed();
doClose();
}
@Override
protected void finalize() throws Throwable {
try {
if (mNativePage != 0) {
doClose();
}
} finally {
super.finalize();
}
}
private void doClose() {
// TODO: Alter PdfiumCore support lib to allow page by page closing (currently only whole doc at once supported)
mNativePage = 0;
mCurrentPage = null;
}
private void throwIfClosed() {
if (mNativePage == 0) {
throw new IllegalStateException("Already closed");
}
}
}
}