PdfRenderer.cpp revision 12328c0fd190c7baddb5f412c48005371672dc55
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "PdfUtils.h"
18
19#include "jni.h"
20#include "JNIHelp.h"
21#include "GraphicsJNI.h"
22#include "SkBitmap.h"
23#include "SkMatrix.h"
24#include "fpdfview.h"
25
26#pragma GCC diagnostic push
27#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
28#include "fsdk_rendercontext.h"
29#pragma GCC diagnostic pop
30
31#include "core_jni_helpers.h"
32#include <vector>
33#include <utils/Log.h>
34#include <unistd.h>
35#include <sys/types.h>
36#include <unistd.h>
37
38namespace android {
39
40static const int RENDER_MODE_FOR_DISPLAY = 1;
41static const int RENDER_MODE_FOR_PRINT = 2;
42
43static struct {
44    jfieldID x;
45    jfieldID y;
46} gPointClassInfo;
47
48static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr,
49        jint pageIndex, jobject outSize) {
50    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
51
52    FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
53    if (!page) {
54        jniThrowException(env, "java/lang/IllegalStateException",
55                "cannot load page");
56        return -1;
57    }
58    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
59
60    double width = 0;
61    double height = 0;
62
63    int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
64    if (!result) {
65        jniThrowException(env, "java/lang/IllegalStateException",
66                    "cannot get page size");
67        return -1;
68    }
69    HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
70
71    env->SetIntField(outSize, gPointClassInfo.x, width);
72    env->SetIntField(outSize, gPointClassInfo.y, height);
73
74    return reinterpret_cast<jlong>(page);
75}
76
77static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) {
78    FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
79    FPDF_ClosePage(page);
80    HANDLE_PDFIUM_ERROR_STATE(env)
81}
82
83static void DropContext(void* data) {
84    delete (CRenderContext*) data;
85}
86
87static void renderPageBitmap(FPDF_BITMAP bitmap, FPDF_PAGE page, int destLeft, int destTop,
88        int destRight, int destBottom, SkMatrix* transform, int flags) {
89    // Note: this code ignores the currently unused RENDER_NO_NATIVETEXT,
90    // FPDF_RENDER_LIMITEDIMAGECACHE, FPDF_RENDER_FORCEHALFTONE, FPDF_GRAYSCALE,
91    // and FPDF_ANNOT flags. To add support for that refer to FPDF_RenderPage_Retail
92    // in fpdfview.cpp
93
94    CRenderContext* pContext = new CRenderContext;
95
96    CPDF_Page* pPage = (CPDF_Page*) page;
97    pPage->SetPrivateData((void*) 1, pContext, DropContext);
98
99    CFX_FxgeDevice* fxgeDevice = new CFX_FxgeDevice;
100    pContext->m_pDevice = fxgeDevice;
101
102    // Reverse the bytes (last argument TRUE) since the Android
103    // format is ARGB while the renderer uses BGRA internally.
104    fxgeDevice->Attach((CFX_DIBitmap*) bitmap, 0, TRUE);
105
106    CPDF_RenderOptions* renderOptions = pContext->m_pOptions;
107
108    if (!renderOptions) {
109        renderOptions = new CPDF_RenderOptions;
110        pContext->m_pOptions = renderOptions;
111    }
112
113    if (flags & FPDF_LCD_TEXT) {
114        renderOptions->m_Flags |= RENDER_CLEARTYPE;
115    } else {
116        renderOptions->m_Flags &= ~RENDER_CLEARTYPE;
117    }
118
119    const CPDF_OCContext::UsageType usage = (flags & FPDF_PRINTING)
120            ? CPDF_OCContext::Print : CPDF_OCContext::View;
121
122    renderOptions->m_AddFlags = flags >> 8;
123    renderOptions->m_pOCContext = new CPDF_OCContext(pPage->m_pDocument, usage);
124
125    fxgeDevice->SaveState();
126
127    FX_RECT clip;
128    clip.left = destLeft;
129    clip.right = destRight;
130    clip.top = destTop;
131    clip.bottom = destBottom;
132    fxgeDevice->SetClip_Rect(&clip);
133
134    CPDF_RenderContext* pageContext = new CPDF_RenderContext(pPage);
135    pContext->m_pContext = pageContext;
136
137    CFX_Matrix matrix;
138    if (!transform) {
139        pPage->GetDisplayMatrix(matrix, destLeft, destTop, destRight - destLeft,
140                destBottom - destTop, 0);
141    } else {
142        // PDF's coordinate system origin is left-bottom while
143        // in graphics it is the top-left, so remap the origin.
144        SkMatrix reflectOnX = SkMatrix::MakeScale(1, -1);
145        SkMatrix moveUp = SkMatrix::MakeTrans(0, FPDF_GetPageHeight(page));
146        SkMatrix m = SkMatrix::Concat(moveUp, reflectOnX);
147
148        // Concatenate transformation and origin transformation
149        m.setConcat(*transform, m);
150
151        SkScalar transformValues[6];
152        if (!m.asAffine(transformValues)) {
153            // Already checked for a return value of false in the caller, so this should never
154            // happen.
155            ALOGE("Error rendering page!");
156        }
157
158        matrix = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
159                  transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
160                  transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]};
161    }
162    pageContext->AppendObjectList(pPage, &matrix);
163
164    pContext->m_pRenderer = new CPDF_ProgressiveRenderer(pageContext, fxgeDevice, renderOptions);
165    pContext->m_pRenderer->Start(NULL);
166
167    fxgeDevice->RestoreState();
168
169    pPage->RemovePrivateData((void*) 1);
170
171    delete pContext;
172}
173
174static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr,
175        jobject jbitmap, jint destLeft, jint destTop, jint destRight, jint destBottom,
176        jlong matrixPtr, jint renderMode) {
177
178    FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
179    SkMatrix* skMatrix = reinterpret_cast<SkMatrix*>(matrixPtr);
180
181    SkBitmap skBitmap;
182    GraphicsJNI::getSkBitmap(env, jbitmap, &skBitmap);
183
184    SkAutoLockPixels alp(skBitmap);
185
186    const int stride = skBitmap.width() * 4;
187
188    FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(),
189            FPDFBitmap_BGRA, skBitmap.getPixels(), stride);
190
191    if (!bitmap) {
192        ALOGE("Erorr creating bitmap");
193        return;
194    }
195
196    int renderFlags = 0;
197    if (renderMode == RENDER_MODE_FOR_DISPLAY) {
198        renderFlags |= FPDF_LCD_TEXT;
199    } else if (renderMode == RENDER_MODE_FOR_PRINT) {
200        renderFlags |= FPDF_PRINTING;
201    }
202
203    if (skMatrix && !skMatrix->asAffine(NULL)) {
204        jniThrowException(env, "java/lang/IllegalArgumentException",
205                "transform matrix has perspective. Only affine matrices are allowed.");
206        return;
207    }
208
209    renderPageBitmap(bitmap, page, destLeft, destTop, destRight,
210            destBottom, skMatrix, renderFlags);
211
212    skBitmap.notifyPixelsChanged();
213}
214
215static const JNINativeMethod gPdfRenderer_Methods[] = {
216    {"nativeCreate", "(IJ)J", (void*) nativeOpen},
217    {"nativeClose", "(J)V", (void*) nativeClose},
218    {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
219    {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
220    {"nativeRenderPage", "(JJLandroid/graphics/Bitmap;IIIIJI)V", (void*) nativeRenderPage},
221    {"nativeOpenPageAndGetSize", "(JILandroid/graphics/Point;)J", (void*) nativeOpenPageAndGetSize},
222    {"nativeClosePage", "(J)V", (void*) nativeClosePage}
223};
224
225int register_android_graphics_pdf_PdfRenderer(JNIEnv* env) {
226    int result = RegisterMethodsOrDie(
227            env, "android/graphics/pdf/PdfRenderer", gPdfRenderer_Methods,
228            NELEM(gPdfRenderer_Methods));
229
230    jclass clazz = FindClassOrDie(env, "android/graphics/Point");
231    gPointClassInfo.x = GetFieldIDOrDie(env, clazz, "x", "I");
232    gPointClassInfo.y = GetFieldIDOrDie(env, clazz, "y", "I");
233
234    return result;
235};
236
237};
238