diff options
author | Svetoslav <svetoslavganov@google.com> | 2014-04-24 18:40:42 -0700 |
---|---|---|
committer | Svetoslav <svetoslavganov@google.com> | 2014-05-13 18:32:42 -0700 |
commit | 2961769ea94f69c191a2dd785b2504666c7292d0 (patch) | |
tree | e5bf30a50c60df65889a60b2c7070300a8212393 | |
parent | bee74c2b479153bb6a4b9e03f068658042a5fdfc (diff) | |
download | frameworks_base-2961769ea94f69c191a2dd785b2504666c7292d0.zip frameworks_base-2961769ea94f69c191a2dd785b2504666c7292d0.tar.gz frameworks_base-2961769ea94f69c191a2dd785b2504666c7292d0.tar.bz2 |
Adding APIs to render PDF documents.
We need to render PDF documents for two main use cases. First,
for print preview. Second, for resterizing the PDF document by
a print service before passing it to a printer which does not
natively support PDF (most consumer ones).
Adding PDF rendering APIs improves guarantees for print quality
as the same library is used for preview and rasterization. Also
print vendors do not have to license third-party rendering engines.
Last but not least as the platform uses PDF as its main print
format it should also be able to natively render it.
Change-Id: I57004a435db147663cafea40cf3296465aba7f99
-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 36aa4a0..eb4f2d5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10113,6 +10113,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[]); @@ -11352,6 +11353,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 7dc639d..ce653a5 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 01d8814..e81440b 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -119,6 +119,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); @@ -1248,6 +1249,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); +} |