diff options
-rw-r--r-- | api/current.txt | 19 | ||||
-rw-r--r-- | core/jni/Android.mk | 6 | ||||
-rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
-rw-r--r-- | core/jni/android/graphics/Matrix.cpp | 8 | ||||
-rw-r--r-- | core/jni/android/graphics/pdf/PdfRenderer.cpp | 279 | ||||
-rw-r--r-- | graphics/java/android/graphics/Matrix.java | 11 | ||||
-rw-r--r-- | graphics/java/android/graphics/pdf/PdfDocument.java | 4 | ||||
-rw-r--r-- | graphics/java/android/graphics/pdf/PdfRenderer.java | 391 |
8 files changed, 718 insertions, 2 deletions
diff --git a/api/current.txt b/api/current.txt index f8b6f59..5fb305a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10117,6 +10117,7 @@ package android.graphics { ctor public Matrix(android.graphics.Matrix); method public void getValues(float[]); method public boolean invert(android.graphics.Matrix); + method public boolean isAffine(); method public boolean isIdentity(); method public void mapPoints(float[], int, float[], int, int); method public void mapPoints(float[], float[]); @@ -11356,6 +11357,24 @@ package android.graphics.pdf { method public android.graphics.pdf.PdfDocument.PageInfo.Builder setContentRect(android.graphics.Rect); } + public final class PdfRenderer implements java.lang.AutoCloseable { + ctor public PdfRenderer(android.os.ParcelFileDescriptor) throws java.io.IOException; + method public void close(); + method public void closePage(android.graphics.pdf.PdfRenderer.Page); + method public int getPageCount(); + method public android.graphics.pdf.PdfRenderer.Page openPage(int); + method public boolean shouldScaleForPrinting(); + } + + public final class PdfRenderer.Page { + method public int getHeight(); + method public int getIndex(); + method public int getWidth(); + method public void render(android.graphics.Bitmap, android.graphics.Rect, android.graphics.Matrix, int); + field public static final int RENDER_MODE_FOR_DISPLAY = 1; // 0x1 + field public static final int RENDER_MODE_FOR_PRINT = 2; // 0x2 + } + } package android.hardware { diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 726b2f7..0ad2ab2 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -5,6 +5,7 @@ LOCAL_CFLAGS += -DHAVE_CONFIG_H -DKHTML_NO_EXCEPTIONS -DGKWQ_NO_JAVA LOCAL_CFLAGS += -DNO_SUPPORT_JS_BINDING -DQT_NO_WHEELEVENT -DKHTML_NO_XBL LOCAL_CFLAGS += -U__APPLE__ LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast +LOCAL_CFLAGS += -Wno-non-virtual-dtor LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses LOCAL_CPPFLAGS += -Wno-conversion-null @@ -125,6 +126,7 @@ LOCAL_SRC_FILES:= \ android/graphics/Xfermode.cpp \ android/graphics/YuvToJpegEncoder.cpp \ android/graphics/pdf/PdfDocument.cpp \ + android/graphics/pdf/PdfRenderer.cpp \ android_media_AudioRecord.cpp \ android_media_AudioSystem.cpp \ android_media_AudioTrack.cpp \ @@ -169,6 +171,9 @@ LOCAL_C_INCLUDES += \ $(call include-path-for, libhardware_legacy)/hardware_legacy \ $(TOP)/frameworks/av/include \ $(TOP)/system/media/camera/include \ + external/pdfrenderer/core/include/fpdfapi \ + external/pdfrenderer/core/include/fpdfdoc \ + external/pdfrenderer/fpdfsdk/include \ external/skia/src/core \ external/skia/src/effects \ external/skia/src/images \ @@ -222,6 +227,7 @@ LOCAL_SHARED_LIBRARIES := \ libharfbuzz_ng \ libz \ libaudioutils \ + libpdfrenderer \ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f41af04..a4dc824 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -120,6 +120,7 @@ extern int register_android_graphics_Region(JNIEnv* env); extern int register_android_graphics_SurfaceTexture(JNIEnv* env); extern int register_android_graphics_Xfermode(JNIEnv* env); extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env); +extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); extern int register_android_view_DisplayEventReceiver(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); extern int register_android_view_RenderNodeAnimator(JNIEnv* env); @@ -1258,6 +1259,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Xfermode), REG_JNI(register_android_graphics_YuvImage), REG_JNI(register_android_graphics_pdf_PdfDocument), + REG_JNI(register_android_graphics_pdf_PdfRenderer), REG_JNI(register_android_database_CursorWindow), REG_JNI(register_android_database_SQLiteConnection), diff --git a/core/jni/android/graphics/Matrix.cpp b/core/jni/android/graphics/Matrix.cpp index 23af860..cbd20e9 100644 --- a/core/jni/android/graphics/Matrix.cpp +++ b/core/jni/android/graphics/Matrix.cpp @@ -50,10 +50,17 @@ public: SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); return obj->isIdentity() ? JNI_TRUE : JNI_FALSE; } + + static jboolean isAffine(JNIEnv* env, jobject clazz, jlong objHandle) { + SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); + return obj->asAffine(NULL) ? JNI_TRUE : JNI_FALSE; + } + static jboolean rectStaysRect(JNIEnv* env, jobject clazz, jlong objHandle) { SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); return obj->rectStaysRect() ? JNI_TRUE : JNI_FALSE; } + static void reset(JNIEnv* env, jobject clazz, jlong objHandle) { SkMatrix* obj = reinterpret_cast<SkMatrix*>(objHandle); obj->reset(); @@ -302,6 +309,7 @@ static JNINativeMethod methods[] = { {"finalizer", "(J)V", (void*) SkMatrixGlue::finalizer}, {"native_create","(J)J", (void*) SkMatrixGlue::create}, {"native_isIdentity","(J)Z", (void*) SkMatrixGlue::isIdentity}, + {"native_isAffine","(J)Z", (void*) SkMatrixGlue::isAffine}, {"native_rectStaysRect","(J)Z", (void*) SkMatrixGlue::rectStaysRect}, {"native_reset","(J)V", (void*) SkMatrixGlue::reset}, {"native_set","(JJ)V", (void*) SkMatrixGlue::set}, diff --git a/core/jni/android/graphics/pdf/PdfRenderer.cpp b/core/jni/android/graphics/pdf/PdfRenderer.cpp new file mode 100644 index 0000000..15de24a --- /dev/null +++ b/core/jni/android/graphics/pdf/PdfRenderer.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni.h" +#include "JNIHelp.h" +#include "GraphicsJNI.h" +#include "SkBitmap.h" +#include "SkMatrix.h" +#include "fpdfview.h" +#include "fsdk_rendercontext.h" + +#include <android_runtime/AndroidRuntime.h> +#include <vector> +#include <utils/Log.h> +#include <unistd.h> +#include <sys/types.h> +#include <unistd.h> + +namespace android { + +static const int RENDER_MODE_FOR_DISPLAY = 1; +static const int RENDER_MODE_FOR_PRINT = 2; + +static struct { + jfieldID x; + jfieldID y; +} gPointClassInfo; + +static Mutex sLock; + +static int sUnmatchedInitRequestCount = 0; + +static void initializeLibraryIfNeeded() { + Mutex::Autolock _l(sLock); + if (sUnmatchedInitRequestCount == 0) { + FPDF_InitLibrary(NULL); + } + sUnmatchedInitRequestCount++; +} + +static void destroyLibraryIfNeeded() { + Mutex::Autolock _l(sLock); + sUnmatchedInitRequestCount--; + if (sUnmatchedInitRequestCount == 0) { + FPDF_DestroyLibrary(); + } +} + +static int getBlock(void* param, unsigned long position, unsigned char* outBuffer, + unsigned long size) { + const int fd = reinterpret_cast<intptr_t>(param); + const int readCount = pread(fd, outBuffer, size, position); + if (readCount < 0) { + ALOGE("Cannot read from file descriptor. Error:%d", errno); + return 0; + } + return 1; +} + +static jlong nativeCreate(JNIEnv* env, jclass thiz, jint fd, jlong size) { + initializeLibraryIfNeeded(); + + FPDF_FILEACCESS loader; + loader.m_FileLen = size; + loader.m_Param = reinterpret_cast<void*>(intptr_t(fd)); + loader.m_GetBlock = &getBlock; + + FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL); + + if (!document) { + const long error = FPDF_GetLastError(); + jniThrowException(env, "java/io/IOException", + "cannot create document. Error:" + error); + destroyLibraryIfNeeded(); + return -1; + } + + return reinterpret_cast<jlong>(document); +} + +static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr, + jint pageIndex, jobject outSize) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + + FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); + + if (!page) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot load page"); + return -1; + } + + double width = 0; + double height = 0; + + const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); + + if (!result) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get page size"); + return -1; + } + + env->SetIntField(outSize, gPointClassInfo.x, width); + env->SetIntField(outSize, gPointClassInfo.y, height); + + return reinterpret_cast<jlong>(page); +} + +static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) { + FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr); + FPDF_ClosePage(page); +} + +static void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + FPDF_CloseDocument(document); + destroyLibraryIfNeeded(); +} + +static jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + return FPDF_GetPageCount(document); +} + +static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) { + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + return FPDF_VIEWERREF_GetPrintScaling(document); +} + +static void DropContext(void* data) { + delete (CRenderContext*) data; +} + +static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, int destTop, + int destRight, int destBottom, SkMatrix* transform, int flags) { + // Note: this code ignores the currently unused RENDER_NO_NATIVETEXT, + // FPDF_RENDER_LIMITEDIMAGECACHE, FPDF_RENDER_FORCEHALFTONE, FPDF_GRAYSCALE, + // and FPDF_ANNOT flags. To add support for that refer to FPDF_RenderPage_Retail + // in fpdfview.cpp + + CRenderContext* pContext = FX_NEW CRenderContext; + + CPDF_Page* pPage = (CPDF_Page*) page; + pPage->SetPrivateData((void*) 1, pContext, DropContext); + + CFX_FxgeDevice* fxgeDevice = FX_NEW CFX_FxgeDevice; + pContext->m_pDevice = fxgeDevice; + + // Reverse the bytes (last argument TRUE) since the Android + // format is ARGB while the renderer uses BGRA internally. + fxgeDevice->Attach((CFX_DIBitmap*) bitmap, 0, TRUE); + + CPDF_RenderOptions* renderOptions = pContext->m_pOptions; + + if (!renderOptions) { + renderOptions = FX_NEW CPDF_RenderOptions; + pContext->m_pOptions = renderOptions; + } + + if (flags & FPDF_LCD_TEXT) { + renderOptions->m_Flags |= RENDER_CLEARTYPE; + } else { + renderOptions->m_Flags &= ~RENDER_CLEARTYPE; + } + + const CPDF_OCContext::UsageType usage = (flags & FPDF_PRINTING) + ? CPDF_OCContext::Print : CPDF_OCContext::View; + + renderOptions->m_AddFlags = flags >> 8; + renderOptions->m_pOCContext = new CPDF_OCContext(pPage->m_pDocument, usage); + + fxgeDevice->SaveState(); + + FX_RECT clip; + clip.left = destLeft; + clip.right = destRight; + clip.top = destTop; + clip.bottom = destBottom; + fxgeDevice->SetClip_Rect(&clip); + + CPDF_RenderContext* pageContext = FX_NEW CPDF_RenderContext; + pContext->m_pContext = pageContext; + pageContext->Create(pPage); + + CFX_AffineMatrix matrix; + if (!transform) { + pPage->GetDisplayMatrix(matrix, destLeft, destTop, destRight - destLeft, + destBottom - destTop, 0); + } else { + // PDF's coordinate system origin is left-bottom while + // in graphics it is the top-left, so remap the origin. + matrix.Set(1, 0, 0, -1, 0, pPage->GetPageHeight()); + matrix.Scale(transform->getScaleX(), transform->getScaleY()); + matrix.Rotate(transform->getSkewX(), transform->getSkewY()); + matrix.Translate(transform->getTranslateX(), transform->getTranslateY()); + } + pageContext->AppendObjectList(pPage, &matrix); + + pContext->m_pRenderer = FX_NEW CPDF_ProgressiveRenderer; + pContext->m_pRenderer->Start(pageContext, fxgeDevice, renderOptions, NULL); + + fxgeDevice->RestoreState(); + + pPage->RemovePrivateData((void*) 1); + + delete pContext; +} + +static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr, + jlong bitmapPtr, jint destLeft, jint destTop, jint destRight, jint destBottom, + jlong matrixPtr, jint renderMode) { + + FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); + FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr); + SkBitmap* skBitmap = reinterpret_cast<SkBitmap*>(bitmapPtr); + SkMatrix* skMatrix = reinterpret_cast<SkMatrix*>(matrixPtr); + + skBitmap->lockPixels(); + + const int stride = skBitmap->width() * 4; + + FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap->width(), skBitmap->height(), + FPDFBitmap_BGRA, skBitmap->getPixels(), stride); + + if (!bitmap) { + ALOGE("Erorr creating bitmap"); + return; + } + + int renderFlags = 0; + if (renderMode == RENDER_MODE_FOR_DISPLAY) { + renderFlags |= FPDF_LCD_TEXT; + } else if (renderMode == RENDER_MODE_FOR_PRINT) { + renderFlags |= FPDF_PRINTING; + } + + renderPageBitmap(bitmap, page, destLeft, destTop, destRight, + destBottom, skMatrix, renderFlags); + + skBitmap->unlockPixels(); +} + +static JNINativeMethod gPdfRenderer_Methods[] = { + {"nativeCreate", "(IJ)J", (void*) nativeCreate}, + {"nativeClose", "(J)V", (void*) nativeClose}, + {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount}, + {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting}, + {"nativeRenderPage", "(JJJIIIIJI)V", (void*) nativeRenderPage}, + {"nativeOpenPageAndGetSize", "(JILandroid/graphics/Point;)J", (void*) nativeOpenPageAndGetSize}, + {"nativeClosePage", "(J)V", (void*) nativeClosePage} +}; + +int register_android_graphics_pdf_PdfRenderer(JNIEnv* env) { + int result = android::AndroidRuntime::registerNativeMethods( + env, "android/graphics/pdf/PdfRenderer", gPdfRenderer_Methods, + NELEM(gPdfRenderer_Methods)); + + jclass clazz = env->FindClass("android/graphics/Point"); + gPointClassInfo.x = env->GetFieldID(clazz, "x", "I"); + gPointClassInfo.y = env->GetFieldID(clazz, "y", "I"); + + return result; +}; + +}; diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index 66bf75c..b4e6bab 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -245,6 +245,16 @@ public class Matrix { } /** + * Gets whether this matrix is affine. An affine matrix preserves + * straight lines and has no perspective. + * + * @return Whether the matrix is affine. + */ + public boolean isAffine() { + return native_isAffine(native_instance); + } + + /** * Returns true if will map a rectangle to another rectangle. This can be * true if the matrix is identity, scale-only, or rotates a multiple of 90 * degrees. @@ -828,6 +838,7 @@ public class Matrix { private static native long native_create(long native_src_or_zero); private static native boolean native_isIdentity(long native_object); + private static native boolean native_isAffine(long native_object); private static native boolean native_rectStaysRect(long native_object); private static native void native_reset(long native_object); private static native void native_set(long native_object, diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java index f5b07c1..d603436 100644 --- a/graphics/java/android/graphics/pdf/PdfDocument.java +++ b/graphics/java/android/graphics/pdf/PdfDocument.java @@ -32,7 +32,7 @@ import java.util.List; /** * <p> * This class enables generating a PDF document from native Android content. You - * open a new document and then for every page you want to add you start a page, + * create a new document and then for every page you want to add you start a page, * write content to the page, and finish the page. After you are done with all * pages, you write the document to an output stream and close the document. * After a document is closed you should not use it anymore. Note that pages are @@ -64,7 +64,7 @@ import java.util.List; * // write the document content * document.writeTo(getOutputStream()); * - * //close the document + * // close the document * document.close(); * </pre> */ diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java new file mode 100644 index 0000000..3fa3b9f --- /dev/null +++ b/graphics/java/android/graphics/pdf/PdfRenderer.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.pdf; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.ParcelFileDescriptor; +import android.system.ErrnoException; +import android.system.OsConstants; +import dalvik.system.CloseGuard; +import libcore.io.Libcore; + +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); + * Bitmap bitmap = getBitmapReuseIfPossible(page); + * + * // say we render for showing on the screen + * page.render(bitmap, getContentBoundsInBitmap(), + * getDesiredTransformation(), Page.RENDER_MODE_FOR_DISPLAY); + * + * // do stuff with the bitmap + * + * renderer.closePage(page); + * } + * + * // close the renderer + * renderer.close(); + * </pre> + * + * @see #close() + */ +public final class PdfRenderer implements AutoCloseable { + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private final Point mTempPoint = new Point(); + + private final long mNativeDocument; + + private final int mPageCount; + + private ParcelFileDescriptor mInput; + + 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> + * + * @param input Seekable file descriptor to read from. + */ + public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException { + if (input == null) { + throw new NullPointerException("input cannot be null"); + } + + final long size; + try { + Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET); + size = Libcore.os.fstat(input.getFileDescriptor()).st_size; + } catch (ErrnoException ee) { + throw new IllegalArgumentException("file descriptor not seekable"); + } + + mInput = input; + mNativeDocument = nativeCreate(mInput.getFd(), size); + mPageCount = nativeGetPageCount(mNativeDocument); + mCloseGuard.open("close"); + } + + /** + * 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 mPageCount; + } + + /** + * 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 nativeScaleForPrinting(mNativeDocument); + } + + /** + * Opens a page for rendering. + * + * @param index The page index. + * @return A page that can be rendered. + * + * @see #closePage(PdfRenderer.Page) + */ + public Page openPage(int index) { + throwIfClosed(); + throwIfPageOpened(); + mCurrentPage = new Page(index); + return mCurrentPage; + } + + /** + * Closes a page opened for rendering. + * + * @param page The page to close. + * + * @see #openPage(int) + */ + public void closePage(@NonNull Page page) { + throwIfClosed(); + throwIfNotCurrentPage(page); + throwIfCurrentPageClosed(); + mCurrentPage.close(); + mCurrentPage = null; + } + + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + if (mInput != null) { + doClose(); + } + } finally { + super.finalize(); + } + } + + private void doClose() { + if (mCurrentPage != null) { + mCurrentPage.close(); + mCurrentPage = null; + } + nativeClose(mNativeDocument); + try { + mInput.close(); + } catch (IOException ioe) { + /* ignore - best effort */ + } + mInput = null; + mCloseGuard.close(); + } + + private void throwIfClosed() { + if (mInput == null) { + throw new IllegalStateException("Already closed"); + } + } + + private void throwIfPageOpened() { + if (mCurrentPage != null) { + throw new IllegalStateException("Current page not closed"); + } + } + + private void throwIfCurrentPageClosed() { + if (mCurrentPage == null) { + throw new IllegalStateException("Already closed"); + } + } + + private void throwIfNotCurrentPage(Page page) { + if (page != mCurrentPage) { + throw new IllegalArgumentException("Page not from document"); + } + } + + /** + * This class represents a PDF document page for rendering. + */ + public final class Page { + + /** + * 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) { + Point size = mTempPoint; + mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size); + mIndex = index; + mWidth = size.x; + mHeight = size.y; + } + + /** + * 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 coordintates 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 profided 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 Config#ARGB_8888 ARGB}. + * </p> + * <p> + * <strong>Note: </strong> The optional transformation matrix must be affine as per + * {@link android.graphics.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() != 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 (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(); + + final long transformPtr = (transform != null) ? transform.native_instance : 0; + + nativeRenderPage(mNativeDocument, mNativePage, destination.mNativeBitmap, contentLeft, + contentTop, contentRight, contentBottom, transformPtr, renderMode); + } + + void close() { + nativeClosePage(mNativePage); + mNativePage = 0; + } + } + + private static native long nativeCreate(int fd, long size); + private static native void nativeClose(long documentPtr); + private static native int nativeGetPageCount(long documentPtr); + private static native boolean nativeScaleForPrinting(long documentPtr); + private static native void nativeRenderPage(long documentPtr, long pagePtr, long destPtr, + int destLeft, int destTop, int destRight, int destBottom, long matrixPtr, int renderMode); + private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex, + Point outSize); + private static native void nativeClosePage(long pagePtr); +} |