Merge "Adding APIs to render PDF documents."
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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},
|
||||
|
||||
279
core/jni/android/graphics/pdf/PdfRenderer.cpp
Normal file
279
core/jni/android/graphics/pdf/PdfRenderer.cpp
Normal file
@@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -244,6 +244,16 @@ public class Matrix {
|
||||
return native_isIdentity(native_instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
*/
|
||||
|
||||
391
graphics/java/android/graphics/pdf/PdfRenderer.java
Normal file
391
graphics/java/android/graphics/pdf/PdfRenderer.java
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user