Merge "Adding APIs to render PDF documents."

This commit is contained in:
Svetoslav
2014-05-15 20:15:06 +00:00
committed by Android (Google) Code Review
8 changed files with 718 additions and 2 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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),

View File

@@ -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},

View 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;
};
};

View File

@@ -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,

View File

@@ -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>
*/

View 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);
}