]> rtime.felk.cvut.cz Git - hornmich/skoda-qr-demo.git/blob - QRScanner/mobile/jni/mupdf.c
Add MuPDF native source codes
[hornmich/skoda-qr-demo.git] / QRScanner / mobile / jni / mupdf.c
1 #include <jni.h>
2 #include <time.h>
3 #include <pthread.h>
4 #include <android/log.h>
5 #include <android/bitmap.h>
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <math.h>
10
11 #ifdef NDK_PROFILER
12 #include "prof.h"
13 #endif
14
15 #include "mupdf/fitz.h"
16 #include "mupdf/pdf.h"
17
18 #define JNI_FN(A) Java_cz_cvut_fel_dce_qrscanner_mupdf_ ## A
19 #define PACKAGENAME "cz/cvut/fel/dce/qrscanner/mupdf"
20
21 #define LOG_TAG "libmupdf"
22 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
23 #define LOGT(...) __android_log_print(ANDROID_LOG_INFO,"alert",__VA_ARGS__)
24 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
25
26 /* Set to 1 to enable debug log traces. */
27 #define DEBUG 0
28
29 /* Enable to log rendering times (render each frame 100 times and time) */
30 #undef TIME_DISPLAY_LIST
31
32 #define MAX_SEARCH_HITS (500)
33 #define NUM_CACHE (3)
34 #define STRIKE_HEIGHT (0.375f)
35 #define UNDERLINE_HEIGHT (0.075f)
36 #define LINE_THICKNESS (0.07f)
37 #define INK_THICKNESS (4.0f)
38 #define SMALL_FLOAT (0.00001)
39
40 enum
41 {
42         NONE,
43         TEXT,
44         LISTBOX,
45         COMBOBOX,
46         SIGNATURE
47 };
48
49 typedef struct rect_node_s rect_node;
50
51 struct rect_node_s
52 {
53         fz_rect rect;
54         rect_node *next;
55 };
56
57 typedef struct
58 {
59         int number;
60         int width;
61         int height;
62         fz_rect media_box;
63         fz_page *page;
64         rect_node *changed_rects;
65         rect_node *hq_changed_rects;
66         fz_display_list *page_list;
67         fz_display_list *annot_list;
68 } page_cache;
69
70 typedef struct globals_s globals;
71
72 struct globals_s
73 {
74         fz_colorspace *colorspace;
75         fz_document *doc;
76         int resolution;
77         fz_context *ctx;
78         fz_rect *hit_bbox;
79         int current;
80         char *current_path;
81
82         page_cache pages[NUM_CACHE];
83
84         int alerts_initialised;
85         // fin_lock and fin_lock2 are used during shutdown. The two waiting tasks
86         // show_alert and waitForAlertInternal respectively take these locks while
87         // waiting. During shutdown, the conditions are signaled and then the fin_locks
88         // are taken momentarily to ensure the blocked threads leave the controlled
89         // area of code before the mutexes and condition variables are destroyed.
90         pthread_mutex_t fin_lock;
91         pthread_mutex_t fin_lock2;
92         // alert_lock is the main lock guarding the variables directly below.
93         pthread_mutex_t alert_lock;
94         // Flag indicating if the alert system is active. When not active, both
95         // show_alert and waitForAlertInternal return immediately.
96         int alerts_active;
97         // Pointer to the alert struct passed in by show_alert, and valid while
98         // show_alert is blocked.
99         pdf_alert_event *current_alert;
100         // Flag and condition varibles to signal a request is present and a reply
101         // is present, respectively. The condition variables alone are not sufficient
102         // because of the pthreads permit spurious signals.
103         int alert_request;
104         int alert_reply;
105         pthread_cond_t alert_request_cond;
106         pthread_cond_t alert_reply_cond;
107
108         // For the buffer reading mode, we need to implement stream reading, which
109         // needs access to the following.
110         JNIEnv *env;
111         jclass thiz;
112 };
113
114 static jfieldID global_fid;
115 static jfieldID buffer_fid;
116
117 static void drop_changed_rects(fz_context *ctx, rect_node **nodePtr)
118 {
119         rect_node *node = *nodePtr;
120         while (node)
121         {
122                 rect_node *tnode = node;
123                 node = node->next;
124                 fz_free(ctx, tnode);
125         }
126
127         *nodePtr = NULL;
128 }
129
130 static void drop_page_cache(globals *glo, page_cache *pc)
131 {
132         fz_context *ctx = glo->ctx;
133         fz_document *doc = glo->doc;
134
135         LOGI("Drop page %d", pc->number);
136         fz_drop_display_list(ctx, pc->page_list);
137         pc->page_list = NULL;
138         fz_drop_display_list(ctx, pc->annot_list);
139         pc->annot_list = NULL;
140         fz_free_page(doc, pc->page);
141         pc->page = NULL;
142         drop_changed_rects(ctx, &pc->changed_rects);
143         drop_changed_rects(ctx, &pc->hq_changed_rects);
144 }
145
146 static void dump_annotation_display_lists(globals *glo)
147 {
148         fz_context *ctx = glo->ctx;
149         int i;
150
151         for (i = 0; i < NUM_CACHE; i++) {
152                 fz_drop_display_list(ctx, glo->pages[i].annot_list);
153                 glo->pages[i].annot_list = NULL;
154         }
155 }
156
157 static void show_alert(globals *glo, pdf_alert_event *alert)
158 {
159         pthread_mutex_lock(&glo->fin_lock2);
160         pthread_mutex_lock(&glo->alert_lock);
161
162         LOGT("Enter show_alert: %s", alert->title);
163         alert->button_pressed = 0;
164
165         if (glo->alerts_active)
166         {
167                 glo->current_alert = alert;
168                 glo->alert_request = 1;
169                 pthread_cond_signal(&glo->alert_request_cond);
170
171                 while (glo->alerts_active && !glo->alert_reply)
172                         pthread_cond_wait(&glo->alert_reply_cond, &glo->alert_lock);
173                 glo->alert_reply = 0;
174                 glo->current_alert = NULL;
175         }
176
177         LOGT("Exit show_alert");
178
179         pthread_mutex_unlock(&glo->alert_lock);
180         pthread_mutex_unlock(&glo->fin_lock2);
181 }
182
183 static void event_cb(pdf_doc_event *event, void *data)
184 {
185         globals *glo = (globals *)data;
186
187         switch (event->type)
188         {
189         case PDF_DOCUMENT_EVENT_ALERT:
190                 show_alert(glo, pdf_access_alert_event(event));
191                 break;
192         }
193 }
194
195 static void alerts_init(globals *glo)
196 {
197         pdf_document *idoc = pdf_specifics(glo->doc);
198
199         if (!idoc || glo->alerts_initialised)
200                 return;
201
202         if (idoc)
203                 pdf_enable_js(idoc);
204
205         glo->alerts_active = 0;
206         glo->alert_request = 0;
207         glo->alert_reply = 0;
208         pthread_mutex_init(&glo->fin_lock, NULL);
209         pthread_mutex_init(&glo->fin_lock2, NULL);
210         pthread_mutex_init(&glo->alert_lock, NULL);
211         pthread_cond_init(&glo->alert_request_cond, NULL);
212         pthread_cond_init(&glo->alert_reply_cond, NULL);
213
214         pdf_set_doc_event_callback(idoc, event_cb, glo);
215         LOGT("alert_init");
216         glo->alerts_initialised = 1;
217 }
218
219 static void alerts_fin(globals *glo)
220 {
221         pdf_document *idoc = pdf_specifics(glo->doc);
222         if (!glo->alerts_initialised)
223                 return;
224
225         LOGT("Enter alerts_fin");
226         if (idoc)
227                 pdf_set_doc_event_callback(idoc, NULL, NULL);
228
229         // Set alerts_active false and wake up show_alert and waitForAlertInternal,
230         pthread_mutex_lock(&glo->alert_lock);
231         glo->current_alert = NULL;
232         glo->alerts_active = 0;
233         pthread_cond_signal(&glo->alert_request_cond);
234         pthread_cond_signal(&glo->alert_reply_cond);
235         pthread_mutex_unlock(&glo->alert_lock);
236
237         // Wait for the fin_locks.
238         pthread_mutex_lock(&glo->fin_lock);
239         pthread_mutex_unlock(&glo->fin_lock);
240         pthread_mutex_lock(&glo->fin_lock2);
241         pthread_mutex_unlock(&glo->fin_lock2);
242
243         pthread_cond_destroy(&glo->alert_reply_cond);
244         pthread_cond_destroy(&glo->alert_request_cond);
245         pthread_mutex_destroy(&glo->alert_lock);
246         pthread_mutex_destroy(&glo->fin_lock2);
247         pthread_mutex_destroy(&glo->fin_lock);
248         LOGT("Exit alerts_fin");
249         glo->alerts_initialised = 0;
250 }
251
252 // Should only be called from the single background AsyncTask thread
253 static globals *get_globals(JNIEnv *env, jobject thiz)
254 {
255         globals *glo = (globals *)(intptr_t)((*env)->GetLongField(env, thiz, global_fid));
256         if (glo != NULL)
257         {
258                 glo->env = env;
259                 glo->thiz = thiz;
260         }
261         return glo;
262 }
263
264 // May be called from any thread, provided the values of glo->env and glo->thiz
265 // are not used.
266 static globals *get_globals_any_thread(JNIEnv *env, jobject thiz)
267 {
268         return (globals *)(intptr_t)((*env)->GetLongField(env, thiz, global_fid));
269 }
270
271 JNIEXPORT jlong JNICALL
272 JNI_FN(MuPDFCore_openFile)(JNIEnv * env, jobject thiz, jstring jfilename)
273 {
274         const char *filename;
275         globals *glo;
276         fz_context *ctx;
277         jclass clazz;
278
279 #ifdef NDK_PROFILER
280         monstartup("libmupdf.so");
281 #endif
282
283         clazz = (*env)->GetObjectClass(env, thiz);
284         global_fid = (*env)->GetFieldID(env, clazz, "globals", "J");
285
286         glo = calloc(1, sizeof(*glo));
287         if (glo == NULL)
288                 return 0;
289         glo->resolution = 160;
290         glo->alerts_initialised = 0;
291
292         filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
293         if (filename == NULL)
294         {
295                 LOGE("Failed to get filename");
296                 free(glo);
297                 return 0;
298         }
299
300         /* 128 MB store for low memory devices. Tweak as necessary. */
301         glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20);
302         if (!ctx)
303         {
304                 LOGE("Failed to initialise context");
305                 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
306                 free(glo);
307                 return 0;
308         }
309
310         fz_register_document_handlers(ctx);
311
312         glo->doc = NULL;
313         fz_try(ctx)
314         {
315                 glo->colorspace = fz_device_rgb(ctx);
316
317                 LOGI("Opening document...");
318                 fz_try(ctx)
319                 {
320                         glo->current_path = fz_strdup(ctx, (char *)filename);
321                         glo->doc = fz_open_document(ctx, (char *)filename);
322                         alerts_init(glo);
323                 }
324                 fz_catch(ctx)
325                 {
326                         fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open document: '%s'", filename);
327                 }
328                 LOGI("Done!");
329         }
330         fz_catch(ctx)
331         {
332                 LOGE("Failed: %s", ctx->error->message);
333                 fz_close_document(glo->doc);
334                 glo->doc = NULL;
335                 fz_free_context(ctx);
336                 glo->ctx = NULL;
337                 free(glo);
338                 glo = NULL;
339         }
340
341         (*env)->ReleaseStringUTFChars(env, jfilename, filename);
342
343         return (jlong)(intptr_t)glo;
344 }
345
346 typedef struct buffer_state_s
347 {
348         globals *globals;
349         char buffer[4096];
350 }
351 buffer_state;
352
353 static int bufferStreamNext(fz_stream *stream, int max)
354 {
355         buffer_state *bs = (buffer_state *)stream->state;
356         globals *glo = bs->globals;
357         JNIEnv *env = glo->env;
358         jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid));
359         int arrayLength = (*env)->GetArrayLength(env, array);
360         int len = sizeof(bs->buffer);
361
362         if (stream->pos > arrayLength)
363                 stream->pos = arrayLength;
364         if (stream->pos < 0)
365                 stream->pos = 0;
366         if (len + stream->pos > arrayLength)
367                 len = arrayLength - stream->pos;
368
369         (*env)->GetByteArrayRegion(env, array, stream->pos, len, bs->buffer);
370         (*env)->DeleteLocalRef(env, array);
371
372         stream->rp = bs->buffer;
373         stream->wp = stream->rp + len;
374         stream->pos += len;
375         if (len == 0)
376                 return EOF;
377         return *stream->rp++;
378 }
379
380 static void bufferStreamClose(fz_context *ctx, void *state)
381 {
382         fz_free(ctx, state);
383 }
384
385 static void bufferStreamSeek(fz_stream *stream, int offset, int whence)
386 {
387         buffer_state *bs = (buffer_state *)stream->state;
388         globals *glo = bs->globals;
389         JNIEnv *env = glo->env;
390         jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid));
391         int arrayLength = (*env)->GetArrayLength(env, array);
392
393         (*env)->DeleteLocalRef(env, array);
394
395         if (whence == 0) /* SEEK_SET */
396                 stream->pos = offset;
397         else if (whence == 1) /* SEEK_CUR */
398                 stream->pos += offset;
399         else if (whence == 2) /* SEEK_END */
400                 stream->pos = arrayLength + offset;
401
402         if (stream->pos > arrayLength)
403                 stream->pos = arrayLength;
404         if (stream->pos < 0)
405                 stream->pos = 0;
406
407         stream->wp = stream->rp;
408 }
409
410 JNIEXPORT jlong JNICALL
411 JNI_FN(MuPDFCore_openBuffer)(JNIEnv * env, jobject thiz, jstring jmagic)
412 {
413         globals *glo;
414         fz_context *ctx;
415         jclass clazz;
416         fz_stream *stream = NULL;
417         buffer_state *bs;
418         const char *magic;
419
420 #ifdef NDK_PROFILER
421         monstartup("libmupdf.so");
422 #endif
423
424         clazz = (*env)->GetObjectClass(env, thiz);
425         global_fid = (*env)->GetFieldID(env, clazz, "globals", "J");
426
427         glo = calloc(1, sizeof(*glo));
428         if (glo == NULL)
429                 return 0;
430         glo->resolution = 160;
431         glo->alerts_initialised = 0;
432         glo->env = env;
433         glo->thiz = thiz;
434         buffer_fid = (*env)->GetFieldID(env, clazz, "fileBuffer", "[B");
435
436         magic = (*env)->GetStringUTFChars(env, jmagic, NULL);
437         if (magic == NULL)
438         {
439                 LOGE("Failed to get magic");
440                 free(glo);
441                 return 0;
442         }
443
444         /* 128 MB store for low memory devices. Tweak as necessary. */
445         glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20);
446         if (!ctx)
447         {
448                 LOGE("Failed to initialise context");
449                 (*env)->ReleaseStringUTFChars(env, jmagic, magic);
450                 free(glo);
451                 return 0;
452         }
453
454         fz_register_document_handlers(ctx);
455         fz_var(stream);
456
457         glo->doc = NULL;
458         fz_try(ctx)
459         {
460                 bs = fz_malloc_struct(ctx, buffer_state);
461                 bs->globals = glo;
462                 stream = fz_new_stream(ctx, bs, bufferStreamNext, bufferStreamClose, NULL);
463                 stream->seek = bufferStreamSeek;
464
465                 glo->colorspace = fz_device_rgb(ctx);
466
467                 LOGI("Opening document...");
468                 fz_try(ctx)
469                 {
470                         glo->current_path = NULL;
471                         glo->doc = fz_open_document_with_stream(ctx, magic, stream);
472                         alerts_init(glo);
473                 }
474                 fz_catch(ctx)
475                 {
476                         fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open memory document");
477                 }
478                 LOGI("Done!");
479         }
480         fz_always(ctx)
481         {
482                 fz_close(stream);
483         }
484         fz_catch(ctx)
485         {
486                 LOGE("Failed: %s", ctx->error->message);
487                 fz_close_document(glo->doc);
488                 glo->doc = NULL;
489                 fz_free_context(ctx);
490                 glo->ctx = NULL;
491                 free(glo);
492                 glo = NULL;
493         }
494
495         (*env)->ReleaseStringUTFChars(env, jmagic, magic);
496
497         return (jlong)(intptr_t)glo;
498 }
499
500 JNIEXPORT int JNICALL
501 JNI_FN(MuPDFCore_countPagesInternal)(JNIEnv *env, jobject thiz)
502 {
503         globals *glo = get_globals(env, thiz);
504         fz_context *ctx = glo->ctx;
505         int count = 0;
506
507         fz_try(ctx)
508         {
509                 count = fz_count_pages(glo->doc);
510         }
511         fz_catch(ctx)
512         {
513                 LOGE("exception while counting pages: %s", ctx->error->message);
514         }
515         return count;
516 }
517
518 JNIEXPORT jstring JNICALL
519 JNI_FN(MuPDFCore_fileFormatInternal)(JNIEnv * env, jobject thiz)
520 {
521         char info[64];
522         globals *glo = get_globals(env, thiz);
523
524         fz_meta(glo->doc, FZ_META_FORMAT_INFO, info, sizeof(info));
525
526         return (*env)->NewStringUTF(env, info);
527 }
528
529 JNIEXPORT jboolean JNICALL
530 JNI_FN(MuPDFCore_isUnencryptedPDFInternal)(JNIEnv * env, jobject thiz)
531 {
532         globals *glo = get_globals_any_thread(env, thiz);
533         if (glo == NULL)
534                 return JNI_FALSE;
535
536         pdf_document *idoc = pdf_specifics(glo->doc);
537         if (idoc == NULL)
538                 return JNI_FALSE; // Not a PDF
539
540         int cryptVer = pdf_crypt_version(idoc);
541         return (cryptVer == 0) ? JNI_TRUE : JNI_FALSE;
542 }
543
544
545 JNIEXPORT void JNICALL
546 JNI_FN(MuPDFCore_gotoPageInternal)(JNIEnv *env, jobject thiz, int page)
547 {
548         int i;
549         int furthest;
550         int furthest_dist = -1;
551         float zoom;
552         fz_matrix ctm;
553         fz_irect bbox;
554         page_cache *pc;
555         globals *glo = get_globals(env, thiz);
556         if (glo == NULL)
557                 return;
558         fz_context *ctx = glo->ctx;
559
560         for (i = 0; i < NUM_CACHE; i++)
561         {
562                 if (glo->pages[i].page != NULL && glo->pages[i].number == page)
563                 {
564                         /* The page is already cached */
565                         glo->current = i;
566                         return;
567                 }
568
569                 if (glo->pages[i].page == NULL)
570                 {
571                         /* cache record unused, and so a good one to use */
572                         furthest = i;
573                         furthest_dist = INT_MAX;
574                 }
575                 else
576                 {
577                         int dist = abs(glo->pages[i].number - page);
578
579                         /* Further away - less likely to be needed again */
580                         if (dist > furthest_dist)
581                         {
582                                 furthest_dist = dist;
583                                 furthest = i;
584                         }
585                 }
586         }
587
588         glo->current = furthest;
589         pc = &glo->pages[glo->current];
590
591         drop_page_cache(glo, pc);
592
593         /* In the event of an error, ensure we give a non-empty page */
594         pc->width = 100;
595         pc->height = 100;
596
597         pc->number = page;
598         LOGI("Goto page %d...", page);
599         fz_try(ctx)
600         {
601                 fz_rect rect;
602                 LOGI("Load page %d", pc->number);
603                 pc->page = fz_load_page(glo->doc, pc->number);
604                 zoom = glo->resolution / 72;
605                 fz_bound_page(glo->doc, pc->page, &pc->media_box);
606                 fz_scale(&ctm, zoom, zoom);
607                 rect = pc->media_box;
608                 fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm));
609                 pc->width = bbox.x1-bbox.x0;
610                 pc->height = bbox.y1-bbox.y0;
611         }
612         fz_catch(ctx)
613         {
614                 LOGE("cannot make displaylist from page %d", pc->number);
615         }
616 }
617
618 JNIEXPORT float JNICALL
619 JNI_FN(MuPDFCore_getPageWidth)(JNIEnv *env, jobject thiz)
620 {
621         globals *glo = get_globals(env, thiz);
622         LOGI("PageWidth=%d", glo->pages[glo->current].width);
623         return glo->pages[glo->current].width;
624 }
625
626 JNIEXPORT float JNICALL
627 JNI_FN(MuPDFCore_getPageHeight)(JNIEnv *env, jobject thiz)
628 {
629         globals *glo = get_globals(env, thiz);
630         LOGI("PageHeight=%d", glo->pages[glo->current].height);
631         return glo->pages[glo->current].height;
632 }
633
634 JNIEXPORT jboolean JNICALL
635 JNI_FN(MuPDFCore_javascriptSupported)(JNIEnv *env, jobject thiz)
636 {
637         globals *glo = get_globals(env, thiz);
638         pdf_document *idoc = pdf_specifics(glo->doc);
639         return pdf_js_supported(idoc);
640 }
641
642 static void update_changed_rects(globals *glo, page_cache *pc, pdf_document *idoc)
643 {
644         fz_annot *annot;
645
646         pdf_update_page(idoc, (pdf_page *)pc->page);
647         while ((annot = (fz_annot *)pdf_poll_changed_annot(idoc, (pdf_page *)pc->page)) != NULL)
648         {
649                 /* FIXME: We bound the annot twice here */
650                 rect_node *node = fz_malloc_struct(glo->ctx, rect_node);
651                 fz_bound_annot(glo->doc, annot, &node->rect);
652                 node->next = pc->changed_rects;
653                 pc->changed_rects = node;
654
655                 node = fz_malloc_struct(glo->ctx, rect_node);
656                 fz_bound_annot(glo->doc, annot, &node->rect);
657                 node->next = pc->hq_changed_rects;
658                 pc->hq_changed_rects = node;
659         }
660 }
661
662 JNIEXPORT jboolean JNICALL
663 JNI_FN(MuPDFCore_drawPage)(JNIEnv *env, jobject thiz, jobject bitmap,
664                 int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr)
665 {
666         AndroidBitmapInfo info;
667         void *pixels;
668         int ret;
669         fz_device *dev = NULL;
670         float zoom;
671         fz_matrix ctm;
672         fz_irect bbox;
673         fz_rect rect;
674         fz_pixmap *pix = NULL;
675         float xscale, yscale;
676         globals *glo = get_globals(env, thiz);
677         fz_context *ctx = glo->ctx;
678         fz_document *doc = glo->doc;
679         page_cache *pc = &glo->pages[glo->current];
680         int hq = (patchW < pageW || patchH < pageH);
681         fz_matrix scale;
682         fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr;
683
684         if (pc->page == NULL)
685                 return 0;
686
687         fz_var(pix);
688         fz_var(dev);
689
690         LOGI("In native method\n");
691         if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
692                 LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
693                 return 0;
694         }
695
696         LOGI("Checking format\n");
697         if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
698                 LOGE("Bitmap format is not RGBA_8888 !");
699                 return 0;
700         }
701
702         LOGI("locking pixels\n");
703         if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
704                 LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
705                 return 0;
706         }
707
708         /* Call mupdf to render display list to screen */
709         LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]",
710                         pc->number, pageW, pageH, patchX, patchY, patchW, patchH);
711
712         fz_try(ctx)
713         {
714                 fz_irect pixbbox;
715                 pdf_document *idoc = pdf_specifics(doc);
716
717                 if (idoc)
718                 {
719                         /* Update the changed-rects for both hq patch and main bitmap */
720                         update_changed_rects(glo, pc, idoc);
721
722                         /* Then drop the changed-rects for the bitmap we're about to
723                         render because we are rendering the entire area */
724                         drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects);
725                 }
726
727                 if (pc->page_list == NULL)
728                 {
729                         /* Render to list */
730                         pc->page_list = fz_new_display_list(ctx);
731                         dev = fz_new_list_device(ctx, pc->page_list);
732                         fz_run_page_contents(doc, pc->page, dev, &fz_identity, cookie);
733                         fz_free_device(dev);
734                         dev = NULL;
735                         if (cookie != NULL && cookie->abort)
736                         {
737                                 fz_drop_display_list(ctx, pc->page_list);
738                                 pc->page_list = NULL;
739                                 fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
740                         }
741                 }
742                 if (pc->annot_list == NULL)
743                 {
744                         fz_annot *annot;
745                         pc->annot_list = fz_new_display_list(ctx);
746                         dev = fz_new_list_device(ctx, pc->annot_list);
747                         for (annot = fz_first_annot(doc, pc->page); annot; annot = fz_next_annot(doc, annot))
748                                 fz_run_annot(doc, pc->page, annot, dev, &fz_identity, cookie);
749                         fz_free_device(dev);
750                         dev = NULL;
751                         if (cookie != NULL && cookie->abort)
752                         {
753                                 fz_drop_display_list(ctx, pc->annot_list);
754                                 pc->annot_list = NULL;
755                                 fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
756                         }
757                 }
758                 bbox.x0 = patchX;
759                 bbox.y0 = patchY;
760                 bbox.x1 = patchX + patchW;
761                 bbox.y1 = patchY + patchH;
762                 pixbbox = bbox;
763                 pixbbox.x1 = pixbbox.x0 + info.width;
764                 /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to
765                  * match the pixels data */
766                 pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels);
767                 if (pc->page_list == NULL && pc->annot_list == NULL)
768                 {
769                         fz_clear_pixmap_with_value(ctx, pix, 0xd0);
770                         break;
771                 }
772                 fz_clear_pixmap_with_value(ctx, pix, 0xff);
773
774                 zoom = glo->resolution / 72;
775                 fz_scale(&ctm, zoom, zoom);
776                 rect = pc->media_box;
777                 fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm));
778                 /* Now, adjust ctm so that it would give the correct page width
779                  * heights. */
780                 xscale = (float)pageW/(float)(bbox.x1-bbox.x0);
781                 yscale = (float)pageH/(float)(bbox.y1-bbox.y0);
782                 fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale));
783                 rect = pc->media_box;
784                 fz_transform_rect(&rect, &ctm);
785                 dev = fz_new_draw_device(ctx, pix);
786 #ifdef TIME_DISPLAY_LIST
787                 {
788                         clock_t time;
789                         int i;
790
791                         LOGI("Executing display list");
792                         time = clock();
793                         for (i=0; i<100;i++) {
794 #endif
795                                 if (pc->page_list)
796                                         fz_run_display_list(pc->page_list, dev, &ctm, &rect, cookie);
797                                 if (cookie != NULL && cookie->abort)
798                                         fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
799
800                                 if (pc->annot_list)
801                                         fz_run_display_list(pc->annot_list, dev, &ctm, &rect, cookie);
802                                 if (cookie != NULL && cookie->abort)
803                                         fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
804
805 #ifdef TIME_DISPLAY_LIST
806                         }
807                         time = clock() - time;
808                         LOGI("100 renders in %d (%d per sec)", time, CLOCKS_PER_SEC);
809                 }
810 #endif
811                 fz_free_device(dev);
812                 dev = NULL;
813                 fz_drop_pixmap(ctx, pix);
814                 LOGI("Rendered");
815         }
816         fz_always(ctx)
817         {
818                 fz_free_device(dev);
819                 dev = NULL;
820         }
821         fz_catch(ctx)
822         {
823                 LOGE("Render failed");
824         }
825
826         AndroidBitmap_unlockPixels(env, bitmap);
827
828         return 1;
829 }
830
831 static char *widget_type_string(int t)
832 {
833         switch(t)
834         {
835         case PDF_WIDGET_TYPE_PUSHBUTTON: return "pushbutton";
836         case PDF_WIDGET_TYPE_CHECKBOX: return "checkbox";
837         case PDF_WIDGET_TYPE_RADIOBUTTON: return "radiobutton";
838         case PDF_WIDGET_TYPE_TEXT: return "text";
839         case PDF_WIDGET_TYPE_LISTBOX: return "listbox";
840         case PDF_WIDGET_TYPE_COMBOBOX: return "combobox";
841         case PDF_WIDGET_TYPE_SIGNATURE: return "signature";
842         default: return "non-widget";
843         }
844 }
845 JNIEXPORT jboolean JNICALL
846 JNI_FN(MuPDFCore_updatePageInternal)(JNIEnv *env, jobject thiz, jobject bitmap, int page,
847                 int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr)
848 {
849         AndroidBitmapInfo info;
850         void *pixels;
851         int ret;
852         fz_device *dev = NULL;
853         float zoom;
854         fz_matrix ctm;
855         fz_irect bbox;
856         fz_rect rect;
857         fz_pixmap *pix = NULL;
858         float xscale, yscale;
859         pdf_document *idoc;
860         page_cache *pc = NULL;
861         int hq = (patchW < pageW || patchH < pageH);
862         int i;
863         globals *glo = get_globals(env, thiz);
864         fz_context *ctx = glo->ctx;
865         fz_document *doc = glo->doc;
866         rect_node *crect;
867         fz_matrix scale;
868         fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr;
869
870         for (i = 0; i < NUM_CACHE; i++)
871         {
872                 if (glo->pages[i].page != NULL && glo->pages[i].number == page)
873                 {
874                         pc = &glo->pages[i];
875                         break;
876                 }
877         }
878
879         if (pc == NULL)
880         {
881                 /* Without a cached page object we cannot perform a partial update so
882                 render the entire bitmap instead */
883                 JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, page);
884                 return JNI_FN(MuPDFCore_drawPage)(env, thiz, bitmap, pageW, pageH, patchX, patchY, patchW, patchH, (jlong)(intptr_t)cookie);
885         }
886
887         idoc = pdf_specifics(doc);
888
889         fz_var(pix);
890         fz_var(dev);
891
892         LOGI("In native method\n");
893         if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
894                 LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
895                 return 0;
896         }
897
898         LOGI("Checking format\n");
899         if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
900                 LOGE("Bitmap format is not RGBA_8888 !");
901                 return 0;
902         }
903
904         LOGI("locking pixels\n");
905         if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
906                 LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
907                 return 0;
908         }
909
910         /* Call mupdf to render display list to screen */
911         LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]",
912                         pc->number, pageW, pageH, patchX, patchY, patchW, patchH);
913
914         fz_try(ctx)
915         {
916                 fz_annot *annot;
917                 fz_irect pixbbox;
918
919                 if (idoc)
920                 {
921                         /* Update the changed-rects for both hq patch and main bitmap */
922                         update_changed_rects(glo, pc, idoc);
923                 }
924
925                 if (pc->page_list == NULL)
926                 {
927                         /* Render to list */
928                         pc->page_list = fz_new_display_list(ctx);
929                         dev = fz_new_list_device(ctx, pc->page_list);
930                         fz_run_page_contents(doc, pc->page, dev, &fz_identity, cookie);
931                         fz_free_device(dev);
932                         dev = NULL;
933                         if (cookie != NULL && cookie->abort)
934                         {
935                                 fz_drop_display_list(ctx, pc->page_list);
936                                 pc->page_list = NULL;
937                                 fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
938                         }
939                 }
940
941                 if (pc->annot_list == NULL) {
942                         pc->annot_list = fz_new_display_list(ctx);
943                         dev = fz_new_list_device(ctx, pc->annot_list);
944                         for (annot = fz_first_annot(doc, pc->page); annot; annot = fz_next_annot(doc, annot))
945                                 fz_run_annot(doc, pc->page, annot, dev, &fz_identity, cookie);
946                         fz_free_device(dev);
947                         dev = NULL;
948                         if (cookie != NULL && cookie->abort)
949                         {
950                                 fz_drop_display_list(ctx, pc->annot_list);
951                                 pc->annot_list = NULL;
952                                 fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
953                         }
954                 }
955
956                 bbox.x0 = patchX;
957                 bbox.y0 = patchY;
958                 bbox.x1 = patchX + patchW;
959                 bbox.y1 = patchY + patchH;
960                 pixbbox = bbox;
961                 pixbbox.x1 = pixbbox.x0 + info.width;
962                 /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to
963                  * match the pixels data */
964                 pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels);
965
966                 zoom = glo->resolution / 72;
967                 fz_scale(&ctm, zoom, zoom);
968                 rect = pc->media_box;
969                 fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm));
970                 /* Now, adjust ctm so that it would give the correct page width
971                  * heights. */
972                 xscale = (float)pageW/(float)(bbox.x1-bbox.x0);
973                 yscale = (float)pageH/(float)(bbox.y1-bbox.y0);
974                 fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale));
975                 rect = pc->media_box;
976                 fz_transform_rect(&rect, &ctm);
977
978                 LOGI("Start partial update");
979                 for (crect = hq ? pc->hq_changed_rects : pc->changed_rects; crect; crect = crect->next)
980                 {
981                         fz_irect abox;
982                         fz_rect arect = crect->rect;
983                         fz_intersect_rect(fz_transform_rect(&arect, &ctm), &rect);
984                         fz_round_rect(&abox, &arect);
985
986                         LOGI("Update rectangle (%d, %d, %d, %d)", abox.x0, abox.y0, abox.x1, abox.y1);
987                         if (!fz_is_empty_irect(&abox))
988                         {
989                                 LOGI("And it isn't empty");
990                                 fz_clear_pixmap_rect_with_value(ctx, pix, 0xff, &abox);
991                                 dev = fz_new_draw_device_with_bbox(ctx, pix, &abox);
992                                 if (pc->page_list)
993                                         fz_run_display_list(pc->page_list, dev, &ctm, &arect, cookie);
994                                 if (cookie != NULL && cookie->abort)
995                                         fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
996
997                                 if (pc->annot_list)
998                                         fz_run_display_list(pc->annot_list, dev, &ctm, &arect, cookie);
999                                 if (cookie != NULL && cookie->abort)
1000                                         fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted");
1001
1002                                 fz_free_device(dev);
1003                                 dev = NULL;
1004                         }
1005                 }
1006                 LOGI("End partial update");
1007
1008                 /* Drop the changed rects we've just rendered */
1009                 drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects);
1010
1011                 LOGI("Rendered");
1012         }
1013         fz_always(ctx)
1014         {
1015                 fz_free_device(dev);
1016                 dev = NULL;
1017         }
1018         fz_catch(ctx)
1019         {
1020                 LOGE("Render failed");
1021         }
1022
1023         fz_drop_pixmap(ctx, pix);
1024         AndroidBitmap_unlockPixels(env, bitmap);
1025
1026         return 1;
1027 }
1028
1029 static int
1030 charat(fz_text_page *page, int idx)
1031 {
1032         fz_char_and_box cab;
1033         return fz_text_char_at(&cab, page, idx)->c;
1034 }
1035
1036 static fz_rect
1037 bboxcharat(fz_text_page *page, int idx)
1038 {
1039         fz_char_and_box cab;
1040         return fz_text_char_at(&cab, page, idx)->bbox;
1041 }
1042
1043 static int
1044 textlen(fz_text_page *page)
1045 {
1046         int len = 0;
1047         int block_num;
1048
1049         for (block_num = 0; block_num < page->len; block_num++)
1050         {
1051                 fz_text_block *block;
1052                 fz_text_line *line;
1053
1054                 if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT)
1055                         continue;
1056                 block = page->blocks[block_num].u.text;
1057                 for (line = block->lines; line < block->lines + block->len; line++)
1058                 {
1059                         fz_text_span *span;
1060
1061                         for (span = line->first_span; span; span = span->next)
1062                         {
1063                                 len += span->len;
1064                         }
1065                         len++; /* pseudo-newline */
1066                 }
1067         }
1068         return len;
1069 }
1070
1071 static int
1072 countOutlineItems(fz_outline *outline)
1073 {
1074         int count = 0;
1075
1076         while (outline)
1077         {
1078                 if (outline->dest.kind == FZ_LINK_GOTO
1079                                 && outline->dest.ld.gotor.page >= 0
1080                                 && outline->title)
1081                         count++;
1082
1083                 count += countOutlineItems(outline->down);
1084                 outline = outline->next;
1085         }
1086
1087         return count;
1088 }
1089
1090 static int
1091 fillInOutlineItems(JNIEnv * env, jclass olClass, jmethodID ctor, jobjectArray arr, int pos, fz_outline *outline, int level)
1092 {
1093         while (outline)
1094         {
1095                 if (outline->dest.kind == FZ_LINK_GOTO)
1096                 {
1097                         int page = outline->dest.ld.gotor.page;
1098                         if (page >= 0 && outline->title)
1099                         {
1100                                 jobject ol;
1101                                 jstring title = (*env)->NewStringUTF(env, outline->title);
1102                                 if (title == NULL) return -1;
1103                                 ol = (*env)->NewObject(env, olClass, ctor, level, title, page);
1104                                 if (ol == NULL) return -1;
1105                                 (*env)->SetObjectArrayElement(env, arr, pos, ol);
1106                                 (*env)->DeleteLocalRef(env, ol);
1107                                 (*env)->DeleteLocalRef(env, title);
1108                                 pos++;
1109                         }
1110                 }
1111                 pos = fillInOutlineItems(env, olClass, ctor, arr, pos, outline->down, level+1);
1112                 if (pos < 0) return -1;
1113                 outline = outline->next;
1114         }
1115
1116         return pos;
1117 }
1118
1119 JNIEXPORT jboolean JNICALL
1120 JNI_FN(MuPDFCore_needsPasswordInternal)(JNIEnv * env, jobject thiz)
1121 {
1122         globals *glo = get_globals(env, thiz);
1123
1124         return fz_needs_password(glo->doc) ? JNI_TRUE : JNI_FALSE;
1125 }
1126
1127 JNIEXPORT jboolean JNICALL
1128 JNI_FN(MuPDFCore_authenticatePasswordInternal)(JNIEnv *env, jobject thiz, jstring password)
1129 {
1130         const char *pw;
1131         int result;
1132         globals *glo = get_globals(env, thiz);
1133
1134         pw = (*env)->GetStringUTFChars(env, password, NULL);
1135         if (pw == NULL)
1136                 return JNI_FALSE;
1137
1138         result = fz_authenticate_password(glo->doc, (char *)pw);
1139         (*env)->ReleaseStringUTFChars(env, password, pw);
1140         return result;
1141 }
1142
1143 JNIEXPORT jboolean JNICALL
1144 JNI_FN(MuPDFCore_hasOutlineInternal)(JNIEnv * env, jobject thiz)
1145 {
1146         globals *glo = get_globals(env, thiz);
1147         fz_outline *outline = fz_load_outline(glo->doc);
1148
1149         fz_free_outline(glo->ctx, outline);
1150         return (outline == NULL) ? JNI_FALSE : JNI_TRUE;
1151 }
1152
1153 JNIEXPORT jobjectArray JNICALL
1154 JNI_FN(MuPDFCore_getOutlineInternal)(JNIEnv * env, jobject thiz)
1155 {
1156         jclass olClass;
1157         jmethodID ctor;
1158         jobjectArray arr;
1159         jobject ol;
1160         fz_outline *outline;
1161         int nItems;
1162         globals *glo = get_globals(env, thiz);
1163         jobjectArray ret;
1164
1165         olClass = (*env)->FindClass(env, PACKAGENAME "/OutlineItem");
1166         if (olClass == NULL) return NULL;
1167         ctor = (*env)->GetMethodID(env, olClass, "<init>", "(ILjava/lang/String;I)V");
1168         if (ctor == NULL) return NULL;
1169
1170         outline = fz_load_outline(glo->doc);
1171         nItems = countOutlineItems(outline);
1172
1173         arr = (*env)->NewObjectArray(env,
1174                                         nItems,
1175                                         olClass,
1176                                         NULL);
1177         if (arr == NULL) return NULL;
1178
1179         ret = fillInOutlineItems(env, olClass, ctor, arr, 0, outline, 0) > 0
1180                         ? arr
1181                         :NULL;
1182         fz_free_outline(glo->ctx, outline);
1183         return ret;
1184 }
1185
1186 JNIEXPORT jobjectArray JNICALL
1187 JNI_FN(MuPDFCore_searchPage)(JNIEnv * env, jobject thiz, jstring jtext)
1188 {
1189         jclass rectClass;
1190         jmethodID ctor;
1191         jobjectArray arr;
1192         jobject rect;
1193         fz_text_sheet *sheet = NULL;
1194         fz_text_page *text = NULL;
1195         fz_device *dev = NULL;
1196         float zoom;
1197         fz_matrix ctm;
1198         int pos;
1199         int len;
1200         int i, n;
1201         int hit_count = 0;
1202         const char *str;
1203         globals *glo = get_globals(env, thiz);
1204         fz_context *ctx = glo->ctx;
1205         fz_document *doc = glo->doc;
1206         page_cache *pc = &glo->pages[glo->current];
1207
1208         rectClass = (*env)->FindClass(env, "android/graphics/RectF");
1209         if (rectClass == NULL) return NULL;
1210         ctor = (*env)->GetMethodID(env, rectClass, "<init>", "(FFFF)V");
1211         if (ctor == NULL) return NULL;
1212         str = (*env)->GetStringUTFChars(env, jtext, NULL);
1213         if (str == NULL) return NULL;
1214
1215         fz_var(sheet);
1216         fz_var(text);
1217         fz_var(dev);
1218
1219         fz_try(ctx)
1220         {
1221                 if (glo->hit_bbox == NULL)
1222                         glo->hit_bbox = fz_malloc_array(ctx, MAX_SEARCH_HITS, sizeof(*glo->hit_bbox));
1223
1224                 zoom = glo->resolution / 72;
1225                 fz_scale(&ctm, zoom, zoom);
1226                 sheet = fz_new_text_sheet(ctx);
1227                 text = fz_new_text_page(ctx);
1228                 dev = fz_new_text_device(ctx, sheet, text);
1229                 fz_run_page(doc, pc->page, dev, &ctm, NULL);
1230                 fz_free_device(dev);
1231                 dev = NULL;
1232
1233                 hit_count = fz_search_text_page(ctx, text, str, glo->hit_bbox, MAX_SEARCH_HITS);
1234         }
1235         fz_always(ctx)
1236         {
1237                 fz_free_text_page(ctx, text);
1238                 fz_free_text_sheet(ctx, sheet);
1239                 fz_free_device(dev);
1240         }
1241         fz_catch(ctx)
1242         {
1243                 jclass cls;
1244                 (*env)->ReleaseStringUTFChars(env, jtext, str);
1245                 cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
1246                 if (cls != NULL)
1247                         (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage");
1248                 (*env)->DeleteLocalRef(env, cls);
1249
1250                 return NULL;
1251         }
1252
1253         (*env)->ReleaseStringUTFChars(env, jtext, str);
1254
1255         arr = (*env)->NewObjectArray(env,
1256                                         hit_count,
1257                                         rectClass,
1258                                         NULL);
1259         if (arr == NULL) return NULL;
1260
1261         for (i = 0; i < hit_count; i++) {
1262                 rect = (*env)->NewObject(env, rectClass, ctor,
1263                                 (float) (glo->hit_bbox[i].x0),
1264                                 (float) (glo->hit_bbox[i].y0),
1265                                 (float) (glo->hit_bbox[i].x1),
1266                                 (float) (glo->hit_bbox[i].y1));
1267                 if (rect == NULL)
1268                         return NULL;
1269                 (*env)->SetObjectArrayElement(env, arr, i, rect);
1270                 (*env)->DeleteLocalRef(env, rect);
1271         }
1272
1273         return arr;
1274 }
1275
1276 JNIEXPORT jobjectArray JNICALL
1277 JNI_FN(MuPDFCore_text)(JNIEnv * env, jobject thiz)
1278 {
1279         jclass textCharClass;
1280         jclass textSpanClass;
1281         jclass textLineClass;
1282         jclass textBlockClass;
1283         jmethodID ctor;
1284         jobjectArray barr = NULL;
1285         fz_text_sheet *sheet = NULL;
1286         fz_text_page *text = NULL;
1287         fz_device *dev = NULL;
1288         float zoom;
1289         fz_matrix ctm;
1290         globals *glo = get_globals(env, thiz);
1291         fz_context *ctx = glo->ctx;
1292         fz_document *doc = glo->doc;
1293         page_cache *pc = &glo->pages[glo->current];
1294
1295         textCharClass = (*env)->FindClass(env, PACKAGENAME "/TextChar");
1296         if (textCharClass == NULL) return NULL;
1297         textSpanClass = (*env)->FindClass(env, "[L" PACKAGENAME "/TextChar;");
1298         if (textSpanClass == NULL) return NULL;
1299         textLineClass = (*env)->FindClass(env, "[[L" PACKAGENAME "/TextChar;");
1300         if (textLineClass == NULL) return NULL;
1301         textBlockClass = (*env)->FindClass(env, "[[[L" PACKAGENAME "/TextChar;");
1302         if (textBlockClass == NULL) return NULL;
1303         ctor = (*env)->GetMethodID(env, textCharClass, "<init>", "(FFFFC)V");
1304         if (ctor == NULL) return NULL;
1305
1306         fz_var(sheet);
1307         fz_var(text);
1308         fz_var(dev);
1309
1310         fz_try(ctx)
1311         {
1312                 int b, l, s, c;
1313
1314                 zoom = glo->resolution / 72;
1315                 fz_scale(&ctm, zoom, zoom);
1316                 sheet = fz_new_text_sheet(ctx);
1317                 text = fz_new_text_page(ctx);
1318                 dev = fz_new_text_device(ctx, sheet, text);
1319                 fz_run_page(doc, pc->page, dev, &ctm, NULL);
1320                 fz_free_device(dev);
1321                 dev = NULL;
1322
1323                 barr = (*env)->NewObjectArray(env, text->len, textBlockClass, NULL);
1324                 if (barr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed");
1325
1326                 for (b = 0; b < text->len; b++)
1327                 {
1328                         fz_text_block *block;
1329                         jobjectArray *larr;
1330
1331                         if (text->blocks[b].type != FZ_PAGE_BLOCK_TEXT)
1332                                 continue;
1333                         block = text->blocks[b].u.text;
1334                         larr = (*env)->NewObjectArray(env, block->len, textLineClass, NULL);
1335                         if (larr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed");
1336
1337                         for (l = 0; l < block->len; l++)
1338                         {
1339                                 fz_text_line *line = &block->lines[l];
1340                                 jobjectArray *sarr;
1341                                 fz_text_span *span;
1342                                 int len = 0;
1343
1344                                 for (span = line->first_span; span; span = span->next)
1345                                         len++;
1346
1347                                 sarr = (*env)->NewObjectArray(env, len, textSpanClass, NULL);
1348                                 if (sarr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed");
1349
1350                                 for (s=0, span = line->first_span; span; s++, span = span->next)
1351                                 {
1352                                         jobjectArray *carr = (*env)->NewObjectArray(env, span->len, textCharClass, NULL);
1353                                         if (carr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed");
1354
1355                                         for (c = 0; c < span->len; c++)
1356                                         {
1357                                                 fz_text_char *ch = &span->text[c];
1358                                                 fz_rect bbox;
1359                                                 fz_text_char_bbox(&bbox, span, c);
1360                                                 jobject cobj = (*env)->NewObject(env, textCharClass, ctor, bbox.x0, bbox.y0, bbox.x1, bbox.y1, ch->c);
1361                                                 if (cobj == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectfailed");
1362
1363                                                 (*env)->SetObjectArrayElement(env, carr, c, cobj);
1364                                                 (*env)->DeleteLocalRef(env, cobj);
1365                                         }
1366
1367                                         (*env)->SetObjectArrayElement(env, sarr, s, carr);
1368                                         (*env)->DeleteLocalRef(env, carr);
1369                                 }
1370
1371                                 (*env)->SetObjectArrayElement(env, larr, l, sarr);
1372                                 (*env)->DeleteLocalRef(env, sarr);
1373                         }
1374
1375                         (*env)->SetObjectArrayElement(env, barr, b, larr);
1376                         (*env)->DeleteLocalRef(env, larr);
1377                 }
1378         }
1379         fz_always(ctx)
1380         {
1381                 fz_free_text_page(ctx, text);
1382                 fz_free_text_sheet(ctx, sheet);
1383                 fz_free_device(dev);
1384         }
1385         fz_catch(ctx)
1386         {
1387                 jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
1388                 if (cls != NULL)
1389                         (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_text");
1390                 (*env)->DeleteLocalRef(env, cls);
1391
1392                 return NULL;
1393         }
1394
1395         return barr;
1396 }
1397
1398 JNIEXPORT jbyteArray JNICALL
1399 JNI_FN(MuPDFCore_textAsHtml)(JNIEnv * env, jobject thiz)
1400 {
1401         fz_text_sheet *sheet = NULL;
1402         fz_text_page *text = NULL;
1403         fz_device *dev = NULL;
1404         fz_matrix ctm;
1405         globals *glo = get_globals(env, thiz);
1406         fz_context *ctx = glo->ctx;
1407         fz_document *doc = glo->doc;
1408         page_cache *pc = &glo->pages[glo->current];
1409         jbyteArray bArray = NULL;
1410         fz_buffer *buf = NULL;
1411         fz_output *out = NULL;
1412
1413         fz_var(sheet);
1414         fz_var(text);
1415         fz_var(dev);
1416         fz_var(buf);
1417         fz_var(out);
1418
1419         fz_try(ctx)
1420         {
1421                 int b, l, s, c;
1422
1423                 ctm = fz_identity;
1424                 sheet = fz_new_text_sheet(ctx);
1425                 text = fz_new_text_page(ctx);
1426                 dev = fz_new_text_device(ctx, sheet, text);
1427                 fz_run_page(doc, pc->page, dev, &ctm, NULL);
1428                 fz_free_device(dev);
1429                 dev = NULL;
1430
1431                 fz_analyze_text(ctx, sheet, text);
1432
1433                 buf = fz_new_buffer(ctx, 256);
1434                 out = fz_new_output_with_buffer(ctx, buf);
1435                 fz_printf(out, "<html>\n");
1436                 fz_printf(out, "<style>\n");
1437                 fz_printf(out, "body{margin:0;}\n");
1438                 fz_printf(out, "div.page{background-color:white;}\n");
1439                 fz_printf(out, "div.block{margin:0pt;padding:0pt;}\n");
1440                 fz_printf(out, "div.metaline{display:table;width:100%%}\n");
1441                 fz_printf(out, "div.line{display:table-row;}\n");
1442                 fz_printf(out, "div.cell{display:table-cell;padding-left:0.25em;padding-right:0.25em}\n");
1443                 //fz_printf(out, "p{margin:0;padding:0;}\n");
1444                 fz_printf(out, "</style>\n");
1445                 fz_printf(out, "<body style=\"margin:0\"><div style=\"padding:10px\" id=\"content\">");
1446                 fz_print_text_page_html(ctx, out, text);
1447                 fz_printf(out, "</div></body>\n");
1448                 fz_printf(out, "<style>\n");
1449                 fz_print_text_sheet(ctx, out, sheet);
1450                 fz_printf(out, "</style>\n</html>\n");
1451                 fz_close_output(out);
1452                 out = NULL;
1453
1454                 bArray = (*env)->NewByteArray(env, buf->len);
1455                 if (bArray == NULL)
1456                         fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to make byteArray");
1457                 (*env)->SetByteArrayRegion(env, bArray, 0, buf->len, buf->data);
1458
1459         }
1460         fz_always(ctx)
1461         {
1462                 fz_free_text_page(ctx, text);
1463                 fz_free_text_sheet(ctx, sheet);
1464                 fz_free_device(dev);
1465                 fz_close_output(out);
1466                 fz_drop_buffer(ctx, buf);
1467         }
1468         fz_catch(ctx)
1469         {
1470                 jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
1471                 if (cls != NULL)
1472                         (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_textAsHtml");
1473                 (*env)->DeleteLocalRef(env, cls);
1474
1475                 return NULL;
1476         }
1477
1478         return bArray;
1479 }
1480
1481 JNIEXPORT void JNICALL
1482 JNI_FN(MuPDFCore_addMarkupAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray points, fz_annot_type type)
1483 {
1484         globals *glo = get_globals(env, thiz);
1485         fz_context *ctx = glo->ctx;
1486         fz_document *doc = glo->doc;
1487         pdf_document *idoc = pdf_specifics(doc);
1488         page_cache *pc = &glo->pages[glo->current];
1489         jclass pt_cls;
1490         jfieldID x_fid, y_fid;
1491         int i, n;
1492         fz_point *pts = NULL;
1493         float color[3];
1494         float alpha;
1495         float line_height;
1496         float line_thickness;
1497
1498         if (idoc == NULL)
1499                 return;
1500
1501         switch (type)
1502         {
1503                 case FZ_ANNOT_HIGHLIGHT:
1504                         color[0] = 1.0;
1505                         color[1] = 1.0;
1506                         color[2] = 0.0;
1507                         alpha = 0.5;
1508                         line_thickness = 1.0;
1509                         line_height = 0.5;
1510                         break;
1511                 case FZ_ANNOT_UNDERLINE:
1512                         color[0] = 0.0;
1513                         color[1] = 0.0;
1514                         color[2] = 1.0;
1515                         alpha = 1.0;
1516                         line_thickness = LINE_THICKNESS;
1517                         line_height = UNDERLINE_HEIGHT;
1518                         break;
1519                 case FZ_ANNOT_STRIKEOUT:
1520                         color[0] = 1.0;
1521                         color[1] = 0.0;
1522                         color[2] = 0.0;
1523                         alpha = 1.0;
1524                         line_thickness = LINE_THICKNESS;
1525                         line_height = STRIKE_HEIGHT;
1526                         break;
1527                 default:
1528                         return;
1529         }
1530
1531         fz_var(pts);
1532         fz_try(ctx)
1533         {
1534                 fz_annot *annot;
1535                 fz_matrix ctm;
1536
1537                 float zoom = glo->resolution / 72;
1538                 zoom = 1.0 / zoom;
1539                 fz_scale(&ctm, zoom, zoom);
1540                 pt_cls = (*env)->FindClass(env, "android/graphics/PointF");
1541                 if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass");
1542                 x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F");
1543                 if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)");
1544                 y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F");
1545                 if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)");
1546
1547                 n = (*env)->GetArrayLength(env, points);
1548
1549                 pts = fz_malloc_array(ctx, n, sizeof(fz_point));
1550
1551                 for (i = 0; i < n; i++)
1552                 {
1553                         jobject opt = (*env)->GetObjectArrayElement(env, points, i);
1554                         pts[i].x = opt ? (*env)->GetFloatField(env, opt, x_fid) : 0.0f;
1555                         pts[i].y = opt ? (*env)->GetFloatField(env, opt, y_fid) : 0.0f;
1556                         fz_transform_point(&pts[i], &ctm);
1557                 }
1558
1559                 annot = (fz_annot *)pdf_create_annot(idoc, (pdf_page *)pc->page, type);
1560
1561                 pdf_set_markup_annot_quadpoints(idoc, (pdf_annot *)annot, pts, n);
1562                 pdf_set_markup_appearance(idoc, (pdf_annot *)annot, color, alpha, line_thickness, line_height);
1563
1564                 dump_annotation_display_lists(glo);
1565         }
1566         fz_always(ctx)
1567         {
1568                 fz_free(ctx, pts);
1569         }
1570         fz_catch(ctx)
1571         {
1572                 LOGE("addStrikeOutAnnotation: %s failed", ctx->error->message);
1573                 jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
1574                 if (cls != NULL)
1575                         (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage");
1576                 (*env)->DeleteLocalRef(env, cls);
1577         }
1578 }
1579
1580 JNIEXPORT void JNICALL
1581 JNI_FN(MuPDFCore_addInkAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray arcs)
1582 {
1583         globals *glo = get_globals(env, thiz);
1584         fz_context *ctx = glo->ctx;
1585         fz_document *doc = glo->doc;
1586         pdf_document *idoc = pdf_specifics(doc);
1587         page_cache *pc = &glo->pages[glo->current];
1588         jclass pt_cls;
1589         jfieldID x_fid, y_fid;
1590         int i, j, k, n;
1591         fz_point *pts = NULL;
1592         int *counts = NULL;
1593         int total = 0;
1594         float color[3];
1595
1596         if (idoc == NULL)
1597                 return;
1598
1599         color[0] = 1.0;
1600         color[1] = 0.0;
1601         color[2] = 0.0;
1602
1603         fz_var(pts);
1604         fz_var(counts);
1605         fz_try(ctx)
1606         {
1607                 fz_annot *annot;
1608                 fz_matrix ctm;
1609
1610                 float zoom = glo->resolution / 72;
1611                 zoom = 1.0 / zoom;
1612                 fz_scale(&ctm, zoom, zoom);
1613                 pt_cls = (*env)->FindClass(env, "android/graphics/PointF");
1614                 if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass");
1615                 x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F");
1616                 if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)");
1617                 y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F");
1618                 if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)");
1619
1620                 n = (*env)->GetArrayLength(env, arcs);
1621
1622                 counts = fz_malloc_array(ctx, n, sizeof(int));
1623
1624                 for (i = 0; i < n; i++)
1625                 {
1626                         jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i);
1627                         int count = (*env)->GetArrayLength(env, arc);
1628
1629                         counts[i] = count;
1630                         total += count;
1631                 }
1632
1633                 pts = fz_malloc_array(ctx, total, sizeof(fz_point));
1634
1635                 k = 0;
1636                 for (i = 0; i < n; i++)
1637                 {
1638                         jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i);
1639                         int count = counts[i];
1640
1641                         for (j = 0; j < count; j++)
1642                         {
1643                                 jobject pt = (*env)->GetObjectArrayElement(env, arc, j);
1644
1645                                 pts[k].x = pt ? (*env)->GetFloatField(env, pt, x_fid) : 0.0f;
1646                                 pts[k].y = pt ? (*env)->GetFloatField(env, pt, y_fid) : 0.0f;
1647                                 (*env)->DeleteLocalRef(env, pt);
1648                                 fz_transform_point(&pts[k], &ctm);
1649                                 k++;
1650                         }
1651                         (*env)->DeleteLocalRef(env, arc);
1652                 }
1653
1654                 annot = (fz_annot *)pdf_create_annot(idoc, (pdf_page *)pc->page, FZ_ANNOT_INK);
1655
1656                 pdf_set_ink_annot_list(idoc, (pdf_annot *)annot, pts, counts, n, color, INK_THICKNESS);
1657
1658                 dump_annotation_display_lists(glo);
1659         }
1660         fz_always(ctx)
1661         {
1662                 fz_free(ctx, pts);
1663                 fz_free(ctx, counts);
1664         }
1665         fz_catch(ctx)
1666         {
1667                 LOGE("addInkAnnotation: %s failed", ctx->error->message);
1668                 jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
1669                 if (cls != NULL)
1670                         (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage");
1671                 (*env)->DeleteLocalRef(env, cls);
1672         }
1673 }
1674
1675 JNIEXPORT void JNICALL
1676 JNI_FN(MuPDFCore_deleteAnnotationInternal)(JNIEnv * env, jobject thiz, int annot_index)
1677 {
1678         globals *glo = get_globals(env, thiz);
1679         fz_context *ctx = glo->ctx;
1680         fz_document *doc = glo->doc;
1681         pdf_document *idoc = pdf_specifics(doc);
1682         page_cache *pc = &glo->pages[glo->current];
1683         fz_annot *annot;
1684         int i;
1685
1686         if (idoc == NULL)
1687                 return;
1688
1689         fz_try(ctx)
1690         {
1691                 annot = fz_first_annot(glo->doc, pc->page);
1692                 for (i = 0; i < annot_index && annot; i++)
1693                         annot = fz_next_annot(glo->doc, annot);
1694
1695                 if (annot)
1696                 {
1697                         pdf_delete_annot(idoc, (pdf_page *)pc->page, (pdf_annot *)annot);
1698                         dump_annotation_display_lists(glo);
1699                 }
1700         }
1701         fz_catch(ctx)
1702         {
1703                 LOGE("deleteAnnotationInternal: %s", ctx->error->message);
1704         }
1705 }
1706
1707 /* Close the document, at least enough to be able to save over it. This
1708  * may be called again later, so must be idempotent. */
1709 static void close_doc(globals *glo)
1710 {
1711         int i;
1712
1713         fz_free(glo->ctx, glo->hit_bbox);
1714         glo->hit_bbox = NULL;
1715
1716         for (i = 0; i < NUM_CACHE; i++)
1717                 drop_page_cache(glo, &glo->pages[i]);
1718
1719         alerts_fin(glo);
1720
1721         fz_close_document(glo->doc);
1722         glo->doc = NULL;
1723 }
1724
1725 JNIEXPORT void JNICALL
1726 JNI_FN(MuPDFCore_destroying)(JNIEnv * env, jobject thiz)
1727 {
1728         globals *glo = get_globals(env, thiz);
1729
1730         if (glo == NULL)
1731                 return;
1732         LOGI("Destroying");
1733         fz_free(glo->ctx, glo->current_path);
1734         glo->current_path = NULL;
1735         close_doc(glo);
1736         fz_free_context(glo->ctx);
1737         glo->ctx = NULL;
1738         free(glo);
1739 #ifdef MEMENTO
1740         LOGI("Destroying dump start");
1741         Memento_listBlocks();
1742         Memento_stats();
1743         LOGI("Destroying dump end");
1744 #endif
1745 #ifdef NDK_PROFILER
1746         // Apparently we should really be writing to whatever path we get
1747         // from calling getFilesDir() in the java part, which supposedly
1748         // gives /sdcard/data/data/com.artifex.MuPDF/gmon.out, but that's
1749         // unfriendly.
1750         setenv("CPUPROFILE", "/sdcard/gmon.out", 1);
1751         moncleanup();
1752 #endif
1753 }
1754
1755 JNIEXPORT jobjectArray JNICALL
1756 JNI_FN(MuPDFCore_getPageLinksInternal)(JNIEnv * env, jobject thiz, int pageNumber)
1757 {
1758         jclass linkInfoClass;
1759         jclass linkInfoInternalClass;
1760         jclass linkInfoExternalClass;
1761         jclass linkInfoRemoteClass;
1762         jmethodID ctorInternal;
1763         jmethodID ctorExternal;
1764         jmethodID ctorRemote;
1765         jobjectArray arr;
1766         jobject linkInfo;
1767         fz_matrix ctm;
1768         float zoom;
1769         fz_link *list;
1770         fz_link *link;
1771         int count;
1772         page_cache *pc;
1773         globals *glo = get_globals(env, thiz);
1774
1775         linkInfoClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfo");
1776         if (linkInfoClass == NULL) return NULL;
1777         linkInfoInternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoInternal");
1778         if (linkInfoInternalClass == NULL) return NULL;
1779         linkInfoExternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoExternal");
1780         if (linkInfoExternalClass == NULL) return NULL;
1781         linkInfoRemoteClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoRemote");
1782         if (linkInfoRemoteClass == NULL) return NULL;
1783         ctorInternal = (*env)->GetMethodID(env, linkInfoInternalClass, "<init>", "(FFFFI)V");
1784         if (ctorInternal == NULL) return NULL;
1785         ctorExternal = (*env)->GetMethodID(env, linkInfoExternalClass, "<init>", "(FFFFLjava/lang/String;)V");
1786         if (ctorExternal == NULL) return NULL;
1787         ctorRemote = (*env)->GetMethodID(env, linkInfoRemoteClass, "<init>", "(FFFFLjava/lang/String;IZ)V");
1788         if (ctorRemote == NULL) return NULL;
1789
1790         JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber);
1791         pc = &glo->pages[glo->current];
1792         if (pc->page == NULL || pc->number != pageNumber)
1793                 return NULL;
1794
1795         zoom = glo->resolution / 72;
1796         fz_scale(&ctm, zoom, zoom);
1797
1798         list = fz_load_links(glo->doc, pc->page);
1799         count = 0;
1800         for (link = list; link; link = link->next)
1801         {
1802                 switch (link->dest.kind)
1803                 {
1804                 case FZ_LINK_GOTO:
1805                 case FZ_LINK_GOTOR:
1806                 case FZ_LINK_URI:
1807                         count++ ;
1808                 }
1809         }
1810
1811         arr = (*env)->NewObjectArray(env, count, linkInfoClass, NULL);
1812         if (arr == NULL)
1813         {
1814                 fz_drop_link(glo->ctx, list);
1815                 return NULL;
1816         }
1817
1818         count = 0;
1819         for (link = list; link; link = link->next)
1820         {
1821                 fz_rect rect = link->rect;
1822                 fz_transform_rect(&rect, &ctm);
1823
1824                 switch (link->dest.kind)
1825                 {
1826                 case FZ_LINK_GOTO:
1827                 {
1828                         linkInfo = (*env)->NewObject(env, linkInfoInternalClass, ctorInternal,
1829                                         (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1,
1830                                         link->dest.ld.gotor.page);
1831                         break;
1832                 }
1833
1834                 case FZ_LINK_GOTOR:
1835                 {
1836                         jstring juri = (*env)->NewStringUTF(env, link->dest.ld.gotor.file_spec);
1837                         linkInfo = (*env)->NewObject(env, linkInfoRemoteClass, ctorRemote,
1838                                         (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1,
1839                                         juri, link->dest.ld.gotor.page, link->dest.ld.gotor.new_window ? JNI_TRUE : JNI_FALSE);
1840                         break;
1841                 }
1842
1843                 case FZ_LINK_URI:
1844                 {
1845                         jstring juri = (*env)->NewStringUTF(env, link->dest.ld.uri.uri);
1846                         linkInfo = (*env)->NewObject(env, linkInfoExternalClass, ctorExternal,
1847                                         (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1,
1848                                         juri);
1849                         break;
1850                 }
1851
1852                 default:
1853                         continue;
1854                 }
1855
1856                 if (linkInfo == NULL)
1857                 {
1858                         fz_drop_link(glo->ctx, list);
1859                         return NULL;
1860                 }
1861                 (*env)->SetObjectArrayElement(env, arr, count, linkInfo);
1862                 (*env)->DeleteLocalRef(env, linkInfo);
1863                 count++;
1864         }
1865         fz_drop_link(glo->ctx, list);
1866
1867         return arr;
1868 }
1869
1870 JNIEXPORT jobjectArray JNICALL
1871 JNI_FN(MuPDFCore_getWidgetAreasInternal)(JNIEnv * env, jobject thiz, int pageNumber)
1872 {
1873         jclass rectFClass;
1874         jmethodID ctor;
1875         jobjectArray arr;
1876         jobject rectF;
1877         pdf_document *idoc;
1878         pdf_widget *widget;
1879         fz_matrix ctm;
1880         float zoom;
1881         int count;
1882         page_cache *pc;
1883         globals *glo = get_globals(env, thiz);
1884         if (glo == NULL)
1885                 return NULL;
1886
1887         rectFClass = (*env)->FindClass(env, "android/graphics/RectF");
1888         if (rectFClass == NULL) return NULL;
1889         ctor = (*env)->GetMethodID(env, rectFClass, "<init>", "(FFFF)V");
1890         if (ctor == NULL) return NULL;
1891
1892         JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber);
1893         pc = &glo->pages[glo->current];
1894         if (pc->number != pageNumber || pc->page == NULL)
1895                 return NULL;
1896
1897         idoc = pdf_specifics(glo->doc);
1898         if (idoc == NULL)
1899                 return NULL;
1900
1901         zoom = glo->resolution / 72;
1902         fz_scale(&ctm, zoom, zoom);
1903
1904         count = 0;
1905         for (widget = pdf_first_widget(idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(widget))
1906                 count ++;
1907
1908         arr = (*env)->NewObjectArray(env, count, rectFClass, NULL);
1909         if (arr == NULL) return NULL;
1910
1911         count = 0;
1912         for (widget = pdf_first_widget(idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(widget))
1913         {
1914                 fz_rect rect;
1915                 pdf_bound_widget(widget, &rect);
1916                 fz_transform_rect(&rect, &ctm);
1917
1918                 rectF = (*env)->NewObject(env, rectFClass, ctor,
1919                                 (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1);
1920                 if (rectF == NULL) return NULL;
1921                 (*env)->SetObjectArrayElement(env, arr, count, rectF);
1922                 (*env)->DeleteLocalRef(env, rectF);
1923
1924                 count ++;
1925         }
1926
1927         return arr;
1928 }
1929
1930 JNIEXPORT jobjectArray JNICALL
1931 JNI_FN(MuPDFCore_getAnnotationsInternal)(JNIEnv * env, jobject thiz, int pageNumber)
1932 {
1933         jclass annotClass;
1934         jmethodID ctor;
1935         jobjectArray arr;
1936         jobject jannot;
1937         fz_annot *annot;
1938         fz_matrix ctm;
1939         float zoom;
1940         int count;
1941         page_cache *pc;
1942         globals *glo = get_globals(env, thiz);
1943         if (glo == NULL)
1944                 return NULL;
1945
1946         annotClass = (*env)->FindClass(env, PACKAGENAME "/Annotation");
1947         if (annotClass == NULL) return NULL;
1948         ctor = (*env)->GetMethodID(env, annotClass, "<init>", "(FFFFI)V");
1949         if (ctor == NULL) return NULL;
1950
1951         JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber);
1952         pc = &glo->pages[glo->current];
1953         if (pc->number != pageNumber || pc->page == NULL)
1954                 return NULL;
1955
1956         zoom = glo->resolution / 72;
1957         fz_scale(&ctm, zoom, zoom);
1958
1959         count = 0;
1960         for (annot = fz_first_annot(glo->doc, pc->page); annot; annot = fz_next_annot(glo->doc, annot))
1961                 count ++;
1962
1963         arr = (*env)->NewObjectArray(env, count, annotClass, NULL);
1964         if (arr == NULL) return NULL;
1965
1966         count = 0;
1967         for (annot = fz_first_annot(glo->doc, pc->page); annot; annot = fz_next_annot(glo->doc, annot))
1968         {
1969                 fz_rect rect;
1970                 fz_annot_type type = pdf_annot_type((pdf_annot *)annot);
1971                 fz_bound_annot(glo->doc, annot, &rect);
1972                 fz_transform_rect(&rect, &ctm);
1973
1974                 jannot = (*env)->NewObject(env, annotClass, ctor,
1975                                 (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, type);
1976                 if (jannot == NULL) return NULL;
1977                 (*env)->SetObjectArrayElement(env, arr, count, jannot);
1978                 (*env)->DeleteLocalRef(env, jannot);
1979
1980                 count ++;
1981         }
1982
1983         return arr;
1984 }
1985
1986 JNIEXPORT int JNICALL
1987 JNI_FN(MuPDFCore_passClickEventInternal)(JNIEnv * env, jobject thiz, int pageNumber, float x, float y)
1988 {
1989         globals *glo = get_globals(env, thiz);
1990         fz_context *ctx = glo->ctx;
1991         fz_matrix ctm;
1992         pdf_document *idoc = pdf_specifics(glo->doc);
1993         float zoom;
1994         fz_point p;
1995         pdf_ui_event event;
1996         int changed = 0;
1997         page_cache *pc;
1998
1999         if (idoc == NULL)
2000                 return 0;
2001
2002         JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber);
2003         pc = &glo->pages[glo->current];
2004         if (pc->number != pageNumber || pc->page == NULL)
2005                 return 0;
2006
2007         p.x = x;
2008         p.y = y;
2009
2010         /* Ultimately we should probably return a pointer to a java structure
2011          * with the link details in, but for now, page number will suffice.
2012          */
2013         zoom = glo->resolution / 72;
2014         fz_scale(&ctm, zoom, zoom);
2015         fz_invert_matrix(&ctm, &ctm);
2016
2017         fz_transform_point(&p, &ctm);
2018
2019         fz_try(ctx)
2020         {
2021                 event.etype = PDF_EVENT_TYPE_POINTER;
2022                 event.event.pointer.pt = p;
2023                 event.event.pointer.ptype = PDF_POINTER_DOWN;
2024                 changed = pdf_pass_event(idoc, (pdf_page *)pc->page, &event);
2025                 event.event.pointer.ptype = PDF_POINTER_UP;
2026                 changed |= pdf_pass_event(idoc, (pdf_page *)pc->page, &event);
2027                 if (changed) {
2028                         dump_annotation_display_lists(glo);
2029                 }
2030         }
2031         fz_catch(ctx)
2032         {
2033                 LOGE("passClickEvent: %s", ctx->error->message);
2034         }
2035
2036         return changed;
2037 }
2038
2039 JNIEXPORT jstring JNICALL
2040 JNI_FN(MuPDFCore_getFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz)
2041 {
2042         char *text = "";
2043         globals *glo = get_globals(env, thiz);
2044         fz_context *ctx = glo->ctx;
2045
2046         fz_try(ctx)
2047         {
2048                 pdf_document *idoc = pdf_specifics(glo->doc);
2049
2050                 if (idoc)
2051                 {
2052                         pdf_widget *focus = pdf_focused_widget(idoc);
2053
2054                         if (focus)
2055                                 text = pdf_text_widget_text(idoc, focus);
2056                 }
2057         }
2058         fz_catch(ctx)
2059         {
2060                 LOGE("getFocusedWidgetText failed: %s", ctx->error->message);
2061         }
2062
2063         return (*env)->NewStringUTF(env, text);
2064 }
2065
2066 JNIEXPORT int JNICALL
2067 JNI_FN(MuPDFCore_setFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz, jstring jtext)
2068 {
2069         const char *text;
2070         int result = 0;
2071         globals *glo = get_globals(env, thiz);
2072         fz_context *ctx = glo->ctx;
2073
2074         text = (*env)->GetStringUTFChars(env, jtext, NULL);
2075         if (text == NULL)
2076         {
2077                 LOGE("Failed to get text");
2078                 return 0;
2079         }
2080
2081         fz_try(ctx)
2082         {
2083                 pdf_document *idoc = pdf_specifics(glo->doc);
2084
2085                 if (idoc)
2086                 {
2087                         pdf_widget *focus = pdf_focused_widget(idoc);
2088
2089                         if (focus)
2090                         {
2091                                 result = pdf_text_widget_set_text(idoc, focus, (char *)text);
2092                                 dump_annotation_display_lists(glo);
2093                         }
2094                 }
2095         }
2096         fz_catch(ctx)
2097         {
2098                 LOGE("setFocusedWidgetText failed: %s", ctx->error->message);
2099         }
2100
2101         (*env)->ReleaseStringUTFChars(env, jtext, text);
2102
2103         return result;
2104 }
2105
2106 JNIEXPORT jobjectArray JNICALL
2107 JNI_FN(MuPDFCore_getFocusedWidgetChoiceOptions)(JNIEnv * env, jobject thiz)
2108 {
2109         globals *glo = get_globals(env, thiz);
2110         fz_context *ctx = glo->ctx;
2111         pdf_document *idoc = pdf_specifics(glo->doc);
2112         pdf_widget *focus;
2113         int type;
2114         int nopts, i;
2115         char **opts = NULL;
2116         jclass stringClass;
2117         jobjectArray arr;
2118
2119         if (idoc == NULL)
2120                 return NULL;
2121
2122         focus = pdf_focused_widget(idoc);
2123         if (focus == NULL)
2124                 return NULL;
2125
2126         type = pdf_widget_get_type(focus);
2127         if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX)
2128                 return NULL;
2129
2130         fz_var(opts);
2131         fz_try(ctx)
2132         {
2133                 nopts = pdf_choice_widget_options(idoc, focus, NULL);
2134                 opts = fz_malloc(ctx, nopts * sizeof(*opts));
2135                 (void)pdf_choice_widget_options(idoc, focus, opts);
2136         }
2137         fz_catch(ctx)
2138         {
2139                 fz_free(ctx, opts);
2140                 LOGE("Failed in getFocuseedWidgetChoiceOptions");
2141                 return NULL;
2142         }
2143
2144         stringClass = (*env)->FindClass(env, "java/lang/String");
2145
2146         arr = (*env)->NewObjectArray(env, nopts, stringClass, NULL);
2147
2148         for (i = 0; i < nopts; i++)
2149         {
2150                 jstring s = (*env)->NewStringUTF(env, opts[i]);
2151                 if (s != NULL)
2152                         (*env)->SetObjectArrayElement(env, arr, i, s);
2153
2154                 (*env)->DeleteLocalRef(env, s);
2155         }
2156
2157         fz_free(ctx, opts);
2158
2159         return arr;
2160 }
2161
2162 JNIEXPORT jobjectArray JNICALL
2163 JNI_FN(MuPDFCore_getFocusedWidgetChoiceSelected)(JNIEnv * env, jobject thiz)
2164 {
2165         globals *glo = get_globals(env, thiz);
2166         fz_context *ctx = glo->ctx;
2167         pdf_document *idoc = pdf_specifics(glo->doc);
2168         pdf_widget *focus;
2169         int type;
2170         int nsel, i;
2171         char **sel = NULL;
2172         jclass stringClass;
2173         jobjectArray arr;
2174
2175         if (idoc == NULL)
2176                 return NULL;
2177
2178         focus = pdf_focused_widget(idoc);
2179         if (focus == NULL)
2180                 return NULL;
2181
2182         type = pdf_widget_get_type(focus);
2183         if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX)
2184                 return NULL;
2185
2186         fz_var(sel);
2187         fz_try(ctx)
2188         {
2189                 nsel = pdf_choice_widget_value(idoc, focus, NULL);
2190                 sel = fz_malloc(ctx, nsel * sizeof(*sel));
2191                 (void)pdf_choice_widget_value(idoc, focus, sel);
2192         }
2193         fz_catch(ctx)
2194         {
2195                 fz_free(ctx, sel);
2196                 LOGE("Failed in getFocuseedWidgetChoiceOptions");
2197                 return NULL;
2198         }
2199
2200         stringClass = (*env)->FindClass(env, "java/lang/String");
2201
2202         arr = (*env)->NewObjectArray(env, nsel, stringClass, NULL);
2203
2204         for (i = 0; i < nsel; i++)
2205         {
2206                 jstring s = (*env)->NewStringUTF(env, sel[i]);
2207                 if (s != NULL)
2208                         (*env)->SetObjectArrayElement(env, arr, i, s);
2209
2210                 (*env)->DeleteLocalRef(env, s);
2211         }
2212
2213         fz_free(ctx, sel);
2214
2215         return arr;
2216 }
2217
2218 JNIEXPORT void JNICALL
2219 JNI_FN(MuPDFCore_setFocusedWidgetChoiceSelectedInternal)(JNIEnv * env, jobject thiz, jobjectArray arr)
2220 {
2221         globals *glo = get_globals(env, thiz);
2222         fz_context *ctx = glo->ctx;
2223         pdf_document *idoc = pdf_specifics(glo->doc);
2224         pdf_widget *focus;
2225         int type;
2226         int nsel, i;
2227         char **sel = NULL;
2228         jstring *objs = NULL;
2229
2230         if (idoc == NULL)
2231                 return;
2232
2233         focus = pdf_focused_widget(idoc);
2234         if (focus == NULL)
2235                 return;
2236
2237         type = pdf_widget_get_type(focus);
2238         if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX)
2239                 return;
2240
2241         nsel = (*env)->GetArrayLength(env, arr);
2242
2243         sel = calloc(nsel, sizeof(*sel));
2244         objs = calloc(nsel, sizeof(*objs));
2245         if (objs == NULL || sel == NULL)
2246         {
2247                 free(sel);
2248                 free(objs);
2249                 LOGE("Failed in setFocusWidgetChoiceSelected");
2250                 return;
2251         }
2252
2253         for (i = 0; i < nsel; i++)
2254         {
2255                 objs[i] = (jstring)(*env)->GetObjectArrayElement(env, arr, i);
2256                 sel[i] = (char *)(*env)->GetStringUTFChars(env, objs[i], NULL);
2257         }
2258
2259         fz_try(ctx)
2260         {
2261                 pdf_choice_widget_set_value(idoc, focus, nsel, sel);
2262                 dump_annotation_display_lists(glo);
2263         }
2264         fz_catch(ctx)
2265         {
2266                 LOGE("Failed in setFocusWidgetChoiceSelected");
2267         }
2268
2269         for (i = 0; i < nsel; i++)
2270                 (*env)->ReleaseStringUTFChars(env, objs[i], sel[i]);
2271
2272         free(sel);
2273         free(objs);
2274 }
2275
2276 JNIEXPORT int JNICALL
2277 JNI_FN(MuPDFCore_getFocusedWidgetTypeInternal)(JNIEnv * env, jobject thiz)
2278 {
2279         globals *glo = get_globals(env, thiz);
2280         pdf_document *idoc = pdf_specifics(glo->doc);
2281         pdf_widget *focus;
2282
2283         if (idoc == NULL)
2284                 return NONE;
2285
2286         focus = pdf_focused_widget(idoc);
2287
2288         if (focus == NULL)
2289                 return NONE;
2290
2291         switch (pdf_widget_get_type(focus))
2292         {
2293         case PDF_WIDGET_TYPE_TEXT: return TEXT;
2294         case PDF_WIDGET_TYPE_LISTBOX: return LISTBOX;
2295         case PDF_WIDGET_TYPE_COMBOBOX: return COMBOBOX;
2296         case PDF_WIDGET_TYPE_SIGNATURE: return SIGNATURE;
2297         }
2298
2299         return NONE;
2300 }
2301
2302 /* This enum should be kept in line with SignatureState in MuPDFPageView.java */
2303 enum
2304 {
2305         Signature_NoSupport,
2306         Signature_Unsigned,
2307         Signature_Signed
2308 };
2309
2310 JNIEXPORT int JNICALL
2311 JNI_FN(MuPDFCore_getFocusedWidgetSignatureState)(JNIEnv * env, jobject thiz)
2312 {
2313         globals *glo = get_globals(env, thiz);
2314         pdf_document *idoc = pdf_specifics(glo->doc);
2315         pdf_widget *focus;
2316
2317         if (idoc == NULL)
2318                 return Signature_NoSupport;
2319
2320         focus = pdf_focused_widget(idoc);
2321
2322         if (focus == NULL)
2323                 return Signature_NoSupport;
2324
2325         if (!pdf_signatures_supported())
2326                 return Signature_NoSupport;
2327
2328         return pdf_dict_gets(((pdf_annot *)focus)->obj, "V") ? Signature_Signed : Signature_Unsigned;
2329 }
2330
2331 JNIEXPORT jstring JNICALL
2332 JNI_FN(MuPDFCore_checkFocusedSignatureInternal)(JNIEnv * env, jobject thiz)
2333 {
2334         globals *glo = get_globals(env, thiz);
2335         pdf_document *idoc = pdf_specifics(glo->doc);
2336         pdf_widget *focus;
2337         char ebuf[256] = "Failed";
2338
2339         if (idoc == NULL)
2340                 goto exit;
2341
2342         focus = pdf_focused_widget(idoc);
2343
2344         if (focus == NULL)
2345                 goto exit;
2346
2347         if (pdf_check_signature(idoc, focus, glo->current_path, ebuf, sizeof(ebuf)))
2348         {
2349                 strcpy(ebuf, "Signature is valid");
2350         }
2351
2352 exit:
2353         return (*env)->NewStringUTF(env, ebuf);
2354 }
2355
2356 JNIEXPORT jboolean JNICALL
2357 JNI_FN(MuPDFCore_signFocusedSignatureInternal)(JNIEnv * env, jobject thiz, jstring jkeyfile, jstring jpassword)
2358 {
2359         globals *glo = get_globals(env, thiz);
2360         fz_context *ctx = glo->ctx;
2361         pdf_document *idoc = pdf_specifics(glo->doc);
2362         pdf_widget *focus;
2363         const char *keyfile;
2364         const char *password;
2365         jboolean res;
2366
2367         if (idoc == NULL)
2368                 return JNI_FALSE;
2369
2370         focus = pdf_focused_widget(idoc);
2371
2372         if (focus == NULL)
2373                 return JNI_FALSE;
2374
2375         keyfile = (*env)->GetStringUTFChars(env, jkeyfile, NULL);
2376         password = (*env)->GetStringUTFChars(env, jpassword, NULL);
2377         if (keyfile == NULL || password == NULL)
2378                 return JNI_FALSE;
2379
2380         fz_var(res);
2381         fz_try(ctx)
2382         {
2383                 pdf_sign_signature(idoc, focus, keyfile, password);
2384                 dump_annotation_display_lists(glo);
2385                 res = JNI_TRUE;
2386         }
2387         fz_catch(ctx)
2388         {
2389                 res = JNI_FALSE;
2390         }
2391
2392         return res;
2393 }
2394
2395 JNIEXPORT jobject JNICALL
2396 JNI_FN(MuPDFCore_waitForAlertInternal)(JNIEnv * env, jobject thiz)
2397 {
2398         globals *glo = get_globals(env, thiz);
2399         jclass alertClass;
2400         jmethodID ctor;
2401         jstring title;
2402         jstring message;
2403         int alert_present;
2404         pdf_alert_event alert;
2405
2406         LOGT("Enter waitForAlert");
2407         pthread_mutex_lock(&glo->fin_lock);
2408         pthread_mutex_lock(&glo->alert_lock);
2409
2410         while (glo->alerts_active && !glo->alert_request)
2411                 pthread_cond_wait(&glo->alert_request_cond, &glo->alert_lock);
2412         glo->alert_request = 0;
2413
2414         alert_present = (glo->alerts_active && glo->current_alert);
2415
2416         if (alert_present)
2417                 alert = *glo->current_alert;
2418
2419         pthread_mutex_unlock(&glo->alert_lock);
2420         pthread_mutex_unlock(&glo->fin_lock);
2421         LOGT("Exit waitForAlert %d", alert_present);
2422
2423         if (!alert_present)
2424                 return NULL;
2425
2426         alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal");
2427         if (alertClass == NULL)
2428                 return NULL;
2429
2430         ctor = (*env)->GetMethodID(env, alertClass, "<init>", "(Ljava/lang/String;IILjava/lang/String;I)V");
2431         if (ctor == NULL)
2432                 return NULL;
2433
2434         title = (*env)->NewStringUTF(env, alert.title);
2435         if (title == NULL)
2436                 return NULL;
2437
2438         message = (*env)->NewStringUTF(env, alert.message);
2439         if (message == NULL)
2440                 return NULL;
2441
2442         return (*env)->NewObject(env, alertClass, ctor, message, alert.icon_type, alert.button_group_type, title, alert.button_pressed);
2443 }
2444
2445 JNIEXPORT void JNICALL
2446 JNI_FN(MuPDFCore_replyToAlertInternal)(JNIEnv * env, jobject thiz, jobject alert)
2447 {
2448         globals *glo = get_globals(env, thiz);
2449         jclass alertClass;
2450         jfieldID field;
2451         int button_pressed;
2452
2453         alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal");
2454         if (alertClass == NULL)
2455                 return;
2456
2457         field = (*env)->GetFieldID(env, alertClass, "buttonPressed", "I");
2458         if (field == NULL)
2459                 return;
2460
2461         button_pressed = (*env)->GetIntField(env, alert, field);
2462
2463         LOGT("Enter replyToAlert");
2464         pthread_mutex_lock(&glo->alert_lock);
2465
2466         if (glo->alerts_active && glo->current_alert)
2467         {
2468                 // Fill in button_pressed and signal reply received.
2469                 glo->current_alert->button_pressed = button_pressed;
2470                 glo->alert_reply = 1;
2471                 pthread_cond_signal(&glo->alert_reply_cond);
2472         }
2473
2474         pthread_mutex_unlock(&glo->alert_lock);
2475         LOGT("Exit replyToAlert");
2476 }
2477
2478 JNIEXPORT void JNICALL
2479 JNI_FN(MuPDFCore_startAlertsInternal)(JNIEnv * env, jobject thiz)
2480 {
2481         globals *glo = get_globals(env, thiz);
2482
2483         if (!glo->alerts_initialised)
2484                 return;
2485
2486         LOGT("Enter startAlerts");
2487         pthread_mutex_lock(&glo->alert_lock);
2488
2489         glo->alert_reply = 0;
2490         glo->alert_request = 0;
2491         glo->alerts_active = 1;
2492         glo->current_alert = NULL;
2493
2494         pthread_mutex_unlock(&glo->alert_lock);
2495         LOGT("Exit startAlerts");
2496 }
2497
2498 JNIEXPORT void JNICALL
2499 JNI_FN(MuPDFCore_stopAlertsInternal)(JNIEnv * env, jobject thiz)
2500 {
2501         globals *glo = get_globals(env, thiz);
2502
2503         if (!glo->alerts_initialised)
2504                 return;
2505
2506         LOGT("Enter stopAlerts");
2507         pthread_mutex_lock(&glo->alert_lock);
2508
2509         glo->alert_reply = 0;
2510         glo->alert_request = 0;
2511         glo->alerts_active = 0;
2512         glo->current_alert = NULL;
2513         pthread_cond_signal(&glo->alert_reply_cond);
2514         pthread_cond_signal(&glo->alert_request_cond);
2515
2516         pthread_mutex_unlock(&glo->alert_lock);
2517         LOGT("Exit stopAleerts");
2518 }
2519
2520 JNIEXPORT jboolean JNICALL
2521 JNI_FN(MuPDFCore_hasChangesInternal)(JNIEnv * env, jobject thiz)
2522 {
2523         globals *glo = get_globals(env, thiz);
2524         pdf_document *idoc = pdf_specifics(glo->doc);
2525
2526         return (idoc && pdf_has_unsaved_changes(idoc)) ? JNI_TRUE : JNI_FALSE;
2527 }
2528
2529 static char *tmp_path(char *path)
2530 {
2531         int f;
2532         char *buf = malloc(strlen(path) + 6 + 1);
2533         if (!buf)
2534                 return NULL;
2535
2536         strcpy(buf, path);
2537         strcat(buf, "XXXXXX");
2538
2539         f = mkstemp(buf);
2540
2541         if (f >= 0)
2542         {
2543                 close(f);
2544                 return buf;
2545         }
2546         else
2547         {
2548                 free(buf);
2549                 return NULL;
2550         }
2551 }
2552
2553 JNIEXPORT void JNICALL
2554 JNI_FN(MuPDFCore_saveInternal)(JNIEnv * env, jobject thiz)
2555 {
2556         globals *glo = get_globals(env, thiz);
2557         fz_context *ctx = glo->ctx;
2558
2559         if (glo->doc && glo->current_path)
2560         {
2561                 char *tmp;
2562                 fz_write_options opts;
2563                 opts.do_incremental = 1;
2564                 opts.do_ascii = 0;
2565                 opts.do_expand = 0;
2566                 opts.do_garbage = 0;
2567                 opts.do_linear = 0;
2568
2569                 tmp = tmp_path(glo->current_path);
2570                 if (tmp)
2571                 {
2572                         int written = 0;
2573
2574                         fz_var(written);
2575                         fz_try(ctx)
2576                         {
2577                                 FILE *fin = fopen(glo->current_path, "rb");
2578                                 FILE *fout = fopen(tmp, "wb");
2579                                 char buf[256];
2580                                 int n, err = 1;
2581
2582                                 if (fin && fout)
2583                                 {
2584                                         while ((n = fread(buf, 1, sizeof(buf), fin)) > 0)
2585                                                 fwrite(buf, 1, n, fout);
2586                                         err = (ferror(fin) || ferror(fout));
2587                                 }
2588
2589                                 if (fin)
2590                                         fclose(fin);
2591                                 if (fout)
2592                                         fclose(fout);
2593
2594                                 if (!err)
2595                                 {
2596                                         fz_write_document(glo->doc, tmp, &opts);
2597                                         written = 1;
2598                                 }
2599                         }
2600                         fz_catch(ctx)
2601                         {
2602                                 written = 0;
2603                         }
2604
2605                         if (written)
2606                         {
2607                                 close_doc(glo);
2608                                 rename(tmp, glo->current_path);
2609                         }
2610
2611                         free(tmp);
2612                 }
2613         }
2614 }
2615
2616 JNIEXPORT void JNICALL
2617 JNI_FN(MuPDFCore_dumpMemoryInternal)(JNIEnv * env, jobject thiz)
2618 {
2619         globals *glo = get_globals(env, thiz);
2620         fz_context *ctx = glo->ctx;
2621
2622 #ifdef MEMENTO
2623         LOGE("dumpMemoryInternal start");
2624         Memento_listNewBlocks();
2625         Memento_stats();
2626         LOGE("dumpMemoryInternal end");
2627 #endif
2628 }
2629
2630 JNIEXPORT jlong JNICALL
2631 JNI_FN(MuPDFCore_createCookie)(JNIEnv * env, jobject thiz)
2632 {
2633         globals *glo = get_globals_any_thread(env, thiz);
2634         if (glo == NULL)
2635                 return 0;
2636         fz_context *ctx = glo->ctx;
2637
2638         return (jlong) (intptr_t) fz_calloc_no_throw(ctx,1, sizeof(fz_cookie));
2639 }
2640
2641 JNIEXPORT void JNICALL
2642 JNI_FN(MuPDFCore_destroyCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr)
2643 {
2644         fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr;
2645         globals *glo = get_globals_any_thread(env, thiz);
2646         if (glo == NULL)
2647                 return;
2648         fz_context *ctx = glo->ctx;
2649
2650         fz_free(ctx, cookie);
2651 }
2652
2653 JNIEXPORT void JNICALL
2654 JNI_FN(MuPDFCore_abortCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr)
2655 {
2656         fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr;
2657         if (cookie != NULL)
2658                 cookie->abort = 1;
2659 }