diff options
Diffstat (limited to 'platform/android/viewer/jni/mupdf.c')
-rw-r--r-- | platform/android/viewer/jni/mupdf.c | 2885 |
1 files changed, 2885 insertions, 0 deletions
diff --git a/platform/android/viewer/jni/mupdf.c b/platform/android/viewer/jni/mupdf.c new file mode 100644 index 00000000..bc23b9ca --- /dev/null +++ b/platform/android/viewer/jni/mupdf.c @@ -0,0 +1,2885 @@ +#include <jni.h> +#include <time.h> +#include <pthread.h> +#include <android/log.h> +#include <android/bitmap.h> + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> + +#ifdef NDK_PROFILER +#include "prof.h" +#endif + +#include "mupdf/fitz.h" +#include "mupdf/pdf.h" + +#define JNI_FN(A) Java_com_artifex_mupdfdemo_ ## A +#define PACKAGENAME "com/artifex/mupdfdemo" + +#define LOG_TAG "libmupdf" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGT(...) __android_log_print(ANDROID_LOG_INFO,"alert",__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + +/* Enable to log rendering times (render each frame 100 times and time) */ +#undef TIME_DISPLAY_LIST + +#define MAX_SEARCH_HITS (500) +#define NUM_CACHE (3) +#define STRIKE_HEIGHT (0.375f) +#define UNDERLINE_HEIGHT (0.075f) +#define LINE_THICKNESS (0.07f) +#define INK_THICKNESS (4.0f) +#define SMALL_FLOAT (0.00001) +#define PROOF_RESOLUTION (300) + +enum +{ + NONE, + TEXT, + LISTBOX, + COMBOBOX, + SIGNATURE +}; + +typedef struct rect_node_s rect_node; + +struct rect_node_s +{ + fz_rect rect; + rect_node *next; +}; + +typedef struct +{ + int number; + int width; + int height; + fz_rect media_box; + fz_page *page; + rect_node *changed_rects; + rect_node *hq_changed_rects; + fz_display_list *page_list; + fz_display_list *annot_list; +} page_cache; + +typedef struct globals_s globals; + +struct globals_s +{ + fz_colorspace *colorspace; + fz_document *doc; + int resolution; + fz_context *ctx; + fz_rect *hit_bbox; + int current; + char *current_path; + + page_cache pages[NUM_CACHE]; + + int alerts_initialised; + // fin_lock and fin_lock2 are used during shutdown. The two waiting tasks + // show_alert and waitForAlertInternal respectively take these locks while + // waiting. During shutdown, the conditions are signaled and then the fin_locks + // are taken momentarily to ensure the blocked threads leave the controlled + // area of code before the mutexes and condition variables are destroyed. + pthread_mutex_t fin_lock; + pthread_mutex_t fin_lock2; + // alert_lock is the main lock guarding the variables directly below. + pthread_mutex_t alert_lock; + // Flag indicating if the alert system is active. When not active, both + // show_alert and waitForAlertInternal return immediately. + int alerts_active; + // Pointer to the alert struct passed in by show_alert, and valid while + // show_alert is blocked. + pdf_alert_event *current_alert; + // Flag and condition varibles to signal a request is present and a reply + // is present, respectively. The condition variables alone are not sufficient + // because of the pthreads permit spurious signals. + int alert_request; + int alert_reply; + pthread_cond_t alert_request_cond; + pthread_cond_t alert_reply_cond; + + // For the buffer reading mode, we need to implement stream reading, which + // needs access to the following. + JNIEnv *env; + jclass thiz; +}; + +static jfieldID global_fid; +static jfieldID buffer_fid; + +// Do our best to avoid casting warnings. +#define CAST(type, var) (type)pointer_cast(var) + +static inline void *pointer_cast(jlong l) +{ + return (void *)(intptr_t)l; +} + +static inline jlong jlong_cast(void *p) +{ + return (jlong)(intptr_t)p; +} + +static void drop_changed_rects(fz_context *ctx, rect_node **nodePtr) +{ + rect_node *node = *nodePtr; + while (node) + { + rect_node *tnode = node; + node = node->next; + fz_free(ctx, tnode); + } + + *nodePtr = NULL; +} + +static void drop_page_cache(globals *glo, page_cache *pc) +{ + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + + LOGI("Drop page %d", pc->number); + fz_drop_display_list(ctx, pc->page_list); + pc->page_list = NULL; + fz_drop_display_list(ctx, pc->annot_list); + pc->annot_list = NULL; + fz_drop_page(ctx, pc->page); + pc->page = NULL; + drop_changed_rects(ctx, &pc->changed_rects); + drop_changed_rects(ctx, &pc->hq_changed_rects); +} + +static void dump_annotation_display_lists(globals *glo) +{ + fz_context *ctx = glo->ctx; + int i; + + for (i = 0; i < NUM_CACHE; i++) { + fz_drop_display_list(ctx, glo->pages[i].annot_list); + glo->pages[i].annot_list = NULL; + } +} + +static void show_alert(globals *glo, pdf_alert_event *alert) +{ + pthread_mutex_lock(&glo->fin_lock2); + pthread_mutex_lock(&glo->alert_lock); + + LOGT("Enter show_alert: %s", alert->title); + alert->button_pressed = 0; + + if (glo->alerts_active) + { + glo->current_alert = alert; + glo->alert_request = 1; + pthread_cond_signal(&glo->alert_request_cond); + + while (glo->alerts_active && !glo->alert_reply) + pthread_cond_wait(&glo->alert_reply_cond, &glo->alert_lock); + glo->alert_reply = 0; + glo->current_alert = NULL; + } + + LOGT("Exit show_alert"); + + pthread_mutex_unlock(&glo->alert_lock); + pthread_mutex_unlock(&glo->fin_lock2); +} + +static void event_cb(fz_context *ctx, pdf_document *doc, pdf_doc_event *event, void *data) +{ + globals *glo = (globals *)data; + + switch (event->type) + { + case PDF_DOCUMENT_EVENT_ALERT: + show_alert(glo, pdf_access_alert_event(ctx, event)); + break; + } +} + +static void alerts_init(globals *glo) +{ + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (!idoc || glo->alerts_initialised) + return; + + if (idoc) + pdf_enable_js(ctx, idoc); + + glo->alerts_active = 0; + glo->alert_request = 0; + glo->alert_reply = 0; + pthread_mutex_init(&glo->fin_lock, NULL); + pthread_mutex_init(&glo->fin_lock2, NULL); + pthread_mutex_init(&glo->alert_lock, NULL); + pthread_cond_init(&glo->alert_request_cond, NULL); + pthread_cond_init(&glo->alert_reply_cond, NULL); + + pdf_set_doc_event_callback(ctx, idoc, event_cb, glo); + LOGT("alert_init"); + glo->alerts_initialised = 1; +} + +static void alerts_fin(globals *glo) +{ + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + if (!glo->alerts_initialised) + return; + + LOGT("Enter alerts_fin"); + if (idoc) + pdf_set_doc_event_callback(ctx, idoc, NULL, NULL); + + // Set alerts_active false and wake up show_alert and waitForAlertInternal, + pthread_mutex_lock(&glo->alert_lock); + glo->current_alert = NULL; + glo->alerts_active = 0; + pthread_cond_signal(&glo->alert_request_cond); + pthread_cond_signal(&glo->alert_reply_cond); + pthread_mutex_unlock(&glo->alert_lock); + + // Wait for the fin_locks. + pthread_mutex_lock(&glo->fin_lock); + pthread_mutex_unlock(&glo->fin_lock); + pthread_mutex_lock(&glo->fin_lock2); + pthread_mutex_unlock(&glo->fin_lock2); + + pthread_cond_destroy(&glo->alert_reply_cond); + pthread_cond_destroy(&glo->alert_request_cond); + pthread_mutex_destroy(&glo->alert_lock); + pthread_mutex_destroy(&glo->fin_lock2); + pthread_mutex_destroy(&glo->fin_lock); + LOGT("Exit alerts_fin"); + glo->alerts_initialised = 0; +} + +// Should only be called from the single background AsyncTask thread +static globals *get_globals(JNIEnv *env, jobject thiz) +{ + globals *glo = CAST(globals *, (*env)->GetLongField(env, thiz, global_fid)); + if (glo != NULL) + { + glo->env = env; + glo->thiz = thiz; + } + return glo; +} + +// May be called from any thread, provided the values of glo->env and glo->thiz +// are not used. +static globals *get_globals_any_thread(JNIEnv *env, jobject thiz) +{ + return (globals *)(intptr_t)((*env)->GetLongField(env, thiz, global_fid)); +} + +JNIEXPORT jlong JNICALL +JNI_FN(MuPDFCore_openFile)(JNIEnv * env, jobject thiz, jstring jfilename) +{ + const char *filename; + globals *glo; + fz_context *ctx; + jclass clazz; + +#ifdef NDK_PROFILER + monstartup("libmupdf_java.so"); +#endif + + clazz = (*env)->GetObjectClass(env, thiz); + global_fid = (*env)->GetFieldID(env, clazz, "globals", "J"); + + glo = calloc(1, sizeof(*glo)); + if (glo == NULL) + return 0; + glo->resolution = 160; + glo->alerts_initialised = 0; + +#ifdef DEBUG + /* Try and send stdout/stderr to file in debug builds. This + * path may not work on all platforms, but it works on the + * LG G3, and it's no worse than not redirecting it anywhere + * on anything else. */ + freopen("/storage/emulated/0/Download/stdout.txt", "a", stdout); + freopen("/storage/emulated/0/Download/stderr.txt", "a", stderr); +#endif + + filename = (*env)->GetStringUTFChars(env, jfilename, NULL); + if (filename == NULL) + { + LOGE("Failed to get filename"); + free(glo); + return 0; + } + + /* 128 MB store for low memory devices. Tweak as necessary. */ + glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20); + if (!ctx) + { + LOGE("Failed to initialise context"); + (*env)->ReleaseStringUTFChars(env, jfilename, filename); + free(glo); + return 0; + } + + fz_register_document_handlers(ctx); + + glo->doc = NULL; + fz_try(ctx) + { + glo->colorspace = fz_device_rgb(ctx); + + LOGI("Opening document..."); + fz_try(ctx) + { + glo->current_path = fz_strdup(ctx, (char *)filename); + glo->doc = fz_open_document(ctx, (char *)filename); + alerts_init(glo); + } + fz_catch(ctx) + { + fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open document: '%s'", filename); + } + LOGI("Done!"); + } + fz_catch(ctx) + { + LOGE("Failed: %s", ctx->error->message); + fz_drop_document(ctx, glo->doc); + glo->doc = NULL; + fz_drop_context(ctx); + glo->ctx = NULL; + free(glo); + glo = NULL; + } + + (*env)->ReleaseStringUTFChars(env, jfilename, filename); + + return jlong_cast(glo); +} + +typedef struct buffer_state_s +{ + globals *globals; + char buffer[4096]; +} +buffer_state; + +static int bufferStreamNext(fz_context *ctx, fz_stream *stream, int max) +{ + buffer_state *bs = (buffer_state *)stream->state; + globals *glo = bs->globals; + JNIEnv *env = glo->env; + jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid)); + int arrayLength = (*env)->GetArrayLength(env, array); + int len = sizeof(bs->buffer); + + if (stream->pos > arrayLength) + stream->pos = arrayLength; + if (stream->pos < 0) + stream->pos = 0; + if (len + stream->pos > arrayLength) + len = arrayLength - stream->pos; + + (*env)->GetByteArrayRegion(env, array, stream->pos, len, bs->buffer); + (*env)->DeleteLocalRef(env, array); + + stream->rp = bs->buffer; + stream->wp = stream->rp + len; + stream->pos += len; + if (len == 0) + return EOF; + return *stream->rp++; +} + +static void bufferStreamClose(fz_context *ctx, void *state) +{ + fz_free(ctx, state); +} + +static void bufferStreamSeek(fz_context *ctx, fz_stream *stream, int offset, int whence) +{ + buffer_state *bs = (buffer_state *)stream->state; + globals *glo = bs->globals; + JNIEnv *env = glo->env; + jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid)); + int arrayLength = (*env)->GetArrayLength(env, array); + + (*env)->DeleteLocalRef(env, array); + + if (whence == 0) /* SEEK_SET */ + stream->pos = offset; + else if (whence == 1) /* SEEK_CUR */ + stream->pos += offset; + else if (whence == 2) /* SEEK_END */ + stream->pos = arrayLength + offset; + + if (stream->pos > arrayLength) + stream->pos = arrayLength; + if (stream->pos < 0) + stream->pos = 0; + + stream->wp = stream->rp; +} + +JNIEXPORT jlong JNICALL +JNI_FN(MuPDFCore_openBuffer)(JNIEnv * env, jobject thiz, jstring jmagic) +{ + globals *glo; + fz_context *ctx; + jclass clazz; + fz_stream *stream = NULL; + buffer_state *bs; + const char *magic; + +#ifdef NDK_PROFILER + monstartup("libmupdf_java.so"); +#endif + + clazz = (*env)->GetObjectClass(env, thiz); + global_fid = (*env)->GetFieldID(env, clazz, "globals", "J"); + + glo = calloc(1, sizeof(*glo)); + if (glo == NULL) + return 0; + glo->resolution = 160; + glo->alerts_initialised = 0; + glo->env = env; + glo->thiz = thiz; + buffer_fid = (*env)->GetFieldID(env, clazz, "fileBuffer", "[B"); + + magic = (*env)->GetStringUTFChars(env, jmagic, NULL); + if (magic == NULL) + { + LOGE("Failed to get magic"); + free(glo); + return 0; + } + + /* 128 MB store for low memory devices. Tweak as necessary. */ + glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20); + if (!ctx) + { + LOGE("Failed to initialise context"); + (*env)->ReleaseStringUTFChars(env, jmagic, magic); + free(glo); + return 0; + } + + fz_register_document_handlers(ctx); + fz_var(stream); + + glo->doc = NULL; + fz_try(ctx) + { + bs = fz_malloc_struct(ctx, buffer_state); + bs->globals = glo; + stream = fz_new_stream(ctx, bs, bufferStreamNext, bufferStreamClose); + stream->seek = bufferStreamSeek; + + glo->colorspace = fz_device_rgb(ctx); + + LOGI("Opening document..."); + fz_try(ctx) + { + glo->current_path = NULL; + glo->doc = fz_open_document_with_stream(ctx, magic, stream); + alerts_init(glo); + } + fz_catch(ctx) + { + fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open memory document"); + } + LOGI("Done!"); + } + fz_always(ctx) + { + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + LOGE("Failed: %s", ctx->error->message); + fz_drop_document(ctx, glo->doc); + glo->doc = NULL; + fz_drop_context(ctx); + glo->ctx = NULL; + free(glo); + glo = NULL; + } + + (*env)->ReleaseStringUTFChars(env, jmagic, magic); + + return jlong_cast(glo); +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_countPagesInternal)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + int count = 0; + + fz_try(ctx) + { + count = fz_count_pages(ctx, glo->doc); + } + fz_catch(ctx) + { + LOGE("exception while counting pages: %s", ctx->error->message); + } + return count; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_fileFormatInternal)(JNIEnv * env, jobject thiz) +{ + char info[64]; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + fz_lookup_metadata(ctx, glo->doc, FZ_META_FORMAT, info, sizeof(info)); + + return (*env)->NewStringUTF(env, info); +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_isUnencryptedPDFInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals_any_thread(env, thiz); + if (glo == NULL) + return JNI_FALSE; + + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + if (idoc == NULL) + return JNI_FALSE; // Not a PDF + + int cryptVer = pdf_crypt_version(ctx, idoc); + return (cryptVer == 0) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_gotoPageInternal)(JNIEnv *env, jobject thiz, int page) +{ + int i; + int furthest; + int furthest_dist = -1; + float zoom; + fz_matrix ctm; + fz_irect bbox; + page_cache *pc; + globals *glo = get_globals(env, thiz); + if (glo == NULL) + return; + fz_context *ctx = glo->ctx; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + { + /* The page is already cached */ + glo->current = i; + return; + } + + if (glo->pages[i].page == NULL) + { + /* cache record unused, and so a good one to use */ + furthest = i; + furthest_dist = INT_MAX; + } + else + { + int dist = abs(glo->pages[i].number - page); + + /* Further away - less likely to be needed again */ + if (dist > furthest_dist) + { + furthest_dist = dist; + furthest = i; + } + } + } + + glo->current = furthest; + pc = &glo->pages[glo->current]; + + drop_page_cache(glo, pc); + + /* In the event of an error, ensure we give a non-empty page */ + pc->width = 100; + pc->height = 100; + + pc->number = page; + LOGI("Goto page %d...", page); + fz_try(ctx) + { + fz_rect rect; + LOGI("Load page %d", pc->number); + pc->page = fz_load_page(ctx, glo->doc, pc->number); + zoom = glo->resolution / 72; + fz_bound_page(ctx, pc->page, &pc->media_box); + fz_scale(&ctm, zoom, zoom); + rect = pc->media_box; + fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); + pc->width = bbox.x1-bbox.x0; + pc->height = bbox.y1-bbox.y0; + } + fz_catch(ctx) + { + LOGE("cannot make displaylist from page %d", pc->number); + } +} + +JNIEXPORT float JNICALL +JNI_FN(MuPDFCore_getPageWidth)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + LOGI("PageWidth=%d", glo->pages[glo->current].width); + return glo->pages[glo->current].width; +} + +JNIEXPORT float JNICALL +JNI_FN(MuPDFCore_getPageHeight)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + LOGI("PageHeight=%d", glo->pages[glo->current].height); + return glo->pages[glo->current].height; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_javascriptSupported)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + if (idoc) + return pdf_js_supported(ctx, idoc); + return 0; +} + +static void update_changed_rects(globals *glo, page_cache *pc, pdf_document *idoc) +{ + fz_context *ctx = glo->ctx; + fz_annot *annot; + + pdf_update_page(ctx, idoc, (pdf_page *)pc->page); + while ((annot = (fz_annot *)pdf_poll_changed_annot(ctx, idoc, (pdf_page *)pc->page)) != NULL) + { + /* FIXME: We bound the annot twice here */ + rect_node *node = fz_malloc_struct(glo->ctx, rect_node); + fz_bound_annot(ctx, annot, &node->rect); + node->next = pc->changed_rects; + pc->changed_rects = node; + + node = fz_malloc_struct(glo->ctx, rect_node); + fz_bound_annot(ctx, annot, &node->rect); + node->next = pc->hq_changed_rects; + pc->hq_changed_rects = node; + } +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_drawPage)(JNIEnv *env, jobject thiz, jobject bitmap, + int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr) +{ + AndroidBitmapInfo info; + void *pixels; + int ret; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + fz_irect bbox; + fz_rect rect; + fz_pixmap *pix = NULL; + float xscale, yscale; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + int hq = (patchW < pageW || patchH < pageH); + fz_matrix scale; + fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr; + + if (pc->page == NULL) + return 0; + + fz_var(pix); + fz_var(dev); + + LOGI("In native method\n"); + if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { + LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); + return 0; + } + + LOGI("Checking format\n"); + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return 0; + } + + LOGI("locking pixels\n"); + if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { + LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); + return 0; + } + + /* Call mupdf to render display list to screen */ + LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]", + pc->number, pageW, pageH, patchX, patchY, patchW, patchH); + + fz_try(ctx) + { + fz_irect pixbbox; + pdf_document *idoc = pdf_specifics(ctx, doc); + + if (idoc) + { + /* Update the changed-rects for both hq patch and main bitmap */ + update_changed_rects(glo, pc, idoc); + + /* Then drop the changed-rects for the bitmap we're about to + render because we are rendering the entire area */ + drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects); + } + + if (pc->page_list == NULL) + { + /* Render to list */ + pc->page_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->page_list); + fz_run_page_contents(ctx, pc->page, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->page_list); + pc->page_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + if (pc->annot_list == NULL) + { + fz_annot *annot; + pc->annot_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->annot_list); + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + fz_run_annot(ctx, annot, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->annot_list); + pc->annot_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + bbox.x0 = patchX; + bbox.y0 = patchY; + bbox.x1 = patchX + patchW; + bbox.y1 = patchY + patchH; + pixbbox = bbox; + pixbbox.x1 = pixbbox.x0 + info.width; + /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to + * match the pixels data */ + pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels); + if (pc->page_list == NULL && pc->annot_list == NULL) + { + fz_clear_pixmap_with_value(ctx, pix, 0xd0); + break; + } + fz_clear_pixmap_with_value(ctx, pix, 0xff); + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + rect = pc->media_box; + fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); + /* Now, adjust ctm so that it would give the correct page width + * heights. */ + xscale = (float)pageW/(float)(bbox.x1-bbox.x0); + yscale = (float)pageH/(float)(bbox.y1-bbox.y0); + fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale)); + rect = pc->media_box; + fz_transform_rect(&rect, &ctm); + dev = fz_new_draw_device(ctx, pix); +#ifdef TIME_DISPLAY_LIST + { + clock_t time; + int i; + + LOGI("Executing display list"); + time = clock(); + for (i=0; i<100;i++) { +#endif + if (pc->page_list) + fz_run_display_list(ctx, pc->page_list, dev, &ctm, &rect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + + if (pc->annot_list) + fz_run_display_list(ctx, pc->annot_list, dev, &ctm, &rect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + +#ifdef TIME_DISPLAY_LIST + } + time = clock() - time; + LOGI("100 renders in %d (%d per sec)", time, CLOCKS_PER_SEC); + } +#endif + fz_drop_device(ctx, dev); + dev = NULL; + fz_drop_pixmap(ctx, pix); + LOGI("Rendered"); + } + fz_always(ctx) + { + fz_drop_device(ctx, dev); + dev = NULL; + } + fz_catch(ctx) + { + LOGE("Render failed"); + } + + AndroidBitmap_unlockPixels(env, bitmap); + + return 1; +} + +static char *widget_type_string(int t) +{ + switch(t) + { + case PDF_WIDGET_TYPE_PUSHBUTTON: return "pushbutton"; + case PDF_WIDGET_TYPE_CHECKBOX: return "checkbox"; + case PDF_WIDGET_TYPE_RADIOBUTTON: return "radiobutton"; + case PDF_WIDGET_TYPE_TEXT: return "text"; + case PDF_WIDGET_TYPE_LISTBOX: return "listbox"; + case PDF_WIDGET_TYPE_COMBOBOX: return "combobox"; + case PDF_WIDGET_TYPE_SIGNATURE: return "signature"; + default: return "non-widget"; + } +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_updatePageInternal)(JNIEnv *env, jobject thiz, jobject bitmap, int page, + int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr) +{ + AndroidBitmapInfo info; + void *pixels; + int ret; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + fz_irect bbox; + fz_rect rect; + fz_pixmap *pix = NULL; + float xscale, yscale; + pdf_document *idoc; + page_cache *pc = NULL; + int hq = (patchW < pageW || patchH < pageH); + int i; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + rect_node *crect; + fz_matrix scale; + fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + { + pc = &glo->pages[i]; + break; + } + } + + if (pc == NULL) + { + /* Without a cached page object we cannot perform a partial update so + render the entire bitmap instead */ + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, page); + return JNI_FN(MuPDFCore_drawPage)(env, thiz, bitmap, pageW, pageH, patchX, patchY, patchW, patchH, (jlong)(intptr_t)cookie); + } + + idoc = pdf_specifics(ctx, doc); + + fz_var(pix); + fz_var(dev); + + LOGI("In native method\n"); + if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { + LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); + return 0; + } + + LOGI("Checking format\n"); + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return 0; + } + + LOGI("locking pixels\n"); + if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { + LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); + return 0; + } + + /* Call mupdf to render display list to screen */ + LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]", + pc->number, pageW, pageH, patchX, patchY, patchW, patchH); + + fz_try(ctx) + { + fz_annot *annot; + fz_irect pixbbox; + + if (idoc) + { + /* Update the changed-rects for both hq patch and main bitmap */ + update_changed_rects(glo, pc, idoc); + } + + if (pc->page_list == NULL) + { + /* Render to list */ + pc->page_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->page_list); + fz_run_page_contents(ctx, pc->page, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->page_list); + pc->page_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + + if (pc->annot_list == NULL) { + pc->annot_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->annot_list); + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + fz_run_annot(ctx, annot, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->annot_list); + pc->annot_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + + bbox.x0 = patchX; + bbox.y0 = patchY; + bbox.x1 = patchX + patchW; + bbox.y1 = patchY + patchH; + pixbbox = bbox; + pixbbox.x1 = pixbbox.x0 + info.width; + /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to + * match the pixels data */ + pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels); + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + rect = pc->media_box; + fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); + /* Now, adjust ctm so that it would give the correct page width + * heights. */ + xscale = (float)pageW/(float)(bbox.x1-bbox.x0); + yscale = (float)pageH/(float)(bbox.y1-bbox.y0); + fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale)); + rect = pc->media_box; + fz_transform_rect(&rect, &ctm); + + LOGI("Start partial update"); + for (crect = hq ? pc->hq_changed_rects : pc->changed_rects; crect; crect = crect->next) + { + fz_irect abox; + fz_rect arect = crect->rect; + fz_intersect_rect(fz_transform_rect(&arect, &ctm), &rect); + fz_round_rect(&abox, &arect); + + LOGI("Update rectangle (%d, %d, %d, %d)", abox.x0, abox.y0, abox.x1, abox.y1); + if (!fz_is_empty_irect(&abox)) + { + LOGI("And it isn't empty"); + fz_clear_pixmap_rect_with_value(ctx, pix, 0xff, &abox); + dev = fz_new_draw_device_with_bbox(ctx, pix, &abox); + if (pc->page_list) + fz_run_display_list(ctx, pc->page_list, dev, &ctm, &arect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + + if (pc->annot_list) + fz_run_display_list(ctx, pc->annot_list, dev, &ctm, &arect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + + fz_drop_device(ctx, dev); + dev = NULL; + } + } + LOGI("End partial update"); + + /* Drop the changed rects we've just rendered */ + drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects); + + LOGI("Rendered"); + } + fz_always(ctx) + { + fz_drop_device(ctx, dev); + dev = NULL; + } + fz_catch(ctx) + { + LOGE("Render failed"); + } + + fz_drop_pixmap(ctx, pix); + AndroidBitmap_unlockPixels(env, bitmap); + + return 1; +} + +static int +charat(fz_context *ctx, fz_stext_page *page, int idx) +{ + fz_char_and_box cab; + return fz_stext_char_at(ctx, &cab, page, idx)->c; +} + +static fz_rect +bboxcharat(fz_context *ctx, fz_stext_page *page, int idx) +{ + fz_char_and_box cab; + return fz_stext_char_at(ctx, &cab, page, idx)->bbox; +} + +static int +textlen(fz_stext_page *page) +{ + int len = 0; + int block_num; + + for (block_num = 0; block_num < page->len; block_num++) + { + fz_stext_block *block; + fz_stext_line *line; + + if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT) + continue; + block = page->blocks[block_num].u.text; + for (line = block->lines; line < block->lines + block->len; line++) + { + fz_stext_span *span; + + for (span = line->first_span; span; span = span->next) + { + len += span->len; + } + len++; /* pseudo-newline */ + } + } + return len; +} + +static int +countOutlineItems(fz_outline *outline) +{ + int count = 0; + + while (outline) + { + if (outline->dest.kind == FZ_LINK_GOTO + && outline->dest.ld.gotor.page >= 0 + && outline->title) + count++; + + count += countOutlineItems(outline->down); + outline = outline->next; + } + + return count; +} + +static int +fillInOutlineItems(JNIEnv * env, jclass olClass, jmethodID ctor, jobjectArray arr, int pos, fz_outline *outline, int level) +{ + while (outline) + { + if (outline->dest.kind == FZ_LINK_GOTO) + { + int page = outline->dest.ld.gotor.page; + if (page >= 0 && outline->title) + { + jobject ol; + jstring title = (*env)->NewStringUTF(env, outline->title); + if (title == NULL) return -1; + ol = (*env)->NewObject(env, olClass, ctor, level, title, page); + if (ol == NULL) return -1; + (*env)->SetObjectArrayElement(env, arr, pos, ol); + (*env)->DeleteLocalRef(env, ol); + (*env)->DeleteLocalRef(env, title); + pos++; + } + } + pos = fillInOutlineItems(env, olClass, ctor, arr, pos, outline->down, level+1); + if (pos < 0) return -1; + outline = outline->next; + } + + return pos; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_needsPasswordInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + return fz_needs_password(ctx, glo->doc) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_authenticatePasswordInternal)(JNIEnv *env, jobject thiz, jstring password) +{ + const char *pw; + int result; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + pw = (*env)->GetStringUTFChars(env, password, NULL); + if (pw == NULL) + return JNI_FALSE; + + result = fz_authenticate_password(ctx, glo->doc, (char *)pw); + (*env)->ReleaseStringUTFChars(env, password, pw); + return result; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_hasOutlineInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_outline *outline = fz_load_outline(ctx, glo->doc); + + fz_drop_outline(glo->ctx, outline); + return (outline == NULL) ? JNI_FALSE : JNI_TRUE; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getOutlineInternal)(JNIEnv * env, jobject thiz) +{ + jclass olClass; + jmethodID ctor; + jobjectArray arr; + jobject ol; + fz_outline *outline; + int nItems; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + jobjectArray ret; + + olClass = (*env)->FindClass(env, PACKAGENAME "/OutlineItem"); + if (olClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, olClass, "<init>", "(ILjava/lang/String;I)V"); + if (ctor == NULL) return NULL; + + outline = fz_load_outline(ctx, glo->doc); + nItems = countOutlineItems(outline); + + arr = (*env)->NewObjectArray(env, + nItems, + olClass, + NULL); + if (arr == NULL) return NULL; + + ret = fillInOutlineItems(env, olClass, ctor, arr, 0, outline, 0) > 0 + ? arr + :NULL; + fz_drop_outline(glo->ctx, outline); + return ret; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_searchPage)(JNIEnv * env, jobject thiz, jstring jtext) +{ + jclass rectClass; + jmethodID ctor; + jobjectArray arr; + jobject rect; + fz_stext_sheet *sheet = NULL; + fz_stext_page *text = NULL; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + int pos; + int len; + int i, n; + int hit_count = 0; + const char *str; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + + rectClass = (*env)->FindClass(env, "android/graphics/RectF"); + if (rectClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, rectClass, "<init>", "(FFFF)V"); + if (ctor == NULL) return NULL; + str = (*env)->GetStringUTFChars(env, jtext, NULL); + if (str == NULL) return NULL; + + fz_var(sheet); + fz_var(text); + fz_var(dev); + + fz_try(ctx) + { + if (glo->hit_bbox == NULL) + glo->hit_bbox = fz_malloc_array(ctx, MAX_SEARCH_HITS, sizeof(*glo->hit_bbox)); + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + sheet = fz_new_stext_sheet(ctx); + text = fz_new_stext_page(ctx); + dev = fz_new_stext_device(ctx, sheet, text); + fz_run_page(ctx, pc->page, dev, &ctm, NULL); + fz_drop_device(ctx, dev); + dev = NULL; + + hit_count = fz_search_stext_page(ctx, text, str, glo->hit_bbox, MAX_SEARCH_HITS); + } + fz_always(ctx) + { + fz_drop_stext_page(ctx, text); + fz_drop_stext_sheet(ctx, sheet); + fz_drop_device(ctx, dev); + } + fz_catch(ctx) + { + jclass cls; + (*env)->ReleaseStringUTFChars(env, jtext, str); + cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); + (*env)->DeleteLocalRef(env, cls); + + return NULL; + } + + (*env)->ReleaseStringUTFChars(env, jtext, str); + + arr = (*env)->NewObjectArray(env, + hit_count, + rectClass, + NULL); + if (arr == NULL) return NULL; + + for (i = 0; i < hit_count; i++) { + rect = (*env)->NewObject(env, rectClass, ctor, + (float) (glo->hit_bbox[i].x0), + (float) (glo->hit_bbox[i].y0), + (float) (glo->hit_bbox[i].x1), + (float) (glo->hit_bbox[i].y1)); + if (rect == NULL) + return NULL; + (*env)->SetObjectArrayElement(env, arr, i, rect); + (*env)->DeleteLocalRef(env, rect); + } + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_text)(JNIEnv * env, jobject thiz) +{ + jclass textCharClass; + jclass textSpanClass; + jclass textLineClass; + jclass textBlockClass; + jmethodID ctor; + jobjectArray barr = NULL; + fz_stext_sheet *sheet = NULL; + fz_stext_page *text = NULL; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + + textCharClass = (*env)->FindClass(env, PACKAGENAME "/TextChar"); + if (textCharClass == NULL) return NULL; + textSpanClass = (*env)->FindClass(env, "[L" PACKAGENAME "/TextChar;"); + if (textSpanClass == NULL) return NULL; + textLineClass = (*env)->FindClass(env, "[[L" PACKAGENAME "/TextChar;"); + if (textLineClass == NULL) return NULL; + textBlockClass = (*env)->FindClass(env, "[[[L" PACKAGENAME "/TextChar;"); + if (textBlockClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, textCharClass, "<init>", "(FFFFC)V"); + if (ctor == NULL) return NULL; + + fz_var(sheet); + fz_var(text); + fz_var(dev); + + fz_try(ctx) + { + int b, l, s, c; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + sheet = fz_new_stext_sheet(ctx); + text = fz_new_stext_page(ctx); + dev = fz_new_stext_device(ctx, sheet, text); + fz_run_page(ctx, pc->page, dev, &ctm, NULL); + fz_drop_device(ctx, dev); + dev = NULL; + + barr = (*env)->NewObjectArray(env, text->len, textBlockClass, NULL); + if (barr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (b = 0; b < text->len; b++) + { + fz_stext_block *block; + jobjectArray *larr; + + if (text->blocks[b].type != FZ_PAGE_BLOCK_TEXT) + continue; + block = text->blocks[b].u.text; + larr = (*env)->NewObjectArray(env, block->len, textLineClass, NULL); + if (larr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (l = 0; l < block->len; l++) + { + fz_stext_line *line = &block->lines[l]; + jobjectArray *sarr; + fz_stext_span *span; + int len = 0; + + for (span = line->first_span; span; span = span->next) + len++; + + sarr = (*env)->NewObjectArray(env, len, textSpanClass, NULL); + if (sarr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (s=0, span = line->first_span; span; s++, span = span->next) + { + jobjectArray *carr = (*env)->NewObjectArray(env, span->len, textCharClass, NULL); + if (carr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (c = 0; c < span->len; c++) + { + fz_stext_char *ch = &span->text[c]; + fz_rect bbox; + fz_stext_char_bbox(ctx, &bbox, span, c); + jobject cobj = (*env)->NewObject(env, textCharClass, ctor, bbox.x0, bbox.y0, bbox.x1, bbox.y1, ch->c); + if (cobj == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectfailed"); + + (*env)->SetObjectArrayElement(env, carr, c, cobj); + (*env)->DeleteLocalRef(env, cobj); + } + + (*env)->SetObjectArrayElement(env, sarr, s, carr); + (*env)->DeleteLocalRef(env, carr); + } + + (*env)->SetObjectArrayElement(env, larr, l, sarr); + (*env)->DeleteLocalRef(env, sarr); + } + + (*env)->SetObjectArrayElement(env, barr, b, larr); + (*env)->DeleteLocalRef(env, larr); + } + } + fz_always(ctx) + { + fz_drop_stext_page(ctx, text); + fz_drop_stext_sheet(ctx, sheet); + fz_drop_device(ctx, dev); + } + fz_catch(ctx) + { + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_text"); + (*env)->DeleteLocalRef(env, cls); + + return NULL; + } + + return barr; +} + +JNIEXPORT jbyteArray JNICALL +JNI_FN(MuPDFCore_textAsHtml)(JNIEnv * env, jobject thiz) +{ + fz_stext_sheet *sheet = NULL; + fz_stext_page *text = NULL; + fz_device *dev = NULL; + fz_matrix ctm; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + jbyteArray bArray = NULL; + fz_buffer *buf = NULL; + fz_output *out = NULL; + + fz_var(sheet); + fz_var(text); + fz_var(dev); + fz_var(buf); + fz_var(out); + + fz_try(ctx) + { + int b, l, s, c; + + ctm = fz_identity; + sheet = fz_new_stext_sheet(ctx); + text = fz_new_stext_page(ctx); + dev = fz_new_stext_device(ctx, sheet, text); + fz_run_page(ctx, pc->page, dev, &ctm, NULL); + fz_drop_device(ctx, dev); + dev = NULL; + + fz_analyze_text(ctx, sheet, text); + + buf = fz_new_buffer(ctx, 256); + out = fz_new_output_with_buffer(ctx, buf); + fz_printf(ctx, out, "<html>\n"); + fz_printf(ctx, out, "<style>\n"); + fz_printf(ctx, out, "body{margin:0;}\n"); + fz_printf(ctx, out, "div.page{background-color:white;}\n"); + fz_printf(ctx, out, "div.block{margin:0pt;padding:0pt;}\n"); + fz_printf(ctx, out, "div.metaline{display:table;width:100%%}\n"); + fz_printf(ctx, out, "div.line{display:table-row;}\n"); + fz_printf(ctx, out, "div.cell{display:table-cell;padding-left:0.25em;padding-right:0.25em}\n"); + //fz_printf(ctx, out, "p{margin:0;padding:0;}\n"); + fz_printf(ctx, out, "</style>\n"); + fz_printf(ctx, out, "<body style=\"margin:0\"><div style=\"padding:10px\" id=\"content\">"); + fz_print_stext_page_html(ctx, out, text); + fz_printf(ctx, out, "</div></body>\n"); + fz_printf(ctx, out, "<style>\n"); + fz_print_stext_sheet(ctx, out, sheet); + fz_printf(ctx, out, "</style>\n</html>\n"); + fz_drop_output(ctx, out); + out = NULL; + + bArray = (*env)->NewByteArray(env, buf->len); + if (bArray == NULL) + fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to make byteArray"); + (*env)->SetByteArrayRegion(env, bArray, 0, buf->len, buf->data); + + } + fz_always(ctx) + { + fz_drop_stext_page(ctx, text); + fz_drop_stext_sheet(ctx, sheet); + fz_drop_device(ctx, dev); + fz_drop_output(ctx, out); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_textAsHtml"); + (*env)->DeleteLocalRef(env, cls); + + return NULL; + } + + return bArray; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_addMarkupAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray points, fz_annot_type type) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + pdf_document *idoc = pdf_specifics(ctx, doc); + page_cache *pc = &glo->pages[glo->current]; + jclass pt_cls; + jfieldID x_fid, y_fid; + int i, n; + fz_point *pts = NULL; + float color[3]; + float alpha; + float line_height; + float line_thickness; + + if (idoc == NULL) + return; + + switch (type) + { + case FZ_ANNOT_HIGHLIGHT: + color[0] = 1.0; + color[1] = 1.0; + color[2] = 0.0; + alpha = 0.5; + line_thickness = 1.0; + line_height = 0.5; + break; + case FZ_ANNOT_UNDERLINE: + color[0] = 0.0; + color[1] = 0.0; + color[2] = 1.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = UNDERLINE_HEIGHT; + break; + case FZ_ANNOT_STRIKEOUT: + color[0] = 1.0; + color[1] = 0.0; + color[2] = 0.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = STRIKE_HEIGHT; + break; + default: + return; + } + + fz_var(pts); + fz_try(ctx) + { + fz_annot *annot; + fz_matrix ctm; + + float zoom = glo->resolution / 72; + zoom = 1.0 / zoom; + fz_scale(&ctm, zoom, zoom); + pt_cls = (*env)->FindClass(env, "android/graphics/PointF"); + if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass"); + x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F"); + if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)"); + y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F"); + if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)"); + + n = (*env)->GetArrayLength(env, points); + + pts = fz_malloc_array(ctx, n, sizeof(fz_point)); + + for (i = 0; i < n; i++) + { + jobject opt = (*env)->GetObjectArrayElement(env, points, i); + pts[i].x = opt ? (*env)->GetFloatField(env, opt, x_fid) : 0.0f; + pts[i].y = opt ? (*env)->GetFloatField(env, opt, y_fid) : 0.0f; + fz_transform_point(&pts[i], &ctm); + } + + annot = (fz_annot *)pdf_create_annot(ctx, idoc, (pdf_page *)pc->page, type); + + pdf_set_markup_annot_quadpoints(ctx, idoc, (pdf_annot *)annot, pts, n); + pdf_set_markup_appearance(ctx, idoc, (pdf_annot *)annot, color, alpha, line_thickness, line_height); + + dump_annotation_display_lists(glo); + } + fz_always(ctx) + { + fz_free(ctx, pts); + } + fz_catch(ctx) + { + LOGE("addStrikeOutAnnotation: %s failed", ctx->error->message); + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); + (*env)->DeleteLocalRef(env, cls); + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_addInkAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray arcs) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + pdf_document *idoc = pdf_specifics(ctx, doc); + page_cache *pc = &glo->pages[glo->current]; + jclass pt_cls; + jfieldID x_fid, y_fid; + int i, j, k, n; + fz_point *pts = NULL; + int *counts = NULL; + int total = 0; + float color[3]; + + if (idoc == NULL) + return; + + color[0] = 1.0; + color[1] = 0.0; + color[2] = 0.0; + + fz_var(pts); + fz_var(counts); + fz_try(ctx) + { + fz_annot *annot; + fz_matrix ctm; + + float zoom = glo->resolution / 72; + zoom = 1.0 / zoom; + fz_scale(&ctm, zoom, zoom); + pt_cls = (*env)->FindClass(env, "android/graphics/PointF"); + if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass"); + x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F"); + if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)"); + y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F"); + if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)"); + + n = (*env)->GetArrayLength(env, arcs); + + counts = fz_malloc_array(ctx, n, sizeof(int)); + + for (i = 0; i < n; i++) + { + jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i); + int count = (*env)->GetArrayLength(env, arc); + + counts[i] = count; + total += count; + } + + pts = fz_malloc_array(ctx, total, sizeof(fz_point)); + + k = 0; + for (i = 0; i < n; i++) + { + jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i); + int count = counts[i]; + + for (j = 0; j < count; j++) + { + jobject pt = (*env)->GetObjectArrayElement(env, arc, j); + + pts[k].x = pt ? (*env)->GetFloatField(env, pt, x_fid) : 0.0f; + pts[k].y = pt ? (*env)->GetFloatField(env, pt, y_fid) : 0.0f; + (*env)->DeleteLocalRef(env, pt); + fz_transform_point(&pts[k], &ctm); + k++; + } + (*env)->DeleteLocalRef(env, arc); + } + + annot = (fz_annot *)pdf_create_annot(ctx, idoc, (pdf_page *)pc->page, FZ_ANNOT_INK); + + pdf_set_ink_annot_list(ctx, idoc, (pdf_annot *)annot, pts, counts, n, color, INK_THICKNESS); + + dump_annotation_display_lists(glo); + } + fz_always(ctx) + { + fz_free(ctx, pts); + fz_free(ctx, counts); + } + fz_catch(ctx) + { + LOGE("addInkAnnotation: %s failed", ctx->error->message); + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); + (*env)->DeleteLocalRef(env, cls); + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_deleteAnnotationInternal)(JNIEnv * env, jobject thiz, int annot_index) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + pdf_document *idoc = pdf_specifics(ctx, doc); + page_cache *pc = &glo->pages[glo->current]; + fz_annot *annot; + int i; + + if (idoc == NULL) + return; + + fz_try(ctx) + { + annot = fz_first_annot(ctx, pc->page); + for (i = 0; i < annot_index && annot; i++) + annot = fz_next_annot(ctx, annot); + + if (annot) + { + pdf_delete_annot(ctx, idoc, (pdf_page *)pc->page, (pdf_annot *)annot); + dump_annotation_display_lists(glo); + } + } + fz_catch(ctx) + { + LOGE("deleteAnnotationInternal: %s", ctx->error->message); + } +} + +/* Close the document, at least enough to be able to save over it. This + * may be called again later, so must be idempotent. */ +static void close_doc(globals *glo) +{ + int i; + + fz_free(glo->ctx, glo->hit_bbox); + glo->hit_bbox = NULL; + + for (i = 0; i < NUM_CACHE; i++) + drop_page_cache(glo, &glo->pages[i]); + + alerts_fin(glo); + + fz_drop_document(glo->ctx, glo->doc); + glo->doc = NULL; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_destroying)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + + if (glo == NULL) + return; + LOGI("Destroying"); + fz_free(glo->ctx, glo->current_path); + glo->current_path = NULL; + close_doc(glo); + fz_drop_context(glo->ctx); + glo->ctx = NULL; + free(glo); +#ifdef MEMENTO + LOGI("Destroying dump start"); + Memento_listBlocks(); + Memento_stats(); + LOGI("Destroying dump end"); +#endif +#ifdef NDK_PROFILER + // Apparently we should really be writing to whatever path we get + // from calling getFilesDir() in the java part, which supposedly + // gives /sdcard/data/data/com.artifex.MuPDF/gmon.out, but that's + // unfriendly. + setenv("CPUPROFILE", "/sdcard/gmon.out", 1); + moncleanup(); +#endif +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getPageLinksInternal)(JNIEnv * env, jobject thiz, int pageNumber) +{ + jclass linkInfoClass; + jclass linkInfoInternalClass; + jclass linkInfoExternalClass; + jclass linkInfoRemoteClass; + jmethodID ctorInternal; + jmethodID ctorExternal; + jmethodID ctorRemote; + jobjectArray arr; + jobject linkInfo; + fz_matrix ctm; + float zoom; + fz_link *list; + fz_link *link; + int count; + page_cache *pc; + globals *glo = get_globals(env, thiz); + + linkInfoClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfo"); + if (linkInfoClass == NULL) return NULL; + linkInfoInternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoInternal"); + if (linkInfoInternalClass == NULL) return NULL; + linkInfoExternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoExternal"); + if (linkInfoExternalClass == NULL) return NULL; + linkInfoRemoteClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoRemote"); + if (linkInfoRemoteClass == NULL) return NULL; + ctorInternal = (*env)->GetMethodID(env, linkInfoInternalClass, "<init>", "(FFFFI)V"); + if (ctorInternal == NULL) return NULL; + ctorExternal = (*env)->GetMethodID(env, linkInfoExternalClass, "<init>", "(FFFFLjava/lang/String;)V"); + if (ctorExternal == NULL) return NULL; + ctorRemote = (*env)->GetMethodID(env, linkInfoRemoteClass, "<init>", "(FFFFLjava/lang/String;IZ)V"); + if (ctorRemote == NULL) return NULL; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->page == NULL || pc->number != pageNumber) + return NULL; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + + list = fz_load_links(glo->ctx, pc->page); + count = 0; + for (link = list; link; link = link->next) + { + switch (link->dest.kind) + { + case FZ_LINK_GOTO: + case FZ_LINK_GOTOR: + case FZ_LINK_URI: + count++ ; + } + } + + arr = (*env)->NewObjectArray(env, count, linkInfoClass, NULL); + if (arr == NULL) + { + fz_drop_link(glo->ctx, list); + return NULL; + } + + count = 0; + for (link = list; link; link = link->next) + { + fz_rect rect = link->rect; + fz_transform_rect(&rect, &ctm); + + switch (link->dest.kind) + { + case FZ_LINK_GOTO: + { + linkInfo = (*env)->NewObject(env, linkInfoInternalClass, ctorInternal, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, + link->dest.ld.gotor.page); + break; + } + + case FZ_LINK_GOTOR: + { + jstring juri = (*env)->NewStringUTF(env, link->dest.ld.gotor.file_spec); + linkInfo = (*env)->NewObject(env, linkInfoRemoteClass, ctorRemote, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, + juri, link->dest.ld.gotor.page, link->dest.ld.gotor.new_window ? JNI_TRUE : JNI_FALSE); + break; + } + + case FZ_LINK_URI: + { + jstring juri = (*env)->NewStringUTF(env, link->dest.ld.uri.uri); + linkInfo = (*env)->NewObject(env, linkInfoExternalClass, ctorExternal, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, + juri); + break; + } + + default: + continue; + } + + if (linkInfo == NULL) + { + fz_drop_link(glo->ctx, list); + return NULL; + } + (*env)->SetObjectArrayElement(env, arr, count, linkInfo); + (*env)->DeleteLocalRef(env, linkInfo); + count++; + } + fz_drop_link(glo->ctx, list); + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getWidgetAreasInternal)(JNIEnv * env, jobject thiz, int pageNumber) +{ + jclass rectFClass; + jmethodID ctor; + jobjectArray arr; + jobject rectF; + pdf_document *idoc; + pdf_widget *widget; + fz_matrix ctm; + float zoom; + int count; + page_cache *pc; + globals *glo = get_globals(env, thiz); + if (glo == NULL) + return NULL; + fz_context *ctx = glo->ctx; + + rectFClass = (*env)->FindClass(env, "android/graphics/RectF"); + if (rectFClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, rectFClass, "<init>", "(FFFF)V"); + if (ctor == NULL) return NULL; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->number != pageNumber || pc->page == NULL) + return NULL; + + idoc = pdf_specifics(ctx, glo->doc); + if (idoc == NULL) + return NULL; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + + count = 0; + for (widget = pdf_first_widget(ctx, idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(ctx, widget)) + count ++; + + arr = (*env)->NewObjectArray(env, count, rectFClass, NULL); + if (arr == NULL) return NULL; + + count = 0; + for (widget = pdf_first_widget(ctx, idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(ctx, widget)) + { + fz_rect rect; + pdf_bound_widget(ctx, widget, &rect); + fz_transform_rect(&rect, &ctm); + + rectF = (*env)->NewObject(env, rectFClass, ctor, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1); + if (rectF == NULL) return NULL; + (*env)->SetObjectArrayElement(env, arr, count, rectF); + (*env)->DeleteLocalRef(env, rectF); + + count ++; + } + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getAnnotationsInternal)(JNIEnv * env, jobject thiz, int pageNumber) +{ + jclass annotClass; + jmethodID ctor; + jobjectArray arr; + jobject jannot; + fz_annot *annot; + fz_matrix ctm; + float zoom; + int count; + page_cache *pc; + globals *glo = get_globals(env, thiz); + if (glo == NULL) + return NULL; + fz_context *ctx = glo->ctx; + + annotClass = (*env)->FindClass(env, PACKAGENAME "/Annotation"); + if (annotClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, annotClass, "<init>", "(FFFFI)V"); + if (ctor == NULL) return NULL; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->number != pageNumber || pc->page == NULL) + return NULL; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + + count = 0; + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + count ++; + + arr = (*env)->NewObjectArray(env, count, annotClass, NULL); + if (arr == NULL) return NULL; + + count = 0; + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + { + fz_rect rect; + fz_annot_type type = pdf_annot_type(ctx, (pdf_annot *)annot); + fz_bound_annot(ctx, annot, &rect); + fz_transform_rect(&rect, &ctm); + + jannot = (*env)->NewObject(env, annotClass, ctor, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, type); + if (jannot == NULL) return NULL; + (*env)->SetObjectArrayElement(env, arr, count, jannot); + (*env)->DeleteLocalRef(env, jannot); + + count ++; + } + + return arr; +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_passClickEventInternal)(JNIEnv * env, jobject thiz, int pageNumber, float x, float y) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_matrix ctm; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + float zoom; + fz_point p; + pdf_ui_event event; + int changed = 0; + page_cache *pc; + + if (idoc == NULL) + return 0; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->number != pageNumber || pc->page == NULL) + return 0; + + p.x = x; + p.y = y; + + /* Ultimately we should probably return a pointer to a java structure + * with the link details in, but for now, page number will suffice. + */ + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + fz_invert_matrix(&ctm, &ctm); + + fz_transform_point(&p, &ctm); + + fz_try(ctx) + { + event.etype = PDF_EVENT_TYPE_POINTER; + event.event.pointer.pt = p; + event.event.pointer.ptype = PDF_POINTER_DOWN; + changed = pdf_pass_event(ctx, idoc, (pdf_page *)pc->page, &event); + event.event.pointer.ptype = PDF_POINTER_UP; + changed |= pdf_pass_event(ctx, idoc, (pdf_page *)pc->page, &event); + if (changed) { + dump_annotation_display_lists(glo); + } + } + fz_catch(ctx) + { + LOGE("passClickEvent: %s", ctx->error->message); + } + + return changed; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz) +{ + char *text = ""; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + fz_try(ctx) + { + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (idoc) + { + pdf_widget *focus = pdf_focused_widget(ctx, idoc); + + if (focus) + text = pdf_text_widget_text(ctx, idoc, focus); + } + } + fz_catch(ctx) + { + LOGE("getFocusedWidgetText failed: %s", ctx->error->message); + } + + return (*env)->NewStringUTF(env, text); +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_setFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz, jstring jtext) +{ + const char *text; + int result = 0; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + text = (*env)->GetStringUTFChars(env, jtext, NULL); + if (text == NULL) + { + LOGE("Failed to get text"); + return 0; + } + + fz_try(ctx) + { + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (idoc) + { + pdf_widget *focus = pdf_focused_widget(ctx, idoc); + + if (focus) + { + result = pdf_text_widget_set_text(ctx, idoc, focus, (char *)text); + dump_annotation_display_lists(glo); + } + } + } + fz_catch(ctx) + { + LOGE("setFocusedWidgetText failed: %s", ctx->error->message); + } + + (*env)->ReleaseStringUTFChars(env, jtext, text); + + return result; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetChoiceOptions)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + int type; + int nopts, i; + char **opts = NULL; + jclass stringClass; + jobjectArray arr; + + if (idoc == NULL) + return NULL; + + focus = pdf_focused_widget(ctx, idoc); + if (focus == NULL) + return NULL; + + type = pdf_widget_get_type(ctx, focus); + if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) + return NULL; + + fz_var(opts); + fz_try(ctx) + { + nopts = pdf_choice_widget_options(ctx, idoc, focus, 0, NULL); + opts = fz_malloc(ctx, nopts * sizeof(*opts)); + (void)pdf_choice_widget_options(ctx, idoc, focus, 0, opts); + } + fz_catch(ctx) + { + fz_free(ctx, opts); + LOGE("Failed in getFocuseedWidgetChoiceOptions"); + return NULL; + } + + stringClass = (*env)->FindClass(env, "java/lang/String"); + + arr = (*env)->NewObjectArray(env, nopts, stringClass, NULL); + + for (i = 0; i < nopts; i++) + { + jstring s = (*env)->NewStringUTF(env, opts[i]); + if (s != NULL) + (*env)->SetObjectArrayElement(env, arr, i, s); + + (*env)->DeleteLocalRef(env, s); + } + + fz_free(ctx, opts); + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetChoiceSelected)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + int type; + int nsel, i; + char **sel = NULL; + jclass stringClass; + jobjectArray arr; + + if (idoc == NULL) + return NULL; + + focus = pdf_focused_widget(ctx, idoc); + if (focus == NULL) + return NULL; + + type = pdf_widget_get_type(ctx, focus); + if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) + return NULL; + + fz_var(sel); + fz_try(ctx) + { + nsel = pdf_choice_widget_value(ctx, idoc, focus, NULL); + sel = fz_malloc(ctx, nsel * sizeof(*sel)); + (void)pdf_choice_widget_value(ctx, idoc, focus, sel); + } + fz_catch(ctx) + { + fz_free(ctx, sel); + LOGE("Failed in getFocuseedWidgetChoiceOptions"); + return NULL; + } + + stringClass = (*env)->FindClass(env, "java/lang/String"); + + arr = (*env)->NewObjectArray(env, nsel, stringClass, NULL); + + for (i = 0; i < nsel; i++) + { + jstring s = (*env)->NewStringUTF(env, sel[i]); + if (s != NULL) + (*env)->SetObjectArrayElement(env, arr, i, s); + + (*env)->DeleteLocalRef(env, s); + } + + fz_free(ctx, sel); + + return arr; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_setFocusedWidgetChoiceSelectedInternal)(JNIEnv * env, jobject thiz, jobjectArray arr) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + int type; + int nsel, i; + char **sel = NULL; + jstring *objs = NULL; + + if (idoc == NULL) + return; + + focus = pdf_focused_widget(ctx, idoc); + if (focus == NULL) + return; + + type = pdf_widget_get_type(ctx, focus); + if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) + return; + + nsel = (*env)->GetArrayLength(env, arr); + + sel = calloc(nsel, sizeof(*sel)); + objs = calloc(nsel, sizeof(*objs)); + if (objs == NULL || sel == NULL) + { + free(sel); + free(objs); + LOGE("Failed in setFocusWidgetChoiceSelected"); + return; + } + + for (i = 0; i < nsel; i++) + { + objs[i] = (jstring)(*env)->GetObjectArrayElement(env, arr, i); + sel[i] = (char *)(*env)->GetStringUTFChars(env, objs[i], NULL); + } + + fz_try(ctx) + { + pdf_choice_widget_set_value(ctx, idoc, focus, nsel, sel); + dump_annotation_display_lists(glo); + } + fz_catch(ctx) + { + LOGE("Failed in setFocusWidgetChoiceSelected"); + } + + for (i = 0; i < nsel; i++) + (*env)->ReleaseStringUTFChars(env, objs[i], sel[i]); + + free(sel); + free(objs); +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetTypeInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + + if (ctx, idoc == NULL) + return NONE; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + return NONE; + + switch (pdf_widget_get_type(ctx, focus)) + { + case PDF_WIDGET_TYPE_TEXT: return TEXT; + case PDF_WIDGET_TYPE_LISTBOX: return LISTBOX; + case PDF_WIDGET_TYPE_COMBOBOX: return COMBOBOX; + case PDF_WIDGET_TYPE_SIGNATURE: return SIGNATURE; + } + + return NONE; +} + +/* This enum should be kept in line with SignatureState in MuPDFPageView.java */ +enum +{ + Signature_NoSupport, + Signature_Unsigned, + Signature_Signed +}; + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetSignatureState)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + + if (ctx, idoc == NULL) + return Signature_NoSupport; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + return Signature_NoSupport; + + if (!pdf_signatures_supported()) + return Signature_NoSupport; + + return pdf_dict_get(ctx, ((pdf_annot *)focus)->obj, PDF_NAME_V) ? Signature_Signed : Signature_Unsigned; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_checkFocusedSignatureInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + char ebuf[256] = "Failed"; + + if (idoc == NULL) + goto exit; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + goto exit; + + if (pdf_check_signature(ctx, idoc, focus, glo->current_path, ebuf, sizeof(ebuf))) + { + strcpy(ebuf, "Signature is valid"); + } + +exit: + return (*env)->NewStringUTF(env, ebuf); +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_signFocusedSignatureInternal)(JNIEnv * env, jobject thiz, jstring jkeyfile, jstring jpassword) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + const char *keyfile; + const char *password; + jboolean res; + + if (idoc == NULL) + return JNI_FALSE; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + return JNI_FALSE; + + keyfile = (*env)->GetStringUTFChars(env, jkeyfile, NULL); + password = (*env)->GetStringUTFChars(env, jpassword, NULL); + if (keyfile == NULL || password == NULL) + return JNI_FALSE; + + fz_var(res); + fz_try(ctx) + { + pdf_sign_signature(ctx, idoc, focus, keyfile, password); + dump_annotation_display_lists(glo); + res = JNI_TRUE; + } + fz_catch(ctx) + { + res = JNI_FALSE; + } + + return res; +} + +JNIEXPORT jobject JNICALL +JNI_FN(MuPDFCore_waitForAlertInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + jclass alertClass; + jmethodID ctor; + jstring title; + jstring message; + int alert_present; + pdf_alert_event alert; + + LOGT("Enter waitForAlert"); + pthread_mutex_lock(&glo->fin_lock); + pthread_mutex_lock(&glo->alert_lock); + + while (glo->alerts_active && !glo->alert_request) + pthread_cond_wait(&glo->alert_request_cond, &glo->alert_lock); + glo->alert_request = 0; + + alert_present = (glo->alerts_active && glo->current_alert); + + if (alert_present) + alert = *glo->current_alert; + + pthread_mutex_unlock(&glo->alert_lock); + pthread_mutex_unlock(&glo->fin_lock); + LOGT("Exit waitForAlert %d", alert_present); + + if (!alert_present) + return NULL; + + alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal"); + if (alertClass == NULL) + return NULL; + + ctor = (*env)->GetMethodID(env, alertClass, "<init>", "(Ljava/lang/String;IILjava/lang/String;I)V"); + if (ctor == NULL) + return NULL; + + title = (*env)->NewStringUTF(env, alert.title); + if (title == NULL) + return NULL; + + message = (*env)->NewStringUTF(env, alert.message); + if (message == NULL) + return NULL; + + return (*env)->NewObject(env, alertClass, ctor, message, alert.icon_type, alert.button_group_type, title, alert.button_pressed); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_replyToAlertInternal)(JNIEnv * env, jobject thiz, jobject alert) +{ + globals *glo = get_globals(env, thiz); + jclass alertClass; + jfieldID field; + int button_pressed; + + alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal"); + if (alertClass == NULL) + return; + + field = (*env)->GetFieldID(env, alertClass, "buttonPressed", "I"); + if (field == NULL) + return; + + button_pressed = (*env)->GetIntField(env, alert, field); + + LOGT("Enter replyToAlert"); + pthread_mutex_lock(&glo->alert_lock); + + if (glo->alerts_active && glo->current_alert) + { + // Fill in button_pressed and signal reply received. + glo->current_alert->button_pressed = button_pressed; + glo->alert_reply = 1; + pthread_cond_signal(&glo->alert_reply_cond); + } + + pthread_mutex_unlock(&glo->alert_lock); + LOGT("Exit replyToAlert"); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_startAlertsInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + + if (!glo->alerts_initialised) + return; + + LOGT("Enter startAlerts"); + pthread_mutex_lock(&glo->alert_lock); + + glo->alert_reply = 0; + glo->alert_request = 0; + glo->alerts_active = 1; + glo->current_alert = NULL; + + pthread_mutex_unlock(&glo->alert_lock); + LOGT("Exit startAlerts"); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_stopAlertsInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + + if (!glo->alerts_initialised) + return; + + LOGT("Enter stopAlerts"); + pthread_mutex_lock(&glo->alert_lock); + + glo->alert_reply = 0; + glo->alert_request = 0; + glo->alerts_active = 0; + glo->current_alert = NULL; + pthread_cond_signal(&glo->alert_reply_cond); + pthread_cond_signal(&glo->alert_request_cond); + + pthread_mutex_unlock(&glo->alert_lock); + LOGT("Exit stopAleerts"); +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_hasChangesInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + return (idoc && pdf_has_unsaved_changes(ctx, idoc)) ? JNI_TRUE : JNI_FALSE; +} + +static char *tmp_path(char *path) +{ + int f; + char *buf = malloc(strlen(path) + 6 + 1); + if (!buf) + return NULL; + + strcpy(buf, path); + strcat(buf, "XXXXXX"); + + f = mkstemp(buf); + + if (f >= 0) + { + close(f); + return buf; + } + else + { + free(buf); + return NULL; + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_saveInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (idoc && glo->current_path) + { + char *tmp; + pdf_write_options opts; + opts.do_incremental = 1; + opts.do_ascii = 0; + opts.do_expand = 0; + opts.do_garbage = 0; + opts.do_linear = 0; + + tmp = tmp_path(glo->current_path); + if (tmp) + { + int written = 0; + + fz_var(written); + fz_try(ctx) + { + FILE *fin = fopen(glo->current_path, "rb"); + FILE *fout = fopen(tmp, "wb"); + char buf[256]; + int n, err = 1; + + if (fin && fout) + { + while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) + fwrite(buf, 1, n, fout); + err = (ferror(fin) || ferror(fout)); + } + + if (fin) + fclose(fin); + if (fout) + fclose(fout); + + if (!err) + { + pdf_save_document(ctx, idoc, tmp, &opts); + written = 1; + } + } + fz_catch(ctx) + { + written = 0; + } + + if (written) + { + close_doc(glo); + rename(tmp, glo->current_path); + } + + free(tmp); + } + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_dumpMemoryInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + +#ifdef MEMENTO + LOGE("dumpMemoryInternal start"); + Memento_listNewBlocks(); + Memento_stats(); + LOGE("dumpMemoryInternal end"); +#endif +} + +JNIEXPORT jlong JNICALL +JNI_FN(MuPDFCore_createCookie)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals_any_thread(env, thiz); + if (glo == NULL) + return 0; + fz_context *ctx = glo->ctx; + + return (jlong) (intptr_t) fz_calloc_no_throw(ctx,1, sizeof(fz_cookie)); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_destroyCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr) +{ + fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr; + globals *glo = get_globals_any_thread(env, thiz); + if (glo == NULL) + return; + fz_context *ctx = glo->ctx; + + fz_free(ctx, cookie); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_abortCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr) +{ + fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr; + if (cookie != NULL) + cookie->abort = 1; +} + +static char *tmp_gproof_path(char *path) +{ + FILE *f; + int i; + char *buf = malloc(strlen(path) + 20 + 1); + if (!buf) + return NULL; + + for (i = 0; i < 10000; i++) + { + sprintf(buf, "%s.%d.gproof", path, i); + + LOGE("Trying for %s\n", buf); + f = fopen(buf, "r"); + if (f != NULL) + { + fclose(f); + continue; + } + + f = fopen(buf, "w"); + if (f != NULL) + { + fclose(f); + break; + } + } + if (i == 10000) + { + LOGE("Failed to find temp gproof name"); + free(buf); + return NULL; + } + + LOGE("Rewritten to %s\n", buf); + return buf; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_startProofInternal)(JNIEnv * env, jobject thiz, int inResolution) +{ +#ifdef SUPPORT_GPROOF + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + char *tmp; + jstring ret; + + if (!glo->doc || !glo->current_path) + return NULL; + + tmp = tmp_gproof_path(glo->current_path); + if (!tmp) + return NULL; + + int theResolution = PROOF_RESOLUTION; + if (inResolution != 0) + theResolution = inResolution; + + fz_try(ctx) + { + fz_save_gproof(ctx, glo->current_path, glo->doc, tmp, theResolution, "", ""); + + LOGE("Creating %s\n", tmp); + ret = (*env)->NewStringUTF(env, tmp); + } + fz_always(ctx) + { + free(tmp); + } + fz_catch(ctx) + { + ret = NULL; + } + return ret; +#else + return NULL; +#endif +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_endProofInternal)(JNIEnv * env, jobject thiz, jstring jfilename) +{ +#ifdef SUPPORT_GPROOF + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + const char *tmp; + + if (!glo->doc || !glo->current_path || jfilename == NULL) + return; + + tmp = (*env)->GetStringUTFChars(env, jfilename, NULL); + if (tmp) + { + LOGE("Deleting %s\n", tmp); + + unlink(tmp); + (*env)->ReleaseStringUTFChars(env, jfilename, tmp); + } +#endif +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_gprfSupportedInternal)(JNIEnv * env) +{ +#ifdef SUPPORT_GPROOF + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_getNumSepsOnPageInternal)(JNIEnv *env, jobject thiz, int page) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + int i; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + break; + } + if (i == NUM_CACHE) + return 0; + + LOGE("Counting seps on page %d", page); + + return fz_count_separations_on_page(ctx, glo->pages[i].page); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_controlSepOnPageInternal)(JNIEnv *env, jobject thiz, int page, int sep, jboolean disable) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + int i; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + break; + } + if (i == NUM_CACHE) + return; + + fz_control_separation_on_page(ctx, glo->pages[i].page, sep, disable); +} + +JNIEXPORT jobject JNICALL +JNI_FN(MuPDFCore_getSepInternal)(JNIEnv *env, jobject thiz, int page, int sep) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + const char *name; + char rgba[4]; + unsigned int bgra; + unsigned int cmyk; + jobject jname; + jclass sepClass; + jmethodID ctor; + int i; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + break; + } + if (i == NUM_CACHE) + return NULL; + + /* MuPDF returns RGBA as bytes. Android wants a packed BGRA int. */ + name = fz_get_separation_on_page(ctx, glo->pages[i].page, sep, (unsigned int *)(&rgba[0]), &cmyk); + bgra = (rgba[0] << 16) | (rgba[1]<<8) | rgba[2] | (rgba[3]<<24); + jname = name ? (*env)->NewStringUTF(env, name) : NULL; + + sepClass = (*env)->FindClass(env, PACKAGENAME "/Separation"); + if (sepClass == NULL) + return NULL; + + ctor = (*env)->GetMethodID(env, sepClass, "<init>", "(Ljava/lang/String;II)V"); + if (ctor == NULL) + return NULL; + + return (*env)->NewObject(env, sepClass, ctor, jname, bgra, cmyk); +} |