From d68576c3785572c1f5d41f83015b8fe6bbcbe9e8 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Wed, 30 Mar 2016 17:49:04 +0200 Subject: Reorganize java and android source. platform/java and platform/android are reorganized: platform/java The new JNI Java classes, mupdf_native.{c,h}, Makefile and Makejar. platform/java/example The example desktop viewer classes. platform/android/viewer The original demo viewer. ndk-build is used to build libmupdf_java.so, making reference to mupdf_native.{c,h} in platform/java. --- platform/android/AndroidManifest.xml | 102 - platform/android/ClassStructure.txt | 187 -- platform/android/Icons.txt | 2 - platform/android/Makefile | 4 - platform/android/ReadMe.txt | 188 -- platform/android/build.gradle | 89 - platform/android/build.sh | 1 - platform/android/build.xml | 103 - platform/android/jni/Android.mk | 39 - platform/android/jni/Application.mk | 49 - platform/android/jni/Core.mk | 79 - platform/android/jni/ThirdParty.mk | 177 -- platform/android/jni/mupdf.c | 2885 -------------------- platform/android/local.properties.sample | 8 - platform/android/project.properties | 11 - platform/android/res/animator/info.xml | 20 - platform/android/res/drawable-hdpi/icon.png | Bin 4791 -> 0 bytes platform/android/res/drawable-ldpi/ic_annot.png | Bin 311 -> 0 bytes .../android/res/drawable-ldpi/ic_annotation.png | Bin 480 -> 0 bytes .../android/res/drawable-ldpi/ic_arrow_left.png | Bin 204 -> 0 bytes .../android/res/drawable-ldpi/ic_arrow_right.png | Bin 208 -> 0 bytes platform/android/res/drawable-ldpi/ic_cancel.png | Bin 204 -> 0 bytes platform/android/res/drawable-ldpi/ic_check.png | Bin 213 -> 0 bytes .../android/res/drawable-ldpi/ic_clipboard.png | Bin 1309 -> 0 bytes platform/android/res/drawable-ldpi/ic_dir.png | Bin 157 -> 0 bytes platform/android/res/drawable-ldpi/ic_doc.png | Bin 1262 -> 0 bytes .../android/res/drawable-ldpi/ic_highlight.png | Bin 378 -> 0 bytes platform/android/res/drawable-ldpi/ic_link.png | Bin 342 -> 0 bytes platform/android/res/drawable-ldpi/ic_list.png | Bin 2821 -> 0 bytes .../res/drawable-ldpi/ic_magnifying_glass.png | Bin 323 -> 0 bytes platform/android/res/drawable-ldpi/ic_more.png | Bin 533 -> 0 bytes platform/android/res/drawable-ldpi/ic_pen.png | Bin 257 -> 0 bytes platform/android/res/drawable-ldpi/ic_print.png | Bin 1248 -> 0 bytes platform/android/res/drawable-ldpi/ic_proof.png | Bin 1229 -> 0 bytes platform/android/res/drawable-ldpi/ic_reflow.png | Bin 220 -> 0 bytes platform/android/res/drawable-ldpi/ic_select.png | Bin 247 -> 0 bytes platform/android/res/drawable-ldpi/ic_share.png | Bin 313 -> 0 bytes platform/android/res/drawable-ldpi/ic_strike.png | Bin 449 -> 0 bytes platform/android/res/drawable-ldpi/ic_trash.png | Bin 246 -> 0 bytes .../android/res/drawable-ldpi/ic_underline.png | Bin 403 -> 0 bytes platform/android/res/drawable-ldpi/ic_updir.png | Bin 268 -> 0 bytes platform/android/res/drawable-ldpi/icon.png | Bin 2158 -> 0 bytes platform/android/res/drawable-mdpi/ic_annot.png | Bin 418 -> 0 bytes .../android/res/drawable-mdpi/ic_annotation.png | Bin 601 -> 0 bytes .../android/res/drawable-mdpi/ic_arrow_left.png | Bin 225 -> 0 bytes .../android/res/drawable-mdpi/ic_arrow_right.png | Bin 233 -> 0 bytes platform/android/res/drawable-mdpi/ic_arrow_up.png | Bin 297 -> 0 bytes platform/android/res/drawable-mdpi/ic_cancel.png | Bin 224 -> 0 bytes platform/android/res/drawable-mdpi/ic_check.png | Bin 251 -> 0 bytes .../android/res/drawable-mdpi/ic_clipboard.png | Bin 1345 -> 0 bytes platform/android/res/drawable-mdpi/ic_dir.png | Bin 165 -> 0 bytes platform/android/res/drawable-mdpi/ic_doc.png | Bin 1277 -> 0 bytes .../android/res/drawable-mdpi/ic_highlight.png | Bin 524 -> 0 bytes platform/android/res/drawable-mdpi/ic_link.png | Bin 374 -> 0 bytes platform/android/res/drawable-mdpi/ic_list.png | Bin 2791 -> 0 bytes .../res/drawable-mdpi/ic_magnifying_glass.png | Bin 386 -> 0 bytes platform/android/res/drawable-mdpi/ic_more.png | Bin 671 -> 0 bytes platform/android/res/drawable-mdpi/ic_pen.png | Bin 282 -> 0 bytes platform/android/res/drawable-mdpi/ic_print.png | Bin 1250 -> 0 bytes platform/android/res/drawable-mdpi/ic_proof.png | Bin 2267 -> 0 bytes platform/android/res/drawable-mdpi/ic_reflow.png | Bin 234 -> 0 bytes platform/android/res/drawable-mdpi/ic_select.png | Bin 280 -> 0 bytes platform/android/res/drawable-mdpi/ic_sep.png | Bin 1098 -> 0 bytes platform/android/res/drawable-mdpi/ic_share.png | Bin 361 -> 0 bytes platform/android/res/drawable-mdpi/ic_strike.png | Bin 622 -> 0 bytes platform/android/res/drawable-mdpi/ic_trash.png | Bin 291 -> 0 bytes .../android/res/drawable-mdpi/ic_underline.png | Bin 565 -> 0 bytes platform/android/res/drawable-mdpi/icon.png | Bin 3009 -> 0 bytes platform/android/res/drawable-xhdpi/icon.png | Bin 6413 -> 0 bytes platform/android/res/drawable/busy.xml | 10 - platform/android/res/drawable/button.xml | 23 - platform/android/res/drawable/darkdenim3.png | Bin 22532 -> 0 bytes platform/android/res/drawable/page_num.xml | 9 - platform/android/res/drawable/search.xml | 37 - platform/android/res/drawable/seek_progress.xml | 6 - platform/android/res/drawable/seek_thumb.xml | 7 - platform/android/res/drawable/tiled_background.xml | 4 - platform/android/res/layout/buttons.xml | 408 --- platform/android/res/layout/main.xml | 5 - platform/android/res/layout/outline_entry.xml | 27 - platform/android/res/layout/picker_entry.xml | 25 - platform/android/res/layout/print_dialog.xml | 9 - platform/android/res/layout/textentry.xml | 8 - platform/android/res/values-ar/strings.xml | 54 - platform/android/res/values-ca/strings.xml | 54 - platform/android/res/values-cs/strings.xml | 54 - platform/android/res/values-da/strings.xml | 54 - platform/android/res/values-de/strings.xml | 54 - platform/android/res/values-el/strings.xml | 54 - platform/android/res/values-es/strings.xml | 54 - platform/android/res/values-et/strings.xml | 54 - platform/android/res/values-fi/strings.xml | 54 - platform/android/res/values-fr/strings.xml | 54 - platform/android/res/values-hi/strings.xml | 54 - platform/android/res/values-hu/strings.xml | 54 - platform/android/res/values-in/strings.xml | 54 - platform/android/res/values-it/strings.xml | 54 - platform/android/res/values-iw/strings.xml | 54 - platform/android/res/values-ja/strings.xml | 54 - platform/android/res/values-ko/strings.xml | 54 - platform/android/res/values-lt/strings.xml | 54 - platform/android/res/values-ms/strings.xml | 54 - platform/android/res/values-nl/strings.xml | 54 - platform/android/res/values-no/strings.xml | 54 - platform/android/res/values-pl/strings.xml | 54 - platform/android/res/values-pt/strings.xml | 54 - platform/android/res/values-ru/strings.xml | 54 - platform/android/res/values-sk/strings.xml | 54 - platform/android/res/values-sv/strings.xml | 54 - platform/android/res/values-th/strings.xml | 54 - platform/android/res/values-tl/strings.xml | 54 - platform/android/res/values-tr/strings.xml | 54 - platform/android/res/values-zh-rTW/strings.xml | 54 - platform/android/res/values-zh/strings.xml | 54 - platform/android/res/values/colors.xml | 16 - platform/android/res/values/strings.xml | 58 - platform/android/res/values/styles.xml | 5 - .../com/artifex/mupdf/fitz/AndroidDrawDevice.java | 22 - .../src/com/artifex/mupdfdemo/Annotation.java | 18 - .../src/com/artifex/mupdfdemo/ArrayDeque.java | 855 ------ .../src/com/artifex/mupdfdemo/AsyncTask.java | 670 ----- .../artifex/mupdfdemo/CancellableAsyncTask.java | 79 - .../mupdfdemo/CancellableTaskDefinition.java | 8 - .../com/artifex/mupdfdemo/ChoosePDFActivity.java | 227 -- .../com/artifex/mupdfdemo/ChoosePDFAdapter.java | 66 - .../src/com/artifex/mupdfdemo/ChoosePDFItem.java | 15 - .../android/src/com/artifex/mupdfdemo/Deque.java | 554 ---- .../src/com/artifex/mupdfdemo/FilePicker.java | 21 - .../src/com/artifex/mupdfdemo/LinkInfo.java | 14 - .../com/artifex/mupdfdemo/LinkInfoExternal.java | 14 - .../com/artifex/mupdfdemo/LinkInfoInternal.java | 14 - .../src/com/artifex/mupdfdemo/LinkInfoRemote.java | 18 - .../src/com/artifex/mupdfdemo/LinkInfoVisitor.java | 7 - .../src/com/artifex/mupdfdemo/MuPDFActivity.java | 1417 ---------- .../src/com/artifex/mupdfdemo/MuPDFAlert.java | 21 - .../com/artifex/mupdfdemo/MuPDFAlertInternal.java | 30 - .../mupdfdemo/MuPDFCancellableTaskDefinition.java | 38 - .../src/com/artifex/mupdfdemo/MuPDFCore.java | 402 --- .../com/artifex/mupdfdemo/MuPDFPageAdapter.java | 87 - .../src/com/artifex/mupdfdemo/MuPDFPageView.java | 692 ----- .../src/com/artifex/mupdfdemo/MuPDFReaderView.java | 276 -- .../com/artifex/mupdfdemo/MuPDFReflowAdapter.java | 43 - .../src/com/artifex/mupdfdemo/MuPDFReflowView.java | 182 -- .../src/com/artifex/mupdfdemo/MuPDFView.java | 33 - .../src/com/artifex/mupdfdemo/OutlineActivity.java | 31 - .../com/artifex/mupdfdemo/OutlineActivityData.java | 17 - .../src/com/artifex/mupdfdemo/OutlineAdapter.java | 46 - .../src/com/artifex/mupdfdemo/OutlineItem.java | 14 - .../src/com/artifex/mupdfdemo/PageView.java | 698 ----- .../com/artifex/mupdfdemo/PrintDialogActivity.java | 145 - .../src/com/artifex/mupdfdemo/ReaderView.java | 936 ------- .../artifex/mupdfdemo/SafeAnimatorInflater.java | 35 - .../src/com/artifex/mupdfdemo/SearchTask.java | 128 - .../com/artifex/mupdfdemo/SearchTaskResult.java | 24 - .../src/com/artifex/mupdfdemo/Separation.java | 15 - .../android/src/com/artifex/mupdfdemo/Stepper.java | 42 - .../src/com/artifex/mupdfdemo/TextChar.java | 12 - .../src/com/artifex/mupdfdemo/TextWord.java | 17 - .../src/com/artifex/mupdfdemo/WidgetType.java | 9 - platform/android/viewer/.gitignore | 8 + platform/android/viewer/AndroidManifest.xml | 102 + platform/android/viewer/ClassStructure.txt | 187 ++ platform/android/viewer/Icons.txt | 2 + platform/android/viewer/Makefile | 3 + platform/android/viewer/ReadMe.txt | 188 ++ platform/android/viewer/build.xml | 85 + platform/android/viewer/jni/Android.mk | 38 + platform/android/viewer/jni/Application.mk | 49 + platform/android/viewer/jni/Core.mk | 80 + platform/android/viewer/jni/ThirdParty.mk | 177 ++ platform/android/viewer/jni/mupdf.c | 2885 ++++++++++++++++++++ platform/android/viewer/local.properties.sample | 5 + platform/android/viewer/project.properties | 11 + platform/android/viewer/res/animator/info.xml | 20 + platform/android/viewer/res/drawable-hdpi/icon.png | Bin 0 -> 4791 bytes .../android/viewer/res/drawable-ldpi/ic_annot.png | Bin 0 -> 311 bytes .../viewer/res/drawable-ldpi/ic_annotation.png | Bin 0 -> 480 bytes .../viewer/res/drawable-ldpi/ic_arrow_left.png | Bin 0 -> 204 bytes .../viewer/res/drawable-ldpi/ic_arrow_right.png | Bin 0 -> 208 bytes .../android/viewer/res/drawable-ldpi/ic_cancel.png | Bin 0 -> 204 bytes .../android/viewer/res/drawable-ldpi/ic_check.png | Bin 0 -> 213 bytes .../viewer/res/drawable-ldpi/ic_clipboard.png | Bin 0 -> 1309 bytes .../android/viewer/res/drawable-ldpi/ic_dir.png | Bin 0 -> 157 bytes .../android/viewer/res/drawable-ldpi/ic_doc.png | Bin 0 -> 1262 bytes .../viewer/res/drawable-ldpi/ic_highlight.png | Bin 0 -> 378 bytes .../android/viewer/res/drawable-ldpi/ic_link.png | Bin 0 -> 342 bytes .../android/viewer/res/drawable-ldpi/ic_list.png | Bin 0 -> 2821 bytes .../res/drawable-ldpi/ic_magnifying_glass.png | Bin 0 -> 323 bytes .../android/viewer/res/drawable-ldpi/ic_more.png | Bin 0 -> 533 bytes .../android/viewer/res/drawable-ldpi/ic_pen.png | Bin 0 -> 257 bytes .../android/viewer/res/drawable-ldpi/ic_print.png | Bin 0 -> 1248 bytes .../android/viewer/res/drawable-ldpi/ic_proof.png | Bin 0 -> 1229 bytes .../android/viewer/res/drawable-ldpi/ic_reflow.png | Bin 0 -> 220 bytes .../android/viewer/res/drawable-ldpi/ic_select.png | Bin 0 -> 247 bytes .../android/viewer/res/drawable-ldpi/ic_share.png | Bin 0 -> 313 bytes .../android/viewer/res/drawable-ldpi/ic_strike.png | Bin 0 -> 449 bytes .../android/viewer/res/drawable-ldpi/ic_trash.png | Bin 0 -> 246 bytes .../viewer/res/drawable-ldpi/ic_underline.png | Bin 0 -> 403 bytes .../android/viewer/res/drawable-ldpi/ic_updir.png | Bin 0 -> 268 bytes platform/android/viewer/res/drawable-ldpi/icon.png | Bin 0 -> 2158 bytes .../android/viewer/res/drawable-mdpi/ic_annot.png | Bin 0 -> 418 bytes .../viewer/res/drawable-mdpi/ic_annotation.png | Bin 0 -> 601 bytes .../viewer/res/drawable-mdpi/ic_arrow_left.png | Bin 0 -> 225 bytes .../viewer/res/drawable-mdpi/ic_arrow_right.png | Bin 0 -> 233 bytes .../viewer/res/drawable-mdpi/ic_arrow_up.png | Bin 0 -> 297 bytes .../android/viewer/res/drawable-mdpi/ic_cancel.png | Bin 0 -> 224 bytes .../android/viewer/res/drawable-mdpi/ic_check.png | Bin 0 -> 251 bytes .../viewer/res/drawable-mdpi/ic_clipboard.png | Bin 0 -> 1345 bytes .../android/viewer/res/drawable-mdpi/ic_dir.png | Bin 0 -> 165 bytes .../android/viewer/res/drawable-mdpi/ic_doc.png | Bin 0 -> 1277 bytes .../viewer/res/drawable-mdpi/ic_highlight.png | Bin 0 -> 524 bytes .../android/viewer/res/drawable-mdpi/ic_link.png | Bin 0 -> 374 bytes .../android/viewer/res/drawable-mdpi/ic_list.png | Bin 0 -> 2791 bytes .../res/drawable-mdpi/ic_magnifying_glass.png | Bin 0 -> 386 bytes .../android/viewer/res/drawable-mdpi/ic_more.png | Bin 0 -> 671 bytes .../android/viewer/res/drawable-mdpi/ic_pen.png | Bin 0 -> 282 bytes .../android/viewer/res/drawable-mdpi/ic_print.png | Bin 0 -> 1250 bytes .../android/viewer/res/drawable-mdpi/ic_proof.png | Bin 0 -> 2267 bytes .../android/viewer/res/drawable-mdpi/ic_reflow.png | Bin 0 -> 234 bytes .../android/viewer/res/drawable-mdpi/ic_select.png | Bin 0 -> 280 bytes .../android/viewer/res/drawable-mdpi/ic_sep.png | Bin 0 -> 1098 bytes .../android/viewer/res/drawable-mdpi/ic_share.png | Bin 0 -> 361 bytes .../android/viewer/res/drawable-mdpi/ic_strike.png | Bin 0 -> 622 bytes .../android/viewer/res/drawable-mdpi/ic_trash.png | Bin 0 -> 291 bytes .../viewer/res/drawable-mdpi/ic_underline.png | Bin 0 -> 565 bytes platform/android/viewer/res/drawable-mdpi/icon.png | Bin 0 -> 3009 bytes .../android/viewer/res/drawable-xhdpi/icon.png | Bin 0 -> 6413 bytes platform/android/viewer/res/drawable/busy.xml | 10 + platform/android/viewer/res/drawable/button.xml | 23 + .../android/viewer/res/drawable/darkdenim3.png | Bin 0 -> 22532 bytes platform/android/viewer/res/drawable/page_num.xml | 9 + platform/android/viewer/res/drawable/search.xml | 37 + .../android/viewer/res/drawable/seek_progress.xml | 6 + .../android/viewer/res/drawable/seek_thumb.xml | 7 + .../viewer/res/drawable/tiled_background.xml | 4 + platform/android/viewer/res/layout/buttons.xml | 408 +++ platform/android/viewer/res/layout/main.xml | 5 + .../android/viewer/res/layout/outline_entry.xml | 27 + .../android/viewer/res/layout/picker_entry.xml | 25 + .../android/viewer/res/layout/print_dialog.xml | 9 + platform/android/viewer/res/layout/textentry.xml | 8 + platform/android/viewer/res/values-ar/strings.xml | 54 + platform/android/viewer/res/values-ca/strings.xml | 54 + platform/android/viewer/res/values-cs/strings.xml | 54 + platform/android/viewer/res/values-da/strings.xml | 54 + platform/android/viewer/res/values-de/strings.xml | 54 + platform/android/viewer/res/values-el/strings.xml | 54 + platform/android/viewer/res/values-es/strings.xml | 54 + platform/android/viewer/res/values-et/strings.xml | 54 + platform/android/viewer/res/values-fi/strings.xml | 54 + platform/android/viewer/res/values-fr/strings.xml | 54 + platform/android/viewer/res/values-hi/strings.xml | 54 + platform/android/viewer/res/values-hu/strings.xml | 54 + platform/android/viewer/res/values-in/strings.xml | 54 + platform/android/viewer/res/values-it/strings.xml | 54 + platform/android/viewer/res/values-iw/strings.xml | 54 + platform/android/viewer/res/values-ja/strings.xml | 54 + platform/android/viewer/res/values-ko/strings.xml | 54 + platform/android/viewer/res/values-lt/strings.xml | 54 + platform/android/viewer/res/values-ms/strings.xml | 54 + platform/android/viewer/res/values-nl/strings.xml | 54 + platform/android/viewer/res/values-no/strings.xml | 54 + platform/android/viewer/res/values-pl/strings.xml | 54 + platform/android/viewer/res/values-pt/strings.xml | 54 + platform/android/viewer/res/values-ru/strings.xml | 54 + platform/android/viewer/res/values-sk/strings.xml | 54 + platform/android/viewer/res/values-sv/strings.xml | 54 + platform/android/viewer/res/values-th/strings.xml | 54 + platform/android/viewer/res/values-tl/strings.xml | 54 + platform/android/viewer/res/values-tr/strings.xml | 54 + .../android/viewer/res/values-zh-rTW/strings.xml | 54 + platform/android/viewer/res/values-zh/strings.xml | 54 + platform/android/viewer/res/values/colors.xml | 16 + platform/android/viewer/res/values/strings.xml | 58 + platform/android/viewer/res/values/styles.xml | 5 + .../src/com/artifex/mupdfdemo/Annotation.java | 18 + .../src/com/artifex/mupdfdemo/ArrayDeque.java | 855 ++++++ .../src/com/artifex/mupdfdemo/AsyncTask.java | 670 +++++ .../artifex/mupdfdemo/CancellableAsyncTask.java | 79 + .../mupdfdemo/CancellableTaskDefinition.java | 8 + .../com/artifex/mupdfdemo/ChoosePDFActivity.java | 227 ++ .../com/artifex/mupdfdemo/ChoosePDFAdapter.java | 66 + .../src/com/artifex/mupdfdemo/ChoosePDFItem.java | 15 + .../viewer/src/com/artifex/mupdfdemo/Deque.java | 554 ++++ .../src/com/artifex/mupdfdemo/FilePicker.java | 21 + .../viewer/src/com/artifex/mupdfdemo/LinkInfo.java | 14 + .../com/artifex/mupdfdemo/LinkInfoExternal.java | 14 + .../com/artifex/mupdfdemo/LinkInfoInternal.java | 14 + .../src/com/artifex/mupdfdemo/LinkInfoRemote.java | 18 + .../src/com/artifex/mupdfdemo/LinkInfoVisitor.java | 7 + .../src/com/artifex/mupdfdemo/MuPDFActivity.java | 1417 ++++++++++ .../src/com/artifex/mupdfdemo/MuPDFAlert.java | 21 + .../com/artifex/mupdfdemo/MuPDFAlertInternal.java | 30 + .../mupdfdemo/MuPDFCancellableTaskDefinition.java | 38 + .../src/com/artifex/mupdfdemo/MuPDFCore.java | 402 +++ .../com/artifex/mupdfdemo/MuPDFPageAdapter.java | 87 + .../src/com/artifex/mupdfdemo/MuPDFPageView.java | 692 +++++ .../src/com/artifex/mupdfdemo/MuPDFReaderView.java | 276 ++ .../com/artifex/mupdfdemo/MuPDFReflowAdapter.java | 43 + .../src/com/artifex/mupdfdemo/MuPDFReflowView.java | 182 ++ .../src/com/artifex/mupdfdemo/MuPDFView.java | 33 + .../src/com/artifex/mupdfdemo/OutlineActivity.java | 31 + .../com/artifex/mupdfdemo/OutlineActivityData.java | 17 + .../src/com/artifex/mupdfdemo/OutlineAdapter.java | 46 + .../src/com/artifex/mupdfdemo/OutlineItem.java | 14 + .../viewer/src/com/artifex/mupdfdemo/PageView.java | 698 +++++ .../com/artifex/mupdfdemo/PrintDialogActivity.java | 145 + .../src/com/artifex/mupdfdemo/ReaderView.java | 936 +++++++ .../artifex/mupdfdemo/SafeAnimatorInflater.java | 35 + .../src/com/artifex/mupdfdemo/SearchTask.java | 128 + .../com/artifex/mupdfdemo/SearchTaskResult.java | 24 + .../src/com/artifex/mupdfdemo/Separation.java | 15 + .../viewer/src/com/artifex/mupdfdemo/Stepper.java | 42 + .../viewer/src/com/artifex/mupdfdemo/TextChar.java | 12 + .../viewer/src/com/artifex/mupdfdemo/TextWord.java | 17 + .../src/com/artifex/mupdfdemo/WidgetType.java | 9 + platform/java/Makefile | 70 +- platform/java/Makejar | 2 +- platform/java/PageCanvas.java | 57 - platform/java/TraceDevice.java | 130 - platform/java/Viewer.java | 108 - platform/java/example/PageCanvas.java | 59 + platform/java/example/TraceDevice.java | 132 + platform/java/example/Viewer.java | 203 ++ platform/java/mupdf_native.c | 4 +- 325 files changed, 14580 insertions(+), 14593 deletions(-) delete mode 100644 platform/android/AndroidManifest.xml delete mode 100644 platform/android/ClassStructure.txt delete mode 100644 platform/android/Icons.txt delete mode 100644 platform/android/Makefile delete mode 100644 platform/android/ReadMe.txt delete mode 100644 platform/android/build.gradle delete mode 100644 platform/android/build.sh delete mode 100644 platform/android/build.xml delete mode 100644 platform/android/jni/Android.mk delete mode 100644 platform/android/jni/Application.mk delete mode 100644 platform/android/jni/Core.mk delete mode 100644 platform/android/jni/ThirdParty.mk delete mode 100644 platform/android/jni/mupdf.c delete mode 100644 platform/android/local.properties.sample delete mode 100644 platform/android/project.properties delete mode 100644 platform/android/res/animator/info.xml delete mode 100644 platform/android/res/drawable-hdpi/icon.png delete mode 100644 platform/android/res/drawable-ldpi/ic_annot.png delete mode 100644 platform/android/res/drawable-ldpi/ic_annotation.png delete mode 100644 platform/android/res/drawable-ldpi/ic_arrow_left.png delete mode 100644 platform/android/res/drawable-ldpi/ic_arrow_right.png delete mode 100644 platform/android/res/drawable-ldpi/ic_cancel.png delete mode 100644 platform/android/res/drawable-ldpi/ic_check.png delete mode 100644 platform/android/res/drawable-ldpi/ic_clipboard.png delete mode 100644 platform/android/res/drawable-ldpi/ic_dir.png delete mode 100644 platform/android/res/drawable-ldpi/ic_doc.png delete mode 100644 platform/android/res/drawable-ldpi/ic_highlight.png delete mode 100644 platform/android/res/drawable-ldpi/ic_link.png delete mode 100644 platform/android/res/drawable-ldpi/ic_list.png delete mode 100644 platform/android/res/drawable-ldpi/ic_magnifying_glass.png delete mode 100644 platform/android/res/drawable-ldpi/ic_more.png delete mode 100644 platform/android/res/drawable-ldpi/ic_pen.png delete mode 100644 platform/android/res/drawable-ldpi/ic_print.png delete mode 100644 platform/android/res/drawable-ldpi/ic_proof.png delete mode 100644 platform/android/res/drawable-ldpi/ic_reflow.png delete mode 100644 platform/android/res/drawable-ldpi/ic_select.png delete mode 100644 platform/android/res/drawable-ldpi/ic_share.png delete mode 100644 platform/android/res/drawable-ldpi/ic_strike.png delete mode 100644 platform/android/res/drawable-ldpi/ic_trash.png delete mode 100644 platform/android/res/drawable-ldpi/ic_underline.png delete mode 100644 platform/android/res/drawable-ldpi/ic_updir.png delete mode 100644 platform/android/res/drawable-ldpi/icon.png delete mode 100644 platform/android/res/drawable-mdpi/ic_annot.png delete mode 100644 platform/android/res/drawable-mdpi/ic_annotation.png delete mode 100644 platform/android/res/drawable-mdpi/ic_arrow_left.png delete mode 100644 platform/android/res/drawable-mdpi/ic_arrow_right.png delete mode 100644 platform/android/res/drawable-mdpi/ic_arrow_up.png delete mode 100644 platform/android/res/drawable-mdpi/ic_cancel.png delete mode 100644 platform/android/res/drawable-mdpi/ic_check.png delete mode 100644 platform/android/res/drawable-mdpi/ic_clipboard.png delete mode 100644 platform/android/res/drawable-mdpi/ic_dir.png delete mode 100644 platform/android/res/drawable-mdpi/ic_doc.png delete mode 100644 platform/android/res/drawable-mdpi/ic_highlight.png delete mode 100644 platform/android/res/drawable-mdpi/ic_link.png delete mode 100644 platform/android/res/drawable-mdpi/ic_list.png delete mode 100644 platform/android/res/drawable-mdpi/ic_magnifying_glass.png delete mode 100644 platform/android/res/drawable-mdpi/ic_more.png delete mode 100644 platform/android/res/drawable-mdpi/ic_pen.png delete mode 100644 platform/android/res/drawable-mdpi/ic_print.png delete mode 100644 platform/android/res/drawable-mdpi/ic_proof.png delete mode 100644 platform/android/res/drawable-mdpi/ic_reflow.png delete mode 100644 platform/android/res/drawable-mdpi/ic_select.png delete mode 100644 platform/android/res/drawable-mdpi/ic_sep.png delete mode 100644 platform/android/res/drawable-mdpi/ic_share.png delete mode 100644 platform/android/res/drawable-mdpi/ic_strike.png delete mode 100644 platform/android/res/drawable-mdpi/ic_trash.png delete mode 100644 platform/android/res/drawable-mdpi/ic_underline.png delete mode 100644 platform/android/res/drawable-mdpi/icon.png delete mode 100644 platform/android/res/drawable-xhdpi/icon.png delete mode 100644 platform/android/res/drawable/busy.xml delete mode 100644 platform/android/res/drawable/button.xml delete mode 100644 platform/android/res/drawable/darkdenim3.png delete mode 100644 platform/android/res/drawable/page_num.xml delete mode 100644 platform/android/res/drawable/search.xml delete mode 100644 platform/android/res/drawable/seek_progress.xml delete mode 100644 platform/android/res/drawable/seek_thumb.xml delete mode 100644 platform/android/res/drawable/tiled_background.xml delete mode 100644 platform/android/res/layout/buttons.xml delete mode 100644 platform/android/res/layout/main.xml delete mode 100644 platform/android/res/layout/outline_entry.xml delete mode 100644 platform/android/res/layout/picker_entry.xml delete mode 100644 platform/android/res/layout/print_dialog.xml delete mode 100644 platform/android/res/layout/textentry.xml delete mode 100644 platform/android/res/values-ar/strings.xml delete mode 100644 platform/android/res/values-ca/strings.xml delete mode 100644 platform/android/res/values-cs/strings.xml delete mode 100644 platform/android/res/values-da/strings.xml delete mode 100644 platform/android/res/values-de/strings.xml delete mode 100644 platform/android/res/values-el/strings.xml delete mode 100644 platform/android/res/values-es/strings.xml delete mode 100644 platform/android/res/values-et/strings.xml delete mode 100644 platform/android/res/values-fi/strings.xml delete mode 100644 platform/android/res/values-fr/strings.xml delete mode 100644 platform/android/res/values-hi/strings.xml delete mode 100644 platform/android/res/values-hu/strings.xml delete mode 100644 platform/android/res/values-in/strings.xml delete mode 100644 platform/android/res/values-it/strings.xml delete mode 100644 platform/android/res/values-iw/strings.xml delete mode 100644 platform/android/res/values-ja/strings.xml delete mode 100644 platform/android/res/values-ko/strings.xml delete mode 100644 platform/android/res/values-lt/strings.xml delete mode 100644 platform/android/res/values-ms/strings.xml delete mode 100644 platform/android/res/values-nl/strings.xml delete mode 100644 platform/android/res/values-no/strings.xml delete mode 100644 platform/android/res/values-pl/strings.xml delete mode 100644 platform/android/res/values-pt/strings.xml delete mode 100644 platform/android/res/values-ru/strings.xml delete mode 100644 platform/android/res/values-sk/strings.xml delete mode 100644 platform/android/res/values-sv/strings.xml delete mode 100644 platform/android/res/values-th/strings.xml delete mode 100644 platform/android/res/values-tl/strings.xml delete mode 100644 platform/android/res/values-tr/strings.xml delete mode 100644 platform/android/res/values-zh-rTW/strings.xml delete mode 100644 platform/android/res/values-zh/strings.xml delete mode 100644 platform/android/res/values/colors.xml delete mode 100644 platform/android/res/values/strings.xml delete mode 100644 platform/android/res/values/styles.xml delete mode 100644 platform/android/src/com/artifex/mupdf/fitz/AndroidDrawDevice.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/Annotation.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/ArrayDeque.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/AsyncTask.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/CancellableAsyncTask.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/ChoosePDFItem.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/Deque.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/FilePicker.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/LinkInfo.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/LinkInfoExternal.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/LinkInfoInternal.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/LinkInfoRemote.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFAlert.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFReaderView.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFReflowView.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/MuPDFView.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/OutlineActivity.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/OutlineActivityData.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/OutlineAdapter.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/OutlineItem.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/PageView.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/PrintDialogActivity.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/ReaderView.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/SearchTask.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/SearchTaskResult.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/Separation.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/Stepper.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/TextChar.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/TextWord.java delete mode 100644 platform/android/src/com/artifex/mupdfdemo/WidgetType.java create mode 100644 platform/android/viewer/.gitignore create mode 100644 platform/android/viewer/AndroidManifest.xml create mode 100644 platform/android/viewer/ClassStructure.txt create mode 100644 platform/android/viewer/Icons.txt create mode 100644 platform/android/viewer/Makefile create mode 100644 platform/android/viewer/ReadMe.txt create mode 100644 platform/android/viewer/build.xml create mode 100644 platform/android/viewer/jni/Android.mk create mode 100644 platform/android/viewer/jni/Application.mk create mode 100644 platform/android/viewer/jni/Core.mk create mode 100644 platform/android/viewer/jni/ThirdParty.mk create mode 100644 platform/android/viewer/jni/mupdf.c create mode 100644 platform/android/viewer/local.properties.sample create mode 100644 platform/android/viewer/project.properties create mode 100644 platform/android/viewer/res/animator/info.xml create mode 100644 platform/android/viewer/res/drawable-hdpi/icon.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_annot.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_annotation.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_arrow_left.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_arrow_right.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_cancel.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_check.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_clipboard.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_dir.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_doc.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_highlight.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_link.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_list.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_magnifying_glass.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_more.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_pen.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_print.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_proof.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_reflow.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_select.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_share.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_strike.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_trash.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_underline.png create mode 100644 platform/android/viewer/res/drawable-ldpi/ic_updir.png create mode 100644 platform/android/viewer/res/drawable-ldpi/icon.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_annot.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_annotation.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_arrow_left.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_arrow_right.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_arrow_up.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_cancel.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_check.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_clipboard.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_dir.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_doc.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_highlight.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_link.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_list.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_magnifying_glass.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_more.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_pen.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_print.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_proof.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_reflow.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_select.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_sep.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_share.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_strike.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_trash.png create mode 100644 platform/android/viewer/res/drawable-mdpi/ic_underline.png create mode 100644 platform/android/viewer/res/drawable-mdpi/icon.png create mode 100644 platform/android/viewer/res/drawable-xhdpi/icon.png create mode 100644 platform/android/viewer/res/drawable/busy.xml create mode 100644 platform/android/viewer/res/drawable/button.xml create mode 100644 platform/android/viewer/res/drawable/darkdenim3.png create mode 100644 platform/android/viewer/res/drawable/page_num.xml create mode 100644 platform/android/viewer/res/drawable/search.xml create mode 100644 platform/android/viewer/res/drawable/seek_progress.xml create mode 100644 platform/android/viewer/res/drawable/seek_thumb.xml create mode 100644 platform/android/viewer/res/drawable/tiled_background.xml create mode 100644 platform/android/viewer/res/layout/buttons.xml create mode 100644 platform/android/viewer/res/layout/main.xml create mode 100644 platform/android/viewer/res/layout/outline_entry.xml create mode 100644 platform/android/viewer/res/layout/picker_entry.xml create mode 100644 platform/android/viewer/res/layout/print_dialog.xml create mode 100644 platform/android/viewer/res/layout/textentry.xml create mode 100644 platform/android/viewer/res/values-ar/strings.xml create mode 100644 platform/android/viewer/res/values-ca/strings.xml create mode 100644 platform/android/viewer/res/values-cs/strings.xml create mode 100644 platform/android/viewer/res/values-da/strings.xml create mode 100644 platform/android/viewer/res/values-de/strings.xml create mode 100644 platform/android/viewer/res/values-el/strings.xml create mode 100644 platform/android/viewer/res/values-es/strings.xml create mode 100644 platform/android/viewer/res/values-et/strings.xml create mode 100644 platform/android/viewer/res/values-fi/strings.xml create mode 100644 platform/android/viewer/res/values-fr/strings.xml create mode 100644 platform/android/viewer/res/values-hi/strings.xml create mode 100644 platform/android/viewer/res/values-hu/strings.xml create mode 100644 platform/android/viewer/res/values-in/strings.xml create mode 100644 platform/android/viewer/res/values-it/strings.xml create mode 100644 platform/android/viewer/res/values-iw/strings.xml create mode 100644 platform/android/viewer/res/values-ja/strings.xml create mode 100644 platform/android/viewer/res/values-ko/strings.xml create mode 100644 platform/android/viewer/res/values-lt/strings.xml create mode 100644 platform/android/viewer/res/values-ms/strings.xml create mode 100644 platform/android/viewer/res/values-nl/strings.xml create mode 100644 platform/android/viewer/res/values-no/strings.xml create mode 100644 platform/android/viewer/res/values-pl/strings.xml create mode 100644 platform/android/viewer/res/values-pt/strings.xml create mode 100644 platform/android/viewer/res/values-ru/strings.xml create mode 100644 platform/android/viewer/res/values-sk/strings.xml create mode 100644 platform/android/viewer/res/values-sv/strings.xml create mode 100644 platform/android/viewer/res/values-th/strings.xml create mode 100644 platform/android/viewer/res/values-tl/strings.xml create mode 100644 platform/android/viewer/res/values-tr/strings.xml create mode 100644 platform/android/viewer/res/values-zh-rTW/strings.xml create mode 100644 platform/android/viewer/res/values-zh/strings.xml create mode 100644 platform/android/viewer/res/values/colors.xml create mode 100644 platform/android/viewer/res/values/strings.xml create mode 100644 platform/android/viewer/res/values/styles.xml create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/Annotation.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/CancellableAsyncTask.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFActivity.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFItem.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/FilePicker.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfo.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoExternal.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoInternal.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoRemote.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoVisitor.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlert.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCore.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReaderView.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowView.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFView.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivity.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/OutlineItem.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/PageView.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/PrintDialogActivity.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/Separation.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/Stepper.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/TextChar.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/TextWord.java create mode 100644 platform/android/viewer/src/com/artifex/mupdfdemo/WidgetType.java delete mode 100644 platform/java/PageCanvas.java delete mode 100644 platform/java/TraceDevice.java delete mode 100644 platform/java/Viewer.java create mode 100644 platform/java/example/PageCanvas.java create mode 100644 platform/java/example/TraceDevice.java create mode 100644 platform/java/example/Viewer.java (limited to 'platform') diff --git a/platform/android/AndroidManifest.xml b/platform/android/AndroidManifest.xml deleted file mode 100644 index 29c20f84..00000000 --- a/platform/android/AndroidManifest.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/ClassStructure.txt b/platform/android/ClassStructure.txt deleted file mode 100644 index 39939674..00000000 --- a/platform/android/ClassStructure.txt +++ /dev/null @@ -1,187 +0,0 @@ -MuPDFActivity -~~~~~~~~~~~~~ - -MuPDFActivity is the main activity used when displaying and interacting with a -document. This class is responsible for creating the view hierarchy and the -menus. - - -Main view classes -~~~~~~~~~~~~~~~~~ - -ReaderView -~~~~~~~~~~ -MuPDF uses Android's standard Adapter/AdapterView paradigm, where a subclass of -BaseAdapter supplies multiple views that have their motion on screen -choreographed by a subclass of AdapterView. There are several standard -AdapterView subclasses, but none support zooming into a specific subview and -then panning within it, so MuPDF has its own AdapterView subclass, namely -ReaderView. The class is intended to be general purpose and usable within any -document-viewing application. During page viewing, ReaderView handles all touch -events, recognises gestures and positions the displayed document pages -accordingly. ReaderView needs to handle positioning slightly differently -depending on whether MuPDF is reflowing text or not, and so it has two slightly -different modes of operation. - -MuPDFReaderView -~~~~~~~~~~~~~~~ -MuPDFReaderView subclasses ReaderView, so as to provide some of the -page-positioning behaviour that is specific to MuPDF. It overrides some of the -gesture recognition methods of ReaderView, so that it can perform special -handling of (e.g.) tapping on the side of the screen for page progression, and -tapping on links or form fields. It also handles the disabling of scrolling -during text-selection and annotation-drawing, and it performs the setup -operations needed by the individual page views as each newly appears. - -MuPDFView -~~~~~~~~~ -Document viewing uses different View subclasses to display the individual pages -depending on whether reflowing text or displaying pages unaltered. MuPDFView is -the common interface to the two view subclasses. - -PageView -~~~~~~~~ -PageView is the main View class used for non-reflow display of a page. Like -ReaderView, it is intended to be, as much as is possible, independent of the -specifics of MuPDF and usable in general document display apps. It is a -subclass of ViewGroup because page displays are built from several layers. The -lowest layer is a rendering of the page at a resolution that matches the screen -exactly when maximally zoomed out so that the page fits the screen. As the user -zooms in, this layer maintains a visible appearance of the page, but one that -becomes more blurred as zooming in progresses. A second layer provides a higher -resolution rendering of just the area of the page that is visible on screen, -and at a resolution that matches the screen. As the user pans, this layer is -updated on a background thread, so parts of the blurred layer will temporarily -become visible, but only momentarily later to be replaced by the high-quality -rendering. There is one further layer that is used to draw transparent shapes -for highlighting and the like. - -MuPDFPageView -~~~~~~~~~~~~~ -MuPDFPageView is a subclass of PageView, which handles some of the specifics of -MuPDF's behaviour, such as taps on links and form fields, text selection, and -annotation drawing. It also handles its parent class's bitmap rendering calls. -This is the class used to display pages in non-reflow mode. It implements the -MuPDFView interface. - -MuPDFReflowView -~~~~~~~~~~~~~~~ -This is the class used to display pages in reflow mode. Like MuPDFPageView it -implements the MuPDFView interface. It is a subclass of WebView, and achieves -reflowing by loading an HTML version of the page, which the MuPDF core -constructs. - -MuPDFPageAdapter and MuPDFReflowAdapter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -As with any AdapterView subclass, ReaderView needs an Adapter subclass to -supply, on demand, the subviews for the pages currently displayed. These are -the two Adapter subclasses, supplying the subviews as MuPDFPageView and -MuPDFReflowView objects respectively. The former is a little more complex than -the latter, since it caches the sizes of the pages corresponding to the views -it supplies. It does so, so that page views, on their second and subsequent -appearances, can take on their correct size immediately. (The determining of -page size is not a completely trivial operation and is performed on a -background thread, as is all interaction with the core MuPDF library). - - -C library wrapper -~~~~~~~~~~~~~~~~~ - -MuPDFCore -~~~~~~~~~ -This class is the interface to the MuPDF C library. It is used to render bitmap -versions of the page for display in the view classes mentioned above. It also -provides for interaction with objects within the page, such as the individual -text objects and annotations. Many of the methods take too long an execution -time to be run on the UI thread, hence they need to be run in the background, -and because even the fast methods have to be synchronised with the slower -methods, (almost) all methods should be called in the background. There are a -few non synchronised ones that have special purposes. - - -Link handling -~~~~~~~~~~~~~ -There are three types of PDF links, each entailing different information and -requiring different handling. There are five classes involved in their -representation. - -LinkInfo is the base class representing any one of the three - -LinkInfoExternal, LinkInfoInternal and LinkInfoRemote are the three subclasses -representing the specific cases. - -LinkInfoVisitor is a class implementing a common Java paradigm which allows -case analysis on the three different types of link, executing different methods -for each. - -BitmapHolder -~~~~~~~~~~~~ -BitmapHolder is the solution to a problem in allocating the Bitmaps to which -rendering is performed by background tasks. Renderings for the purpose of -update have to be passed a Bitmap with the current page state. During frenetic -page flicking a large number of rendering tasks can be queued, each holding -reference to a Bitmap. Rather than pass the Bitmap directly, we pass a -BitmapHolder containing a reference to the Bitmap. When a page view transitions -off screen, the BitmapHolder's reference to the Bitmap can be nulled to release -it. - -SearchTask and SearchTaskResult -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -SearchTask encapsulates the process of searching for a text string within a -document. The class uses an AsyncTask internally to perform the search in the -background and reports the result by calling onTextFound. A SearchTaskResult -object is used to return the result of the search. - -SafeAnimatorInflator -~~~~~~~~~~~~~~~~~~~~ -This class is a simple wrapper around AnimatorInflator. AnimatorInflator -doesn't exist in some of the Android API levels MuPDF supports, and the wrapper -allows for a test of API-level before the point at which the class would be -loaded. - -MuPDFAlert and MuPDFAlertInternal -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This class represents the information issued by a javascript app.alert call. -MuPDFAlertInternal represents the same information, but with Java enums -replaced by ints, which are easier to return from JNI code. - -TextChar and TextWord -~~~~~~~~~~~~~~~~~~~~~ -TextChar is used when processing the individual characters of the page. Each -TextChar object contains the character and the rectangular area of the page at -which it appears. TextWord is used to gather TextChars into words. - -Annotation -~~~~~~~~~~ -This class represents the type and position on page of a PDF annotation. - -Other activities -~~~~~~~~~~~~~~~~ -The app has three activities other than document-viewing. - -ChoosePDFActivity -~~~~~~~~~~~~~~~~~ -ChoosePDFActivity allows the user to navigate local disc directories and view a -list of loadable files, from which one can be chosen. It derives off -ListActivity, and so displays the files in a standard ListView. ChoosePDFItem -represents the various types of list entry: up-one, directory or file. -ChoosePDFAdapter populates the list view. - -OutlineActivity -~~~~~~~~~~~~~~~ -OutlineActivity displays a PDF document's outline as a list of selectable -section titles. OutlineActivityData represents the current state of the -activity. OutlineItem represents the individual items, and OutlineAdapter -populates the list view. - -PrintDialogActivity -~~~~~~~~~~~~~~~~~~~ -This activity allows the user to print documents via Google Cloud Print. - - -Copied system classes -~~~~~~~~~~~~~~~~~~~~~ -AsyncTask has had improvements made to it since issuing at the lowest android -API level we support, and so we include the improved version as part of the -MuPDF app. We also include Deque and ArrayDeque, which are used my AsyncTask. - diff --git a/platform/android/Icons.txt b/platform/android/Icons.txt deleted file mode 100644 index 9d0082bf..00000000 --- a/platform/android/Icons.txt +++ /dev/null @@ -1,2 +0,0 @@ -The icons are from http://somerandomdude.com/work/iconic/ -They are covered by the CC-BY-SA license: http://creativecommons.org/licenses/by-sa/3.0/us/ diff --git a/platform/android/Makefile b/platform/android/Makefile deleted file mode 100644 index 68c8d5b3..00000000 --- a/platform/android/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -default: - $(MAKE) -C ../java -f Makejar - ndk-build - ant debug diff --git a/platform/android/ReadMe.txt b/platform/android/ReadMe.txt deleted file mode 100644 index ab0e4ef9..00000000 --- a/platform/android/ReadMe.txt +++ /dev/null @@ -1,188 +0,0 @@ -To build/debug android build. - -1) Download the android sdk, and install it. These instructions have been -written with r14 (the latest version at time of writing) of the SDK in mind; -other versions may give problems. On windows r14 unpacked as: - - C:\Program Files (x86)\Android\android-sdk - -on Macos an older version installed as: - - /Library/android-sdk-mac_x86 - -on Linux install it as: - - mkdir ~/android-sdk - cd ~/android-sdk - tar ~/Downloads/android-sdk_r20.0.3-linux.tgz - -Whatever directory it unpacks to, ensure that both the 'tools' and -'platform-tools' directories inside it have been added to your PATH. - -2) Download the android ndk, and unpack it. These instructions were written -with NDK r6b (the latest version at the time of writing) in mind, but the -build has now been tweaked to work with r10b. Other versions may give problems. - -It's important that you use the correct NDK for the target platform. If you're -targeting a 32-bit platform (such as "ARM EABI v7a" or "Intel x86 Arm") then -you MUST use the 32-bit target NDK. If you get UnsatisfiedLinkError when -opening a document in MuPDF, then you've tried to use the 64-bit target NDK -with a 32-bit target! - -On windows I unpacked it as: - - C:\android-ndk-r10b - -on Macos an older version unpacked as: - - /Library/android-ndk-r5 - -on Linux as: - - mkdir ~/android-ndk - cd ~/android-ndk - tar jxvf ~/Downloads/android-ndk32-r10b-linux-x86.tar.bz2 - -It is very important that you should unpack it to a directory with no -spaces in the name! (Don't be tempted to put it in C:\Program Files etc) - -Ensure that that directory is also added to your PATH. - -3) On windows, to use the ndk, you *must* be running under cygwin. This means -you need to install Cygwin 1.7 or greater now. - -[ In version r5 of the ndk, when running under cygwin, there were ] -[ bugs to do with the automatic conversion of dependencies from DOS ] -[ format paths to cygwin format paths. The 2 fixes can be found in: ] -[ ] -[ ] -[ ] -[ Use the latest version and there should not be a problem. ] - -4) If the SDK has not popped up a window already, bring up a shell, and run -'android' (or android.bat on cygwin/windows). You should now have a window -with a graphical gui for the sdk. From here you can install the different SDK -components for the different flavours of android. Download them all - -bandwidth and disk space are cheap, right? Make sure you get at least -the API level 11 as this is the current dependency for mupdf. - -5) In new versions of the GUI there is a 'Tools' menu from which you can -select 'Manage AVDs...'. In old versions, go to the Virtual Devices entry -on the right hand side. You need to create yourself an emulator image to -use. Click 'New...' on the right hand side and a window will appear. Fill -in the entries as follows: - - Name: FroyoEm - Target: Android 2.2 - API Level 8 - CPU/ABI: ARM (armeabi) (If this option exists) - SD card: Size: 1024MiB - Skin: Resolution: 480x756 (756 just fits my macbook screen, but 800 may - be 'more standard') - -Click 'Create AVD' (on old versions you may have to wait for a minute or -so while it is prepared. Now you can exit the GUI. - -6) You will need a copy of the JDK installed. See -. When this -installs, ensure that JAVA_HOME is set to point to the installation -directory. - -7) You will need a copy of Apache ANT installed. -See . Ensure that ANT_HOME is set to point to -the top level directory, and that ANT_HOME/bin is on the PATH. - -8) Now we are ready to build mupdf for Android. Check out a copy of MuPDF -(but you've done that already, cos you're reading this, right?). - -9) You will also need a copy of mupdf's thirdparty libraries. If you are -using git, make sure to do a git submodule update --init from the top of -the build tree. Older versions packaged this source code in a .zip-file -(see the source code link on http://mupdf.com/). Unpack the contents of -this into a 'thirdparty' directory created within the mupdf directory -(i.e. at the same level as fitz, pdf, android etc). - -10) Finally, you will need a copy of a 'generated' directory. This is not -currently available to download. - -The normal mupdf build process involves running some code on the host -(the machine on which you are compiling), rather than the target (the -machine/device on which you eventually want to run mupdf). This code -repacks various bits of information (fonts, CMAPs etc) into a more -compact and usable form. - -Unfortunately, the android SDK does not provide a compiler for the host -machine, so we cannot run this step automatically as part of the android -build. You will need to generate it by running a different build, such -as the windows or linux native builds. - -We do not make a snapshot of the generated directory available to -download as the contents of this directory change frequently, and we'd -have to keep multiple versions on the website. We assume that anyone -capable of building for android is capable of doing a normal hosted -build. - -On windows (where you are using cygwin), or on linux/macos, this can be -as simple as running 'make generate' in the top level directory. - -11) Change into mupdf's android directory. Copy the -android/local.properties.sample file to be android/local.properties and -change the sdk path there as appropriate. This should be the only bit of -localisation you need to do. - -12) Change into the android directory (note, the android directory, NOT -the android/jni directory!), and execute (in a Cygwin window on Windows!): - - ndk-build - -This should build the native code portion. - -If this dies with an error in thirdparty/jbig2/os_types.h load this -file into an editor, and change line 43 from: - - #else - -to - - #elif !defined(HAVE_STDINT_H) - -and this should solve the problem. - -13) Then execute: - - ant debug - -or on windows under cygwin: - - ant.bat debug - -This should build the java wrapper. - -14) Now start the emulator by executing: - - emulator -avd FroyoEm - -This will take a while to full start up (be patient). - -15) We now need to give the demo file something to chew on, so let's copy -a file into the SD card image of the emulator (this should only need to be -done once). With the emulator running type: - - adb push ../../MyTests/pdf_reference17.pdf /mnt/sdcard/Download/test.pdf - -(where obviously ../../MyTests/pdf_reference17.pdf is altered for your -machine, and under Windows, should start c:/ even if invoked from cygwin) -(adb lives in /platform-tools if it's not on your path). - -16) With the emulator running (see step 14), execute - - ant debug install - -('ant.bat debug install' on Windows) and that will copy MuPDF into the -emulator where you can run it from the launchpad screen. - -17) To see debug messages from the emulator (including stdout/stderr from -our app), execute: - - adb logcat - -Good luck! diff --git a/platform/android/build.gradle b/platform/android/build.gradle deleted file mode 100644 index 7c1a1ed5..00000000 --- a/platform/android/build.gradle +++ /dev/null @@ -1,89 +0,0 @@ -import org.apache.tools.ant.taskdefs.condition.Os - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' - } -} - -allprojects { - repositories { - jcenter() - } -} - -apply plugin: 'com.android.application' - -dependencies { - compile fileTree(dir: 'libs', include: '*.so') -} - -android { - - compileSdkVersion 17 - buildToolsVersion '21.1.2' - - /* this stops gradle from making it's own Android.mk file */ - sourceSets.main.jni.srcDirs = [] - - sourceSets { - main { - manifest { - srcFile 'AndroidManifest.xml' - } - java { - srcDir 'src' - exclude 'com/artifex/mupdf/fitz/AndroidDrawDevice.java' - } - res { - srcDir 'res' - } - assets { - srcDir 'assets' - } - resources { - srcDir 'src' - } - jniLibs { - srcDir 'libs' - } - } - } - - /* This is important, it will run lint checks but won't abort build */ - lintOptions { - abortOnError false - } - -} - -/* This defines the path to Android's ndk-build. */ -def ndkBuildPath = plugins.getPlugin('com.android.application').sdkHandler.getNdkFolder().absolutePath + File.separator + 'ndk-build' -if (Os.isFamily(Os.FAMILY_WINDOWS)) { - ndkBuildPath +='.cmd' -} - -/* This task builds the native part */ -task buildNative(type: Exec,description: 'Compile JNI source via NDK') { - println('executing buildNative') - commandLine ndkBuildPath, '-C', file('.').absolutePath, ' SUPPORT_GPROOF=1' - //'NDK_PROJECT_PATH=build','APP_BUILD_SCRIPT=src/main/jni/Android.mk'//force using appropriate Makefile -} - -/* This task cleans the native part */ -task cleanNative(type: Exec, description: 'Clean JNI object files') { - println('executing cleanNative') - commandLine ndkBuildPath, '-C', file('.').absolutePath, 'clean' -} - -/* cleaning should also include cleaning native */ -clean.dependsOn 'cleanNative' - -/* building should include native */ -tasks.withType(JavaCompile){ - compileTask -> compileTask.dependsOn buildNative -} - diff --git a/platform/android/build.sh b/platform/android/build.sh deleted file mode 100644 index 36ad883f..00000000 --- a/platform/android/build.sh +++ /dev/null @@ -1 +0,0 @@ -ndk-build && ant.bat install diff --git a/platform/android/build.xml b/platform/android/build.xml deleted file mode 100644 index 1d6e5f10..00000000 --- a/platform/android/build.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - ORIGINAL jars.path : ${toString:project.all.jars.path} - - - - - - - - - HACKED jars.path : ${toString:project.all.jars.path} - - - - - - - - - - - - - - - diff --git a/platform/android/jni/Android.mk b/platform/android/jni/Android.mk deleted file mode 100644 index f9be3dde..00000000 --- a/platform/android/jni/Android.mk +++ /dev/null @@ -1,39 +0,0 @@ -LOCAL_PATH := $(call my-dir) -TOP_LOCAL_PATH := $(LOCAL_PATH) - -MUPDF_ROOT := ../.. - -ifdef NDK_PROFILER -include android-ndk-profiler.mk -endif - -include $(TOP_LOCAL_PATH)/Core.mk -include $(TOP_LOCAL_PATH)/ThirdParty.mk - -include $(CLEAR_VARS) - -LOCAL_C_INCLUDES := \ - jni/andprof \ - $(MUPDF_ROOT)/include \ - $(MUPDF_ROOT)/source/fitz \ - $(MUPDF_ROOT)/source/pdf \ - $(MUPDF_ROOT)/platform/java -LOCAL_CFLAGS := -DHAVE_ANDROID -LOCAL_MODULE := mupdf_java -LOCAL_SRC_FILES := mupdf.c \ - $(MUPDF_ROOT)/java/mupdf_native.c -LOCAL_STATIC_LIBRARIES := mupdfcore mupdfthirdparty -ifdef NDK_PROFILER -LOCAL_CFLAGS += -pg -DNDK_PROFILER -LOCAL_STATIC_LIBRARIES += andprof -endif -ifdef SUPPORT_GPROOF -LOCAL_CFLAGS += -DSUPPORT_GPROOF -endif - -LOCAL_LDLIBS := -lm -llog -ljnigraphics -ifdef SSL_BUILD -LOCAL_LDLIBS += -L$(MUPDF_ROOT)/thirdparty/openssl/android -lcrypto -lssl -endif - -include $(BUILD_SHARED_LIBRARY) diff --git a/platform/android/jni/Application.mk b/platform/android/jni/Application.mk deleted file mode 100644 index 4cedaf8d..00000000 --- a/platform/android/jni/Application.mk +++ /dev/null @@ -1,49 +0,0 @@ -# When we build for google play, we build 4 different apk's, each with -# a different version, by uncommenting one of the pairs of lines below. -# Suppose our base version is X: - -# Version X: armeabi -#APP_PLATFORM=android-8 -#APP_ABI := armeabi - -# Version X+1: armeabi-v7a (Much faster due to the availability of hardware -# FP, but cannot be run in the emulator). -APP_PLATFORM=android-8 -APP_ABI := armeabi-v7a - -# Version X+2: x86 (Requires android-9, so a change needs to be made in -# AndroidManifest.xml too) -#APP_PLATFORM=android-9 -#APP_ABI := x86 - -# Version X+3: mips (Requires android-9, so a change needs to be made in -# AndroidManifest.xml too) -#APP_PLATFORM=android-9 -#APP_ABI := mips - -ifdef NDK_PROFILER -# The profiler doesn't seem to receive ticks when run on release code. -# Accordingly, we need to build as debug - but this turns optimisations -# off, which is less than ideal. -APP_OPTIM := debug -APP_CFLAGS := -O2 -else -ifdef DEBUG -APP_OPTIM := debug -APP_CFLAGS := -DDEBUG -else -APP_OPTIM := release -endif -endif -ifdef V8_BUILD -APP_STL := stlport_static -endif -ifdef MEMENTO -APP_CFLAGS += -DMEMENTO -DMEMENTO_LEAKONLY -endif - -# If the ndk is r8b then workaround bug by uncommenting the following line -#NDK_TOOLCHAIN_VERSION=4.4.3 - -# If the ndk is newer than r8c, try using clang. -#NDK_TOOLCHAIN_VERSION=clang3.1 diff --git a/platform/android/jni/Core.mk b/platform/android/jni/Core.mk deleted file mode 100644 index 6dcb8f1b..00000000 --- a/platform/android/jni/Core.mk +++ /dev/null @@ -1,79 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -ifdef SUPPORT_GPROOF -include $(CLEAR_VARS) -LOCAL_MODULE := gsso -LOCAL_SRC_FILES := libgs.so -include $(PREBUILT_SHARED_LIBRARY) -endif - -include $(CLEAR_VARS) - -MY_ROOT := ../.. - -LOCAL_CFLAGS += -Wall - -ifeq ($(TARGET_ARCH),arm) -LOCAL_CFLAGS += -DARCH_ARM -DARCH_THUMB -DARCH_ARM_CAN_LOAD_UNALIGNED -ifdef NDK_PROFILER -LOCAL_CFLAGS += -pg -DNDK_PROFILER -endif -endif -ifdef SUPPORT_GPROOF -LOCAL_CFLAGS += -DSUPPORT_GPROOF -endif -LOCAL_CFLAGS += -DAA_BITS=8 -ifdef MEMENTO -LOCAL_CFLAGS += -DMEMENTO -DMEMENTO_LEAKONLY -endif -ifdef SSL_BUILD -LOCAL_CFLAGS += -DHAVE_OPENSSL -endif - -LOCAL_C_INCLUDES := \ - ../../thirdparty/harfbuzz/src \ - ../../thirdparty/jbig2dec \ - ../../thirdparty/openjpeg/libopenjpeg \ - ../../thirdparty/jpeg \ - ../../thirdparty/mujs \ - ../../thirdparty/zlib \ - ../../thirdparty/freetype/include \ - ../../source/fitz \ - ../../source/pdf \ - ../../source/xps \ - ../../source/cbz \ - ../../source/img \ - ../../source/tiff \ - ../../scripts/freetype \ - ../../scripts/jpeg \ - ../../scripts/openjpeg \ - ../../generated \ - ../../resources \ - ../../include \ - ../.. -ifdef V8_BUILD -LOCAL_C_INCLUDES += ../../thirdparty/$(V8)/include -endif -ifdef SSL_BUILD -LOCAL_C_INCLUDES += ../../thirdparty/openssl/include -endif - -LOCAL_MODULE := mupdfcore -LOCAL_SRC_FILES := \ - $(wildcard $(MY_ROOT)/source/fitz/*.c) \ - $(wildcard $(MY_ROOT)/source/pdf/*.c) \ - $(wildcard $(MY_ROOT)/source/xps/*.c) \ - $(wildcard $(MY_ROOT)/source/cbz/*.c) \ - $(wildcard $(MY_ROOT)/source/gprf/*.c) \ - $(wildcard $(MY_ROOT)/source/html/*.c) -LOCAL_SRC_FILES += \ - $(MY_ROOT)/source/pdf/js/pdf-js.c \ - -ifdef SUPPORT_GPROOF -LOCAL_SHARED_LIBRARIES := gsso -endif -LOCAL_LDLIBS := -lm -llog -ljnigraphics - -LOCAL_SRC_FILES := $(addprefix ../, $(LOCAL_SRC_FILES)) - -include $(BUILD_STATIC_LIBRARY) diff --git a/platform/android/jni/ThirdParty.mk b/platform/android/jni/ThirdParty.mk deleted file mode 100644 index d7140dfa..00000000 --- a/platform/android/jni/ThirdParty.mk +++ /dev/null @@ -1,177 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -MY_ROOT := ../.. - -LOCAL_C_INCLUDES := \ - ../../include/ \ - ../../thirdparty/harfbuzz/src \ - ../../thirdparty/jbig2dec \ - ../../thirdparty/openjpeg/libopenjpeg \ - ../../thirdparty/jpeg \ - ../../thirdparty/mujs \ - ../../thirdparty/zlib \ - ../../thirdparty/freetype/include \ - ../../scripts/freetype \ - ../../scripts/jpeg \ - ../../scripts/openjpeg - -LOCAL_CFLAGS := \ - -DFT2_BUILD_LIBRARY -DDARWIN_NO_CARBON -DHAVE_STDINT_H \ - -DOPJ_HAVE_STDINT_H \ - '-DFT_CONFIG_MODULES_H="slimftmodules.h"' \ - '-DFT_CONFIG_OPTIONS_H="slimftoptions.h"' \ - -Dhb_malloc_impl=hb_malloc -Dhb_calloc_impl=hb_calloc \ - -Dhb_realloc_impl=hb_realloc -Dhb_free_impl=hb_free \ - -DHAVE_OT -DHAVE_UCDN -DHB_NO_MT -ifdef NDK_PROFILER -LOCAL_CFLAGS += -pg -DNDK_PROFILER -O2 -endif -ifdef MEMENTO -LOCAL_CFLAGS += -DMEMENTO -DMEMENTO_LEAKONLY -endif - -LOCAL_CPP_EXTENSION := .cc - -LOCAL_MODULE := mupdfthirdparty -LOCAL_SRC_FILES := \ - $(MY_ROOT)/thirdparty/mujs/one.c \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-blob.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-buffer.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-buffer-serialize.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-common.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-face.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-fallback-shape.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-font.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ft.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-font.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-layout.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-map.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-arabic.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-default.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-hangul.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-hebrew.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-indic-table.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-indic.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-myanmar.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-thai.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-tibetan.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-use-table.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-use.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-fallback.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-normalize.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-tag.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-set.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-shape-plan.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-shape.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-shaper.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ucdn.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-unicode.cc \ - $(MY_ROOT)/thirdparty/harfbuzz/src/hb-warning.cc \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_arith.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_arith_iaid.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_arith_int.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_generic.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_halftone.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_huffman.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_image.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_metadata.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_mmr.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_page.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_refinement.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_segment.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_symbol_dict.c \ - $(MY_ROOT)/thirdparty/jbig2dec/jbig2_text.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/bio.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/cidx_manager.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/cio.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/dwt.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/event.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/function_list.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/image.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/invert.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/j2k.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/jp2.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/mct.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/mqc.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/openjpeg.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/opj_clock.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/phix_manager.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/pi.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/ppix_manager.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/raw.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/t1.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/t1_generate_luts.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/t2.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/tcd.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/tgt.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/thix_manager.c \ - $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/tpix_manager.c \ - $(MY_ROOT)/thirdparty/jpeg/jaricom.c \ - $(MY_ROOT)/thirdparty/jpeg/jcomapi.c \ - $(MY_ROOT)/thirdparty/jpeg/jdapimin.c \ - $(MY_ROOT)/thirdparty/jpeg/jdapistd.c \ - $(MY_ROOT)/thirdparty/jpeg/jdarith.c \ - $(MY_ROOT)/thirdparty/jpeg/jdatadst.c \ - $(MY_ROOT)/thirdparty/jpeg/jdatasrc.c \ - $(MY_ROOT)/thirdparty/jpeg/jdcoefct.c \ - $(MY_ROOT)/thirdparty/jpeg/jdcolor.c \ - $(MY_ROOT)/thirdparty/jpeg/jddctmgr.c \ - $(MY_ROOT)/thirdparty/jpeg/jdhuff.c \ - $(MY_ROOT)/thirdparty/jpeg/jdinput.c \ - $(MY_ROOT)/thirdparty/jpeg/jdmainct.c \ - $(MY_ROOT)/thirdparty/jpeg/jdmarker.c \ - $(MY_ROOT)/thirdparty/jpeg/jdmaster.c \ - $(MY_ROOT)/thirdparty/jpeg/jdmerge.c \ - $(MY_ROOT)/thirdparty/jpeg/jdpostct.c \ - $(MY_ROOT)/thirdparty/jpeg/jdsample.c \ - $(MY_ROOT)/thirdparty/jpeg/jdtrans.c \ - $(MY_ROOT)/thirdparty/jpeg/jerror.c \ - $(MY_ROOT)/thirdparty/jpeg/jfdctflt.c \ - $(MY_ROOT)/thirdparty/jpeg/jfdctfst.c \ - $(MY_ROOT)/thirdparty/jpeg/jfdctint.c \ - $(MY_ROOT)/thirdparty/jpeg/jidctflt.c \ - $(MY_ROOT)/thirdparty/jpeg/jidctfst.c \ - $(MY_ROOT)/thirdparty/jpeg/jidctint.c \ - $(MY_ROOT)/thirdparty/jpeg/jmemmgr.c \ - $(MY_ROOT)/thirdparty/jpeg/jquant1.c \ - $(MY_ROOT)/thirdparty/jpeg/jquant2.c \ - $(MY_ROOT)/thirdparty/jpeg/jutils.c \ - $(MY_ROOT)/thirdparty/zlib/adler32.c \ - $(MY_ROOT)/thirdparty/zlib/compress.c \ - $(MY_ROOT)/thirdparty/zlib/crc32.c \ - $(MY_ROOT)/thirdparty/zlib/deflate.c \ - $(MY_ROOT)/thirdparty/zlib/inffast.c \ - $(MY_ROOT)/thirdparty/zlib/inflate.c \ - $(MY_ROOT)/thirdparty/zlib/inftrees.c \ - $(MY_ROOT)/thirdparty/zlib/trees.c \ - $(MY_ROOT)/thirdparty/zlib/uncompr.c \ - $(MY_ROOT)/thirdparty/zlib/zutil.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftbase.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftbbox.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftbitmap.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftfntfmt.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftgasp.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftglyph.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftinit.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftstroke.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftsynth.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/ftsystem.c \ - $(MY_ROOT)/thirdparty/freetype/src/base/fttype1.c \ - $(MY_ROOT)/thirdparty/freetype/src/cff/cff.c \ - $(MY_ROOT)/thirdparty/freetype/src/cid/type1cid.c \ - $(MY_ROOT)/thirdparty/freetype/src/psaux/psaux.c \ - $(MY_ROOT)/thirdparty/freetype/src/pshinter/pshinter.c \ - $(MY_ROOT)/thirdparty/freetype/src/psnames/psnames.c \ - $(MY_ROOT)/thirdparty/freetype/src/raster/raster.c \ - $(MY_ROOT)/thirdparty/freetype/src/smooth/smooth.c \ - $(MY_ROOT)/thirdparty/freetype/src/sfnt/sfnt.c \ - $(MY_ROOT)/thirdparty/freetype/src/truetype/truetype.c \ - $(MY_ROOT)/thirdparty/freetype/src/type1/type1.c - -LOCAL_SRC_FILES := $(addprefix ../, $(LOCAL_SRC_FILES)) - -include $(BUILD_STATIC_LIBRARY) diff --git a/platform/android/jni/mupdf.c b/platform/android/jni/mupdf.c deleted file mode 100644 index bc23b9ca..00000000 --- a/platform/android/jni/mupdf.c +++ /dev/null @@ -1,2885 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifdef NDK_PROFILER -#include "prof.h" -#endif - -#include "mupdf/fitz.h" -#include "mupdf/pdf.h" - -#define JNI_FN(A) Java_com_artifex_mupdfdemo_ ## A -#define PACKAGENAME "com/artifex/mupdfdemo" - -#define LOG_TAG "libmupdf" -#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) -#define LOGT(...) __android_log_print(ANDROID_LOG_INFO,"alert",__VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) - -/* Enable to log rendering times (render each frame 100 times and time) */ -#undef TIME_DISPLAY_LIST - -#define MAX_SEARCH_HITS (500) -#define NUM_CACHE (3) -#define STRIKE_HEIGHT (0.375f) -#define UNDERLINE_HEIGHT (0.075f) -#define LINE_THICKNESS (0.07f) -#define INK_THICKNESS (4.0f) -#define SMALL_FLOAT (0.00001) -#define PROOF_RESOLUTION (300) - -enum -{ - NONE, - TEXT, - LISTBOX, - COMBOBOX, - SIGNATURE -}; - -typedef struct rect_node_s rect_node; - -struct rect_node_s -{ - fz_rect rect; - rect_node *next; -}; - -typedef struct -{ - int number; - int width; - int height; - fz_rect media_box; - fz_page *page; - rect_node *changed_rects; - rect_node *hq_changed_rects; - fz_display_list *page_list; - fz_display_list *annot_list; -} page_cache; - -typedef struct globals_s globals; - -struct globals_s -{ - fz_colorspace *colorspace; - fz_document *doc; - int resolution; - fz_context *ctx; - fz_rect *hit_bbox; - int current; - char *current_path; - - page_cache pages[NUM_CACHE]; - - int alerts_initialised; - // fin_lock and fin_lock2 are used during shutdown. The two waiting tasks - // show_alert and waitForAlertInternal respectively take these locks while - // waiting. During shutdown, the conditions are signaled and then the fin_locks - // are taken momentarily to ensure the blocked threads leave the controlled - // area of code before the mutexes and condition variables are destroyed. - pthread_mutex_t fin_lock; - pthread_mutex_t fin_lock2; - // alert_lock is the main lock guarding the variables directly below. - pthread_mutex_t alert_lock; - // Flag indicating if the alert system is active. When not active, both - // show_alert and waitForAlertInternal return immediately. - int alerts_active; - // Pointer to the alert struct passed in by show_alert, and valid while - // show_alert is blocked. - pdf_alert_event *current_alert; - // Flag and condition varibles to signal a request is present and a reply - // is present, respectively. The condition variables alone are not sufficient - // because of the pthreads permit spurious signals. - int alert_request; - int alert_reply; - pthread_cond_t alert_request_cond; - pthread_cond_t alert_reply_cond; - - // For the buffer reading mode, we need to implement stream reading, which - // needs access to the following. - JNIEnv *env; - jclass thiz; -}; - -static jfieldID global_fid; -static jfieldID buffer_fid; - -// Do our best to avoid casting warnings. -#define CAST(type, var) (type)pointer_cast(var) - -static inline void *pointer_cast(jlong l) -{ - return (void *)(intptr_t)l; -} - -static inline jlong jlong_cast(void *p) -{ - return (jlong)(intptr_t)p; -} - -static void drop_changed_rects(fz_context *ctx, rect_node **nodePtr) -{ - rect_node *node = *nodePtr; - while (node) - { - rect_node *tnode = node; - node = node->next; - fz_free(ctx, tnode); - } - - *nodePtr = NULL; -} - -static void drop_page_cache(globals *glo, page_cache *pc) -{ - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - - LOGI("Drop page %d", pc->number); - fz_drop_display_list(ctx, pc->page_list); - pc->page_list = NULL; - fz_drop_display_list(ctx, pc->annot_list); - pc->annot_list = NULL; - fz_drop_page(ctx, pc->page); - pc->page = NULL; - drop_changed_rects(ctx, &pc->changed_rects); - drop_changed_rects(ctx, &pc->hq_changed_rects); -} - -static void dump_annotation_display_lists(globals *glo) -{ - fz_context *ctx = glo->ctx; - int i; - - for (i = 0; i < NUM_CACHE; i++) { - fz_drop_display_list(ctx, glo->pages[i].annot_list); - glo->pages[i].annot_list = NULL; - } -} - -static void show_alert(globals *glo, pdf_alert_event *alert) -{ - pthread_mutex_lock(&glo->fin_lock2); - pthread_mutex_lock(&glo->alert_lock); - - LOGT("Enter show_alert: %s", alert->title); - alert->button_pressed = 0; - - if (glo->alerts_active) - { - glo->current_alert = alert; - glo->alert_request = 1; - pthread_cond_signal(&glo->alert_request_cond); - - while (glo->alerts_active && !glo->alert_reply) - pthread_cond_wait(&glo->alert_reply_cond, &glo->alert_lock); - glo->alert_reply = 0; - glo->current_alert = NULL; - } - - LOGT("Exit show_alert"); - - pthread_mutex_unlock(&glo->alert_lock); - pthread_mutex_unlock(&glo->fin_lock2); -} - -static void event_cb(fz_context *ctx, pdf_document *doc, pdf_doc_event *event, void *data) -{ - globals *glo = (globals *)data; - - switch (event->type) - { - case PDF_DOCUMENT_EVENT_ALERT: - show_alert(glo, pdf_access_alert_event(ctx, event)); - break; - } -} - -static void alerts_init(globals *glo) -{ - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - - if (!idoc || glo->alerts_initialised) - return; - - if (idoc) - pdf_enable_js(ctx, idoc); - - glo->alerts_active = 0; - glo->alert_request = 0; - glo->alert_reply = 0; - pthread_mutex_init(&glo->fin_lock, NULL); - pthread_mutex_init(&glo->fin_lock2, NULL); - pthread_mutex_init(&glo->alert_lock, NULL); - pthread_cond_init(&glo->alert_request_cond, NULL); - pthread_cond_init(&glo->alert_reply_cond, NULL); - - pdf_set_doc_event_callback(ctx, idoc, event_cb, glo); - LOGT("alert_init"); - glo->alerts_initialised = 1; -} - -static void alerts_fin(globals *glo) -{ - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - if (!glo->alerts_initialised) - return; - - LOGT("Enter alerts_fin"); - if (idoc) - pdf_set_doc_event_callback(ctx, idoc, NULL, NULL); - - // Set alerts_active false and wake up show_alert and waitForAlertInternal, - pthread_mutex_lock(&glo->alert_lock); - glo->current_alert = NULL; - glo->alerts_active = 0; - pthread_cond_signal(&glo->alert_request_cond); - pthread_cond_signal(&glo->alert_reply_cond); - pthread_mutex_unlock(&glo->alert_lock); - - // Wait for the fin_locks. - pthread_mutex_lock(&glo->fin_lock); - pthread_mutex_unlock(&glo->fin_lock); - pthread_mutex_lock(&glo->fin_lock2); - pthread_mutex_unlock(&glo->fin_lock2); - - pthread_cond_destroy(&glo->alert_reply_cond); - pthread_cond_destroy(&glo->alert_request_cond); - pthread_mutex_destroy(&glo->alert_lock); - pthread_mutex_destroy(&glo->fin_lock2); - pthread_mutex_destroy(&glo->fin_lock); - LOGT("Exit alerts_fin"); - glo->alerts_initialised = 0; -} - -// Should only be called from the single background AsyncTask thread -static globals *get_globals(JNIEnv *env, jobject thiz) -{ - globals *glo = CAST(globals *, (*env)->GetLongField(env, thiz, global_fid)); - if (glo != NULL) - { - glo->env = env; - glo->thiz = thiz; - } - return glo; -} - -// May be called from any thread, provided the values of glo->env and glo->thiz -// are not used. -static globals *get_globals_any_thread(JNIEnv *env, jobject thiz) -{ - return (globals *)(intptr_t)((*env)->GetLongField(env, thiz, global_fid)); -} - -JNIEXPORT jlong JNICALL -JNI_FN(MuPDFCore_openFile)(JNIEnv * env, jobject thiz, jstring jfilename) -{ - const char *filename; - globals *glo; - fz_context *ctx; - jclass clazz; - -#ifdef NDK_PROFILER - monstartup("libmupdf_java.so"); -#endif - - clazz = (*env)->GetObjectClass(env, thiz); - global_fid = (*env)->GetFieldID(env, clazz, "globals", "J"); - - glo = calloc(1, sizeof(*glo)); - if (glo == NULL) - return 0; - glo->resolution = 160; - glo->alerts_initialised = 0; - -#ifdef DEBUG - /* Try and send stdout/stderr to file in debug builds. This - * path may not work on all platforms, but it works on the - * LG G3, and it's no worse than not redirecting it anywhere - * on anything else. */ - freopen("/storage/emulated/0/Download/stdout.txt", "a", stdout); - freopen("/storage/emulated/0/Download/stderr.txt", "a", stderr); -#endif - - filename = (*env)->GetStringUTFChars(env, jfilename, NULL); - if (filename == NULL) - { - LOGE("Failed to get filename"); - free(glo); - return 0; - } - - /* 128 MB store for low memory devices. Tweak as necessary. */ - glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20); - if (!ctx) - { - LOGE("Failed to initialise context"); - (*env)->ReleaseStringUTFChars(env, jfilename, filename); - free(glo); - return 0; - } - - fz_register_document_handlers(ctx); - - glo->doc = NULL; - fz_try(ctx) - { - glo->colorspace = fz_device_rgb(ctx); - - LOGI("Opening document..."); - fz_try(ctx) - { - glo->current_path = fz_strdup(ctx, (char *)filename); - glo->doc = fz_open_document(ctx, (char *)filename); - alerts_init(glo); - } - fz_catch(ctx) - { - fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open document: '%s'", filename); - } - LOGI("Done!"); - } - fz_catch(ctx) - { - LOGE("Failed: %s", ctx->error->message); - fz_drop_document(ctx, glo->doc); - glo->doc = NULL; - fz_drop_context(ctx); - glo->ctx = NULL; - free(glo); - glo = NULL; - } - - (*env)->ReleaseStringUTFChars(env, jfilename, filename); - - return jlong_cast(glo); -} - -typedef struct buffer_state_s -{ - globals *globals; - char buffer[4096]; -} -buffer_state; - -static int bufferStreamNext(fz_context *ctx, fz_stream *stream, int max) -{ - buffer_state *bs = (buffer_state *)stream->state; - globals *glo = bs->globals; - JNIEnv *env = glo->env; - jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid)); - int arrayLength = (*env)->GetArrayLength(env, array); - int len = sizeof(bs->buffer); - - if (stream->pos > arrayLength) - stream->pos = arrayLength; - if (stream->pos < 0) - stream->pos = 0; - if (len + stream->pos > arrayLength) - len = arrayLength - stream->pos; - - (*env)->GetByteArrayRegion(env, array, stream->pos, len, bs->buffer); - (*env)->DeleteLocalRef(env, array); - - stream->rp = bs->buffer; - stream->wp = stream->rp + len; - stream->pos += len; - if (len == 0) - return EOF; - return *stream->rp++; -} - -static void bufferStreamClose(fz_context *ctx, void *state) -{ - fz_free(ctx, state); -} - -static void bufferStreamSeek(fz_context *ctx, fz_stream *stream, int offset, int whence) -{ - buffer_state *bs = (buffer_state *)stream->state; - globals *glo = bs->globals; - JNIEnv *env = glo->env; - jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid)); - int arrayLength = (*env)->GetArrayLength(env, array); - - (*env)->DeleteLocalRef(env, array); - - if (whence == 0) /* SEEK_SET */ - stream->pos = offset; - else if (whence == 1) /* SEEK_CUR */ - stream->pos += offset; - else if (whence == 2) /* SEEK_END */ - stream->pos = arrayLength + offset; - - if (stream->pos > arrayLength) - stream->pos = arrayLength; - if (stream->pos < 0) - stream->pos = 0; - - stream->wp = stream->rp; -} - -JNIEXPORT jlong JNICALL -JNI_FN(MuPDFCore_openBuffer)(JNIEnv * env, jobject thiz, jstring jmagic) -{ - globals *glo; - fz_context *ctx; - jclass clazz; - fz_stream *stream = NULL; - buffer_state *bs; - const char *magic; - -#ifdef NDK_PROFILER - monstartup("libmupdf_java.so"); -#endif - - clazz = (*env)->GetObjectClass(env, thiz); - global_fid = (*env)->GetFieldID(env, clazz, "globals", "J"); - - glo = calloc(1, sizeof(*glo)); - if (glo == NULL) - return 0; - glo->resolution = 160; - glo->alerts_initialised = 0; - glo->env = env; - glo->thiz = thiz; - buffer_fid = (*env)->GetFieldID(env, clazz, "fileBuffer", "[B"); - - magic = (*env)->GetStringUTFChars(env, jmagic, NULL); - if (magic == NULL) - { - LOGE("Failed to get magic"); - free(glo); - return 0; - } - - /* 128 MB store for low memory devices. Tweak as necessary. */ - glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20); - if (!ctx) - { - LOGE("Failed to initialise context"); - (*env)->ReleaseStringUTFChars(env, jmagic, magic); - free(glo); - return 0; - } - - fz_register_document_handlers(ctx); - fz_var(stream); - - glo->doc = NULL; - fz_try(ctx) - { - bs = fz_malloc_struct(ctx, buffer_state); - bs->globals = glo; - stream = fz_new_stream(ctx, bs, bufferStreamNext, bufferStreamClose); - stream->seek = bufferStreamSeek; - - glo->colorspace = fz_device_rgb(ctx); - - LOGI("Opening document..."); - fz_try(ctx) - { - glo->current_path = NULL; - glo->doc = fz_open_document_with_stream(ctx, magic, stream); - alerts_init(glo); - } - fz_catch(ctx) - { - fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open memory document"); - } - LOGI("Done!"); - } - fz_always(ctx) - { - fz_drop_stream(ctx, stream); - } - fz_catch(ctx) - { - LOGE("Failed: %s", ctx->error->message); - fz_drop_document(ctx, glo->doc); - glo->doc = NULL; - fz_drop_context(ctx); - glo->ctx = NULL; - free(glo); - glo = NULL; - } - - (*env)->ReleaseStringUTFChars(env, jmagic, magic); - - return jlong_cast(glo); -} - -JNIEXPORT int JNICALL -JNI_FN(MuPDFCore_countPagesInternal)(JNIEnv *env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - int count = 0; - - fz_try(ctx) - { - count = fz_count_pages(ctx, glo->doc); - } - fz_catch(ctx) - { - LOGE("exception while counting pages: %s", ctx->error->message); - } - return count; -} - -JNIEXPORT jstring JNICALL -JNI_FN(MuPDFCore_fileFormatInternal)(JNIEnv * env, jobject thiz) -{ - char info[64]; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - - fz_lookup_metadata(ctx, glo->doc, FZ_META_FORMAT, info, sizeof(info)); - - return (*env)->NewStringUTF(env, info); -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_isUnencryptedPDFInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals_any_thread(env, thiz); - if (glo == NULL) - return JNI_FALSE; - - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - if (idoc == NULL) - return JNI_FALSE; // Not a PDF - - int cryptVer = pdf_crypt_version(ctx, idoc); - return (cryptVer == 0) ? JNI_TRUE : JNI_FALSE; -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_gotoPageInternal)(JNIEnv *env, jobject thiz, int page) -{ - int i; - int furthest; - int furthest_dist = -1; - float zoom; - fz_matrix ctm; - fz_irect bbox; - page_cache *pc; - globals *glo = get_globals(env, thiz); - if (glo == NULL) - return; - fz_context *ctx = glo->ctx; - - for (i = 0; i < NUM_CACHE; i++) - { - if (glo->pages[i].page != NULL && glo->pages[i].number == page) - { - /* The page is already cached */ - glo->current = i; - return; - } - - if (glo->pages[i].page == NULL) - { - /* cache record unused, and so a good one to use */ - furthest = i; - furthest_dist = INT_MAX; - } - else - { - int dist = abs(glo->pages[i].number - page); - - /* Further away - less likely to be needed again */ - if (dist > furthest_dist) - { - furthest_dist = dist; - furthest = i; - } - } - } - - glo->current = furthest; - pc = &glo->pages[glo->current]; - - drop_page_cache(glo, pc); - - /* In the event of an error, ensure we give a non-empty page */ - pc->width = 100; - pc->height = 100; - - pc->number = page; - LOGI("Goto page %d...", page); - fz_try(ctx) - { - fz_rect rect; - LOGI("Load page %d", pc->number); - pc->page = fz_load_page(ctx, glo->doc, pc->number); - zoom = glo->resolution / 72; - fz_bound_page(ctx, pc->page, &pc->media_box); - fz_scale(&ctm, zoom, zoom); - rect = pc->media_box; - fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); - pc->width = bbox.x1-bbox.x0; - pc->height = bbox.y1-bbox.y0; - } - fz_catch(ctx) - { - LOGE("cannot make displaylist from page %d", pc->number); - } -} - -JNIEXPORT float JNICALL -JNI_FN(MuPDFCore_getPageWidth)(JNIEnv *env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - LOGI("PageWidth=%d", glo->pages[glo->current].width); - return glo->pages[glo->current].width; -} - -JNIEXPORT float JNICALL -JNI_FN(MuPDFCore_getPageHeight)(JNIEnv *env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - LOGI("PageHeight=%d", glo->pages[glo->current].height); - return glo->pages[glo->current].height; -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_javascriptSupported)(JNIEnv *env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - if (idoc) - return pdf_js_supported(ctx, idoc); - return 0; -} - -static void update_changed_rects(globals *glo, page_cache *pc, pdf_document *idoc) -{ - fz_context *ctx = glo->ctx; - fz_annot *annot; - - pdf_update_page(ctx, idoc, (pdf_page *)pc->page); - while ((annot = (fz_annot *)pdf_poll_changed_annot(ctx, idoc, (pdf_page *)pc->page)) != NULL) - { - /* FIXME: We bound the annot twice here */ - rect_node *node = fz_malloc_struct(glo->ctx, rect_node); - fz_bound_annot(ctx, annot, &node->rect); - node->next = pc->changed_rects; - pc->changed_rects = node; - - node = fz_malloc_struct(glo->ctx, rect_node); - fz_bound_annot(ctx, annot, &node->rect); - node->next = pc->hq_changed_rects; - pc->hq_changed_rects = node; - } -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_drawPage)(JNIEnv *env, jobject thiz, jobject bitmap, - int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr) -{ - AndroidBitmapInfo info; - void *pixels; - int ret; - fz_device *dev = NULL; - float zoom; - fz_matrix ctm; - fz_irect bbox; - fz_rect rect; - fz_pixmap *pix = NULL; - float xscale, yscale; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - page_cache *pc = &glo->pages[glo->current]; - int hq = (patchW < pageW || patchH < pageH); - fz_matrix scale; - fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr; - - if (pc->page == NULL) - return 0; - - fz_var(pix); - fz_var(dev); - - LOGI("In native method\n"); - if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { - LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); - return 0; - } - - LOGI("Checking format\n"); - if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { - LOGE("Bitmap format is not RGBA_8888 !"); - return 0; - } - - LOGI("locking pixels\n"); - if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { - LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); - return 0; - } - - /* Call mupdf to render display list to screen */ - LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]", - pc->number, pageW, pageH, patchX, patchY, patchW, patchH); - - fz_try(ctx) - { - fz_irect pixbbox; - pdf_document *idoc = pdf_specifics(ctx, doc); - - if (idoc) - { - /* Update the changed-rects for both hq patch and main bitmap */ - update_changed_rects(glo, pc, idoc); - - /* Then drop the changed-rects for the bitmap we're about to - render because we are rendering the entire area */ - drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects); - } - - if (pc->page_list == NULL) - { - /* Render to list */ - pc->page_list = fz_new_display_list(ctx); - dev = fz_new_list_device(ctx, pc->page_list); - fz_run_page_contents(ctx, pc->page, dev, &fz_identity, cookie); - fz_drop_device(ctx, dev); - dev = NULL; - if (cookie != NULL && cookie->abort) - { - fz_drop_display_list(ctx, pc->page_list); - pc->page_list = NULL; - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - } - } - if (pc->annot_list == NULL) - { - fz_annot *annot; - pc->annot_list = fz_new_display_list(ctx); - dev = fz_new_list_device(ctx, pc->annot_list); - for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) - fz_run_annot(ctx, annot, dev, &fz_identity, cookie); - fz_drop_device(ctx, dev); - dev = NULL; - if (cookie != NULL && cookie->abort) - { - fz_drop_display_list(ctx, pc->annot_list); - pc->annot_list = NULL; - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - } - } - bbox.x0 = patchX; - bbox.y0 = patchY; - bbox.x1 = patchX + patchW; - bbox.y1 = patchY + patchH; - pixbbox = bbox; - pixbbox.x1 = pixbbox.x0 + info.width; - /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to - * match the pixels data */ - pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels); - if (pc->page_list == NULL && pc->annot_list == NULL) - { - fz_clear_pixmap_with_value(ctx, pix, 0xd0); - break; - } - fz_clear_pixmap_with_value(ctx, pix, 0xff); - - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - rect = pc->media_box; - fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); - /* Now, adjust ctm so that it would give the correct page width - * heights. */ - xscale = (float)pageW/(float)(bbox.x1-bbox.x0); - yscale = (float)pageH/(float)(bbox.y1-bbox.y0); - fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale)); - rect = pc->media_box; - fz_transform_rect(&rect, &ctm); - dev = fz_new_draw_device(ctx, pix); -#ifdef TIME_DISPLAY_LIST - { - clock_t time; - int i; - - LOGI("Executing display list"); - time = clock(); - for (i=0; i<100;i++) { -#endif - if (pc->page_list) - fz_run_display_list(ctx, pc->page_list, dev, &ctm, &rect, cookie); - if (cookie != NULL && cookie->abort) - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - - if (pc->annot_list) - fz_run_display_list(ctx, pc->annot_list, dev, &ctm, &rect, cookie); - if (cookie != NULL && cookie->abort) - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - -#ifdef TIME_DISPLAY_LIST - } - time = clock() - time; - LOGI("100 renders in %d (%d per sec)", time, CLOCKS_PER_SEC); - } -#endif - fz_drop_device(ctx, dev); - dev = NULL; - fz_drop_pixmap(ctx, pix); - LOGI("Rendered"); - } - fz_always(ctx) - { - fz_drop_device(ctx, dev); - dev = NULL; - } - fz_catch(ctx) - { - LOGE("Render failed"); - } - - AndroidBitmap_unlockPixels(env, bitmap); - - return 1; -} - -static char *widget_type_string(int t) -{ - switch(t) - { - case PDF_WIDGET_TYPE_PUSHBUTTON: return "pushbutton"; - case PDF_WIDGET_TYPE_CHECKBOX: return "checkbox"; - case PDF_WIDGET_TYPE_RADIOBUTTON: return "radiobutton"; - case PDF_WIDGET_TYPE_TEXT: return "text"; - case PDF_WIDGET_TYPE_LISTBOX: return "listbox"; - case PDF_WIDGET_TYPE_COMBOBOX: return "combobox"; - case PDF_WIDGET_TYPE_SIGNATURE: return "signature"; - default: return "non-widget"; - } -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_updatePageInternal)(JNIEnv *env, jobject thiz, jobject bitmap, int page, - int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr) -{ - AndroidBitmapInfo info; - void *pixels; - int ret; - fz_device *dev = NULL; - float zoom; - fz_matrix ctm; - fz_irect bbox; - fz_rect rect; - fz_pixmap *pix = NULL; - float xscale, yscale; - pdf_document *idoc; - page_cache *pc = NULL; - int hq = (patchW < pageW || patchH < pageH); - int i; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - rect_node *crect; - fz_matrix scale; - fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr; - - for (i = 0; i < NUM_CACHE; i++) - { - if (glo->pages[i].page != NULL && glo->pages[i].number == page) - { - pc = &glo->pages[i]; - break; - } - } - - if (pc == NULL) - { - /* Without a cached page object we cannot perform a partial update so - render the entire bitmap instead */ - JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, page); - return JNI_FN(MuPDFCore_drawPage)(env, thiz, bitmap, pageW, pageH, patchX, patchY, patchW, patchH, (jlong)(intptr_t)cookie); - } - - idoc = pdf_specifics(ctx, doc); - - fz_var(pix); - fz_var(dev); - - LOGI("In native method\n"); - if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { - LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); - return 0; - } - - LOGI("Checking format\n"); - if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { - LOGE("Bitmap format is not RGBA_8888 !"); - return 0; - } - - LOGI("locking pixels\n"); - if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { - LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); - return 0; - } - - /* Call mupdf to render display list to screen */ - LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]", - pc->number, pageW, pageH, patchX, patchY, patchW, patchH); - - fz_try(ctx) - { - fz_annot *annot; - fz_irect pixbbox; - - if (idoc) - { - /* Update the changed-rects for both hq patch and main bitmap */ - update_changed_rects(glo, pc, idoc); - } - - if (pc->page_list == NULL) - { - /* Render to list */ - pc->page_list = fz_new_display_list(ctx); - dev = fz_new_list_device(ctx, pc->page_list); - fz_run_page_contents(ctx, pc->page, dev, &fz_identity, cookie); - fz_drop_device(ctx, dev); - dev = NULL; - if (cookie != NULL && cookie->abort) - { - fz_drop_display_list(ctx, pc->page_list); - pc->page_list = NULL; - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - } - } - - if (pc->annot_list == NULL) { - pc->annot_list = fz_new_display_list(ctx); - dev = fz_new_list_device(ctx, pc->annot_list); - for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) - fz_run_annot(ctx, annot, dev, &fz_identity, cookie); - fz_drop_device(ctx, dev); - dev = NULL; - if (cookie != NULL && cookie->abort) - { - fz_drop_display_list(ctx, pc->annot_list); - pc->annot_list = NULL; - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - } - } - - bbox.x0 = patchX; - bbox.y0 = patchY; - bbox.x1 = patchX + patchW; - bbox.y1 = patchY + patchH; - pixbbox = bbox; - pixbbox.x1 = pixbbox.x0 + info.width; - /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to - * match the pixels data */ - pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels); - - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - rect = pc->media_box; - fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); - /* Now, adjust ctm so that it would give the correct page width - * heights. */ - xscale = (float)pageW/(float)(bbox.x1-bbox.x0); - yscale = (float)pageH/(float)(bbox.y1-bbox.y0); - fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale)); - rect = pc->media_box; - fz_transform_rect(&rect, &ctm); - - LOGI("Start partial update"); - for (crect = hq ? pc->hq_changed_rects : pc->changed_rects; crect; crect = crect->next) - { - fz_irect abox; - fz_rect arect = crect->rect; - fz_intersect_rect(fz_transform_rect(&arect, &ctm), &rect); - fz_round_rect(&abox, &arect); - - LOGI("Update rectangle (%d, %d, %d, %d)", abox.x0, abox.y0, abox.x1, abox.y1); - if (!fz_is_empty_irect(&abox)) - { - LOGI("And it isn't empty"); - fz_clear_pixmap_rect_with_value(ctx, pix, 0xff, &abox); - dev = fz_new_draw_device_with_bbox(ctx, pix, &abox); - if (pc->page_list) - fz_run_display_list(ctx, pc->page_list, dev, &ctm, &arect, cookie); - if (cookie != NULL && cookie->abort) - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - - if (pc->annot_list) - fz_run_display_list(ctx, pc->annot_list, dev, &ctm, &arect, cookie); - if (cookie != NULL && cookie->abort) - fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); - - fz_drop_device(ctx, dev); - dev = NULL; - } - } - LOGI("End partial update"); - - /* Drop the changed rects we've just rendered */ - drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects); - - LOGI("Rendered"); - } - fz_always(ctx) - { - fz_drop_device(ctx, dev); - dev = NULL; - } - fz_catch(ctx) - { - LOGE("Render failed"); - } - - fz_drop_pixmap(ctx, pix); - AndroidBitmap_unlockPixels(env, bitmap); - - return 1; -} - -static int -charat(fz_context *ctx, fz_stext_page *page, int idx) -{ - fz_char_and_box cab; - return fz_stext_char_at(ctx, &cab, page, idx)->c; -} - -static fz_rect -bboxcharat(fz_context *ctx, fz_stext_page *page, int idx) -{ - fz_char_and_box cab; - return fz_stext_char_at(ctx, &cab, page, idx)->bbox; -} - -static int -textlen(fz_stext_page *page) -{ - int len = 0; - int block_num; - - for (block_num = 0; block_num < page->len; block_num++) - { - fz_stext_block *block; - fz_stext_line *line; - - if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT) - continue; - block = page->blocks[block_num].u.text; - for (line = block->lines; line < block->lines + block->len; line++) - { - fz_stext_span *span; - - for (span = line->first_span; span; span = span->next) - { - len += span->len; - } - len++; /* pseudo-newline */ - } - } - return len; -} - -static int -countOutlineItems(fz_outline *outline) -{ - int count = 0; - - while (outline) - { - if (outline->dest.kind == FZ_LINK_GOTO - && outline->dest.ld.gotor.page >= 0 - && outline->title) - count++; - - count += countOutlineItems(outline->down); - outline = outline->next; - } - - return count; -} - -static int -fillInOutlineItems(JNIEnv * env, jclass olClass, jmethodID ctor, jobjectArray arr, int pos, fz_outline *outline, int level) -{ - while (outline) - { - if (outline->dest.kind == FZ_LINK_GOTO) - { - int page = outline->dest.ld.gotor.page; - if (page >= 0 && outline->title) - { - jobject ol; - jstring title = (*env)->NewStringUTF(env, outline->title); - if (title == NULL) return -1; - ol = (*env)->NewObject(env, olClass, ctor, level, title, page); - if (ol == NULL) return -1; - (*env)->SetObjectArrayElement(env, arr, pos, ol); - (*env)->DeleteLocalRef(env, ol); - (*env)->DeleteLocalRef(env, title); - pos++; - } - } - pos = fillInOutlineItems(env, olClass, ctor, arr, pos, outline->down, level+1); - if (pos < 0) return -1; - outline = outline->next; - } - - return pos; -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_needsPasswordInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - - return fz_needs_password(ctx, glo->doc) ? JNI_TRUE : JNI_FALSE; -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_authenticatePasswordInternal)(JNIEnv *env, jobject thiz, jstring password) -{ - const char *pw; - int result; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - - pw = (*env)->GetStringUTFChars(env, password, NULL); - if (pw == NULL) - return JNI_FALSE; - - result = fz_authenticate_password(ctx, glo->doc, (char *)pw); - (*env)->ReleaseStringUTFChars(env, password, pw); - return result; -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_hasOutlineInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_outline *outline = fz_load_outline(ctx, glo->doc); - - fz_drop_outline(glo->ctx, outline); - return (outline == NULL) ? JNI_FALSE : JNI_TRUE; -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_getOutlineInternal)(JNIEnv * env, jobject thiz) -{ - jclass olClass; - jmethodID ctor; - jobjectArray arr; - jobject ol; - fz_outline *outline; - int nItems; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - jobjectArray ret; - - olClass = (*env)->FindClass(env, PACKAGENAME "/OutlineItem"); - if (olClass == NULL) return NULL; - ctor = (*env)->GetMethodID(env, olClass, "", "(ILjava/lang/String;I)V"); - if (ctor == NULL) return NULL; - - outline = fz_load_outline(ctx, glo->doc); - nItems = countOutlineItems(outline); - - arr = (*env)->NewObjectArray(env, - nItems, - olClass, - NULL); - if (arr == NULL) return NULL; - - ret = fillInOutlineItems(env, olClass, ctor, arr, 0, outline, 0) > 0 - ? arr - :NULL; - fz_drop_outline(glo->ctx, outline); - return ret; -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_searchPage)(JNIEnv * env, jobject thiz, jstring jtext) -{ - jclass rectClass; - jmethodID ctor; - jobjectArray arr; - jobject rect; - fz_stext_sheet *sheet = NULL; - fz_stext_page *text = NULL; - fz_device *dev = NULL; - float zoom; - fz_matrix ctm; - int pos; - int len; - int i, n; - int hit_count = 0; - const char *str; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - page_cache *pc = &glo->pages[glo->current]; - - rectClass = (*env)->FindClass(env, "android/graphics/RectF"); - if (rectClass == NULL) return NULL; - ctor = (*env)->GetMethodID(env, rectClass, "", "(FFFF)V"); - if (ctor == NULL) return NULL; - str = (*env)->GetStringUTFChars(env, jtext, NULL); - if (str == NULL) return NULL; - - fz_var(sheet); - fz_var(text); - fz_var(dev); - - fz_try(ctx) - { - if (glo->hit_bbox == NULL) - glo->hit_bbox = fz_malloc_array(ctx, MAX_SEARCH_HITS, sizeof(*glo->hit_bbox)); - - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - sheet = fz_new_stext_sheet(ctx); - text = fz_new_stext_page(ctx); - dev = fz_new_stext_device(ctx, sheet, text); - fz_run_page(ctx, pc->page, dev, &ctm, NULL); - fz_drop_device(ctx, dev); - dev = NULL; - - hit_count = fz_search_stext_page(ctx, text, str, glo->hit_bbox, MAX_SEARCH_HITS); - } - fz_always(ctx) - { - fz_drop_stext_page(ctx, text); - fz_drop_stext_sheet(ctx, sheet); - fz_drop_device(ctx, dev); - } - fz_catch(ctx) - { - jclass cls; - (*env)->ReleaseStringUTFChars(env, jtext, str); - cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); - if (cls != NULL) - (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); - (*env)->DeleteLocalRef(env, cls); - - return NULL; - } - - (*env)->ReleaseStringUTFChars(env, jtext, str); - - arr = (*env)->NewObjectArray(env, - hit_count, - rectClass, - NULL); - if (arr == NULL) return NULL; - - for (i = 0; i < hit_count; i++) { - rect = (*env)->NewObject(env, rectClass, ctor, - (float) (glo->hit_bbox[i].x0), - (float) (glo->hit_bbox[i].y0), - (float) (glo->hit_bbox[i].x1), - (float) (glo->hit_bbox[i].y1)); - if (rect == NULL) - return NULL; - (*env)->SetObjectArrayElement(env, arr, i, rect); - (*env)->DeleteLocalRef(env, rect); - } - - return arr; -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_text)(JNIEnv * env, jobject thiz) -{ - jclass textCharClass; - jclass textSpanClass; - jclass textLineClass; - jclass textBlockClass; - jmethodID ctor; - jobjectArray barr = NULL; - fz_stext_sheet *sheet = NULL; - fz_stext_page *text = NULL; - fz_device *dev = NULL; - float zoom; - fz_matrix ctm; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - page_cache *pc = &glo->pages[glo->current]; - - textCharClass = (*env)->FindClass(env, PACKAGENAME "/TextChar"); - if (textCharClass == NULL) return NULL; - textSpanClass = (*env)->FindClass(env, "[L" PACKAGENAME "/TextChar;"); - if (textSpanClass == NULL) return NULL; - textLineClass = (*env)->FindClass(env, "[[L" PACKAGENAME "/TextChar;"); - if (textLineClass == NULL) return NULL; - textBlockClass = (*env)->FindClass(env, "[[[L" PACKAGENAME "/TextChar;"); - if (textBlockClass == NULL) return NULL; - ctor = (*env)->GetMethodID(env, textCharClass, "", "(FFFFC)V"); - if (ctor == NULL) return NULL; - - fz_var(sheet); - fz_var(text); - fz_var(dev); - - fz_try(ctx) - { - int b, l, s, c; - - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - sheet = fz_new_stext_sheet(ctx); - text = fz_new_stext_page(ctx); - dev = fz_new_stext_device(ctx, sheet, text); - fz_run_page(ctx, pc->page, dev, &ctm, NULL); - fz_drop_device(ctx, dev); - dev = NULL; - - barr = (*env)->NewObjectArray(env, text->len, textBlockClass, NULL); - if (barr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); - - for (b = 0; b < text->len; b++) - { - fz_stext_block *block; - jobjectArray *larr; - - if (text->blocks[b].type != FZ_PAGE_BLOCK_TEXT) - continue; - block = text->blocks[b].u.text; - larr = (*env)->NewObjectArray(env, block->len, textLineClass, NULL); - if (larr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); - - for (l = 0; l < block->len; l++) - { - fz_stext_line *line = &block->lines[l]; - jobjectArray *sarr; - fz_stext_span *span; - int len = 0; - - for (span = line->first_span; span; span = span->next) - len++; - - sarr = (*env)->NewObjectArray(env, len, textSpanClass, NULL); - if (sarr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); - - for (s=0, span = line->first_span; span; s++, span = span->next) - { - jobjectArray *carr = (*env)->NewObjectArray(env, span->len, textCharClass, NULL); - if (carr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); - - for (c = 0; c < span->len; c++) - { - fz_stext_char *ch = &span->text[c]; - fz_rect bbox; - fz_stext_char_bbox(ctx, &bbox, span, c); - jobject cobj = (*env)->NewObject(env, textCharClass, ctor, bbox.x0, bbox.y0, bbox.x1, bbox.y1, ch->c); - if (cobj == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectfailed"); - - (*env)->SetObjectArrayElement(env, carr, c, cobj); - (*env)->DeleteLocalRef(env, cobj); - } - - (*env)->SetObjectArrayElement(env, sarr, s, carr); - (*env)->DeleteLocalRef(env, carr); - } - - (*env)->SetObjectArrayElement(env, larr, l, sarr); - (*env)->DeleteLocalRef(env, sarr); - } - - (*env)->SetObjectArrayElement(env, barr, b, larr); - (*env)->DeleteLocalRef(env, larr); - } - } - fz_always(ctx) - { - fz_drop_stext_page(ctx, text); - fz_drop_stext_sheet(ctx, sheet); - fz_drop_device(ctx, dev); - } - fz_catch(ctx) - { - jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); - if (cls != NULL) - (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_text"); - (*env)->DeleteLocalRef(env, cls); - - return NULL; - } - - return barr; -} - -JNIEXPORT jbyteArray JNICALL -JNI_FN(MuPDFCore_textAsHtml)(JNIEnv * env, jobject thiz) -{ - fz_stext_sheet *sheet = NULL; - fz_stext_page *text = NULL; - fz_device *dev = NULL; - fz_matrix ctm; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - page_cache *pc = &glo->pages[glo->current]; - jbyteArray bArray = NULL; - fz_buffer *buf = NULL; - fz_output *out = NULL; - - fz_var(sheet); - fz_var(text); - fz_var(dev); - fz_var(buf); - fz_var(out); - - fz_try(ctx) - { - int b, l, s, c; - - ctm = fz_identity; - sheet = fz_new_stext_sheet(ctx); - text = fz_new_stext_page(ctx); - dev = fz_new_stext_device(ctx, sheet, text); - fz_run_page(ctx, pc->page, dev, &ctm, NULL); - fz_drop_device(ctx, dev); - dev = NULL; - - fz_analyze_text(ctx, sheet, text); - - buf = fz_new_buffer(ctx, 256); - out = fz_new_output_with_buffer(ctx, buf); - fz_printf(ctx, out, "\n"); - fz_printf(ctx, out, "\n"); - fz_printf(ctx, out, "
"); - fz_print_stext_page_html(ctx, out, text); - fz_printf(ctx, out, "
\n"); - fz_printf(ctx, out, "\n\n"); - fz_drop_output(ctx, out); - out = NULL; - - bArray = (*env)->NewByteArray(env, buf->len); - if (bArray == NULL) - fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to make byteArray"); - (*env)->SetByteArrayRegion(env, bArray, 0, buf->len, buf->data); - - } - fz_always(ctx) - { - fz_drop_stext_page(ctx, text); - fz_drop_stext_sheet(ctx, sheet); - fz_drop_device(ctx, dev); - fz_drop_output(ctx, out); - fz_drop_buffer(ctx, buf); - } - fz_catch(ctx) - { - jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); - if (cls != NULL) - (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_textAsHtml"); - (*env)->DeleteLocalRef(env, cls); - - return NULL; - } - - return bArray; -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_addMarkupAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray points, fz_annot_type type) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - pdf_document *idoc = pdf_specifics(ctx, doc); - page_cache *pc = &glo->pages[glo->current]; - jclass pt_cls; - jfieldID x_fid, y_fid; - int i, n; - fz_point *pts = NULL; - float color[3]; - float alpha; - float line_height; - float line_thickness; - - if (idoc == NULL) - return; - - switch (type) - { - case FZ_ANNOT_HIGHLIGHT: - color[0] = 1.0; - color[1] = 1.0; - color[2] = 0.0; - alpha = 0.5; - line_thickness = 1.0; - line_height = 0.5; - break; - case FZ_ANNOT_UNDERLINE: - color[0] = 0.0; - color[1] = 0.0; - color[2] = 1.0; - alpha = 1.0; - line_thickness = LINE_THICKNESS; - line_height = UNDERLINE_HEIGHT; - break; - case FZ_ANNOT_STRIKEOUT: - color[0] = 1.0; - color[1] = 0.0; - color[2] = 0.0; - alpha = 1.0; - line_thickness = LINE_THICKNESS; - line_height = STRIKE_HEIGHT; - break; - default: - return; - } - - fz_var(pts); - fz_try(ctx) - { - fz_annot *annot; - fz_matrix ctm; - - float zoom = glo->resolution / 72; - zoom = 1.0 / zoom; - fz_scale(&ctm, zoom, zoom); - pt_cls = (*env)->FindClass(env, "android/graphics/PointF"); - if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass"); - x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F"); - if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)"); - y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F"); - if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)"); - - n = (*env)->GetArrayLength(env, points); - - pts = fz_malloc_array(ctx, n, sizeof(fz_point)); - - for (i = 0; i < n; i++) - { - jobject opt = (*env)->GetObjectArrayElement(env, points, i); - pts[i].x = opt ? (*env)->GetFloatField(env, opt, x_fid) : 0.0f; - pts[i].y = opt ? (*env)->GetFloatField(env, opt, y_fid) : 0.0f; - fz_transform_point(&pts[i], &ctm); - } - - annot = (fz_annot *)pdf_create_annot(ctx, idoc, (pdf_page *)pc->page, type); - - pdf_set_markup_annot_quadpoints(ctx, idoc, (pdf_annot *)annot, pts, n); - pdf_set_markup_appearance(ctx, idoc, (pdf_annot *)annot, color, alpha, line_thickness, line_height); - - dump_annotation_display_lists(glo); - } - fz_always(ctx) - { - fz_free(ctx, pts); - } - fz_catch(ctx) - { - LOGE("addStrikeOutAnnotation: %s failed", ctx->error->message); - jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); - if (cls != NULL) - (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); - (*env)->DeleteLocalRef(env, cls); - } -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_addInkAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray arcs) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - pdf_document *idoc = pdf_specifics(ctx, doc); - page_cache *pc = &glo->pages[glo->current]; - jclass pt_cls; - jfieldID x_fid, y_fid; - int i, j, k, n; - fz_point *pts = NULL; - int *counts = NULL; - int total = 0; - float color[3]; - - if (idoc == NULL) - return; - - color[0] = 1.0; - color[1] = 0.0; - color[2] = 0.0; - - fz_var(pts); - fz_var(counts); - fz_try(ctx) - { - fz_annot *annot; - fz_matrix ctm; - - float zoom = glo->resolution / 72; - zoom = 1.0 / zoom; - fz_scale(&ctm, zoom, zoom); - pt_cls = (*env)->FindClass(env, "android/graphics/PointF"); - if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass"); - x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F"); - if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)"); - y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F"); - if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)"); - - n = (*env)->GetArrayLength(env, arcs); - - counts = fz_malloc_array(ctx, n, sizeof(int)); - - for (i = 0; i < n; i++) - { - jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i); - int count = (*env)->GetArrayLength(env, arc); - - counts[i] = count; - total += count; - } - - pts = fz_malloc_array(ctx, total, sizeof(fz_point)); - - k = 0; - for (i = 0; i < n; i++) - { - jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i); - int count = counts[i]; - - for (j = 0; j < count; j++) - { - jobject pt = (*env)->GetObjectArrayElement(env, arc, j); - - pts[k].x = pt ? (*env)->GetFloatField(env, pt, x_fid) : 0.0f; - pts[k].y = pt ? (*env)->GetFloatField(env, pt, y_fid) : 0.0f; - (*env)->DeleteLocalRef(env, pt); - fz_transform_point(&pts[k], &ctm); - k++; - } - (*env)->DeleteLocalRef(env, arc); - } - - annot = (fz_annot *)pdf_create_annot(ctx, idoc, (pdf_page *)pc->page, FZ_ANNOT_INK); - - pdf_set_ink_annot_list(ctx, idoc, (pdf_annot *)annot, pts, counts, n, color, INK_THICKNESS); - - dump_annotation_display_lists(glo); - } - fz_always(ctx) - { - fz_free(ctx, pts); - fz_free(ctx, counts); - } - fz_catch(ctx) - { - LOGE("addInkAnnotation: %s failed", ctx->error->message); - jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); - if (cls != NULL) - (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); - (*env)->DeleteLocalRef(env, cls); - } -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_deleteAnnotationInternal)(JNIEnv * env, jobject thiz, int annot_index) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_document *doc = glo->doc; - pdf_document *idoc = pdf_specifics(ctx, doc); - page_cache *pc = &glo->pages[glo->current]; - fz_annot *annot; - int i; - - if (idoc == NULL) - return; - - fz_try(ctx) - { - annot = fz_first_annot(ctx, pc->page); - for (i = 0; i < annot_index && annot; i++) - annot = fz_next_annot(ctx, annot); - - if (annot) - { - pdf_delete_annot(ctx, idoc, (pdf_page *)pc->page, (pdf_annot *)annot); - dump_annotation_display_lists(glo); - } - } - fz_catch(ctx) - { - LOGE("deleteAnnotationInternal: %s", ctx->error->message); - } -} - -/* Close the document, at least enough to be able to save over it. This - * may be called again later, so must be idempotent. */ -static void close_doc(globals *glo) -{ - int i; - - fz_free(glo->ctx, glo->hit_bbox); - glo->hit_bbox = NULL; - - for (i = 0; i < NUM_CACHE; i++) - drop_page_cache(glo, &glo->pages[i]); - - alerts_fin(glo); - - fz_drop_document(glo->ctx, glo->doc); - glo->doc = NULL; -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_destroying)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - - if (glo == NULL) - return; - LOGI("Destroying"); - fz_free(glo->ctx, glo->current_path); - glo->current_path = NULL; - close_doc(glo); - fz_drop_context(glo->ctx); - glo->ctx = NULL; - free(glo); -#ifdef MEMENTO - LOGI("Destroying dump start"); - Memento_listBlocks(); - Memento_stats(); - LOGI("Destroying dump end"); -#endif -#ifdef NDK_PROFILER - // Apparently we should really be writing to whatever path we get - // from calling getFilesDir() in the java part, which supposedly - // gives /sdcard/data/data/com.artifex.MuPDF/gmon.out, but that's - // unfriendly. - setenv("CPUPROFILE", "/sdcard/gmon.out", 1); - moncleanup(); -#endif -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_getPageLinksInternal)(JNIEnv * env, jobject thiz, int pageNumber) -{ - jclass linkInfoClass; - jclass linkInfoInternalClass; - jclass linkInfoExternalClass; - jclass linkInfoRemoteClass; - jmethodID ctorInternal; - jmethodID ctorExternal; - jmethodID ctorRemote; - jobjectArray arr; - jobject linkInfo; - fz_matrix ctm; - float zoom; - fz_link *list; - fz_link *link; - int count; - page_cache *pc; - globals *glo = get_globals(env, thiz); - - linkInfoClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfo"); - if (linkInfoClass == NULL) return NULL; - linkInfoInternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoInternal"); - if (linkInfoInternalClass == NULL) return NULL; - linkInfoExternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoExternal"); - if (linkInfoExternalClass == NULL) return NULL; - linkInfoRemoteClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoRemote"); - if (linkInfoRemoteClass == NULL) return NULL; - ctorInternal = (*env)->GetMethodID(env, linkInfoInternalClass, "", "(FFFFI)V"); - if (ctorInternal == NULL) return NULL; - ctorExternal = (*env)->GetMethodID(env, linkInfoExternalClass, "", "(FFFFLjava/lang/String;)V"); - if (ctorExternal == NULL) return NULL; - ctorRemote = (*env)->GetMethodID(env, linkInfoRemoteClass, "", "(FFFFLjava/lang/String;IZ)V"); - if (ctorRemote == NULL) return NULL; - - JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); - pc = &glo->pages[glo->current]; - if (pc->page == NULL || pc->number != pageNumber) - return NULL; - - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - - list = fz_load_links(glo->ctx, pc->page); - count = 0; - for (link = list; link; link = link->next) - { - switch (link->dest.kind) - { - case FZ_LINK_GOTO: - case FZ_LINK_GOTOR: - case FZ_LINK_URI: - count++ ; - } - } - - arr = (*env)->NewObjectArray(env, count, linkInfoClass, NULL); - if (arr == NULL) - { - fz_drop_link(glo->ctx, list); - return NULL; - } - - count = 0; - for (link = list; link; link = link->next) - { - fz_rect rect = link->rect; - fz_transform_rect(&rect, &ctm); - - switch (link->dest.kind) - { - case FZ_LINK_GOTO: - { - linkInfo = (*env)->NewObject(env, linkInfoInternalClass, ctorInternal, - (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, - link->dest.ld.gotor.page); - break; - } - - case FZ_LINK_GOTOR: - { - jstring juri = (*env)->NewStringUTF(env, link->dest.ld.gotor.file_spec); - linkInfo = (*env)->NewObject(env, linkInfoRemoteClass, ctorRemote, - (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, - juri, link->dest.ld.gotor.page, link->dest.ld.gotor.new_window ? JNI_TRUE : JNI_FALSE); - break; - } - - case FZ_LINK_URI: - { - jstring juri = (*env)->NewStringUTF(env, link->dest.ld.uri.uri); - linkInfo = (*env)->NewObject(env, linkInfoExternalClass, ctorExternal, - (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, - juri); - break; - } - - default: - continue; - } - - if (linkInfo == NULL) - { - fz_drop_link(glo->ctx, list); - return NULL; - } - (*env)->SetObjectArrayElement(env, arr, count, linkInfo); - (*env)->DeleteLocalRef(env, linkInfo); - count++; - } - fz_drop_link(glo->ctx, list); - - return arr; -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_getWidgetAreasInternal)(JNIEnv * env, jobject thiz, int pageNumber) -{ - jclass rectFClass; - jmethodID ctor; - jobjectArray arr; - jobject rectF; - pdf_document *idoc; - pdf_widget *widget; - fz_matrix ctm; - float zoom; - int count; - page_cache *pc; - globals *glo = get_globals(env, thiz); - if (glo == NULL) - return NULL; - fz_context *ctx = glo->ctx; - - rectFClass = (*env)->FindClass(env, "android/graphics/RectF"); - if (rectFClass == NULL) return NULL; - ctor = (*env)->GetMethodID(env, rectFClass, "", "(FFFF)V"); - if (ctor == NULL) return NULL; - - JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); - pc = &glo->pages[glo->current]; - if (pc->number != pageNumber || pc->page == NULL) - return NULL; - - idoc = pdf_specifics(ctx, glo->doc); - if (idoc == NULL) - return NULL; - - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - - count = 0; - for (widget = pdf_first_widget(ctx, idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(ctx, widget)) - count ++; - - arr = (*env)->NewObjectArray(env, count, rectFClass, NULL); - if (arr == NULL) return NULL; - - count = 0; - for (widget = pdf_first_widget(ctx, idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(ctx, widget)) - { - fz_rect rect; - pdf_bound_widget(ctx, widget, &rect); - fz_transform_rect(&rect, &ctm); - - rectF = (*env)->NewObject(env, rectFClass, ctor, - (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1); - if (rectF == NULL) return NULL; - (*env)->SetObjectArrayElement(env, arr, count, rectF); - (*env)->DeleteLocalRef(env, rectF); - - count ++; - } - - return arr; -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_getAnnotationsInternal)(JNIEnv * env, jobject thiz, int pageNumber) -{ - jclass annotClass; - jmethodID ctor; - jobjectArray arr; - jobject jannot; - fz_annot *annot; - fz_matrix ctm; - float zoom; - int count; - page_cache *pc; - globals *glo = get_globals(env, thiz); - if (glo == NULL) - return NULL; - fz_context *ctx = glo->ctx; - - annotClass = (*env)->FindClass(env, PACKAGENAME "/Annotation"); - if (annotClass == NULL) return NULL; - ctor = (*env)->GetMethodID(env, annotClass, "", "(FFFFI)V"); - if (ctor == NULL) return NULL; - - JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); - pc = &glo->pages[glo->current]; - if (pc->number != pageNumber || pc->page == NULL) - return NULL; - - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - - count = 0; - for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) - count ++; - - arr = (*env)->NewObjectArray(env, count, annotClass, NULL); - if (arr == NULL) return NULL; - - count = 0; - for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) - { - fz_rect rect; - fz_annot_type type = pdf_annot_type(ctx, (pdf_annot *)annot); - fz_bound_annot(ctx, annot, &rect); - fz_transform_rect(&rect, &ctm); - - jannot = (*env)->NewObject(env, annotClass, ctor, - (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, type); - if (jannot == NULL) return NULL; - (*env)->SetObjectArrayElement(env, arr, count, jannot); - (*env)->DeleteLocalRef(env, jannot); - - count ++; - } - - return arr; -} - -JNIEXPORT int JNICALL -JNI_FN(MuPDFCore_passClickEventInternal)(JNIEnv * env, jobject thiz, int pageNumber, float x, float y) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - fz_matrix ctm; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - float zoom; - fz_point p; - pdf_ui_event event; - int changed = 0; - page_cache *pc; - - if (idoc == NULL) - return 0; - - JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); - pc = &glo->pages[glo->current]; - if (pc->number != pageNumber || pc->page == NULL) - return 0; - - p.x = x; - p.y = y; - - /* Ultimately we should probably return a pointer to a java structure - * with the link details in, but for now, page number will suffice. - */ - zoom = glo->resolution / 72; - fz_scale(&ctm, zoom, zoom); - fz_invert_matrix(&ctm, &ctm); - - fz_transform_point(&p, &ctm); - - fz_try(ctx) - { - event.etype = PDF_EVENT_TYPE_POINTER; - event.event.pointer.pt = p; - event.event.pointer.ptype = PDF_POINTER_DOWN; - changed = pdf_pass_event(ctx, idoc, (pdf_page *)pc->page, &event); - event.event.pointer.ptype = PDF_POINTER_UP; - changed |= pdf_pass_event(ctx, idoc, (pdf_page *)pc->page, &event); - if (changed) { - dump_annotation_display_lists(glo); - } - } - fz_catch(ctx) - { - LOGE("passClickEvent: %s", ctx->error->message); - } - - return changed; -} - -JNIEXPORT jstring JNICALL -JNI_FN(MuPDFCore_getFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz) -{ - char *text = ""; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - - fz_try(ctx) - { - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - - if (idoc) - { - pdf_widget *focus = pdf_focused_widget(ctx, idoc); - - if (focus) - text = pdf_text_widget_text(ctx, idoc, focus); - } - } - fz_catch(ctx) - { - LOGE("getFocusedWidgetText failed: %s", ctx->error->message); - } - - return (*env)->NewStringUTF(env, text); -} - -JNIEXPORT int JNICALL -JNI_FN(MuPDFCore_setFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz, jstring jtext) -{ - const char *text; - int result = 0; - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - - text = (*env)->GetStringUTFChars(env, jtext, NULL); - if (text == NULL) - { - LOGE("Failed to get text"); - return 0; - } - - fz_try(ctx) - { - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - - if (idoc) - { - pdf_widget *focus = pdf_focused_widget(ctx, idoc); - - if (focus) - { - result = pdf_text_widget_set_text(ctx, idoc, focus, (char *)text); - dump_annotation_display_lists(glo); - } - } - } - fz_catch(ctx) - { - LOGE("setFocusedWidgetText failed: %s", ctx->error->message); - } - - (*env)->ReleaseStringUTFChars(env, jtext, text); - - return result; -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_getFocusedWidgetChoiceOptions)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - pdf_widget *focus; - int type; - int nopts, i; - char **opts = NULL; - jclass stringClass; - jobjectArray arr; - - if (idoc == NULL) - return NULL; - - focus = pdf_focused_widget(ctx, idoc); - if (focus == NULL) - return NULL; - - type = pdf_widget_get_type(ctx, focus); - if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) - return NULL; - - fz_var(opts); - fz_try(ctx) - { - nopts = pdf_choice_widget_options(ctx, idoc, focus, 0, NULL); - opts = fz_malloc(ctx, nopts * sizeof(*opts)); - (void)pdf_choice_widget_options(ctx, idoc, focus, 0, opts); - } - fz_catch(ctx) - { - fz_free(ctx, opts); - LOGE("Failed in getFocuseedWidgetChoiceOptions"); - return NULL; - } - - stringClass = (*env)->FindClass(env, "java/lang/String"); - - arr = (*env)->NewObjectArray(env, nopts, stringClass, NULL); - - for (i = 0; i < nopts; i++) - { - jstring s = (*env)->NewStringUTF(env, opts[i]); - if (s != NULL) - (*env)->SetObjectArrayElement(env, arr, i, s); - - (*env)->DeleteLocalRef(env, s); - } - - fz_free(ctx, opts); - - return arr; -} - -JNIEXPORT jobjectArray JNICALL -JNI_FN(MuPDFCore_getFocusedWidgetChoiceSelected)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - pdf_widget *focus; - int type; - int nsel, i; - char **sel = NULL; - jclass stringClass; - jobjectArray arr; - - if (idoc == NULL) - return NULL; - - focus = pdf_focused_widget(ctx, idoc); - if (focus == NULL) - return NULL; - - type = pdf_widget_get_type(ctx, focus); - if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) - return NULL; - - fz_var(sel); - fz_try(ctx) - { - nsel = pdf_choice_widget_value(ctx, idoc, focus, NULL); - sel = fz_malloc(ctx, nsel * sizeof(*sel)); - (void)pdf_choice_widget_value(ctx, idoc, focus, sel); - } - fz_catch(ctx) - { - fz_free(ctx, sel); - LOGE("Failed in getFocuseedWidgetChoiceOptions"); - return NULL; - } - - stringClass = (*env)->FindClass(env, "java/lang/String"); - - arr = (*env)->NewObjectArray(env, nsel, stringClass, NULL); - - for (i = 0; i < nsel; i++) - { - jstring s = (*env)->NewStringUTF(env, sel[i]); - if (s != NULL) - (*env)->SetObjectArrayElement(env, arr, i, s); - - (*env)->DeleteLocalRef(env, s); - } - - fz_free(ctx, sel); - - return arr; -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_setFocusedWidgetChoiceSelectedInternal)(JNIEnv * env, jobject thiz, jobjectArray arr) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - pdf_widget *focus; - int type; - int nsel, i; - char **sel = NULL; - jstring *objs = NULL; - - if (idoc == NULL) - return; - - focus = pdf_focused_widget(ctx, idoc); - if (focus == NULL) - return; - - type = pdf_widget_get_type(ctx, focus); - if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) - return; - - nsel = (*env)->GetArrayLength(env, arr); - - sel = calloc(nsel, sizeof(*sel)); - objs = calloc(nsel, sizeof(*objs)); - if (objs == NULL || sel == NULL) - { - free(sel); - free(objs); - LOGE("Failed in setFocusWidgetChoiceSelected"); - return; - } - - for (i = 0; i < nsel; i++) - { - objs[i] = (jstring)(*env)->GetObjectArrayElement(env, arr, i); - sel[i] = (char *)(*env)->GetStringUTFChars(env, objs[i], NULL); - } - - fz_try(ctx) - { - pdf_choice_widget_set_value(ctx, idoc, focus, nsel, sel); - dump_annotation_display_lists(glo); - } - fz_catch(ctx) - { - LOGE("Failed in setFocusWidgetChoiceSelected"); - } - - for (i = 0; i < nsel; i++) - (*env)->ReleaseStringUTFChars(env, objs[i], sel[i]); - - free(sel); - free(objs); -} - -JNIEXPORT int JNICALL -JNI_FN(MuPDFCore_getFocusedWidgetTypeInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - pdf_widget *focus; - - if (ctx, idoc == NULL) - return NONE; - - focus = pdf_focused_widget(ctx, idoc); - - if (focus == NULL) - return NONE; - - switch (pdf_widget_get_type(ctx, focus)) - { - case PDF_WIDGET_TYPE_TEXT: return TEXT; - case PDF_WIDGET_TYPE_LISTBOX: return LISTBOX; - case PDF_WIDGET_TYPE_COMBOBOX: return COMBOBOX; - case PDF_WIDGET_TYPE_SIGNATURE: return SIGNATURE; - } - - return NONE; -} - -/* This enum should be kept in line with SignatureState in MuPDFPageView.java */ -enum -{ - Signature_NoSupport, - Signature_Unsigned, - Signature_Signed -}; - -JNIEXPORT int JNICALL -JNI_FN(MuPDFCore_getFocusedWidgetSignatureState)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - pdf_widget *focus; - - if (ctx, idoc == NULL) - return Signature_NoSupport; - - focus = pdf_focused_widget(ctx, idoc); - - if (focus == NULL) - return Signature_NoSupport; - - if (!pdf_signatures_supported()) - return Signature_NoSupport; - - return pdf_dict_get(ctx, ((pdf_annot *)focus)->obj, PDF_NAME_V) ? Signature_Signed : Signature_Unsigned; -} - -JNIEXPORT jstring JNICALL -JNI_FN(MuPDFCore_checkFocusedSignatureInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - pdf_widget *focus; - char ebuf[256] = "Failed"; - - if (idoc == NULL) - goto exit; - - focus = pdf_focused_widget(ctx, idoc); - - if (focus == NULL) - goto exit; - - if (pdf_check_signature(ctx, idoc, focus, glo->current_path, ebuf, sizeof(ebuf))) - { - strcpy(ebuf, "Signature is valid"); - } - -exit: - return (*env)->NewStringUTF(env, ebuf); -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_signFocusedSignatureInternal)(JNIEnv * env, jobject thiz, jstring jkeyfile, jstring jpassword) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - pdf_widget *focus; - const char *keyfile; - const char *password; - jboolean res; - - if (idoc == NULL) - return JNI_FALSE; - - focus = pdf_focused_widget(ctx, idoc); - - if (focus == NULL) - return JNI_FALSE; - - keyfile = (*env)->GetStringUTFChars(env, jkeyfile, NULL); - password = (*env)->GetStringUTFChars(env, jpassword, NULL); - if (keyfile == NULL || password == NULL) - return JNI_FALSE; - - fz_var(res); - fz_try(ctx) - { - pdf_sign_signature(ctx, idoc, focus, keyfile, password); - dump_annotation_display_lists(glo); - res = JNI_TRUE; - } - fz_catch(ctx) - { - res = JNI_FALSE; - } - - return res; -} - -JNIEXPORT jobject JNICALL -JNI_FN(MuPDFCore_waitForAlertInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - jclass alertClass; - jmethodID ctor; - jstring title; - jstring message; - int alert_present; - pdf_alert_event alert; - - LOGT("Enter waitForAlert"); - pthread_mutex_lock(&glo->fin_lock); - pthread_mutex_lock(&glo->alert_lock); - - while (glo->alerts_active && !glo->alert_request) - pthread_cond_wait(&glo->alert_request_cond, &glo->alert_lock); - glo->alert_request = 0; - - alert_present = (glo->alerts_active && glo->current_alert); - - if (alert_present) - alert = *glo->current_alert; - - pthread_mutex_unlock(&glo->alert_lock); - pthread_mutex_unlock(&glo->fin_lock); - LOGT("Exit waitForAlert %d", alert_present); - - if (!alert_present) - return NULL; - - alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal"); - if (alertClass == NULL) - return NULL; - - ctor = (*env)->GetMethodID(env, alertClass, "", "(Ljava/lang/String;IILjava/lang/String;I)V"); - if (ctor == NULL) - return NULL; - - title = (*env)->NewStringUTF(env, alert.title); - if (title == NULL) - return NULL; - - message = (*env)->NewStringUTF(env, alert.message); - if (message == NULL) - return NULL; - - return (*env)->NewObject(env, alertClass, ctor, message, alert.icon_type, alert.button_group_type, title, alert.button_pressed); -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_replyToAlertInternal)(JNIEnv * env, jobject thiz, jobject alert) -{ - globals *glo = get_globals(env, thiz); - jclass alertClass; - jfieldID field; - int button_pressed; - - alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal"); - if (alertClass == NULL) - return; - - field = (*env)->GetFieldID(env, alertClass, "buttonPressed", "I"); - if (field == NULL) - return; - - button_pressed = (*env)->GetIntField(env, alert, field); - - LOGT("Enter replyToAlert"); - pthread_mutex_lock(&glo->alert_lock); - - if (glo->alerts_active && glo->current_alert) - { - // Fill in button_pressed and signal reply received. - glo->current_alert->button_pressed = button_pressed; - glo->alert_reply = 1; - pthread_cond_signal(&glo->alert_reply_cond); - } - - pthread_mutex_unlock(&glo->alert_lock); - LOGT("Exit replyToAlert"); -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_startAlertsInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - - if (!glo->alerts_initialised) - return; - - LOGT("Enter startAlerts"); - pthread_mutex_lock(&glo->alert_lock); - - glo->alert_reply = 0; - glo->alert_request = 0; - glo->alerts_active = 1; - glo->current_alert = NULL; - - pthread_mutex_unlock(&glo->alert_lock); - LOGT("Exit startAlerts"); -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_stopAlertsInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - - if (!glo->alerts_initialised) - return; - - LOGT("Enter stopAlerts"); - pthread_mutex_lock(&glo->alert_lock); - - glo->alert_reply = 0; - glo->alert_request = 0; - glo->alerts_active = 0; - glo->current_alert = NULL; - pthread_cond_signal(&glo->alert_reply_cond); - pthread_cond_signal(&glo->alert_request_cond); - - pthread_mutex_unlock(&glo->alert_lock); - LOGT("Exit stopAleerts"); -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_hasChangesInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - - return (idoc && pdf_has_unsaved_changes(ctx, idoc)) ? JNI_TRUE : JNI_FALSE; -} - -static char *tmp_path(char *path) -{ - int f; - char *buf = malloc(strlen(path) + 6 + 1); - if (!buf) - return NULL; - - strcpy(buf, path); - strcat(buf, "XXXXXX"); - - f = mkstemp(buf); - - if (f >= 0) - { - close(f); - return buf; - } - else - { - free(buf); - return NULL; - } -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_saveInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - pdf_document *idoc = pdf_specifics(ctx, glo->doc); - - if (idoc && glo->current_path) - { - char *tmp; - pdf_write_options opts; - opts.do_incremental = 1; - opts.do_ascii = 0; - opts.do_expand = 0; - opts.do_garbage = 0; - opts.do_linear = 0; - - tmp = tmp_path(glo->current_path); - if (tmp) - { - int written = 0; - - fz_var(written); - fz_try(ctx) - { - FILE *fin = fopen(glo->current_path, "rb"); - FILE *fout = fopen(tmp, "wb"); - char buf[256]; - int n, err = 1; - - if (fin && fout) - { - while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) - fwrite(buf, 1, n, fout); - err = (ferror(fin) || ferror(fout)); - } - - if (fin) - fclose(fin); - if (fout) - fclose(fout); - - if (!err) - { - pdf_save_document(ctx, idoc, tmp, &opts); - written = 1; - } - } - fz_catch(ctx) - { - written = 0; - } - - if (written) - { - close_doc(glo); - rename(tmp, glo->current_path); - } - - free(tmp); - } - } -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_dumpMemoryInternal)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - -#ifdef MEMENTO - LOGE("dumpMemoryInternal start"); - Memento_listNewBlocks(); - Memento_stats(); - LOGE("dumpMemoryInternal end"); -#endif -} - -JNIEXPORT jlong JNICALL -JNI_FN(MuPDFCore_createCookie)(JNIEnv * env, jobject thiz) -{ - globals *glo = get_globals_any_thread(env, thiz); - if (glo == NULL) - return 0; - fz_context *ctx = glo->ctx; - - return (jlong) (intptr_t) fz_calloc_no_throw(ctx,1, sizeof(fz_cookie)); -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_destroyCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr) -{ - fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr; - globals *glo = get_globals_any_thread(env, thiz); - if (glo == NULL) - return; - fz_context *ctx = glo->ctx; - - fz_free(ctx, cookie); -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_abortCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr) -{ - fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr; - if (cookie != NULL) - cookie->abort = 1; -} - -static char *tmp_gproof_path(char *path) -{ - FILE *f; - int i; - char *buf = malloc(strlen(path) + 20 + 1); - if (!buf) - return NULL; - - for (i = 0; i < 10000; i++) - { - sprintf(buf, "%s.%d.gproof", path, i); - - LOGE("Trying for %s\n", buf); - f = fopen(buf, "r"); - if (f != NULL) - { - fclose(f); - continue; - } - - f = fopen(buf, "w"); - if (f != NULL) - { - fclose(f); - break; - } - } - if (i == 10000) - { - LOGE("Failed to find temp gproof name"); - free(buf); - return NULL; - } - - LOGE("Rewritten to %s\n", buf); - return buf; -} - -JNIEXPORT jstring JNICALL -JNI_FN(MuPDFCore_startProofInternal)(JNIEnv * env, jobject thiz, int inResolution) -{ -#ifdef SUPPORT_GPROOF - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - char *tmp; - jstring ret; - - if (!glo->doc || !glo->current_path) - return NULL; - - tmp = tmp_gproof_path(glo->current_path); - if (!tmp) - return NULL; - - int theResolution = PROOF_RESOLUTION; - if (inResolution != 0) - theResolution = inResolution; - - fz_try(ctx) - { - fz_save_gproof(ctx, glo->current_path, glo->doc, tmp, theResolution, "", ""); - - LOGE("Creating %s\n", tmp); - ret = (*env)->NewStringUTF(env, tmp); - } - fz_always(ctx) - { - free(tmp); - } - fz_catch(ctx) - { - ret = NULL; - } - return ret; -#else - return NULL; -#endif -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_endProofInternal)(JNIEnv * env, jobject thiz, jstring jfilename) -{ -#ifdef SUPPORT_GPROOF - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - const char *tmp; - - if (!glo->doc || !glo->current_path || jfilename == NULL) - return; - - tmp = (*env)->GetStringUTFChars(env, jfilename, NULL); - if (tmp) - { - LOGE("Deleting %s\n", tmp); - - unlink(tmp); - (*env)->ReleaseStringUTFChars(env, jfilename, tmp); - } -#endif -} - -JNIEXPORT jboolean JNICALL -JNI_FN(MuPDFCore_gprfSupportedInternal)(JNIEnv * env) -{ -#ifdef SUPPORT_GPROOF - return JNI_TRUE; -#else - return JNI_FALSE; -#endif -} - -JNIEXPORT int JNICALL -JNI_FN(MuPDFCore_getNumSepsOnPageInternal)(JNIEnv *env, jobject thiz, int page) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - int i; - - for (i = 0; i < NUM_CACHE; i++) - { - if (glo->pages[i].page != NULL && glo->pages[i].number == page) - break; - } - if (i == NUM_CACHE) - return 0; - - LOGE("Counting seps on page %d", page); - - return fz_count_separations_on_page(ctx, glo->pages[i].page); -} - -JNIEXPORT void JNICALL -JNI_FN(MuPDFCore_controlSepOnPageInternal)(JNIEnv *env, jobject thiz, int page, int sep, jboolean disable) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - int i; - - for (i = 0; i < NUM_CACHE; i++) - { - if (glo->pages[i].page != NULL && glo->pages[i].number == page) - break; - } - if (i == NUM_CACHE) - return; - - fz_control_separation_on_page(ctx, glo->pages[i].page, sep, disable); -} - -JNIEXPORT jobject JNICALL -JNI_FN(MuPDFCore_getSepInternal)(JNIEnv *env, jobject thiz, int page, int sep) -{ - globals *glo = get_globals(env, thiz); - fz_context *ctx = glo->ctx; - const char *name; - char rgba[4]; - unsigned int bgra; - unsigned int cmyk; - jobject jname; - jclass sepClass; - jmethodID ctor; - int i; - - for (i = 0; i < NUM_CACHE; i++) - { - if (glo->pages[i].page != NULL && glo->pages[i].number == page) - break; - } - if (i == NUM_CACHE) - return NULL; - - /* MuPDF returns RGBA as bytes. Android wants a packed BGRA int. */ - name = fz_get_separation_on_page(ctx, glo->pages[i].page, sep, (unsigned int *)(&rgba[0]), &cmyk); - bgra = (rgba[0] << 16) | (rgba[1]<<8) | rgba[2] | (rgba[3]<<24); - jname = name ? (*env)->NewStringUTF(env, name) : NULL; - - sepClass = (*env)->FindClass(env, PACKAGENAME "/Separation"); - if (sepClass == NULL) - return NULL; - - ctor = (*env)->GetMethodID(env, sepClass, "", "(Ljava/lang/String;II)V"); - if (ctor == NULL) - return NULL; - - return (*env)->NewObject(env, sepClass, ctor, jname, bgra, cmyk); -} diff --git a/platform/android/local.properties.sample b/platform/android/local.properties.sample deleted file mode 100644 index 557fbc14..00000000 --- a/platform/android/local.properties.sample +++ /dev/null @@ -1,8 +0,0 @@ -# Uncomment and edit the appropriate line below. -# Resave this file as local.properties. - -# For MacOS/Linux you want a line such as: -#sdk.dir=/Library/android-sdk-mac_x86 - -# For Windows/Cygwin you want something like the following: -#sdk.dir=C:\\Program Files (x86)\\Android\\android-sdk diff --git a/platform/android/project.properties b/platform/android/project.properties deleted file mode 100644 index 895c9ce2..00000000 --- a/platform/android/project.properties +++ /dev/null @@ -1,11 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-16 diff --git a/platform/android/res/animator/info.xml b/platform/android/res/animator/info.xml deleted file mode 100644 index 9085a9ee..00000000 --- a/platform/android/res/animator/info.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/platform/android/res/drawable-hdpi/icon.png b/platform/android/res/drawable-hdpi/icon.png deleted file mode 100644 index 4f47347d..00000000 Binary files a/platform/android/res/drawable-hdpi/icon.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_annot.png b/platform/android/res/drawable-ldpi/ic_annot.png deleted file mode 100644 index c4f1df07..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_annot.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_annotation.png b/platform/android/res/drawable-ldpi/ic_annotation.png deleted file mode 100644 index 1f4e6d48..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_annotation.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_arrow_left.png b/platform/android/res/drawable-ldpi/ic_arrow_left.png deleted file mode 100644 index d49c7438..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_arrow_left.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_arrow_right.png b/platform/android/res/drawable-ldpi/ic_arrow_right.png deleted file mode 100644 index e76d0cb0..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_arrow_right.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_cancel.png b/platform/android/res/drawable-ldpi/ic_cancel.png deleted file mode 100644 index 6912e1ed..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_cancel.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_check.png b/platform/android/res/drawable-ldpi/ic_check.png deleted file mode 100644 index fb789c8d..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_check.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_clipboard.png b/platform/android/res/drawable-ldpi/ic_clipboard.png deleted file mode 100644 index 3023c6eb..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_clipboard.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_dir.png b/platform/android/res/drawable-ldpi/ic_dir.png deleted file mode 100644 index 2236f2f8..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_dir.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_doc.png b/platform/android/res/drawable-ldpi/ic_doc.png deleted file mode 100644 index 407ed5d4..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_doc.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_highlight.png b/platform/android/res/drawable-ldpi/ic_highlight.png deleted file mode 100644 index 3d6d29b9..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_highlight.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_link.png b/platform/android/res/drawable-ldpi/ic_link.png deleted file mode 100644 index a447b87d..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_link.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_list.png b/platform/android/res/drawable-ldpi/ic_list.png deleted file mode 100644 index 4a2dde6d..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_list.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_magnifying_glass.png b/platform/android/res/drawable-ldpi/ic_magnifying_glass.png deleted file mode 100644 index a3c8f598..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_magnifying_glass.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_more.png b/platform/android/res/drawable-ldpi/ic_more.png deleted file mode 100644 index 68988a56..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_more.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_pen.png b/platform/android/res/drawable-ldpi/ic_pen.png deleted file mode 100644 index 7b7de296..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_pen.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_print.png b/platform/android/res/drawable-ldpi/ic_print.png deleted file mode 100644 index f191fc85..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_print.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_proof.png b/platform/android/res/drawable-ldpi/ic_proof.png deleted file mode 100644 index fee26a7b..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_proof.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_reflow.png b/platform/android/res/drawable-ldpi/ic_reflow.png deleted file mode 100644 index e9e8b052..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_reflow.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_select.png b/platform/android/res/drawable-ldpi/ic_select.png deleted file mode 100644 index 81af6738..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_select.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_share.png b/platform/android/res/drawable-ldpi/ic_share.png deleted file mode 100644 index 05fbe31a..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_share.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_strike.png b/platform/android/res/drawable-ldpi/ic_strike.png deleted file mode 100644 index fc39409f..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_strike.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_trash.png b/platform/android/res/drawable-ldpi/ic_trash.png deleted file mode 100644 index 465d1245..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_trash.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_underline.png b/platform/android/res/drawable-ldpi/ic_underline.png deleted file mode 100644 index 0a5be3d4..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_underline.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/ic_updir.png b/platform/android/res/drawable-ldpi/ic_updir.png deleted file mode 100644 index b923e429..00000000 Binary files a/platform/android/res/drawable-ldpi/ic_updir.png and /dev/null differ diff --git a/platform/android/res/drawable-ldpi/icon.png b/platform/android/res/drawable-ldpi/icon.png deleted file mode 100644 index 82655e72..00000000 Binary files a/platform/android/res/drawable-ldpi/icon.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_annot.png b/platform/android/res/drawable-mdpi/ic_annot.png deleted file mode 100644 index 0b4bfd92..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_annot.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_annotation.png b/platform/android/res/drawable-mdpi/ic_annotation.png deleted file mode 100644 index 6f81c4a0..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_annotation.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_arrow_left.png b/platform/android/res/drawable-mdpi/ic_arrow_left.png deleted file mode 100644 index 16a31b21..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_arrow_left.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_arrow_right.png b/platform/android/res/drawable-mdpi/ic_arrow_right.png deleted file mode 100644 index cc34067e..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_arrow_right.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_arrow_up.png b/platform/android/res/drawable-mdpi/ic_arrow_up.png deleted file mode 100644 index de2726ce..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_arrow_up.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_cancel.png b/platform/android/res/drawable-mdpi/ic_cancel.png deleted file mode 100644 index 0b794b4d..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_cancel.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_check.png b/platform/android/res/drawable-mdpi/ic_check.png deleted file mode 100644 index 527aaeb9..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_check.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_clipboard.png b/platform/android/res/drawable-mdpi/ic_clipboard.png deleted file mode 100644 index c05deffd..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_clipboard.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_dir.png b/platform/android/res/drawable-mdpi/ic_dir.png deleted file mode 100644 index e15200c5..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_dir.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_doc.png b/platform/android/res/drawable-mdpi/ic_doc.png deleted file mode 100644 index 1eb722be..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_doc.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_highlight.png b/platform/android/res/drawable-mdpi/ic_highlight.png deleted file mode 100644 index 2a8fe4db..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_highlight.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_link.png b/platform/android/res/drawable-mdpi/ic_link.png deleted file mode 100644 index 7f7ac170..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_link.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_list.png b/platform/android/res/drawable-mdpi/ic_list.png deleted file mode 100644 index e4f3164c..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_list.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_magnifying_glass.png b/platform/android/res/drawable-mdpi/ic_magnifying_glass.png deleted file mode 100644 index 389cebd5..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_magnifying_glass.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_more.png b/platform/android/res/drawable-mdpi/ic_more.png deleted file mode 100644 index 2b662ab3..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_more.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_pen.png b/platform/android/res/drawable-mdpi/ic_pen.png deleted file mode 100644 index db805373..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_pen.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_print.png b/platform/android/res/drawable-mdpi/ic_print.png deleted file mode 100644 index 58105463..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_print.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_proof.png b/platform/android/res/drawable-mdpi/ic_proof.png deleted file mode 100644 index cbda8721..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_proof.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_reflow.png b/platform/android/res/drawable-mdpi/ic_reflow.png deleted file mode 100644 index 84bd5418..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_reflow.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_select.png b/platform/android/res/drawable-mdpi/ic_select.png deleted file mode 100644 index 9eaf6924..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_select.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_sep.png b/platform/android/res/drawable-mdpi/ic_sep.png deleted file mode 100644 index 2167be2e..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_sep.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_share.png b/platform/android/res/drawable-mdpi/ic_share.png deleted file mode 100644 index cae51b69..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_share.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_strike.png b/platform/android/res/drawable-mdpi/ic_strike.png deleted file mode 100644 index b15e9324..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_strike.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_trash.png b/platform/android/res/drawable-mdpi/ic_trash.png deleted file mode 100644 index 3006fec3..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_trash.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/ic_underline.png b/platform/android/res/drawable-mdpi/ic_underline.png deleted file mode 100644 index 5d4dd5a4..00000000 Binary files a/platform/android/res/drawable-mdpi/ic_underline.png and /dev/null differ diff --git a/platform/android/res/drawable-mdpi/icon.png b/platform/android/res/drawable-mdpi/icon.png deleted file mode 100644 index e05de27c..00000000 Binary files a/platform/android/res/drawable-mdpi/icon.png and /dev/null differ diff --git a/platform/android/res/drawable-xhdpi/icon.png b/platform/android/res/drawable-xhdpi/icon.png deleted file mode 100644 index 0995b78e..00000000 Binary files a/platform/android/res/drawable-xhdpi/icon.png and /dev/null differ diff --git a/platform/android/res/drawable/busy.xml b/platform/android/res/drawable/busy.xml deleted file mode 100644 index f7f42a44..00000000 --- a/platform/android/res/drawable/busy.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/platform/android/res/drawable/button.xml b/platform/android/res/drawable/button.xml deleted file mode 100644 index 0a9bcd51..00000000 --- a/platform/android/res/drawable/button.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/res/drawable/darkdenim3.png b/platform/android/res/drawable/darkdenim3.png deleted file mode 100644 index be532f6d..00000000 Binary files a/platform/android/res/drawable/darkdenim3.png and /dev/null differ diff --git a/platform/android/res/drawable/page_num.xml b/platform/android/res/drawable/page_num.xml deleted file mode 100644 index 8d50df85..00000000 --- a/platform/android/res/drawable/page_num.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/platform/android/res/drawable/search.xml b/platform/android/res/drawable/search.xml deleted file mode 100644 index 4fc58830..00000000 --- a/platform/android/res/drawable/search.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/res/drawable/seek_progress.xml b/platform/android/res/drawable/seek_progress.xml deleted file mode 100644 index 328139c2..00000000 --- a/platform/android/res/drawable/seek_progress.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/platform/android/res/drawable/seek_thumb.xml b/platform/android/res/drawable/seek_thumb.xml deleted file mode 100644 index e3a9bad4..00000000 --- a/platform/android/res/drawable/seek_thumb.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/platform/android/res/drawable/tiled_background.xml b/platform/android/res/drawable/tiled_background.xml deleted file mode 100644 index 60e08f3c..00000000 --- a/platform/android/res/drawable/tiled_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/platform/android/res/layout/buttons.xml b/platform/android/res/layout/buttons.xml deleted file mode 100644 index 6c1620ba..00000000 --- a/platform/android/res/layout/buttons.xml +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/android/res/layout/main.xml b/platform/android/res/layout/main.xml deleted file mode 100644 index 50b4746b..00000000 --- a/platform/android/res/layout/main.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/platform/android/res/layout/outline_entry.xml b/platform/android/res/layout/outline_entry.xml deleted file mode 100644 index ea7912e4..00000000 --- a/platform/android/res/layout/outline_entry.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - diff --git a/platform/android/res/layout/picker_entry.xml b/platform/android/res/layout/picker_entry.xml deleted file mode 100644 index 673a4724..00000000 --- a/platform/android/res/layout/picker_entry.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - diff --git a/platform/android/res/layout/print_dialog.xml b/platform/android/res/layout/print_dialog.xml deleted file mode 100644 index 1d54d22f..00000000 --- a/platform/android/res/layout/print_dialog.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/platform/android/res/layout/textentry.xml b/platform/android/res/layout/textentry.xml deleted file mode 100644 index 08823df8..00000000 --- a/platform/android/res/layout/textentry.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/platform/android/res/values-ar/strings.xml b/platform/android/res/values-ar/strings.xml deleted file mode 100644 index f16d5ba9..00000000 --- a/platform/android/res/values-ar/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - قبول - MuPDF - إلغاء - تعذر فتح المخزن المؤقت - تعذر فتح المستند - تعذر فتح المستند: %1$s - تعذر فتح الملف: %1$s - اختر قيمة - تم النسخ إلى الحافظة - نسخ - نسخ النص - نسخ النص إلى الحافظة - حذف - تجاهل - يحتوي المستند على تغييرات. هل تريد حفظها؟ - سحب تعليق توضيحي - تعديل التعليقات التوضيحية - أدخل كلمة المرور - دخول إلى وضع إعادة التدفق - تعبئة حقل النص - التنسيق غير مدعوم حاليًا - تظليل - حبر - خروج من وضع إعادة التدفق - المزيد - لا - لم يتم العثور على متكررات أخرى - مشاركة وسائط التخزين مع حاسوب شخصي قد يمنع الوصول إليها - وسائط التخزين غير موجودة - لم يتم تحديد نص - غير مدعوم - لا يوجد شيء لحفظه - موافق - جدول المحتويات - [أعلى مستوى واحد] - %1$s %2$s: %3$s - طباعة - فشلت الطباعة - حفظ - بحث - بحث إلى الخلف - بحث في المستند - بحث إلى الأمام - جاري البحث في&#8230; - تحديد - تحديد النص - شطب - لم يتم العثور على النص - تظليل وتمكين الروابط - تسطير - نعم - diff --git a/platform/android/res/values-ca/strings.xml b/platform/android/res/values-ca/strings.xml deleted file mode 100644 index ef72886e..00000000 --- a/platform/android/res/values-ca/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Acceptar - MuPDF - Cancel·lar - No es pot obrir el buffer - No es pot obrir el document - No es pot obrir el document: %1$s - No es pot obrir l\'arxiu: %1$s - Tria el valor - Copiat al portapapers - Copiar - copiar text - Copiar text al portapapers - Esborrar - Descartar - El document té canvis. Desar? - Dibuixar anotació - Editar anotacions - Introduir contrasenya - Entrant en modo de reflux - Emplena el camp de text - Format no suportat actualment - Destacar - Tinta - Abandonant modo de reflux - Més - No - No hi ha més coincidències - Compartir el mitjà d\'emmagatzematge amb un PC pot fer que sigui inaccessible - Mitjà d\'emmagatzematge no present - No s\'ha seleccionat text - No compatible - No hi ha gens que guardar - Acceptar - Índex - [Pujar un nivell] - %1$s %2$s: %3$s - Imprimir - Fallada al imprimir - Desar - Buscar - Buscar cap a enrere - Buscar document - Buscar cap a davant - Buscant… - Seleccionar - Seleccionar text - Ratllat - Text no trobat - Ressaltar i habilitar enllaços - Subratllat - - diff --git a/platform/android/res/values-cs/strings.xml b/platform/android/res/values-cs/strings.xml deleted file mode 100644 index 6c870391..00000000 --- a/platform/android/res/values-cs/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Přijmout - MuPDF - Zrušit - Nelze otevřít vyrovnávací paměť - Nelze otevřít dokument - Nelze otevřít dokument: %1$s - Nelze otevřít soubor: %1$s - Zvolte hodnotu - Kopírováno do schránky - Kopírovat - kopírovat text - Kopírovat text do schránky - Smazat - Odmítnout - Dokument byl změněn. Uložit? - Vložit anotaci - Upravit anotace - Zadat heslo - Vstup do režimu přeformátování řádků - Vyplnit textové pole - Formát aktuálně nepodporován - Zvýraznit - Inkoust - Odchod z režimu přeformátování řádků - Více - Ne - Nenalezeny další výskyty - Při sdílení s PC může být paměťové médium nedostupné - Paměťové médim nenalezeno - Nevybrán žádný text - Nepodporováno - Nic k uložení - OK - Obsah - [Nahoru o jednu úroveň] - %1$s %2$s: %3$s - Tisk - Tisk selhal - Uložit - Hledat - Hledat zpět - Prohledat dokument - Hledat vpřed - Hledání&#8230; - Vybrat - Vybrat text - Přeškrtnout - Text nenalezen - Zvýraznit a aktivovat odkazy - Podtrhnout - Ano - diff --git a/platform/android/res/values-da/strings.xml b/platform/android/res/values-da/strings.xml deleted file mode 100644 index b7de1fdc..00000000 --- a/platform/android/res/values-da/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Accepter - MuPDF - Annuller - Buffer kan ikke åbnes - Dokument kan ikke åbnes - Kan ikke åbne dokumentet: %1$s - Kan ikke åbne filen: %1$s - Vælg værdi - Kopieret til udklipsholder - Kopier - kopier tekst - kopier tekst til udklipsholder - Slet - Afvis - Dokumentet er ændret. Gem ændringer? - Lav anmærkning - Rediger anmærkninger - Indtast adgangskode - Går over til konverteringstilstand - Udfyld tekstfelt - Format ikke understøttet i øjeblikket - Fremhæv - Ink - Forlader konverteringstilstand - Mere - Nej - Der blev ikke fundet flere tilfælde - Deles lagermediet med en PC, kan det gøre det utilgængeligt - Lagermedie ikke fundet - Ingen tekst valgt - Ikke understøttet - Intet at gemme - Okay - Indholdsfortegnelse - [Et niveau op] - %1$s %2$s: %3$s - Udskriv - Udskrivning mislykket - Gem - Søg - Søg bagud - Søg i dokument - Søg fremad - Søger&#8230; - Vælg - Vælg tekst - Gennemstreget - Tekst ikke fundet - Fremhæv og aktiver links - Understreg - Ja - diff --git a/platform/android/res/values-de/strings.xml b/platform/android/res/values-de/strings.xml deleted file mode 100644 index 2e69d369..00000000 --- a/platform/android/res/values-de/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Akzeptieren - MuPDF - Abbrechen - Zwischenspeicher kann nicht geöffnet werden - Dokument kann nicht geöffnet werden - Dokument kann nicht geöffnet werden: %1$s - Datei kann nicht geöffnet werden: %1$s - Wert auswählen - In die Zwischenanlage kopiert - Kopieren - Text kopieren - Text in Zwischenablage kopieren - Entfernen - Verwerfen - Das Dokument wurde verändert. Sollen die Änderungen gespeichert werden? - Kommentar einfügen - Kommentar bearbeiten - Passwort eingeben - Anpassungsmodus wird gestartet - Textfeld ausfüllen - Format wird momentan nicht unterstützt - Markieren - Farbe - Anpassungsmodus wird beendet - Mehr - Nein - Keine weiteren Treffer - Die Freigabe des Speichermediums für einen PC kann es unzugänglich machen - Speichermedium nicht vorhanden - Kein Text ausgewählt - Nicht unterstützt - Nichts zum Speichern - OK - Inhaltsverzeichnis - [Eine Ebene nach oben] - %1$s %2$s: %3$s - Drucken - Fehler beim Drucken - Speichern - Suchen - Rückwärts suchen - Dokument durchsuchen - Vorwärts suchen - Suche… - Auswählen - Text auswählen - Durchstreichen - Text konnte nicht gefunden werden - Markiere und aktiviere Verknüpfungen - Unterstreichen - Ja - diff --git a/platform/android/res/values-el/strings.xml b/platform/android/res/values-el/strings.xml deleted file mode 100644 index f994f287..00000000 --- a/platform/android/res/values-el/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Αποδοχή - MuPDF - Ακύρωση - Αδυναμία ανοίγματος buffer - Αδυναμία ανοίγματος εγγράφου - Αδυναμία ανοίγματος εγγράφου: %1$s - Αδυναμία ανοίγματος αρχείου: %1$s - Επιλογή τιμής - Αντιγράφηκε στο πρόχειρο - Αντιγραφή - αντιγραφή κειμένου - Αντιγραφή κειμένου στο πρόχειρο - Διαγραφή - Ματαίωση - Το έγγραφο έχει αλλαγές. Να αποθηκευτούν; - Σχεδίαση σχολίου - Επεξεργασία σχολίων - Πληκτρολογήστε κωδικό πρόσβασης - Είσοδος σε λειτουργία δυναμικής προσαρμογής - Συμπλήρωση πεδίου κειμένου - Αυτή η μορφή δεν υποστηρίζεται τη δεδομένη στιγμή - Επισήμανση - Γραφή - Έξοδος από λειτουργία δυναμικής προσαρμογής - Περισσότερο - Όχι - Δεν βρέθηκαν άλλες εμφανίσεις - Η κοινή χρήση του αποθηκευτικού μέσου με έναν υπολογιστή μπορεί να το καταστήσει μη προσβάσιμο - Δεν υπάρχει αποθηκευτικό μέσο - Δεν έχει επιλεγεί κείμενο - Δεν υποστηρίζεται - Δεν υπάρχει περιεχόμενο για αποθήκευση - ΟΚ - Πίνακας περιεχομένων - [Ένα επίπεδο επάνω] - %1$s %2$s: %3$s - Εκτύπωση - Η εκτύπωση απέτυχε - Αποθήκευση - Αναζήτηση - Αναζήτηση προς τα πίσω - Αναζήτηση εγγράφου - Αναζήτηση προς τα μπροστά - Αναζήτηση&#8230; - Επιλογή - Επιλογή κειμένου - Διακριτή διαγραφή - Δεν βρέθηκε το κείμενο - Επισήμανση και ενεργοποίηση συνδέσεων - Υπογράμμιση - Ναι - diff --git a/platform/android/res/values-es/strings.xml b/platform/android/res/values-es/strings.xml deleted file mode 100644 index 0e28a909..00000000 --- a/platform/android/res/values-es/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Aceptar - MuPDF - Cancelar - No se puede abrir el búfer - No se puede abrir el documento - No se puede abrir el documento:%1$s - No se puede abrir el archivo: %1$s - Elegir valor - Copiado al portapapeles - Copiar - copiar texto - Copiar texto al portapapeles - Borrar - Ignorar - El documento tiene cambios. ¿Guardar? - Dibujar anotación - Editar anotaicones - Introducir contraseña - Entrando en el modo de redistribución - Rellenar el campo de texto - Formato actualmente no soportado - Resaltar - Tinta - Saliendo del modo de redistribución - Más - No - No se han encontrado más casos - Compartir el medio de almacenamiento con un PC puede hacerlo inaccesible - Medio de almacenimiento no presente - Texto no seleccionado - No aceptado - Nada que guardar - OK - Tabla de contenidos - [Subir un nivel] - %1$s %2$s: %3$s - Imprimir - No se ha imprimido - Guardar - Buscar - Buscar hacia atrás - Buscar documento - Buscar hacia adelante - Buscando&#8230; - Seleccionar - Seleccionar texto - Tachar - Texto no encontrado - Resaltar y activar - Subrayar - - diff --git a/platform/android/res/values-et/strings.xml b/platform/android/res/values-et/strings.xml deleted file mode 100644 index fddd25a8..00000000 --- a/platform/android/res/values-et/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Nõustu - MuPDF - Tühista - Ei saa avada puhvrit - Ei saa avada dokumenti - Ei saa avada dokumenti: %1$s - Ei saa avada faili: %1$s - Vali väärtus - Kopeeritud lõikelauale - Kopeeri - kopeeri tekst - Kopeeri tekst lõikelauale - Kustuta - Lõpeta - Dokumendis on tehtud muudatusi. Kas salvestada need? - Tee marginaal - Redigeeri marginaale - Sisesta salasõna - Sisenen ümberpaigutamise režiimi - Täida tekstiväli - Vormingul puudub hetkel tugi - Tõsta esile - Tint - Lahkun ümberpaigutamise režiimist - Veel - Ei - Ei leitud rohkem juhtumeid - Salvestuskandja jagamine arvutiga võib selle juurdepääsmatuks muuta - Salvestuskandja puudub - Teksti ei ole valitud - Puudub tugi - Ei ole midagi salvestada - OK - Sisukord - [Taseme võrra üles] - %1$s%2$s%3$s - Prindi - Printimine ebaõnnestus - Salvesta - Otsi - Otsi tagasisuunas - Otsi dokumendist - Otsi edasisuunas - Otsin&#8230; - Vali - Vali tekst - Läbikriipsutus - Teksti ei leitud - Tõsta lingid esile ja luba need - Jooni alla - Jah - diff --git a/platform/android/res/values-fi/strings.xml b/platform/android/res/values-fi/strings.xml deleted file mode 100644 index ae13e724..00000000 --- a/platform/android/res/values-fi/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Hyväksy - MuPDF - Peruuta - Puskuria ei voi avata - Tiedostoa ei voi avata - Ei voi avata tiedostoa: %1$s - Ei voi avata tiedostoa: %1$s - Valitse arvo - Kopioitu leikepöydälle - Kopioi - kopio teksti - Kopioi teksti leikepöydälle - Poista - Hylkää - Tiedostossa on muutoksia. Haluatko tallentaa muutokset? - Piirrä huomautus - Muokkaa huomautuksia - Anna salasana - Siirrytään takaisinmuuntotilaan - Täytä tekstikenttä - Muotoa ei tällä hetkellä tueta - Korosta - Muste - Poistutaan takaisinmuuntotilasta - Lisää - Ei - Muita esiintymiä ei löydy - Tallennustietovälineen jakaminen tietokoneen kanssa voi estää sen käyttämisen - Tallennustietoväline ei ole käytössä - Ei valittua tekstiä - Ei tuettu - Ei mitään tallennettavaa - OK - Sisällys - [Yksi taso ylöspäin] - %1$s %2$s: %3$s - Tulosta - Tulostus ei onnistunut - Tallenna - Haku - Hae taaksepäin - Hae tiedostosta - Hae eteenpäin - Haetaan &#8230; - Valitse - Valitse teksti - Yliviivaa - Tekstiä ei löydy - Korosta ja ota käyttöön linkit - Alleviivaa - Kyllä - diff --git a/platform/android/res/values-fr/strings.xml b/platform/android/res/values-fr/strings.xml deleted file mode 100644 index 967707b9..00000000 --- a/platform/android/res/values-fr/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Accepter - MuPDF - Annuler - Impossible d\'ouvrir le buffer - Impossible d\'ouvrir le document - Impossible d\'ouvrir le document : %1$s - Impossible d\'ouvrir le fichier : %1$s - Choisir la valeur - Copié dans le presse-papier - Copier - copier le texte - Copier le texte sur le presse-papier - Supprimer - Ignorer - Des modifications ont été effectuées au document. Les sauvegarder ? - Dessiner une note - Éditer une note - Introduire le mot de passe - Entrer en mode refusion - Remplir le champ du texte - Format non compatible pour l\'instant - Surligner - Encre - Quitter le mode refusion - Plus - Non - Aucune occurrence trouvée - Sauvegarder le support de stockage avec un PC peut le rendre inaccessible - Support de stockage absent - Aucun texte sélectionné - Non compatible - Rien à sauvegarder - OK - Table des matières - [Niveau supérieur] - %1$s%2$s : %3$s - Imprimer - L\'impression a échoué - Sauvegarder - Rechercher - Rechercher en arrière - Rechercher document - Rechercher en avant - Chercher&#8230 ; - Sélectionner - Sélectionner le texte - Rayer - Texte introuvable - Surligner et autoriser les liens - Souligner - Oui - diff --git a/platform/android/res/values-hi/strings.xml b/platform/android/res/values-hi/strings.xml deleted file mode 100644 index 4d09a972..00000000 --- a/platform/android/res/values-hi/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - स्वीकार करें - MuPDF - रद्द करें - बफ़र खोल नहीं सके - दस्तावेज़ खोल नहीं सके - दस्तावेज़ नहीं खोल सके: %1$s - फ़ाइल खोल नहीं सके: %1$s - मान चुनें - क्लिपबोर्ड में कॉप कर दिया गया - कॉपी करें - पाठ कॉपी करें - पाठ को क्लिपबोर्ड में कॉपी करें - हटाएँ - खारिज करें - दस्तावेज़ में परिवर्तन हैं। उन्हें सहेजें? - एनोटेशन बनाएँ - एनोटेशनों को संपादित करें - पासवर्ड दर्ज करें - रीफ़्लो मोड में प्रवेश कर रहे हैं - पाठ फ़ील्ड को भरें - इस समय इस फ़ॉर्मेट को समर्थन नहीं प्राप्त है - हाइलाइट करें - स्याही - रीफ़्लो मोड को छोड़ रहे हैं - और भी - नहीं - यह और कहीं नहीं मिला - संग्रह माध्यम को पीसी के साथ साझा करने से उस तक पहुँचना मुश्किल हो सकता है - संग्रह माध्यम मौजूद नहीं है - कोई भी पाठ नहीं चुना गया है - असमर्थित - सहेजने के लिए कुछ नहीं है - ठीक है - विषय सूची - [एक स्तर ऊपर] - %1$s%2$s:%3$s - मुद्रित करें - मुद्रण विफल हुआ - सहेजें - खोजें - पीछे की ओर खोजें - दस्तावेज़ में खोजें - आगे की ओर खोजें - &#8230 को खोज रहे हैं; - चुनें - पाठ चुनें - काटें - पाठ नहीं मिला - लिंकों को हाइलाइट और सक्षम करें - रेखांकित करें - हाँ - diff --git a/platform/android/res/values-hu/strings.xml b/platform/android/res/values-hu/strings.xml deleted file mode 100644 index 1533b65a..00000000 --- a/platform/android/res/values-hu/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Elfogadás - MuPDF - Mégse - A puffert nem lehet megnyitni - A dokumentumot nem lehet megnyitni - A dokumentumot nem lehet megnyitni: %1$s - A fájlt nem lehet megnyitni: %1$s - Érték kiválasztása - A vágólapra másolva - Másolás - szöveg másolása - Szöveg másolása a vágólapra - Törlés - Bezárás - A dokumentum módosítva lett. Menti a változtatásokat? - Jegyzet rajzolása - Jegyzetek szerkesztése - Jelszó megadása - Belépés az újrarendezési módba - Szövegmező kitöltése - A formátum jelenleg nem támogatott - Kiemelés - Kézírás - Kilépés az újrarendezési módból - Több - Nem - Nincsenek további találatok - Az adathordozó a PC-vel való megosztás esetén elérhetetlenné válhat - Nincs jelen adathordozó - Nincs kijelölt szöveg - Nem támogatott - Nem kell semmit menteni - OK - Tartalomjegyzék - [Egy szinttel feljebb] - %1$s %2$s: %3$s - Nyomtatás - Nyomtatás sikertelen - Mentés - Keresés - Keresés visszafelé - Dokumentum keresése - Keresés előrefelé - Keresés:&#8230; - Kijelölés - Szöveg kijelölése - Áthúzás - Szöveg nem található - Kiemelés és linkek engedélyezése - Aláhúzás - Igen - diff --git a/platform/android/res/values-in/strings.xml b/platform/android/res/values-in/strings.xml deleted file mode 100644 index f90d1b3b..00000000 --- a/platform/android/res/values-in/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Terima - MuPDF - Batal - Tidak bisa membuka penyangga - Tidak bisa membuka dokumen - Tidak bisa membuka dokumen: %1$s - Tidak bisa membuka berkas: %1$s - Pilih nilai - Disalin ke papan klip - Salin - Salin teks - Salin teks ke papan klip - Hapus - Hilangkan - Dokumen telah berubah. Simpan perubahan? - Gambar anotasi - Sunting anotasi - Masukkan kata sandi - Masuk mode alir-ulang - Isi bidang teks - Format ini tidak didukung - Sorotan - Tinta - Tinggalkan mode alir-ulang - Selengkapnya - Tidak - Tidak ditemukan kejadian lain - Berbagi media penyimpanan dengan PC dapat membuatnya tidak bisa diakses - Media penyimpanan tidak ada - Tidak ada teks yang dipilih - Tidak didukung - Tidak ada yang disimpan - Oke - Daftar Isi - [Naik satu tingkat] - %1$s %2$s: %3$s - Cetak - Pencetakan gagal - Simpan - Cari - Cari mundur - Cari dokumen - Cari maju - Mencari… - Pilih - Pilih teks - Gagal - Teks tidak ditemukan - Sorot dan aktifkan tautan - Garis bawah - Ya - diff --git a/platform/android/res/values-it/strings.xml b/platform/android/res/values-it/strings.xml deleted file mode 100644 index 25cf56dd..00000000 --- a/platform/android/res/values-it/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Accetta - MuPDF - Annulla - Impossibile aprire buffering - Impossibile aprire documento - Impossibile aprire documento: %1$s - Impossibile aprire file: %1$s - Scegli valore - Copiato negli appunti - Copia - copia testo - Copia testo negli appunti - Elimina - Ignora - Il documento contiene modifiche. Salvare? - Disegna annotazione - Modifica annotazione - Inserisci password - Inserimento modalità di adattamento dinamico del contenuto - Riempi il campo di testo - Formato attualmente non supportato - Evidenzia - Inchiostro - Abbandono della modalità di adattamento dinamico del contenuto - Altro - No - Nessun\'altra occorrenza trovata - La condivisione del supporto di archiviazione con un PC può renderlo inaccessibile - Supporto di archiviazione non presente - Nessun testo selezionato - Non supportato - Niente da salvare - Ok - Sommario - [Su di un livello] - %1$s %2$s: %3$s - Stampa - Stampa non riuscita - Salva - Cerca - Cerca indietro - Cerca documento - Cerca avanti - Ricerca... - Seleziona - Seleziona testo - Barrato - Testo non trovato - Evidenzia e abilita link - Sottolinea - - diff --git a/platform/android/res/values-iw/strings.xml b/platform/android/res/values-iw/strings.xml deleted file mode 100644 index d259ae76..00000000 --- a/platform/android/res/values-iw/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - קבל - MuPDF - בטל - אין אפשרות לפתוח מאגר - אין אפשרות לפתוח מסמך - אין אפשרות לפתוח מסמך: %1$s - אין אפשרות לפתוח קובץ: %1$s - בחר ערך - הועתק ללוח - העתק - העתק טקסט - העתק טקסט ללוח - מחק - התעלם - קיימים שינויים במסמך. לשמור אותם? - רשום ביאור - ערוך ביאורים - הזן סיסמה - כניסה למצב הזרמה מחדש - מלא את שדה הטקסט - תבנית לא נתמכת כעת - הבלטה - דיו - יציאה ממצב הזרמה מחדש - עוד - לא - לא עוד - שיתוף מדיית האחסון עם מחשב עשויה להפוך אותה לבלתי נגישה - מדיית אחסון לא קיימת - לא נבחר טקסט - לא נתמך - אין מה לשמור - בסדר - תוכן העניינים - [למעלה ברמה אחת] - %1$s %2$s: %3$s - הדפס - ההדפסה נכשלה - שמור - חפש - חפש אחורה - חפש במסמך - חפש קדימה - מחפש&#8230; - בחר ערך - בחר טקסט - הדגש - לא נמצא טקסט - הבלט ואפשר קישורים - קו תחתון - כן - diff --git a/platform/android/res/values-ja/strings.xml b/platform/android/res/values-ja/strings.xml deleted file mode 100644 index 8ceb5e09..00000000 --- a/platform/android/res/values-ja/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 承諾する - MuPDF - キャンセル - バッファーを開けません - ドキュメントを開けません - 次のドキュメントを開けません:%1$s - 次のファイルを開けません: %1$s - バリューを選択してください - クリップボードにコピーされました - コピー - テキストをコピー - テキストをクリップボードにコピー - 削除 - 却下 - ドキュメントは変更されました。保存しますか? - 注釈を挿入する - 注釈を編集する - パスワードを入力する - リフローモードを開始する - テキストフィールドに書き込む - このフォーマットは現在サポートされていません - ハイライト - インク - リフローモードを終了する - もっと - いいえ - 他にオカレンスは見つかりませんでした - 記憶媒体をPCとシェアするとアクセスできなくなる可能性があります - 記憶媒体が見つかりません - テキストが選択されていません - サポートされていません - 保存するものがありません - 了解 - 目次 - [一つ上位のレベル] - %1$s %2$s: %3$s - 印刷 - 印刷に失敗しました - 保存 - 検索 - 逆方向検索 - ドキュメントを検索する - 順方向検索 - 検索中 - 選択 - テキストを選択する - 取り消し線を引く - テキストが見つかりません - ハイライトしてリンクを有効にする - 下線を引く - はい - diff --git a/platform/android/res/values-ko/strings.xml b/platform/android/res/values-ko/strings.xml deleted file mode 100644 index b52a2f5a..00000000 --- a/platform/android/res/values-ko/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 수락 - MuPDF - 취소 - 버퍼 열 수 없음 - 문서 열 수 없음 - 문서 열 수 없음: %1$s - 파일 열 수 없음: %1$s - 값 선택 - 클립보드로 복사됨 - 복사 - 텍스트 복사 - 클립보드로 텍스트 복사 - 삭제 - 무시 - 문서에 변경사항이 있습니다. 저장? - 주석달기 - 주석 편집 - 패스워드 입력 - 리플로우 모드 시작 - 텍스트 입력란에 기입하십시오. - 현재 지원되지 않는 포맷 - 주요기능 - 잉크 - 리플로우 모드 해제 - 기타 - 아니오 - 발견된 추가 발생 없음 - PC와 스토리지 미디어를 공유하면 액세스할 수 없습니다. - 스토리지 미디어 없음 - 선택된 텍스트 없음 - 지원 안됨 - 저장 대상 없음 - 확인 - 목차 - [레벨 한 단계 상승] - %1$s %2$s: %3$s - 인쇄 - 인쇄 실패 - 저장 - 검색 - 뒤로 검색 - 문서 검색 - 앞으로 검색 - 검색 중&#8230; - 선택 - 텍스트 선택 - 삭제 - 발견된 텍스트 없음 - 하이라이트 및 링크 활성화 - 밑줄 - - diff --git a/platform/android/res/values-lt/strings.xml b/platform/android/res/values-lt/strings.xml deleted file mode 100644 index f66ba305..00000000 --- a/platform/android/res/values-lt/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Priimti - „MuPDF“ - Atšaukti - Nepavyksta atverti buferinės atmintinės - Nepavyksta atverti dokumento - Nepavyksta atverti dokumento: %1$s - Nepavyksta atverti failo: %1$s - Pasirinkti vertę - Nukopijuota į iškarpinę - Kopijuoti - kopijuoti tekstą - Kopijuoti tekstą į iškarpinę - Naikinti - Atmesti - Dokumente yra pakeitimų. Ar juos įrašyti? - Braižyti anotaciją - Redaguoti anotacijas - Įvesti slaptažodį - Pereinama į pertvarkymo režimą - Užpildyti teksto lauką - Formatas šiuo metu nedera - Pažymėti - Rašalas - Išeinama iš pertvarkymo režimo - Daugiau - Ne - Daugiau įrašų nerasta - Pabendrinus laikmeną su kompiuteriu, ji gali tapti nebepasiekiama - Laikmenos nėra - Neparinktas tekstas - Nedera - Nėra ką įrašyti - Gerai - Turinys - [Vienu lygiu aukštyn] - %1$s %2$s: %3$s - Spausdinti - Išspausdinti nepavyko - Įrašyti - Ieškoti - Ieškoti atgal - Ieškoti dokumente - Ieškoti pirmyn - Ieškoma&#8230; - Pasirinkti - Pasirinkti tekstą - Išbraukti - Teksto nerasta - Pažymėti ir įjungti nuorodas - Pabraukti - Taip - diff --git a/platform/android/res/values-ms/strings.xml b/platform/android/res/values-ms/strings.xml deleted file mode 100644 index 64541e6f..00000000 --- a/platform/android/res/values-ms/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Terima - MuPDF - Batal - Tidak boleh membuka penimbal - Tidak boleh membuka dokumen - Tidak boleh membuka dokumen: %1$s - Tidak boleh membuka fail: %1$s - Pilih nilai - Disalin ke papan klip - Salin - salin teks - Salin teks ke papan klip - Padam - Singkir - Dokumen mempunyai perubahan. Simpankannya? - Lakarkan catatan - Suntingkan catatan - Masukkan kata laluan - Memasuki mod penyusunan semula - Mengisi medan teks - Format buat masa ini tidak disokong - Serlahkan - Dakwat - Meninggalkan mod penyusunan semula - Lagi - Tidak - Tiada kejadian lanjut ditemui - Berkongsi media storan dengan PC boleh menjadikannya tidak dapat dicapai - Media storan tidak wujud - Tiada teks dipilih - Tidak disokong - Tiada apa untuk disimpan - Okey - Jadual Kandungan - [Naik satu tahap] - %1$s %2$s: %3$s - Cetak - Gagal dicetak - Simpan - Carian - Carian ke belakang - Carian dokumen - Carian ke depan - Mencari&#8230; - Pilih - Pilih teks - Mansuhkan - Teks tidak ditemui - Serlahkan dan dayakan pautan - Gariskan - Ya - diff --git a/platform/android/res/values-nl/strings.xml b/platform/android/res/values-nl/strings.xml deleted file mode 100644 index 21945c86..00000000 --- a/platform/android/res/values-nl/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Accepteren - MuPDF - Annuleren - Buffer kan niet geopend worden - Document kan niet geopend worden - Document kan niet geopend worden: %1$s - Bestand kan niet geopend worden : %1$s - Kies waarde - Gekopieerd naar klembord - Kopiëren - tekst kopiëren - Tekst kopiëren naar klembord - Verwijderen - Afwijzen - Het document is gewijzigd. Opslaan? - Opmerking tekenen - Opmerkingen bewerken - Voer wachtwoord in - Conversiemodus wordt geopend - Vul het tekstveld in - Formaat wordt momenteel niet ondersteund - Markeren - Inkten - Conversiemodus wordt beëindigd - Meer - Nee - Geen andere resultaten gevonden - Het opslagmedium kan ontoegankelijk worden als het met een pc wordt gedeeld - Geen opslagmedium aanwezig - Geen tekst geselecteerd - Niet ondersteund - Niets om op te slaan - Oké - Inhoudsopgave - [Een niveau hoger] - %1$s %2$s: %3$s - Afdrukken - Afdrukken mislukt - Opslaan - Zoeken - Achterstevoren zoeken - Document doorzoeken - Vooruit zoeken - Aan het zoeken … - Selecteren - Tekst selecteren - Doorhalen - Tekst niet gevonden - Markeren en koppelingen inschakelen - Onderstrepen - Ja - diff --git a/platform/android/res/values-no/strings.xml b/platform/android/res/values-no/strings.xml deleted file mode 100644 index 31bd6bf0..00000000 --- a/platform/android/res/values-no/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Aksepter - MuPDF - Avbryt - Kan ikke åpne buffer - Kan ikke åpne dukumentet - Kan ikke åpne dokumentet: %1$s - Kan ikke åpne filen: %1$s - Velg verdi - Kopiert til utklippstavlen - Kopier - kopier tekst - Kopier teksten til utklippstavlen - Slett - Avvis - Det er endringer i dokumentet. Lagre dem? - Lag merknad - Rediger merknader - Skriv inn passord - Bytter til konverteringsmodus - Fyll ut tekstfeltet - Formatet er ikke støttet for øyeblikket - Uthev - Håndskrift - Går ut av konverteringsmodus - Mer - Nei - Ingen flere hendelser funnet - Deling av lagringsmedia med en PC kan gjøre det utilgjengelig - Lagringsmedia ikke til stede - Ingen tekst er valgt - Ikke støttet - Ingenting å lagre - Ok - Innholdsfortegnelse - [OPP ett nivå] - %1$s%2$s%3$s - Skriv ut - Kunne ikke skrive ut - Lagre - Søk - Søk bakover - Søk i dokument - Søk framover - Søker&#8230; - Velg - Valgt tekst - Gjennomstreking - Teksten ble ikke funnet - Uthev og aktiver koblinger - Understrek - Ja - diff --git a/platform/android/res/values-pl/strings.xml b/platform/android/res/values-pl/strings.xml deleted file mode 100644 index 42511e42..00000000 --- a/platform/android/res/values-pl/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Zaakceptuj - MuPDF - Anuluj - Nie można otworzyć bufora - Nie można otworzyć dokumentu - Nie można otworzyć dokumentu: %1$s - Nie można otworzyć pliku: %1$s - Wybierz wartość - Skopiowano do schowka - Kopiuj - kopiuj tekst - Kopiuj tekst do schowka - Usuń - Odrzuć - W dokumencie dokonano zmian. Czy chcesz je zapisać? - Sporządź notatkę - Edytuj notatki - Wprowadź hasło - Włączanie trybu zawijania tekstu - Wypełnij pole tekstowe - Format obecnie nieobsługiwany - Podświetl - Atrament - Wyłączanie trybu zawijania tekstu - Więcej - Nie - Nie znaleziono więcej wystąpień - Współdzielenie nośnika danych z komputerem PC może sprawić, że będzie niedostępny - Nośnik danych niedostępny - Nie wybrano tekstu - Nieobsługiwany - Nie ma nic do zapisania - OK - Spis treści - [W górę o jeden poziom] - %1$s %2$s: %3$s - Drukuj - Drukowanie nieudane - Zapisz - Szukaj - Szukaj z tyłu - Szukaj w dokumencie - Szukaj z przodu - Wyszukiwanie&#8230; - Wybierz - Wybierz tekst - Przekreślenie - Nie znaleziono tekstu - Podświetl i aktywuj linki - Podkreślenie - Tak - diff --git a/platform/android/res/values-pt/strings.xml b/platform/android/res/values-pt/strings.xml deleted file mode 100644 index 15f86283..00000000 --- a/platform/android/res/values-pt/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Aceitar - MuPDF - Cancelar - Não é possível abrir a memória intermédia - Não é possível abrir o documento - Não é possível abrir o documento: %1$s - Não é possível abrir o ficheiro: %1$s - Escolha um valor - Copiado para a área de transferência - Copiar - copiar o texto - Copiar o texto para a área de transferência - Eliminar - Desistir - Há alterações ao documento. Deseja guardá-las? - Adicionar anotação - Editar anotações - Escrever a palavra-passe - A entrar no modo de refluxo - Preencher o campo de texto - Esse formato não é atualmente suportado - Destacar - Tinta - A sair do modo de refluxo - Mais - Não - Não foram encontradas mais ocorrências - Partilhar o dispositivo de armazenamento com um PC poderá torná-lo inacessível - O dispositivo de armazenamento não está presente - Não há texto selecionado - Não suportado - Não há nada para guardar - Okay - Índice - [subir um nível] - %1$s%2$s: %3$s - Imprimir - Falha na Impressão - Guardar - Pesquisar - Pesquisar para trás - Pesquisar no documento - Pesquisar para a frente - A pesquisar&#8230; - Selecionar - Selecionar o texto - Rasurado - Texto não encontrado - Destacar e permitir links - Sublinhado - Sim - diff --git a/platform/android/res/values-ru/strings.xml b/platform/android/res/values-ru/strings.xml deleted file mode 100644 index 7cc35187..00000000 --- a/platform/android/res/values-ru/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Принять - MuPDF - Отмена - Невозможно открыть буфер - Невозможно открыть документ - Невозможно открыть документ: %1$s - Невозможно открыть файл: %1$s - Выберите значение - Скопировано в буфер - Копировать - копировать текст - Копировать текст в буфер - Удалить - Пропустить - Документ был изменен. Сохранить изменения? - Создать аннтоацию - Редактировать аннотации - Введите пароль - Переход в режим Reflow - Заполните текстовое поле - Формат не поддерживается - Выделить - Чернила - Выход из режима Reflow - Еще - Нет - Других ошибок не зафиксировано - Подключение компьютеров к хранилищу данных может привести к потере доступа к хранилищу - Хранилище данных отсутствует - Текст не выбран - Не поддерживается - Не выбраны файлы для сохранения - ОК - Содержание - [Вверх на один уровень] - %1$s %2$s: %3$s - Печать - Печать не выполнена - Сохранить - Поиск - Искать в предыдущей части документа - Искать в документе - Искать в остальной части документа - Поиск&#8230; - Выбор - Выбрать текст - Зачеркнуть - Текст не найден - Выделить и включить ссылки - Подчеркнуть - Да - diff --git a/platform/android/res/values-sk/strings.xml b/platform/android/res/values-sk/strings.xml deleted file mode 100644 index e11737ef..00000000 --- a/platform/android/res/values-sk/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Prijať - MuPDF - Zrušiť - Buffer sa nedá otvoriť - Dokument sa nedá otvoriť - Nedá sa otvoriť dokument: %1$s - Nedá sa otvoriť súbor: %1$s - Vyberte si hodnotu - Skopírované do vyrovnávacej pamäti - Kopírovať - kopírovať text - Kopírovať text do vyrovnávacej pamäti - Zmazať - Zrušiť - Dokument bol zmený. Uložiť zmeny? - Zostaviť anotáciu - Upraviť anotácie - Zadať heslo - Vstupujem do režimu opätovného nalievania - Vyplniť textové pole - Tento formát momentálne nepodporujem - Zvýrazniť - Atrament - Vystupujem z režimu opätovného nalievania - Viac - Nie - Viac príkladov sa nenašlo - Zdieľanie úložného média s PC môže znemožniť prístup - Nie je tu úložné médium - Žiadny text nie je vybraný - Nepodporované - Niet čo uložiť - Dobre - Obsah - [O úroveň vyššie] - %1$s %2$s: %3$s - Tlačiť - Tlačenie zlyhalo - Uložiť - Hľadať - Hľadať spätne - Hľadať v dokumente - Hľadať dopredu - Hľadám&#8230; - Vybrať - Vybrať text - Preškrtnúť - Text sa nenašiel - Zvýrazniť a zapnúť linky - Podčiarknúť - Áno - diff --git a/platform/android/res/values-sv/strings.xml b/platform/android/res/values-sv/strings.xml deleted file mode 100644 index 61d14d05..00000000 --- a/platform/android/res/values-sv/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Acceptera - MuPDF - Avbryt - Kan inte öppna buffer - Kan inte öppna dokument - Kan inte öppna dokument: %1$s - Kan inte öppna fil: %1$s - Välj värde - Kopierat till klippbordet - Kopiera - kopiera text - Kopiera text till klippbordet - Ta bort - Avfärda - Dokumentet har ändrats. Spara ändringar? - Rita annotation - Ändra annotation - Fyll i lösenord - Aktiverar reflow-läge - Fyll i textfält - Formatat stöds inte för närvarande - Markera - Bläck - Lämnar reflow-läge - Mer - Nej - Inga flera förekomster hittades - Att dela lagringsmediet med en PC kan göra den oåtkomlig - Lagringsmedia finns inte - Ingen text har valts - Stöds ej - Inget att spara - OK - Innehållsförteckning - [Upp en nivå] - %1$s %2$s: %3$s - Skriv ut - Utskrift misslyckades - Spara - Sök - Sök bakåt - Sök dokument - Sök framåt - Letar&#8230; - Välj - Välj text - Stryk - Text hittades ej - Markera och aktivera länkar - Understryk - Ja - diff --git a/platform/android/res/values-th/strings.xml b/platform/android/res/values-th/strings.xml deleted file mode 100644 index e6827125..00000000 --- a/platform/android/res/values-th/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - ยอมรับ - MuPDF - ยกเลิก - ไม่สามารถเปิดบัฟเฟอร์ - ไม่สามารถเปิดเอกสาร - ไม่สามารถเปิดเอกสาร: %1$s - ไม่สามารถเปิดไฟล์: %1$s - เลือกค่า - คัดลอกไปที่คลิปบอร์ดแล้ว - คัดลอก - คัดลอกข้อความ - คัดลอกข้อความไปที่คลิปบอร์ด - ลบ - เลิกใช้ - เอกสารมีการเปลี่ยนแปลง ต้องการบันทึกหรือไม่ - เขียนคำอธิบายประกอบ - แก้ไขคำอธิบายประกอบ - ป้อนรหัสผ่าน - เข้าสู่โหมดเรียงหน้ากระดาษใหม่ - เติมในช่องข้อความ - ไม่รองรับรูปแบบในขณะนี้ - ไฮไลท์ - หมึก - ออกจากโหมดเรียงหน้ากระดาษใหม่ - เพิ่มเติม - ไม่ - ไม่พบเหตุการณ์ที่เกิดขึ้นเพิ่มเติม - การแบ่งปันสื่อจัดเก็บข้อมูลกับพีซีสามารถทำให้สื่อจัดเก็บข้อมูลไม่สามารถเข้าถึงได้ - สื่อเก็บข้อมูลไม่ปรากฏ - ไม่มีข้อความที่เลือก - ไม่รองรับ - ไม่มีอะไรให้บันทึก - ตกลง - สารบัญ - [ขึ้นหนึ่งระดับ] - %1$s %2$s: %3$s - พิมพ์ - พิมพ์ล้มเหลว - บันทึก - ค้นหา - ค้นหาย้อนกลับ - ค้นหาเอกสาร - ค้นหาไปข้างหน้า - กำลังค้นหา&#8230; - เลือก - เลือกข้อความ - ขีดทับ - ไม่พบข้อความ - ไฮไลท์และเปิดใช้งานลิงก์ - ขีดเส้นใต้ - ใช่ - diff --git a/platform/android/res/values-tl/strings.xml b/platform/android/res/values-tl/strings.xml deleted file mode 100644 index 39611fcb..00000000 --- a/platform/android/res/values-tl/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Tanggapin - MuPDF - Kanselahin - Hindi mabuksan ang buffer - Hindi mabuksan ang dokumento - Hindi mabuksan ang dokumentong: %1$s - Hindi mabuksan ang file na: %1$s - Pumili ng halaga - Kinopya sa clipboard - Kopyahin - kopyahin ang teksto - Kopyahin ang teksto sa clipboard - Alisin - Umalis - May mga pagbabago sa dokumento. I-save ang mga ito? - Gumuhit ng anotasyon - I-edit ang mga anotasyon - Ilagay ang password - Pumapasok sa reflow mode - Punan ang puwang para sa teksto - Ang format ay kasalukuyang hindi gumagana dito - I-highlight - Lagdaan (Ink) - Umaalis sa reflow mode - Higit pa - Hindi - Walang nahanap na karagdagang paglitaw - Ang pagbabahagi ng storage media sa isang PC ay gagawin itong hindi magagamit - Walang storage media - Walang piniling teksto - Hindi gumagana dito - Walang ise-save - Okay - Talaan ng Nilalaman - [Umakyat ng isang antas] - %1$s %2$s: %3$s - I-print - Hindi nai-print - I-save - Maghanap - Maghanap pabalik - Maghanap sa dokumento - Maghanap nang pasulong - Hinahanap ang&#8230; - Piliin - Piliin ang teksto - Guhitan ang teksto (strike-out) - Hindi nahanap ang teksto - I-highlight at paganahin ang mga link - Guhitan - Oo - diff --git a/platform/android/res/values-tr/strings.xml b/platform/android/res/values-tr/strings.xml deleted file mode 100644 index c64ab7ce..00000000 --- a/platform/android/res/values-tr/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Kabul et - MuPDF - İptal et - Arabellek açılamıyor - Belge açılamıyor - Belge açılamıyor: %1$s - Dosya açılamıyor: %1$s - Değeri seç - Panoya kopyalandı - Kopyala - metni kopyala - Metni panoya kopyala - Sil - Bırak - Belgede değişiklikler var. Kaydetmek istiyor musunuz? - Ek açıklama çiz - Ek açıklamalar düzenle - Şifreyi gir - Yeniden akma moduna giriyor - Metin alanını doldurun - Bu format şu an için desteklenmiyor - Vurgula - Mürekkep - Yeniden akma modundan çıkılıyor - Daha fazla - Hayır - Daha fazla öğe bulunamadı - Depolama ortamının bilgisayar ile paylaşımı onu erişilmez yapabilir - Depolama ortamı bulunmuyor - Seçili metin bulunmuyor - Desteklenmiyor - Kaydedecek bir şey yok - Tamam - İçindekiler Tablosu - [Bir seviye üste çık] - %1$s %2$s: %3$s - Yazdır - Yazdırma başarısız oldu - Kaydet - Ara - Geriye doğru ara - Belge ara - İleriye doğru ara - Aranıyor&#8230; - Seç - Metin seç - Üstünü çiz - Metin bulunamadı - Bağlantıları vurgula ve etkinleştir - Altını çiz - Evet - diff --git a/platform/android/res/values-zh-rTW/strings.xml b/platform/android/res/values-zh-rTW/strings.xml deleted file mode 100644 index 4cd89709..00000000 --- a/platform/android/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 同意 - MuPDF - 取消 - 未能開啟緩衝 - 未能開啟文件 - 未能開啟文件: %1$s - 未能開啟檔案%1$s - 選擇數值 - 複製至剪貼簿 - 複製 - 複製文字 - 複製文字至剪貼簿 - 刪除 - 取消 - 你需要儲存已編輯的文件嗎? - 繪畫註釋 - 編輯註釋 - 輸入密碼 - 根據螢幕大小顯示 - 填寫文字欄 - 暫時不支援此格式 - 標示重點 - 墨水 - 不根據螢幕大小顯示 - 更多 - 沒有 - 沒有相符項目 - 未能與電腦分享存放裝置 - 沒有存放裝置 - 沒有選擇文字 - 不支援 - 沒有資料儲存 - 完成 - 目錄 - [升一級] - %1$s%2$s%3$s - 列印 - 列印失敗 - 儲存 - 搜尋 - 往後搜尋 - 搜尋文件 - 往前搜尋 - 搜尋中&#8230; - 選擇 - 選擇文字 - 刪除線 - 未能找到文字 - 標示及允許連結 - 在下面劃線 - - diff --git a/platform/android/res/values-zh/strings.xml b/platform/android/res/values-zh/strings.xml deleted file mode 100644 index 60fcbb82..00000000 --- a/platform/android/res/values-zh/strings.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - 接受 - MuPDF - 取消 - 无法打开缓冲器 - 无法打开文档 - 无法打开文档: %1$s - 无法打开文件:%1$s - 选择值 - 已复制到剪贴板 - 复制 - 复制文本 - 将文本复制到剪贴板 - 删除 - 解除 - 文档已变更,保存变更吗? - 作批注 - 编辑批注 - 输入密码 - 输入重排模式 - 填充文本字段 - 当前不支持此格式 - 高亮 - 墨迹 - 正在离开重排模式 - 更多 - - 未发现更多实例。 - 存储介质在设备和 PC 上共同使用,会导致该存储介质在设备上无法被访问 - 没有存储介质 - 未选择文本 - 不被支持 - 没有要保存的内容 - 确定 - 目录 - [向上一级] - %1$s%2$s:%3$s - 打印 - 未能打印 - 保存 - 搜索 - 向后搜索 - 搜索文档 - 向前搜索 - 正在搜索… - 选择 - 选择文本 - 删除线 - 未发现文本 - 高亮并启用墨迹 - 下划线 - - diff --git a/platform/android/res/values/colors.xml b/platform/android/res/values/colors.xml deleted file mode 100644 index ecd1519d..00000000 --- a/platform/android/res/values/colors.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - #404040 - #C0000000 - #C0202020 - #C0202020 - #00000000 - #FF2572AC - #FFFFFF - #FFFFFF - #000000 - #2572AC - #000000 - #2572AC - #FFFFFF - diff --git a/platform/android/res/values/strings.xml b/platform/android/res/values/strings.xml deleted file mode 100644 index 269b47ee..00000000 --- a/platform/android/res/values/strings.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - MuPDF - 1.8 (git build) - Storage media not present - Sharing the storage media with a PC can make it inaccessible - Cancel - Search backwards - Search forwards - Search document - %1$s %2$s: %3$s - Table of Contents - Enter password - Text not found - Searching… - Highlight and enable links - No further occurrences found - Select - Search - Copy - Strike-out - Delete - Highlight - Underline - Edit annotations - Ink - Save - Proof - Separation - Print - Dismiss - [Up one level] - Yes - No - Entering reflow mode - Leaving reflow mode - Print failed - Select text - Copied to clipboard - No text selected - Draw annotation - Nothing to save - Document has changes. Save them? - Cannot open document - Cannot open document: %1$s - Cannot open file: %1$s - Cannot open buffer - Fill out text field - Okay - Choose value - Not supported - Copy text to the clipboard - More - Accept - copy text - Format currently not supported - Toggle reflow mode - diff --git a/platform/android/res/values/styles.xml b/platform/android/res/values/styles.xml deleted file mode 100644 index ade851dd..00000000 --- a/platform/android/res/values/styles.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/platform/android/src/com/artifex/mupdf/fitz/AndroidDrawDevice.java b/platform/android/src/com/artifex/mupdf/fitz/AndroidDrawDevice.java deleted file mode 100644 index 4a8daaa7..00000000 --- a/platform/android/src/com/artifex/mupdf/fitz/AndroidDrawDevice.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.artifex.mupdf.fitz; - -import android.graphics.Bitmap; - -public final class AndroidDrawDevice extends NativeDevice -{ - // NOT static. - private native long newNative(Bitmap bitmap, int pageX0, int pageY0, int pageX1, int pageY1, int patchX0, int patchY0, int patchX1, int patchY1); - - // Construction - public AndroidDrawDevice(Bitmap bitmap, int pageX0, int pageY0, int pageX1, int pageY1, int patchX0, int patchY0, int patchX1, int patchY1) - { - super(0); - pointer = newNative(bitmap, pageX0, pageY0, pageX1, pageY1, patchX0, patchY0, patchX1, patchY1); - } - - public AndroidDrawDevice(Bitmap bitmap, RectI page, RectI patch) - { - super(0); - pointer = newNative(bitmap, page.x0, page.y0, page.x1, page.y1, patch.x0, patch.y0, patch.x1, patch.y1); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/Annotation.java b/platform/android/src/com/artifex/mupdfdemo/Annotation.java deleted file mode 100644 index cf915524..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/Annotation.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.graphics.RectF; - -public class Annotation extends RectF { - enum Type { - TEXT, LINK, FREETEXT, LINE, SQUARE, CIRCLE, POLYGON, POLYLINE, HIGHLIGHT, - UNDERLINE, SQUIGGLY, STRIKEOUT, STAMP, CARET, INK, POPUP, FILEATTACHMENT, - SOUND, MOVIE, WIDGET, SCREEN, PRINTERMARK, TRAPNET, WATERMARK, A3D, UNKNOWN - } - - public final Type type; - - public Annotation(float x0, float y0, float x1, float y1, int _type) { - super(x0, y0, x1, y1); - type = _type == -1 ? Type.UNKNOWN : Type.values()[_type]; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/ArrayDeque.java b/platform/android/src/com/artifex/mupdfdemo/ArrayDeque.java deleted file mode 100644 index 4f06ea41..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/ArrayDeque.java +++ /dev/null @@ -1,855 +0,0 @@ -/* - * Written by Josh Bloch of Google Inc. and released to the public domain, - * as explained at http://creativecommons.org/publicdomain/zero/1.0/. - */ - -package com.artifex.mupdfdemo; - -import java.util.AbstractCollection; -import java.util.Arrays; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Queue; -import java.util.Stack; - -// BEGIN android-note -// removed link to collections framework docs -// END android-note - -/** - * Resizable-array implementation of the {@link Deque} interface. Array - * deques have no capacity restrictions; they grow as necessary to support - * usage. They are not thread-safe; in the absence of external - * synchronization, they do not support concurrent access by multiple threads. - * Null elements are prohibited. This class is likely to be faster than - * {@link Stack} when used as a stack, and faster than {@link LinkedList} - * when used as a queue. - * - *

Most ArrayDeque operations run in amortized constant time. - * Exceptions include {@link #remove(Object) remove}, {@link - * #removeFirstOccurrence removeFirstOccurrence}, {@link #removeLastOccurrence - * removeLastOccurrence}, {@link #contains contains}, {@link #iterator - * iterator.remove()}, and the bulk operations, all of which run in linear - * time. - * - *

The iterators returned by this class's iterator method are - * fail-fast: If the deque is modified at any time after the iterator - * is created, in any way except through the iterator's own remove - * method, the iterator will generally throw a {@link - * ConcurrentModificationException}. Thus, in the face of concurrent - * modification, the iterator fails quickly and cleanly, rather than risking - * arbitrary, non-deterministic behavior at an undetermined time in the - * future. - * - *

Note that the fail-fast behavior of an iterator cannot be guaranteed - * as it is, generally speaking, impossible to make any hard guarantees in the - * presence of unsynchronized concurrent modification. Fail-fast iterators - * throw ConcurrentModificationException on a best-effort basis. - * Therefore, it would be wrong to write a program that depended on this - * exception for its correctness: the fail-fast behavior of iterators - * should be used only to detect bugs. - * - *

This class and its iterator implement all of the - * optional methods of the {@link Collection} and {@link - * Iterator} interfaces. - * - * @author Josh Bloch and Doug Lea - * @since 1.6 - * @param the type of elements held in this collection - */ -public class ArrayDeque extends AbstractCollection - implements Deque, Cloneable, java.io.Serializable -{ - /** - * The array in which the elements of the deque are stored. - * The capacity of the deque is the length of this array, which is - * always a power of two. The array is never allowed to become - * full, except transiently within an addX method where it is - * resized (see doubleCapacity) immediately upon becoming full, - * thus avoiding head and tail wrapping around to equal each - * other. We also guarantee that all array cells not holding - * deque elements are always null. - */ - private transient Object[] elements; - - /** - * The index of the element at the head of the deque (which is the - * element that would be removed by remove() or pop()); or an - * arbitrary number equal to tail if the deque is empty. - */ - private transient int head; - - /** - * The index at which the next element would be added to the tail - * of the deque (via addLast(E), add(E), or push(E)). - */ - private transient int tail; - - /** - * The minimum capacity that we'll use for a newly created deque. - * Must be a power of 2. - */ - private static final int MIN_INITIAL_CAPACITY = 8; - - // ****** Array allocation and resizing utilities ****** - - /** - * Allocate empty array to hold the given number of elements. - * - * @param numElements the number of elements to hold - */ - private void allocateElements(int numElements) { - int initialCapacity = MIN_INITIAL_CAPACITY; - // Find the best power of two to hold elements. - // Tests "<=" because arrays aren't kept full. - if (numElements >= initialCapacity) { - initialCapacity = numElements; - initialCapacity |= (initialCapacity >>> 1); - initialCapacity |= (initialCapacity >>> 2); - initialCapacity |= (initialCapacity >>> 4); - initialCapacity |= (initialCapacity >>> 8); - initialCapacity |= (initialCapacity >>> 16); - initialCapacity++; - - if (initialCapacity < 0) // Too many elements, must back off - initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements - } - elements = new Object[initialCapacity]; - } - - /** - * Double the capacity of this deque. Call only when full, i.e., - * when head and tail have wrapped around to become equal. - */ - private void doubleCapacity() { - // assert head == tail; - int p = head; - int n = elements.length; - int r = n - p; // number of elements to the right of p - int newCapacity = n << 1; - if (newCapacity < 0) - throw new IllegalStateException("Sorry, deque too big"); - Object[] a = new Object[newCapacity]; - System.arraycopy(elements, p, a, 0, r); - System.arraycopy(elements, 0, a, r, p); - elements = a; - head = 0; - tail = n; - } - - /** - * Copies the elements from our element array into the specified array, - * in order (from first to last element in the deque). It is assumed - * that the array is large enough to hold all elements in the deque. - * - * @return its argument - */ - private T[] copyElements(T[] a) { - if (head < tail) { - System.arraycopy(elements, head, a, 0, size()); - } else if (head > tail) { - int headPortionLen = elements.length - head; - System.arraycopy(elements, head, a, 0, headPortionLen); - System.arraycopy(elements, 0, a, headPortionLen, tail); - } - return a; - } - - /** - * Constructs an empty array deque with an initial capacity - * sufficient to hold 16 elements. - */ - public ArrayDeque() { - elements = new Object[16]; - } - - /** - * Constructs an empty array deque with an initial capacity - * sufficient to hold the specified number of elements. - * - * @param numElements lower bound on initial capacity of the deque - */ - public ArrayDeque(int numElements) { - allocateElements(numElements); - } - - /** - * Constructs a deque containing the elements of the specified - * collection, in the order they are returned by the collection's - * iterator. (The first element returned by the collection's - * iterator becomes the first element, or front of the - * deque.) - * - * @param c the collection whose elements are to be placed into the deque - * @throws NullPointerException if the specified collection is null - */ - public ArrayDeque(Collection c) { - allocateElements(c.size()); - addAll(c); - } - - // The main insertion and extraction methods are addFirst, - // addLast, pollFirst, pollLast. The other methods are defined in - // terms of these. - - /** - * Inserts the specified element at the front of this deque. - * - * @param e the element to add - * @throws NullPointerException if the specified element is null - */ - public void addFirst(E e) { - if (e == null) - throw new NullPointerException("e == null"); - elements[head = (head - 1) & (elements.length - 1)] = e; - if (head == tail) - doubleCapacity(); - } - - /** - * Inserts the specified element at the end of this deque. - * - *

This method is equivalent to {@link #add}. - * - * @param e the element to add - * @throws NullPointerException if the specified element is null - */ - public void addLast(E e) { - if (e == null) - throw new NullPointerException("e == null"); - elements[tail] = e; - if ( (tail = (tail + 1) & (elements.length - 1)) == head) - doubleCapacity(); - } - - /** - * Inserts the specified element at the front of this deque. - * - * @param e the element to add - * @return true (as specified by {@link Deque#offerFirst}) - * @throws NullPointerException if the specified element is null - */ - public boolean offerFirst(E e) { - addFirst(e); - return true; - } - - /** - * Inserts the specified element at the end of this deque. - * - * @param e the element to add - * @return true (as specified by {@link Deque#offerLast}) - * @throws NullPointerException if the specified element is null - */ - public boolean offerLast(E e) { - addLast(e); - return true; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E removeFirst() { - E x = pollFirst(); - if (x == null) - throw new NoSuchElementException(); - return x; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E removeLast() { - E x = pollLast(); - if (x == null) - throw new NoSuchElementException(); - return x; - } - - public E pollFirst() { - int h = head; - @SuppressWarnings("unchecked") E result = (E) elements[h]; - // Element is null if deque empty - if (result == null) - return null; - elements[h] = null; // Must null out slot - head = (h + 1) & (elements.length - 1); - return result; - } - - public E pollLast() { - int t = (tail - 1) & (elements.length - 1); - @SuppressWarnings("unchecked") E result = (E) elements[t]; - if (result == null) - return null; - elements[t] = null; - tail = t; - return result; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E getFirst() { - @SuppressWarnings("unchecked") E result = (E) elements[head]; - if (result == null) - throw new NoSuchElementException(); - return result; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E getLast() { - @SuppressWarnings("unchecked") - E result = (E) elements[(tail - 1) & (elements.length - 1)]; - if (result == null) - throw new NoSuchElementException(); - return result; - } - - public E peekFirst() { - @SuppressWarnings("unchecked") E result = (E) elements[head]; - // elements[head] is null if deque empty - return result; - } - - public E peekLast() { - @SuppressWarnings("unchecked") - E result = (E) elements[(tail - 1) & (elements.length - 1)]; - return result; - } - - /** - * Removes the first occurrence of the specified element in this - * deque (when traversing the deque from head to tail). - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element e such that - * o.equals(e) (if such an element exists). - * Returns true if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return true if the deque contained the specified element - */ - public boolean removeFirstOccurrence(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = head; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) { - delete(i); - return true; - } - i = (i + 1) & mask; - } - return false; - } - - /** - * Removes the last occurrence of the specified element in this - * deque (when traversing the deque from head to tail). - * If the deque does not contain the element, it is unchanged. - * More formally, removes the last element e such that - * o.equals(e) (if such an element exists). - * Returns true if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return true if the deque contained the specified element - */ - public boolean removeLastOccurrence(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = (tail - 1) & mask; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) { - delete(i); - return true; - } - i = (i - 1) & mask; - } - return false; - } - - // *** Queue methods *** - - /** - * Inserts the specified element at the end of this deque. - * - *

This method is equivalent to {@link #addLast}. - * - * @param e the element to add - * @return true (as specified by {@link Collection#add}) - * @throws NullPointerException if the specified element is null - */ - public boolean add(E e) { - addLast(e); - return true; - } - - /** - * Inserts the specified element at the end of this deque. - * - *

This method is equivalent to {@link #offerLast}. - * - * @param e the element to add - * @return true (as specified by {@link Queue#offer}) - * @throws NullPointerException if the specified element is null - */ - public boolean offer(E e) { - return offerLast(e); - } - - /** - * Retrieves and removes the head of the queue represented by this deque. - * - * This method differs from {@link #poll poll} only in that it throws an - * exception if this deque is empty. - * - *

This method is equivalent to {@link #removeFirst}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException {@inheritDoc} - */ - public E remove() { - return removeFirst(); - } - - /** - * Retrieves and removes the head of the queue represented by this deque - * (in other words, the first element of this deque), or returns - * null if this deque is empty. - * - *

This method is equivalent to {@link #pollFirst}. - * - * @return the head of the queue represented by this deque, or - * null if this deque is empty - */ - public E poll() { - return pollFirst(); - } - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque. This method differs from {@link #peek peek} only in - * that it throws an exception if this deque is empty. - * - *

This method is equivalent to {@link #getFirst}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException {@inheritDoc} - */ - public E element() { - return getFirst(); - } - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque, or returns null if this deque is empty. - * - *

This method is equivalent to {@link #peekFirst}. - * - * @return the head of the queue represented by this deque, or - * null if this deque is empty - */ - public E peek() { - return peekFirst(); - } - - // *** Stack methods *** - - /** - * Pushes an element onto the stack represented by this deque. In other - * words, inserts the element at the front of this deque. - * - *

This method is equivalent to {@link #addFirst}. - * - * @param e the element to push - * @throws NullPointerException if the specified element is null - */ - public void push(E e) { - addFirst(e); - } - - /** - * Pops an element from the stack represented by this deque. In other - * words, removes and returns the first element of this deque. - * - *

This method is equivalent to {@link #removeFirst()}. - * - * @return the element at the front of this deque (which is the top - * of the stack represented by this deque) - * @throws NoSuchElementException {@inheritDoc} - */ - public E pop() { - return removeFirst(); - } - - private void checkInvariants() { - // assert elements[tail] == null; - // assert head == tail ? elements[head] == null : - // (elements[head] != null && - // elements[(tail - 1) & (elements.length - 1)] != null); - // assert elements[(head - 1) & (elements.length - 1)] == null; - } - - /** - * Removes the element at the specified position in the elements array, - * adjusting head and tail as necessary. This can result in motion of - * elements backwards or forwards in the array. - * - *

This method is called delete rather than remove to emphasize - * that its semantics differ from those of {@link List#remove(int)}. - * - * @return true if elements moved backwards - */ - private boolean delete(int i) { - //checkInvariants(); - final Object[] elements = this.elements; - final int mask = elements.length - 1; - final int h = head; - final int t = tail; - final int front = (i - h) & mask; - final int back = (t - i) & mask; - - // Invariant: head <= i < tail mod circularity - if (front >= ((t - h) & mask)) - throw new ConcurrentModificationException(); - - // Optimize for least element motion - if (front < back) { - if (h <= i) { - System.arraycopy(elements, h, elements, h + 1, front); - } else { // Wrap around - System.arraycopy(elements, 0, elements, 1, i); - elements[0] = elements[mask]; - System.arraycopy(elements, h, elements, h + 1, mask - h); - } - elements[h] = null; - head = (h + 1) & mask; - return false; - } else { - if (i < t) { // Copy the null tail as well - System.arraycopy(elements, i + 1, elements, i, back); - tail = t - 1; - } else { // Wrap around - System.arraycopy(elements, i + 1, elements, i, mask - i); - elements[mask] = elements[0]; - System.arraycopy(elements, 1, elements, 0, t); - tail = (t - 1) & mask; - } - return true; - } - } - - // *** Collection Methods *** - - /** - * Returns the number of elements in this deque. - * - * @return the number of elements in this deque - */ - public int size() { - return (tail - head) & (elements.length - 1); - } - - /** - * Returns true if this deque contains no elements. - * - * @return true if this deque contains no elements - */ - public boolean isEmpty() { - return head == tail; - } - - /** - * Returns an iterator over the elements in this deque. The elements - * will be ordered from first (head) to last (tail). This is the same - * order that elements would be dequeued (via successive calls to - * {@link #remove} or popped (via successive calls to {@link #pop}). - * - * @return an iterator over the elements in this deque - */ - public Iterator iterator() { - return new DeqIterator(); - } - - public Iterator descendingIterator() { - return new DescendingIterator(); - } - - private class DeqIterator implements Iterator { - /** - * Index of element to be returned by subsequent call to next. - */ - private int cursor = head; - - /** - * Tail recorded at construction (also in remove), to stop - * iterator and also to check for comodification. - */ - private int fence = tail; - - /** - * Index of element returned by most recent call to next. - * Reset to -1 if element is deleted by a call to remove. - */ - private int lastRet = -1; - - public boolean hasNext() { - return cursor != fence; - } - - public E next() { - if (cursor == fence) - throw new NoSuchElementException(); - @SuppressWarnings("unchecked") E result = (E) elements[cursor]; - // This check doesn't catch all possible comodifications, - // but does catch the ones that corrupt traversal - if (tail != fence || result == null) - throw new ConcurrentModificationException(); - lastRet = cursor; - cursor = (cursor + 1) & (elements.length - 1); - return result; - } - - public void remove() { - if (lastRet < 0) - throw new IllegalStateException(); - if (delete(lastRet)) { // if left-shifted, undo increment in next() - cursor = (cursor - 1) & (elements.length - 1); - fence = tail; - } - lastRet = -1; - } - } - - private class DescendingIterator implements Iterator { - /* - * This class is nearly a mirror-image of DeqIterator, using - * tail instead of head for initial cursor, and head instead of - * tail for fence. - */ - private int cursor = tail; - private int fence = head; - private int lastRet = -1; - - public boolean hasNext() { - return cursor != fence; - } - - public E next() { - if (cursor == fence) - throw new NoSuchElementException(); - cursor = (cursor - 1) & (elements.length - 1); - @SuppressWarnings("unchecked") E result = (E) elements[cursor]; - if (head != fence || result == null) - throw new ConcurrentModificationException(); - lastRet = cursor; - return result; - } - - public void remove() { - if (lastRet < 0) - throw new IllegalStateException(); - if (!delete(lastRet)) { - cursor = (cursor + 1) & (elements.length - 1); - fence = head; - } - lastRet = -1; - } - } - - /** - * Returns true if this deque contains the specified element. - * More formally, returns true if and only if this deque contains - * at least one element e such that o.equals(e). - * - * @param o object to be checked for containment in this deque - * @return true if this deque contains the specified element - */ - public boolean contains(Object o) { - if (o == null) - return false; - int mask = elements.length - 1; - int i = head; - Object x; - while ( (x = elements[i]) != null) { - if (o.equals(x)) - return true; - i = (i + 1) & mask; - } - return false; - } - - /** - * Removes a single instance of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element e such that - * o.equals(e) (if such an element exists). - * Returns true if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - *

This method is equivalent to {@link #removeFirstOccurrence}. - * - * @param o element to be removed from this deque, if present - * @return true if this deque contained the specified element - */ - public boolean remove(Object o) { - return removeFirstOccurrence(o); - } - - /** - * Removes all of the elements from this deque. - * The deque will be empty after this call returns. - */ - public void clear() { - int h = head; - int t = tail; - if (h != t) { // clear all cells - head = tail = 0; - int i = h; - int mask = elements.length - 1; - do { - elements[i] = null; - i = (i + 1) & mask; - } while (i != t); - } - } - - /** - * Returns an array containing all of the elements in this deque - * in proper sequence (from first to last element). - * - *

The returned array will be "safe" in that no references to it are - * maintained by this deque. (In other words, this method must allocate - * a new array). The caller is thus free to modify the returned array. - * - *

This method acts as bridge between array-based and collection-based - * APIs. - * - * @return an array containing all of the elements in this deque - */ - public Object[] toArray() { - return copyElements(new Object[size()]); - } - - /** - * Returns an array containing all of the elements in this deque in - * proper sequence (from first to last element); the runtime type of the - * returned array is that of the specified array. If the deque fits in - * the specified array, it is returned therein. Otherwise, a new array - * is allocated with the runtime type of the specified array and the - * size of this deque. - * - *

If this deque fits in the specified array with room to spare - * (i.e., the array has more elements than this deque), the element in - * the array immediately following the end of the deque is set to - * null. - * - *

Like the {@link #toArray()} method, this method acts as bridge between - * array-based and collection-based APIs. Further, this method allows - * precise control over the runtime type of the output array, and may, - * under certain circumstances, be used to save allocation costs. - * - *

Suppose x is a deque known to contain only strings. - * The following code can be used to dump the deque into a newly - * allocated array of String: - * - *

 {@code String[] y = x.toArray(new String[0]);}
- * - * Note that toArray(new Object[0]) is identical in function to - * toArray(). - * - * @param a the array into which the elements of the deque are to - * be stored, if it is big enough; otherwise, a new array of the - * same runtime type is allocated for this purpose - * @return an array containing all of the elements in this deque - * @throws ArrayStoreException if the runtime type of the specified array - * is not a supertype of the runtime type of every element in - * this deque - * @throws NullPointerException if the specified array is null - */ - @SuppressWarnings("unchecked") - public T[] toArray(T[] a) { - int size = size(); - if (a.length < size) - a = (T[])java.lang.reflect.Array.newInstance( - a.getClass().getComponentType(), size); - copyElements(a); - if (a.length > size) - a[size] = null; - return a; - } - - // *** Object methods *** - - /** - * Returns a copy of this deque. - * - * @return a copy of this deque - */ - public ArrayDeque clone() { - try { - @SuppressWarnings("unchecked") - ArrayDeque result = (ArrayDeque) super.clone(); - result.elements = Arrays.copyOf(elements, elements.length); - return result; - - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } - - /** - * Appease the serialization gods. - */ - private static final long serialVersionUID = 2340985798034038923L; - - /** - * Serialize this deque. - * - * @serialData The current size (int) of the deque, - * followed by all of its elements (each an object reference) in - * first-to-last order. - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - s.defaultWriteObject(); - - // Write out size - s.writeInt(size()); - - // Write out elements in order. - int mask = elements.length - 1; - for (int i = head; i != tail; i = (i + 1) & mask) - s.writeObject(elements[i]); - } - - /** - * Deserialize this deque. - */ - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); - - // Read in size and allocate array - int size = s.readInt(); - allocateElements(size); - head = 0; - tail = size; - - // Read in all elements in the proper order. - for (int i = 0; i < size; i++) - elements[i] = s.readObject(); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/AsyncTask.java b/platform/android/src/com/artifex/mupdfdemo/AsyncTask.java deleted file mode 100644 index b370794c..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/AsyncTask.java +++ /dev/null @@ -1,670 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.artifex.mupdfdemo; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import android.os.Process; -import android.os.Handler; -import android.os.Message; - -/** - *

AsyncTask enables proper and easy use of the UI thread. This class allows to - * perform background operations and publish results on the UI thread without - * having to manipulate threads and/or handlers.

- * - *

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} - * and does not constitute a generic threading framework. AsyncTasks should ideally be - * used for short operations (a few seconds at the most.) If you need to keep threads - * running for long periods of time, it is highly recommended you use the various APIs - * provided by the java.util.concurrent pacakge such as {@link Executor}, - * {@link ThreadPoolExecutor} and {@link FutureTask}.

- * - *

An asynchronous task is defined by a computation that runs on a background thread and - * whose result is published on the UI thread. An asynchronous task is defined by 3 generic - * types, called Params, Progress and Result, - * and 4 steps, called onPreExecute, doInBackground, - * onProgressUpdate and onPostExecute.

- * - *
- *

Developer Guides

- *

For more information about using tasks and threads, read the - * Processes and - * Threads developer guide.

- *
- * - *

Usage

- *

AsyncTask must be subclassed to be used. The subclass will override at least - * one method ({@link #doInBackground}), and most often will override a - * second one ({@link #onPostExecute}.)

- * - *

Here is an example of subclassing:

- *
- * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
- *     protected Long doInBackground(URL... urls) {
- *         int count = urls.length;
- *         long totalSize = 0;
- *         for (int i = 0; i < count; i++) {
- *             totalSize += Downloader.downloadFile(urls[i]);
- *             publishProgress((int) ((i / (float) count) * 100));
- *             // Escape early if cancel() is called
- *             if (isCancelled()) break;
- *         }
- *         return totalSize;
- *     }
- *
- *     protected void onProgressUpdate(Integer... progress) {
- *         setProgressPercent(progress[0]);
- *     }
- *
- *     protected void onPostExecute(Long result) {
- *         showDialog("Downloaded " + result + " bytes");
- *     }
- * }
- * 
- * - *

Once created, a task is executed very simply:

- *
- * new DownloadFilesTask().execute(url1, url2, url3);
- * 
- * - *

AsyncTask's generic types

- *

The three types used by an asynchronous task are the following:

- *
    - *
  1. Params, the type of the parameters sent to the task upon - * execution.
  2. - *
  3. Progress, the type of the progress units published during - * the background computation.
  4. - *
  5. Result, the type of the result of the background - * computation.
  6. - *
- *

Not all types are always used by an asynchronous task. To mark a type as unused, - * simply use the type {@link Void}:

- *
- * private class MyTask extends AsyncTask<Void, Void, Void> { ... }
- * 
- * - *

The 4 steps

- *

When an asynchronous task is executed, the task goes through 4 steps:

- *
    - *
  1. {@link #onPreExecute()}, invoked on the UI thread before the task - * is executed. This step is normally used to setup the task, for instance by - * showing a progress bar in the user interface.
  2. - *
  3. {@link #doInBackground}, invoked on the background thread - * immediately after {@link #onPreExecute()} finishes executing. This step is used - * to perform background computation that can take a long time. The parameters - * of the asynchronous task are passed to this step. The result of the computation must - * be returned by this step and will be passed back to the last step. This step - * can also use {@link #publishProgress} to publish one or more units - * of progress. These values are published on the UI thread, in the - * {@link #onProgressUpdate} step.
  4. - *
  5. {@link #onProgressUpdate}, invoked on the UI thread after a - * call to {@link #publishProgress}. The timing of the execution is - * undefined. This method is used to display any form of progress in the user - * interface while the background computation is still executing. For instance, - * it can be used to animate a progress bar or show logs in a text field.
  6. - *
  7. {@link #onPostExecute}, invoked on the UI thread after the background - * computation finishes. The result of the background computation is passed to - * this step as a parameter.
  8. - *
- * - *

Cancelling a task

- *

A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking - * this method will cause subsequent calls to {@link #isCancelled()} to return true. - * After invoking this method, {@link #onCancelled(Object)}, instead of - * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} - * returns. To ensure that a task is cancelled as quickly as possible, you should always - * check the return value of {@link #isCancelled()} periodically from - * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)

- * - *

Threading rules

- *

There are a few threading rules that must be followed for this class to - * work properly:

- *
    - *
  • The AsyncTask class must be loaded on the UI thread. This is done - * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
  • - *
  • The task instance must be created on the UI thread.
  • - *
  • {@link #execute} must be invoked on the UI thread.
  • - *
  • Do not call {@link #onPreExecute()}, {@link #onPostExecute}, - * {@link #doInBackground}, {@link #onProgressUpdate} manually.
  • - *
  • The task can be executed only once (an exception will be thrown if - * a second execution is attempted.)
  • - *
- * - *

Memory observability

- *

AsyncTask guarantees that all callback calls are synchronized in such a way that the following - * operations are safe without explicit synchronizations.

- *
    - *
  • Set member fields in the constructor or {@link #onPreExecute}, and refer to them - * in {@link #doInBackground}. - *
  • Set member fields in {@link #doInBackground}, and refer to them in - * {@link #onProgressUpdate} and {@link #onPostExecute}. - *
- * - *

Order of execution

- *

When first introduced, AsyncTasks were executed serially on a single background - * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed - * to a pool of threads allowing multiple tasks to operate in parallel. Starting with - * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single - * thread to avoid common application errors caused by parallel execution.

- *

If you truly want parallel execution, you can invoke - * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with - * {@link #THREAD_POOL_EXECUTOR}.

- */ -public abstract class AsyncTask { - private static final String LOG_TAG = "AsyncTask"; - - private static final int CORE_POOL_SIZE = 5; - private static final int MAXIMUM_POOL_SIZE = 128; - private static final int KEEP_ALIVE = 1; - - private static final ThreadFactory sThreadFactory = new ThreadFactory() { - private final AtomicInteger mCount = new AtomicInteger(1); - - public Thread newThread(Runnable r) { - return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); - } - }; - - private static final BlockingQueue sPoolWorkQueue = - new LinkedBlockingQueue(10); - - /** - * An {@link Executor} that can be used to execute tasks in parallel. - */ - public static final Executor THREAD_POOL_EXECUTOR - = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, - TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); - - /** - * An {@link Executor} that executes tasks one at a time in serial - * order. This serialization is global to a particular process. - */ - public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); - - private static final int MESSAGE_POST_RESULT = 0x1; - private static final int MESSAGE_POST_PROGRESS = 0x2; - - private static final InternalHandler sHandler = new InternalHandler(); - - private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; - private final WorkerRunnable mWorker; - private final FutureTask mFuture; - - private volatile Status mStatus = Status.PENDING; - - private final AtomicBoolean mCancelled = new AtomicBoolean(); - private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); - - private static class SerialExecutor implements Executor { - final ArrayDeque mTasks = new ArrayDeque(); - Runnable mActive; - - public synchronized void execute(final Runnable r) { - mTasks.offer(new Runnable() { - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - - protected synchronized void scheduleNext() { - if ((mActive = mTasks.poll()) != null) { - THREAD_POOL_EXECUTOR.execute(mActive); - } - } - } - - /** - * Indicates the current status of the task. Each status will be set only once - * during the lifetime of a task. - */ - public enum Status { - /** - * Indicates that the task has not been executed yet. - */ - PENDING, - /** - * Indicates that the task is running. - */ - RUNNING, - /** - * Indicates that {@link AsyncTask#onPostExecute} has finished. - */ - FINISHED, - } - - /** @hide Used to force static handler to be created. */ - public static void init() { - sHandler.getLooper(); - } - - /** @hide */ - public static void setDefaultExecutor(Executor exec) { - sDefaultExecutor = exec; - } - - /** - * Creates a new asynchronous task. This constructor must be invoked on the UI thread. - */ - public AsyncTask() { - mWorker = new WorkerRunnable() { - public Result call() throws Exception { - mTaskInvoked.set(true); - - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - //noinspection unchecked - return postResult(doInBackground(mParams)); - } - }; - - mFuture = new FutureTask(mWorker) { - @Override - protected void done() { - try { - postResultIfNotInvoked(get()); - } catch (InterruptedException e) { - android.util.Log.w(LOG_TAG, e); - } catch (ExecutionException e) { - throw new RuntimeException("An error occured while executing doInBackground()", - e.getCause()); - } catch (CancellationException e) { - postResultIfNotInvoked(null); - } - } - }; - } - - private void postResultIfNotInvoked(Result result) { - final boolean wasTaskInvoked = mTaskInvoked.get(); - if (!wasTaskInvoked) { - postResult(result); - } - } - - private Result postResult(Result result) { - @SuppressWarnings("unchecked") - Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, - new AsyncTaskResult(this, result)); - message.sendToTarget(); - return result; - } - - /** - * Returns the current status of this task. - * - * @return The current status. - */ - public final Status getStatus() { - return mStatus; - } - - /** - * Override this method to perform a computation on a background thread. The - * specified parameters are the parameters passed to {@link #execute} - * by the caller of this task. - * - * This method can call {@link #publishProgress} to publish updates - * on the UI thread. - * - * @param params The parameters of the task. - * - * @return A result, defined by the subclass of this task. - * - * @see #onPreExecute() - * @see #onPostExecute - * @see #publishProgress - */ - protected abstract Result doInBackground(Params... params); - - /** - * Runs on the UI thread before {@link #doInBackground}. - * - * @see #onPostExecute - * @see #doInBackground - */ - protected void onPreExecute() { - } - - /** - *

Runs on the UI thread after {@link #doInBackground}. The - * specified result is the value returned by {@link #doInBackground}.

- * - *

This method won't be invoked if the task was cancelled.

- * - * @param result The result of the operation computed by {@link #doInBackground}. - * - * @see #onPreExecute - * @see #doInBackground - * @see #onCancelled(Object) - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected void onPostExecute(Result result) { - } - - /** - * Runs on the UI thread after {@link #publishProgress} is invoked. - * The specified values are the values passed to {@link #publishProgress}. - * - * @param values The values indicating progress. - * - * @see #publishProgress - * @see #doInBackground - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected void onProgressUpdate(Progress... values) { - } - - /** - *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and - * {@link #doInBackground(Object[])} has finished.

- * - *

The default implementation simply invokes {@link #onCancelled()} and - * ignores the result. If you write your own implementation, do not call - * super.onCancelled(result).

- * - * @param result The result, if any, computed in - * {@link #doInBackground(Object[])}, can be null - * - * @see #cancel(boolean) - * @see #isCancelled() - */ - @SuppressWarnings({"UnusedParameters"}) - protected void onCancelled(Result result) { - onCancelled(); - } - - /** - *

Applications should preferably override {@link #onCancelled(Object)}. - * This method is invoked by the default implementation of - * {@link #onCancelled(Object)}.

- * - *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and - * {@link #doInBackground(Object[])} has finished.

- * - * @see #onCancelled(Object) - * @see #cancel(boolean) - * @see #isCancelled() - */ - protected void onCancelled() { - } - - /** - * Returns true if this task was cancelled before it completed - * normally. If you are calling {@link #cancel(boolean)} on the task, - * the value returned by this method should be checked periodically from - * {@link #doInBackground(Object[])} to end the task as soon as possible. - * - * @return true if task was cancelled before it completed - * - * @see #cancel(boolean) - */ - public final boolean isCancelled() { - return mCancelled.get(); - } - - /** - *

Attempts to cancel execution of this task. This attempt will - * fail if the task has already completed, already been cancelled, - * or could not be cancelled for some other reason. If successful, - * and this task has not started when cancel is called, - * this task should never run. If the task has already started, - * then the mayInterruptIfRunning parameter determines - * whether the thread executing this task should be interrupted in - * an attempt to stop the task.

- * - *

Calling this method will result in {@link #onCancelled(Object)} being - * invoked on the UI thread after {@link #doInBackground(Object[])} - * returns. Calling this method guarantees that {@link #onPostExecute(Object)} - * is never invoked. After invoking this method, you should check the - * value returned by {@link #isCancelled()} periodically from - * {@link #doInBackground(Object[])} to finish the task as early as - * possible.

- * - * @param mayInterruptIfRunning true if the thread executing this - * task should be interrupted; otherwise, in-progress tasks are allowed - * to complete. - * - * @return false if the task could not be cancelled, - * typically because it has already completed normally; - * true otherwise - * - * @see #isCancelled() - * @see #onCancelled(Object) - */ - public final boolean cancel(boolean mayInterruptIfRunning) { - mCancelled.set(true); - return mFuture.cancel(mayInterruptIfRunning); - } - - /** - * Waits if necessary for the computation to complete, and then - * retrieves its result. - * - * @return The computed result. - * - * @throws CancellationException If the computation was cancelled. - * @throws ExecutionException If the computation threw an exception. - * @throws InterruptedException If the current thread was interrupted - * while waiting. - */ - public final Result get() throws InterruptedException, ExecutionException { - return mFuture.get(); - } - - /** - * Waits if necessary for at most the given time for the computation - * to complete, and then retrieves its result. - * - * @param timeout Time to wait before cancelling the operation. - * @param unit The time unit for the timeout. - * - * @return The computed result. - * - * @throws CancellationException If the computation was cancelled. - * @throws ExecutionException If the computation threw an exception. - * @throws InterruptedException If the current thread was interrupted - * while waiting. - * @throws TimeoutException If the wait timed out. - */ - public final Result get(long timeout, TimeUnit unit) throws InterruptedException, - ExecutionException, TimeoutException { - return mFuture.get(timeout, unit); - } - - /** - * Executes the task with the specified parameters. The task returns - * itself (this) so that the caller can keep a reference to it. - * - *

Note: this function schedules the task on a queue for a single background - * thread or pool of threads depending on the platform version. When first - * introduced, AsyncTasks were executed serially on a single background thread. - * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed - * to a pool of threads allowing multiple tasks to operate in parallel. Starting - * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being - * executed on a single thread to avoid common application errors caused - * by parallel execution. If you truly want parallel execution, you can use - * the {@link #executeOnExecutor} version of this method - * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings - * on its use. - * - *

This method must be invoked on the UI thread. - * - * @param params The parameters of the task. - * - * @return This instance of AsyncTask. - * - * @throws IllegalStateException If {@link #getStatus()} returns either - * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. - * - * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) - * @see #execute(Runnable) - */ - public final AsyncTask execute(Params... params) { - return executeOnExecutor(sDefaultExecutor, params); - } - - /** - * Executes the task with the specified parameters. The task returns - * itself (this) so that the caller can keep a reference to it. - * - *

This method is typically used with {@link #THREAD_POOL_EXECUTOR} to - * allow multiple tasks to run in parallel on a pool of threads managed by - * AsyncTask, however you can also use your own {@link Executor} for custom - * behavior. - * - *

Warning: Allowing multiple tasks to run in parallel from - * a thread pool is generally not what one wants, because the order - * of their operation is not defined. For example, if these tasks are used - * to modify any state in common (such as writing a file due to a button click), - * there are no guarantees on the order of the modifications. - * Without careful work it is possible in rare cases for the newer version - * of the data to be over-written by an older one, leading to obscure data - * loss and stability issues. Such changes are best - * executed in serial; to guarantee such work is serialized regardless of - * platform version you can use this function with {@link #SERIAL_EXECUTOR}. - * - *

This method must be invoked on the UI thread. - * - * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a - * convenient process-wide thread pool for tasks that are loosely coupled. - * @param params The parameters of the task. - * - * @return This instance of AsyncTask. - * - * @throws IllegalStateException If {@link #getStatus()} returns either - * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. - * - * @see #execute(Object[]) - */ - public final AsyncTask executeOnExecutor(Executor exec, - Params... params) { - if (mStatus != Status.PENDING) { - switch (mStatus) { - case RUNNING: - throw new IllegalStateException("Cannot execute task:" - + " the task is already running."); - case FINISHED: - throw new IllegalStateException("Cannot execute task:" - + " the task has already been executed " - + "(a task can be executed only once)"); - } - } - - mStatus = Status.RUNNING; - - onPreExecute(); - - mWorker.mParams = params; - exec.execute(mFuture); - - return this; - } - - /** - * Convenience version of {@link #execute(Object...)} for use with - * a simple Runnable object. See {@link #execute(Object[])} for more - * information on the order of execution. - * - * @see #execute(Object[]) - * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) - */ - public static void execute(Runnable runnable) { - sDefaultExecutor.execute(runnable); - } - - /** - * This method can be invoked from {@link #doInBackground} to - * publish updates on the UI thread while the background computation is - * still running. Each call to this method will trigger the execution of - * {@link #onProgressUpdate} on the UI thread. - * - * {@link #onProgressUpdate} will note be called if the task has been - * canceled. - * - * @param values The progress values to update the UI with. - * - * @see #onProgressUpdate - * @see #doInBackground - */ - protected final void publishProgress(Progress... values) { - if (!isCancelled()) { - sHandler.obtainMessage(MESSAGE_POST_PROGRESS, - new AsyncTaskResult(this, values)).sendToTarget(); - } - } - - private void finish(Result result) { - if (isCancelled()) { - onCancelled(result); - } else { - onPostExecute(result); - } - mStatus = Status.FINISHED; - } - - private static class InternalHandler extends Handler { - @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) - @Override - public void handleMessage(Message msg) { - AsyncTaskResult result = (AsyncTaskResult) msg.obj; - switch (msg.what) { - case MESSAGE_POST_RESULT: - // There is only one result - result.mTask.finish(result.mData[0]); - break; - case MESSAGE_POST_PROGRESS: - result.mTask.onProgressUpdate(result.mData); - break; - } - } - } - - private static abstract class WorkerRunnable implements Callable { - Params[] mParams; - } - - @SuppressWarnings({"RawUseOfParameterizedType"}) - private static class AsyncTaskResult { - final AsyncTask mTask; - final Data[] mData; - - AsyncTaskResult(AsyncTask task, Data... data) { - mTask = task; - mData = data; - } - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/CancellableAsyncTask.java b/platform/android/src/com/artifex/mupdfdemo/CancellableAsyncTask.java deleted file mode 100644 index fcb1b744..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/CancellableAsyncTask.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.artifex.mupdfdemo; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; - -// Ideally this would be a subclass of AsyncTask, however the cancel() method is final, and cannot -// be overridden. I felt that having two different, but similar cancel methods was a bad idea. -public class CancellableAsyncTask -{ - private final AsyncTask asyncTask; - private final CancellableTaskDefinition ourTask; - - public void onPreExecute() - { - - } - - public void onPostExecute(Result result) - { - - } - - public CancellableAsyncTask(final CancellableTaskDefinition task) - { - if (task == null) - throw new IllegalArgumentException(); - - this.ourTask = task; - asyncTask = new AsyncTask() - { - @Override - protected Result doInBackground(Params... params) - { - return task.doInBackground(params); - } - - @Override - protected void onPreExecute() - { - CancellableAsyncTask.this.onPreExecute(); - } - - @Override - protected void onPostExecute(Result result) - { - CancellableAsyncTask.this.onPostExecute(result); - task.doCleanup(); - } - }; - } - - public void cancelAndWait() - { - this.asyncTask.cancel(true); - ourTask.doCancel(); - - try - { - this.asyncTask.get(); - } - catch (InterruptedException e) - { - } - catch (ExecutionException e) - { - } - catch (CancellationException e) - { - } - - ourTask.doCleanup(); - } - - public void execute(Params ... params) - { - asyncTask.execute(params); - } - -} diff --git a/platform/android/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java b/platform/android/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java deleted file mode 100644 index 62b04f30..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.artifex.mupdfdemo; - -public interface CancellableTaskDefinition -{ - public Result doInBackground(Params ... params); - public void doCancel(); - public void doCleanup(); -} diff --git a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java b/platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java deleted file mode 100644 index f6068bac..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFActivity.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.artifex.mupdfdemo; - -import java.io.File; -import java.io.FileFilter; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; - -import android.app.AlertDialog; -import android.app.ListActivity; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.os.FileObserver; -import android.os.Handler; -import android.view.View; -import android.widget.ListView; - -enum Purpose { - PickPDF, - PickKeyFile -} - -public class ChoosePDFActivity extends ListActivity { - static public final String PICK_KEY_FILE = "com.artifex.mupdfdemo.PICK_KEY_FILE"; - static private File mDirectory; - static private Map mPositions = new HashMap(); - private File mParent; - private File [] mDirs; - private File [] mFiles; - private Handler mHandler; - private Runnable mUpdateFiles; - private ChoosePDFAdapter adapter; - private Purpose mPurpose; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mPurpose = PICK_KEY_FILE.equals(getIntent().getAction()) ? Purpose.PickKeyFile : Purpose.PickPDF; - - - String storageState = Environment.getExternalStorageState(); - - if (!Environment.MEDIA_MOUNTED.equals(storageState) - && !Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) - { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.no_media_warning); - builder.setMessage(R.string.no_media_hint); - AlertDialog alert = builder.create(); - alert.setButton(AlertDialog.BUTTON_POSITIVE,getString(R.string.dismiss), - new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - alert.show(); - return; - } - - if (mDirectory == null) - mDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - - // Create a list adapter... - adapter = new ChoosePDFAdapter(getLayoutInflater()); - setListAdapter(adapter); - - // ...that is updated dynamically when files are scanned - mHandler = new Handler(); - mUpdateFiles = new Runnable() { - public void run() { - Resources res = getResources(); - String appName = res.getString(R.string.app_name); - String version = res.getString(R.string.version); - String title = res.getString(R.string.picker_title_App_Ver_Dir); - setTitle(String.format(title, appName, version, mDirectory)); - - mParent = mDirectory.getParentFile(); - - mDirs = mDirectory.listFiles(new FileFilter() { - - public boolean accept(File file) { - return file.isDirectory(); - } - }); - if (mDirs == null) - mDirs = new File[0]; - - mFiles = mDirectory.listFiles(new FileFilter() { - - public boolean accept(File file) { - if (file.isDirectory()) - return false; - String fname = file.getName().toLowerCase(); - switch (mPurpose) { - case PickPDF: - if (fname.endsWith(".pdf")) - return true; - if (fname.endsWith(".xps")) - return true; - if (fname.endsWith(".cbz")) - return true; - if (fname.endsWith(".epub")) - return true; - if (fname.endsWith(".png")) - return true; - if (fname.endsWith(".jpe")) - return true; - if (fname.endsWith(".jpeg")) - return true; - if (fname.endsWith(".jpg")) - return true; - if (fname.endsWith(".jfif")) - return true; - if (fname.endsWith(".jfif-tbnl")) - return true; - if (fname.endsWith(".tif")) - return true; - if (fname.endsWith(".tiff")) - return true; - return false; - case PickKeyFile: - if (fname.endsWith(".pfx")) - return true; - return false; - default: - return false; - } - } - }); - if (mFiles == null) - mFiles = new File[0]; - - Arrays.sort(mFiles, new Comparator() { - public int compare(File arg0, File arg1) { - return arg0.getName().compareToIgnoreCase(arg1.getName()); - } - }); - - Arrays.sort(mDirs, new Comparator() { - public int compare(File arg0, File arg1) { - return arg0.getName().compareToIgnoreCase(arg1.getName()); - } - }); - - adapter.clear(); - if (mParent != null) - adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.PARENT, getString(R.string.parent_directory))); - for (File f : mDirs) - adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DIR, f.getName())); - for (File f : mFiles) - adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DOC, f.getName())); - - lastPosition(); - } - }; - - // Start initial file scan... - mHandler.post(mUpdateFiles); - - // ...and observe the directory and scan files upon changes. - FileObserver observer = new FileObserver(mDirectory.getPath(), FileObserver.CREATE | FileObserver.DELETE) { - public void onEvent(int event, String path) { - mHandler.post(mUpdateFiles); - } - }; - observer.startWatching(); - } - - private void lastPosition() { - String p = mDirectory.getAbsolutePath(); - if (mPositions.containsKey(p)) - getListView().setSelection(mPositions.get(p)); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - - mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); - - if (position < (mParent == null ? 0 : 1)) { - mDirectory = mParent; - mHandler.post(mUpdateFiles); - return; - } - - position -= (mParent == null ? 0 : 1); - - if (position < mDirs.length) { - mDirectory = mDirs[position]; - mHandler.post(mUpdateFiles); - return; - } - - position -= mDirs.length; - - Uri uri = Uri.fromFile(mFiles[position]); - Intent intent = new Intent(this,MuPDFActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(uri); - switch (mPurpose) { - case PickPDF: - // Start an activity to display the PDF file - startActivity(intent); - break; - case PickKeyFile: - // Return the uri to the caller - setResult(RESULT_OK, intent); - finish(); - break; - } - } - - @Override - protected void onPause() { - super.onPause(); - if (mDirectory != null) - mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java b/platform/android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java deleted file mode 100644 index 0b3c6418..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.artifex.mupdfdemo; - -import java.util.LinkedList; - -import android.graphics.Color; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -public class ChoosePDFAdapter extends BaseAdapter { - private final LinkedList mItems; - private final LayoutInflater mInflater; - - public ChoosePDFAdapter(LayoutInflater inflater) { - mInflater = inflater; - mItems = new LinkedList(); - } - - public void clear() { - mItems.clear(); - } - - public void add(ChoosePDFItem item) { - mItems.add(item); - notifyDataSetChanged(); - } - - public int getCount() { - return mItems.size(); - } - - public Object getItem(int i) { - return null; - } - - public long getItemId(int arg0) { - return 0; - } - - private int iconForType(ChoosePDFItem.Type type) { - switch (type) { - case PARENT: return R.drawable.ic_arrow_up; - case DIR: return R.drawable.ic_dir; - case DOC: return R.drawable.ic_doc; - default: return 0; - } - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v; - if (convertView == null) { - v = mInflater.inflate(R.layout.picker_entry, null); - } else { - v = convertView; - } - ChoosePDFItem item = mItems.get(position); - ((TextView)v.findViewById(R.id.name)).setText(item.name); - ((ImageView)v.findViewById(R.id.icon)).setImageResource(iconForType(item.type)); - ((ImageView)v.findViewById(R.id.icon)).setColorFilter(Color.argb(255, 0, 0, 0)); - return v; - } - -} diff --git a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFItem.java b/platform/android/src/com/artifex/mupdfdemo/ChoosePDFItem.java deleted file mode 100644 index de6e1d52..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/ChoosePDFItem.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.artifex.mupdfdemo; - -public class ChoosePDFItem { - enum Type { - PARENT, DIR, DOC - } - - final public Type type; - final public String name; - - public ChoosePDFItem (Type t, String n) { - type = t; - name = n; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/Deque.java b/platform/android/src/com/artifex/mupdfdemo/Deque.java deleted file mode 100644 index 4bb176b2..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/Deque.java +++ /dev/null @@ -1,554 +0,0 @@ -/* - * Written by Doug Lea and Josh Bloch with assistance from members of - * JCP JSR-166 Expert Group and released to the public domain, as explained - * at http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package com.artifex.mupdfdemo; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Queue; -import java.util.Stack; - -// BEGIN android-note -// removed link to collections framework docs -// END android-note - -/** - * A linear collection that supports element insertion and removal at - * both ends. The name deque is short for "double ended queue" - * and is usually pronounced "deck". Most Deque - * implementations place no fixed limits on the number of elements - * they may contain, but this interface supports capacity-restricted - * deques as well as those with no fixed size limit. - * - *

This interface defines methods to access the elements at both - * ends of the deque. Methods are provided to insert, remove, and - * examine the element. Each of these methods exists in two forms: - * one throws an exception if the operation fails, the other returns a - * special value (either null or false, depending on - * the operation). The latter form of the insert operation is - * designed specifically for use with capacity-restricted - * Deque implementations; in most implementations, insert - * operations cannot fail. - * - *

The twelve methods described above are summarized in the - * following table: - * - *

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
First Element (Head) Last Element (Tail)
Throws exceptionSpecial valueThrows exceptionSpecial value
Insert{@link #addFirst addFirst(e)}{@link #offerFirst offerFirst(e)}{@link #addLast addLast(e)}{@link #offerLast offerLast(e)}
Remove{@link #removeFirst removeFirst()}{@link #pollFirst pollFirst()}{@link #removeLast removeLast()}{@link #pollLast pollLast()}
Examine{@link #getFirst getFirst()}{@link #peekFirst peekFirst()}{@link #getLast getLast()}{@link #peekLast peekLast()}
- * - *

This interface extends the {@link Queue} interface. When a deque is - * used as a queue, FIFO (First-In-First-Out) behavior results. Elements are - * added at the end of the deque and removed from the beginning. The methods - * inherited from the Queue interface are precisely equivalent to - * Deque methods as indicated in the following table: - * - *

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Queue Method Equivalent Deque Method
{@link java.util.Queue#add add(e)}{@link #addLast addLast(e)}
{@link java.util.Queue#offer offer(e)}{@link #offerLast offerLast(e)}
{@link java.util.Queue#remove remove()}{@link #removeFirst removeFirst()}
{@link java.util.Queue#poll poll()}{@link #pollFirst pollFirst()}
{@link java.util.Queue#element element()}{@link #getFirst getFirst()}
{@link java.util.Queue#peek peek()}{@link #peek peekFirst()}
- * - *

Deques can also be used as LIFO (Last-In-First-Out) stacks. This - * interface should be used in preference to the legacy {@link Stack} class. - * When a deque is used as a stack, elements are pushed and popped from the - * beginning of the deque. Stack methods are precisely equivalent to - * Deque methods as indicated in the table below: - * - *

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Stack Method Equivalent Deque Method
{@link #push push(e)}{@link #addFirst addFirst(e)}
{@link #pop pop()}{@link #removeFirst removeFirst()}
{@link #peek peek()}{@link #peekFirst peekFirst()}
- * - *

Note that the {@link #peek peek} method works equally well when - * a deque is used as a queue or a stack; in either case, elements are - * drawn from the beginning of the deque. - * - *

This interface provides two methods to remove interior - * elements, {@link #removeFirstOccurrence removeFirstOccurrence} and - * {@link #removeLastOccurrence removeLastOccurrence}. - * - *

Unlike the {@link List} interface, this interface does not - * provide support for indexed access to elements. - * - *

While Deque implementations are not strictly required - * to prohibit the insertion of null elements, they are strongly - * encouraged to do so. Users of any Deque implementations - * that do allow null elements are strongly encouraged not to - * take advantage of the ability to insert nulls. This is so because - * null is used as a special return value by various methods - * to indicated that the deque is empty. - * - *

Deque implementations generally do not define - * element-based versions of the equals and hashCode - * methods, but instead inherit the identity-based versions from class - * Object. - * - * @author Doug Lea - * @author Josh Bloch - * @since 1.6 - * @param the type of elements held in this collection - */ - -public interface Deque extends Queue { - /** - * Inserts the specified element at the front of this deque if it is - * possible to do so immediately without violating capacity restrictions. - * When using a capacity-restricted deque, it is generally preferable to - * use method {@link #offerFirst}. - * - * @param e the element to add - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - void addFirst(E e); - - /** - * Inserts the specified element at the end of this deque if it is - * possible to do so immediately without violating capacity restrictions. - * When using a capacity-restricted deque, it is generally preferable to - * use method {@link #offerLast}. - * - *

This method is equivalent to {@link #add}. - * - * @param e the element to add - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - void addLast(E e); - - /** - * Inserts the specified element at the front of this deque unless it would - * violate capacity restrictions. When using a capacity-restricted deque, - * this method is generally preferable to the {@link #addFirst} method, - * which can fail to insert an element only by throwing an exception. - * - * @param e the element to add - * @return true if the element was added to this deque, else - * false - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean offerFirst(E e); - - /** - * Inserts the specified element at the end of this deque unless it would - * violate capacity restrictions. When using a capacity-restricted deque, - * this method is generally preferable to the {@link #addLast} method, - * which can fail to insert an element only by throwing an exception. - * - * @param e the element to add - * @return true if the element was added to this deque, else - * false - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean offerLast(E e); - - /** - * Retrieves and removes the first element of this deque. This method - * differs from {@link #pollFirst pollFirst} only in that it throws an - * exception if this deque is empty. - * - * @return the head of this deque - * @throws NoSuchElementException if this deque is empty - */ - E removeFirst(); - - /** - * Retrieves and removes the last element of this deque. This method - * differs from {@link #pollLast pollLast} only in that it throws an - * exception if this deque is empty. - * - * @return the tail of this deque - * @throws NoSuchElementException if this deque is empty - */ - E removeLast(); - - /** - * Retrieves and removes the first element of this deque, - * or returns null if this deque is empty. - * - * @return the head of this deque, or null if this deque is empty - */ - E pollFirst(); - - /** - * Retrieves and removes the last element of this deque, - * or returns null if this deque is empty. - * - * @return the tail of this deque, or null if this deque is empty - */ - E pollLast(); - - /** - * Retrieves, but does not remove, the first element of this deque. - * - * This method differs from {@link #peekFirst peekFirst} only in that it - * throws an exception if this deque is empty. - * - * @return the head of this deque - * @throws NoSuchElementException if this deque is empty - */ - E getFirst(); - - /** - * Retrieves, but does not remove, the last element of this deque. - * This method differs from {@link #peekLast peekLast} only in that it - * throws an exception if this deque is empty. - * - * @return the tail of this deque - * @throws NoSuchElementException if this deque is empty - */ - E getLast(); - - /** - * Retrieves, but does not remove, the first element of this deque, - * or returns null if this deque is empty. - * - * @return the head of this deque, or null if this deque is empty - */ - E peekFirst(); - - /** - * Retrieves, but does not remove, the last element of this deque, - * or returns null if this deque is empty. - * - * @return the tail of this deque, or null if this deque is empty - */ - E peekLast(); - - /** - * Removes the first occurrence of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element e such that - * (o==null ? e==null : o.equals(e)) - * (if such an element exists). - * Returns true if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return true if an element was removed as a result of this call - * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean removeFirstOccurrence(Object o); - - /** - * Removes the last occurrence of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the last element e such that - * (o==null ? e==null : o.equals(e)) - * (if such an element exists). - * Returns true if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - * @param o element to be removed from this deque, if present - * @return true if an element was removed as a result of this call - * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean removeLastOccurrence(Object o); - - // *** Queue methods *** - - /** - * Inserts the specified element into the queue represented by this deque - * (in other words, at the tail of this deque) if it is possible to do so - * immediately without violating capacity restrictions, returning - * true upon success and throwing an - * IllegalStateException if no space is currently available. - * When using a capacity-restricted deque, it is generally preferable to - * use {@link #offer(Object) offer}. - * - *

This method is equivalent to {@link #addLast}. - * - * @param e the element to add - * @return true (as specified by {@link Collection#add}) - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean add(E e); - - /** - * Inserts the specified element into the queue represented by this deque - * (in other words, at the tail of this deque) if it is possible to do so - * immediately without violating capacity restrictions, returning - * true upon success and false if no space is currently - * available. When using a capacity-restricted deque, this method is - * generally preferable to the {@link #add} method, which can fail to - * insert an element only by throwing an exception. - * - *

This method is equivalent to {@link #offerLast}. - * - * @param e the element to add - * @return true if the element was added to this deque, else - * false - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - boolean offer(E e); - - /** - * Retrieves and removes the head of the queue represented by this deque - * (in other words, the first element of this deque). - * This method differs from {@link #poll poll} only in that it throws an - * exception if this deque is empty. - * - *

This method is equivalent to {@link #removeFirst()}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException if this deque is empty - */ - E remove(); - - /** - * Retrieves and removes the head of the queue represented by this deque - * (in other words, the first element of this deque), or returns - * null if this deque is empty. - * - *

This method is equivalent to {@link #pollFirst()}. - * - * @return the first element of this deque, or null if - * this deque is empty - */ - E poll(); - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque (in other words, the first element of this deque). - * This method differs from {@link #peek peek} only in that it throws an - * exception if this deque is empty. - * - *

This method is equivalent to {@link #getFirst()}. - * - * @return the head of the queue represented by this deque - * @throws NoSuchElementException if this deque is empty - */ - E element(); - - /** - * Retrieves, but does not remove, the head of the queue represented by - * this deque (in other words, the first element of this deque), or - * returns null if this deque is empty. - * - *

This method is equivalent to {@link #peekFirst()}. - * - * @return the head of the queue represented by this deque, or - * null if this deque is empty - */ - E peek(); - - - // *** Stack methods *** - - /** - * Pushes an element onto the stack represented by this deque (in other - * words, at the head of this deque) if it is possible to do so - * immediately without violating capacity restrictions, returning - * true upon success and throwing an - * IllegalStateException if no space is currently available. - * - *

This method is equivalent to {@link #addFirst}. - * - * @param e the element to push - * @throws IllegalStateException if the element cannot be added at this - * time due to capacity restrictions - * @throws ClassCastException if the class of the specified element - * prevents it from being added to this deque - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements - * @throws IllegalArgumentException if some property of the specified - * element prevents it from being added to this deque - */ - void push(E e); - - /** - * Pops an element from the stack represented by this deque. In other - * words, removes and returns the first element of this deque. - * - *

This method is equivalent to {@link #removeFirst()}. - * - * @return the element at the front of this deque (which is the top - * of the stack represented by this deque) - * @throws NoSuchElementException if this deque is empty - */ - E pop(); - - - // *** Collection methods *** - - /** - * Removes the first occurrence of the specified element from this deque. - * If the deque does not contain the element, it is unchanged. - * More formally, removes the first element e such that - * (o==null ? e==null : o.equals(e)) - * (if such an element exists). - * Returns true if this deque contained the specified element - * (or equivalently, if this deque changed as a result of the call). - * - *

This method is equivalent to {@link #removeFirstOccurrence}. - * - * @param o element to be removed from this deque, if present - * @return true if an element was removed as a result of this call - * @throws ClassCastException if the class of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean remove(Object o); - - /** - * Returns true if this deque contains the specified element. - * More formally, returns true if and only if this deque contains - * at least one element e such that - * (o==null ? e==null : o.equals(e)). - * - * @param o element whose presence in this deque is to be tested - * @return true if this deque contains the specified element - * @throws ClassCastException if the type of the specified element - * is incompatible with this deque (optional) - * @throws NullPointerException if the specified element is null and this - * deque does not permit null elements (optional) - */ - boolean contains(Object o); - - /** - * Returns the number of elements in this deque. - * - * @return the number of elements in this deque - */ - public int size(); - - /** - * Returns an iterator over the elements in this deque in proper sequence. - * The elements will be returned in order from first (head) to last (tail). - * - * @return an iterator over the elements in this deque in proper sequence - */ - Iterator iterator(); - - /** - * Returns an iterator over the elements in this deque in reverse - * sequential order. The elements will be returned in order from - * last (tail) to first (head). - * - * @return an iterator over the elements in this deque in reverse - * sequence - */ - Iterator descendingIterator(); - -} diff --git a/platform/android/src/com/artifex/mupdfdemo/FilePicker.java b/platform/android/src/com/artifex/mupdfdemo/FilePicker.java deleted file mode 100644 index d1953531..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/FilePicker.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.net.Uri; - -public abstract class FilePicker { - public interface FilePickerSupport { - void performPickFor(FilePicker picker); - } - - private final FilePickerSupport support; - - FilePicker(FilePickerSupport _support) { - support = _support; - } - - void pick() { - support.performPickFor(this); - } - - abstract void onPick(Uri uri); -} diff --git a/platform/android/src/com/artifex/mupdfdemo/LinkInfo.java b/platform/android/src/com/artifex/mupdfdemo/LinkInfo.java deleted file mode 100644 index 5aeaccbe..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/LinkInfo.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.graphics.RectF; - -public class LinkInfo { - final public RectF rect; - - public LinkInfo(float l, float t, float r, float b) { - rect = new RectF(l, t, r, b); - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/LinkInfoExternal.java b/platform/android/src/com/artifex/mupdfdemo/LinkInfoExternal.java deleted file mode 100644 index 574b6264..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/LinkInfoExternal.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.artifex.mupdfdemo; - -public class LinkInfoExternal extends LinkInfo { - final public String url; - - public LinkInfoExternal(float l, float t, float r, float b, String u) { - super(l, t, r, b); - url = u; - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - visitor.visitExternal(this); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/LinkInfoInternal.java b/platform/android/src/com/artifex/mupdfdemo/LinkInfoInternal.java deleted file mode 100644 index 761bf87a..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/LinkInfoInternal.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.artifex.mupdfdemo; - -public class LinkInfoInternal extends LinkInfo { - final public int pageNumber; - - public LinkInfoInternal(float l, float t, float r, float b, int p) { - super(l, t, r, b); - pageNumber = p; - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - visitor.visitInternal(this); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/LinkInfoRemote.java b/platform/android/src/com/artifex/mupdfdemo/LinkInfoRemote.java deleted file mode 100644 index 731e6408..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/LinkInfoRemote.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.artifex.mupdfdemo; - -public class LinkInfoRemote extends LinkInfo { - final public String fileSpec; - final public int pageNumber; - final public boolean newWindow; - - public LinkInfoRemote(float l, float t, float r, float b, String f, int p, boolean n) { - super(l, t, r, b); - fileSpec = f; - pageNumber = p; - newWindow = n; - } - - public void acceptVisitor(LinkInfoVisitor visitor) { - visitor.visitRemote(this); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java b/platform/android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java deleted file mode 100644 index ecd093e4..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/LinkInfoVisitor.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.artifex.mupdfdemo; - -abstract public class LinkInfoVisitor { - public abstract void visitInternal(LinkInfoInternal li); - public abstract void visitExternal(LinkInfoExternal li); - public abstract void visitRemote(LinkInfoRemote li); -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java deleted file mode 100644 index 91b1e8fe..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFActivity.java +++ /dev/null @@ -1,1417 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.text.Editable; -import android.text.TextWatcher; -import android.text.method.PasswordTransformationMethod; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MenuItem.OnMenuItemClickListener; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.TranslateAnimation; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.PopupMenu; -import android.widget.RelativeLayout; -import android.widget.SeekBar; -import android.widget.TextView; -import android.widget.ViewAnimator; - -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.concurrent.Executor; - -class ThreadPerTaskExecutor implements Executor { - public void execute(Runnable r) { - new Thread(r).start(); - } -} - -public class MuPDFActivity extends Activity implements FilePicker.FilePickerSupport -{ - /* The core rendering instance */ - enum TopBarMode {Main, Search, Annot, Delete, More, Accept}; - enum AcceptMode {Highlight, Underline, StrikeOut, Ink, CopyText}; - - private final int OUTLINE_REQUEST=0; - private final int PRINT_REQUEST=1; - private final int FILEPICK_REQUEST=2; - private final int PROOF_REQUEST=3; - private MuPDFCore core; - private String mFileName; - private MuPDFReaderView mDocView; - private View mButtonsView; - private boolean mButtonsVisible; - private EditText mPasswordView; - private TextView mFilenameView; - private SeekBar mPageSlider; - private int mPageSliderRes; - private TextView mPageNumberView; - private TextView mInfoView; - private ImageButton mSearchButton; - private ImageButton mReflowButton; - private ImageButton mOutlineButton; - private ImageButton mMoreButton; - private TextView mAnnotTypeText; - private ImageButton mAnnotButton; - private ViewAnimator mTopBarSwitcher; - private ImageButton mLinkButton; - private TopBarMode mTopBarMode = TopBarMode.Main; - private AcceptMode mAcceptMode; - private ImageButton mSearchBack; - private ImageButton mSearchFwd; - private EditText mSearchText; - private SearchTask mSearchTask; - private ImageButton mProofButton; - private ImageButton mSepsButton; - private AlertDialog.Builder mAlertBuilder; - private boolean mLinkHighlight = false; - private final Handler mHandler = new Handler(); - private boolean mAlertsActive= false; - private boolean mReflow = false; - private AsyncTask mAlertTask; - private AlertDialog mAlertDialog; - private FilePicker mFilePicker; - private String mProofFile; - private boolean mSepEnabled[][]; - - static private AlertDialog.Builder gAlertBuilder; - static public AlertDialog.Builder getAlertBuilder() {return gAlertBuilder;} - - public void createAlertWaiter() { - mAlertsActive = true; - // All mupdf library calls are performed on asynchronous tasks to avoid stalling - // the UI. Some calls can lead to javascript-invoked requests to display an - // alert dialog and collect a reply from the user. The task has to be blocked - // until the user's reply is received. This method creates an asynchronous task, - // the purpose of which is to wait of these requests and produce the dialog - // in response, while leaving the core blocked. When the dialog receives the - // user's response, it is sent to the core via replyToAlert, unblocking it. - // Another alert-waiting task is then created to pick up the next alert. - if (mAlertTask != null) { - mAlertTask.cancel(true); - mAlertTask = null; - } - if (mAlertDialog != null) { - mAlertDialog.cancel(); - mAlertDialog = null; - } - mAlertTask = new AsyncTask() { - - @Override - protected MuPDFAlert doInBackground(Void... arg0) { - if (!mAlertsActive) - return null; - - return core.waitForAlert(); - } - - @Override - protected void onPostExecute(final MuPDFAlert result) { - // core.waitForAlert may return null when shutting down - if (result == null) - return; - final MuPDFAlert.ButtonPressed pressed[] = new MuPDFAlert.ButtonPressed[3]; - for(int i = 0; i < 3; i++) - pressed[i] = MuPDFAlert.ButtonPressed.None; - DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mAlertDialog = null; - if (mAlertsActive) { - int index = 0; - switch (which) { - case AlertDialog.BUTTON1: index=0; break; - case AlertDialog.BUTTON2: index=1; break; - case AlertDialog.BUTTON3: index=2; break; - } - result.buttonPressed = pressed[index]; - // Send the user's response to the core, so that it can - // continue processing. - core.replyToAlert(result); - // Create another alert-waiter to pick up the next alert. - createAlertWaiter(); - } - } - }; - mAlertDialog = mAlertBuilder.create(); - mAlertDialog.setTitle(result.title); - mAlertDialog.setMessage(result.message); - switch (result.iconType) - { - case Error: - break; - case Warning: - break; - case Question: - break; - case Status: - break; - } - switch (result.buttonGroupType) - { - case OkCancel: - mAlertDialog.setButton(AlertDialog.BUTTON2, getString(R.string.cancel), listener); - pressed[1] = MuPDFAlert.ButtonPressed.Cancel; - case Ok: - mAlertDialog.setButton(AlertDialog.BUTTON1, getString(R.string.okay), listener); - pressed[0] = MuPDFAlert.ButtonPressed.Ok; - break; - case YesNoCancel: - mAlertDialog.setButton(AlertDialog.BUTTON3, getString(R.string.cancel), listener); - pressed[2] = MuPDFAlert.ButtonPressed.Cancel; - case YesNo: - mAlertDialog.setButton(AlertDialog.BUTTON1, getString(R.string.yes), listener); - pressed[0] = MuPDFAlert.ButtonPressed.Yes; - mAlertDialog.setButton(AlertDialog.BUTTON2, getString(R.string.no), listener); - pressed[1] = MuPDFAlert.ButtonPressed.No; - break; - } - mAlertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - mAlertDialog = null; - if (mAlertsActive) { - result.buttonPressed = MuPDFAlert.ButtonPressed.None; - core.replyToAlert(result); - createAlertWaiter(); - } - } - }); - - mAlertDialog.show(); - } - }; - - mAlertTask.executeOnExecutor(new ThreadPerTaskExecutor()); - } - - public void destroyAlertWaiter() { - mAlertsActive = false; - if (mAlertDialog != null) { - mAlertDialog.cancel(); - mAlertDialog = null; - } - if (mAlertTask != null) { - mAlertTask.cancel(true); - mAlertTask = null; - } - } - - private MuPDFCore openFile(String path) - { - int lastSlashPos = path.lastIndexOf('/'); - mFileName = new String(lastSlashPos == -1 - ? path - : path.substring(lastSlashPos+1)); - System.out.println("Trying to open " + path); - try - { - core = new MuPDFCore(this, path); - // New file: drop the old outline data - OutlineActivityData.set(null); - } - catch (Exception e) - { - System.out.println(e); - return null; - } - catch (java.lang.OutOfMemoryError e) - { - // out of memory is not an Exception, so we catch it separately. - System.out.println(e); - return null; - } - return core; - } - - private MuPDFCore openBuffer(byte buffer[], String magic) - { - System.out.println("Trying to open byte buffer"); - try - { - core = new MuPDFCore(this, buffer, magic); - // New file: drop the old outline data - OutlineActivityData.set(null); - } - catch (Exception e) - { - System.out.println(e); - return null; - } - return core; - } - - // determine whether the current activity is a proofing activity. - public boolean isProofing() - { - String format = core.fileFormat(); - return (format.equals("GPROOF")); - } - - /** Called when the activity is first created. */ - @Override - public void onCreate(final Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - mAlertBuilder = new AlertDialog.Builder(this); - gAlertBuilder = mAlertBuilder; // keep a static copy of this that other classes can use - - if (core == null) { - core = (MuPDFCore)getLastNonConfigurationInstance(); - - if (savedInstanceState != null && savedInstanceState.containsKey("FileName")) { - mFileName = savedInstanceState.getString("FileName"); - } - } - if (core == null) { - Intent intent = getIntent(); - byte buffer[] = null; - - if (Intent.ACTION_VIEW.equals(intent.getAction())) { - Uri uri = intent.getData(); - System.out.println("URI to open is: " + uri); - if (uri.toString().startsWith("content://")) { - String reason = null; - try { - InputStream is = getContentResolver().openInputStream(uri); - int len = is.available(); - buffer = new byte[len]; - is.read(buffer, 0, len); - is.close(); - } - catch (java.lang.OutOfMemoryError e) { - System.out.println("Out of memory during buffer reading"); - reason = e.toString(); - } - catch (Exception e) { - System.out.println("Exception reading from stream: " + e); - - // Handle view requests from the Transformer Prime's file manager - // Hopefully other file managers will use this same scheme, if not - // using explicit paths. - // I'm hoping that this case below is no longer needed...but it's - // hard to test as the file manager seems to have changed in 4.x. - try { - Cursor cursor = getContentResolver().query(uri, new String[]{"_data"}, null, null, null); - if (cursor.moveToFirst()) { - String str = cursor.getString(0); - if (str == null) { - reason = "Couldn't parse data in intent"; - } - else { - uri = Uri.parse(str); - } - } - } - catch (Exception e2) { - System.out.println("Exception in Transformer Prime file manager code: " + e2); - reason = e2.toString(); - } - } - if (reason != null) { - buffer = null; - Resources res = getResources(); - AlertDialog alert = mAlertBuilder.create(); - setTitle(String.format(res.getString(R.string.cannot_open_document_Reason), reason)); - alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.dismiss), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - alert.show(); - return; - } - } - if (buffer != null) { - core = openBuffer(buffer, intent.getType()); - } else { - String path = Uri.decode(uri.getEncodedPath()); - if (path == null) { - path = uri.toString(); - } - core = openFile(path); - } - SearchTaskResult.set(null); - } - if (core != null && core.needsPassword()) { - requestPassword(savedInstanceState); - return; - } - if (core != null && core.countPages() == 0) - { - core = null; - } - } - if (core == null) - { - AlertDialog alert = mAlertBuilder.create(); - alert.setTitle(R.string.cannot_open_document); - alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.dismiss), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - alert.setOnCancelListener(new OnCancelListener() { - - @Override - public void onCancel(DialogInterface dialog) { - finish(); - } - }); - alert.show(); - return; - } - - createUI(savedInstanceState); - - // hide the proof button if this file can't be proofed - if (!core.canProof()) { - mProofButton.setVisibility(View.INVISIBLE); - } - - if (isProofing()) { - - // start the activity with a new array - mSepEnabled = null; - - // show the separations button - mSepsButton.setVisibility(View.VISIBLE); - - // hide some other buttons - mLinkButton.setVisibility(View.INVISIBLE); - mReflowButton.setVisibility(View.INVISIBLE); - mOutlineButton.setVisibility(View.INVISIBLE); - mSearchButton.setVisibility(View.INVISIBLE); - mMoreButton.setVisibility(View.INVISIBLE); - } - else { - // hide the separations button - mSepsButton.setVisibility(View.INVISIBLE); - } - - } - - public void requestPassword(final Bundle savedInstanceState) { - mPasswordView = new EditText(this); - mPasswordView.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); - mPasswordView.setTransformationMethod(new PasswordTransformationMethod()); - - AlertDialog alert = mAlertBuilder.create(); - alert.setTitle(R.string.enter_password); - alert.setView(mPasswordView); - alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.okay), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (core.authenticatePassword(mPasswordView.getText().toString())) { - createUI(savedInstanceState); - } else { - requestPassword(savedInstanceState); - } - } - }); - alert.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel), - new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - alert.show(); - } - - public void createUI(Bundle savedInstanceState) { - if (core == null) - return; - - // Now create the UI. - // First create the document view - mDocView = new MuPDFReaderView(this) { - @Override - protected void onMoveToChild(int i) { - if (core == null) - return; - - mPageNumberView.setText(String.format("%d / %d", i + 1, - core.countPages())); - mPageSlider.setMax((core.countPages() - 1) * mPageSliderRes); - mPageSlider.setProgress(i * mPageSliderRes); - super.onMoveToChild(i); - } - - @Override - protected void onTapMainDocArea() { - if (!mButtonsVisible) { - showButtons(); - } else { - if (mTopBarMode == TopBarMode.Main) - hideButtons(); - } - } - - @Override - protected void onDocMotion() { - hideButtons(); - } - - @Override - protected void onHit(Hit item) { - switch (mTopBarMode) { - case Annot: - if (item == Hit.Annotation) { - showButtons(); - mTopBarMode = TopBarMode.Delete; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - break; - case Delete: - mTopBarMode = TopBarMode.Annot; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - // fall through - default: - // Not in annotation editing mode, but the pageview will - // still select and highlight hit annotations, so - // deselect just in case. - MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); - if (pageView != null) - pageView.deselectAnnotation(); - break; - } - } - }; - mDocView.setAdapter(new MuPDFPageAdapter(this, this, core)); - - mSearchTask = new SearchTask(this, core) { - @Override - protected void onTextFound(SearchTaskResult result) { - SearchTaskResult.set(result); - // Ask the ReaderView to move to the resulting page - mDocView.setDisplayedViewIndex(result.pageNumber); - // Make the ReaderView act on the change to SearchTaskResult - // via overridden onChildSetup method. - mDocView.resetupChildren(); - } - }; - - // Make the buttons overlay, and store all its - // controls in variables - makeButtonsView(); - - // Set up the page slider - int smax = Math.max(core.countPages()-1,1); - mPageSliderRes = ((10 + smax - 1)/smax) * 2; - - // Set the file-name text - mFilenameView.setText(mFileName); - - // Activate the seekbar - mPageSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - public void onStopTrackingTouch(SeekBar seekBar) { - mDocView.setDisplayedViewIndex((seekBar.getProgress()+mPageSliderRes/2)/mPageSliderRes); - } - - public void onStartTrackingTouch(SeekBar seekBar) {} - - public void onProgressChanged(SeekBar seekBar, int progress, - boolean fromUser) { - updatePageNumView((progress+mPageSliderRes/2)/mPageSliderRes); - } - }); - - // Activate the search-preparing button - mSearchButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - searchModeOn(); - } - }); - - // Activate the reflow button - mReflowButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - toggleReflow(); - } - }); - - if (core.fileFormat().startsWith("PDF") && core.isUnencryptedPDF() && !core.wasOpenedFromBuffer()) - { - mAnnotButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - mTopBarMode = TopBarMode.Annot; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - }); - } - else - { - mAnnotButton.setVisibility(View.GONE); - } - - // Search invoking buttons are disabled while there is no text specified - mSearchBack.setEnabled(false); - mSearchFwd.setEnabled(false); - mSearchBack.setColorFilter(Color.argb(255, 128, 128, 128)); - mSearchFwd.setColorFilter(Color.argb(255, 128, 128, 128)); - - // React to interaction with the text widget - mSearchText.addTextChangedListener(new TextWatcher() { - - public void afterTextChanged(Editable s) { - boolean haveText = s.toString().length() > 0; - setButtonEnabled(mSearchBack, haveText); - setButtonEnabled(mSearchFwd, haveText); - - // Remove any previous search results - if (SearchTaskResult.get() != null && !mSearchText.getText().toString().equals(SearchTaskResult.get().txt)) { - SearchTaskResult.set(null); - mDocView.resetupChildren(); - } - } - public void beforeTextChanged(CharSequence s, int start, int count, - int after) {} - public void onTextChanged(CharSequence s, int start, int before, - int count) {} - }); - - //React to Done button on keyboard - mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() { - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_DONE) - search(1); - return false; - } - }); - - mSearchText.setOnKeyListener(new View.OnKeyListener() { - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) - search(1); - return false; - } - }); - - // Activate search invoking buttons - mSearchBack.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - search(-1); - } - }); - mSearchFwd.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - search(1); - } - }); - - mLinkButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - setLinkHighlight(!mLinkHighlight); - } - }); - - if (core.hasOutline()) { - mOutlineButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - OutlineItem outline[] = core.getOutline(); - if (outline != null) { - OutlineActivityData.get().items = outline; - Intent intent = new Intent(MuPDFActivity.this, OutlineActivity.class); - startActivityForResult(intent, OUTLINE_REQUEST); - } - } - }); - } else { - mOutlineButton.setVisibility(View.GONE); - } - - // Reenstate last state if it was recorded - SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); - mDocView.setDisplayedViewIndex(prefs.getInt("page"+mFileName, 0)); - - if (savedInstanceState == null || !savedInstanceState.getBoolean("ButtonsHidden", false)) - showButtons(); - - if(savedInstanceState != null && savedInstanceState.getBoolean("SearchMode", false)) - searchModeOn(); - - if(savedInstanceState != null && savedInstanceState.getBoolean("ReflowMode", false)) - reflowModeSet(true); - - // Stick the document view and the buttons overlay into a parent view - RelativeLayout layout = new RelativeLayout(this); - layout.addView(mDocView); - layout.addView(mButtonsView); - setContentView(layout); - - if (isProofing()) { - // go to the current page - int currentPage = getIntent().getIntExtra("startingPage", 0); - mDocView.setDisplayedViewIndex(currentPage); - } - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case OUTLINE_REQUEST: - if (resultCode >= 0) - mDocView.setDisplayedViewIndex(resultCode); - break; - case PRINT_REQUEST: - if (resultCode == RESULT_CANCELED) - showInfo(getString(R.string.print_failed)); - break; - case FILEPICK_REQUEST: - if (mFilePicker != null && resultCode == RESULT_OK) - mFilePicker.onPick(data.getData()); - case PROOF_REQUEST: - // we're returning from a proofing activity - - if (mProofFile != null) - { - core.endProof(mProofFile); - mProofFile = null; - } - - // return the top bar to default - mTopBarMode = TopBarMode.Main; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - super.onActivityResult(requestCode, resultCode, data); - } - - public Object onRetainNonConfigurationInstance() - { - MuPDFCore mycore = core; - core = null; - return mycore; - } - - private void reflowModeSet(boolean reflow) - { - mReflow = reflow; - mDocView.setAdapter(mReflow ? new MuPDFReflowAdapter(this, core) : new MuPDFPageAdapter(this, this, core)); - mReflowButton.setColorFilter(mReflow ? Color.argb(0xFF, 172, 114, 37) : Color.argb(0xFF, 255, 255, 255)); - setButtonEnabled(mAnnotButton, !reflow); - setButtonEnabled(mSearchButton, !reflow); - if (reflow) setLinkHighlight(false); - setButtonEnabled(mLinkButton, !reflow); - setButtonEnabled(mMoreButton, !reflow); - mDocView.refresh(mReflow); - } - - private void toggleReflow() { - reflowModeSet(!mReflow); - showInfo(mReflow ? getString(R.string.entering_reflow_mode) : getString(R.string.leaving_reflow_mode)); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - if (mFileName != null && mDocView != null) { - outState.putString("FileName", mFileName); - - // Store current page in the prefs against the file name, - // so that we can pick it up each time the file is loaded - // Other info is needed only for screen-orientation change, - // so it can go in the bundle - SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); - SharedPreferences.Editor edit = prefs.edit(); - edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); - edit.commit(); - } - - if (!mButtonsVisible) - outState.putBoolean("ButtonsHidden", true); - - if (mTopBarMode == TopBarMode.Search) - outState.putBoolean("SearchMode", true); - - if (mReflow) - outState.putBoolean("ReflowMode", true); - } - - @Override - protected void onPause() { - super.onPause(); - - if (mSearchTask != null) - mSearchTask.stop(); - - if (mFileName != null && mDocView != null) { - SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); - SharedPreferences.Editor edit = prefs.edit(); - edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); - edit.commit(); - } - } - - public void onDestroy() - { - if (mDocView != null) { - mDocView.applyToChildren(new ReaderView.ViewMapper() { - void applyToView(View view) { - ((MuPDFView)view).releaseBitmaps(); - } - }); - } - if (core != null) - core.onDestroy(); - if (mAlertTask != null) { - mAlertTask.cancel(true); - mAlertTask = null; - } - core = null; - super.onDestroy(); - } - - private void setButtonEnabled(ImageButton button, boolean enabled) { - button.setEnabled(enabled); - button.setColorFilter(enabled ? Color.argb(255, 255, 255, 255) : Color.argb(255, 128, 128, 128)); - } - - private void setLinkHighlight(boolean highlight) { - mLinkHighlight = highlight; - // LINK_COLOR tint - mLinkButton.setColorFilter(highlight ? Color.argb(0xFF, 172, 114, 37) : Color.argb(0xFF, 255, 255, 255)); - // Inform pages of the change. - mDocView.setLinksEnabled(highlight); - } - - private void showButtons() { - if (core == null) - return; - if (!mButtonsVisible) { - mButtonsVisible = true; - // Update page number text and slider - int index = mDocView.getDisplayedViewIndex(); - updatePageNumView(index); - mPageSlider.setMax((core.countPages()-1)*mPageSliderRes); - mPageSlider.setProgress(index * mPageSliderRes); - if (mTopBarMode == TopBarMode.Search) { - mSearchText.requestFocus(); - showKeyboard(); - } - - Animation anim = new TranslateAnimation(0, 0, -mTopBarSwitcher.getHeight(), 0); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) { - mTopBarSwitcher.setVisibility(View.VISIBLE); - } - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) {} - }); - mTopBarSwitcher.startAnimation(anim); - - anim = new TranslateAnimation(0, 0, mPageSlider.getHeight(), 0); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) { - mPageSlider.setVisibility(View.VISIBLE); - } - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) { - mPageNumberView.setVisibility(View.VISIBLE); - } - }); - mPageSlider.startAnimation(anim); - } - } - - private void hideButtons() { - if (mButtonsVisible) { - mButtonsVisible = false; - hideKeyboard(); - - Animation anim = new TranslateAnimation(0, 0, 0, -mTopBarSwitcher.getHeight()); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) {} - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) { - mTopBarSwitcher.setVisibility(View.INVISIBLE); - } - }); - mTopBarSwitcher.startAnimation(anim); - - anim = new TranslateAnimation(0, 0, 0, mPageSlider.getHeight()); - anim.setDuration(200); - anim.setAnimationListener(new Animation.AnimationListener() { - public void onAnimationStart(Animation animation) { - mPageNumberView.setVisibility(View.INVISIBLE); - } - public void onAnimationRepeat(Animation animation) {} - public void onAnimationEnd(Animation animation) { - mPageSlider.setVisibility(View.INVISIBLE); - } - }); - mPageSlider.startAnimation(anim); - } - } - - private void searchModeOn() { - if (mTopBarMode != TopBarMode.Search) { - mTopBarMode = TopBarMode.Search; - //Focus on EditTextWidget - mSearchText.requestFocus(); - showKeyboard(); - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - } - - private void searchModeOff() { - if (mTopBarMode == TopBarMode.Search) { - mTopBarMode = TopBarMode.Main; - hideKeyboard(); - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - SearchTaskResult.set(null); - // Make the ReaderView act on the change to mSearchTaskResult - // via overridden onChildSetup method. - mDocView.resetupChildren(); - } - } - - private void updatePageNumView(int index) { - if (core == null) - return; - mPageNumberView.setText(String.format("%d / %d", index + 1, core.countPages())); - } - - private void printDoc() { - if (!core.fileFormat().startsWith("PDF")) { - showInfo(getString(R.string.format_currently_not_supported)); - return; - } - - Intent myIntent = getIntent(); - Uri docUri = myIntent != null ? myIntent.getData() : null; - - if (docUri == null) { - showInfo(getString(R.string.print_failed)); - } - - if (docUri.getScheme() == null) - docUri = Uri.parse("file://"+docUri.toString()); - - Intent printIntent = new Intent(this, PrintDialogActivity.class); - printIntent.setDataAndType(docUri, "aplication/pdf"); - printIntent.putExtra("title", mFileName); - startActivityForResult(printIntent, PRINT_REQUEST); - } - - private void showInfo(String message) { - mInfoView.setText(message); - - int currentApiVersion = android.os.Build.VERSION.SDK_INT; - if (currentApiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { - SafeAnimatorInflater safe = new SafeAnimatorInflater((Activity)this, R.animator.info, (View)mInfoView); - } else { - mInfoView.setVisibility(View.VISIBLE); - mHandler.postDelayed(new Runnable() { - public void run() { - mInfoView.setVisibility(View.INVISIBLE); - } - }, 500); - } - } - - private void makeButtonsView() { - mButtonsView = getLayoutInflater().inflate(R.layout.buttons,null); - mFilenameView = (TextView)mButtonsView.findViewById(R.id.docNameText); - mPageSlider = (SeekBar)mButtonsView.findViewById(R.id.pageSlider); - mPageNumberView = (TextView)mButtonsView.findViewById(R.id.pageNumber); - mInfoView = (TextView)mButtonsView.findViewById(R.id.info); - mSearchButton = (ImageButton)mButtonsView.findViewById(R.id.searchButton); - mReflowButton = (ImageButton)mButtonsView.findViewById(R.id.reflowButton); - mOutlineButton = (ImageButton)mButtonsView.findViewById(R.id.outlineButton); - mAnnotButton = (ImageButton)mButtonsView.findViewById(R.id.editAnnotButton); - mAnnotTypeText = (TextView)mButtonsView.findViewById(R.id.annotType); - mTopBarSwitcher = (ViewAnimator)mButtonsView.findViewById(R.id.switcher); - mSearchBack = (ImageButton)mButtonsView.findViewById(R.id.searchBack); - mSearchFwd = (ImageButton)mButtonsView.findViewById(R.id.searchForward); - mSearchText = (EditText)mButtonsView.findViewById(R.id.searchText); - mLinkButton = (ImageButton)mButtonsView.findViewById(R.id.linkButton); - mMoreButton = (ImageButton)mButtonsView.findViewById(R.id.moreButton); - mProofButton = (ImageButton)mButtonsView.findViewById(R.id.proofButton); - mSepsButton = (ImageButton)mButtonsView.findViewById(R.id.sepsButton); - mTopBarSwitcher.setVisibility(View.INVISIBLE); - mPageNumberView.setVisibility(View.INVISIBLE); - mInfoView.setVisibility(View.INVISIBLE); - - mPageSlider.setVisibility(View.INVISIBLE); - if (!core.gprfSupported()) { - mProofButton.setVisibility(View.INVISIBLE); - } - mSepsButton.setVisibility(View.INVISIBLE); - } - - public void OnMoreButtonClick(View v) { - mTopBarMode = TopBarMode.More; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - public void OnCancelMoreButtonClick(View v) { - mTopBarMode = TopBarMode.Main; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - public void OnPrintButtonClick(View v) { - printDoc(); - } - - // start a proof activity with the given resolution. - public void proofWithResolution (int resolution) - { - mProofFile = core.startProof(resolution); - Uri uri = Uri.parse("file://"+mProofFile); - Intent intent = new Intent(this, MuPDFActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(uri); - // add the current page so it can be found when the activity is running - intent.putExtra("startingPage", mDocView.getDisplayedViewIndex()); - startActivityForResult(intent, PROOF_REQUEST); - } - - public void OnProofButtonClick(final View v) - { - // set up the menu or resolutions. - final PopupMenu popup = new PopupMenu(this, v); - popup.getMenu().add(0, 1, 0, "Select a resolution:"); - popup.getMenu().add(0, 72, 0, "72"); - popup.getMenu().add(0, 96, 0, "96"); - popup.getMenu().add(0, 150, 0, "150"); - popup.getMenu().add(0, 300, 0, "300"); - popup.getMenu().add(0, 600, 0, "600"); - popup.getMenu().add(0, 1200, 0, "1200"); - popup.getMenu().add(0, 2400, 0, "2400"); - - // prevent the first item from being dismissed. - // is there not a better way to do this? It requires minimum API 14 - MenuItem item = popup.getMenu().getItem(0); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - item.setActionView(new View(v.getContext())); - item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - return false; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - return false; - } - }); - - popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - int id = item.getItemId(); - if (id != 1) { - // it's a resolution. The id is also the resolution value - proofWithResolution(id); - return true; - } - return false; - } - }); - - popup.show(); - } - - public void OnSepsButtonClick(final View v) - { - if (isProofing()) { - - // get the current page - final int currentPage = mDocView.getDisplayedViewIndex(); - - // buid a popup menu based on the given separations - final PopupMenu menu = new PopupMenu(this, v); - - // This makes the popup menu display icons, which by default it does not do. - // I worry that this relies on the internals of PopupMenu, which could change. - try { - Field[] fields = menu.getClass().getDeclaredFields(); - for (Field field : fields) { - if ("mPopup".equals(field.getName())) { - field.setAccessible(true); - Object menuPopupHelper = field.get(menu); - Class classPopupHelper = Class.forName(menuPopupHelper - .getClass().getName()); - Method setForceIcons = classPopupHelper.getMethod( - "setForceShowIcon", boolean.class); - setForceIcons.invoke(menuPopupHelper, true); - break; - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - // get the maximum number of seps on any page. - // We use this to dimension an array further down - int maxSeps = 0; - int numPages = core.countPages(); - for (int page=0; pagemaxSeps) - maxSeps = numSeps; - } - - // if this is the first time, create the "enabled" array - if (mSepEnabled==null) { - mSepEnabled = new boolean[numPages][maxSeps]; - for (int page=0; page> 24) & 0xFF; - int red = (sep.rgba >> 16) & 0xFF; - int green = (sep.rgba >> 8 ) & 0xFF; - int blue = (sep.rgba >> 0 ) & 0xFF; - int color = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); - - ShapeDrawable swatch = new ShapeDrawable (new RectShape()); - swatch.setIntrinsicHeight(iconSize); - swatch.setIntrinsicWidth(iconSize); - swatch.setBounds(new Rect(0, 0, iconSize, iconSize)); - swatch.getPaint().setColor(color); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - item.setIcon(swatch); - - // check it (or not) - item.setChecked(mSepEnabled[currentPage][i]); - - // establishing a menu item listener - item.setOnMenuItemClickListener(new OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // someone tapped a menu item. get the ID - int sep = item.getItemId(); - - // toggle the sep - mSepEnabled[currentPage][sep] = !mSepEnabled[currentPage][sep]; - item.setChecked(mSepEnabled[currentPage][sep]); - core.controlSepOnPage(currentPage, sep, !mSepEnabled[currentPage][sep]); - - // prevent the menu from being dismissed by these items - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); - item.setActionView(new View(v.getContext())); - item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - return false; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - return false; - } - }); - return false; - } - }); - - // tell core to enable or disable each sep as appropriate - // but don't refresh the page yet. - core.controlSepOnPage(currentPage, i, !mSepEnabled[currentPage][i]); - } - - // add one for done - MenuItem itemDone = menu.getMenu().add(0, 0, 0, "Done"); - itemDone.setOnMenuItemClickListener(new OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // refresh the view - mDocView.refresh(false); - return true; - } - }); - - // show the menu - menu.show(); - } - - } - - public void OnCopyTextButtonClick(View v) { - mTopBarMode = TopBarMode.Accept; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - mAcceptMode = AcceptMode.CopyText; - mDocView.setMode(MuPDFReaderView.Mode.Selecting); - mAnnotTypeText.setText(getString(R.string.copy_text)); - showInfo(getString(R.string.select_text)); - } - - public void OnEditAnnotButtonClick(View v) { - mTopBarMode = TopBarMode.Annot; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - public void OnCancelAnnotButtonClick(View v) { - mTopBarMode = TopBarMode.More; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - public void OnHighlightButtonClick(View v) { - mTopBarMode = TopBarMode.Accept; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - mAcceptMode = AcceptMode.Highlight; - mDocView.setMode(MuPDFReaderView.Mode.Selecting); - mAnnotTypeText.setText(R.string.highlight); - showInfo(getString(R.string.select_text)); - } - - public void OnUnderlineButtonClick(View v) { - mTopBarMode = TopBarMode.Accept; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - mAcceptMode = AcceptMode.Underline; - mDocView.setMode(MuPDFReaderView.Mode.Selecting); - mAnnotTypeText.setText(R.string.underline); - showInfo(getString(R.string.select_text)); - } - - public void OnStrikeOutButtonClick(View v) { - mTopBarMode = TopBarMode.Accept; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - mAcceptMode = AcceptMode.StrikeOut; - mDocView.setMode(MuPDFReaderView.Mode.Selecting); - mAnnotTypeText.setText(R.string.strike_out); - showInfo(getString(R.string.select_text)); - } - - public void OnInkButtonClick(View v) { - mTopBarMode = TopBarMode.Accept; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - mAcceptMode = AcceptMode.Ink; - mDocView.setMode(MuPDFReaderView.Mode.Drawing); - mAnnotTypeText.setText(R.string.ink); - showInfo(getString(R.string.draw_annotation)); - } - - public void OnCancelAcceptButtonClick(View v) { - MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); - if (pageView != null) { - pageView.deselectText(); - pageView.cancelDraw(); - } - mDocView.setMode(MuPDFReaderView.Mode.Viewing); - switch (mAcceptMode) { - case CopyText: - mTopBarMode = TopBarMode.More; - break; - default: - mTopBarMode = TopBarMode.Annot; - break; - } - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - public void OnAcceptButtonClick(View v) { - MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); - boolean success = false; - switch (mAcceptMode) { - case CopyText: - if (pageView != null) - success = pageView.copySelection(); - mTopBarMode = TopBarMode.More; - showInfo(success?getString(R.string.copied_to_clipboard):getString(R.string.no_text_selected)); - break; - - case Highlight: - if (pageView != null) - success = pageView.markupSelection(Annotation.Type.HIGHLIGHT); - mTopBarMode = TopBarMode.Annot; - if (!success) - showInfo(getString(R.string.no_text_selected)); - break; - - case Underline: - if (pageView != null) - success = pageView.markupSelection(Annotation.Type.UNDERLINE); - mTopBarMode = TopBarMode.Annot; - if (!success) - showInfo(getString(R.string.no_text_selected)); - break; - - case StrikeOut: - if (pageView != null) - success = pageView.markupSelection(Annotation.Type.STRIKEOUT); - mTopBarMode = TopBarMode.Annot; - if (!success) - showInfo(getString(R.string.no_text_selected)); - break; - - case Ink: - if (pageView != null) - success = pageView.saveDraw(); - mTopBarMode = TopBarMode.Annot; - if (!success) - showInfo(getString(R.string.nothing_to_save)); - break; - } - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - mDocView.setMode(MuPDFReaderView.Mode.Viewing); - } - - public void OnCancelSearchButtonClick(View v) { - searchModeOff(); - } - - public void OnDeleteButtonClick(View v) { - MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); - if (pageView != null) - pageView.deleteSelectedAnnotation(); - mTopBarMode = TopBarMode.Annot; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - public void OnCancelDeleteButtonClick(View v) { - MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); - if (pageView != null) - pageView.deselectAnnotation(); - mTopBarMode = TopBarMode.Annot; - mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); - } - - private void showKeyboard() { - InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) - imm.showSoftInput(mSearchText, 0); - } - - private void hideKeyboard() { - InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) - imm.hideSoftInputFromWindow(mSearchText.getWindowToken(), 0); - } - - private void search(int direction) { - hideKeyboard(); - int displayPage = mDocView.getDisplayedViewIndex(); - SearchTaskResult r = SearchTaskResult.get(); - int searchPage = r != null ? r.pageNumber : -1; - mSearchTask.go(mSearchText.getText().toString(), direction, displayPage, searchPage); - } - - @Override - public boolean onSearchRequested() { - if (mButtonsVisible && mTopBarMode == TopBarMode.Search) { - hideButtons(); - } else { - showButtons(); - searchModeOn(); - } - return super.onSearchRequested(); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (mButtonsVisible && mTopBarMode != TopBarMode.Search) { - hideButtons(); - } else { - showButtons(); - searchModeOff(); - } - return super.onPrepareOptionsMenu(menu); - } - - @Override - protected void onStart() { - if (core != null) - { - core.startAlerts(); - createAlertWaiter(); - } - - super.onStart(); - } - - @Override - protected void onStop() { - if (core != null) - { - destroyAlertWaiter(); - core.stopAlerts(); - } - - super.onStop(); - } - - @Override - public void onBackPressed() { - if (core != null && core.hasChanges()) { - DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (which == AlertDialog.BUTTON_POSITIVE) - core.save(); - - finish(); - } - }; - AlertDialog alert = mAlertBuilder.create(); - alert.setTitle("MuPDF"); - alert.setMessage(getString(R.string.document_has_changes_save_them_)); - alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.yes), listener); - alert.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.no), listener); - alert.show(); - } else { - super.onBackPressed(); - } - } - - @Override - public void performPickFor(FilePicker picker) { - mFilePicker = picker; - Intent intent = new Intent(this, ChoosePDFActivity.class); - intent.setAction(ChoosePDFActivity.PICK_KEY_FILE); - startActivityForResult(intent, FILEPICK_REQUEST); - } - -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFAlert.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFAlert.java deleted file mode 100644 index 76ed3a65..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFAlert.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.artifex.mupdfdemo; - -public class MuPDFAlert { - public enum IconType {Error,Warning,Question,Status}; - public enum ButtonPressed {None,Ok,Cancel,No,Yes}; - public enum ButtonGroupType {Ok,OkCancel,YesNo,YesNoCancel}; - - public final String message; - public final IconType iconType; - public final ButtonGroupType buttonGroupType; - public final String title; - public ButtonPressed buttonPressed; - - MuPDFAlert(String aMessage, IconType aIconType, ButtonGroupType aButtonGroupType, String aTitle, ButtonPressed aButtonPressed) { - message = aMessage; - iconType = aIconType; - buttonGroupType = aButtonGroupType; - title = aTitle; - buttonPressed = aButtonPressed; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java deleted file mode 100644 index 5d65768f..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.artifex.mupdfdemo; - -// Version of MuPDFAlert without enums to simplify JNI -public class MuPDFAlertInternal { - public final String message; - public final int iconType; - public final int buttonGroupType; - public final String title; - public int buttonPressed; - - MuPDFAlertInternal(String aMessage, int aIconType, int aButtonGroupType, String aTitle, int aButtonPressed) { - message = aMessage; - iconType = aIconType; - buttonGroupType = aButtonGroupType; - title = aTitle; - buttonPressed = aButtonPressed; - } - - MuPDFAlertInternal(MuPDFAlert alert) { - message = alert.message; - iconType = alert.iconType.ordinal(); - buttonGroupType = alert.buttonGroupType.ordinal(); - title = alert.message; - buttonPressed = alert.buttonPressed.ordinal(); - } - - MuPDFAlert toAlert() { - return new MuPDFAlert(message, MuPDFAlert.IconType.values()[iconType], MuPDFAlert.ButtonGroupType.values()[buttonGroupType], title, MuPDFAlert.ButtonPressed.values()[buttonPressed]); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java deleted file mode 100644 index b95d8e93..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.artifex.mupdfdemo; - -public abstract class MuPDFCancellableTaskDefinition implements CancellableTaskDefinition -{ - private MuPDFCore.Cookie cookie; - - public MuPDFCancellableTaskDefinition(MuPDFCore core) - { - this.cookie = core.new Cookie(); - } - - @Override - public void doCancel() - { - if (cookie == null) - return; - - cookie.abort(); - } - - @Override - public void doCleanup() - { - if (cookie == null) - return; - - cookie.destroy(); - cookie = null; - } - - @Override - public final Result doInBackground(Params ... params) - { - return doInBackground(cookie, params); - } - - public abstract Result doInBackground(MuPDFCore.Cookie cookie, Params ... params); -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java deleted file mode 100644 index 80174416..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFCore.java +++ /dev/null @@ -1,402 +0,0 @@ -package com.artifex.mupdfdemo; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.PointF; -import android.graphics.RectF; - -import java.util.ArrayList; - -public class MuPDFCore -{ - /* load our native library */ - private static boolean gs_so_available = false; - static { - System.out.println("Loading dll"); - System.loadLibrary("mupdf_java"); - System.out.println("Loaded dll"); - if (gprfSupportedInternal()) - { - try { - System.loadLibrary("gs"); - gs_so_available = true; - } - catch (UnsatisfiedLinkError e) { - gs_so_available = false; - } - } - } - - /* Readable members */ - private int numPages = -1; - private float pageWidth; - private float pageHeight; - private long globals; - private byte fileBuffer[]; - private String file_format; - private boolean isUnencryptedPDF; - private final boolean wasOpenedFromBuffer; - - /* The native functions */ - private static native boolean gprfSupportedInternal(); - private native long openFile(String filename); - private native long openBuffer(String magic); - private native String fileFormatInternal(); - private native boolean isUnencryptedPDFInternal(); - private native int countPagesInternal(); - private native void gotoPageInternal(int localActionPageNum); - private native float getPageWidth(); - private native float getPageHeight(); - private native void drawPage(Bitmap bitmap, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH, - long cookiePtr); - private native void updatePageInternal(Bitmap bitmap, - int page, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH, - long cookiePtr); - private native RectF[] searchPage(String text); - private native TextChar[][][][] text(); - private native byte[] textAsHtml(); - private native void addMarkupAnnotationInternal(PointF[] quadPoints, int type); - private native void addInkAnnotationInternal(PointF[][] arcs); - private native void deleteAnnotationInternal(int annot_index); - private native int passClickEventInternal(int page, float x, float y); - private native void setFocusedWidgetChoiceSelectedInternal(String [] selected); - private native String [] getFocusedWidgetChoiceSelected(); - private native String [] getFocusedWidgetChoiceOptions(); - private native int getFocusedWidgetSignatureState(); - private native String checkFocusedSignatureInternal(); - private native boolean signFocusedSignatureInternal(String keyFile, String password); - private native int setFocusedWidgetTextInternal(String text); - private native String getFocusedWidgetTextInternal(); - private native int getFocusedWidgetTypeInternal(); - private native LinkInfo [] getPageLinksInternal(int page); - private native RectF[] getWidgetAreasInternal(int page); - private native Annotation[] getAnnotationsInternal(int page); - private native OutlineItem [] getOutlineInternal(); - private native boolean hasOutlineInternal(); - private native boolean needsPasswordInternal(); - private native boolean authenticatePasswordInternal(String password); - private native MuPDFAlertInternal waitForAlertInternal(); - private native void replyToAlertInternal(MuPDFAlertInternal alert); - private native void startAlertsInternal(); - private native void stopAlertsInternal(); - private native void destroying(); - private native boolean hasChangesInternal(); - private native void saveInternal(); - private native long createCookie(); - private native void destroyCookie(long cookie); - private native void abortCookie(long cookie); - - private native String startProofInternal(int resolution); - private native void endProofInternal(String filename); - private native int getNumSepsOnPageInternal(int page); - private native int controlSepOnPageInternal(int page, int sep, boolean disable); - private native Separation getSepInternal(int page, int sep); - - public native boolean javascriptSupported(); - - public class Cookie - { - private final long cookiePtr; - - public Cookie() - { - cookiePtr = createCookie(); - if (cookiePtr == 0) - throw new OutOfMemoryError(); - } - - public void abort() - { - abortCookie(cookiePtr); - } - - public void destroy() - { - // We could do this in finalize, but there's no guarantee that - // a finalize will occur before the muPDF context occurs. - destroyCookie(cookiePtr); - } - } - - public MuPDFCore(Context context, String filename) throws Exception - { - globals = openFile(filename); - if (globals == 0) - { - throw new Exception(String.format(context.getString(R.string.cannot_open_file_Path), filename)); - } - file_format = fileFormatInternal(); - isUnencryptedPDF = isUnencryptedPDFInternal(); - wasOpenedFromBuffer = false; - } - - public MuPDFCore(Context context, byte buffer[], String magic) throws Exception { - fileBuffer = buffer; - globals = openBuffer(magic != null ? magic : ""); - if (globals == 0) - { - throw new Exception(context.getString(R.string.cannot_open_buffer)); - } - file_format = fileFormatInternal(); - isUnencryptedPDF = isUnencryptedPDFInternal(); - wasOpenedFromBuffer = true; - } - - public int countPages() - { - if (numPages < 0) - numPages = countPagesSynchronized(); - return numPages; - } - - public String fileFormat() - { - return file_format; - } - - public boolean isUnencryptedPDF() - { - return isUnencryptedPDF; - } - - public boolean wasOpenedFromBuffer() - { - return wasOpenedFromBuffer; - } - - private synchronized int countPagesSynchronized() { - return countPagesInternal(); - } - - /* Shim function */ - private void gotoPage(int page) - { - if (page > numPages-1) - page = numPages-1; - else if (page < 0) - page = 0; - gotoPageInternal(page); - this.pageWidth = getPageWidth(); - this.pageHeight = getPageHeight(); - } - - public synchronized PointF getPageSize(int page) { - gotoPage(page); - return new PointF(pageWidth, pageHeight); - } - - public MuPDFAlert waitForAlert() { - MuPDFAlertInternal alert = waitForAlertInternal(); - return alert != null ? alert.toAlert() : null; - } - - public void replyToAlert(MuPDFAlert alert) { - replyToAlertInternal(new MuPDFAlertInternal(alert)); - } - - public void stopAlerts() { - stopAlertsInternal(); - } - - public void startAlerts() { - startAlertsInternal(); - } - - public synchronized void onDestroy() { - destroying(); - globals = 0; - } - - public synchronized void drawPage(Bitmap bm, int page, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH, - MuPDFCore.Cookie cookie) { - gotoPage(page); - drawPage(bm, pageW, pageH, patchX, patchY, patchW, patchH, cookie.cookiePtr); - } - - public synchronized void updatePage(Bitmap bm, int page, - int pageW, int pageH, - int patchX, int patchY, - int patchW, int patchH, - MuPDFCore.Cookie cookie) { - updatePageInternal(bm, page, pageW, pageH, patchX, patchY, patchW, patchH, cookie.cookiePtr); - } - - public synchronized PassClickResult passClickEvent(int page, float x, float y) { - boolean changed = passClickEventInternal(page, x, y) != 0; - - switch (WidgetType.values()[getFocusedWidgetTypeInternal()]) - { - case TEXT: - return new PassClickResultText(changed, getFocusedWidgetTextInternal()); - case LISTBOX: - case COMBOBOX: - return new PassClickResultChoice(changed, getFocusedWidgetChoiceOptions(), getFocusedWidgetChoiceSelected()); - case SIGNATURE: - return new PassClickResultSignature(changed, getFocusedWidgetSignatureState()); - default: - return new PassClickResult(changed); - } - - } - - public synchronized boolean setFocusedWidgetText(int page, String text) { - boolean success; - gotoPage(page); - success = setFocusedWidgetTextInternal(text) != 0 ? true : false; - - return success; - } - - public synchronized void setFocusedWidgetChoiceSelected(String [] selected) { - setFocusedWidgetChoiceSelectedInternal(selected); - } - - public synchronized String checkFocusedSignature() { - return checkFocusedSignatureInternal(); - } - - public synchronized boolean signFocusedSignature(String keyFile, String password) { - return signFocusedSignatureInternal(keyFile, password); - } - - public synchronized LinkInfo [] getPageLinks(int page) { - return getPageLinksInternal(page); - } - - public synchronized RectF [] getWidgetAreas(int page) { - return getWidgetAreasInternal(page); - } - - public synchronized Annotation [] getAnnoations(int page) { - return getAnnotationsInternal(page); - } - - public synchronized RectF [] searchPage(int page, String text) { - gotoPage(page); - return searchPage(text); - } - - public synchronized byte[] html(int page) { - gotoPage(page); - return textAsHtml(); - } - - public synchronized TextWord [][] textLines(int page) { - gotoPage(page); - TextChar[][][][] chars = text(); - - // The text of the page held in a hierarchy (blocks, lines, spans). - // Currently we don't need to distinguish the blocks level or - // the spans, and we need to collect the text into words. - ArrayList lns = new ArrayList(); - - for (TextChar[][][] bl: chars) { - if (bl == null) - continue; - for (TextChar[][] ln: bl) { - ArrayList wds = new ArrayList(); - TextWord wd = new TextWord(); - - for (TextChar[] sp: ln) { - for (TextChar tc: sp) { - if (tc.c != ' ') { - wd.Add(tc); - } else if (wd.w.length() > 0) { - wds.add(wd); - wd = new TextWord(); - } - } - } - - if (wd.w.length() > 0) - wds.add(wd); - - if (wds.size() > 0) - lns.add(wds.toArray(new TextWord[wds.size()])); - } - } - - return lns.toArray(new TextWord[lns.size()][]); - } - - public synchronized void addMarkupAnnotation(int page, PointF[] quadPoints, Annotation.Type type) { - gotoPage(page); - addMarkupAnnotationInternal(quadPoints, type.ordinal()); - } - - public synchronized void addInkAnnotation(int page, PointF[][] arcs) { - gotoPage(page); - addInkAnnotationInternal(arcs); - } - - public synchronized void deleteAnnotation(int page, int annot_index) { - gotoPage(page); - deleteAnnotationInternal(annot_index); - } - - public synchronized boolean hasOutline() { - return hasOutlineInternal(); - } - - public synchronized OutlineItem [] getOutline() { - return getOutlineInternal(); - } - - public synchronized boolean needsPassword() { - return needsPasswordInternal(); - } - - public synchronized boolean authenticatePassword(String password) { - return authenticatePasswordInternal(password); - } - - public synchronized boolean hasChanges() { - return hasChangesInternal(); - } - - public synchronized void save() { - saveInternal(); - } - - public synchronized String startProof(int resolution) { - return startProofInternal(resolution); - } - - public synchronized void endProof(String filename) { - endProofInternal(filename); - } - - public static boolean gprfSupported() { - if (gs_so_available == false) - return false; - return gprfSupportedInternal(); - } - - public boolean canProof() - { - String format = fileFormat(); - if (format.contains("PDF")) - return true; - return false; - } - - public synchronized int getNumSepsOnPage(int page) { - return getNumSepsOnPageInternal(page); - } - - public synchronized int controlSepOnPage(int page, int sep, boolean disable) { - return controlSepOnPageInternal(page, sep, disable); - } - - public synchronized Separation getSep(int page, int sep) { - return getSepInternal(page, sep); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java deleted file mode 100644 index abdac845..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.PointF; -import android.util.SparseArray; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -public class MuPDFPageAdapter extends BaseAdapter { - private final Context mContext; - private final FilePicker.FilePickerSupport mFilePickerSupport; - private final MuPDFCore mCore; - private final SparseArray mPageSizes = new SparseArray(); - private Bitmap mSharedHqBm; - - public MuPDFPageAdapter(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core) { - mContext = c; - mFilePickerSupport = filePickerSupport; - mCore = core; - } - - public int getCount() { - return mCore.countPages(); - } - - public Object getItem(int position) { - return null; - } - - public long getItemId(int position) { - return 0; - } - - public void releaseBitmaps() - { - // recycle and release the shared bitmap. - if (mSharedHqBm!=null) - mSharedHqBm.recycle(); - mSharedHqBm = null; - } - - public View getView(final int position, View convertView, ViewGroup parent) { - final MuPDFPageView pageView; - if (convertView == null) { - if (mSharedHqBm == null || mSharedHqBm.getWidth() != parent.getWidth() || mSharedHqBm.getHeight() != parent.getHeight()) - mSharedHqBm = Bitmap.createBitmap(parent.getWidth(), parent.getHeight(), Bitmap.Config.ARGB_8888); - - pageView = new MuPDFPageView(mContext, mFilePickerSupport, mCore, new Point(parent.getWidth(), parent.getHeight()), mSharedHqBm); - } else { - pageView = (MuPDFPageView) convertView; - } - - PointF pageSize = mPageSizes.get(position); - if (pageSize != null) { - // We already know the page size. Set it up - // immediately - pageView.setPage(position, pageSize); - } else { - // Page size as yet unknown. Blank it for now, and - // start a background task to find the size - pageView.blank(position); - AsyncTask sizingTask = new AsyncTask() { - @Override - protected PointF doInBackground(Void... arg0) { - return mCore.getPageSize(position); - } - - @Override - protected void onPostExecute(PointF result) { - super.onPostExecute(result); - // We now know the page size - mPageSizes.put(position, result); - // Check that this view hasn't been reused for - // another page since we started - if (pageView.getPage() == position) - pageView.setPage(position, result); - } - }; - - sizingTask.execute((Void)null); - } - return pageView; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java deleted file mode 100644 index ff6b6bbb..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFPageView.java +++ /dev/null @@ -1,692 +0,0 @@ -package com.artifex.mupdfdemo; - -import java.util.ArrayList; - -import com.artifex.mupdfdemo.MuPDFCore.Cookie; - -import android.annotation.TargetApi; -import android.app.AlertDialog; -import android.content.ClipData; -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Build; -import android.text.method.PasswordTransformationMethod; -import android.view.LayoutInflater; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; - -/* This enum should be kept in line with the cooresponding C enum in mupdf.c */ -enum SignatureState { - NoSupport, - Unsigned, - Signed -} - -abstract class PassClickResultVisitor { - public abstract void visitText(PassClickResultText result); - public abstract void visitChoice(PassClickResultChoice result); - public abstract void visitSignature(PassClickResultSignature result); -} - -class PassClickResult { - public final boolean changed; - - public PassClickResult(boolean _changed) { - changed = _changed; - } - - public void acceptVisitor(PassClickResultVisitor visitor) { - } -} - -class PassClickResultText extends PassClickResult { - public final String text; - - public PassClickResultText(boolean _changed, String _text) { - super(_changed); - text = _text; - } - - public void acceptVisitor(PassClickResultVisitor visitor) { - visitor.visitText(this); - } -} - -class PassClickResultChoice extends PassClickResult { - public final String [] options; - public final String [] selected; - - public PassClickResultChoice(boolean _changed, String [] _options, String [] _selected) { - super(_changed); - options = _options; - selected = _selected; - } - - public void acceptVisitor(PassClickResultVisitor visitor) { - visitor.visitChoice(this); - } -} - -class PassClickResultSignature extends PassClickResult { - public final SignatureState state; - - public PassClickResultSignature(boolean _changed, int _state) { - super(_changed); - state = SignatureState.values()[_state]; - } - - public void acceptVisitor(PassClickResultVisitor visitor) { - visitor.visitSignature(this); - } -} - -public class MuPDFPageView extends PageView implements MuPDFView { - final private FilePicker.FilePickerSupport mFilePickerSupport; - private final MuPDFCore mCore; - private AsyncTask mPassClick; - private RectF mWidgetAreas[]; - private Annotation mAnnotations[]; - private int mSelectedAnnotationIndex = -1; - private AsyncTask mLoadWidgetAreas; - private AsyncTask mLoadAnnotations; - private AlertDialog.Builder mTextEntryBuilder; - private AlertDialog.Builder mChoiceEntryBuilder; - private AlertDialog.Builder mSigningDialogBuilder; - private AlertDialog.Builder mSignatureReportBuilder; - private AlertDialog.Builder mPasswordEntryBuilder; - private EditText mPasswordText; - private AlertDialog mTextEntry; - private AlertDialog mPasswordEntry; - private EditText mEditText; - private AsyncTask mSetWidgetText; - private AsyncTask mSetWidgetChoice; - private AsyncTask mAddStrikeOut; - private AsyncTask mAddInk; - private AsyncTask mDeleteAnnotation; - private AsyncTask mCheckSignature; - private AsyncTask mSign; - private Runnable changeReporter; - - public MuPDFPageView(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core, Point parentSize, Bitmap sharedHqBm) { - super(c, parentSize, sharedHqBm); - mFilePickerSupport = filePickerSupport; - mCore = core; - mTextEntryBuilder = new AlertDialog.Builder(c); - mTextEntryBuilder.setTitle(getContext().getString(R.string.fill_out_text_field)); - LayoutInflater inflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mEditText = (EditText)inflater.inflate(R.layout.textentry, null); - mTextEntryBuilder.setView(mEditText); - mTextEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - mTextEntryBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mSetWidgetText = new AsyncTask () { - @Override - protected Boolean doInBackground(String... arg0) { - return mCore.setFocusedWidgetText(mPageNumber, arg0[0]); - } - @Override - protected void onPostExecute(Boolean result) { - changeReporter.run(); - if (!result) - invokeTextDialog(mEditText.getText().toString()); - } - }; - - mSetWidgetText.execute(mEditText.getText().toString()); - } - }); - mTextEntry = mTextEntryBuilder.create(); - - mChoiceEntryBuilder = new AlertDialog.Builder(c); - mChoiceEntryBuilder.setTitle(getContext().getString(R.string.choose_value)); - - mSigningDialogBuilder = new AlertDialog.Builder(c); - mSigningDialogBuilder.setTitle("Select certificate and sign?"); - mSigningDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - mSigningDialogBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - FilePicker picker = new FilePicker(mFilePickerSupport) { - @Override - void onPick(Uri uri) { - signWithKeyFile(uri); - } - }; - - picker.pick(); - } - }); - - mSignatureReportBuilder = new AlertDialog.Builder(c); - mSignatureReportBuilder.setTitle("Signature checked"); - mSignatureReportBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - - mPasswordText = new EditText(c); - mPasswordText.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); - mPasswordText.setTransformationMethod(new PasswordTransformationMethod()); - - mPasswordEntryBuilder = new AlertDialog.Builder(c); - mPasswordEntryBuilder.setTitle(R.string.enter_password); - mPasswordEntryBuilder.setView(mPasswordText); - mPasswordEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - - mPasswordEntry = mPasswordEntryBuilder.create(); - } - - private void signWithKeyFile(final Uri uri) { - mPasswordEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - mPasswordEntry.setButton(AlertDialog.BUTTON_POSITIVE, "Sign", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - signWithKeyFileAndPassword(uri, mPasswordText.getText().toString()); - } - }); - - mPasswordEntry.show(); - } - - private void signWithKeyFileAndPassword(final Uri uri, final String password) { - mSign = new AsyncTask() { - @Override - protected Boolean doInBackground(Void... params) { - return mCore.signFocusedSignature(Uri.decode(uri.getEncodedPath()), password); - } - @Override - protected void onPostExecute(Boolean result) { - if (result) - { - changeReporter.run(); - } - else - { - mPasswordText.setText(""); - signWithKeyFile(uri); - } - } - - }; - - mSign.execute(); - } - - public LinkInfo hitLink(float x, float y) { - // Since link highlighting was implemented, the super class - // PageView has had sufficient information to be able to - // perform this method directly. Making that change would - // make MuPDFCore.hitLinkPage superfluous. - float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - float docRelX = (x - getLeft())/scale; - float docRelY = (y - getTop())/scale; - - for (LinkInfo l: mLinks) - if (l.rect.contains(docRelX, docRelY)) - return l; - - return null; - } - - private void invokeTextDialog(String text) { - mEditText.setText(text); - mTextEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - mTextEntry.show(); - } - - private void invokeChoiceDialog(final String [] options) { - mChoiceEntryBuilder.setItems(options, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mSetWidgetChoice = new AsyncTask() { - @Override - protected Void doInBackground(String... params) { - String [] sel = {params[0]}; - mCore.setFocusedWidgetChoiceSelected(sel); - return null; - } - - @Override - protected void onPostExecute(Void result) { - changeReporter.run(); - } - }; - - mSetWidgetChoice.execute(options[which]); - } - }); - AlertDialog dialog = mChoiceEntryBuilder.create(); - dialog.show(); - } - - private void invokeSignatureCheckingDialog() { - mCheckSignature = new AsyncTask () { - @Override - protected String doInBackground(Void... params) { - return mCore.checkFocusedSignature(); - } - @Override - protected void onPostExecute(String result) { - AlertDialog report = mSignatureReportBuilder.create(); - report.setMessage(result); - report.show(); - } - }; - - mCheckSignature.execute(); - } - - private void invokeSigningDialog() { - AlertDialog dialog = mSigningDialogBuilder.create(); - dialog.show(); - } - - private void warnNoSignatureSupport() { - AlertDialog dialog = mSignatureReportBuilder.create(); - dialog.setTitle("App built with no signature support"); - dialog.show(); - } - - public void setChangeReporter(Runnable reporter) { - changeReporter = reporter; - } - - public Hit passClickEvent(float x, float y) { - float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - final float docRelX = (x - getLeft())/scale; - final float docRelY = (y - getTop())/scale; - boolean hit = false; - int i; - - if (mAnnotations != null) { - for (i = 0; i < mAnnotations.length; i++) - if (mAnnotations[i].contains(docRelX, docRelY)) { - hit = true; - break; - } - - if (hit) { - switch (mAnnotations[i].type) { - case HIGHLIGHT: - case UNDERLINE: - case SQUIGGLY: - case STRIKEOUT: - case INK: - mSelectedAnnotationIndex = i; - setItemSelectBox(mAnnotations[i]); - return Hit.Annotation; - } - } - } - - mSelectedAnnotationIndex = -1; - setItemSelectBox(null); - - if (!mCore.javascriptSupported()) - return Hit.Nothing; - - if (mWidgetAreas != null) { - for (i = 0; i < mWidgetAreas.length && !hit; i++) - if (mWidgetAreas[i].contains(docRelX, docRelY)) - hit = true; - } - - if (hit) { - mPassClick = new AsyncTask() { - @Override - protected PassClickResult doInBackground(Void... arg0) { - return mCore.passClickEvent(mPageNumber, docRelX, docRelY); - } - - @Override - protected void onPostExecute(PassClickResult result) { - if (result.changed) { - changeReporter.run(); - } - - result.acceptVisitor(new PassClickResultVisitor() { - @Override - public void visitText(PassClickResultText result) { - invokeTextDialog(result.text); - } - - @Override - public void visitChoice(PassClickResultChoice result) { - invokeChoiceDialog(result.options); - } - - @Override - public void visitSignature(PassClickResultSignature result) { - switch (result.state) { - case NoSupport: - warnNoSignatureSupport(); - break; - case Unsigned: - invokeSigningDialog(); - break; - case Signed: - invokeSignatureCheckingDialog(); - break; - } - } - }); - } - }; - - mPassClick.execute(); - return Hit.Widget; - } - - return Hit.Nothing; - } - - @TargetApi(11) - public boolean copySelection() { - final StringBuilder text = new StringBuilder(); - - processSelectedText(new TextProcessor() { - StringBuilder line; - - public void onStartLine() { - line = new StringBuilder(); - } - - public void onWord(TextWord word) { - if (line.length() > 0) - line.append(' '); - line.append(word.w); - } - - public void onEndLine() { - if (text.length() > 0) - text.append('\n'); - text.append(line); - } - }); - - if (text.length() == 0) - return false; - - int currentApiVersion = android.os.Build.VERSION.SDK_INT; - if (currentApiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { - android.content.ClipboardManager cm = (android.content.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE); - - cm.setPrimaryClip(ClipData.newPlainText("MuPDF", text)); - } else { - android.text.ClipboardManager cm = (android.text.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE); - cm.setText(text); - } - - deselectText(); - - return true; - } - - public boolean markupSelection(final Annotation.Type type) { - final ArrayList quadPoints = new ArrayList(); - processSelectedText(new TextProcessor() { - RectF rect; - - public void onStartLine() { - rect = new RectF(); - } - - public void onWord(TextWord word) { - rect.union(word); - } - - public void onEndLine() { - if (!rect.isEmpty()) { - quadPoints.add(new PointF(rect.left, rect.bottom)); - quadPoints.add(new PointF(rect.right, rect.bottom)); - quadPoints.add(new PointF(rect.right, rect.top)); - quadPoints.add(new PointF(rect.left, rect.top)); - } - } - }); - - if (quadPoints.size() == 0) - return false; - - mAddStrikeOut = new AsyncTask() { - @Override - protected Void doInBackground(PointF[]... params) { - addMarkup(params[0], type); - return null; - } - - @Override - protected void onPostExecute(Void result) { - loadAnnotations(); - update(); - } - }; - - mAddStrikeOut.execute(quadPoints.toArray(new PointF[quadPoints.size()])); - - deselectText(); - - return true; - } - - public void deleteSelectedAnnotation() { - if (mSelectedAnnotationIndex != -1) { - if (mDeleteAnnotation != null) - mDeleteAnnotation.cancel(true); - - mDeleteAnnotation = new AsyncTask() { - @Override - protected Void doInBackground(Integer... params) { - mCore.deleteAnnotation(mPageNumber, params[0]); - return null; - } - - @Override - protected void onPostExecute(Void result) { - loadAnnotations(); - update(); - } - }; - - mDeleteAnnotation.execute(mSelectedAnnotationIndex); - - mSelectedAnnotationIndex = -1; - setItemSelectBox(null); - } - } - - public void deselectAnnotation() { - mSelectedAnnotationIndex = -1; - setItemSelectBox(null); - } - - public boolean saveDraw() { - PointF[][] path = getDraw(); - - if (path == null) - return false; - - if (mAddInk != null) { - mAddInk.cancel(true); - mAddInk = null; - } - mAddInk = new AsyncTask() { - @Override - protected Void doInBackground(PointF[][]... params) { - mCore.addInkAnnotation(mPageNumber, params[0]); - return null; - } - - @Override - protected void onPostExecute(Void result) { - loadAnnotations(); - update(); - } - - }; - - mAddInk.execute(getDraw()); - cancelDraw(); - - return true; - } - - - @Override - protected CancellableTaskDefinition getDrawPageTask(final Bitmap bm, final int sizeX, final int sizeY, - final int patchX, final int patchY, final int patchWidth, final int patchHeight) { - return new MuPDFCancellableTaskDefinition(mCore) { - @Override - public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) { - // Workaround bug in Android Honeycomb 3.x, where the bitmap generation count - // is not incremented when drawing. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && - Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) - bm.eraseColor(0); - mCore.drawPage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie); - return null; - } - }; - - } - - protected CancellableTaskDefinition getUpdatePageTask(final Bitmap bm, final int sizeX, final int sizeY, - final int patchX, final int patchY, final int patchWidth, final int patchHeight) - { - return new MuPDFCancellableTaskDefinition(mCore) { - - @Override - public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) { - // Workaround bug in Android Honeycomb 3.x, where the bitmap generation count - // is not incremented when drawing. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && - Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) - bm.eraseColor(0); - mCore.updatePage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie); - return null; - } - }; - } - - @Override - protected LinkInfo[] getLinkInfo() { - return mCore.getPageLinks(mPageNumber); - } - - @Override - protected TextWord[][] getText() { - return mCore.textLines(mPageNumber); - } - - @Override - protected void addMarkup(PointF[] quadPoints, Annotation.Type type) { - mCore.addMarkupAnnotation(mPageNumber, quadPoints, type); - } - - private void loadAnnotations() { - mAnnotations = null; - if (mLoadAnnotations != null) - mLoadAnnotations.cancel(true); - mLoadAnnotations = new AsyncTask () { - @Override - protected Annotation[] doInBackground(Void... params) { - return mCore.getAnnoations(mPageNumber); - } - - @Override - protected void onPostExecute(Annotation[] result) { - mAnnotations = result; - } - }; - - mLoadAnnotations.execute(); - } - - @Override - public void setPage(final int page, PointF size) { - loadAnnotations(); - - mLoadWidgetAreas = new AsyncTask () { - @Override - protected RectF[] doInBackground(Void... arg0) { - return mCore.getWidgetAreas(page); - } - - @Override - protected void onPostExecute(RectF[] result) { - mWidgetAreas = result; - } - }; - - mLoadWidgetAreas.execute(); - - super.setPage(page, size); - } - - public void setScale(float scale) { - // This type of view scales automatically to fit the size - // determined by the parent view groups during layout - } - - @Override - public void releaseResources() { - if (mPassClick != null) { - mPassClick.cancel(true); - mPassClick = null; - } - - if (mLoadWidgetAreas != null) { - mLoadWidgetAreas.cancel(true); - mLoadWidgetAreas = null; - } - - if (mLoadAnnotations != null) { - mLoadAnnotations.cancel(true); - mLoadAnnotations = null; - } - - if (mSetWidgetText != null) { - mSetWidgetText.cancel(true); - mSetWidgetText = null; - } - - if (mSetWidgetChoice != null) { - mSetWidgetChoice.cancel(true); - mSetWidgetChoice = null; - } - - if (mAddStrikeOut != null) { - mAddStrikeOut.cancel(true); - mAddStrikeOut = null; - } - - if (mDeleteAnnotation != null) { - mDeleteAnnotation.cancel(true); - mDeleteAnnotation = null; - } - - super.releaseResources(); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFReaderView.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFReaderView.java deleted file mode 100644 index f2d7f8fe..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFReaderView.java +++ /dev/null @@ -1,276 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; -import android.view.WindowManager; - -public class MuPDFReaderView extends ReaderView { - public enum Mode {Viewing, Selecting, Drawing} - private final Context mContext; - private boolean mLinksEnabled = false; - private Mode mMode = Mode.Viewing; - private boolean tapDisabled = false; - private int tapPageMargin; - - protected void onTapMainDocArea() {} - protected void onDocMotion() {} - protected void onHit(Hit item) {}; - - public void setLinksEnabled(boolean b) { - mLinksEnabled = b; - resetupChildren(); - } - - public void setMode(Mode m) { - mMode = m; - } - - private void setup() - { - // Get the screen size etc to customise tap margins. - // We calculate the size of 1 inch of the screen for tapping. - // On some devices the dpi values returned are wrong, so we - // sanity check it: we first restrict it so that we are never - // less than 100 pixels (the smallest Android device screen - // dimension I've seen is 480 pixels or so). Then we check - // to ensure we are never more than 1/5 of the screen width. - DisplayMetrics dm = new DisplayMetrics(); - WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getMetrics(dm); - tapPageMargin = (int)dm.xdpi; - if (tapPageMargin < 100) - tapPageMargin = 100; - if (tapPageMargin > dm.widthPixels/5) - tapPageMargin = dm.widthPixels/5; - } - - public MuPDFReaderView(Context context) { - super(context); - mContext = context; - setup(); - } - - public MuPDFReaderView(Context context, AttributeSet attrs) - { - super(context, attrs); - mContext = context; - setup(); - } - - public boolean onSingleTapUp(MotionEvent e) { - LinkInfo link = null; - - if (mMode == Mode.Viewing && !tapDisabled) { - MuPDFView pageView = (MuPDFView) getDisplayedView(); - Hit item = pageView.passClickEvent(e.getX(), e.getY()); - onHit(item); - if (item == Hit.Nothing) { - if (mLinksEnabled && pageView != null - && (link = pageView.hitLink(e.getX(), e.getY())) != null) { - link.acceptVisitor(new LinkInfoVisitor() { - @Override - public void visitInternal(LinkInfoInternal li) { - // Clicked on an internal (GoTo) link - setDisplayedViewIndex(li.pageNumber); - } - - @Override - public void visitExternal(LinkInfoExternal li) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri - .parse(li.url)); - mContext.startActivity(intent); - } - - @Override - public void visitRemote(LinkInfoRemote li) { - // Clicked on a remote (GoToR) link - } - }); - } else if (e.getX() < tapPageMargin) { - super.smartMoveBackwards(); - } else if (e.getX() > super.getWidth() - tapPageMargin) { - super.smartMoveForwards(); - } else if (e.getY() < tapPageMargin) { - super.smartMoveBackwards(); - } else if (e.getY() > super.getHeight() - tapPageMargin) { - super.smartMoveForwards(); - } else { - onTapMainDocArea(); - } - } - } - return super.onSingleTapUp(e); - } - - @Override - public boolean onDown(MotionEvent e) { - - return super.onDown(e); - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, - float distanceY) { - MuPDFView pageView = (MuPDFView)getDisplayedView(); - switch (mMode) { - case Viewing: - if (!tapDisabled) - onDocMotion(); - - return super.onScroll(e1, e2, distanceX, distanceY); - case Selecting: - if (pageView != null) - pageView.selectText(e1.getX(), e1.getY(), e2.getX(), e2.getY()); - return true; - default: - return true; - } - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - switch (mMode) { - case Viewing: - return super.onFling(e1, e2, velocityX, velocityY); - default: - return true; - } - } - - public boolean onScaleBegin(ScaleGestureDetector d) { - // Disabled showing the buttons until next touch. - // Not sure why this is needed, but without it - // pinch zoom can make the buttons appear - tapDisabled = true; - return super.onScaleBegin(d); - } - - public boolean onTouchEvent(MotionEvent event) { - - if ( mMode == Mode.Drawing ) - { - float x = event.getX(); - float y = event.getY(); - switch (event.getAction()) - { - case MotionEvent.ACTION_DOWN: - touch_start(x, y); - break; - case MotionEvent.ACTION_MOVE: - touch_move(x, y); - break; - case MotionEvent.ACTION_UP: - touch_up(); - break; - } - } - - if ((event.getAction() & event.getActionMasked()) == MotionEvent.ACTION_DOWN) - { - tapDisabled = false; - } - - return super.onTouchEvent(event); - } - - private float mX, mY; - - private static final float TOUCH_TOLERANCE = 2; - - private void touch_start(float x, float y) { - - MuPDFView pageView = (MuPDFView)getDisplayedView(); - if (pageView != null) - { - pageView.startDraw(x, y); - } - mX = x; - mY = y; - } - - private void touch_move(float x, float y) { - - float dx = Math.abs(x - mX); - float dy = Math.abs(y - mY); - if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) - { - MuPDFView pageView = (MuPDFView)getDisplayedView(); - if (pageView != null) - { - pageView.continueDraw(x, y); - } - mX = x; - mY = y; - } - } - - private void touch_up() { - - // NOOP - } - - protected void onChildSetup(int i, View v) { - if (SearchTaskResult.get() != null - && SearchTaskResult.get().pageNumber == i) - ((MuPDFView) v).setSearchBoxes(SearchTaskResult.get().searchBoxes); - else - ((MuPDFView) v).setSearchBoxes(null); - - ((MuPDFView) v).setLinkHighlighting(mLinksEnabled); - - ((MuPDFView) v).setChangeReporter(new Runnable() { - public void run() { - applyToChildren(new ReaderView.ViewMapper() { - @Override - void applyToView(View view) { - ((MuPDFView) view).update(); - } - }); - } - }); - } - - protected void onMoveToChild(int i) { - if (SearchTaskResult.get() != null - && SearchTaskResult.get().pageNumber != i) { - SearchTaskResult.set(null); - resetupChildren(); - } - } - - @Override - protected void onMoveOffChild(int i) { - View v = getView(i); - if (v != null) - ((MuPDFView)v).deselectAnnotation(); - } - - protected void onSettle(View v) { - // When the layout has settled ask the page to render - // in HQ - ((MuPDFView) v).updateHq(false); - } - - protected void onUnsettle(View v) { - // When something changes making the previous settled view - // no longer appropriate, tell the page to remove HQ - ((MuPDFView) v).removeHq(); - } - - @Override - protected void onNotInUse(View v) { - ((MuPDFView) v).releaseResources(); - } - - @Override - protected void onScaleChild(View v, Float scale) { - ((MuPDFView) v).setScale(scale); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java deleted file mode 100644 index 48625a7e..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.PointF; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -public class MuPDFReflowAdapter extends BaseAdapter { - private final Context mContext; - private final MuPDFCore mCore; - - public MuPDFReflowAdapter(Context c, MuPDFCore core) { - mContext = c; - mCore = core; - } - - public int getCount() { - return mCore.countPages(); - } - - public Object getItem(int arg0) { - return null; - } - - public long getItemId(int arg0) { - return 0; - } - - public View getView(int position, View convertView, ViewGroup parent) { - final MuPDFReflowView reflowView; - if (convertView == null) { - reflowView = new MuPDFReflowView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight())); - } else { - reflowView = (MuPDFReflowView) convertView; - } - - reflowView.setPage(position, new PointF()); - - return reflowView; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFReflowView.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFReflowView.java deleted file mode 100644 index 7d41a9b9..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFReflowView.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.RectF; -import android.os.Handler; -import android.util.Base64; -import android.view.MotionEvent; -import android.view.View; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -public class MuPDFReflowView extends WebView implements MuPDFView { - private final MuPDFCore mCore; - private final Handler mHandler; - private final Point mParentSize; - private int mPage; - private float mScale; - private int mContentHeight; - AsyncTask mLoadHTML; - - public MuPDFReflowView(Context c, MuPDFCore core, Point parentSize) { - super(c); - mHandler = new Handler(); - mCore = core; - mParentSize = parentSize; - mScale = 1.0f; - mContentHeight = parentSize.y; - getSettings().setJavaScriptEnabled(true); - addJavascriptInterface(new Object(){ - public void reportContentHeight(String value) { - mContentHeight = (int)Float.parseFloat(value); - mHandler.post(new Runnable() { - public void run() { - requestLayout(); - } - }); - } - }, "HTMLOUT"); - setWebViewClient(new WebViewClient() { - @Override - public void onPageFinished(WebView view, String url) { - setScale(mScale); - } - }); - } - - private void requestHeight() { - // Get the webview to report the content height via the interface setup - // above. Workaround for getContentHeight not working - loadUrl("javascript:elem=document.getElementById('content');window.HTMLOUT.reportContentHeight("+mParentSize.x+"*elem.offsetHeight/elem.offsetWidth)"); - } - - public void setPage(int page, PointF size) { - mPage = page; - if (mLoadHTML != null) { - mLoadHTML.cancel(true); - } - mLoadHTML = new AsyncTask() { - @Override - protected byte[] doInBackground(Void... params) { - return mCore.html(mPage); - } - @Override - protected void onPostExecute(byte[] result) { - String b64 = Base64.encodeToString(result, Base64.DEFAULT); - loadData(b64, "text/html; charset=utf-8", "base64"); - } - }; - mLoadHTML.execute(); - } - - public int getPage() { - return mPage; - } - - public void setScale(float scale) { - mScale = scale; - loadUrl("javascript:document.getElementById('content').style.zoom=\""+(int)(mScale*100)+"%\""); - requestHeight(); - } - - public void blank(int page) { - } - - public Hit passClickEvent(float x, float y) { - return Hit.Nothing; - } - - public LinkInfo hitLink(float x, float y) { - return null; - } - - public void selectText(float x0, float y0, float x1, float y1) { - } - - public void deselectText() { - } - - public boolean copySelection() { - return false; - } - - public boolean markupSelection(Annotation.Type type) { - return false; - } - - public void startDraw(float x, float y) { - } - - public void continueDraw(float x, float y) { - } - - public void cancelDraw() { - } - - public boolean saveDraw() { - return false; - } - - public void setSearchBoxes(RectF[] searchBoxes) { - } - - public void setLinkHighlighting(boolean f) { - } - - public void deleteSelectedAnnotation() { - } - - public void deselectAnnotation() { - } - - public void setChangeReporter(Runnable reporter) { - } - - public void update() { - } - - public void updateHq(boolean update) { - } - - public void removeHq() { - } - - public void releaseResources() { - if (mLoadHTML != null) { - mLoadHTML.cancel(true); - mLoadHTML = null; - } - } - - public void releaseBitmaps() { - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int x, y; - switch(View.MeasureSpec.getMode(widthMeasureSpec)) { - case View.MeasureSpec.UNSPECIFIED: - x = mParentSize.x; - break; - default: - x = View.MeasureSpec.getSize(widthMeasureSpec); - } - switch(View.MeasureSpec.getMode(heightMeasureSpec)) { - case View.MeasureSpec.UNSPECIFIED: - y = mContentHeight; - break; - default: - y = View.MeasureSpec.getSize(heightMeasureSpec); - } - - setMeasuredDimension(x, y); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - // TODO Auto-generated method stub - return false; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/MuPDFView.java b/platform/android/src/com/artifex/mupdfdemo/MuPDFView.java deleted file mode 100644 index ec3d3d9a..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/MuPDFView.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.graphics.PointF; -import android.graphics.RectF; - -enum Hit {Nothing, Widget, Annotation}; - -public interface MuPDFView { - public void setPage(int page, PointF size); - public void setScale(float scale); - public int getPage(); - public void blank(int page); - public Hit passClickEvent(float x, float y); - public LinkInfo hitLink(float x, float y); - public void selectText(float x0, float y0, float x1, float y1); - public void deselectText(); - public boolean copySelection(); - public boolean markupSelection(Annotation.Type type); - public void deleteSelectedAnnotation(); - public void setSearchBoxes(RectF searchBoxes[]); - public void setLinkHighlighting(boolean f); - public void deselectAnnotation(); - public void startDraw(float x, float y); - public void continueDraw(float x, float y); - public void cancelDraw(); - public boolean saveDraw(); - public void setChangeReporter(Runnable reporter); - public void update(); - public void updateHq(boolean update); - public void removeHq(); - public void releaseResources(); - public void releaseBitmaps(); -} diff --git a/platform/android/src/com/artifex/mupdfdemo/OutlineActivity.java b/platform/android/src/com/artifex/mupdfdemo/OutlineActivity.java deleted file mode 100644 index 52b0d410..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/OutlineActivity.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.app.ListActivity; -import android.os.Bundle; -import android.view.View; -import android.widget.ListView; - -public class OutlineActivity extends ListActivity { - OutlineItem mItems[]; - - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mItems = OutlineActivityData.get().items; - setListAdapter(new OutlineAdapter(getLayoutInflater(),mItems)); - // Restore the position within the list from last viewing - getListView().setSelection(OutlineActivityData.get().position); - getListView().setDividerHeight(0); - setResult(-1); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - OutlineActivityData.get().position = getListView().getFirstVisiblePosition(); - setResult(mItems[position].page); - finish(); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/OutlineActivityData.java b/platform/android/src/com/artifex/mupdfdemo/OutlineActivityData.java deleted file mode 100644 index a703e61e..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/OutlineActivityData.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.artifex.mupdfdemo; - -public class OutlineActivityData { - public OutlineItem items[]; - public int position; - static private OutlineActivityData singleton; - - static public void set(OutlineActivityData d) { - singleton = d; - } - - static public OutlineActivityData get() { - if (singleton == null) - singleton = new OutlineActivityData(); - return singleton; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/OutlineAdapter.java b/platform/android/src/com/artifex/mupdfdemo/OutlineAdapter.java deleted file mode 100644 index 4251ed8e..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/OutlineAdapter.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -public class OutlineAdapter extends BaseAdapter { - private final OutlineItem mItems[]; - private final LayoutInflater mInflater; - public OutlineAdapter(LayoutInflater inflater, OutlineItem items[]) { - mInflater = inflater; - mItems = items; - } - - public int getCount() { - return mItems.length; - } - - public Object getItem(int arg0) { - return null; - } - - public long getItemId(int arg0) { - return 0; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v; - if (convertView == null) { - v = mInflater.inflate(R.layout.outline_entry, null); - } else { - v = convertView; - } - int level = mItems[position].level; - if (level > 8) level = 8; - String space = ""; - for (int i=0; i lines = new ArrayList(); - for (TextWord[] line : mText) - if (line[0].bottom > mSelectBox.top && line[0].top < mSelectBox.bottom) - lines.add(line); - - Iterator it = lines.iterator(); - while (it.hasNext()) { - TextWord[] line = it.next(); - boolean firstLine = line[0].top < mSelectBox.top; - boolean lastLine = line[0].bottom > mSelectBox.bottom; - float start = Float.NEGATIVE_INFINITY; - float end = Float.POSITIVE_INFINITY; - - if (firstLine && lastLine) { - start = Math.min(mSelectBox.left, mSelectBox.right); - end = Math.max(mSelectBox.left, mSelectBox.right); - } else if (firstLine) { - start = mSelectBox.left; - } else if (lastLine) { - end = mSelectBox.right; - } - - tp.onStartLine(); - - for (TextWord word : line) - if (word.right > start && word.left < end) - tp.onWord(word); - - tp.onEndLine(); - } - } -} - -public abstract class PageView extends ViewGroup { - private static final int HIGHLIGHT_COLOR = 0x802572AC; - private static final int LINK_COLOR = 0x80AC7225; - private static final int BOX_COLOR = 0xFF4444FF; - private static final int INK_COLOR = 0xFFFF0000; - private static final float INK_THICKNESS = 10.0f; - private static final int BACKGROUND_COLOR = 0xFFFFFFFF; - private static final int PROGRESS_DIALOG_DELAY = 200; - protected final Context mContext; - protected int mPageNumber; - private Point mParentSize; - protected Point mSize; // Size of page at minimum zoom - protected float mSourceScale; - - private ImageView mEntire; // Image rendered at minimum zoom - private Bitmap mEntireBm; - private Matrix mEntireMat; - private AsyncTask mGetText; - private AsyncTask mGetLinkInfo; - private CancellableAsyncTask mDrawEntire; - - private Point mPatchViewSize; // View size on the basis of which the patch was created - private Rect mPatchArea; - private ImageView mPatch; - private Bitmap mPatchBm; - private CancellableAsyncTask mDrawPatch; - private RectF mSearchBoxes[]; - protected LinkInfo mLinks[]; - private RectF mSelectBox; - private TextWord mText[][]; - private RectF mItemSelectBox; - protected ArrayList> mDrawing; - private View mSearchView; - private boolean mIsBlank; - private boolean mHighlightLinks; - - private ProgressBar mBusyIndicator; - private final Handler mHandler = new Handler(); - - public PageView(Context c, Point parentSize, Bitmap sharedHqBm) { - super(c); - mContext = c; - mParentSize = parentSize; - setBackgroundColor(BACKGROUND_COLOR); - mEntireBm = Bitmap.createBitmap(parentSize.x, parentSize.y, Config.ARGB_8888); - mPatchBm = sharedHqBm; - mEntireMat = new Matrix(); - } - - protected abstract CancellableTaskDefinition getDrawPageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); - protected abstract CancellableTaskDefinition getUpdatePageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); - protected abstract LinkInfo[] getLinkInfo(); - protected abstract TextWord[][] getText(); - protected abstract void addMarkup(PointF[] quadPoints, Annotation.Type type); - - private void reinit() { - // Cancel pending render task - if (mDrawEntire != null) { - mDrawEntire.cancelAndWait(); - mDrawEntire = null; - } - - if (mDrawPatch != null) { - mDrawPatch.cancelAndWait(); - mDrawPatch = null; - } - - if (mGetLinkInfo != null) { - mGetLinkInfo.cancel(true); - mGetLinkInfo = null; - } - - if (mGetText != null) { - mGetText.cancel(true); - mGetText = null; - } - - mIsBlank = true; - mPageNumber = 0; - - if (mSize == null) - mSize = mParentSize; - - if (mEntire != null) { - mEntire.setImageBitmap(null); - mEntire.invalidate(); - } - - if (mPatch != null) { - mPatch.setImageBitmap(null); - mPatch.invalidate(); - } - - mPatchViewSize = null; - mPatchArea = null; - - mSearchBoxes = null; - mLinks = null; - mSelectBox = null; - mText = null; - mItemSelectBox = null; - } - - public void releaseResources() { - reinit(); - - if (mBusyIndicator != null) { - removeView(mBusyIndicator); - mBusyIndicator = null; - } - } - - public void releaseBitmaps() { - reinit(); - - // recycle bitmaps before releasing them. - - if (mEntireBm!=null) - mEntireBm.recycle(); - mEntireBm = null; - - if (mPatchBm!=null) - mPatchBm.recycle(); - mPatchBm = null; - } - - public void blank(int page) { - reinit(); - mPageNumber = page; - - if (mBusyIndicator == null) { - mBusyIndicator = new ProgressBar(mContext); - mBusyIndicator.setIndeterminate(true); - mBusyIndicator.setBackgroundResource(R.drawable.busy); - addView(mBusyIndicator); - } - - setBackgroundColor(BACKGROUND_COLOR); - } - - public void setPage(int page, PointF size) { - // Cancel pending render task - if (mDrawEntire != null) { - mDrawEntire.cancelAndWait(); - mDrawEntire = null; - } - - mIsBlank = false; - // Highlights may be missing because mIsBlank was true on last draw - if (mSearchView != null) - mSearchView.invalidate(); - - mPageNumber = page; - if (mEntire == null) { - mEntire = new OpaqueImageView(mContext); - mEntire.setScaleType(ImageView.ScaleType.MATRIX); - addView(mEntire); - } - - // Calculate scaled size that fits within the screen limits - // This is the size at minimum zoom - mSourceScale = Math.min(mParentSize.x/size.x, mParentSize.y/size.y); - Point newSize = new Point((int)(size.x*mSourceScale), (int)(size.y*mSourceScale)); - mSize = newSize; - - mEntire.setImageBitmap(null); - mEntire.invalidate(); - - // Get the link info in the background - mGetLinkInfo = new AsyncTask() { - protected LinkInfo[] doInBackground(Void... v) { - return getLinkInfo(); - } - - protected void onPostExecute(LinkInfo[] v) { - mLinks = v; - if (mSearchView != null) - mSearchView.invalidate(); - } - }; - - mGetLinkInfo.execute(); - - // Render the page in the background - mDrawEntire = new CancellableAsyncTask(getDrawPageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) { - - @Override - public void onPreExecute() { - setBackgroundColor(BACKGROUND_COLOR); - mEntire.setImageBitmap(null); - mEntire.invalidate(); - - if (mBusyIndicator == null) { - mBusyIndicator = new ProgressBar(mContext); - mBusyIndicator.setIndeterminate(true); - mBusyIndicator.setBackgroundResource(R.drawable.busy); - addView(mBusyIndicator); - mBusyIndicator.setVisibility(INVISIBLE); - mHandler.postDelayed(new Runnable() { - public void run() { - if (mBusyIndicator != null) - mBusyIndicator.setVisibility(VISIBLE); - } - }, PROGRESS_DIALOG_DELAY); - } - } - - @Override - public void onPostExecute(Void result) { - removeView(mBusyIndicator); - mBusyIndicator = null; - mEntire.setImageBitmap(mEntireBm); - mEntire.invalidate(); - setBackgroundColor(Color.TRANSPARENT); - - } - }; - - mDrawEntire.execute(); - - if (mSearchView == null) { - mSearchView = new View(mContext) { - @Override - protected void onDraw(final Canvas canvas) { - super.onDraw(canvas); - // Work out current total scale factor - // from source to view - final float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - final Paint paint = new Paint(); - - if (!mIsBlank && mSearchBoxes != null) { - paint.setColor(HIGHLIGHT_COLOR); - for (RectF rect : mSearchBoxes) - canvas.drawRect(rect.left*scale, rect.top*scale, - rect.right*scale, rect.bottom*scale, - paint); - } - - if (!mIsBlank && mLinks != null && mHighlightLinks) { - paint.setColor(LINK_COLOR); - for (LinkInfo link : mLinks) - canvas.drawRect(link.rect.left*scale, link.rect.top*scale, - link.rect.right*scale, link.rect.bottom*scale, - paint); - } - - if (mSelectBox != null && mText != null) { - paint.setColor(HIGHLIGHT_COLOR); - processSelectedText(new TextProcessor() { - RectF rect; - - public void onStartLine() { - rect = new RectF(); - } - - public void onWord(TextWord word) { - rect.union(word); - } - - public void onEndLine() { - if (!rect.isEmpty()) - canvas.drawRect(rect.left*scale, rect.top*scale, rect.right*scale, rect.bottom*scale, paint); - } - }); - } - - if (mItemSelectBox != null) { - paint.setStyle(Paint.Style.STROKE); - paint.setColor(BOX_COLOR); - canvas.drawRect(mItemSelectBox.left*scale, mItemSelectBox.top*scale, mItemSelectBox.right*scale, mItemSelectBox.bottom*scale, paint); - } - - if (mDrawing != null) { - Path path = new Path(); - PointF p; - - paint.setAntiAlias(true); - paint.setDither(true); - paint.setStrokeJoin(Paint.Join.ROUND); - paint.setStrokeCap(Paint.Cap.ROUND); - - paint.setStyle(Paint.Style.FILL); - paint.setStrokeWidth(INK_THICKNESS * scale); - paint.setColor(INK_COLOR); - - Iterator> it = mDrawing.iterator(); - while (it.hasNext()) { - ArrayList arc = it.next(); - if (arc.size() >= 2) { - Iterator iit = arc.iterator(); - p = iit.next(); - float mX = p.x * scale; - float mY = p.y * scale; - path.moveTo(mX, mY); - while (iit.hasNext()) { - p = iit.next(); - float x = p.x * scale; - float y = p.y * scale; - path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); - mX = x; - mY = y; - } - path.lineTo(mX, mY); - } else { - p = arc.get(0); - canvas.drawCircle(p.x * scale, p.y * scale, INK_THICKNESS * scale / 2, paint); - } - } - - paint.setStyle(Paint.Style.STROKE); - canvas.drawPath(path, paint); - } - } - }; - - addView(mSearchView); - } - requestLayout(); - } - - public void setSearchBoxes(RectF searchBoxes[]) { - mSearchBoxes = searchBoxes; - if (mSearchView != null) - mSearchView.invalidate(); - } - - public void setLinkHighlighting(boolean f) { - mHighlightLinks = f; - if (mSearchView != null) - mSearchView.invalidate(); - } - - public void deselectText() { - mSelectBox = null; - mSearchView.invalidate(); - } - - public void selectText(float x0, float y0, float x1, float y1) { - float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - float docRelX0 = (x0 - getLeft())/scale; - float docRelY0 = (y0 - getTop())/scale; - float docRelX1 = (x1 - getLeft())/scale; - float docRelY1 = (y1 - getTop())/scale; - // Order on Y but maintain the point grouping - if (docRelY0 <= docRelY1) - mSelectBox = new RectF(docRelX0, docRelY0, docRelX1, docRelY1); - else - mSelectBox = new RectF(docRelX1, docRelY1, docRelX0, docRelY0); - - mSearchView.invalidate(); - - if (mGetText == null) { - mGetText = new AsyncTask() { - @Override - protected TextWord[][] doInBackground(Void... params) { - return getText(); - } - @Override - protected void onPostExecute(TextWord[][] result) { - mText = result; - mSearchView.invalidate(); - } - }; - - mGetText.execute(); - } - } - - public void startDraw(float x, float y) { - float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - float docRelX = (x - getLeft())/scale; - float docRelY = (y - getTop())/scale; - if (mDrawing == null) - mDrawing = new ArrayList>(); - - ArrayList arc = new ArrayList(); - arc.add(new PointF(docRelX, docRelY)); - mDrawing.add(arc); - mSearchView.invalidate(); - } - - public void continueDraw(float x, float y) { - float scale = mSourceScale*(float)getWidth()/(float)mSize.x; - float docRelX = (x - getLeft())/scale; - float docRelY = (y - getTop())/scale; - - if (mDrawing != null && mDrawing.size() > 0) { - ArrayList arc = mDrawing.get(mDrawing.size() - 1); - arc.add(new PointF(docRelX, docRelY)); - mSearchView.invalidate(); - } - } - - public void cancelDraw() { - mDrawing = null; - mSearchView.invalidate(); - } - - protected PointF[][] getDraw() { - if (mDrawing == null) - return null; - - PointF[][] path = new PointF[mDrawing.size()][]; - - for (int i = 0; i < mDrawing.size(); i++) { - ArrayList arc = mDrawing.get(i); - path[i] = arc.toArray(new PointF[arc.size()]); - } - - return path; - } - - protected void processSelectedText(TextProcessor tp) { - (new TextSelector(mText, mSelectBox)).select(tp); - } - - public void setItemSelectBox(RectF rect) { - mItemSelectBox = rect; - if (mSearchView != null) - mSearchView.invalidate(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int x, y; - switch(View.MeasureSpec.getMode(widthMeasureSpec)) { - case View.MeasureSpec.UNSPECIFIED: - x = mSize.x; - break; - default: - x = View.MeasureSpec.getSize(widthMeasureSpec); - } - switch(View.MeasureSpec.getMode(heightMeasureSpec)) { - case View.MeasureSpec.UNSPECIFIED: - y = mSize.y; - break; - default: - y = View.MeasureSpec.getSize(heightMeasureSpec); - } - - setMeasuredDimension(x, y); - - if (mBusyIndicator != null) { - int limit = Math.min(mParentSize.x, mParentSize.y)/2; - mBusyIndicator.measure(View.MeasureSpec.AT_MOST | limit, View.MeasureSpec.AT_MOST | limit); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - int w = right-left; - int h = bottom-top; - - if (mEntire != null) { - if (mEntire.getWidth() != w || mEntire.getHeight() != h) { - mEntireMat.setScale(w/(float)mSize.x, h/(float)mSize.y); - mEntire.setImageMatrix(mEntireMat); - mEntire.invalidate(); - } - mEntire.layout(0, 0, w, h); - } - - if (mSearchView != null) { - mSearchView.layout(0, 0, w, h); - } - - if (mPatchViewSize != null) { - if (mPatchViewSize.x != w || mPatchViewSize.y != h) { - // Zoomed since patch was created - mPatchViewSize = null; - mPatchArea = null; - if (mPatch != null) { - mPatch.setImageBitmap(null); - mPatch.invalidate(); - } - } else { - mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); - } - } - - if (mBusyIndicator != null) { - int bw = mBusyIndicator.getMeasuredWidth(); - int bh = mBusyIndicator.getMeasuredHeight(); - - mBusyIndicator.layout((w-bw)/2, (h-bh)/2, (w+bw)/2, (h+bh)/2); - } - } - - public void updateHq(boolean update) { - Rect viewArea = new Rect(getLeft(),getTop(),getRight(),getBottom()); - if (viewArea.width() == mSize.x || viewArea.height() == mSize.y) { - // If the viewArea's size matches the unzoomed size, there is no need for an hq patch - if (mPatch != null) { - mPatch.setImageBitmap(null); - mPatch.invalidate(); - } - } else { - final Point patchViewSize = new Point(viewArea.width(), viewArea.height()); - final Rect patchArea = new Rect(0, 0, mParentSize.x, mParentSize.y); - - // Intersect and test that there is an intersection - if (!patchArea.intersect(viewArea)) - return; - - // Offset patch area to be relative to the view top left - patchArea.offset(-viewArea.left, -viewArea.top); - - boolean area_unchanged = patchArea.equals(mPatchArea) && patchViewSize.equals(mPatchViewSize); - - // If being asked for the same area as last time and not because of an update then nothing to do - if (area_unchanged && !update) - return; - - boolean completeRedraw = !(area_unchanged && update); - - // Stop the drawing of previous patch if still going - if (mDrawPatch != null) { - mDrawPatch.cancelAndWait(); - mDrawPatch = null; - } - - // Create and add the image view if not already done - if (mPatch == null) { - mPatch = new OpaqueImageView(mContext); - mPatch.setScaleType(ImageView.ScaleType.MATRIX); - addView(mPatch); - mSearchView.bringToFront(); - } - - CancellableTaskDefinition task; - - if (completeRedraw) - task = getDrawPageTask(mPatchBm, patchViewSize.x, patchViewSize.y, - patchArea.left, patchArea.top, - patchArea.width(), patchArea.height()); - else - task = getUpdatePageTask(mPatchBm, patchViewSize.x, patchViewSize.y, - patchArea.left, patchArea.top, - patchArea.width(), patchArea.height()); - - mDrawPatch = new CancellableAsyncTask(task) { - - public void onPostExecute(Void result) { - mPatchViewSize = patchViewSize; - mPatchArea = patchArea; - mPatch.setImageBitmap(mPatchBm); - mPatch.invalidate(); - //requestLayout(); - // Calling requestLayout here doesn't lead to a later call to layout. No idea - // why, but apparently others have run into the problem. - mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); - } - }; - - mDrawPatch.execute(); - } - } - - public void update() { - // Cancel pending render task - if (mDrawEntire != null) { - mDrawEntire.cancelAndWait(); - mDrawEntire = null; - } - - if (mDrawPatch != null) { - mDrawPatch.cancelAndWait(); - mDrawPatch = null; - } - - - // Render the page in the background - mDrawEntire = new CancellableAsyncTask(getUpdatePageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) { - - public void onPostExecute(Void result) { - mEntire.setImageBitmap(mEntireBm); - mEntire.invalidate(); - } - }; - - mDrawEntire.execute(); - - updateHq(true); - } - - public void removeHq() { - // Stop the drawing of the patch if still going - if (mDrawPatch != null) { - mDrawPatch.cancelAndWait(); - mDrawPatch = null; - } - - // And get rid of it - mPatchViewSize = null; - mPatchArea = null; - if (mPatch != null) { - mPatch.setImageBitmap(null); - mPatch.invalidate(); - } - } - - public int getPage() { - return mPageNumber; - } - - @Override - public boolean isOpaque() { - return true; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/PrintDialogActivity.java b/platform/android/src/com/artifex/mupdfdemo/PrintDialogActivity.java deleted file mode 100644 index d96322d5..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/PrintDialogActivity.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.artifex.mupdfdemo; - -import java.io.ByteArrayOutputStream; -import java.io.InputStream; - -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.ContentResolver; -import android.content.Intent; -import android.os.Bundle; -import android.util.Base64; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -public class PrintDialogActivity extends Activity { - private static final String PRINT_DIALOG_URL = "https://www.google.com/cloudprint/dialog.html"; - private static final String JS_INTERFACE = "AndroidPrintDialog"; - private static final String CONTENT_TRANSFER_ENCODING = "base64"; - - private static final String ZXING_URL = "http://zxing.appspot.com"; - private static final int ZXING_SCAN_REQUEST = 65743; - - /** - * Post message that is sent by Print Dialog web page when the printing dialog - * needs to be closed. - */ - private static final String CLOSE_POST_MESSAGE_NAME = "cp-dialog-on-close"; - - /** - * Web view element to show the printing dialog in. - */ - private WebView dialogWebView; - - /** - * Intent that started the action. - */ - Intent cloudPrintIntent; - - private int resultCode; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - - resultCode = RESULT_OK; - setContentView(R.layout.print_dialog); - dialogWebView = (WebView) findViewById(R.id.webview); - cloudPrintIntent = this.getIntent(); - - WebSettings settings = dialogWebView.getSettings(); - settings.setJavaScriptEnabled(true); - - dialogWebView.setWebViewClient(new PrintDialogWebClient()); - dialogWebView.addJavascriptInterface( - new PrintDialogJavaScriptInterface(), JS_INTERFACE); - - dialogWebView.loadUrl(PRINT_DIALOG_URL); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - if (requestCode == ZXING_SCAN_REQUEST && resultCode == RESULT_OK) { - dialogWebView.loadUrl(intent.getStringExtra("SCAN_RESULT")); - } - } - - final class PrintDialogJavaScriptInterface { - public String getType() { - return cloudPrintIntent.getType(); - } - - public String getTitle() { - return cloudPrintIntent.getExtras().getString("title"); - } - - public String getContent() { - try { - ContentResolver contentResolver = getContentResolver(); - InputStream is = contentResolver.openInputStream(cloudPrintIntent.getData()); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - byte[] buffer = new byte[4096]; - int n = is.read(buffer); - while (n >= 0) { - baos.write(buffer, 0, n); - n = is.read(buffer); - } - is.close(); - baos.flush(); - - return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT); - } catch (Throwable e) { - resultCode = RESULT_CANCELED; - setResult(resultCode); - finish(); - e.printStackTrace(); - } - return ""; - } - - public String getEncoding() { - return CONTENT_TRANSFER_ENCODING; - } - - public void onPostMessage(String message) { - if (message.startsWith(CLOSE_POST_MESSAGE_NAME)) { - setResult(resultCode); - finish(); - } - } - } - - private final class PrintDialogWebClient extends WebViewClient { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.startsWith(ZXING_URL)) { - Intent intentScan = new Intent("com.google.zxing.client.android.SCAN"); - intentScan.putExtra("SCAN_MODE", "QR_CODE_MODE"); - try { - startActivityForResult(intentScan, ZXING_SCAN_REQUEST); - } catch (ActivityNotFoundException error) { - view.loadUrl(url); - } - } else { - view.loadUrl(url); - } - return false; - } - - @Override - public void onPageFinished(WebView view, String url) { - if (PRINT_DIALOG_URL.equals(url)) { - // Submit print document. - view.loadUrl("javascript:printDialog.setPrintDocument(printDialog.createPrintDocument(" - + "window." + JS_INTERFACE + ".getType(),window." + JS_INTERFACE + ".getTitle()," - + "window." + JS_INTERFACE + ".getContent(),window." + JS_INTERFACE + ".getEncoding()))"); - - // Add post messages listener. - view.loadUrl("javascript:window.addEventListener('message'," - + "function(evt){window." + JS_INTERFACE + ".onPostMessage(evt.data)}, false)"); - } - } - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/ReaderView.java b/platform/android/src/com/artifex/mupdfdemo/ReaderView.java deleted file mode 100644 index 65d8f665..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/ReaderView.java +++ /dev/null @@ -1,936 +0,0 @@ -package com.artifex.mupdfdemo; - -import java.util.LinkedList; -import java.util.NoSuchElementException; - -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.content.DialogInterface; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.util.SparseArray; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; -import android.widget.Adapter; -import android.widget.AdapterView; -import android.widget.Scroller; - -public class ReaderView - extends AdapterView - implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable { - private static final int MOVING_DIAGONALLY = 0; - private static final int MOVING_LEFT = 1; - private static final int MOVING_RIGHT = 2; - private static final int MOVING_UP = 3; - private static final int MOVING_DOWN = 4; - - private static final int FLING_MARGIN = 100; - private static final int GAP = 20; - - private static final float MIN_SCALE = 1.0f; - private static final float MAX_SCALE = 5.0f; - private static final float REFLOW_SCALE_FACTOR = 0.5f; - - private static final boolean HORIZONTAL_SCROLLING = true; - - private Adapter mAdapter; - private int mCurrent; // Adapter's index for the current view - private boolean mResetLayout; - private final SparseArray - mChildViews = new SparseArray(3); - // Shadows the children of the adapter view - // but with more sensible indexing - private final LinkedList - mViewCache = new LinkedList(); - private boolean mUserInteracting; // Whether the user is interacting - private boolean mScaling; // Whether the user is currently pinch zooming - private float mScale = 1.0f; - private int mXScroll; // Scroll amounts recorded from events. - private int mYScroll; // and then accounted for in onLayout - private boolean mReflow = false; - private boolean mReflowChanged = false; - private final GestureDetector - mGestureDetector; - private final ScaleGestureDetector - mScaleGestureDetector; - private final Scroller mScroller; - private final Stepper mStepper; - private int mScrollerLastX; - private int mScrollerLastY; - private float mLastScaleFocusX; - private float mLastScaleFocusY; - - static abstract class ViewMapper { - abstract void applyToView(View view); - } - - public ReaderView(Context context) { - super(context); - mGestureDetector = new GestureDetector(this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - mStepper = new Stepper(this, this); - } - - public ReaderView(Context context, AttributeSet attrs) { - super(context, attrs); - - // "Edit mode" means when the View is being displayed in the Android GUI editor. (this class - // is instantiated in the IDE, so we need to be a bit careful what we do). - if (isInEditMode()) - { - mGestureDetector = null; - mScaleGestureDetector = null; - mScroller = null; - mStepper = null; - } - else - { - mGestureDetector = new GestureDetector(this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - mStepper = new Stepper(this, this); - } - } - - public ReaderView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mGestureDetector = new GestureDetector(this); - mScaleGestureDetector = new ScaleGestureDetector(context, this); - mScroller = new Scroller(context); - mStepper = new Stepper(this, this); - } - - public int getDisplayedViewIndex() { - return mCurrent; - } - - public void setDisplayedViewIndex(int i) { - if (0 <= i && i < mAdapter.getCount()) { - onMoveOffChild(mCurrent); - mCurrent = i; - onMoveToChild(i); - mResetLayout = true; - requestLayout(); - } - } - - public void moveToNext() { - View v = mChildViews.get(mCurrent+1); - if (v != null) - slideViewOntoScreen(v); - } - - public void moveToPrevious() { - View v = mChildViews.get(mCurrent-1); - if (v != null) - slideViewOntoScreen(v); - } - - // When advancing down the page, we want to advance by about - // 90% of a screenful. But we'd be happy to advance by between - // 80% and 95% if it means we hit the bottom in a whole number - // of steps. - private int smartAdvanceAmount(int screenHeight, int max) { - int advance = (int)(screenHeight * 0.9 + 0.5); - int leftOver = max % advance; - int steps = max / advance; - if (leftOver == 0) { - // We'll make it exactly. No adjustment - } else if ((float)leftOver / steps <= screenHeight * 0.05) { - // We can adjust up by less than 5% to make it exact. - advance += (int)((float)leftOver/steps + 0.5); - } else { - int overshoot = advance - leftOver; - if ((float)overshoot / steps <= screenHeight * 0.1) { - // We can adjust down by less than 10% to make it exact. - advance -= (int)((float)overshoot/steps + 0.5); - } - } - if (advance > max) - advance = max; - return advance; - } - - public void smartMoveForwards() { - View v = mChildViews.get(mCurrent); - if (v == null) - return; - - // The following code works in terms of where the screen is on the views; - // so for example, if the currentView is at (-100,-100), the visible - // region would be at (100,100). If the previous page was (2000, 3000) in - // size, the visible region of the previous page might be (2100 + GAP, 100) - // (i.e. off the previous page). This is different to the way the rest of - // the code in this file is written, but it's easier for me to think about. - // At some point we may refactor this to fit better with the rest of the - // code. - - // screenWidth/Height are the actual width/height of the screen. e.g. 480/800 - int screenWidth = getWidth(); - int screenHeight = getHeight(); - // We might be mid scroll; we want to calculate where we scroll to based on - // where this scroll would end, not where we are now (to allow for people - // bashing 'forwards' very fast. - int remainingX = mScroller.getFinalX() - mScroller.getCurrX(); - int remainingY = mScroller.getFinalY() - mScroller.getCurrY(); - // right/bottom is in terms of pixels within the scaled document; e.g. 1000 - int top = -(v.getTop() + mYScroll + remainingY); - int right = screenWidth -(v.getLeft() + mXScroll + remainingX); - int bottom = screenHeight+top; - // docWidth/Height are the width/height of the scaled document e.g. 2000x3000 - int docWidth = v.getMeasuredWidth(); - int docHeight = v.getMeasuredHeight(); - - int xOffset, yOffset; - if (bottom >= docHeight) { - // We are flush with the bottom. Advance to next column. - if (right + screenWidth > docWidth) { - // No room for another column - go to next page - View nv = mChildViews.get(mCurrent+1); - if (nv == null) // No page to advance to - return; - int nextTop = -(nv.getTop() + mYScroll + remainingY); - int nextLeft = -(nv.getLeft() + mXScroll + remainingX); - int nextDocWidth = nv.getMeasuredWidth(); - int nextDocHeight = nv.getMeasuredHeight(); - - // Allow for the next page maybe being shorter than the screen is high - yOffset = (nextDocHeight < screenHeight ? ((nextDocHeight - screenHeight)>>1) : 0); - - if (nextDocWidth < screenWidth) { - // Next page is too narrow to fill the screen. Scroll to the top, centred. - xOffset = (nextDocWidth - screenWidth)>>1; - } else { - // Reset X back to the left hand column - xOffset = right % screenWidth; - // Adjust in case the previous page is less wide - if (xOffset + screenWidth > nextDocWidth) - xOffset = nextDocWidth - screenWidth; - } - xOffset -= nextLeft; - yOffset -= nextTop; - } else { - // Move to top of next column - xOffset = screenWidth; - yOffset = screenHeight - bottom; - } - } else { - // Advance by 90% of the screen height downwards (in case lines are partially cut off) - xOffset = 0; - yOffset = smartAdvanceAmount(screenHeight, docHeight - bottom); - } - mScrollerLastX = mScrollerLastY = 0; - mScroller.startScroll(0, 0, remainingX - xOffset, remainingY - yOffset, 400); - mStepper.prod(); - } - - public void smartMoveBackwards() { - View v = mChildViews.get(mCurrent); - if (v == null) - return; - - // The following code works in terms of where the screen is on the views; - // so for example, if the currentView is at (-100,-100), the visible - // region would be at (100,100). If the previous page was (2000, 3000) in - // size, the visible region of the previous page might be (2100 + GAP, 100) - // (i.e. off the previous page). This is different to the way the rest of - // the code in this file is written, but it's easier for me to think about. - // At some point we may refactor this to fit better with the rest of the - // code. - - // screenWidth/Height are the actual width/height of the screen. e.g. 480/800 - int screenWidth = getWidth(); - int screenHeight = getHeight(); - // We might be mid scroll; we want to calculate where we scroll to based on - // where this scroll would end, not where we are now (to allow for people - // bashing 'forwards' very fast. - int remainingX = mScroller.getFinalX() - mScroller.getCurrX(); - int remainingY = mScroller.getFinalY() - mScroller.getCurrY(); - // left/top is in terms of pixels within the scaled document; e.g. 1000 - int left = -(v.getLeft() + mXScroll + remainingX); - int top = -(v.getTop() + mYScroll + remainingY); - // docWidth/Height are the width/height of the scaled document e.g. 2000x3000 - int docHeight = v.getMeasuredHeight(); - - int xOffset, yOffset; - if (top <= 0) { - // We are flush with the top. Step back to previous column. - if (left < screenWidth) { - /* No room for previous column - go to previous page */ - View pv = mChildViews.get(mCurrent-1); - if (pv == null) /* No page to advance to */ - return; - int prevDocWidth = pv.getMeasuredWidth(); - int prevDocHeight = pv.getMeasuredHeight(); - - // Allow for the next page maybe being shorter than the screen is high - yOffset = (prevDocHeight < screenHeight ? ((prevDocHeight - screenHeight)>>1) : 0); - - int prevLeft = -(pv.getLeft() + mXScroll); - int prevTop = -(pv.getTop() + mYScroll); - if (prevDocWidth < screenWidth) { - // Previous page is too narrow to fill the screen. Scroll to the bottom, centred. - xOffset = (prevDocWidth - screenWidth)>>1; - } else { - // Reset X back to the right hand column - xOffset = (left > 0 ? left % screenWidth : 0); - if (xOffset + screenWidth > prevDocWidth) - xOffset = prevDocWidth - screenWidth; - while (xOffset + screenWidth*2 < prevDocWidth) - xOffset += screenWidth; - } - xOffset -= prevLeft; - yOffset -= prevTop-prevDocHeight+screenHeight; - } else { - // Move to bottom of previous column - xOffset = -screenWidth; - yOffset = docHeight - screenHeight + top; - } - } else { - // Retreat by 90% of the screen height downwards (in case lines are partially cut off) - xOffset = 0; - yOffset = -smartAdvanceAmount(screenHeight, top); - } - mScrollerLastX = mScrollerLastY = 0; - mScroller.startScroll(0, 0, remainingX - xOffset, remainingY - yOffset, 400); - mStepper.prod(); - } - - public void resetupChildren() { - for (int i = 0; i < mChildViews.size(); i++) - onChildSetup(mChildViews.keyAt(i), mChildViews.valueAt(i)); - } - - public void applyToChildren(ViewMapper mapper) { - for (int i = 0; i < mChildViews.size(); i++) - mapper.applyToView(mChildViews.valueAt(i)); - } - - public void refresh(boolean reflow) { - mReflow = reflow; - mReflowChanged = true; - mResetLayout = true; - - mScale = 1.0f; - mXScroll = mYScroll = 0; - - requestLayout(); - } - - protected void onChildSetup(int i, View v) {} - - protected void onMoveToChild(int i) {} - - protected void onMoveOffChild(int i) {} - - protected void onSettle(View v) {}; - - protected void onUnsettle(View v) {}; - - protected void onNotInUse(View v) {}; - - protected void onScaleChild(View v, Float scale) {}; - - public View getView(int i) { - return mChildViews.get(i); - } - - public View getDisplayedView() { - return mChildViews.get(mCurrent); - } - - public void run() { - if (!mScroller.isFinished()) { - mScroller.computeScrollOffset(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - mXScroll += x - mScrollerLastX; - mYScroll += y - mScrollerLastY; - mScrollerLastX = x; - mScrollerLastY = y; - requestLayout(); - mStepper.prod(); - } - else if (!mUserInteracting) { - // End of an inertial scroll and the user is not interacting. - // The layout is stable - View v = mChildViews.get(mCurrent); - if (v != null) - postSettle(v); - } - } - - public boolean onDown(MotionEvent arg0) { - mScroller.forceFinished(true); - return true; - } - - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - if (mScaling) - return true; - - View v = mChildViews.get(mCurrent); - if (v != null) { - Rect bounds = getScrollBounds(v); - switch(directionOfTravel(velocityX, velocityY)) { - case MOVING_LEFT: - if (HORIZONTAL_SCROLLING && bounds.left >= 0) { - // Fling off to the left bring next view onto screen - View vl = mChildViews.get(mCurrent+1); - - if (vl != null) { - slideViewOntoScreen(vl); - return true; - } - } - break; - case MOVING_UP: - if (!HORIZONTAL_SCROLLING && bounds.top >= 0) { - // Fling off to the top bring next view onto screen - View vl = mChildViews.get(mCurrent+1); - - if (vl != null) { - slideViewOntoScreen(vl); - return true; - } - } - break; - case MOVING_RIGHT: - if (HORIZONTAL_SCROLLING && bounds.right <= 0) { - // Fling off to the right bring previous view onto screen - View vr = mChildViews.get(mCurrent-1); - - if (vr != null) { - slideViewOntoScreen(vr); - return true; - } - } - break; - case MOVING_DOWN: - if (!HORIZONTAL_SCROLLING && bounds.bottom <= 0) { - // Fling off to the bottom bring previous view onto screen - View vr = mChildViews.get(mCurrent-1); - - if (vr != null) { - slideViewOntoScreen(vr); - return true; - } - } - break; - } - mScrollerLastX = mScrollerLastY = 0; - // If the page has been dragged out of bounds then we want to spring back - // nicely. fling jumps back into bounds instantly, so we don't want to use - // fling in that case. On the other hand, we don't want to forgo a fling - // just because of a slightly off-angle drag taking us out of bounds other - // than in the direction of the drag, so we test for out of bounds only - // in the direction of travel. - // - // Also don't fling if out of bounds in any direction by more than fling - // margin - Rect expandedBounds = new Rect(bounds); - expandedBounds.inset(-FLING_MARGIN, -FLING_MARGIN); - - if(withinBoundsInDirectionOfTravel(bounds, velocityX, velocityY) - && expandedBounds.contains(0, 0)) { - mScroller.fling(0, 0, (int)velocityX, (int)velocityY, bounds.left, bounds.right, bounds.top, bounds.bottom); - mStepper.prod(); - } - } - - return true; - } - - public void onLongPress(MotionEvent e) { - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, - float distanceY) { - if (!mScaling) { - mXScroll -= distanceX; - mYScroll -= distanceY; - requestLayout(); - } - return true; - } - - public void onShowPress(MotionEvent e) { - } - - public boolean onSingleTapUp(MotionEvent e) { - return false; - } - - public boolean onScale(ScaleGestureDetector detector) { - float previousScale = mScale; - float scale_factor = mReflow ? REFLOW_SCALE_FACTOR : 1.0f; - float min_scale = MIN_SCALE * scale_factor; - float max_scale = MAX_SCALE * scale_factor; - mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), min_scale), max_scale); - - if (mReflow) { - View v = mChildViews.get(mCurrent); - if (v != null) - onScaleChild(v, mScale); - } else { - float factor = mScale/previousScale; - - View v = mChildViews.get(mCurrent); - if (v != null) { - float currentFocusX = detector.getFocusX(); - float currentFocusY = detector.getFocusY(); - // Work out the focus point relative to the view top left - int viewFocusX = (int)currentFocusX - (v.getLeft() + mXScroll); - int viewFocusY = (int)currentFocusY - (v.getTop() + mYScroll); - // Scroll to maintain the focus point - mXScroll += viewFocusX - viewFocusX * factor; - mYScroll += viewFocusY - viewFocusY * factor; - - if (mLastScaleFocusX>=0) - mXScroll+=currentFocusX-mLastScaleFocusX; - if (mLastScaleFocusY>=0) - mYScroll+=currentFocusY-mLastScaleFocusY; - - mLastScaleFocusX=currentFocusX; - mLastScaleFocusY=currentFocusY; - requestLayout(); - } - } - return true; - } - - public boolean onScaleBegin(ScaleGestureDetector detector) { - mScaling = true; - // Ignore any scroll amounts yet to be accounted for: the - // screen is not showing the effect of them, so they can - // only confuse the user - mXScroll = mYScroll = 0; - mLastScaleFocusX = mLastScaleFocusY = -1; - return true; - } - - public void onScaleEnd(ScaleGestureDetector detector) { - if (mReflow) { - applyToChildren(new ViewMapper() { - @Override - void applyToView(View view) { - onScaleChild(view, mScale); - } - }); - } - mScaling = false; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - mScaleGestureDetector.onTouchEvent(event); - mGestureDetector.onTouchEvent(event); - - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { - mUserInteracting = true; - } - if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { - mUserInteracting = false; - - View v = mChildViews.get(mCurrent); - if (v != null) { - if (mScroller.isFinished()) { - // If, at the end of user interaction, there is no - // current inertial scroll in operation then animate - // the view onto screen if necessary - slideViewOntoScreen(v); - } - - if (mScroller.isFinished()) { - // If still there is no inertial scroll in operation - // then the layout is stable - postSettle(v); - } - } - } - - requestLayout(); - return true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int n = getChildCount(); - for (int i = 0; i < n; i++) - measureView(getChildAt(i)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - try { - onLayout2(changed, left, top, right, bottom); - } - catch (java.lang.OutOfMemoryError e) { - System.out.println("Out of memory during layout"); - - // we might get an out of memory error. - // so let's display an alert. - // TODO: a better message, in resources. - - if (!memAlert) { - memAlert = true; - AlertDialog alertDialog = MuPDFActivity.getAlertBuilder().create(); - alertDialog.setMessage("Out of memory during layout"); - alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - memAlert = false; - } - }); - alertDialog.show(); - } - } - } - - private boolean memAlert = false; - - private void onLayout2(boolean changed, int left, int top, int right, - int bottom) { - - // "Edit mode" means when the View is being displayed in the Android GUI editor. (this class - // is instantiated in the IDE, so we need to be a bit careful what we do). - if (isInEditMode()) - return; - - View cv = mChildViews.get(mCurrent); - Point cvOffset; - - if (!mResetLayout) { - // Move to next or previous if current is sufficiently off center - if (cv != null) { - boolean move; - cvOffset = subScreenSizeOffset(cv); - // cv.getRight() may be out of date with the current scale - // so add left to the measured width for the correct position - if (HORIZONTAL_SCROLLING) - move = cv.getLeft() + cv.getMeasuredWidth() + cvOffset.x + GAP/2 + mXScroll < getWidth()/2; - else - move = cv.getTop() + cv.getMeasuredHeight() + cvOffset.y + GAP/2 + mYScroll < getHeight()/2; - if (move && mCurrent + 1 < mAdapter.getCount()) { - postUnsettle(cv); - // post to invoke test for end of animation - // where we must set hq area for the new current view - mStepper.prod(); - - onMoveOffChild(mCurrent); - mCurrent++; - onMoveToChild(mCurrent); - } - - if (HORIZONTAL_SCROLLING) - move = cv.getLeft() - cvOffset.x - GAP/2 + mXScroll >= getWidth()/2; - else - move = cv.getTop() - cvOffset.y - GAP/2 + mYScroll >= getHeight()/2; - if (move && mCurrent > 0) { - postUnsettle(cv); - // post to invoke test for end of animation - // where we must set hq area for the new current view - mStepper.prod(); - - onMoveOffChild(mCurrent); - mCurrent--; - onMoveToChild(mCurrent); - } - } - - // Remove not needed children and hold them for reuse - int numChildren = mChildViews.size(); - int childIndices[] = new int[numChildren]; - for (int i = 0; i < numChildren; i++) - childIndices[i] = mChildViews.keyAt(i); - - for (int i = 0; i < numChildren; i++) { - int ai = childIndices[i]; - if (ai < mCurrent - 1 || ai > mCurrent + 1) { - View v = mChildViews.get(ai); - onNotInUse(v); - mViewCache.add(v); - removeViewInLayout(v); - mChildViews.remove(ai); - } - } - } else { - mResetLayout = false; - mXScroll = mYScroll = 0; - - // Remove all children and hold them for reuse - int numChildren = mChildViews.size(); - for (int i = 0; i < numChildren; i++) { - View v = mChildViews.valueAt(i); - onNotInUse(v); - mViewCache.add(v); - removeViewInLayout(v); - } - mChildViews.clear(); - - // Don't reuse cached views if the adapter has changed - if (mReflowChanged) { - mReflowChanged = false; - mViewCache.clear(); - } - - // post to ensure generation of hq area - mStepper.prod(); - } - - // Ensure current view is present - int cvLeft, cvRight, cvTop, cvBottom; - boolean notPresent = (mChildViews.get(mCurrent) == null); - cv = getOrCreateChild(mCurrent); - // When the view is sub-screen-size in either dimension we - // offset it to center within the screen area, and to keep - // the views spaced out - cvOffset = subScreenSizeOffset(cv); - if (notPresent) { - //Main item not already present. Just place it top left - cvLeft = cvOffset.x; - cvTop = cvOffset.y; - } else { - // Main item already present. Adjust by scroll offsets - cvLeft = cv.getLeft() + mXScroll; - cvTop = cv.getTop() + mYScroll; - } - // Scroll values have been accounted for - mXScroll = mYScroll = 0; - cvRight = cvLeft + cv.getMeasuredWidth(); - cvBottom = cvTop + cv.getMeasuredHeight(); - - if (!mUserInteracting && mScroller.isFinished()) { - Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvRight += corr.x; - cvLeft += corr.x; - cvTop += corr.y; - cvBottom += corr.y; - } else if (HORIZONTAL_SCROLLING && cv.getMeasuredHeight() <= getHeight()) { - // When the current view is as small as the screen in height, clamp - // it vertically - Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvTop += corr.y; - cvBottom += corr.y; - } else if (!HORIZONTAL_SCROLLING && cv.getMeasuredWidth() <= getWidth()) { - // When the current view is as small as the screen in width, clamp - // it horizontally - Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); - cvRight += corr.x; - cvLeft += corr.x; - } - - cv.layout(cvLeft, cvTop, cvRight, cvBottom); - - if (mCurrent > 0) { - View lv = getOrCreateChild(mCurrent - 1); - Point leftOffset = subScreenSizeOffset(lv); - if (HORIZONTAL_SCROLLING) - { - int gap = leftOffset.x + GAP + cvOffset.x; - lv.layout(cvLeft - lv.getMeasuredWidth() - gap, - (cvBottom + cvTop - lv.getMeasuredHeight())/2, - cvLeft - gap, - (cvBottom + cvTop + lv.getMeasuredHeight())/2); - } else { - int gap = leftOffset.y + GAP + cvOffset.y; - lv.layout((cvLeft + cvRight - lv.getMeasuredWidth())/2, - cvTop - lv.getMeasuredHeight() - gap, - (cvLeft + cvRight + lv.getMeasuredWidth())/2, - cvTop - gap); - } - } - - if (mCurrent + 1 < mAdapter.getCount()) { - View rv = getOrCreateChild(mCurrent + 1); - Point rightOffset = subScreenSizeOffset(rv); - if (HORIZONTAL_SCROLLING) - { - int gap = cvOffset.x + GAP + rightOffset.x; - rv.layout(cvRight + gap, - (cvBottom + cvTop - rv.getMeasuredHeight())/2, - cvRight + rv.getMeasuredWidth() + gap, - (cvBottom + cvTop + rv.getMeasuredHeight())/2); - } else { - int gap = cvOffset.y + GAP + rightOffset.y; - rv.layout((cvLeft + cvRight - rv.getMeasuredWidth())/2, - cvBottom + gap, - (cvLeft + cvRight + rv.getMeasuredWidth())/2, - cvBottom + gap + rv.getMeasuredHeight()); - } - } - - invalidate(); - } - - @Override - public Adapter getAdapter() { - return mAdapter; - } - - @Override - public View getSelectedView() { - return null; - } - - @Override - public void setAdapter(Adapter adapter) { - - // release previous adapter's bitmaps - if (null!=mAdapter && adapter!=mAdapter) { - if (adapter instanceof MuPDFPageAdapter){ - ((MuPDFPageAdapter) adapter).releaseBitmaps(); - } - } - - mAdapter = adapter; - - requestLayout(); - } - - @Override - public void setSelection(int arg0) { - throw new UnsupportedOperationException(getContext().getString(R.string.not_supported)); - } - - private View getCached() { - if (mViewCache.size() == 0) - return null; - else - return mViewCache.removeFirst(); - } - - private View getOrCreateChild(int i) { - View v = mChildViews.get(i); - if (v == null) { - v = mAdapter.getView(i, getCached(), this); - addAndMeasureChild(i, v); - onChildSetup(i, v); - onScaleChild(v, mScale); - } - - return v; - } - - private void addAndMeasureChild(int i, View v) { - LayoutParams params = v.getLayoutParams(); - if (params == null) { - params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - } - addViewInLayout(v, 0, params, true); - mChildViews.append(i, v); // Record the view against it's adapter index - measureView(v); - } - - private void measureView(View v) { - // See what size the view wants to be - v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - - if (!mReflow) { - // Work out a scale that will fit it to this view - float scale = Math.min((float)getWidth()/(float)v.getMeasuredWidth(), - (float)getHeight()/(float)v.getMeasuredHeight()); - // Use the fitting values scaled by our current scale factor - v.measure(View.MeasureSpec.EXACTLY | (int)(v.getMeasuredWidth()*scale*mScale), - View.MeasureSpec.EXACTLY | (int)(v.getMeasuredHeight()*scale*mScale)); - } else { - v.measure(View.MeasureSpec.EXACTLY | (int)(v.getMeasuredWidth()), - View.MeasureSpec.EXACTLY | (int)(v.getMeasuredHeight())); - } - } - - private Rect getScrollBounds(int left, int top, int right, int bottom) { - int xmin = getWidth() - right; - int xmax = -left; - int ymin = getHeight() - bottom; - int ymax = -top; - - // In either dimension, if view smaller than screen then - // constrain it to be central - if (xmin > xmax) xmin = xmax = (xmin + xmax)/2; - if (ymin > ymax) ymin = ymax = (ymin + ymax)/2; - - return new Rect(xmin, ymin, xmax, ymax); - } - - private Rect getScrollBounds(View v) { - // There can be scroll amounts not yet accounted for in - // onLayout, so add mXScroll and mYScroll to the current - // positions when calculating the bounds. - return getScrollBounds(v.getLeft() + mXScroll, - v.getTop() + mYScroll, - v.getLeft() + v.getMeasuredWidth() + mXScroll, - v.getTop() + v.getMeasuredHeight() + mYScroll); - } - - private Point getCorrection(Rect bounds) { - return new Point(Math.min(Math.max(0,bounds.left),bounds.right), - Math.min(Math.max(0,bounds.top),bounds.bottom)); - } - - private void postSettle(final View v) { - // onSettle and onUnsettle are posted so that the calls - // wont be executed until after the system has performed - // layout. - post (new Runnable() { - public void run () { - onSettle(v); - } - }); - } - - private void postUnsettle(final View v) { - post (new Runnable() { - public void run () { - onUnsettle(v); - } - }); - } - - private void slideViewOntoScreen(View v) { - Point corr = getCorrection(getScrollBounds(v)); - if (corr.x != 0 || corr.y != 0) { - mScrollerLastX = mScrollerLastY = 0; - mScroller.startScroll(0, 0, corr.x, corr.y, 400); - mStepper.prod(); - } - } - - private Point subScreenSizeOffset(View v) { - return new Point(Math.max((getWidth() - v.getMeasuredWidth())/2, 0), - Math.max((getHeight() - v.getMeasuredHeight())/2, 0)); - } - - private static int directionOfTravel(float vx, float vy) { - if (Math.abs(vx) > 2 * Math.abs(vy)) - return (vx > 0) ? MOVING_RIGHT : MOVING_LEFT; - else if (Math.abs(vy) > 2 * Math.abs(vx)) - return (vy > 0) ? MOVING_DOWN : MOVING_UP; - else - return MOVING_DIAGONALLY; - } - - private static boolean withinBoundsInDirectionOfTravel(Rect bounds, float vx, float vy) { - switch (directionOfTravel(vx, vy)) { - case MOVING_DIAGONALLY: return bounds.contains(0, 0); - case MOVING_LEFT: return bounds.left <= 0; - case MOVING_RIGHT: return bounds.right >= 0; - case MOVING_UP: return bounds.top <= 0; - case MOVING_DOWN: return bounds.bottom >= 0; - default: throw new NoSuchElementException(); - } - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java b/platform/android/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java deleted file mode 100644 index 7f715bb4..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.animation.AnimatorSet; -import android.app.Activity; -import android.view.View; - -public class SafeAnimatorInflater -{ - private View mView; - - public SafeAnimatorInflater(Activity activity, int animation, View view) - { - AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(activity, R.animator.info); - mView = view; - set.setTarget(view); - set.addListener(new Animator.AnimatorListener() { - public void onAnimationStart(Animator animation) { - mView.setVisibility(View.VISIBLE); - } - - public void onAnimationRepeat(Animator animation) { - } - - public void onAnimationEnd(Animator animation) { - mView.setVisibility(View.INVISIBLE); - } - - public void onAnimationCancel(Animator animation) { - } - }); - set.start(); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/SearchTask.java b/platform/android/src/com/artifex/mupdfdemo/SearchTask.java deleted file mode 100644 index d3969f10..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/SearchTask.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.RectF; -import android.os.Handler; - -class ProgressDialogX extends ProgressDialog { - public ProgressDialogX(Context context) { - super(context); - } - - private boolean mCancelled = false; - - public boolean isCancelled() { - return mCancelled; - } - - @Override - public void cancel() { - mCancelled = true; - super.cancel(); - } -} - -public abstract class SearchTask { - private static final int SEARCH_PROGRESS_DELAY = 200; - private final Context mContext; - private final MuPDFCore mCore; - private final Handler mHandler; - private final AlertDialog.Builder mAlertBuilder; - private AsyncTask mSearchTask; - - public SearchTask(Context context, MuPDFCore core) { - mContext = context; - mCore = core; - mHandler = new Handler(); - mAlertBuilder = new AlertDialog.Builder(context); - } - - protected abstract void onTextFound(SearchTaskResult result); - - public void stop() { - if (mSearchTask != null) { - mSearchTask.cancel(true); - mSearchTask = null; - } - } - - public void go(final String text, int direction, int displayPage, int searchPage) { - if (mCore == null) - return; - stop(); - - final int increment = direction; - final int startIndex = searchPage == -1 ? displayPage : searchPage + increment; - - final ProgressDialogX progressDialog = new ProgressDialogX(mContext); - progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progressDialog.setTitle(mContext.getString(R.string.searching_)); - progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - stop(); - } - }); - progressDialog.setMax(mCore.countPages()); - - mSearchTask = new AsyncTask() { - @Override - protected SearchTaskResult doInBackground(Void... params) { - int index = startIndex; - - while (0 <= index && index < mCore.countPages() && !isCancelled()) { - publishProgress(index); - RectF searchHits[] = mCore.searchPage(index, text); - - if (searchHits != null && searchHits.length > 0) - return new SearchTaskResult(text, index, searchHits); - - index += increment; - } - return null; - } - - @Override - protected void onPostExecute(SearchTaskResult result) { - progressDialog.cancel(); - if (result != null) { - onTextFound(result); - } else { - mAlertBuilder.setTitle(SearchTaskResult.get() == null ? R.string.text_not_found : R.string.no_further_occurrences_found); - AlertDialog alert = mAlertBuilder.create(); - alert.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getString(R.string.dismiss), - (DialogInterface.OnClickListener)null); - alert.show(); - } - } - - @Override - protected void onCancelled() { - progressDialog.cancel(); - } - - @Override - protected void onProgressUpdate(Integer... values) { - progressDialog.setProgress(values[0].intValue()); - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - mHandler.postDelayed(new Runnable() { - public void run() { - if (!progressDialog.isCancelled()) - { - progressDialog.show(); - progressDialog.setProgress(startIndex); - } - } - }, SEARCH_PROGRESS_DELAY); - } - }; - - mSearchTask.execute(); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/SearchTaskResult.java b/platform/android/src/com/artifex/mupdfdemo/SearchTaskResult.java deleted file mode 100644 index 8fa3c3a2..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/SearchTaskResult.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.graphics.RectF; - -public class SearchTaskResult { - public final String txt; - public final int pageNumber; - public final RectF searchBoxes[]; - static private SearchTaskResult singleton; - - SearchTaskResult(String _txt, int _pageNumber, RectF _searchBoxes[]) { - txt = _txt; - pageNumber = _pageNumber; - searchBoxes = _searchBoxes; - } - - static public SearchTaskResult get() { - return singleton; - } - - static public void set(SearchTaskResult r) { - singleton = r; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/Separation.java b/platform/android/src/com/artifex/mupdfdemo/Separation.java deleted file mode 100644 index eadda4ba..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/Separation.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.artifex.mupdfdemo; - -public class Separation -{ - String name; - int rgba; - int cmyk; - - public Separation(String name, int rgba, int cmyk) - { - this.name = name; - this.rgba = rgba; - this.cmyk = cmyk; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/Stepper.java b/platform/android/src/com/artifex/mupdfdemo/Stepper.java deleted file mode 100644 index d22240ef..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/Stepper.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.annotation.SuppressLint; -import android.os.Build; -import android.view.View; - -public class Stepper { - protected final View mPoster; - protected final Runnable mTask; - protected boolean mPending; - - public Stepper(View v, Runnable r) { - mPoster = v; - mTask = r; - mPending = false; - } - - @SuppressLint("NewApi") - public void prod() { - if (!mPending) { - mPending = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - mPoster.postOnAnimation(new Runnable() { - @Override - public void run() { - mPending = false; - mTask.run(); - } - }); - } else { - mPoster.post(new Runnable() { - @Override - public void run() { - mPending = false; - mTask.run(); - } - }); - - } - } - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/TextChar.java b/platform/android/src/com/artifex/mupdfdemo/TextChar.java deleted file mode 100644 index aebf519f..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/TextChar.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.graphics.RectF; - -public class TextChar extends RectF { - public char c; - - public TextChar(float x0, float y0, float x1, float y1, char _c) { - super(x0, y0, x1, y1); - c = _c; - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/TextWord.java b/platform/android/src/com/artifex/mupdfdemo/TextWord.java deleted file mode 100644 index d9672573..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/TextWord.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.artifex.mupdfdemo; - -import android.graphics.RectF; - -public class TextWord extends RectF { - public String w; - - public TextWord() { - super(); - w = new String(); - } - - public void Add(TextChar tc) { - super.union(tc); - w = w.concat(new String(new char[]{tc.c})); - } -} diff --git a/platform/android/src/com/artifex/mupdfdemo/WidgetType.java b/platform/android/src/com/artifex/mupdfdemo/WidgetType.java deleted file mode 100644 index 882a38f2..00000000 --- a/platform/android/src/com/artifex/mupdfdemo/WidgetType.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.artifex.mupdfdemo; - -public enum WidgetType { - NONE, - TEXT, - LISTBOX, - COMBOBOX, - SIGNATURE -} diff --git a/platform/android/viewer/.gitignore b/platform/android/viewer/.gitignore new file mode 100644 index 00000000..644037fa --- /dev/null +++ b/platform/android/viewer/.gitignore @@ -0,0 +1,8 @@ +.gradle +/local.properties +.idea +.DS_Store +/build +/captures +*.iml +obj diff --git a/platform/android/viewer/AndroidManifest.xml b/platform/android/viewer/AndroidManifest.xml new file mode 100644 index 00000000..29c20f84 --- /dev/null +++ b/platform/android/viewer/AndroidManifest.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/viewer/ClassStructure.txt b/platform/android/viewer/ClassStructure.txt new file mode 100644 index 00000000..39939674 --- /dev/null +++ b/platform/android/viewer/ClassStructure.txt @@ -0,0 +1,187 @@ +MuPDFActivity +~~~~~~~~~~~~~ + +MuPDFActivity is the main activity used when displaying and interacting with a +document. This class is responsible for creating the view hierarchy and the +menus. + + +Main view classes +~~~~~~~~~~~~~~~~~ + +ReaderView +~~~~~~~~~~ +MuPDF uses Android's standard Adapter/AdapterView paradigm, where a subclass of +BaseAdapter supplies multiple views that have their motion on screen +choreographed by a subclass of AdapterView. There are several standard +AdapterView subclasses, but none support zooming into a specific subview and +then panning within it, so MuPDF has its own AdapterView subclass, namely +ReaderView. The class is intended to be general purpose and usable within any +document-viewing application. During page viewing, ReaderView handles all touch +events, recognises gestures and positions the displayed document pages +accordingly. ReaderView needs to handle positioning slightly differently +depending on whether MuPDF is reflowing text or not, and so it has two slightly +different modes of operation. + +MuPDFReaderView +~~~~~~~~~~~~~~~ +MuPDFReaderView subclasses ReaderView, so as to provide some of the +page-positioning behaviour that is specific to MuPDF. It overrides some of the +gesture recognition methods of ReaderView, so that it can perform special +handling of (e.g.) tapping on the side of the screen for page progression, and +tapping on links or form fields. It also handles the disabling of scrolling +during text-selection and annotation-drawing, and it performs the setup +operations needed by the individual page views as each newly appears. + +MuPDFView +~~~~~~~~~ +Document viewing uses different View subclasses to display the individual pages +depending on whether reflowing text or displaying pages unaltered. MuPDFView is +the common interface to the two view subclasses. + +PageView +~~~~~~~~ +PageView is the main View class used for non-reflow display of a page. Like +ReaderView, it is intended to be, as much as is possible, independent of the +specifics of MuPDF and usable in general document display apps. It is a +subclass of ViewGroup because page displays are built from several layers. The +lowest layer is a rendering of the page at a resolution that matches the screen +exactly when maximally zoomed out so that the page fits the screen. As the user +zooms in, this layer maintains a visible appearance of the page, but one that +becomes more blurred as zooming in progresses. A second layer provides a higher +resolution rendering of just the area of the page that is visible on screen, +and at a resolution that matches the screen. As the user pans, this layer is +updated on a background thread, so parts of the blurred layer will temporarily +become visible, but only momentarily later to be replaced by the high-quality +rendering. There is one further layer that is used to draw transparent shapes +for highlighting and the like. + +MuPDFPageView +~~~~~~~~~~~~~ +MuPDFPageView is a subclass of PageView, which handles some of the specifics of +MuPDF's behaviour, such as taps on links and form fields, text selection, and +annotation drawing. It also handles its parent class's bitmap rendering calls. +This is the class used to display pages in non-reflow mode. It implements the +MuPDFView interface. + +MuPDFReflowView +~~~~~~~~~~~~~~~ +This is the class used to display pages in reflow mode. Like MuPDFPageView it +implements the MuPDFView interface. It is a subclass of WebView, and achieves +reflowing by loading an HTML version of the page, which the MuPDF core +constructs. + +MuPDFPageAdapter and MuPDFReflowAdapter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +As with any AdapterView subclass, ReaderView needs an Adapter subclass to +supply, on demand, the subviews for the pages currently displayed. These are +the two Adapter subclasses, supplying the subviews as MuPDFPageView and +MuPDFReflowView objects respectively. The former is a little more complex than +the latter, since it caches the sizes of the pages corresponding to the views +it supplies. It does so, so that page views, on their second and subsequent +appearances, can take on their correct size immediately. (The determining of +page size is not a completely trivial operation and is performed on a +background thread, as is all interaction with the core MuPDF library). + + +C library wrapper +~~~~~~~~~~~~~~~~~ + +MuPDFCore +~~~~~~~~~ +This class is the interface to the MuPDF C library. It is used to render bitmap +versions of the page for display in the view classes mentioned above. It also +provides for interaction with objects within the page, such as the individual +text objects and annotations. Many of the methods take too long an execution +time to be run on the UI thread, hence they need to be run in the background, +and because even the fast methods have to be synchronised with the slower +methods, (almost) all methods should be called in the background. There are a +few non synchronised ones that have special purposes. + + +Link handling +~~~~~~~~~~~~~ +There are three types of PDF links, each entailing different information and +requiring different handling. There are five classes involved in their +representation. + +LinkInfo is the base class representing any one of the three + +LinkInfoExternal, LinkInfoInternal and LinkInfoRemote are the three subclasses +representing the specific cases. + +LinkInfoVisitor is a class implementing a common Java paradigm which allows +case analysis on the three different types of link, executing different methods +for each. + +BitmapHolder +~~~~~~~~~~~~ +BitmapHolder is the solution to a problem in allocating the Bitmaps to which +rendering is performed by background tasks. Renderings for the purpose of +update have to be passed a Bitmap with the current page state. During frenetic +page flicking a large number of rendering tasks can be queued, each holding +reference to a Bitmap. Rather than pass the Bitmap directly, we pass a +BitmapHolder containing a reference to the Bitmap. When a page view transitions +off screen, the BitmapHolder's reference to the Bitmap can be nulled to release +it. + +SearchTask and SearchTaskResult +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +SearchTask encapsulates the process of searching for a text string within a +document. The class uses an AsyncTask internally to perform the search in the +background and reports the result by calling onTextFound. A SearchTaskResult +object is used to return the result of the search. + +SafeAnimatorInflator +~~~~~~~~~~~~~~~~~~~~ +This class is a simple wrapper around AnimatorInflator. AnimatorInflator +doesn't exist in some of the Android API levels MuPDF supports, and the wrapper +allows for a test of API-level before the point at which the class would be +loaded. + +MuPDFAlert and MuPDFAlertInternal +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This class represents the information issued by a javascript app.alert call. +MuPDFAlertInternal represents the same information, but with Java enums +replaced by ints, which are easier to return from JNI code. + +TextChar and TextWord +~~~~~~~~~~~~~~~~~~~~~ +TextChar is used when processing the individual characters of the page. Each +TextChar object contains the character and the rectangular area of the page at +which it appears. TextWord is used to gather TextChars into words. + +Annotation +~~~~~~~~~~ +This class represents the type and position on page of a PDF annotation. + +Other activities +~~~~~~~~~~~~~~~~ +The app has three activities other than document-viewing. + +ChoosePDFActivity +~~~~~~~~~~~~~~~~~ +ChoosePDFActivity allows the user to navigate local disc directories and view a +list of loadable files, from which one can be chosen. It derives off +ListActivity, and so displays the files in a standard ListView. ChoosePDFItem +represents the various types of list entry: up-one, directory or file. +ChoosePDFAdapter populates the list view. + +OutlineActivity +~~~~~~~~~~~~~~~ +OutlineActivity displays a PDF document's outline as a list of selectable +section titles. OutlineActivityData represents the current state of the +activity. OutlineItem represents the individual items, and OutlineAdapter +populates the list view. + +PrintDialogActivity +~~~~~~~~~~~~~~~~~~~ +This activity allows the user to print documents via Google Cloud Print. + + +Copied system classes +~~~~~~~~~~~~~~~~~~~~~ +AsyncTask has had improvements made to it since issuing at the lowest android +API level we support, and so we include the improved version as part of the +MuPDF app. We also include Deque and ArrayDeque, which are used my AsyncTask. + diff --git a/platform/android/viewer/Icons.txt b/platform/android/viewer/Icons.txt new file mode 100644 index 00000000..9d0082bf --- /dev/null +++ b/platform/android/viewer/Icons.txt @@ -0,0 +1,2 @@ +The icons are from http://somerandomdude.com/work/iconic/ +They are covered by the CC-BY-SA license: http://creativecommons.org/licenses/by-sa/3.0/us/ diff --git a/platform/android/viewer/Makefile b/platform/android/viewer/Makefile new file mode 100644 index 00000000..9d061919 --- /dev/null +++ b/platform/android/viewer/Makefile @@ -0,0 +1,3 @@ +default: + ndk-build + ant debug diff --git a/platform/android/viewer/ReadMe.txt b/platform/android/viewer/ReadMe.txt new file mode 100644 index 00000000..f1c0c48c --- /dev/null +++ b/platform/android/viewer/ReadMe.txt @@ -0,0 +1,188 @@ +To build/debug android viewer. + +1) Download the android sdk, and install it. These instructions have been +written with r14 (the latest version at time of writing) of the SDK in mind; +other versions may give problems. On windows r14 unpacked as: + + C:\Program Files (x86)\Android\android-sdk + +on Macos an older version installed as: + + /Library/android-sdk-mac_x86 + +on Linux install it as: + + mkdir ~/android-sdk + cd ~/android-sdk + tar ~/Downloads/android-sdk_r20.0.3-linux.tgz + +Whatever directory it unpacks to, ensure that both the 'tools' and +'platform-tools' directories inside it have been added to your PATH. + +2) Download the android ndk, and unpack it. These instructions were written +with NDK r6b (the latest version at the time of writing) in mind, but the +build has now been tweaked to work with r10b. Other versions may give problems. + +It's important that you use the correct NDK for the target platform. If you're +targeting a 32-bit platform (such as "ARM EABI v7a" or "Intel x86 Arm") then +you MUST use the 32-bit target NDK. If you get UnsatisfiedLinkError when +opening a document in MuPDF, then you've tried to use the 64-bit target NDK +with a 32-bit target! + +On windows I unpacked it as: + + C:\android-ndk-r10b + +on Macos an older version unpacked as: + + /Library/android-ndk-r5 + +on Linux as: + + mkdir ~/android-ndk + cd ~/android-ndk + tar jxvf ~/Downloads/android-ndk32-r10b-linux-x86.tar.bz2 + +It is very important that you should unpack it to a directory with no +spaces in the name! (Don't be tempted to put it in C:\Program Files etc) + +Ensure that that directory is also added to your PATH. + +3) On windows, to use the ndk, you *must* be running under cygwin. This means +you need to install Cygwin 1.7 or greater now. + +[ In version r5 of the ndk, when running under cygwin, there were ] +[ bugs to do with the automatic conversion of dependencies from DOS ] +[ format paths to cygwin format paths. The 2 fixes can be found in: ] +[ ] +[ ] +[ ] +[ Use the latest version and there should not be a problem. ] + +4) If the SDK has not popped up a window already, bring up a shell, and run +'android' (or android.bat on cygwin/windows). You should now have a window +with a graphical gui for the sdk. From here you can install the different SDK +components for the different flavours of android. Download them all - +bandwidth and disk space are cheap, right? Make sure you get at least +the API level 11 as this is the current dependency for mupdf. + +5) In new versions of the GUI there is a 'Tools' menu from which you can +select 'Manage AVDs...'. In old versions, go to the Virtual Devices entry +on the right hand side. You need to create yourself an emulator image to +use. Click 'New...' on the right hand side and a window will appear. Fill +in the entries as follows: + + Name: FroyoEm + Target: Android 2.2 - API Level 8 + CPU/ABI: ARM (armeabi) (If this option exists) + SD card: Size: 1024MiB + Skin: Resolution: 480x756 (756 just fits my macbook screen, but 800 may + be 'more standard') + +Click 'Create AVD' (on old versions you may have to wait for a minute or +so while it is prepared. Now you can exit the GUI. + +6) You will need a copy of the JDK installed. See +. When this +installs, ensure that JAVA_HOME is set to point to the installation +directory. + +7) You will need a copy of Apache ANT installed. +See . Ensure that ANT_HOME is set to point to +the top level directory, and that ANT_HOME/bin is on the PATH. + +8) Now we are ready to build mupdf viewer for Android. Check out a copy of MuPDF +(but you've done that already, cos you're reading this, right?). + +9) You will also need a copy of mupdf's thirdparty libraries. If you are +using git, make sure to do a git submodule update --init from the top of +the build tree. Older versions packaged this source code in a .zip-file +(see the source code link on http://mupdf.com/). Unpack the contents of +this into a 'thirdparty' directory created within the mupdf directory +(i.e. at the same level as fitz, pdf, android etc). + +10) Finally, you will need a copy of a 'generated' directory. This is not +currently available to download. + +The normal mupdf build process involves running some code on the host +(the machine on which you are compiling), rather than the target (the +machine/device on which you eventually want to run mupdf). This code +repacks various bits of information (fonts, CMAPs etc) into a more +compact and usable form. + +Unfortunately, the android SDK does not provide a compiler for the host +machine, so we cannot run this step automatically as part of the android +viewer build. You will need to generate it by running a different build, such +as the windows or linux native builds. + +We do not make a snapshot of the generated directory available to +download as the contents of this directory change frequently, and we'd +have to keep multiple versions on the website. We assume that anyone +capable of building for android is capable of doing a normal hosted +build. + +On windows (where you are using cygwin), or on linux/macos, this can be +as simple as running 'make generate' in the top level directory. + +11) Change into mupdf's android/viewer directory. Copy the +android/viewer/local.properties.sample file to be android/viewer/local.properties and +change the sdk path there as appropriate. This should be the only bit of +localisation you need to do. + +12) Change into the android/viewer directory (note, the android/viewer directory, NOT +the android/viewer/jni directory!), and execute (in a Cygwin window on Windows!): + + ndk-build + +This should build the native code portion. + +If this dies with an error in thirdparty/jbig2/os_types.h load this +file into an editor, and change line 43 from: + + #else + +to + + #elif !defined(HAVE_STDINT_H) + +and this should solve the problem. + +13) Then execute: + + ant debug + +or on windows under cygwin: + + ant.bat debug + +This should build the java wrapper. + +14) Now start the emulator by executing: + + emulator -avd FroyoEm + +This will take a while to full start up (be patient). + +15) We now need to give the demo file something to chew on, so let's copy +a file into the SD card image of the emulator (this should only need to be +done once). With the emulator running type: + + adb push ../../MyTests/pdf_reference17.pdf /mnt/sdcard/Download/test.pdf + +(where obviously ../../MyTests/pdf_reference17.pdf is altered for your +machine, and under Windows, should start c:/ even if invoked from cygwin) +(adb lives in /platform-tools if it's not on your path). + +16) With the emulator running (see step 14), execute + + ant debug install + +('ant.bat debug install' on Windows) and that will copy MuPDF into the +emulator where you can run it from the launchpad screen. + +17) To see debug messages from the emulator (including stdout/stderr from +our app), execute: + + adb logcat + +Good luck! diff --git a/platform/android/viewer/build.xml b/platform/android/viewer/build.xml new file mode 100644 index 00000000..7cb2cdf8 --- /dev/null +++ b/platform/android/viewer/build.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/viewer/jni/Android.mk b/platform/android/viewer/jni/Android.mk new file mode 100644 index 00000000..1351680f --- /dev/null +++ b/platform/android/viewer/jni/Android.mk @@ -0,0 +1,38 @@ +LOCAL_PATH := $(call my-dir) +TOP_LOCAL_PATH := $(LOCAL_PATH) + +MUPDF_ROOT := ../../.. + +ifdef NDK_PROFILER +include android-ndk-profiler.mk +endif + +include $(TOP_LOCAL_PATH)/Core.mk +include $(TOP_LOCAL_PATH)/ThirdParty.mk + +include $(CLEAR_VARS) + +LOCAL_C_INCLUDES := \ + jni/andprof \ + $(MUPDF_ROOT)/include \ + $(MUPDF_ROOT)/source/fitz \ + $(MUPDF_ROOT)/source/pdf \ + $(MUPDF_ROOT)/platform/java +LOCAL_CFLAGS := -DHAVE_ANDROID +LOCAL_MODULE := mupdf_java +LOCAL_SRC_FILES := mupdf.c +LOCAL_STATIC_LIBRARIES := mupdfcore mupdfthirdparty +ifdef NDK_PROFILER +LOCAL_CFLAGS += -pg -DNDK_PROFILER +LOCAL_STATIC_LIBRARIES += andprof +endif +ifdef SUPPORT_GPROOF +LOCAL_CFLAGS += -DSUPPORT_GPROOF +endif + +LOCAL_LDLIBS := -lm -llog -ljnigraphics +ifdef SSL_BUILD +LOCAL_LDLIBS += -L$(MUPDF_ROOT)/thirdparty/openssl/android -lcrypto -lssl +endif + +include $(BUILD_SHARED_LIBRARY) diff --git a/platform/android/viewer/jni/Application.mk b/platform/android/viewer/jni/Application.mk new file mode 100644 index 00000000..4cedaf8d --- /dev/null +++ b/platform/android/viewer/jni/Application.mk @@ -0,0 +1,49 @@ +# When we build for google play, we build 4 different apk's, each with +# a different version, by uncommenting one of the pairs of lines below. +# Suppose our base version is X: + +# Version X: armeabi +#APP_PLATFORM=android-8 +#APP_ABI := armeabi + +# Version X+1: armeabi-v7a (Much faster due to the availability of hardware +# FP, but cannot be run in the emulator). +APP_PLATFORM=android-8 +APP_ABI := armeabi-v7a + +# Version X+2: x86 (Requires android-9, so a change needs to be made in +# AndroidManifest.xml too) +#APP_PLATFORM=android-9 +#APP_ABI := x86 + +# Version X+3: mips (Requires android-9, so a change needs to be made in +# AndroidManifest.xml too) +#APP_PLATFORM=android-9 +#APP_ABI := mips + +ifdef NDK_PROFILER +# The profiler doesn't seem to receive ticks when run on release code. +# Accordingly, we need to build as debug - but this turns optimisations +# off, which is less than ideal. +APP_OPTIM := debug +APP_CFLAGS := -O2 +else +ifdef DEBUG +APP_OPTIM := debug +APP_CFLAGS := -DDEBUG +else +APP_OPTIM := release +endif +endif +ifdef V8_BUILD +APP_STL := stlport_static +endif +ifdef MEMENTO +APP_CFLAGS += -DMEMENTO -DMEMENTO_LEAKONLY +endif + +# If the ndk is r8b then workaround bug by uncommenting the following line +#NDK_TOOLCHAIN_VERSION=4.4.3 + +# If the ndk is newer than r8c, try using clang. +#NDK_TOOLCHAIN_VERSION=clang3.1 diff --git a/platform/android/viewer/jni/Core.mk b/platform/android/viewer/jni/Core.mk new file mode 100644 index 00000000..76241841 --- /dev/null +++ b/platform/android/viewer/jni/Core.mk @@ -0,0 +1,80 @@ +LOCAL_PATH := $(call my-dir) + +ifdef SUPPORT_GPROOF +include $(CLEAR_VARS) +LOCAL_MODULE := gsso +LOCAL_SRC_FILES := libgs.so +include $(PREBUILT_SHARED_LIBRARY) +endif + +include $(CLEAR_VARS) + +MY_ROOT := ../../.. + +LOCAL_CFLAGS += -Wall -Wno-maybe-uninitialized + +ifeq ($(TARGET_ARCH),arm) +LOCAL_CFLAGS += -DARCH_ARM -DARCH_THUMB -DARCH_ARM_CAN_LOAD_UNALIGNED +ifdef NDK_PROFILER +LOCAL_CFLAGS += -pg -DNDK_PROFILER +endif +endif +ifdef SUPPORT_GPROOF +LOCAL_CFLAGS += -DSUPPORT_GPROOF +endif +LOCAL_CFLAGS += -DAA_BITS=8 +ifdef MEMENTO +LOCAL_CFLAGS += -DMEMENTO -DMEMENTO_LEAKONLY +endif +ifdef SSL_BUILD +LOCAL_CFLAGS += -DHAVE_OPENSSL +endif + +LOCAL_C_INCLUDES := \ + $(MY_ROOT)/thirdparty/harfbuzz/src \ + $(MY_ROOT)/thirdparty/jbig2dec \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg \ + $(MY_ROOT)/thirdparty/jpeg \ + $(MY_ROOT)/thirdparty/mujs \ + $(MY_ROOT)/thirdparty/zlib \ + $(MY_ROOT)/thirdparty/freetype/include \ + $(MY_ROOT)/source/fitz \ + $(MY_ROOT)/source/pdf \ + $(MY_ROOT)/source/xps \ + $(MY_ROOT)/source/cbz \ + $(MY_ROOT)/source/img \ + $(MY_ROOT)/source/tiff \ + $(MY_ROOT)/scripts/freetype \ + $(MY_ROOT)/scripts/jpeg \ + $(MY_ROOT)/scripts/openjpeg \ + $(MY_ROOT)/generated \ + $(MY_ROOT)/resources \ + $(MY_ROOT)/include \ + $(MY_ROOT) +ifdef V8_BUILD +LOCAL_C_INCLUDES += $(MY_ROOT)/thirdparty/$(V8)/include +endif +ifdef SSL_BUILD +LOCAL_C_INCLUDES += $(MY_ROOT)/thirdparty/openssl/include +endif + +LOCAL_MODULE := mupdfcore +LOCAL_SRC_FILES := \ + $(wildcard $(MY_ROOT)/source/fitz/*.c) \ + $(wildcard $(MY_ROOT)/source/pdf/*.c) \ + $(wildcard $(MY_ROOT)/source/xps/*.c) \ + $(wildcard $(MY_ROOT)/source/cbz/*.c) \ + $(wildcard $(MY_ROOT)/source/gprf/*.c) \ + $(wildcard $(MY_ROOT)/source/html/*.c) \ + $(wildcard $(MY_ROOT)/generated/*.c) +LOCAL_SRC_FILES += \ + $(MY_ROOT)/source/pdf/js/pdf-js.c \ + +ifdef SUPPORT_GPROOF +LOCAL_SHARED_LIBRARIES := gsso +endif +LOCAL_LDLIBS := -lm -llog -ljnigraphics + +LOCAL_SRC_FILES := $(addprefix ../, $(LOCAL_SRC_FILES)) + +include $(BUILD_STATIC_LIBRARY) diff --git a/platform/android/viewer/jni/ThirdParty.mk b/platform/android/viewer/jni/ThirdParty.mk new file mode 100644 index 00000000..f8965ed9 --- /dev/null +++ b/platform/android/viewer/jni/ThirdParty.mk @@ -0,0 +1,177 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +MY_ROOT := ../../.. + +LOCAL_C_INCLUDES := \ + $(MY_ROOT)/include/ \ + $(MY_ROOT)/thirdparty/harfbuzz/src \ + $(MY_ROOT)/thirdparty/jbig2dec \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg \ + $(MY_ROOT)/thirdparty/jpeg \ + $(MY_ROOT)/thirdparty/mujs \ + $(MY_ROOT)/thirdparty/zlib \ + $(MY_ROOT)/thirdparty/freetype/include \ + $(MY_ROOT)/scripts/freetype \ + $(MY_ROOT)/scripts/jpeg \ + $(MY_ROOT)/scripts/openjpeg + +LOCAL_CFLAGS := \ + -DFT2_BUILD_LIBRARY -DDARWIN_NO_CARBON -DHAVE_STDINT_H \ + -DOPJ_HAVE_STDINT_H \ + '-DFT_CONFIG_MODULES_H="slimftmodules.h"' \ + '-DFT_CONFIG_OPTIONS_H="slimftoptions.h"' \ + -Dhb_malloc_impl=hb_malloc -Dhb_calloc_impl=hb_calloc \ + -Dhb_realloc_impl=hb_realloc -Dhb_free_impl=hb_free \ + -DHAVE_OT -DHAVE_UCDN -DHB_NO_MT +ifdef NDK_PROFILER +LOCAL_CFLAGS += -pg -DNDK_PROFILER -O2 +endif +ifdef MEMENTO +LOCAL_CFLAGS += -DMEMENTO -DMEMENTO_LEAKONLY +endif + +LOCAL_CPP_EXTENSION := .cc + +LOCAL_MODULE := mupdfthirdparty +LOCAL_SRC_FILES := \ + $(MY_ROOT)/thirdparty/mujs/one.c \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-blob.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-buffer.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-buffer-serialize.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-common.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-face.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-fallback-shape.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-font.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ft.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-font.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-layout.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-map.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-arabic.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-default.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-hangul.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-hebrew.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-indic-table.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-indic.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-myanmar.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-thai.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-tibetan.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-use-table.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-complex-use.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-fallback.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape-normalize.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-shape.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ot-tag.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-set.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-shape-plan.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-shape.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-shaper.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-ucdn.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-unicode.cc \ + $(MY_ROOT)/thirdparty/harfbuzz/src/hb-warning.cc \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_arith.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_arith_iaid.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_arith_int.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_generic.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_halftone.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_huffman.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_image.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_metadata.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_mmr.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_page.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_refinement.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_segment.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_symbol_dict.c \ + $(MY_ROOT)/thirdparty/jbig2dec/jbig2_text.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/bio.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/cidx_manager.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/cio.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/dwt.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/event.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/function_list.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/image.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/invert.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/j2k.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/jp2.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/mct.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/mqc.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/openjpeg.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/opj_clock.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/phix_manager.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/pi.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/ppix_manager.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/raw.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/t1.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/t1_generate_luts.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/t2.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/tcd.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/tgt.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/thix_manager.c \ + $(MY_ROOT)/thirdparty/openjpeg/libopenjpeg/tpix_manager.c \ + $(MY_ROOT)/thirdparty/jpeg/jaricom.c \ + $(MY_ROOT)/thirdparty/jpeg/jcomapi.c \ + $(MY_ROOT)/thirdparty/jpeg/jdapimin.c \ + $(MY_ROOT)/thirdparty/jpeg/jdapistd.c \ + $(MY_ROOT)/thirdparty/jpeg/jdarith.c \ + $(MY_ROOT)/thirdparty/jpeg/jdatadst.c \ + $(MY_ROOT)/thirdparty/jpeg/jdatasrc.c \ + $(MY_ROOT)/thirdparty/jpeg/jdcoefct.c \ + $(MY_ROOT)/thirdparty/jpeg/jdcolor.c \ + $(MY_ROOT)/thirdparty/jpeg/jddctmgr.c \ + $(MY_ROOT)/thirdparty/jpeg/jdhuff.c \ + $(MY_ROOT)/thirdparty/jpeg/jdinput.c \ + $(MY_ROOT)/thirdparty/jpeg/jdmainct.c \ + $(MY_ROOT)/thirdparty/jpeg/jdmarker.c \ + $(MY_ROOT)/thirdparty/jpeg/jdmaster.c \ + $(MY_ROOT)/thirdparty/jpeg/jdmerge.c \ + $(MY_ROOT)/thirdparty/jpeg/jdpostct.c \ + $(MY_ROOT)/thirdparty/jpeg/jdsample.c \ + $(MY_ROOT)/thirdparty/jpeg/jdtrans.c \ + $(MY_ROOT)/thirdparty/jpeg/jerror.c \ + $(MY_ROOT)/thirdparty/jpeg/jfdctflt.c \ + $(MY_ROOT)/thirdparty/jpeg/jfdctfst.c \ + $(MY_ROOT)/thirdparty/jpeg/jfdctint.c \ + $(MY_ROOT)/thirdparty/jpeg/jidctflt.c \ + $(MY_ROOT)/thirdparty/jpeg/jidctfst.c \ + $(MY_ROOT)/thirdparty/jpeg/jidctint.c \ + $(MY_ROOT)/thirdparty/jpeg/jmemmgr.c \ + $(MY_ROOT)/thirdparty/jpeg/jquant1.c \ + $(MY_ROOT)/thirdparty/jpeg/jquant2.c \ + $(MY_ROOT)/thirdparty/jpeg/jutils.c \ + $(MY_ROOT)/thirdparty/zlib/adler32.c \ + $(MY_ROOT)/thirdparty/zlib/compress.c \ + $(MY_ROOT)/thirdparty/zlib/crc32.c \ + $(MY_ROOT)/thirdparty/zlib/deflate.c \ + $(MY_ROOT)/thirdparty/zlib/inffast.c \ + $(MY_ROOT)/thirdparty/zlib/inflate.c \ + $(MY_ROOT)/thirdparty/zlib/inftrees.c \ + $(MY_ROOT)/thirdparty/zlib/trees.c \ + $(MY_ROOT)/thirdparty/zlib/uncompr.c \ + $(MY_ROOT)/thirdparty/zlib/zutil.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftbase.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftbbox.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftbitmap.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftfntfmt.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftgasp.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftglyph.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftinit.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftstroke.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftsynth.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/ftsystem.c \ + $(MY_ROOT)/thirdparty/freetype/src/base/fttype1.c \ + $(MY_ROOT)/thirdparty/freetype/src/cff/cff.c \ + $(MY_ROOT)/thirdparty/freetype/src/cid/type1cid.c \ + $(MY_ROOT)/thirdparty/freetype/src/psaux/psaux.c \ + $(MY_ROOT)/thirdparty/freetype/src/pshinter/pshinter.c \ + $(MY_ROOT)/thirdparty/freetype/src/psnames/psnames.c \ + $(MY_ROOT)/thirdparty/freetype/src/raster/raster.c \ + $(MY_ROOT)/thirdparty/freetype/src/smooth/smooth.c \ + $(MY_ROOT)/thirdparty/freetype/src/sfnt/sfnt.c \ + $(MY_ROOT)/thirdparty/freetype/src/truetype/truetype.c \ + $(MY_ROOT)/thirdparty/freetype/src/type1/type1.c + +LOCAL_SRC_FILES := $(addprefix ../, $(LOCAL_SRC_FILES)) + +include $(BUILD_STATIC_LIBRARY) diff --git a/platform/android/viewer/jni/mupdf.c b/platform/android/viewer/jni/mupdf.c new file mode 100644 index 00000000..bc23b9ca --- /dev/null +++ b/platform/android/viewer/jni/mupdf.c @@ -0,0 +1,2885 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef NDK_PROFILER +#include "prof.h" +#endif + +#include "mupdf/fitz.h" +#include "mupdf/pdf.h" + +#define JNI_FN(A) Java_com_artifex_mupdfdemo_ ## A +#define PACKAGENAME "com/artifex/mupdfdemo" + +#define LOG_TAG "libmupdf" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGT(...) __android_log_print(ANDROID_LOG_INFO,"alert",__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + +/* Enable to log rendering times (render each frame 100 times and time) */ +#undef TIME_DISPLAY_LIST + +#define MAX_SEARCH_HITS (500) +#define NUM_CACHE (3) +#define STRIKE_HEIGHT (0.375f) +#define UNDERLINE_HEIGHT (0.075f) +#define LINE_THICKNESS (0.07f) +#define INK_THICKNESS (4.0f) +#define SMALL_FLOAT (0.00001) +#define PROOF_RESOLUTION (300) + +enum +{ + NONE, + TEXT, + LISTBOX, + COMBOBOX, + SIGNATURE +}; + +typedef struct rect_node_s rect_node; + +struct rect_node_s +{ + fz_rect rect; + rect_node *next; +}; + +typedef struct +{ + int number; + int width; + int height; + fz_rect media_box; + fz_page *page; + rect_node *changed_rects; + rect_node *hq_changed_rects; + fz_display_list *page_list; + fz_display_list *annot_list; +} page_cache; + +typedef struct globals_s globals; + +struct globals_s +{ + fz_colorspace *colorspace; + fz_document *doc; + int resolution; + fz_context *ctx; + fz_rect *hit_bbox; + int current; + char *current_path; + + page_cache pages[NUM_CACHE]; + + int alerts_initialised; + // fin_lock and fin_lock2 are used during shutdown. The two waiting tasks + // show_alert and waitForAlertInternal respectively take these locks while + // waiting. During shutdown, the conditions are signaled and then the fin_locks + // are taken momentarily to ensure the blocked threads leave the controlled + // area of code before the mutexes and condition variables are destroyed. + pthread_mutex_t fin_lock; + pthread_mutex_t fin_lock2; + // alert_lock is the main lock guarding the variables directly below. + pthread_mutex_t alert_lock; + // Flag indicating if the alert system is active. When not active, both + // show_alert and waitForAlertInternal return immediately. + int alerts_active; + // Pointer to the alert struct passed in by show_alert, and valid while + // show_alert is blocked. + pdf_alert_event *current_alert; + // Flag and condition varibles to signal a request is present and a reply + // is present, respectively. The condition variables alone are not sufficient + // because of the pthreads permit spurious signals. + int alert_request; + int alert_reply; + pthread_cond_t alert_request_cond; + pthread_cond_t alert_reply_cond; + + // For the buffer reading mode, we need to implement stream reading, which + // needs access to the following. + JNIEnv *env; + jclass thiz; +}; + +static jfieldID global_fid; +static jfieldID buffer_fid; + +// Do our best to avoid casting warnings. +#define CAST(type, var) (type)pointer_cast(var) + +static inline void *pointer_cast(jlong l) +{ + return (void *)(intptr_t)l; +} + +static inline jlong jlong_cast(void *p) +{ + return (jlong)(intptr_t)p; +} + +static void drop_changed_rects(fz_context *ctx, rect_node **nodePtr) +{ + rect_node *node = *nodePtr; + while (node) + { + rect_node *tnode = node; + node = node->next; + fz_free(ctx, tnode); + } + + *nodePtr = NULL; +} + +static void drop_page_cache(globals *glo, page_cache *pc) +{ + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + + LOGI("Drop page %d", pc->number); + fz_drop_display_list(ctx, pc->page_list); + pc->page_list = NULL; + fz_drop_display_list(ctx, pc->annot_list); + pc->annot_list = NULL; + fz_drop_page(ctx, pc->page); + pc->page = NULL; + drop_changed_rects(ctx, &pc->changed_rects); + drop_changed_rects(ctx, &pc->hq_changed_rects); +} + +static void dump_annotation_display_lists(globals *glo) +{ + fz_context *ctx = glo->ctx; + int i; + + for (i = 0; i < NUM_CACHE; i++) { + fz_drop_display_list(ctx, glo->pages[i].annot_list); + glo->pages[i].annot_list = NULL; + } +} + +static void show_alert(globals *glo, pdf_alert_event *alert) +{ + pthread_mutex_lock(&glo->fin_lock2); + pthread_mutex_lock(&glo->alert_lock); + + LOGT("Enter show_alert: %s", alert->title); + alert->button_pressed = 0; + + if (glo->alerts_active) + { + glo->current_alert = alert; + glo->alert_request = 1; + pthread_cond_signal(&glo->alert_request_cond); + + while (glo->alerts_active && !glo->alert_reply) + pthread_cond_wait(&glo->alert_reply_cond, &glo->alert_lock); + glo->alert_reply = 0; + glo->current_alert = NULL; + } + + LOGT("Exit show_alert"); + + pthread_mutex_unlock(&glo->alert_lock); + pthread_mutex_unlock(&glo->fin_lock2); +} + +static void event_cb(fz_context *ctx, pdf_document *doc, pdf_doc_event *event, void *data) +{ + globals *glo = (globals *)data; + + switch (event->type) + { + case PDF_DOCUMENT_EVENT_ALERT: + show_alert(glo, pdf_access_alert_event(ctx, event)); + break; + } +} + +static void alerts_init(globals *glo) +{ + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (!idoc || glo->alerts_initialised) + return; + + if (idoc) + pdf_enable_js(ctx, idoc); + + glo->alerts_active = 0; + glo->alert_request = 0; + glo->alert_reply = 0; + pthread_mutex_init(&glo->fin_lock, NULL); + pthread_mutex_init(&glo->fin_lock2, NULL); + pthread_mutex_init(&glo->alert_lock, NULL); + pthread_cond_init(&glo->alert_request_cond, NULL); + pthread_cond_init(&glo->alert_reply_cond, NULL); + + pdf_set_doc_event_callback(ctx, idoc, event_cb, glo); + LOGT("alert_init"); + glo->alerts_initialised = 1; +} + +static void alerts_fin(globals *glo) +{ + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + if (!glo->alerts_initialised) + return; + + LOGT("Enter alerts_fin"); + if (idoc) + pdf_set_doc_event_callback(ctx, idoc, NULL, NULL); + + // Set alerts_active false and wake up show_alert and waitForAlertInternal, + pthread_mutex_lock(&glo->alert_lock); + glo->current_alert = NULL; + glo->alerts_active = 0; + pthread_cond_signal(&glo->alert_request_cond); + pthread_cond_signal(&glo->alert_reply_cond); + pthread_mutex_unlock(&glo->alert_lock); + + // Wait for the fin_locks. + pthread_mutex_lock(&glo->fin_lock); + pthread_mutex_unlock(&glo->fin_lock); + pthread_mutex_lock(&glo->fin_lock2); + pthread_mutex_unlock(&glo->fin_lock2); + + pthread_cond_destroy(&glo->alert_reply_cond); + pthread_cond_destroy(&glo->alert_request_cond); + pthread_mutex_destroy(&glo->alert_lock); + pthread_mutex_destroy(&glo->fin_lock2); + pthread_mutex_destroy(&glo->fin_lock); + LOGT("Exit alerts_fin"); + glo->alerts_initialised = 0; +} + +// Should only be called from the single background AsyncTask thread +static globals *get_globals(JNIEnv *env, jobject thiz) +{ + globals *glo = CAST(globals *, (*env)->GetLongField(env, thiz, global_fid)); + if (glo != NULL) + { + glo->env = env; + glo->thiz = thiz; + } + return glo; +} + +// May be called from any thread, provided the values of glo->env and glo->thiz +// are not used. +static globals *get_globals_any_thread(JNIEnv *env, jobject thiz) +{ + return (globals *)(intptr_t)((*env)->GetLongField(env, thiz, global_fid)); +} + +JNIEXPORT jlong JNICALL +JNI_FN(MuPDFCore_openFile)(JNIEnv * env, jobject thiz, jstring jfilename) +{ + const char *filename; + globals *glo; + fz_context *ctx; + jclass clazz; + +#ifdef NDK_PROFILER + monstartup("libmupdf_java.so"); +#endif + + clazz = (*env)->GetObjectClass(env, thiz); + global_fid = (*env)->GetFieldID(env, clazz, "globals", "J"); + + glo = calloc(1, sizeof(*glo)); + if (glo == NULL) + return 0; + glo->resolution = 160; + glo->alerts_initialised = 0; + +#ifdef DEBUG + /* Try and send stdout/stderr to file in debug builds. This + * path may not work on all platforms, but it works on the + * LG G3, and it's no worse than not redirecting it anywhere + * on anything else. */ + freopen("/storage/emulated/0/Download/stdout.txt", "a", stdout); + freopen("/storage/emulated/0/Download/stderr.txt", "a", stderr); +#endif + + filename = (*env)->GetStringUTFChars(env, jfilename, NULL); + if (filename == NULL) + { + LOGE("Failed to get filename"); + free(glo); + return 0; + } + + /* 128 MB store for low memory devices. Tweak as necessary. */ + glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20); + if (!ctx) + { + LOGE("Failed to initialise context"); + (*env)->ReleaseStringUTFChars(env, jfilename, filename); + free(glo); + return 0; + } + + fz_register_document_handlers(ctx); + + glo->doc = NULL; + fz_try(ctx) + { + glo->colorspace = fz_device_rgb(ctx); + + LOGI("Opening document..."); + fz_try(ctx) + { + glo->current_path = fz_strdup(ctx, (char *)filename); + glo->doc = fz_open_document(ctx, (char *)filename); + alerts_init(glo); + } + fz_catch(ctx) + { + fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open document: '%s'", filename); + } + LOGI("Done!"); + } + fz_catch(ctx) + { + LOGE("Failed: %s", ctx->error->message); + fz_drop_document(ctx, glo->doc); + glo->doc = NULL; + fz_drop_context(ctx); + glo->ctx = NULL; + free(glo); + glo = NULL; + } + + (*env)->ReleaseStringUTFChars(env, jfilename, filename); + + return jlong_cast(glo); +} + +typedef struct buffer_state_s +{ + globals *globals; + char buffer[4096]; +} +buffer_state; + +static int bufferStreamNext(fz_context *ctx, fz_stream *stream, int max) +{ + buffer_state *bs = (buffer_state *)stream->state; + globals *glo = bs->globals; + JNIEnv *env = glo->env; + jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid)); + int arrayLength = (*env)->GetArrayLength(env, array); + int len = sizeof(bs->buffer); + + if (stream->pos > arrayLength) + stream->pos = arrayLength; + if (stream->pos < 0) + stream->pos = 0; + if (len + stream->pos > arrayLength) + len = arrayLength - stream->pos; + + (*env)->GetByteArrayRegion(env, array, stream->pos, len, bs->buffer); + (*env)->DeleteLocalRef(env, array); + + stream->rp = bs->buffer; + stream->wp = stream->rp + len; + stream->pos += len; + if (len == 0) + return EOF; + return *stream->rp++; +} + +static void bufferStreamClose(fz_context *ctx, void *state) +{ + fz_free(ctx, state); +} + +static void bufferStreamSeek(fz_context *ctx, fz_stream *stream, int offset, int whence) +{ + buffer_state *bs = (buffer_state *)stream->state; + globals *glo = bs->globals; + JNIEnv *env = glo->env; + jbyteArray array = (jbyteArray)(void *)((*env)->GetObjectField(env, glo->thiz, buffer_fid)); + int arrayLength = (*env)->GetArrayLength(env, array); + + (*env)->DeleteLocalRef(env, array); + + if (whence == 0) /* SEEK_SET */ + stream->pos = offset; + else if (whence == 1) /* SEEK_CUR */ + stream->pos += offset; + else if (whence == 2) /* SEEK_END */ + stream->pos = arrayLength + offset; + + if (stream->pos > arrayLength) + stream->pos = arrayLength; + if (stream->pos < 0) + stream->pos = 0; + + stream->wp = stream->rp; +} + +JNIEXPORT jlong JNICALL +JNI_FN(MuPDFCore_openBuffer)(JNIEnv * env, jobject thiz, jstring jmagic) +{ + globals *glo; + fz_context *ctx; + jclass clazz; + fz_stream *stream = NULL; + buffer_state *bs; + const char *magic; + +#ifdef NDK_PROFILER + monstartup("libmupdf_java.so"); +#endif + + clazz = (*env)->GetObjectClass(env, thiz); + global_fid = (*env)->GetFieldID(env, clazz, "globals", "J"); + + glo = calloc(1, sizeof(*glo)); + if (glo == NULL) + return 0; + glo->resolution = 160; + glo->alerts_initialised = 0; + glo->env = env; + glo->thiz = thiz; + buffer_fid = (*env)->GetFieldID(env, clazz, "fileBuffer", "[B"); + + magic = (*env)->GetStringUTFChars(env, jmagic, NULL); + if (magic == NULL) + { + LOGE("Failed to get magic"); + free(glo); + return 0; + } + + /* 128 MB store for low memory devices. Tweak as necessary. */ + glo->ctx = ctx = fz_new_context(NULL, NULL, 128 << 20); + if (!ctx) + { + LOGE("Failed to initialise context"); + (*env)->ReleaseStringUTFChars(env, jmagic, magic); + free(glo); + return 0; + } + + fz_register_document_handlers(ctx); + fz_var(stream); + + glo->doc = NULL; + fz_try(ctx) + { + bs = fz_malloc_struct(ctx, buffer_state); + bs->globals = glo; + stream = fz_new_stream(ctx, bs, bufferStreamNext, bufferStreamClose); + stream->seek = bufferStreamSeek; + + glo->colorspace = fz_device_rgb(ctx); + + LOGI("Opening document..."); + fz_try(ctx) + { + glo->current_path = NULL; + glo->doc = fz_open_document_with_stream(ctx, magic, stream); + alerts_init(glo); + } + fz_catch(ctx) + { + fz_throw(ctx, FZ_ERROR_GENERIC, "Cannot open memory document"); + } + LOGI("Done!"); + } + fz_always(ctx) + { + fz_drop_stream(ctx, stream); + } + fz_catch(ctx) + { + LOGE("Failed: %s", ctx->error->message); + fz_drop_document(ctx, glo->doc); + glo->doc = NULL; + fz_drop_context(ctx); + glo->ctx = NULL; + free(glo); + glo = NULL; + } + + (*env)->ReleaseStringUTFChars(env, jmagic, magic); + + return jlong_cast(glo); +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_countPagesInternal)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + int count = 0; + + fz_try(ctx) + { + count = fz_count_pages(ctx, glo->doc); + } + fz_catch(ctx) + { + LOGE("exception while counting pages: %s", ctx->error->message); + } + return count; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_fileFormatInternal)(JNIEnv * env, jobject thiz) +{ + char info[64]; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + fz_lookup_metadata(ctx, glo->doc, FZ_META_FORMAT, info, sizeof(info)); + + return (*env)->NewStringUTF(env, info); +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_isUnencryptedPDFInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals_any_thread(env, thiz); + if (glo == NULL) + return JNI_FALSE; + + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + if (idoc == NULL) + return JNI_FALSE; // Not a PDF + + int cryptVer = pdf_crypt_version(ctx, idoc); + return (cryptVer == 0) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_gotoPageInternal)(JNIEnv *env, jobject thiz, int page) +{ + int i; + int furthest; + int furthest_dist = -1; + float zoom; + fz_matrix ctm; + fz_irect bbox; + page_cache *pc; + globals *glo = get_globals(env, thiz); + if (glo == NULL) + return; + fz_context *ctx = glo->ctx; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + { + /* The page is already cached */ + glo->current = i; + return; + } + + if (glo->pages[i].page == NULL) + { + /* cache record unused, and so a good one to use */ + furthest = i; + furthest_dist = INT_MAX; + } + else + { + int dist = abs(glo->pages[i].number - page); + + /* Further away - less likely to be needed again */ + if (dist > furthest_dist) + { + furthest_dist = dist; + furthest = i; + } + } + } + + glo->current = furthest; + pc = &glo->pages[glo->current]; + + drop_page_cache(glo, pc); + + /* In the event of an error, ensure we give a non-empty page */ + pc->width = 100; + pc->height = 100; + + pc->number = page; + LOGI("Goto page %d...", page); + fz_try(ctx) + { + fz_rect rect; + LOGI("Load page %d", pc->number); + pc->page = fz_load_page(ctx, glo->doc, pc->number); + zoom = glo->resolution / 72; + fz_bound_page(ctx, pc->page, &pc->media_box); + fz_scale(&ctm, zoom, zoom); + rect = pc->media_box; + fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); + pc->width = bbox.x1-bbox.x0; + pc->height = bbox.y1-bbox.y0; + } + fz_catch(ctx) + { + LOGE("cannot make displaylist from page %d", pc->number); + } +} + +JNIEXPORT float JNICALL +JNI_FN(MuPDFCore_getPageWidth)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + LOGI("PageWidth=%d", glo->pages[glo->current].width); + return glo->pages[glo->current].width; +} + +JNIEXPORT float JNICALL +JNI_FN(MuPDFCore_getPageHeight)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + LOGI("PageHeight=%d", glo->pages[glo->current].height); + return glo->pages[glo->current].height; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_javascriptSupported)(JNIEnv *env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + if (idoc) + return pdf_js_supported(ctx, idoc); + return 0; +} + +static void update_changed_rects(globals *glo, page_cache *pc, pdf_document *idoc) +{ + fz_context *ctx = glo->ctx; + fz_annot *annot; + + pdf_update_page(ctx, idoc, (pdf_page *)pc->page); + while ((annot = (fz_annot *)pdf_poll_changed_annot(ctx, idoc, (pdf_page *)pc->page)) != NULL) + { + /* FIXME: We bound the annot twice here */ + rect_node *node = fz_malloc_struct(glo->ctx, rect_node); + fz_bound_annot(ctx, annot, &node->rect); + node->next = pc->changed_rects; + pc->changed_rects = node; + + node = fz_malloc_struct(glo->ctx, rect_node); + fz_bound_annot(ctx, annot, &node->rect); + node->next = pc->hq_changed_rects; + pc->hq_changed_rects = node; + } +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_drawPage)(JNIEnv *env, jobject thiz, jobject bitmap, + int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr) +{ + AndroidBitmapInfo info; + void *pixels; + int ret; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + fz_irect bbox; + fz_rect rect; + fz_pixmap *pix = NULL; + float xscale, yscale; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + int hq = (patchW < pageW || patchH < pageH); + fz_matrix scale; + fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr; + + if (pc->page == NULL) + return 0; + + fz_var(pix); + fz_var(dev); + + LOGI("In native method\n"); + if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { + LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); + return 0; + } + + LOGI("Checking format\n"); + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return 0; + } + + LOGI("locking pixels\n"); + if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { + LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); + return 0; + } + + /* Call mupdf to render display list to screen */ + LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]", + pc->number, pageW, pageH, patchX, patchY, patchW, patchH); + + fz_try(ctx) + { + fz_irect pixbbox; + pdf_document *idoc = pdf_specifics(ctx, doc); + + if (idoc) + { + /* Update the changed-rects for both hq patch and main bitmap */ + update_changed_rects(glo, pc, idoc); + + /* Then drop the changed-rects for the bitmap we're about to + render because we are rendering the entire area */ + drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects); + } + + if (pc->page_list == NULL) + { + /* Render to list */ + pc->page_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->page_list); + fz_run_page_contents(ctx, pc->page, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->page_list); + pc->page_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + if (pc->annot_list == NULL) + { + fz_annot *annot; + pc->annot_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->annot_list); + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + fz_run_annot(ctx, annot, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->annot_list); + pc->annot_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + bbox.x0 = patchX; + bbox.y0 = patchY; + bbox.x1 = patchX + patchW; + bbox.y1 = patchY + patchH; + pixbbox = bbox; + pixbbox.x1 = pixbbox.x0 + info.width; + /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to + * match the pixels data */ + pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels); + if (pc->page_list == NULL && pc->annot_list == NULL) + { + fz_clear_pixmap_with_value(ctx, pix, 0xd0); + break; + } + fz_clear_pixmap_with_value(ctx, pix, 0xff); + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + rect = pc->media_box; + fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); + /* Now, adjust ctm so that it would give the correct page width + * heights. */ + xscale = (float)pageW/(float)(bbox.x1-bbox.x0); + yscale = (float)pageH/(float)(bbox.y1-bbox.y0); + fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale)); + rect = pc->media_box; + fz_transform_rect(&rect, &ctm); + dev = fz_new_draw_device(ctx, pix); +#ifdef TIME_DISPLAY_LIST + { + clock_t time; + int i; + + LOGI("Executing display list"); + time = clock(); + for (i=0; i<100;i++) { +#endif + if (pc->page_list) + fz_run_display_list(ctx, pc->page_list, dev, &ctm, &rect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + + if (pc->annot_list) + fz_run_display_list(ctx, pc->annot_list, dev, &ctm, &rect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + +#ifdef TIME_DISPLAY_LIST + } + time = clock() - time; + LOGI("100 renders in %d (%d per sec)", time, CLOCKS_PER_SEC); + } +#endif + fz_drop_device(ctx, dev); + dev = NULL; + fz_drop_pixmap(ctx, pix); + LOGI("Rendered"); + } + fz_always(ctx) + { + fz_drop_device(ctx, dev); + dev = NULL; + } + fz_catch(ctx) + { + LOGE("Render failed"); + } + + AndroidBitmap_unlockPixels(env, bitmap); + + return 1; +} + +static char *widget_type_string(int t) +{ + switch(t) + { + case PDF_WIDGET_TYPE_PUSHBUTTON: return "pushbutton"; + case PDF_WIDGET_TYPE_CHECKBOX: return "checkbox"; + case PDF_WIDGET_TYPE_RADIOBUTTON: return "radiobutton"; + case PDF_WIDGET_TYPE_TEXT: return "text"; + case PDF_WIDGET_TYPE_LISTBOX: return "listbox"; + case PDF_WIDGET_TYPE_COMBOBOX: return "combobox"; + case PDF_WIDGET_TYPE_SIGNATURE: return "signature"; + default: return "non-widget"; + } +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_updatePageInternal)(JNIEnv *env, jobject thiz, jobject bitmap, int page, + int pageW, int pageH, int patchX, int patchY, int patchW, int patchH, jlong cookiePtr) +{ + AndroidBitmapInfo info; + void *pixels; + int ret; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + fz_irect bbox; + fz_rect rect; + fz_pixmap *pix = NULL; + float xscale, yscale; + pdf_document *idoc; + page_cache *pc = NULL; + int hq = (patchW < pageW || patchH < pageH); + int i; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + rect_node *crect; + fz_matrix scale; + fz_cookie *cookie = (fz_cookie *)(intptr_t)cookiePtr; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + { + pc = &glo->pages[i]; + break; + } + } + + if (pc == NULL) + { + /* Without a cached page object we cannot perform a partial update so + render the entire bitmap instead */ + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, page); + return JNI_FN(MuPDFCore_drawPage)(env, thiz, bitmap, pageW, pageH, patchX, patchY, patchW, patchH, (jlong)(intptr_t)cookie); + } + + idoc = pdf_specifics(ctx, doc); + + fz_var(pix); + fz_var(dev); + + LOGI("In native method\n"); + if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { + LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); + return 0; + } + + LOGI("Checking format\n"); + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return 0; + } + + LOGI("locking pixels\n"); + if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { + LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); + return 0; + } + + /* Call mupdf to render display list to screen */ + LOGI("Rendering page(%d)=%dx%d patch=[%d,%d,%d,%d]", + pc->number, pageW, pageH, patchX, patchY, patchW, patchH); + + fz_try(ctx) + { + fz_annot *annot; + fz_irect pixbbox; + + if (idoc) + { + /* Update the changed-rects for both hq patch and main bitmap */ + update_changed_rects(glo, pc, idoc); + } + + if (pc->page_list == NULL) + { + /* Render to list */ + pc->page_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->page_list); + fz_run_page_contents(ctx, pc->page, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->page_list); + pc->page_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + + if (pc->annot_list == NULL) { + pc->annot_list = fz_new_display_list(ctx); + dev = fz_new_list_device(ctx, pc->annot_list); + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + fz_run_annot(ctx, annot, dev, &fz_identity, cookie); + fz_drop_device(ctx, dev); + dev = NULL; + if (cookie != NULL && cookie->abort) + { + fz_drop_display_list(ctx, pc->annot_list); + pc->annot_list = NULL; + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + } + } + + bbox.x0 = patchX; + bbox.y0 = patchY; + bbox.x1 = patchX + patchW; + bbox.y1 = patchY + patchH; + pixbbox = bbox; + pixbbox.x1 = pixbbox.x0 + info.width; + /* pixmaps cannot handle right-edge padding, so the bbox must be expanded to + * match the pixels data */ + pix = fz_new_pixmap_with_bbox_and_data(ctx, glo->colorspace, &pixbbox, pixels); + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + rect = pc->media_box; + fz_round_rect(&bbox, fz_transform_rect(&rect, &ctm)); + /* Now, adjust ctm so that it would give the correct page width + * heights. */ + xscale = (float)pageW/(float)(bbox.x1-bbox.x0); + yscale = (float)pageH/(float)(bbox.y1-bbox.y0); + fz_concat(&ctm, &ctm, fz_scale(&scale, xscale, yscale)); + rect = pc->media_box; + fz_transform_rect(&rect, &ctm); + + LOGI("Start partial update"); + for (crect = hq ? pc->hq_changed_rects : pc->changed_rects; crect; crect = crect->next) + { + fz_irect abox; + fz_rect arect = crect->rect; + fz_intersect_rect(fz_transform_rect(&arect, &ctm), &rect); + fz_round_rect(&abox, &arect); + + LOGI("Update rectangle (%d, %d, %d, %d)", abox.x0, abox.y0, abox.x1, abox.y1); + if (!fz_is_empty_irect(&abox)) + { + LOGI("And it isn't empty"); + fz_clear_pixmap_rect_with_value(ctx, pix, 0xff, &abox); + dev = fz_new_draw_device_with_bbox(ctx, pix, &abox); + if (pc->page_list) + fz_run_display_list(ctx, pc->page_list, dev, &ctm, &arect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + + if (pc->annot_list) + fz_run_display_list(ctx, pc->annot_list, dev, &ctm, &arect, cookie); + if (cookie != NULL && cookie->abort) + fz_throw(ctx, FZ_ERROR_GENERIC, "Render aborted"); + + fz_drop_device(ctx, dev); + dev = NULL; + } + } + LOGI("End partial update"); + + /* Drop the changed rects we've just rendered */ + drop_changed_rects(ctx, hq ? &pc->hq_changed_rects : &pc->changed_rects); + + LOGI("Rendered"); + } + fz_always(ctx) + { + fz_drop_device(ctx, dev); + dev = NULL; + } + fz_catch(ctx) + { + LOGE("Render failed"); + } + + fz_drop_pixmap(ctx, pix); + AndroidBitmap_unlockPixels(env, bitmap); + + return 1; +} + +static int +charat(fz_context *ctx, fz_stext_page *page, int idx) +{ + fz_char_and_box cab; + return fz_stext_char_at(ctx, &cab, page, idx)->c; +} + +static fz_rect +bboxcharat(fz_context *ctx, fz_stext_page *page, int idx) +{ + fz_char_and_box cab; + return fz_stext_char_at(ctx, &cab, page, idx)->bbox; +} + +static int +textlen(fz_stext_page *page) +{ + int len = 0; + int block_num; + + for (block_num = 0; block_num < page->len; block_num++) + { + fz_stext_block *block; + fz_stext_line *line; + + if (page->blocks[block_num].type != FZ_PAGE_BLOCK_TEXT) + continue; + block = page->blocks[block_num].u.text; + for (line = block->lines; line < block->lines + block->len; line++) + { + fz_stext_span *span; + + for (span = line->first_span; span; span = span->next) + { + len += span->len; + } + len++; /* pseudo-newline */ + } + } + return len; +} + +static int +countOutlineItems(fz_outline *outline) +{ + int count = 0; + + while (outline) + { + if (outline->dest.kind == FZ_LINK_GOTO + && outline->dest.ld.gotor.page >= 0 + && outline->title) + count++; + + count += countOutlineItems(outline->down); + outline = outline->next; + } + + return count; +} + +static int +fillInOutlineItems(JNIEnv * env, jclass olClass, jmethodID ctor, jobjectArray arr, int pos, fz_outline *outline, int level) +{ + while (outline) + { + if (outline->dest.kind == FZ_LINK_GOTO) + { + int page = outline->dest.ld.gotor.page; + if (page >= 0 && outline->title) + { + jobject ol; + jstring title = (*env)->NewStringUTF(env, outline->title); + if (title == NULL) return -1; + ol = (*env)->NewObject(env, olClass, ctor, level, title, page); + if (ol == NULL) return -1; + (*env)->SetObjectArrayElement(env, arr, pos, ol); + (*env)->DeleteLocalRef(env, ol); + (*env)->DeleteLocalRef(env, title); + pos++; + } + } + pos = fillInOutlineItems(env, olClass, ctor, arr, pos, outline->down, level+1); + if (pos < 0) return -1; + outline = outline->next; + } + + return pos; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_needsPasswordInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + return fz_needs_password(ctx, glo->doc) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_authenticatePasswordInternal)(JNIEnv *env, jobject thiz, jstring password) +{ + const char *pw; + int result; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + pw = (*env)->GetStringUTFChars(env, password, NULL); + if (pw == NULL) + return JNI_FALSE; + + result = fz_authenticate_password(ctx, glo->doc, (char *)pw); + (*env)->ReleaseStringUTFChars(env, password, pw); + return result; +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_hasOutlineInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_outline *outline = fz_load_outline(ctx, glo->doc); + + fz_drop_outline(glo->ctx, outline); + return (outline == NULL) ? JNI_FALSE : JNI_TRUE; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getOutlineInternal)(JNIEnv * env, jobject thiz) +{ + jclass olClass; + jmethodID ctor; + jobjectArray arr; + jobject ol; + fz_outline *outline; + int nItems; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + jobjectArray ret; + + olClass = (*env)->FindClass(env, PACKAGENAME "/OutlineItem"); + if (olClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, olClass, "", "(ILjava/lang/String;I)V"); + if (ctor == NULL) return NULL; + + outline = fz_load_outline(ctx, glo->doc); + nItems = countOutlineItems(outline); + + arr = (*env)->NewObjectArray(env, + nItems, + olClass, + NULL); + if (arr == NULL) return NULL; + + ret = fillInOutlineItems(env, olClass, ctor, arr, 0, outline, 0) > 0 + ? arr + :NULL; + fz_drop_outline(glo->ctx, outline); + return ret; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_searchPage)(JNIEnv * env, jobject thiz, jstring jtext) +{ + jclass rectClass; + jmethodID ctor; + jobjectArray arr; + jobject rect; + fz_stext_sheet *sheet = NULL; + fz_stext_page *text = NULL; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + int pos; + int len; + int i, n; + int hit_count = 0; + const char *str; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + + rectClass = (*env)->FindClass(env, "android/graphics/RectF"); + if (rectClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, rectClass, "", "(FFFF)V"); + if (ctor == NULL) return NULL; + str = (*env)->GetStringUTFChars(env, jtext, NULL); + if (str == NULL) return NULL; + + fz_var(sheet); + fz_var(text); + fz_var(dev); + + fz_try(ctx) + { + if (glo->hit_bbox == NULL) + glo->hit_bbox = fz_malloc_array(ctx, MAX_SEARCH_HITS, sizeof(*glo->hit_bbox)); + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + sheet = fz_new_stext_sheet(ctx); + text = fz_new_stext_page(ctx); + dev = fz_new_stext_device(ctx, sheet, text); + fz_run_page(ctx, pc->page, dev, &ctm, NULL); + fz_drop_device(ctx, dev); + dev = NULL; + + hit_count = fz_search_stext_page(ctx, text, str, glo->hit_bbox, MAX_SEARCH_HITS); + } + fz_always(ctx) + { + fz_drop_stext_page(ctx, text); + fz_drop_stext_sheet(ctx, sheet); + fz_drop_device(ctx, dev); + } + fz_catch(ctx) + { + jclass cls; + (*env)->ReleaseStringUTFChars(env, jtext, str); + cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); + (*env)->DeleteLocalRef(env, cls); + + return NULL; + } + + (*env)->ReleaseStringUTFChars(env, jtext, str); + + arr = (*env)->NewObjectArray(env, + hit_count, + rectClass, + NULL); + if (arr == NULL) return NULL; + + for (i = 0; i < hit_count; i++) { + rect = (*env)->NewObject(env, rectClass, ctor, + (float) (glo->hit_bbox[i].x0), + (float) (glo->hit_bbox[i].y0), + (float) (glo->hit_bbox[i].x1), + (float) (glo->hit_bbox[i].y1)); + if (rect == NULL) + return NULL; + (*env)->SetObjectArrayElement(env, arr, i, rect); + (*env)->DeleteLocalRef(env, rect); + } + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_text)(JNIEnv * env, jobject thiz) +{ + jclass textCharClass; + jclass textSpanClass; + jclass textLineClass; + jclass textBlockClass; + jmethodID ctor; + jobjectArray barr = NULL; + fz_stext_sheet *sheet = NULL; + fz_stext_page *text = NULL; + fz_device *dev = NULL; + float zoom; + fz_matrix ctm; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + + textCharClass = (*env)->FindClass(env, PACKAGENAME "/TextChar"); + if (textCharClass == NULL) return NULL; + textSpanClass = (*env)->FindClass(env, "[L" PACKAGENAME "/TextChar;"); + if (textSpanClass == NULL) return NULL; + textLineClass = (*env)->FindClass(env, "[[L" PACKAGENAME "/TextChar;"); + if (textLineClass == NULL) return NULL; + textBlockClass = (*env)->FindClass(env, "[[[L" PACKAGENAME "/TextChar;"); + if (textBlockClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, textCharClass, "", "(FFFFC)V"); + if (ctor == NULL) return NULL; + + fz_var(sheet); + fz_var(text); + fz_var(dev); + + fz_try(ctx) + { + int b, l, s, c; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + sheet = fz_new_stext_sheet(ctx); + text = fz_new_stext_page(ctx); + dev = fz_new_stext_device(ctx, sheet, text); + fz_run_page(ctx, pc->page, dev, &ctm, NULL); + fz_drop_device(ctx, dev); + dev = NULL; + + barr = (*env)->NewObjectArray(env, text->len, textBlockClass, NULL); + if (barr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (b = 0; b < text->len; b++) + { + fz_stext_block *block; + jobjectArray *larr; + + if (text->blocks[b].type != FZ_PAGE_BLOCK_TEXT) + continue; + block = text->blocks[b].u.text; + larr = (*env)->NewObjectArray(env, block->len, textLineClass, NULL); + if (larr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (l = 0; l < block->len; l++) + { + fz_stext_line *line = &block->lines[l]; + jobjectArray *sarr; + fz_stext_span *span; + int len = 0; + + for (span = line->first_span; span; span = span->next) + len++; + + sarr = (*env)->NewObjectArray(env, len, textSpanClass, NULL); + if (sarr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (s=0, span = line->first_span; span; s++, span = span->next) + { + jobjectArray *carr = (*env)->NewObjectArray(env, span->len, textCharClass, NULL); + if (carr == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectArray failed"); + + for (c = 0; c < span->len; c++) + { + fz_stext_char *ch = &span->text[c]; + fz_rect bbox; + fz_stext_char_bbox(ctx, &bbox, span, c); + jobject cobj = (*env)->NewObject(env, textCharClass, ctor, bbox.x0, bbox.y0, bbox.x1, bbox.y1, ch->c); + if (cobj == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "NewObjectfailed"); + + (*env)->SetObjectArrayElement(env, carr, c, cobj); + (*env)->DeleteLocalRef(env, cobj); + } + + (*env)->SetObjectArrayElement(env, sarr, s, carr); + (*env)->DeleteLocalRef(env, carr); + } + + (*env)->SetObjectArrayElement(env, larr, l, sarr); + (*env)->DeleteLocalRef(env, sarr); + } + + (*env)->SetObjectArrayElement(env, barr, b, larr); + (*env)->DeleteLocalRef(env, larr); + } + } + fz_always(ctx) + { + fz_drop_stext_page(ctx, text); + fz_drop_stext_sheet(ctx, sheet); + fz_drop_device(ctx, dev); + } + fz_catch(ctx) + { + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_text"); + (*env)->DeleteLocalRef(env, cls); + + return NULL; + } + + return barr; +} + +JNIEXPORT jbyteArray JNICALL +JNI_FN(MuPDFCore_textAsHtml)(JNIEnv * env, jobject thiz) +{ + fz_stext_sheet *sheet = NULL; + fz_stext_page *text = NULL; + fz_device *dev = NULL; + fz_matrix ctm; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + page_cache *pc = &glo->pages[glo->current]; + jbyteArray bArray = NULL; + fz_buffer *buf = NULL; + fz_output *out = NULL; + + fz_var(sheet); + fz_var(text); + fz_var(dev); + fz_var(buf); + fz_var(out); + + fz_try(ctx) + { + int b, l, s, c; + + ctm = fz_identity; + sheet = fz_new_stext_sheet(ctx); + text = fz_new_stext_page(ctx); + dev = fz_new_stext_device(ctx, sheet, text); + fz_run_page(ctx, pc->page, dev, &ctm, NULL); + fz_drop_device(ctx, dev); + dev = NULL; + + fz_analyze_text(ctx, sheet, text); + + buf = fz_new_buffer(ctx, 256); + out = fz_new_output_with_buffer(ctx, buf); + fz_printf(ctx, out, "\n"); + fz_printf(ctx, out, "\n"); + fz_printf(ctx, out, "

"); + fz_print_stext_page_html(ctx, out, text); + fz_printf(ctx, out, "
\n"); + fz_printf(ctx, out, "\n\n"); + fz_drop_output(ctx, out); + out = NULL; + + bArray = (*env)->NewByteArray(env, buf->len); + if (bArray == NULL) + fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to make byteArray"); + (*env)->SetByteArrayRegion(env, bArray, 0, buf->len, buf->data); + + } + fz_always(ctx) + { + fz_drop_stext_page(ctx, text); + fz_drop_stext_sheet(ctx, sheet); + fz_drop_device(ctx, dev); + fz_drop_output(ctx, out); + fz_drop_buffer(ctx, buf); + } + fz_catch(ctx) + { + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_textAsHtml"); + (*env)->DeleteLocalRef(env, cls); + + return NULL; + } + + return bArray; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_addMarkupAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray points, fz_annot_type type) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + pdf_document *idoc = pdf_specifics(ctx, doc); + page_cache *pc = &glo->pages[glo->current]; + jclass pt_cls; + jfieldID x_fid, y_fid; + int i, n; + fz_point *pts = NULL; + float color[3]; + float alpha; + float line_height; + float line_thickness; + + if (idoc == NULL) + return; + + switch (type) + { + case FZ_ANNOT_HIGHLIGHT: + color[0] = 1.0; + color[1] = 1.0; + color[2] = 0.0; + alpha = 0.5; + line_thickness = 1.0; + line_height = 0.5; + break; + case FZ_ANNOT_UNDERLINE: + color[0] = 0.0; + color[1] = 0.0; + color[2] = 1.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = UNDERLINE_HEIGHT; + break; + case FZ_ANNOT_STRIKEOUT: + color[0] = 1.0; + color[1] = 0.0; + color[2] = 0.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = STRIKE_HEIGHT; + break; + default: + return; + } + + fz_var(pts); + fz_try(ctx) + { + fz_annot *annot; + fz_matrix ctm; + + float zoom = glo->resolution / 72; + zoom = 1.0 / zoom; + fz_scale(&ctm, zoom, zoom); + pt_cls = (*env)->FindClass(env, "android/graphics/PointF"); + if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass"); + x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F"); + if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)"); + y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F"); + if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)"); + + n = (*env)->GetArrayLength(env, points); + + pts = fz_malloc_array(ctx, n, sizeof(fz_point)); + + for (i = 0; i < n; i++) + { + jobject opt = (*env)->GetObjectArrayElement(env, points, i); + pts[i].x = opt ? (*env)->GetFloatField(env, opt, x_fid) : 0.0f; + pts[i].y = opt ? (*env)->GetFloatField(env, opt, y_fid) : 0.0f; + fz_transform_point(&pts[i], &ctm); + } + + annot = (fz_annot *)pdf_create_annot(ctx, idoc, (pdf_page *)pc->page, type); + + pdf_set_markup_annot_quadpoints(ctx, idoc, (pdf_annot *)annot, pts, n); + pdf_set_markup_appearance(ctx, idoc, (pdf_annot *)annot, color, alpha, line_thickness, line_height); + + dump_annotation_display_lists(glo); + } + fz_always(ctx) + { + fz_free(ctx, pts); + } + fz_catch(ctx) + { + LOGE("addStrikeOutAnnotation: %s failed", ctx->error->message); + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); + (*env)->DeleteLocalRef(env, cls); + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_addInkAnnotationInternal)(JNIEnv * env, jobject thiz, jobjectArray arcs) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + pdf_document *idoc = pdf_specifics(ctx, doc); + page_cache *pc = &glo->pages[glo->current]; + jclass pt_cls; + jfieldID x_fid, y_fid; + int i, j, k, n; + fz_point *pts = NULL; + int *counts = NULL; + int total = 0; + float color[3]; + + if (idoc == NULL) + return; + + color[0] = 1.0; + color[1] = 0.0; + color[2] = 0.0; + + fz_var(pts); + fz_var(counts); + fz_try(ctx) + { + fz_annot *annot; + fz_matrix ctm; + + float zoom = glo->resolution / 72; + zoom = 1.0 / zoom; + fz_scale(&ctm, zoom, zoom); + pt_cls = (*env)->FindClass(env, "android/graphics/PointF"); + if (pt_cls == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "FindClass"); + x_fid = (*env)->GetFieldID(env, pt_cls, "x", "F"); + if (x_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(x)"); + y_fid = (*env)->GetFieldID(env, pt_cls, "y", "F"); + if (y_fid == NULL) fz_throw(ctx, FZ_ERROR_GENERIC, "GetFieldID(y)"); + + n = (*env)->GetArrayLength(env, arcs); + + counts = fz_malloc_array(ctx, n, sizeof(int)); + + for (i = 0; i < n; i++) + { + jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i); + int count = (*env)->GetArrayLength(env, arc); + + counts[i] = count; + total += count; + } + + pts = fz_malloc_array(ctx, total, sizeof(fz_point)); + + k = 0; + for (i = 0; i < n; i++) + { + jobjectArray arc = (jobjectArray)(*env)->GetObjectArrayElement(env, arcs, i); + int count = counts[i]; + + for (j = 0; j < count; j++) + { + jobject pt = (*env)->GetObjectArrayElement(env, arc, j); + + pts[k].x = pt ? (*env)->GetFloatField(env, pt, x_fid) : 0.0f; + pts[k].y = pt ? (*env)->GetFloatField(env, pt, y_fid) : 0.0f; + (*env)->DeleteLocalRef(env, pt); + fz_transform_point(&pts[k], &ctm); + k++; + } + (*env)->DeleteLocalRef(env, arc); + } + + annot = (fz_annot *)pdf_create_annot(ctx, idoc, (pdf_page *)pc->page, FZ_ANNOT_INK); + + pdf_set_ink_annot_list(ctx, idoc, (pdf_annot *)annot, pts, counts, n, color, INK_THICKNESS); + + dump_annotation_display_lists(glo); + } + fz_always(ctx) + { + fz_free(ctx, pts); + fz_free(ctx, counts); + } + fz_catch(ctx) + { + LOGE("addInkAnnotation: %s failed", ctx->error->message); + jclass cls = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if (cls != NULL) + (*env)->ThrowNew(env, cls, "Out of memory in MuPDFCore_searchPage"); + (*env)->DeleteLocalRef(env, cls); + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_deleteAnnotationInternal)(JNIEnv * env, jobject thiz, int annot_index) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_document *doc = glo->doc; + pdf_document *idoc = pdf_specifics(ctx, doc); + page_cache *pc = &glo->pages[glo->current]; + fz_annot *annot; + int i; + + if (idoc == NULL) + return; + + fz_try(ctx) + { + annot = fz_first_annot(ctx, pc->page); + for (i = 0; i < annot_index && annot; i++) + annot = fz_next_annot(ctx, annot); + + if (annot) + { + pdf_delete_annot(ctx, idoc, (pdf_page *)pc->page, (pdf_annot *)annot); + dump_annotation_display_lists(glo); + } + } + fz_catch(ctx) + { + LOGE("deleteAnnotationInternal: %s", ctx->error->message); + } +} + +/* Close the document, at least enough to be able to save over it. This + * may be called again later, so must be idempotent. */ +static void close_doc(globals *glo) +{ + int i; + + fz_free(glo->ctx, glo->hit_bbox); + glo->hit_bbox = NULL; + + for (i = 0; i < NUM_CACHE; i++) + drop_page_cache(glo, &glo->pages[i]); + + alerts_fin(glo); + + fz_drop_document(glo->ctx, glo->doc); + glo->doc = NULL; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_destroying)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + + if (glo == NULL) + return; + LOGI("Destroying"); + fz_free(glo->ctx, glo->current_path); + glo->current_path = NULL; + close_doc(glo); + fz_drop_context(glo->ctx); + glo->ctx = NULL; + free(glo); +#ifdef MEMENTO + LOGI("Destroying dump start"); + Memento_listBlocks(); + Memento_stats(); + LOGI("Destroying dump end"); +#endif +#ifdef NDK_PROFILER + // Apparently we should really be writing to whatever path we get + // from calling getFilesDir() in the java part, which supposedly + // gives /sdcard/data/data/com.artifex.MuPDF/gmon.out, but that's + // unfriendly. + setenv("CPUPROFILE", "/sdcard/gmon.out", 1); + moncleanup(); +#endif +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getPageLinksInternal)(JNIEnv * env, jobject thiz, int pageNumber) +{ + jclass linkInfoClass; + jclass linkInfoInternalClass; + jclass linkInfoExternalClass; + jclass linkInfoRemoteClass; + jmethodID ctorInternal; + jmethodID ctorExternal; + jmethodID ctorRemote; + jobjectArray arr; + jobject linkInfo; + fz_matrix ctm; + float zoom; + fz_link *list; + fz_link *link; + int count; + page_cache *pc; + globals *glo = get_globals(env, thiz); + + linkInfoClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfo"); + if (linkInfoClass == NULL) return NULL; + linkInfoInternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoInternal"); + if (linkInfoInternalClass == NULL) return NULL; + linkInfoExternalClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoExternal"); + if (linkInfoExternalClass == NULL) return NULL; + linkInfoRemoteClass = (*env)->FindClass(env, PACKAGENAME "/LinkInfoRemote"); + if (linkInfoRemoteClass == NULL) return NULL; + ctorInternal = (*env)->GetMethodID(env, linkInfoInternalClass, "", "(FFFFI)V"); + if (ctorInternal == NULL) return NULL; + ctorExternal = (*env)->GetMethodID(env, linkInfoExternalClass, "", "(FFFFLjava/lang/String;)V"); + if (ctorExternal == NULL) return NULL; + ctorRemote = (*env)->GetMethodID(env, linkInfoRemoteClass, "", "(FFFFLjava/lang/String;IZ)V"); + if (ctorRemote == NULL) return NULL; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->page == NULL || pc->number != pageNumber) + return NULL; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + + list = fz_load_links(glo->ctx, pc->page); + count = 0; + for (link = list; link; link = link->next) + { + switch (link->dest.kind) + { + case FZ_LINK_GOTO: + case FZ_LINK_GOTOR: + case FZ_LINK_URI: + count++ ; + } + } + + arr = (*env)->NewObjectArray(env, count, linkInfoClass, NULL); + if (arr == NULL) + { + fz_drop_link(glo->ctx, list); + return NULL; + } + + count = 0; + for (link = list; link; link = link->next) + { + fz_rect rect = link->rect; + fz_transform_rect(&rect, &ctm); + + switch (link->dest.kind) + { + case FZ_LINK_GOTO: + { + linkInfo = (*env)->NewObject(env, linkInfoInternalClass, ctorInternal, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, + link->dest.ld.gotor.page); + break; + } + + case FZ_LINK_GOTOR: + { + jstring juri = (*env)->NewStringUTF(env, link->dest.ld.gotor.file_spec); + linkInfo = (*env)->NewObject(env, linkInfoRemoteClass, ctorRemote, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, + juri, link->dest.ld.gotor.page, link->dest.ld.gotor.new_window ? JNI_TRUE : JNI_FALSE); + break; + } + + case FZ_LINK_URI: + { + jstring juri = (*env)->NewStringUTF(env, link->dest.ld.uri.uri); + linkInfo = (*env)->NewObject(env, linkInfoExternalClass, ctorExternal, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, + juri); + break; + } + + default: + continue; + } + + if (linkInfo == NULL) + { + fz_drop_link(glo->ctx, list); + return NULL; + } + (*env)->SetObjectArrayElement(env, arr, count, linkInfo); + (*env)->DeleteLocalRef(env, linkInfo); + count++; + } + fz_drop_link(glo->ctx, list); + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getWidgetAreasInternal)(JNIEnv * env, jobject thiz, int pageNumber) +{ + jclass rectFClass; + jmethodID ctor; + jobjectArray arr; + jobject rectF; + pdf_document *idoc; + pdf_widget *widget; + fz_matrix ctm; + float zoom; + int count; + page_cache *pc; + globals *glo = get_globals(env, thiz); + if (glo == NULL) + return NULL; + fz_context *ctx = glo->ctx; + + rectFClass = (*env)->FindClass(env, "android/graphics/RectF"); + if (rectFClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, rectFClass, "", "(FFFF)V"); + if (ctor == NULL) return NULL; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->number != pageNumber || pc->page == NULL) + return NULL; + + idoc = pdf_specifics(ctx, glo->doc); + if (idoc == NULL) + return NULL; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + + count = 0; + for (widget = pdf_first_widget(ctx, idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(ctx, widget)) + count ++; + + arr = (*env)->NewObjectArray(env, count, rectFClass, NULL); + if (arr == NULL) return NULL; + + count = 0; + for (widget = pdf_first_widget(ctx, idoc, (pdf_page *)pc->page); widget; widget = pdf_next_widget(ctx, widget)) + { + fz_rect rect; + pdf_bound_widget(ctx, widget, &rect); + fz_transform_rect(&rect, &ctm); + + rectF = (*env)->NewObject(env, rectFClass, ctor, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1); + if (rectF == NULL) return NULL; + (*env)->SetObjectArrayElement(env, arr, count, rectF); + (*env)->DeleteLocalRef(env, rectF); + + count ++; + } + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getAnnotationsInternal)(JNIEnv * env, jobject thiz, int pageNumber) +{ + jclass annotClass; + jmethodID ctor; + jobjectArray arr; + jobject jannot; + fz_annot *annot; + fz_matrix ctm; + float zoom; + int count; + page_cache *pc; + globals *glo = get_globals(env, thiz); + if (glo == NULL) + return NULL; + fz_context *ctx = glo->ctx; + + annotClass = (*env)->FindClass(env, PACKAGENAME "/Annotation"); + if (annotClass == NULL) return NULL; + ctor = (*env)->GetMethodID(env, annotClass, "", "(FFFFI)V"); + if (ctor == NULL) return NULL; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->number != pageNumber || pc->page == NULL) + return NULL; + + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + + count = 0; + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + count ++; + + arr = (*env)->NewObjectArray(env, count, annotClass, NULL); + if (arr == NULL) return NULL; + + count = 0; + for (annot = fz_first_annot(ctx, pc->page); annot; annot = fz_next_annot(ctx, annot)) + { + fz_rect rect; + fz_annot_type type = pdf_annot_type(ctx, (pdf_annot *)annot); + fz_bound_annot(ctx, annot, &rect); + fz_transform_rect(&rect, &ctm); + + jannot = (*env)->NewObject(env, annotClass, ctor, + (float)rect.x0, (float)rect.y0, (float)rect.x1, (float)rect.y1, type); + if (jannot == NULL) return NULL; + (*env)->SetObjectArrayElement(env, arr, count, jannot); + (*env)->DeleteLocalRef(env, jannot); + + count ++; + } + + return arr; +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_passClickEventInternal)(JNIEnv * env, jobject thiz, int pageNumber, float x, float y) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + fz_matrix ctm; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + float zoom; + fz_point p; + pdf_ui_event event; + int changed = 0; + page_cache *pc; + + if (idoc == NULL) + return 0; + + JNI_FN(MuPDFCore_gotoPageInternal)(env, thiz, pageNumber); + pc = &glo->pages[glo->current]; + if (pc->number != pageNumber || pc->page == NULL) + return 0; + + p.x = x; + p.y = y; + + /* Ultimately we should probably return a pointer to a java structure + * with the link details in, but for now, page number will suffice. + */ + zoom = glo->resolution / 72; + fz_scale(&ctm, zoom, zoom); + fz_invert_matrix(&ctm, &ctm); + + fz_transform_point(&p, &ctm); + + fz_try(ctx) + { + event.etype = PDF_EVENT_TYPE_POINTER; + event.event.pointer.pt = p; + event.event.pointer.ptype = PDF_POINTER_DOWN; + changed = pdf_pass_event(ctx, idoc, (pdf_page *)pc->page, &event); + event.event.pointer.ptype = PDF_POINTER_UP; + changed |= pdf_pass_event(ctx, idoc, (pdf_page *)pc->page, &event); + if (changed) { + dump_annotation_display_lists(glo); + } + } + fz_catch(ctx) + { + LOGE("passClickEvent: %s", ctx->error->message); + } + + return changed; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz) +{ + char *text = ""; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + fz_try(ctx) + { + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (idoc) + { + pdf_widget *focus = pdf_focused_widget(ctx, idoc); + + if (focus) + text = pdf_text_widget_text(ctx, idoc, focus); + } + } + fz_catch(ctx) + { + LOGE("getFocusedWidgetText failed: %s", ctx->error->message); + } + + return (*env)->NewStringUTF(env, text); +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_setFocusedWidgetTextInternal)(JNIEnv * env, jobject thiz, jstring jtext) +{ + const char *text; + int result = 0; + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + + text = (*env)->GetStringUTFChars(env, jtext, NULL); + if (text == NULL) + { + LOGE("Failed to get text"); + return 0; + } + + fz_try(ctx) + { + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (idoc) + { + pdf_widget *focus = pdf_focused_widget(ctx, idoc); + + if (focus) + { + result = pdf_text_widget_set_text(ctx, idoc, focus, (char *)text); + dump_annotation_display_lists(glo); + } + } + } + fz_catch(ctx) + { + LOGE("setFocusedWidgetText failed: %s", ctx->error->message); + } + + (*env)->ReleaseStringUTFChars(env, jtext, text); + + return result; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetChoiceOptions)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + int type; + int nopts, i; + char **opts = NULL; + jclass stringClass; + jobjectArray arr; + + if (idoc == NULL) + return NULL; + + focus = pdf_focused_widget(ctx, idoc); + if (focus == NULL) + return NULL; + + type = pdf_widget_get_type(ctx, focus); + if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) + return NULL; + + fz_var(opts); + fz_try(ctx) + { + nopts = pdf_choice_widget_options(ctx, idoc, focus, 0, NULL); + opts = fz_malloc(ctx, nopts * sizeof(*opts)); + (void)pdf_choice_widget_options(ctx, idoc, focus, 0, opts); + } + fz_catch(ctx) + { + fz_free(ctx, opts); + LOGE("Failed in getFocuseedWidgetChoiceOptions"); + return NULL; + } + + stringClass = (*env)->FindClass(env, "java/lang/String"); + + arr = (*env)->NewObjectArray(env, nopts, stringClass, NULL); + + for (i = 0; i < nopts; i++) + { + jstring s = (*env)->NewStringUTF(env, opts[i]); + if (s != NULL) + (*env)->SetObjectArrayElement(env, arr, i, s); + + (*env)->DeleteLocalRef(env, s); + } + + fz_free(ctx, opts); + + return arr; +} + +JNIEXPORT jobjectArray JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetChoiceSelected)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + int type; + int nsel, i; + char **sel = NULL; + jclass stringClass; + jobjectArray arr; + + if (idoc == NULL) + return NULL; + + focus = pdf_focused_widget(ctx, idoc); + if (focus == NULL) + return NULL; + + type = pdf_widget_get_type(ctx, focus); + if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) + return NULL; + + fz_var(sel); + fz_try(ctx) + { + nsel = pdf_choice_widget_value(ctx, idoc, focus, NULL); + sel = fz_malloc(ctx, nsel * sizeof(*sel)); + (void)pdf_choice_widget_value(ctx, idoc, focus, sel); + } + fz_catch(ctx) + { + fz_free(ctx, sel); + LOGE("Failed in getFocuseedWidgetChoiceOptions"); + return NULL; + } + + stringClass = (*env)->FindClass(env, "java/lang/String"); + + arr = (*env)->NewObjectArray(env, nsel, stringClass, NULL); + + for (i = 0; i < nsel; i++) + { + jstring s = (*env)->NewStringUTF(env, sel[i]); + if (s != NULL) + (*env)->SetObjectArrayElement(env, arr, i, s); + + (*env)->DeleteLocalRef(env, s); + } + + fz_free(ctx, sel); + + return arr; +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_setFocusedWidgetChoiceSelectedInternal)(JNIEnv * env, jobject thiz, jobjectArray arr) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + int type; + int nsel, i; + char **sel = NULL; + jstring *objs = NULL; + + if (idoc == NULL) + return; + + focus = pdf_focused_widget(ctx, idoc); + if (focus == NULL) + return; + + type = pdf_widget_get_type(ctx, focus); + if (type != PDF_WIDGET_TYPE_LISTBOX && type != PDF_WIDGET_TYPE_COMBOBOX) + return; + + nsel = (*env)->GetArrayLength(env, arr); + + sel = calloc(nsel, sizeof(*sel)); + objs = calloc(nsel, sizeof(*objs)); + if (objs == NULL || sel == NULL) + { + free(sel); + free(objs); + LOGE("Failed in setFocusWidgetChoiceSelected"); + return; + } + + for (i = 0; i < nsel; i++) + { + objs[i] = (jstring)(*env)->GetObjectArrayElement(env, arr, i); + sel[i] = (char *)(*env)->GetStringUTFChars(env, objs[i], NULL); + } + + fz_try(ctx) + { + pdf_choice_widget_set_value(ctx, idoc, focus, nsel, sel); + dump_annotation_display_lists(glo); + } + fz_catch(ctx) + { + LOGE("Failed in setFocusWidgetChoiceSelected"); + } + + for (i = 0; i < nsel; i++) + (*env)->ReleaseStringUTFChars(env, objs[i], sel[i]); + + free(sel); + free(objs); +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetTypeInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + + if (ctx, idoc == NULL) + return NONE; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + return NONE; + + switch (pdf_widget_get_type(ctx, focus)) + { + case PDF_WIDGET_TYPE_TEXT: return TEXT; + case PDF_WIDGET_TYPE_LISTBOX: return LISTBOX; + case PDF_WIDGET_TYPE_COMBOBOX: return COMBOBOX; + case PDF_WIDGET_TYPE_SIGNATURE: return SIGNATURE; + } + + return NONE; +} + +/* This enum should be kept in line with SignatureState in MuPDFPageView.java */ +enum +{ + Signature_NoSupport, + Signature_Unsigned, + Signature_Signed +}; + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_getFocusedWidgetSignatureState)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + + if (ctx, idoc == NULL) + return Signature_NoSupport; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + return Signature_NoSupport; + + if (!pdf_signatures_supported()) + return Signature_NoSupport; + + return pdf_dict_get(ctx, ((pdf_annot *)focus)->obj, PDF_NAME_V) ? Signature_Signed : Signature_Unsigned; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_checkFocusedSignatureInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + char ebuf[256] = "Failed"; + + if (idoc == NULL) + goto exit; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + goto exit; + + if (pdf_check_signature(ctx, idoc, focus, glo->current_path, ebuf, sizeof(ebuf))) + { + strcpy(ebuf, "Signature is valid"); + } + +exit: + return (*env)->NewStringUTF(env, ebuf); +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_signFocusedSignatureInternal)(JNIEnv * env, jobject thiz, jstring jkeyfile, jstring jpassword) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + pdf_widget *focus; + const char *keyfile; + const char *password; + jboolean res; + + if (idoc == NULL) + return JNI_FALSE; + + focus = pdf_focused_widget(ctx, idoc); + + if (focus == NULL) + return JNI_FALSE; + + keyfile = (*env)->GetStringUTFChars(env, jkeyfile, NULL); + password = (*env)->GetStringUTFChars(env, jpassword, NULL); + if (keyfile == NULL || password == NULL) + return JNI_FALSE; + + fz_var(res); + fz_try(ctx) + { + pdf_sign_signature(ctx, idoc, focus, keyfile, password); + dump_annotation_display_lists(glo); + res = JNI_TRUE; + } + fz_catch(ctx) + { + res = JNI_FALSE; + } + + return res; +} + +JNIEXPORT jobject JNICALL +JNI_FN(MuPDFCore_waitForAlertInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + jclass alertClass; + jmethodID ctor; + jstring title; + jstring message; + int alert_present; + pdf_alert_event alert; + + LOGT("Enter waitForAlert"); + pthread_mutex_lock(&glo->fin_lock); + pthread_mutex_lock(&glo->alert_lock); + + while (glo->alerts_active && !glo->alert_request) + pthread_cond_wait(&glo->alert_request_cond, &glo->alert_lock); + glo->alert_request = 0; + + alert_present = (glo->alerts_active && glo->current_alert); + + if (alert_present) + alert = *glo->current_alert; + + pthread_mutex_unlock(&glo->alert_lock); + pthread_mutex_unlock(&glo->fin_lock); + LOGT("Exit waitForAlert %d", alert_present); + + if (!alert_present) + return NULL; + + alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal"); + if (alertClass == NULL) + return NULL; + + ctor = (*env)->GetMethodID(env, alertClass, "", "(Ljava/lang/String;IILjava/lang/String;I)V"); + if (ctor == NULL) + return NULL; + + title = (*env)->NewStringUTF(env, alert.title); + if (title == NULL) + return NULL; + + message = (*env)->NewStringUTF(env, alert.message); + if (message == NULL) + return NULL; + + return (*env)->NewObject(env, alertClass, ctor, message, alert.icon_type, alert.button_group_type, title, alert.button_pressed); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_replyToAlertInternal)(JNIEnv * env, jobject thiz, jobject alert) +{ + globals *glo = get_globals(env, thiz); + jclass alertClass; + jfieldID field; + int button_pressed; + + alertClass = (*env)->FindClass(env, PACKAGENAME "/MuPDFAlertInternal"); + if (alertClass == NULL) + return; + + field = (*env)->GetFieldID(env, alertClass, "buttonPressed", "I"); + if (field == NULL) + return; + + button_pressed = (*env)->GetIntField(env, alert, field); + + LOGT("Enter replyToAlert"); + pthread_mutex_lock(&glo->alert_lock); + + if (glo->alerts_active && glo->current_alert) + { + // Fill in button_pressed and signal reply received. + glo->current_alert->button_pressed = button_pressed; + glo->alert_reply = 1; + pthread_cond_signal(&glo->alert_reply_cond); + } + + pthread_mutex_unlock(&glo->alert_lock); + LOGT("Exit replyToAlert"); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_startAlertsInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + + if (!glo->alerts_initialised) + return; + + LOGT("Enter startAlerts"); + pthread_mutex_lock(&glo->alert_lock); + + glo->alert_reply = 0; + glo->alert_request = 0; + glo->alerts_active = 1; + glo->current_alert = NULL; + + pthread_mutex_unlock(&glo->alert_lock); + LOGT("Exit startAlerts"); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_stopAlertsInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + + if (!glo->alerts_initialised) + return; + + LOGT("Enter stopAlerts"); + pthread_mutex_lock(&glo->alert_lock); + + glo->alert_reply = 0; + glo->alert_request = 0; + glo->alerts_active = 0; + glo->current_alert = NULL; + pthread_cond_signal(&glo->alert_reply_cond); + pthread_cond_signal(&glo->alert_request_cond); + + pthread_mutex_unlock(&glo->alert_lock); + LOGT("Exit stopAleerts"); +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_hasChangesInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + return (idoc && pdf_has_unsaved_changes(ctx, idoc)) ? JNI_TRUE : JNI_FALSE; +} + +static char *tmp_path(char *path) +{ + int f; + char *buf = malloc(strlen(path) + 6 + 1); + if (!buf) + return NULL; + + strcpy(buf, path); + strcat(buf, "XXXXXX"); + + f = mkstemp(buf); + + if (f >= 0) + { + close(f); + return buf; + } + else + { + free(buf); + return NULL; + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_saveInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + pdf_document *idoc = pdf_specifics(ctx, glo->doc); + + if (idoc && glo->current_path) + { + char *tmp; + pdf_write_options opts; + opts.do_incremental = 1; + opts.do_ascii = 0; + opts.do_expand = 0; + opts.do_garbage = 0; + opts.do_linear = 0; + + tmp = tmp_path(glo->current_path); + if (tmp) + { + int written = 0; + + fz_var(written); + fz_try(ctx) + { + FILE *fin = fopen(glo->current_path, "rb"); + FILE *fout = fopen(tmp, "wb"); + char buf[256]; + int n, err = 1; + + if (fin && fout) + { + while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) + fwrite(buf, 1, n, fout); + err = (ferror(fin) || ferror(fout)); + } + + if (fin) + fclose(fin); + if (fout) + fclose(fout); + + if (!err) + { + pdf_save_document(ctx, idoc, tmp, &opts); + written = 1; + } + } + fz_catch(ctx) + { + written = 0; + } + + if (written) + { + close_doc(glo); + rename(tmp, glo->current_path); + } + + free(tmp); + } + } +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_dumpMemoryInternal)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + +#ifdef MEMENTO + LOGE("dumpMemoryInternal start"); + Memento_listNewBlocks(); + Memento_stats(); + LOGE("dumpMemoryInternal end"); +#endif +} + +JNIEXPORT jlong JNICALL +JNI_FN(MuPDFCore_createCookie)(JNIEnv * env, jobject thiz) +{ + globals *glo = get_globals_any_thread(env, thiz); + if (glo == NULL) + return 0; + fz_context *ctx = glo->ctx; + + return (jlong) (intptr_t) fz_calloc_no_throw(ctx,1, sizeof(fz_cookie)); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_destroyCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr) +{ + fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr; + globals *glo = get_globals_any_thread(env, thiz); + if (glo == NULL) + return; + fz_context *ctx = glo->ctx; + + fz_free(ctx, cookie); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_abortCookie)(JNIEnv * env, jobject thiz, jlong cookiePtr) +{ + fz_cookie *cookie = (fz_cookie *) (intptr_t) cookiePtr; + if (cookie != NULL) + cookie->abort = 1; +} + +static char *tmp_gproof_path(char *path) +{ + FILE *f; + int i; + char *buf = malloc(strlen(path) + 20 + 1); + if (!buf) + return NULL; + + for (i = 0; i < 10000; i++) + { + sprintf(buf, "%s.%d.gproof", path, i); + + LOGE("Trying for %s\n", buf); + f = fopen(buf, "r"); + if (f != NULL) + { + fclose(f); + continue; + } + + f = fopen(buf, "w"); + if (f != NULL) + { + fclose(f); + break; + } + } + if (i == 10000) + { + LOGE("Failed to find temp gproof name"); + free(buf); + return NULL; + } + + LOGE("Rewritten to %s\n", buf); + return buf; +} + +JNIEXPORT jstring JNICALL +JNI_FN(MuPDFCore_startProofInternal)(JNIEnv * env, jobject thiz, int inResolution) +{ +#ifdef SUPPORT_GPROOF + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + char *tmp; + jstring ret; + + if (!glo->doc || !glo->current_path) + return NULL; + + tmp = tmp_gproof_path(glo->current_path); + if (!tmp) + return NULL; + + int theResolution = PROOF_RESOLUTION; + if (inResolution != 0) + theResolution = inResolution; + + fz_try(ctx) + { + fz_save_gproof(ctx, glo->current_path, glo->doc, tmp, theResolution, "", ""); + + LOGE("Creating %s\n", tmp); + ret = (*env)->NewStringUTF(env, tmp); + } + fz_always(ctx) + { + free(tmp); + } + fz_catch(ctx) + { + ret = NULL; + } + return ret; +#else + return NULL; +#endif +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_endProofInternal)(JNIEnv * env, jobject thiz, jstring jfilename) +{ +#ifdef SUPPORT_GPROOF + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + const char *tmp; + + if (!glo->doc || !glo->current_path || jfilename == NULL) + return; + + tmp = (*env)->GetStringUTFChars(env, jfilename, NULL); + if (tmp) + { + LOGE("Deleting %s\n", tmp); + + unlink(tmp); + (*env)->ReleaseStringUTFChars(env, jfilename, tmp); + } +#endif +} + +JNIEXPORT jboolean JNICALL +JNI_FN(MuPDFCore_gprfSupportedInternal)(JNIEnv * env) +{ +#ifdef SUPPORT_GPROOF + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + +JNIEXPORT int JNICALL +JNI_FN(MuPDFCore_getNumSepsOnPageInternal)(JNIEnv *env, jobject thiz, int page) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + int i; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + break; + } + if (i == NUM_CACHE) + return 0; + + LOGE("Counting seps on page %d", page); + + return fz_count_separations_on_page(ctx, glo->pages[i].page); +} + +JNIEXPORT void JNICALL +JNI_FN(MuPDFCore_controlSepOnPageInternal)(JNIEnv *env, jobject thiz, int page, int sep, jboolean disable) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + int i; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + break; + } + if (i == NUM_CACHE) + return; + + fz_control_separation_on_page(ctx, glo->pages[i].page, sep, disable); +} + +JNIEXPORT jobject JNICALL +JNI_FN(MuPDFCore_getSepInternal)(JNIEnv *env, jobject thiz, int page, int sep) +{ + globals *glo = get_globals(env, thiz); + fz_context *ctx = glo->ctx; + const char *name; + char rgba[4]; + unsigned int bgra; + unsigned int cmyk; + jobject jname; + jclass sepClass; + jmethodID ctor; + int i; + + for (i = 0; i < NUM_CACHE; i++) + { + if (glo->pages[i].page != NULL && glo->pages[i].number == page) + break; + } + if (i == NUM_CACHE) + return NULL; + + /* MuPDF returns RGBA as bytes. Android wants a packed BGRA int. */ + name = fz_get_separation_on_page(ctx, glo->pages[i].page, sep, (unsigned int *)(&rgba[0]), &cmyk); + bgra = (rgba[0] << 16) | (rgba[1]<<8) | rgba[2] | (rgba[3]<<24); + jname = name ? (*env)->NewStringUTF(env, name) : NULL; + + sepClass = (*env)->FindClass(env, PACKAGENAME "/Separation"); + if (sepClass == NULL) + return NULL; + + ctor = (*env)->GetMethodID(env, sepClass, "", "(Ljava/lang/String;II)V"); + if (ctor == NULL) + return NULL; + + return (*env)->NewObject(env, sepClass, ctor, jname, bgra, cmyk); +} diff --git a/platform/android/viewer/local.properties.sample b/platform/android/viewer/local.properties.sample new file mode 100644 index 00000000..bc40e6ef --- /dev/null +++ b/platform/android/viewer/local.properties.sample @@ -0,0 +1,5 @@ +# Uncomment and edit the appropriate line below. +# Resave this file as local.properties. + +sdk.dir=/path/to/android/sdk +ndk.dir=/path/to/android/ndk diff --git a/platform/android/viewer/project.properties b/platform/android/viewer/project.properties new file mode 100644 index 00000000..895c9ce2 --- /dev/null +++ b/platform/android/viewer/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-16 diff --git a/platform/android/viewer/res/animator/info.xml b/platform/android/viewer/res/animator/info.xml new file mode 100644 index 00000000..9085a9ee --- /dev/null +++ b/platform/android/viewer/res/animator/info.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/platform/android/viewer/res/drawable-hdpi/icon.png b/platform/android/viewer/res/drawable-hdpi/icon.png new file mode 100644 index 00000000..4f47347d Binary files /dev/null and b/platform/android/viewer/res/drawable-hdpi/icon.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_annot.png b/platform/android/viewer/res/drawable-ldpi/ic_annot.png new file mode 100644 index 00000000..c4f1df07 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_annot.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_annotation.png b/platform/android/viewer/res/drawable-ldpi/ic_annotation.png new file mode 100644 index 00000000..1f4e6d48 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_annotation.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_arrow_left.png b/platform/android/viewer/res/drawable-ldpi/ic_arrow_left.png new file mode 100644 index 00000000..d49c7438 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_arrow_left.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_arrow_right.png b/platform/android/viewer/res/drawable-ldpi/ic_arrow_right.png new file mode 100644 index 00000000..e76d0cb0 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_arrow_right.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_cancel.png b/platform/android/viewer/res/drawable-ldpi/ic_cancel.png new file mode 100644 index 00000000..6912e1ed Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_cancel.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_check.png b/platform/android/viewer/res/drawable-ldpi/ic_check.png new file mode 100644 index 00000000..fb789c8d Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_check.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_clipboard.png b/platform/android/viewer/res/drawable-ldpi/ic_clipboard.png new file mode 100644 index 00000000..3023c6eb Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_clipboard.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_dir.png b/platform/android/viewer/res/drawable-ldpi/ic_dir.png new file mode 100644 index 00000000..2236f2f8 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_dir.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_doc.png b/platform/android/viewer/res/drawable-ldpi/ic_doc.png new file mode 100644 index 00000000..407ed5d4 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_doc.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_highlight.png b/platform/android/viewer/res/drawable-ldpi/ic_highlight.png new file mode 100644 index 00000000..3d6d29b9 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_highlight.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_link.png b/platform/android/viewer/res/drawable-ldpi/ic_link.png new file mode 100644 index 00000000..a447b87d Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_link.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_list.png b/platform/android/viewer/res/drawable-ldpi/ic_list.png new file mode 100644 index 00000000..4a2dde6d Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_list.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_magnifying_glass.png b/platform/android/viewer/res/drawable-ldpi/ic_magnifying_glass.png new file mode 100644 index 00000000..a3c8f598 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_magnifying_glass.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_more.png b/platform/android/viewer/res/drawable-ldpi/ic_more.png new file mode 100644 index 00000000..68988a56 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_more.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_pen.png b/platform/android/viewer/res/drawable-ldpi/ic_pen.png new file mode 100644 index 00000000..7b7de296 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_pen.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_print.png b/platform/android/viewer/res/drawable-ldpi/ic_print.png new file mode 100644 index 00000000..f191fc85 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_print.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_proof.png b/platform/android/viewer/res/drawable-ldpi/ic_proof.png new file mode 100644 index 00000000..fee26a7b Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_proof.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_reflow.png b/platform/android/viewer/res/drawable-ldpi/ic_reflow.png new file mode 100644 index 00000000..e9e8b052 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_reflow.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_select.png b/platform/android/viewer/res/drawable-ldpi/ic_select.png new file mode 100644 index 00000000..81af6738 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_select.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_share.png b/platform/android/viewer/res/drawable-ldpi/ic_share.png new file mode 100644 index 00000000..05fbe31a Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_share.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_strike.png b/platform/android/viewer/res/drawable-ldpi/ic_strike.png new file mode 100644 index 00000000..fc39409f Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_strike.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_trash.png b/platform/android/viewer/res/drawable-ldpi/ic_trash.png new file mode 100644 index 00000000..465d1245 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_trash.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_underline.png b/platform/android/viewer/res/drawable-ldpi/ic_underline.png new file mode 100644 index 00000000..0a5be3d4 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_underline.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/ic_updir.png b/platform/android/viewer/res/drawable-ldpi/ic_updir.png new file mode 100644 index 00000000..b923e429 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/ic_updir.png differ diff --git a/platform/android/viewer/res/drawable-ldpi/icon.png b/platform/android/viewer/res/drawable-ldpi/icon.png new file mode 100644 index 00000000..82655e72 Binary files /dev/null and b/platform/android/viewer/res/drawable-ldpi/icon.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_annot.png b/platform/android/viewer/res/drawable-mdpi/ic_annot.png new file mode 100644 index 00000000..0b4bfd92 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_annot.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_annotation.png b/platform/android/viewer/res/drawable-mdpi/ic_annotation.png new file mode 100644 index 00000000..6f81c4a0 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_annotation.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_arrow_left.png b/platform/android/viewer/res/drawable-mdpi/ic_arrow_left.png new file mode 100644 index 00000000..16a31b21 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_arrow_left.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_arrow_right.png b/platform/android/viewer/res/drawable-mdpi/ic_arrow_right.png new file mode 100644 index 00000000..cc34067e Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_arrow_right.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_arrow_up.png b/platform/android/viewer/res/drawable-mdpi/ic_arrow_up.png new file mode 100644 index 00000000..de2726ce Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_arrow_up.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_cancel.png b/platform/android/viewer/res/drawable-mdpi/ic_cancel.png new file mode 100644 index 00000000..0b794b4d Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_cancel.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_check.png b/platform/android/viewer/res/drawable-mdpi/ic_check.png new file mode 100644 index 00000000..527aaeb9 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_check.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_clipboard.png b/platform/android/viewer/res/drawable-mdpi/ic_clipboard.png new file mode 100644 index 00000000..c05deffd Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_clipboard.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_dir.png b/platform/android/viewer/res/drawable-mdpi/ic_dir.png new file mode 100644 index 00000000..e15200c5 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_dir.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_doc.png b/platform/android/viewer/res/drawable-mdpi/ic_doc.png new file mode 100644 index 00000000..1eb722be Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_doc.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_highlight.png b/platform/android/viewer/res/drawable-mdpi/ic_highlight.png new file mode 100644 index 00000000..2a8fe4db Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_highlight.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_link.png b/platform/android/viewer/res/drawable-mdpi/ic_link.png new file mode 100644 index 00000000..7f7ac170 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_link.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_list.png b/platform/android/viewer/res/drawable-mdpi/ic_list.png new file mode 100644 index 00000000..e4f3164c Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_list.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_magnifying_glass.png b/platform/android/viewer/res/drawable-mdpi/ic_magnifying_glass.png new file mode 100644 index 00000000..389cebd5 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_magnifying_glass.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_more.png b/platform/android/viewer/res/drawable-mdpi/ic_more.png new file mode 100644 index 00000000..2b662ab3 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_more.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_pen.png b/platform/android/viewer/res/drawable-mdpi/ic_pen.png new file mode 100644 index 00000000..db805373 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_pen.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_print.png b/platform/android/viewer/res/drawable-mdpi/ic_print.png new file mode 100644 index 00000000..58105463 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_print.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_proof.png b/platform/android/viewer/res/drawable-mdpi/ic_proof.png new file mode 100644 index 00000000..cbda8721 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_proof.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_reflow.png b/platform/android/viewer/res/drawable-mdpi/ic_reflow.png new file mode 100644 index 00000000..84bd5418 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_reflow.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_select.png b/platform/android/viewer/res/drawable-mdpi/ic_select.png new file mode 100644 index 00000000..9eaf6924 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_select.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_sep.png b/platform/android/viewer/res/drawable-mdpi/ic_sep.png new file mode 100644 index 00000000..2167be2e Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_sep.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_share.png b/platform/android/viewer/res/drawable-mdpi/ic_share.png new file mode 100644 index 00000000..cae51b69 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_share.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_strike.png b/platform/android/viewer/res/drawable-mdpi/ic_strike.png new file mode 100644 index 00000000..b15e9324 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_strike.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_trash.png b/platform/android/viewer/res/drawable-mdpi/ic_trash.png new file mode 100644 index 00000000..3006fec3 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_trash.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/ic_underline.png b/platform/android/viewer/res/drawable-mdpi/ic_underline.png new file mode 100644 index 00000000..5d4dd5a4 Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/ic_underline.png differ diff --git a/platform/android/viewer/res/drawable-mdpi/icon.png b/platform/android/viewer/res/drawable-mdpi/icon.png new file mode 100644 index 00000000..e05de27c Binary files /dev/null and b/platform/android/viewer/res/drawable-mdpi/icon.png differ diff --git a/platform/android/viewer/res/drawable-xhdpi/icon.png b/platform/android/viewer/res/drawable-xhdpi/icon.png new file mode 100644 index 00000000..0995b78e Binary files /dev/null and b/platform/android/viewer/res/drawable-xhdpi/icon.png differ diff --git a/platform/android/viewer/res/drawable/busy.xml b/platform/android/viewer/res/drawable/busy.xml new file mode 100644 index 00000000..f7f42a44 --- /dev/null +++ b/platform/android/viewer/res/drawable/busy.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/platform/android/viewer/res/drawable/button.xml b/platform/android/viewer/res/drawable/button.xml new file mode 100644 index 00000000..0a9bcd51 --- /dev/null +++ b/platform/android/viewer/res/drawable/button.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/viewer/res/drawable/darkdenim3.png b/platform/android/viewer/res/drawable/darkdenim3.png new file mode 100644 index 00000000..be532f6d Binary files /dev/null and b/platform/android/viewer/res/drawable/darkdenim3.png differ diff --git a/platform/android/viewer/res/drawable/page_num.xml b/platform/android/viewer/res/drawable/page_num.xml new file mode 100644 index 00000000..8d50df85 --- /dev/null +++ b/platform/android/viewer/res/drawable/page_num.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/platform/android/viewer/res/drawable/search.xml b/platform/android/viewer/res/drawable/search.xml new file mode 100644 index 00000000..4fc58830 --- /dev/null +++ b/platform/android/viewer/res/drawable/search.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/viewer/res/drawable/seek_progress.xml b/platform/android/viewer/res/drawable/seek_progress.xml new file mode 100644 index 00000000..328139c2 --- /dev/null +++ b/platform/android/viewer/res/drawable/seek_progress.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/platform/android/viewer/res/drawable/seek_thumb.xml b/platform/android/viewer/res/drawable/seek_thumb.xml new file mode 100644 index 00000000..e3a9bad4 --- /dev/null +++ b/platform/android/viewer/res/drawable/seek_thumb.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/platform/android/viewer/res/drawable/tiled_background.xml b/platform/android/viewer/res/drawable/tiled_background.xml new file mode 100644 index 00000000..60e08f3c --- /dev/null +++ b/platform/android/viewer/res/drawable/tiled_background.xml @@ -0,0 +1,4 @@ + + diff --git a/platform/android/viewer/res/layout/buttons.xml b/platform/android/viewer/res/layout/buttons.xml new file mode 100644 index 00000000..6c1620ba --- /dev/null +++ b/platform/android/viewer/res/layout/buttons.xml @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/viewer/res/layout/main.xml b/platform/android/viewer/res/layout/main.xml new file mode 100644 index 00000000..50b4746b --- /dev/null +++ b/platform/android/viewer/res/layout/main.xml @@ -0,0 +1,5 @@ + + diff --git a/platform/android/viewer/res/layout/outline_entry.xml b/platform/android/viewer/res/layout/outline_entry.xml new file mode 100644 index 00000000..ea7912e4 --- /dev/null +++ b/platform/android/viewer/res/layout/outline_entry.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/platform/android/viewer/res/layout/picker_entry.xml b/platform/android/viewer/res/layout/picker_entry.xml new file mode 100644 index 00000000..673a4724 --- /dev/null +++ b/platform/android/viewer/res/layout/picker_entry.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/platform/android/viewer/res/layout/print_dialog.xml b/platform/android/viewer/res/layout/print_dialog.xml new file mode 100644 index 00000000..1d54d22f --- /dev/null +++ b/platform/android/viewer/res/layout/print_dialog.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/platform/android/viewer/res/layout/textentry.xml b/platform/android/viewer/res/layout/textentry.xml new file mode 100644 index 00000000..08823df8 --- /dev/null +++ b/platform/android/viewer/res/layout/textentry.xml @@ -0,0 +1,8 @@ + + + diff --git a/platform/android/viewer/res/values-ar/strings.xml b/platform/android/viewer/res/values-ar/strings.xml new file mode 100644 index 00000000..f16d5ba9 --- /dev/null +++ b/platform/android/viewer/res/values-ar/strings.xml @@ -0,0 +1,54 @@ + + + قبول + MuPDF + إلغاء + تعذر فتح المخزن المؤقت + تعذر فتح المستند + تعذر فتح المستند: %1$s + تعذر فتح الملف: %1$s + اختر قيمة + تم النسخ إلى الحافظة + نسخ + نسخ النص + نسخ النص إلى الحافظة + حذف + تجاهل + يحتوي المستند على تغييرات. هل تريد حفظها؟ + سحب تعليق توضيحي + تعديل التعليقات التوضيحية + أدخل كلمة المرور + دخول إلى وضع إعادة التدفق + تعبئة حقل النص + التنسيق غير مدعوم حاليًا + تظليل + حبر + خروج من وضع إعادة التدفق + المزيد + لا + لم يتم العثور على متكررات أخرى + مشاركة وسائط التخزين مع حاسوب شخصي قد يمنع الوصول إليها + وسائط التخزين غير موجودة + لم يتم تحديد نص + غير مدعوم + لا يوجد شيء لحفظه + موافق + جدول المحتويات + [أعلى مستوى واحد] + %1$s %2$s: %3$s + طباعة + فشلت الطباعة + حفظ + بحث + بحث إلى الخلف + بحث في المستند + بحث إلى الأمام + جاري البحث في&#8230; + تحديد + تحديد النص + شطب + لم يتم العثور على النص + تظليل وتمكين الروابط + تسطير + نعم + diff --git a/platform/android/viewer/res/values-ca/strings.xml b/platform/android/viewer/res/values-ca/strings.xml new file mode 100644 index 00000000..ef72886e --- /dev/null +++ b/platform/android/viewer/res/values-ca/strings.xml @@ -0,0 +1,54 @@ + + + Acceptar + MuPDF + Cancel·lar + No es pot obrir el buffer + No es pot obrir el document + No es pot obrir el document: %1$s + No es pot obrir l\'arxiu: %1$s + Tria el valor + Copiat al portapapers + Copiar + copiar text + Copiar text al portapapers + Esborrar + Descartar + El document té canvis. Desar? + Dibuixar anotació + Editar anotacions + Introduir contrasenya + Entrant en modo de reflux + Emplena el camp de text + Format no suportat actualment + Destacar + Tinta + Abandonant modo de reflux + Més + No + No hi ha més coincidències + Compartir el mitjà d\'emmagatzematge amb un PC pot fer que sigui inaccessible + Mitjà d\'emmagatzematge no present + No s\'ha seleccionat text + No compatible + No hi ha gens que guardar + Acceptar + Índex + [Pujar un nivell] + %1$s %2$s: %3$s + Imprimir + Fallada al imprimir + Desar + Buscar + Buscar cap a enrere + Buscar document + Buscar cap a davant + Buscant… + Seleccionar + Seleccionar text + Ratllat + Text no trobat + Ressaltar i habilitar enllaços + Subratllat + + diff --git a/platform/android/viewer/res/values-cs/strings.xml b/platform/android/viewer/res/values-cs/strings.xml new file mode 100644 index 00000000..6c870391 --- /dev/null +++ b/platform/android/viewer/res/values-cs/strings.xml @@ -0,0 +1,54 @@ + + + Přijmout + MuPDF + Zrušit + Nelze otevřít vyrovnávací paměť + Nelze otevřít dokument + Nelze otevřít dokument: %1$s + Nelze otevřít soubor: %1$s + Zvolte hodnotu + Kopírováno do schránky + Kopírovat + kopírovat text + Kopírovat text do schránky + Smazat + Odmítnout + Dokument byl změněn. Uložit? + Vložit anotaci + Upravit anotace + Zadat heslo + Vstup do režimu přeformátování řádků + Vyplnit textové pole + Formát aktuálně nepodporován + Zvýraznit + Inkoust + Odchod z režimu přeformátování řádků + Více + Ne + Nenalezeny další výskyty + Při sdílení s PC může být paměťové médium nedostupné + Paměťové médim nenalezeno + Nevybrán žádný text + Nepodporováno + Nic k uložení + OK + Obsah + [Nahoru o jednu úroveň] + %1$s %2$s: %3$s + Tisk + Tisk selhal + Uložit + Hledat + Hledat zpět + Prohledat dokument + Hledat vpřed + Hledání&#8230; + Vybrat + Vybrat text + Přeškrtnout + Text nenalezen + Zvýraznit a aktivovat odkazy + Podtrhnout + Ano + diff --git a/platform/android/viewer/res/values-da/strings.xml b/platform/android/viewer/res/values-da/strings.xml new file mode 100644 index 00000000..b7de1fdc --- /dev/null +++ b/platform/android/viewer/res/values-da/strings.xml @@ -0,0 +1,54 @@ + + + Accepter + MuPDF + Annuller + Buffer kan ikke åbnes + Dokument kan ikke åbnes + Kan ikke åbne dokumentet: %1$s + Kan ikke åbne filen: %1$s + Vælg værdi + Kopieret til udklipsholder + Kopier + kopier tekst + kopier tekst til udklipsholder + Slet + Afvis + Dokumentet er ændret. Gem ændringer? + Lav anmærkning + Rediger anmærkninger + Indtast adgangskode + Går over til konverteringstilstand + Udfyld tekstfelt + Format ikke understøttet i øjeblikket + Fremhæv + Ink + Forlader konverteringstilstand + Mere + Nej + Der blev ikke fundet flere tilfælde + Deles lagermediet med en PC, kan det gøre det utilgængeligt + Lagermedie ikke fundet + Ingen tekst valgt + Ikke understøttet + Intet at gemme + Okay + Indholdsfortegnelse + [Et niveau op] + %1$s %2$s: %3$s + Udskriv + Udskrivning mislykket + Gem + Søg + Søg bagud + Søg i dokument + Søg fremad + Søger&#8230; + Vælg + Vælg tekst + Gennemstreget + Tekst ikke fundet + Fremhæv og aktiver links + Understreg + Ja + diff --git a/platform/android/viewer/res/values-de/strings.xml b/platform/android/viewer/res/values-de/strings.xml new file mode 100644 index 00000000..2e69d369 --- /dev/null +++ b/platform/android/viewer/res/values-de/strings.xml @@ -0,0 +1,54 @@ + + + Akzeptieren + MuPDF + Abbrechen + Zwischenspeicher kann nicht geöffnet werden + Dokument kann nicht geöffnet werden + Dokument kann nicht geöffnet werden: %1$s + Datei kann nicht geöffnet werden: %1$s + Wert auswählen + In die Zwischenanlage kopiert + Kopieren + Text kopieren + Text in Zwischenablage kopieren + Entfernen + Verwerfen + Das Dokument wurde verändert. Sollen die Änderungen gespeichert werden? + Kommentar einfügen + Kommentar bearbeiten + Passwort eingeben + Anpassungsmodus wird gestartet + Textfeld ausfüllen + Format wird momentan nicht unterstützt + Markieren + Farbe + Anpassungsmodus wird beendet + Mehr + Nein + Keine weiteren Treffer + Die Freigabe des Speichermediums für einen PC kann es unzugänglich machen + Speichermedium nicht vorhanden + Kein Text ausgewählt + Nicht unterstützt + Nichts zum Speichern + OK + Inhaltsverzeichnis + [Eine Ebene nach oben] + %1$s %2$s: %3$s + Drucken + Fehler beim Drucken + Speichern + Suchen + Rückwärts suchen + Dokument durchsuchen + Vorwärts suchen + Suche… + Auswählen + Text auswählen + Durchstreichen + Text konnte nicht gefunden werden + Markiere und aktiviere Verknüpfungen + Unterstreichen + Ja + diff --git a/platform/android/viewer/res/values-el/strings.xml b/platform/android/viewer/res/values-el/strings.xml new file mode 100644 index 00000000..f994f287 --- /dev/null +++ b/platform/android/viewer/res/values-el/strings.xml @@ -0,0 +1,54 @@ + + + Αποδοχή + MuPDF + Ακύρωση + Αδυναμία ανοίγματος buffer + Αδυναμία ανοίγματος εγγράφου + Αδυναμία ανοίγματος εγγράφου: %1$s + Αδυναμία ανοίγματος αρχείου: %1$s + Επιλογή τιμής + Αντιγράφηκε στο πρόχειρο + Αντιγραφή + αντιγραφή κειμένου + Αντιγραφή κειμένου στο πρόχειρο + Διαγραφή + Ματαίωση + Το έγγραφο έχει αλλαγές. Να αποθηκευτούν; + Σχεδίαση σχολίου + Επεξεργασία σχολίων + Πληκτρολογήστε κωδικό πρόσβασης + Είσοδος σε λειτουργία δυναμικής προσαρμογής + Συμπλήρωση πεδίου κειμένου + Αυτή η μορφή δεν υποστηρίζεται τη δεδομένη στιγμή + Επισήμανση + Γραφή + Έξοδος από λειτουργία δυναμικής προσαρμογής + Περισσότερο + Όχι + Δεν βρέθηκαν άλλες εμφανίσεις + Η κοινή χρήση του αποθηκευτικού μέσου με έναν υπολογιστή μπορεί να το καταστήσει μη προσβάσιμο + Δεν υπάρχει αποθηκευτικό μέσο + Δεν έχει επιλεγεί κείμενο + Δεν υποστηρίζεται + Δεν υπάρχει περιεχόμενο για αποθήκευση + ΟΚ + Πίνακας περιεχομένων + [Ένα επίπεδο επάνω] + %1$s %2$s: %3$s + Εκτύπωση + Η εκτύπωση απέτυχε + Αποθήκευση + Αναζήτηση + Αναζήτηση προς τα πίσω + Αναζήτηση εγγράφου + Αναζήτηση προς τα μπροστά + Αναζήτηση&#8230; + Επιλογή + Επιλογή κειμένου + Διακριτή διαγραφή + Δεν βρέθηκε το κείμενο + Επισήμανση και ενεργοποίηση συνδέσεων + Υπογράμμιση + Ναι + diff --git a/platform/android/viewer/res/values-es/strings.xml b/platform/android/viewer/res/values-es/strings.xml new file mode 100644 index 00000000..0e28a909 --- /dev/null +++ b/platform/android/viewer/res/values-es/strings.xml @@ -0,0 +1,54 @@ + + + Aceptar + MuPDF + Cancelar + No se puede abrir el búfer + No se puede abrir el documento + No se puede abrir el documento:%1$s + No se puede abrir el archivo: %1$s + Elegir valor + Copiado al portapapeles + Copiar + copiar texto + Copiar texto al portapapeles + Borrar + Ignorar + El documento tiene cambios. ¿Guardar? + Dibujar anotación + Editar anotaicones + Introducir contraseña + Entrando en el modo de redistribución + Rellenar el campo de texto + Formato actualmente no soportado + Resaltar + Tinta + Saliendo del modo de redistribución + Más + No + No se han encontrado más casos + Compartir el medio de almacenamiento con un PC puede hacerlo inaccesible + Medio de almacenimiento no presente + Texto no seleccionado + No aceptado + Nada que guardar + OK + Tabla de contenidos + [Subir un nivel] + %1$s %2$s: %3$s + Imprimir + No se ha imprimido + Guardar + Buscar + Buscar hacia atrás + Buscar documento + Buscar hacia adelante + Buscando&#8230; + Seleccionar + Seleccionar texto + Tachar + Texto no encontrado + Resaltar y activar + Subrayar + + diff --git a/platform/android/viewer/res/values-et/strings.xml b/platform/android/viewer/res/values-et/strings.xml new file mode 100644 index 00000000..fddd25a8 --- /dev/null +++ b/platform/android/viewer/res/values-et/strings.xml @@ -0,0 +1,54 @@ + + + Nõustu + MuPDF + Tühista + Ei saa avada puhvrit + Ei saa avada dokumenti + Ei saa avada dokumenti: %1$s + Ei saa avada faili: %1$s + Vali väärtus + Kopeeritud lõikelauale + Kopeeri + kopeeri tekst + Kopeeri tekst lõikelauale + Kustuta + Lõpeta + Dokumendis on tehtud muudatusi. Kas salvestada need? + Tee marginaal + Redigeeri marginaale + Sisesta salasõna + Sisenen ümberpaigutamise režiimi + Täida tekstiväli + Vormingul puudub hetkel tugi + Tõsta esile + Tint + Lahkun ümberpaigutamise režiimist + Veel + Ei + Ei leitud rohkem juhtumeid + Salvestuskandja jagamine arvutiga võib selle juurdepääsmatuks muuta + Salvestuskandja puudub + Teksti ei ole valitud + Puudub tugi + Ei ole midagi salvestada + OK + Sisukord + [Taseme võrra üles] + %1$s%2$s%3$s + Prindi + Printimine ebaõnnestus + Salvesta + Otsi + Otsi tagasisuunas + Otsi dokumendist + Otsi edasisuunas + Otsin&#8230; + Vali + Vali tekst + Läbikriipsutus + Teksti ei leitud + Tõsta lingid esile ja luba need + Jooni alla + Jah + diff --git a/platform/android/viewer/res/values-fi/strings.xml b/platform/android/viewer/res/values-fi/strings.xml new file mode 100644 index 00000000..ae13e724 --- /dev/null +++ b/platform/android/viewer/res/values-fi/strings.xml @@ -0,0 +1,54 @@ + + + Hyväksy + MuPDF + Peruuta + Puskuria ei voi avata + Tiedostoa ei voi avata + Ei voi avata tiedostoa: %1$s + Ei voi avata tiedostoa: %1$s + Valitse arvo + Kopioitu leikepöydälle + Kopioi + kopio teksti + Kopioi teksti leikepöydälle + Poista + Hylkää + Tiedostossa on muutoksia. Haluatko tallentaa muutokset? + Piirrä huomautus + Muokkaa huomautuksia + Anna salasana + Siirrytään takaisinmuuntotilaan + Täytä tekstikenttä + Muotoa ei tällä hetkellä tueta + Korosta + Muste + Poistutaan takaisinmuuntotilasta + Lisää + Ei + Muita esiintymiä ei löydy + Tallennustietovälineen jakaminen tietokoneen kanssa voi estää sen käyttämisen + Tallennustietoväline ei ole käytössä + Ei valittua tekstiä + Ei tuettu + Ei mitään tallennettavaa + OK + Sisällys + [Yksi taso ylöspäin] + %1$s %2$s: %3$s + Tulosta + Tulostus ei onnistunut + Tallenna + Haku + Hae taaksepäin + Hae tiedostosta + Hae eteenpäin + Haetaan &#8230; + Valitse + Valitse teksti + Yliviivaa + Tekstiä ei löydy + Korosta ja ota käyttöön linkit + Alleviivaa + Kyllä + diff --git a/platform/android/viewer/res/values-fr/strings.xml b/platform/android/viewer/res/values-fr/strings.xml new file mode 100644 index 00000000..967707b9 --- /dev/null +++ b/platform/android/viewer/res/values-fr/strings.xml @@ -0,0 +1,54 @@ + + + Accepter + MuPDF + Annuler + Impossible d\'ouvrir le buffer + Impossible d\'ouvrir le document + Impossible d\'ouvrir le document : %1$s + Impossible d\'ouvrir le fichier : %1$s + Choisir la valeur + Copié dans le presse-papier + Copier + copier le texte + Copier le texte sur le presse-papier + Supprimer + Ignorer + Des modifications ont été effectuées au document. Les sauvegarder ? + Dessiner une note + Éditer une note + Introduire le mot de passe + Entrer en mode refusion + Remplir le champ du texte + Format non compatible pour l\'instant + Surligner + Encre + Quitter le mode refusion + Plus + Non + Aucune occurrence trouvée + Sauvegarder le support de stockage avec un PC peut le rendre inaccessible + Support de stockage absent + Aucun texte sélectionné + Non compatible + Rien à sauvegarder + OK + Table des matières + [Niveau supérieur] + %1$s%2$s : %3$s + Imprimer + L\'impression a échoué + Sauvegarder + Rechercher + Rechercher en arrière + Rechercher document + Rechercher en avant + Chercher&#8230 ; + Sélectionner + Sélectionner le texte + Rayer + Texte introuvable + Surligner et autoriser les liens + Souligner + Oui + diff --git a/platform/android/viewer/res/values-hi/strings.xml b/platform/android/viewer/res/values-hi/strings.xml new file mode 100644 index 00000000..4d09a972 --- /dev/null +++ b/platform/android/viewer/res/values-hi/strings.xml @@ -0,0 +1,54 @@ + + + स्वीकार करें + MuPDF + रद्द करें + बफ़र खोल नहीं सके + दस्तावेज़ खोल नहीं सके + दस्तावेज़ नहीं खोल सके: %1$s + फ़ाइल खोल नहीं सके: %1$s + मान चुनें + क्लिपबोर्ड में कॉप कर दिया गया + कॉपी करें + पाठ कॉपी करें + पाठ को क्लिपबोर्ड में कॉपी करें + हटाएँ + खारिज करें + दस्तावेज़ में परिवर्तन हैं। उन्हें सहेजें? + एनोटेशन बनाएँ + एनोटेशनों को संपादित करें + पासवर्ड दर्ज करें + रीफ़्लो मोड में प्रवेश कर रहे हैं + पाठ फ़ील्ड को भरें + इस समय इस फ़ॉर्मेट को समर्थन नहीं प्राप्त है + हाइलाइट करें + स्याही + रीफ़्लो मोड को छोड़ रहे हैं + और भी + नहीं + यह और कहीं नहीं मिला + संग्रह माध्यम को पीसी के साथ साझा करने से उस तक पहुँचना मुश्किल हो सकता है + संग्रह माध्यम मौजूद नहीं है + कोई भी पाठ नहीं चुना गया है + असमर्थित + सहेजने के लिए कुछ नहीं है + ठीक है + विषय सूची + [एक स्तर ऊपर] + %1$s%2$s:%3$s + मुद्रित करें + मुद्रण विफल हुआ + सहेजें + खोजें + पीछे की ओर खोजें + दस्तावेज़ में खोजें + आगे की ओर खोजें + &#8230 को खोज रहे हैं; + चुनें + पाठ चुनें + काटें + पाठ नहीं मिला + लिंकों को हाइलाइट और सक्षम करें + रेखांकित करें + हाँ + diff --git a/platform/android/viewer/res/values-hu/strings.xml b/platform/android/viewer/res/values-hu/strings.xml new file mode 100644 index 00000000..1533b65a --- /dev/null +++ b/platform/android/viewer/res/values-hu/strings.xml @@ -0,0 +1,54 @@ + + + Elfogadás + MuPDF + Mégse + A puffert nem lehet megnyitni + A dokumentumot nem lehet megnyitni + A dokumentumot nem lehet megnyitni: %1$s + A fájlt nem lehet megnyitni: %1$s + Érték kiválasztása + A vágólapra másolva + Másolás + szöveg másolása + Szöveg másolása a vágólapra + Törlés + Bezárás + A dokumentum módosítva lett. Menti a változtatásokat? + Jegyzet rajzolása + Jegyzetek szerkesztése + Jelszó megadása + Belépés az újrarendezési módba + Szövegmező kitöltése + A formátum jelenleg nem támogatott + Kiemelés + Kézírás + Kilépés az újrarendezési módból + Több + Nem + Nincsenek további találatok + Az adathordozó a PC-vel való megosztás esetén elérhetetlenné válhat + Nincs jelen adathordozó + Nincs kijelölt szöveg + Nem támogatott + Nem kell semmit menteni + OK + Tartalomjegyzék + [Egy szinttel feljebb] + %1$s %2$s: %3$s + Nyomtatás + Nyomtatás sikertelen + Mentés + Keresés + Keresés visszafelé + Dokumentum keresése + Keresés előrefelé + Keresés:&#8230; + Kijelölés + Szöveg kijelölése + Áthúzás + Szöveg nem található + Kiemelés és linkek engedélyezése + Aláhúzás + Igen + diff --git a/platform/android/viewer/res/values-in/strings.xml b/platform/android/viewer/res/values-in/strings.xml new file mode 100644 index 00000000..f90d1b3b --- /dev/null +++ b/platform/android/viewer/res/values-in/strings.xml @@ -0,0 +1,54 @@ + + + Terima + MuPDF + Batal + Tidak bisa membuka penyangga + Tidak bisa membuka dokumen + Tidak bisa membuka dokumen: %1$s + Tidak bisa membuka berkas: %1$s + Pilih nilai + Disalin ke papan klip + Salin + Salin teks + Salin teks ke papan klip + Hapus + Hilangkan + Dokumen telah berubah. Simpan perubahan? + Gambar anotasi + Sunting anotasi + Masukkan kata sandi + Masuk mode alir-ulang + Isi bidang teks + Format ini tidak didukung + Sorotan + Tinta + Tinggalkan mode alir-ulang + Selengkapnya + Tidak + Tidak ditemukan kejadian lain + Berbagi media penyimpanan dengan PC dapat membuatnya tidak bisa diakses + Media penyimpanan tidak ada + Tidak ada teks yang dipilih + Tidak didukung + Tidak ada yang disimpan + Oke + Daftar Isi + [Naik satu tingkat] + %1$s %2$s: %3$s + Cetak + Pencetakan gagal + Simpan + Cari + Cari mundur + Cari dokumen + Cari maju + Mencari… + Pilih + Pilih teks + Gagal + Teks tidak ditemukan + Sorot dan aktifkan tautan + Garis bawah + Ya + diff --git a/platform/android/viewer/res/values-it/strings.xml b/platform/android/viewer/res/values-it/strings.xml new file mode 100644 index 00000000..25cf56dd --- /dev/null +++ b/platform/android/viewer/res/values-it/strings.xml @@ -0,0 +1,54 @@ + + + Accetta + MuPDF + Annulla + Impossibile aprire buffering + Impossibile aprire documento + Impossibile aprire documento: %1$s + Impossibile aprire file: %1$s + Scegli valore + Copiato negli appunti + Copia + copia testo + Copia testo negli appunti + Elimina + Ignora + Il documento contiene modifiche. Salvare? + Disegna annotazione + Modifica annotazione + Inserisci password + Inserimento modalità di adattamento dinamico del contenuto + Riempi il campo di testo + Formato attualmente non supportato + Evidenzia + Inchiostro + Abbandono della modalità di adattamento dinamico del contenuto + Altro + No + Nessun\'altra occorrenza trovata + La condivisione del supporto di archiviazione con un PC può renderlo inaccessibile + Supporto di archiviazione non presente + Nessun testo selezionato + Non supportato + Niente da salvare + Ok + Sommario + [Su di un livello] + %1$s %2$s: %3$s + Stampa + Stampa non riuscita + Salva + Cerca + Cerca indietro + Cerca documento + Cerca avanti + Ricerca... + Seleziona + Seleziona testo + Barrato + Testo non trovato + Evidenzia e abilita link + Sottolinea + + diff --git a/platform/android/viewer/res/values-iw/strings.xml b/platform/android/viewer/res/values-iw/strings.xml new file mode 100644 index 00000000..d259ae76 --- /dev/null +++ b/platform/android/viewer/res/values-iw/strings.xml @@ -0,0 +1,54 @@ + + + קבל + MuPDF + בטל + אין אפשרות לפתוח מאגר + אין אפשרות לפתוח מסמך + אין אפשרות לפתוח מסמך: %1$s + אין אפשרות לפתוח קובץ: %1$s + בחר ערך + הועתק ללוח + העתק + העתק טקסט + העתק טקסט ללוח + מחק + התעלם + קיימים שינויים במסמך. לשמור אותם? + רשום ביאור + ערוך ביאורים + הזן סיסמה + כניסה למצב הזרמה מחדש + מלא את שדה הטקסט + תבנית לא נתמכת כעת + הבלטה + דיו + יציאה ממצב הזרמה מחדש + עוד + לא + לא עוד + שיתוף מדיית האחסון עם מחשב עשויה להפוך אותה לבלתי נגישה + מדיית אחסון לא קיימת + לא נבחר טקסט + לא נתמך + אין מה לשמור + בסדר + תוכן העניינים + [למעלה ברמה אחת] + %1$s %2$s: %3$s + הדפס + ההדפסה נכשלה + שמור + חפש + חפש אחורה + חפש במסמך + חפש קדימה + מחפש&#8230; + בחר ערך + בחר טקסט + הדגש + לא נמצא טקסט + הבלט ואפשר קישורים + קו תחתון + כן + diff --git a/platform/android/viewer/res/values-ja/strings.xml b/platform/android/viewer/res/values-ja/strings.xml new file mode 100644 index 00000000..8ceb5e09 --- /dev/null +++ b/platform/android/viewer/res/values-ja/strings.xml @@ -0,0 +1,54 @@ + + + 承諾する + MuPDF + キャンセル + バッファーを開けません + ドキュメントを開けません + 次のドキュメントを開けません:%1$s + 次のファイルを開けません: %1$s + バリューを選択してください + クリップボードにコピーされました + コピー + テキストをコピー + テキストをクリップボードにコピー + 削除 + 却下 + ドキュメントは変更されました。保存しますか? + 注釈を挿入する + 注釈を編集する + パスワードを入力する + リフローモードを開始する + テキストフィールドに書き込む + このフォーマットは現在サポートされていません + ハイライト + インク + リフローモードを終了する + もっと + いいえ + 他にオカレンスは見つかりませんでした + 記憶媒体をPCとシェアするとアクセスできなくなる可能性があります + 記憶媒体が見つかりません + テキストが選択されていません + サポートされていません + 保存するものがありません + 了解 + 目次 + [一つ上位のレベル] + %1$s %2$s: %3$s + 印刷 + 印刷に失敗しました + 保存 + 検索 + 逆方向検索 + ドキュメントを検索する + 順方向検索 + 検索中 + 選択 + テキストを選択する + 取り消し線を引く + テキストが見つかりません + ハイライトしてリンクを有効にする + 下線を引く + はい + diff --git a/platform/android/viewer/res/values-ko/strings.xml b/platform/android/viewer/res/values-ko/strings.xml new file mode 100644 index 00000000..b52a2f5a --- /dev/null +++ b/platform/android/viewer/res/values-ko/strings.xml @@ -0,0 +1,54 @@ + + + 수락 + MuPDF + 취소 + 버퍼 열 수 없음 + 문서 열 수 없음 + 문서 열 수 없음: %1$s + 파일 열 수 없음: %1$s + 값 선택 + 클립보드로 복사됨 + 복사 + 텍스트 복사 + 클립보드로 텍스트 복사 + 삭제 + 무시 + 문서에 변경사항이 있습니다. 저장? + 주석달기 + 주석 편집 + 패스워드 입력 + 리플로우 모드 시작 + 텍스트 입력란에 기입하십시오. + 현재 지원되지 않는 포맷 + 주요기능 + 잉크 + 리플로우 모드 해제 + 기타 + 아니오 + 발견된 추가 발생 없음 + PC와 스토리지 미디어를 공유하면 액세스할 수 없습니다. + 스토리지 미디어 없음 + 선택된 텍스트 없음 + 지원 안됨 + 저장 대상 없음 + 확인 + 목차 + [레벨 한 단계 상승] + %1$s %2$s: %3$s + 인쇄 + 인쇄 실패 + 저장 + 검색 + 뒤로 검색 + 문서 검색 + 앞으로 검색 + 검색 중&#8230; + 선택 + 텍스트 선택 + 삭제 + 발견된 텍스트 없음 + 하이라이트 및 링크 활성화 + 밑줄 + + diff --git a/platform/android/viewer/res/values-lt/strings.xml b/platform/android/viewer/res/values-lt/strings.xml new file mode 100644 index 00000000..f66ba305 --- /dev/null +++ b/platform/android/viewer/res/values-lt/strings.xml @@ -0,0 +1,54 @@ + + + Priimti + „MuPDF“ + Atšaukti + Nepavyksta atverti buferinės atmintinės + Nepavyksta atverti dokumento + Nepavyksta atverti dokumento: %1$s + Nepavyksta atverti failo: %1$s + Pasirinkti vertę + Nukopijuota į iškarpinę + Kopijuoti + kopijuoti tekstą + Kopijuoti tekstą į iškarpinę + Naikinti + Atmesti + Dokumente yra pakeitimų. Ar juos įrašyti? + Braižyti anotaciją + Redaguoti anotacijas + Įvesti slaptažodį + Pereinama į pertvarkymo režimą + Užpildyti teksto lauką + Formatas šiuo metu nedera + Pažymėti + Rašalas + Išeinama iš pertvarkymo režimo + Daugiau + Ne + Daugiau įrašų nerasta + Pabendrinus laikmeną su kompiuteriu, ji gali tapti nebepasiekiama + Laikmenos nėra + Neparinktas tekstas + Nedera + Nėra ką įrašyti + Gerai + Turinys + [Vienu lygiu aukštyn] + %1$s %2$s: %3$s + Spausdinti + Išspausdinti nepavyko + Įrašyti + Ieškoti + Ieškoti atgal + Ieškoti dokumente + Ieškoti pirmyn + Ieškoma&#8230; + Pasirinkti + Pasirinkti tekstą + Išbraukti + Teksto nerasta + Pažymėti ir įjungti nuorodas + Pabraukti + Taip + diff --git a/platform/android/viewer/res/values-ms/strings.xml b/platform/android/viewer/res/values-ms/strings.xml new file mode 100644 index 00000000..64541e6f --- /dev/null +++ b/platform/android/viewer/res/values-ms/strings.xml @@ -0,0 +1,54 @@ + + + Terima + MuPDF + Batal + Tidak boleh membuka penimbal + Tidak boleh membuka dokumen + Tidak boleh membuka dokumen: %1$s + Tidak boleh membuka fail: %1$s + Pilih nilai + Disalin ke papan klip + Salin + salin teks + Salin teks ke papan klip + Padam + Singkir + Dokumen mempunyai perubahan. Simpankannya? + Lakarkan catatan + Suntingkan catatan + Masukkan kata laluan + Memasuki mod penyusunan semula + Mengisi medan teks + Format buat masa ini tidak disokong + Serlahkan + Dakwat + Meninggalkan mod penyusunan semula + Lagi + Tidak + Tiada kejadian lanjut ditemui + Berkongsi media storan dengan PC boleh menjadikannya tidak dapat dicapai + Media storan tidak wujud + Tiada teks dipilih + Tidak disokong + Tiada apa untuk disimpan + Okey + Jadual Kandungan + [Naik satu tahap] + %1$s %2$s: %3$s + Cetak + Gagal dicetak + Simpan + Carian + Carian ke belakang + Carian dokumen + Carian ke depan + Mencari&#8230; + Pilih + Pilih teks + Mansuhkan + Teks tidak ditemui + Serlahkan dan dayakan pautan + Gariskan + Ya + diff --git a/platform/android/viewer/res/values-nl/strings.xml b/platform/android/viewer/res/values-nl/strings.xml new file mode 100644 index 00000000..21945c86 --- /dev/null +++ b/platform/android/viewer/res/values-nl/strings.xml @@ -0,0 +1,54 @@ + + + Accepteren + MuPDF + Annuleren + Buffer kan niet geopend worden + Document kan niet geopend worden + Document kan niet geopend worden: %1$s + Bestand kan niet geopend worden : %1$s + Kies waarde + Gekopieerd naar klembord + Kopiëren + tekst kopiëren + Tekst kopiëren naar klembord + Verwijderen + Afwijzen + Het document is gewijzigd. Opslaan? + Opmerking tekenen + Opmerkingen bewerken + Voer wachtwoord in + Conversiemodus wordt geopend + Vul het tekstveld in + Formaat wordt momenteel niet ondersteund + Markeren + Inkten + Conversiemodus wordt beëindigd + Meer + Nee + Geen andere resultaten gevonden + Het opslagmedium kan ontoegankelijk worden als het met een pc wordt gedeeld + Geen opslagmedium aanwezig + Geen tekst geselecteerd + Niet ondersteund + Niets om op te slaan + Oké + Inhoudsopgave + [Een niveau hoger] + %1$s %2$s: %3$s + Afdrukken + Afdrukken mislukt + Opslaan + Zoeken + Achterstevoren zoeken + Document doorzoeken + Vooruit zoeken + Aan het zoeken … + Selecteren + Tekst selecteren + Doorhalen + Tekst niet gevonden + Markeren en koppelingen inschakelen + Onderstrepen + Ja + diff --git a/platform/android/viewer/res/values-no/strings.xml b/platform/android/viewer/res/values-no/strings.xml new file mode 100644 index 00000000..31bd6bf0 --- /dev/null +++ b/platform/android/viewer/res/values-no/strings.xml @@ -0,0 +1,54 @@ + + + Aksepter + MuPDF + Avbryt + Kan ikke åpne buffer + Kan ikke åpne dukumentet + Kan ikke åpne dokumentet: %1$s + Kan ikke åpne filen: %1$s + Velg verdi + Kopiert til utklippstavlen + Kopier + kopier tekst + Kopier teksten til utklippstavlen + Slett + Avvis + Det er endringer i dokumentet. Lagre dem? + Lag merknad + Rediger merknader + Skriv inn passord + Bytter til konverteringsmodus + Fyll ut tekstfeltet + Formatet er ikke støttet for øyeblikket + Uthev + Håndskrift + Går ut av konverteringsmodus + Mer + Nei + Ingen flere hendelser funnet + Deling av lagringsmedia med en PC kan gjøre det utilgjengelig + Lagringsmedia ikke til stede + Ingen tekst er valgt + Ikke støttet + Ingenting å lagre + Ok + Innholdsfortegnelse + [OPP ett nivå] + %1$s%2$s%3$s + Skriv ut + Kunne ikke skrive ut + Lagre + Søk + Søk bakover + Søk i dokument + Søk framover + Søker&#8230; + Velg + Valgt tekst + Gjennomstreking + Teksten ble ikke funnet + Uthev og aktiver koblinger + Understrek + Ja + diff --git a/platform/android/viewer/res/values-pl/strings.xml b/platform/android/viewer/res/values-pl/strings.xml new file mode 100644 index 00000000..42511e42 --- /dev/null +++ b/platform/android/viewer/res/values-pl/strings.xml @@ -0,0 +1,54 @@ + + + Zaakceptuj + MuPDF + Anuluj + Nie można otworzyć bufora + Nie można otworzyć dokumentu + Nie można otworzyć dokumentu: %1$s + Nie można otworzyć pliku: %1$s + Wybierz wartość + Skopiowano do schowka + Kopiuj + kopiuj tekst + Kopiuj tekst do schowka + Usuń + Odrzuć + W dokumencie dokonano zmian. Czy chcesz je zapisać? + Sporządź notatkę + Edytuj notatki + Wprowadź hasło + Włączanie trybu zawijania tekstu + Wypełnij pole tekstowe + Format obecnie nieobsługiwany + Podświetl + Atrament + Wyłączanie trybu zawijania tekstu + Więcej + Nie + Nie znaleziono więcej wystąpień + Współdzielenie nośnika danych z komputerem PC może sprawić, że będzie niedostępny + Nośnik danych niedostępny + Nie wybrano tekstu + Nieobsługiwany + Nie ma nic do zapisania + OK + Spis treści + [W górę o jeden poziom] + %1$s %2$s: %3$s + Drukuj + Drukowanie nieudane + Zapisz + Szukaj + Szukaj z tyłu + Szukaj w dokumencie + Szukaj z przodu + Wyszukiwanie&#8230; + Wybierz + Wybierz tekst + Przekreślenie + Nie znaleziono tekstu + Podświetl i aktywuj linki + Podkreślenie + Tak + diff --git a/platform/android/viewer/res/values-pt/strings.xml b/platform/android/viewer/res/values-pt/strings.xml new file mode 100644 index 00000000..15f86283 --- /dev/null +++ b/platform/android/viewer/res/values-pt/strings.xml @@ -0,0 +1,54 @@ + + + Aceitar + MuPDF + Cancelar + Não é possível abrir a memória intermédia + Não é possível abrir o documento + Não é possível abrir o documento: %1$s + Não é possível abrir o ficheiro: %1$s + Escolha um valor + Copiado para a área de transferência + Copiar + copiar o texto + Copiar o texto para a área de transferência + Eliminar + Desistir + Há alterações ao documento. Deseja guardá-las? + Adicionar anotação + Editar anotações + Escrever a palavra-passe + A entrar no modo de refluxo + Preencher o campo de texto + Esse formato não é atualmente suportado + Destacar + Tinta + A sair do modo de refluxo + Mais + Não + Não foram encontradas mais ocorrências + Partilhar o dispositivo de armazenamento com um PC poderá torná-lo inacessível + O dispositivo de armazenamento não está presente + Não há texto selecionado + Não suportado + Não há nada para guardar + Okay + Índice + [subir um nível] + %1$s%2$s: %3$s + Imprimir + Falha na Impressão + Guardar + Pesquisar + Pesquisar para trás + Pesquisar no documento + Pesquisar para a frente + A pesquisar&#8230; + Selecionar + Selecionar o texto + Rasurado + Texto não encontrado + Destacar e permitir links + Sublinhado + Sim + diff --git a/platform/android/viewer/res/values-ru/strings.xml b/platform/android/viewer/res/values-ru/strings.xml new file mode 100644 index 00000000..7cc35187 --- /dev/null +++ b/platform/android/viewer/res/values-ru/strings.xml @@ -0,0 +1,54 @@ + + + Принять + MuPDF + Отмена + Невозможно открыть буфер + Невозможно открыть документ + Невозможно открыть документ: %1$s + Невозможно открыть файл: %1$s + Выберите значение + Скопировано в буфер + Копировать + копировать текст + Копировать текст в буфер + Удалить + Пропустить + Документ был изменен. Сохранить изменения? + Создать аннтоацию + Редактировать аннотации + Введите пароль + Переход в режим Reflow + Заполните текстовое поле + Формат не поддерживается + Выделить + Чернила + Выход из режима Reflow + Еще + Нет + Других ошибок не зафиксировано + Подключение компьютеров к хранилищу данных может привести к потере доступа к хранилищу + Хранилище данных отсутствует + Текст не выбран + Не поддерживается + Не выбраны файлы для сохранения + ОК + Содержание + [Вверх на один уровень] + %1$s %2$s: %3$s + Печать + Печать не выполнена + Сохранить + Поиск + Искать в предыдущей части документа + Искать в документе + Искать в остальной части документа + Поиск&#8230; + Выбор + Выбрать текст + Зачеркнуть + Текст не найден + Выделить и включить ссылки + Подчеркнуть + Да + diff --git a/platform/android/viewer/res/values-sk/strings.xml b/platform/android/viewer/res/values-sk/strings.xml new file mode 100644 index 00000000..e11737ef --- /dev/null +++ b/platform/android/viewer/res/values-sk/strings.xml @@ -0,0 +1,54 @@ + + + Prijať + MuPDF + Zrušiť + Buffer sa nedá otvoriť + Dokument sa nedá otvoriť + Nedá sa otvoriť dokument: %1$s + Nedá sa otvoriť súbor: %1$s + Vyberte si hodnotu + Skopírované do vyrovnávacej pamäti + Kopírovať + kopírovať text + Kopírovať text do vyrovnávacej pamäti + Zmazať + Zrušiť + Dokument bol zmený. Uložiť zmeny? + Zostaviť anotáciu + Upraviť anotácie + Zadať heslo + Vstupujem do režimu opätovného nalievania + Vyplniť textové pole + Tento formát momentálne nepodporujem + Zvýrazniť + Atrament + Vystupujem z režimu opätovného nalievania + Viac + Nie + Viac príkladov sa nenašlo + Zdieľanie úložného média s PC môže znemožniť prístup + Nie je tu úložné médium + Žiadny text nie je vybraný + Nepodporované + Niet čo uložiť + Dobre + Obsah + [O úroveň vyššie] + %1$s %2$s: %3$s + Tlačiť + Tlačenie zlyhalo + Uložiť + Hľadať + Hľadať spätne + Hľadať v dokumente + Hľadať dopredu + Hľadám&#8230; + Vybrať + Vybrať text + Preškrtnúť + Text sa nenašiel + Zvýrazniť a zapnúť linky + Podčiarknúť + Áno + diff --git a/platform/android/viewer/res/values-sv/strings.xml b/platform/android/viewer/res/values-sv/strings.xml new file mode 100644 index 00000000..61d14d05 --- /dev/null +++ b/platform/android/viewer/res/values-sv/strings.xml @@ -0,0 +1,54 @@ + + + Acceptera + MuPDF + Avbryt + Kan inte öppna buffer + Kan inte öppna dokument + Kan inte öppna dokument: %1$s + Kan inte öppna fil: %1$s + Välj värde + Kopierat till klippbordet + Kopiera + kopiera text + Kopiera text till klippbordet + Ta bort + Avfärda + Dokumentet har ändrats. Spara ändringar? + Rita annotation + Ändra annotation + Fyll i lösenord + Aktiverar reflow-läge + Fyll i textfält + Formatat stöds inte för närvarande + Markera + Bläck + Lämnar reflow-läge + Mer + Nej + Inga flera förekomster hittades + Att dela lagringsmediet med en PC kan göra den oåtkomlig + Lagringsmedia finns inte + Ingen text har valts + Stöds ej + Inget att spara + OK + Innehållsförteckning + [Upp en nivå] + %1$s %2$s: %3$s + Skriv ut + Utskrift misslyckades + Spara + Sök + Sök bakåt + Sök dokument + Sök framåt + Letar&#8230; + Välj + Välj text + Stryk + Text hittades ej + Markera och aktivera länkar + Understryk + Ja + diff --git a/platform/android/viewer/res/values-th/strings.xml b/platform/android/viewer/res/values-th/strings.xml new file mode 100644 index 00000000..e6827125 --- /dev/null +++ b/platform/android/viewer/res/values-th/strings.xml @@ -0,0 +1,54 @@ + + + ยอมรับ + MuPDF + ยกเลิก + ไม่สามารถเปิดบัฟเฟอร์ + ไม่สามารถเปิดเอกสาร + ไม่สามารถเปิดเอกสาร: %1$s + ไม่สามารถเปิดไฟล์: %1$s + เลือกค่า + คัดลอกไปที่คลิปบอร์ดแล้ว + คัดลอก + คัดลอกข้อความ + คัดลอกข้อความไปที่คลิปบอร์ด + ลบ + เลิกใช้ + เอกสารมีการเปลี่ยนแปลง ต้องการบันทึกหรือไม่ + เขียนคำอธิบายประกอบ + แก้ไขคำอธิบายประกอบ + ป้อนรหัสผ่าน + เข้าสู่โหมดเรียงหน้ากระดาษใหม่ + เติมในช่องข้อความ + ไม่รองรับรูปแบบในขณะนี้ + ไฮไลท์ + หมึก + ออกจากโหมดเรียงหน้ากระดาษใหม่ + เพิ่มเติม + ไม่ + ไม่พบเหตุการณ์ที่เกิดขึ้นเพิ่มเติม + การแบ่งปันสื่อจัดเก็บข้อมูลกับพีซีสามารถทำให้สื่อจัดเก็บข้อมูลไม่สามารถเข้าถึงได้ + สื่อเก็บข้อมูลไม่ปรากฏ + ไม่มีข้อความที่เลือก + ไม่รองรับ + ไม่มีอะไรให้บันทึก + ตกลง + สารบัญ + [ขึ้นหนึ่งระดับ] + %1$s %2$s: %3$s + พิมพ์ + พิมพ์ล้มเหลว + บันทึก + ค้นหา + ค้นหาย้อนกลับ + ค้นหาเอกสาร + ค้นหาไปข้างหน้า + กำลังค้นหา&#8230; + เลือก + เลือกข้อความ + ขีดทับ + ไม่พบข้อความ + ไฮไลท์และเปิดใช้งานลิงก์ + ขีดเส้นใต้ + ใช่ + diff --git a/platform/android/viewer/res/values-tl/strings.xml b/platform/android/viewer/res/values-tl/strings.xml new file mode 100644 index 00000000..39611fcb --- /dev/null +++ b/platform/android/viewer/res/values-tl/strings.xml @@ -0,0 +1,54 @@ + + + Tanggapin + MuPDF + Kanselahin + Hindi mabuksan ang buffer + Hindi mabuksan ang dokumento + Hindi mabuksan ang dokumentong: %1$s + Hindi mabuksan ang file na: %1$s + Pumili ng halaga + Kinopya sa clipboard + Kopyahin + kopyahin ang teksto + Kopyahin ang teksto sa clipboard + Alisin + Umalis + May mga pagbabago sa dokumento. I-save ang mga ito? + Gumuhit ng anotasyon + I-edit ang mga anotasyon + Ilagay ang password + Pumapasok sa reflow mode + Punan ang puwang para sa teksto + Ang format ay kasalukuyang hindi gumagana dito + I-highlight + Lagdaan (Ink) + Umaalis sa reflow mode + Higit pa + Hindi + Walang nahanap na karagdagang paglitaw + Ang pagbabahagi ng storage media sa isang PC ay gagawin itong hindi magagamit + Walang storage media + Walang piniling teksto + Hindi gumagana dito + Walang ise-save + Okay + Talaan ng Nilalaman + [Umakyat ng isang antas] + %1$s %2$s: %3$s + I-print + Hindi nai-print + I-save + Maghanap + Maghanap pabalik + Maghanap sa dokumento + Maghanap nang pasulong + Hinahanap ang&#8230; + Piliin + Piliin ang teksto + Guhitan ang teksto (strike-out) + Hindi nahanap ang teksto + I-highlight at paganahin ang mga link + Guhitan + Oo + diff --git a/platform/android/viewer/res/values-tr/strings.xml b/platform/android/viewer/res/values-tr/strings.xml new file mode 100644 index 00000000..c64ab7ce --- /dev/null +++ b/platform/android/viewer/res/values-tr/strings.xml @@ -0,0 +1,54 @@ + + + Kabul et + MuPDF + İptal et + Arabellek açılamıyor + Belge açılamıyor + Belge açılamıyor: %1$s + Dosya açılamıyor: %1$s + Değeri seç + Panoya kopyalandı + Kopyala + metni kopyala + Metni panoya kopyala + Sil + Bırak + Belgede değişiklikler var. Kaydetmek istiyor musunuz? + Ek açıklama çiz + Ek açıklamalar düzenle + Şifreyi gir + Yeniden akma moduna giriyor + Metin alanını doldurun + Bu format şu an için desteklenmiyor + Vurgula + Mürekkep + Yeniden akma modundan çıkılıyor + Daha fazla + Hayır + Daha fazla öğe bulunamadı + Depolama ortamının bilgisayar ile paylaşımı onu erişilmez yapabilir + Depolama ortamı bulunmuyor + Seçili metin bulunmuyor + Desteklenmiyor + Kaydedecek bir şey yok + Tamam + İçindekiler Tablosu + [Bir seviye üste çık] + %1$s %2$s: %3$s + Yazdır + Yazdırma başarısız oldu + Kaydet + Ara + Geriye doğru ara + Belge ara + İleriye doğru ara + Aranıyor&#8230; + Seç + Metin seç + Üstünü çiz + Metin bulunamadı + Bağlantıları vurgula ve etkinleştir + Altını çiz + Evet + diff --git a/platform/android/viewer/res/values-zh-rTW/strings.xml b/platform/android/viewer/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..4cd89709 --- /dev/null +++ b/platform/android/viewer/res/values-zh-rTW/strings.xml @@ -0,0 +1,54 @@ + + + 同意 + MuPDF + 取消 + 未能開啟緩衝 + 未能開啟文件 + 未能開啟文件: %1$s + 未能開啟檔案%1$s + 選擇數值 + 複製至剪貼簿 + 複製 + 複製文字 + 複製文字至剪貼簿 + 刪除 + 取消 + 你需要儲存已編輯的文件嗎? + 繪畫註釋 + 編輯註釋 + 輸入密碼 + 根據螢幕大小顯示 + 填寫文字欄 + 暫時不支援此格式 + 標示重點 + 墨水 + 不根據螢幕大小顯示 + 更多 + 沒有 + 沒有相符項目 + 未能與電腦分享存放裝置 + 沒有存放裝置 + 沒有選擇文字 + 不支援 + 沒有資料儲存 + 完成 + 目錄 + [升一級] + %1$s%2$s%3$s + 列印 + 列印失敗 + 儲存 + 搜尋 + 往後搜尋 + 搜尋文件 + 往前搜尋 + 搜尋中&#8230; + 選擇 + 選擇文字 + 刪除線 + 未能找到文字 + 標示及允許連結 + 在下面劃線 + + diff --git a/platform/android/viewer/res/values-zh/strings.xml b/platform/android/viewer/res/values-zh/strings.xml new file mode 100644 index 00000000..60fcbb82 --- /dev/null +++ b/platform/android/viewer/res/values-zh/strings.xml @@ -0,0 +1,54 @@ + + + 接受 + MuPDF + 取消 + 无法打开缓冲器 + 无法打开文档 + 无法打开文档: %1$s + 无法打开文件:%1$s + 选择值 + 已复制到剪贴板 + 复制 + 复制文本 + 将文本复制到剪贴板 + 删除 + 解除 + 文档已变更,保存变更吗? + 作批注 + 编辑批注 + 输入密码 + 输入重排模式 + 填充文本字段 + 当前不支持此格式 + 高亮 + 墨迹 + 正在离开重排模式 + 更多 + + 未发现更多实例。 + 存储介质在设备和 PC 上共同使用,会导致该存储介质在设备上无法被访问 + 没有存储介质 + 未选择文本 + 不被支持 + 没有要保存的内容 + 确定 + 目录 + [向上一级] + %1$s%2$s:%3$s + 打印 + 未能打印 + 保存 + 搜索 + 向后搜索 + 搜索文档 + 向前搜索 + 正在搜索… + 选择 + 选择文本 + 删除线 + 未发现文本 + 高亮并启用墨迹 + 下划线 + + diff --git a/platform/android/viewer/res/values/colors.xml b/platform/android/viewer/res/values/colors.xml new file mode 100644 index 00000000..ecd1519d --- /dev/null +++ b/platform/android/viewer/res/values/colors.xml @@ -0,0 +1,16 @@ + + + #404040 + #C0000000 + #C0202020 + #C0202020 + #00000000 + #FF2572AC + #FFFFFF + #FFFFFF + #000000 + #2572AC + #000000 + #2572AC + #FFFFFF + diff --git a/platform/android/viewer/res/values/strings.xml b/platform/android/viewer/res/values/strings.xml new file mode 100644 index 00000000..269b47ee --- /dev/null +++ b/platform/android/viewer/res/values/strings.xml @@ -0,0 +1,58 @@ + + + MuPDF + 1.8 (git build) + Storage media not present + Sharing the storage media with a PC can make it inaccessible + Cancel + Search backwards + Search forwards + Search document + %1$s %2$s: %3$s + Table of Contents + Enter password + Text not found + Searching… + Highlight and enable links + No further occurrences found + Select + Search + Copy + Strike-out + Delete + Highlight + Underline + Edit annotations + Ink + Save + Proof + Separation + Print + Dismiss + [Up one level] + Yes + No + Entering reflow mode + Leaving reflow mode + Print failed + Select text + Copied to clipboard + No text selected + Draw annotation + Nothing to save + Document has changes. Save them? + Cannot open document + Cannot open document: %1$s + Cannot open file: %1$s + Cannot open buffer + Fill out text field + Okay + Choose value + Not supported + Copy text to the clipboard + More + Accept + copy text + Format currently not supported + Toggle reflow mode + diff --git a/platform/android/viewer/res/values/styles.xml b/platform/android/viewer/res/values/styles.xml new file mode 100644 index 00000000..ade851dd --- /dev/null +++ b/platform/android/viewer/res/values/styles.xml @@ -0,0 +1,5 @@ + + + diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/Annotation.java b/platform/android/viewer/src/com/artifex/mupdfdemo/Annotation.java new file mode 100644 index 00000000..cf915524 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/Annotation.java @@ -0,0 +1,18 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class Annotation extends RectF { + enum Type { + TEXT, LINK, FREETEXT, LINE, SQUARE, CIRCLE, POLYGON, POLYLINE, HIGHLIGHT, + UNDERLINE, SQUIGGLY, STRIKEOUT, STAMP, CARET, INK, POPUP, FILEATTACHMENT, + SOUND, MOVIE, WIDGET, SCREEN, PRINTERMARK, TRAPNET, WATERMARK, A3D, UNKNOWN + } + + public final Type type; + + public Annotation(float x0, float y0, float x1, float y1, int _type) { + super(x0, y0, x1, y1); + type = _type == -1 ? Type.UNKNOWN : Type.values()[_type]; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java b/platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java new file mode 100644 index 00000000..4f06ea41 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/ArrayDeque.java @@ -0,0 +1,855 @@ +/* + * Written by Josh Bloch of Google Inc. and released to the public domain, + * as explained at http://creativecommons.org/publicdomain/zero/1.0/. + */ + +package com.artifex.mupdfdemo; + +import java.util.AbstractCollection; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Stack; + +// BEGIN android-note +// removed link to collections framework docs +// END android-note + +/** + * Resizable-array implementation of the {@link Deque} interface. Array + * deques have no capacity restrictions; they grow as necessary to support + * usage. They are not thread-safe; in the absence of external + * synchronization, they do not support concurrent access by multiple threads. + * Null elements are prohibited. This class is likely to be faster than + * {@link Stack} when used as a stack, and faster than {@link LinkedList} + * when used as a queue. + * + *

Most ArrayDeque operations run in amortized constant time. + * Exceptions include {@link #remove(Object) remove}, {@link + * #removeFirstOccurrence removeFirstOccurrence}, {@link #removeLastOccurrence + * removeLastOccurrence}, {@link #contains contains}, {@link #iterator + * iterator.remove()}, and the bulk operations, all of which run in linear + * time. + * + *

The iterators returned by this class's iterator method are + * fail-fast: If the deque is modified at any time after the iterator + * is created, in any way except through the iterator's own remove + * method, the iterator will generally throw a {@link + * ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the + * future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw ConcurrentModificationException on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

This class and its iterator implement all of the + * optional methods of the {@link Collection} and {@link + * Iterator} interfaces. + * + * @author Josh Bloch and Doug Lea + * @since 1.6 + * @param the type of elements held in this collection + */ +public class ArrayDeque extends AbstractCollection + implements Deque, Cloneable, java.io.Serializable +{ + /** + * The array in which the elements of the deque are stored. + * The capacity of the deque is the length of this array, which is + * always a power of two. The array is never allowed to become + * full, except transiently within an addX method where it is + * resized (see doubleCapacity) immediately upon becoming full, + * thus avoiding head and tail wrapping around to equal each + * other. We also guarantee that all array cells not holding + * deque elements are always null. + */ + private transient Object[] elements; + + /** + * The index of the element at the head of the deque (which is the + * element that would be removed by remove() or pop()); or an + * arbitrary number equal to tail if the deque is empty. + */ + private transient int head; + + /** + * The index at which the next element would be added to the tail + * of the deque (via addLast(E), add(E), or push(E)). + */ + private transient int tail; + + /** + * The minimum capacity that we'll use for a newly created deque. + * Must be a power of 2. + */ + private static final int MIN_INITIAL_CAPACITY = 8; + + // ****** Array allocation and resizing utilities ****** + + /** + * Allocate empty array to hold the given number of elements. + * + * @param numElements the number of elements to hold + */ + private void allocateElements(int numElements) { + int initialCapacity = MIN_INITIAL_CAPACITY; + // Find the best power of two to hold elements. + // Tests "<=" because arrays aren't kept full. + if (numElements >= initialCapacity) { + initialCapacity = numElements; + initialCapacity |= (initialCapacity >>> 1); + initialCapacity |= (initialCapacity >>> 2); + initialCapacity |= (initialCapacity >>> 4); + initialCapacity |= (initialCapacity >>> 8); + initialCapacity |= (initialCapacity >>> 16); + initialCapacity++; + + if (initialCapacity < 0) // Too many elements, must back off + initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements + } + elements = new Object[initialCapacity]; + } + + /** + * Double the capacity of this deque. Call only when full, i.e., + * when head and tail have wrapped around to become equal. + */ + private void doubleCapacity() { + // assert head == tail; + int p = head; + int n = elements.length; + int r = n - p; // number of elements to the right of p + int newCapacity = n << 1; + if (newCapacity < 0) + throw new IllegalStateException("Sorry, deque too big"); + Object[] a = new Object[newCapacity]; + System.arraycopy(elements, p, a, 0, r); + System.arraycopy(elements, 0, a, r, p); + elements = a; + head = 0; + tail = n; + } + + /** + * Copies the elements from our element array into the specified array, + * in order (from first to last element in the deque). It is assumed + * that the array is large enough to hold all elements in the deque. + * + * @return its argument + */ + private T[] copyElements(T[] a) { + if (head < tail) { + System.arraycopy(elements, head, a, 0, size()); + } else if (head > tail) { + int headPortionLen = elements.length - head; + System.arraycopy(elements, head, a, 0, headPortionLen); + System.arraycopy(elements, 0, a, headPortionLen, tail); + } + return a; + } + + /** + * Constructs an empty array deque with an initial capacity + * sufficient to hold 16 elements. + */ + public ArrayDeque() { + elements = new Object[16]; + } + + /** + * Constructs an empty array deque with an initial capacity + * sufficient to hold the specified number of elements. + * + * @param numElements lower bound on initial capacity of the deque + */ + public ArrayDeque(int numElements) { + allocateElements(numElements); + } + + /** + * Constructs a deque containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. (The first element returned by the collection's + * iterator becomes the first element, or front of the + * deque.) + * + * @param c the collection whose elements are to be placed into the deque + * @throws NullPointerException if the specified collection is null + */ + public ArrayDeque(Collection c) { + allocateElements(c.size()); + addAll(c); + } + + // The main insertion and extraction methods are addFirst, + // addLast, pollFirst, pollLast. The other methods are defined in + // terms of these. + + /** + * Inserts the specified element at the front of this deque. + * + * @param e the element to add + * @throws NullPointerException if the specified element is null + */ + public void addFirst(E e) { + if (e == null) + throw new NullPointerException("e == null"); + elements[head = (head - 1) & (elements.length - 1)] = e; + if (head == tail) + doubleCapacity(); + } + + /** + * Inserts the specified element at the end of this deque. + * + *

This method is equivalent to {@link #add}. + * + * @param e the element to add + * @throws NullPointerException if the specified element is null + */ + public void addLast(E e) { + if (e == null) + throw new NullPointerException("e == null"); + elements[tail] = e; + if ( (tail = (tail + 1) & (elements.length - 1)) == head) + doubleCapacity(); + } + + /** + * Inserts the specified element at the front of this deque. + * + * @param e the element to add + * @return true (as specified by {@link Deque#offerFirst}) + * @throws NullPointerException if the specified element is null + */ + public boolean offerFirst(E e) { + addFirst(e); + return true; + } + + /** + * Inserts the specified element at the end of this deque. + * + * @param e the element to add + * @return true (as specified by {@link Deque#offerLast}) + * @throws NullPointerException if the specified element is null + */ + public boolean offerLast(E e) { + addLast(e); + return true; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E removeFirst() { + E x = pollFirst(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E removeLast() { + E x = pollLast(); + if (x == null) + throw new NoSuchElementException(); + return x; + } + + public E pollFirst() { + int h = head; + @SuppressWarnings("unchecked") E result = (E) elements[h]; + // Element is null if deque empty + if (result == null) + return null; + elements[h] = null; // Must null out slot + head = (h + 1) & (elements.length - 1); + return result; + } + + public E pollLast() { + int t = (tail - 1) & (elements.length - 1); + @SuppressWarnings("unchecked") E result = (E) elements[t]; + if (result == null) + return null; + elements[t] = null; + tail = t; + return result; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E getFirst() { + @SuppressWarnings("unchecked") E result = (E) elements[head]; + if (result == null) + throw new NoSuchElementException(); + return result; + } + + /** + * @throws NoSuchElementException {@inheritDoc} + */ + public E getLast() { + @SuppressWarnings("unchecked") + E result = (E) elements[(tail - 1) & (elements.length - 1)]; + if (result == null) + throw new NoSuchElementException(); + return result; + } + + public E peekFirst() { + @SuppressWarnings("unchecked") E result = (E) elements[head]; + // elements[head] is null if deque empty + return result; + } + + public E peekLast() { + @SuppressWarnings("unchecked") + E result = (E) elements[(tail - 1) & (elements.length - 1)]; + return result; + } + + /** + * Removes the first occurrence of the specified element in this + * deque (when traversing the deque from head to tail). + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element e such that + * o.equals(e) (if such an element exists). + * Returns true if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return true if the deque contained the specified element + */ + public boolean removeFirstOccurrence(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = head; + Object x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) { + delete(i); + return true; + } + i = (i + 1) & mask; + } + return false; + } + + /** + * Removes the last occurrence of the specified element in this + * deque (when traversing the deque from head to tail). + * If the deque does not contain the element, it is unchanged. + * More formally, removes the last element e such that + * o.equals(e) (if such an element exists). + * Returns true if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return true if the deque contained the specified element + */ + public boolean removeLastOccurrence(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = (tail - 1) & mask; + Object x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) { + delete(i); + return true; + } + i = (i - 1) & mask; + } + return false; + } + + // *** Queue methods *** + + /** + * Inserts the specified element at the end of this deque. + * + *

This method is equivalent to {@link #addLast}. + * + * @param e the element to add + * @return true (as specified by {@link Collection#add}) + * @throws NullPointerException if the specified element is null + */ + public boolean add(E e) { + addLast(e); + return true; + } + + /** + * Inserts the specified element at the end of this deque. + * + *

This method is equivalent to {@link #offerLast}. + * + * @param e the element to add + * @return true (as specified by {@link Queue#offer}) + * @throws NullPointerException if the specified element is null + */ + public boolean offer(E e) { + return offerLast(e); + } + + /** + * Retrieves and removes the head of the queue represented by this deque. + * + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. + * + *

This method is equivalent to {@link #removeFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException {@inheritDoc} + */ + public E remove() { + return removeFirst(); + } + + /** + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque), or returns + * null if this deque is empty. + * + *

This method is equivalent to {@link #pollFirst}. + * + * @return the head of the queue represented by this deque, or + * null if this deque is empty + */ + public E poll() { + return pollFirst(); + } + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque. This method differs from {@link #peek peek} only in + * that it throws an exception if this deque is empty. + * + *

This method is equivalent to {@link #getFirst}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException {@inheritDoc} + */ + public E element() { + return getFirst(); + } + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque, or returns null if this deque is empty. + * + *

This method is equivalent to {@link #peekFirst}. + * + * @return the head of the queue represented by this deque, or + * null if this deque is empty + */ + public E peek() { + return peekFirst(); + } + + // *** Stack methods *** + + /** + * Pushes an element onto the stack represented by this deque. In other + * words, inserts the element at the front of this deque. + * + *

This method is equivalent to {@link #addFirst}. + * + * @param e the element to push + * @throws NullPointerException if the specified element is null + */ + public void push(E e) { + addFirst(e); + } + + /** + * Pops an element from the stack represented by this deque. In other + * words, removes and returns the first element of this deque. + * + *

This method is equivalent to {@link #removeFirst()}. + * + * @return the element at the front of this deque (which is the top + * of the stack represented by this deque) + * @throws NoSuchElementException {@inheritDoc} + */ + public E pop() { + return removeFirst(); + } + + private void checkInvariants() { + // assert elements[tail] == null; + // assert head == tail ? elements[head] == null : + // (elements[head] != null && + // elements[(tail - 1) & (elements.length - 1)] != null); + // assert elements[(head - 1) & (elements.length - 1)] == null; + } + + /** + * Removes the element at the specified position in the elements array, + * adjusting head and tail as necessary. This can result in motion of + * elements backwards or forwards in the array. + * + *

This method is called delete rather than remove to emphasize + * that its semantics differ from those of {@link List#remove(int)}. + * + * @return true if elements moved backwards + */ + private boolean delete(int i) { + //checkInvariants(); + final Object[] elements = this.elements; + final int mask = elements.length - 1; + final int h = head; + final int t = tail; + final int front = (i - h) & mask; + final int back = (t - i) & mask; + + // Invariant: head <= i < tail mod circularity + if (front >= ((t - h) & mask)) + throw new ConcurrentModificationException(); + + // Optimize for least element motion + if (front < back) { + if (h <= i) { + System.arraycopy(elements, h, elements, h + 1, front); + } else { // Wrap around + System.arraycopy(elements, 0, elements, 1, i); + elements[0] = elements[mask]; + System.arraycopy(elements, h, elements, h + 1, mask - h); + } + elements[h] = null; + head = (h + 1) & mask; + return false; + } else { + if (i < t) { // Copy the null tail as well + System.arraycopy(elements, i + 1, elements, i, back); + tail = t - 1; + } else { // Wrap around + System.arraycopy(elements, i + 1, elements, i, mask - i); + elements[mask] = elements[0]; + System.arraycopy(elements, 1, elements, 0, t); + tail = (t - 1) & mask; + } + return true; + } + } + + // *** Collection Methods *** + + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size() { + return (tail - head) & (elements.length - 1); + } + + /** + * Returns true if this deque contains no elements. + * + * @return true if this deque contains no elements + */ + public boolean isEmpty() { + return head == tail; + } + + /** + * Returns an iterator over the elements in this deque. The elements + * will be ordered from first (head) to last (tail). This is the same + * order that elements would be dequeued (via successive calls to + * {@link #remove} or popped (via successive calls to {@link #pop}). + * + * @return an iterator over the elements in this deque + */ + public Iterator iterator() { + return new DeqIterator(); + } + + public Iterator descendingIterator() { + return new DescendingIterator(); + } + + private class DeqIterator implements Iterator { + /** + * Index of element to be returned by subsequent call to next. + */ + private int cursor = head; + + /** + * Tail recorded at construction (also in remove), to stop + * iterator and also to check for comodification. + */ + private int fence = tail; + + /** + * Index of element returned by most recent call to next. + * Reset to -1 if element is deleted by a call to remove. + */ + private int lastRet = -1; + + public boolean hasNext() { + return cursor != fence; + } + + public E next() { + if (cursor == fence) + throw new NoSuchElementException(); + @SuppressWarnings("unchecked") E result = (E) elements[cursor]; + // This check doesn't catch all possible comodifications, + // but does catch the ones that corrupt traversal + if (tail != fence || result == null) + throw new ConcurrentModificationException(); + lastRet = cursor; + cursor = (cursor + 1) & (elements.length - 1); + return result; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + if (delete(lastRet)) { // if left-shifted, undo increment in next() + cursor = (cursor - 1) & (elements.length - 1); + fence = tail; + } + lastRet = -1; + } + } + + private class DescendingIterator implements Iterator { + /* + * This class is nearly a mirror-image of DeqIterator, using + * tail instead of head for initial cursor, and head instead of + * tail for fence. + */ + private int cursor = tail; + private int fence = head; + private int lastRet = -1; + + public boolean hasNext() { + return cursor != fence; + } + + public E next() { + if (cursor == fence) + throw new NoSuchElementException(); + cursor = (cursor - 1) & (elements.length - 1); + @SuppressWarnings("unchecked") E result = (E) elements[cursor]; + if (head != fence || result == null) + throw new ConcurrentModificationException(); + lastRet = cursor; + return result; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + if (!delete(lastRet)) { + cursor = (cursor + 1) & (elements.length - 1); + fence = head; + } + lastRet = -1; + } + } + + /** + * Returns true if this deque contains the specified element. + * More formally, returns true if and only if this deque contains + * at least one element e such that o.equals(e). + * + * @param o object to be checked for containment in this deque + * @return true if this deque contains the specified element + */ + public boolean contains(Object o) { + if (o == null) + return false; + int mask = elements.length - 1; + int i = head; + Object x; + while ( (x = elements[i]) != null) { + if (o.equals(x)) + return true; + i = (i + 1) & mask; + } + return false; + } + + /** + * Removes a single instance of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element e such that + * o.equals(e) (if such an element exists). + * Returns true if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + *

This method is equivalent to {@link #removeFirstOccurrence}. + * + * @param o element to be removed from this deque, if present + * @return true if this deque contained the specified element + */ + public boolean remove(Object o) { + return removeFirstOccurrence(o); + } + + /** + * Removes all of the elements from this deque. + * The deque will be empty after this call returns. + */ + public void clear() { + int h = head; + int t = tail; + if (h != t) { // clear all cells + head = tail = 0; + int i = h; + int mask = elements.length - 1; + do { + elements[i] = null; + i = (i + 1) & mask; + } while (i != t); + } + } + + /** + * Returns an array containing all of the elements in this deque + * in proper sequence (from first to last element). + * + *

The returned array will be "safe" in that no references to it are + * maintained by this deque. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + *

This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this deque + */ + public Object[] toArray() { + return copyElements(new Object[size()]); + } + + /** + * Returns an array containing all of the elements in this deque in + * proper sequence (from first to last element); the runtime type of the + * returned array is that of the specified array. If the deque fits in + * the specified array, it is returned therein. Otherwise, a new array + * is allocated with the runtime type of the specified array and the + * size of this deque. + * + *

If this deque fits in the specified array with room to spare + * (i.e., the array has more elements than this deque), the element in + * the array immediately following the end of the deque is set to + * null. + * + *

Like the {@link #toArray()} method, this method acts as bridge between + * array-based and collection-based APIs. Further, this method allows + * precise control over the runtime type of the output array, and may, + * under certain circumstances, be used to save allocation costs. + * + *

Suppose x is a deque known to contain only strings. + * The following code can be used to dump the deque into a newly + * allocated array of String: + * + *

 {@code String[] y = x.toArray(new String[0]);}
+ * + * Note that toArray(new Object[0]) is identical in function to + * toArray(). + * + * @param a the array into which the elements of the deque are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose + * @return an array containing all of the elements in this deque + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this deque + * @throws NullPointerException if the specified array is null + */ + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + int size = size(); + if (a.length < size) + a = (T[])java.lang.reflect.Array.newInstance( + a.getClass().getComponentType(), size); + copyElements(a); + if (a.length > size) + a[size] = null; + return a; + } + + // *** Object methods *** + + /** + * Returns a copy of this deque. + * + * @return a copy of this deque + */ + public ArrayDeque clone() { + try { + @SuppressWarnings("unchecked") + ArrayDeque result = (ArrayDeque) super.clone(); + result.elements = Arrays.copyOf(elements, elements.length); + return result; + + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + /** + * Appease the serialization gods. + */ + private static final long serialVersionUID = 2340985798034038923L; + + /** + * Serialize this deque. + * + * @serialData The current size (int) of the deque, + * followed by all of its elements (each an object reference) in + * first-to-last order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + + // Write out size + s.writeInt(size()); + + // Write out elements in order. + int mask = elements.length - 1; + for (int i = head; i != tail; i = (i + 1) & mask) + s.writeObject(elements[i]); + } + + /** + * Deserialize this deque. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + + // Read in size and allocate array + int size = s.readInt(); + allocateElements(size); + head = 0; + tail = size; + + // Read in all elements in the proper order. + for (int i = 0; i < size; i++) + elements[i] = s.readObject(); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java b/platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java new file mode 100644 index 00000000..b370794c --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/AsyncTask.java @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.artifex.mupdfdemo; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import android.os.Process; +import android.os.Handler; +import android.os.Message; + +/** + *

AsyncTask enables proper and easy use of the UI thread. This class allows to + * perform background operations and publish results on the UI thread without + * having to manipulate threads and/or handlers.

+ * + *

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} + * and does not constitute a generic threading framework. AsyncTasks should ideally be + * used for short operations (a few seconds at the most.) If you need to keep threads + * running for long periods of time, it is highly recommended you use the various APIs + * provided by the java.util.concurrent pacakge such as {@link Executor}, + * {@link ThreadPoolExecutor} and {@link FutureTask}.

+ * + *

An asynchronous task is defined by a computation that runs on a background thread and + * whose result is published on the UI thread. An asynchronous task is defined by 3 generic + * types, called Params, Progress and Result, + * and 4 steps, called onPreExecute, doInBackground, + * onProgressUpdate and onPostExecute.

+ * + *
+ *

Developer Guides

+ *

For more information about using tasks and threads, read the + * Processes and + * Threads developer guide.

+ *
+ * + *

Usage

+ *

AsyncTask must be subclassed to be used. The subclass will override at least + * one method ({@link #doInBackground}), and most often will override a + * second one ({@link #onPostExecute}.)

+ * + *

Here is an example of subclassing:

+ *
+ * private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
+ *     protected Long doInBackground(URL... urls) {
+ *         int count = urls.length;
+ *         long totalSize = 0;
+ *         for (int i = 0; i < count; i++) {
+ *             totalSize += Downloader.downloadFile(urls[i]);
+ *             publishProgress((int) ((i / (float) count) * 100));
+ *             // Escape early if cancel() is called
+ *             if (isCancelled()) break;
+ *         }
+ *         return totalSize;
+ *     }
+ *
+ *     protected void onProgressUpdate(Integer... progress) {
+ *         setProgressPercent(progress[0]);
+ *     }
+ *
+ *     protected void onPostExecute(Long result) {
+ *         showDialog("Downloaded " + result + " bytes");
+ *     }
+ * }
+ * 
+ * + *

Once created, a task is executed very simply:

+ *
+ * new DownloadFilesTask().execute(url1, url2, url3);
+ * 
+ * + *

AsyncTask's generic types

+ *

The three types used by an asynchronous task are the following:

+ *
    + *
  1. Params, the type of the parameters sent to the task upon + * execution.
  2. + *
  3. Progress, the type of the progress units published during + * the background computation.
  4. + *
  5. Result, the type of the result of the background + * computation.
  6. + *
+ *

Not all types are always used by an asynchronous task. To mark a type as unused, + * simply use the type {@link Void}:

+ *
+ * private class MyTask extends AsyncTask<Void, Void, Void> { ... }
+ * 
+ * + *

The 4 steps

+ *

When an asynchronous task is executed, the task goes through 4 steps:

+ *
    + *
  1. {@link #onPreExecute()}, invoked on the UI thread before the task + * is executed. This step is normally used to setup the task, for instance by + * showing a progress bar in the user interface.
  2. + *
  3. {@link #doInBackground}, invoked on the background thread + * immediately after {@link #onPreExecute()} finishes executing. This step is used + * to perform background computation that can take a long time. The parameters + * of the asynchronous task are passed to this step. The result of the computation must + * be returned by this step and will be passed back to the last step. This step + * can also use {@link #publishProgress} to publish one or more units + * of progress. These values are published on the UI thread, in the + * {@link #onProgressUpdate} step.
  4. + *
  5. {@link #onProgressUpdate}, invoked on the UI thread after a + * call to {@link #publishProgress}. The timing of the execution is + * undefined. This method is used to display any form of progress in the user + * interface while the background computation is still executing. For instance, + * it can be used to animate a progress bar or show logs in a text field.
  6. + *
  7. {@link #onPostExecute}, invoked on the UI thread after the background + * computation finishes. The result of the background computation is passed to + * this step as a parameter.
  8. + *
+ * + *

Cancelling a task

+ *

A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking + * this method will cause subsequent calls to {@link #isCancelled()} to return true. + * After invoking this method, {@link #onCancelled(Object)}, instead of + * {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])} + * returns. To ensure that a task is cancelled as quickly as possible, you should always + * check the return value of {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)

+ * + *

Threading rules

+ *

There are a few threading rules that must be followed for this class to + * work properly:

+ *
    + *
  • The AsyncTask class must be loaded on the UI thread. This is done + * automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
  • + *
  • The task instance must be created on the UI thread.
  • + *
  • {@link #execute} must be invoked on the UI thread.
  • + *
  • Do not call {@link #onPreExecute()}, {@link #onPostExecute}, + * {@link #doInBackground}, {@link #onProgressUpdate} manually.
  • + *
  • The task can be executed only once (an exception will be thrown if + * a second execution is attempted.)
  • + *
+ * + *

Memory observability

+ *

AsyncTask guarantees that all callback calls are synchronized in such a way that the following + * operations are safe without explicit synchronizations.

+ *
    + *
  • Set member fields in the constructor or {@link #onPreExecute}, and refer to them + * in {@link #doInBackground}. + *
  • Set member fields in {@link #doInBackground}, and refer to them in + * {@link #onProgressUpdate} and {@link #onPostExecute}. + *
+ * + *

Order of execution

+ *

When first introduced, AsyncTasks were executed serially on a single background + * thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting with + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single + * thread to avoid common application errors caused by parallel execution.

+ *

If you truly want parallel execution, you can invoke + * {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with + * {@link #THREAD_POOL_EXECUTOR}.

+ */ +public abstract class AsyncTask { + private static final String LOG_TAG = "AsyncTask"; + + private static final int CORE_POOL_SIZE = 5; + private static final int MAXIMUM_POOL_SIZE = 128; + private static final int KEEP_ALIVE = 1; + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); + } + }; + + private static final BlockingQueue sPoolWorkQueue = + new LinkedBlockingQueue(10); + + /** + * An {@link Executor} that can be used to execute tasks in parallel. + */ + public static final Executor THREAD_POOL_EXECUTOR + = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, + TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); + + /** + * An {@link Executor} that executes tasks one at a time in serial + * order. This serialization is global to a particular process. + */ + public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); + + private static final int MESSAGE_POST_RESULT = 0x1; + private static final int MESSAGE_POST_PROGRESS = 0x2; + + private static final InternalHandler sHandler = new InternalHandler(); + + private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; + private final WorkerRunnable mWorker; + private final FutureTask mFuture; + + private volatile Status mStatus = Status.PENDING; + + private final AtomicBoolean mCancelled = new AtomicBoolean(); + private final AtomicBoolean mTaskInvoked = new AtomicBoolean(); + + private static class SerialExecutor implements Executor { + final ArrayDeque mTasks = new ArrayDeque(); + Runnable mActive; + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + } + + /** + * Indicates the current status of the task. Each status will be set only once + * during the lifetime of a task. + */ + public enum Status { + /** + * Indicates that the task has not been executed yet. + */ + PENDING, + /** + * Indicates that the task is running. + */ + RUNNING, + /** + * Indicates that {@link AsyncTask#onPostExecute} has finished. + */ + FINISHED, + } + + /** @hide Used to force static handler to be created. */ + public static void init() { + sHandler.getLooper(); + } + + /** @hide */ + public static void setDefaultExecutor(Executor exec) { + sDefaultExecutor = exec; + } + + /** + * Creates a new asynchronous task. This constructor must be invoked on the UI thread. + */ + public AsyncTask() { + mWorker = new WorkerRunnable() { + public Result call() throws Exception { + mTaskInvoked.set(true); + + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + //noinspection unchecked + return postResult(doInBackground(mParams)); + } + }; + + mFuture = new FutureTask(mWorker) { + @Override + protected void done() { + try { + postResultIfNotInvoked(get()); + } catch (InterruptedException e) { + android.util.Log.w(LOG_TAG, e); + } catch (ExecutionException e) { + throw new RuntimeException("An error occured while executing doInBackground()", + e.getCause()); + } catch (CancellationException e) { + postResultIfNotInvoked(null); + } + } + }; + } + + private void postResultIfNotInvoked(Result result) { + final boolean wasTaskInvoked = mTaskInvoked.get(); + if (!wasTaskInvoked) { + postResult(result); + } + } + + private Result postResult(Result result) { + @SuppressWarnings("unchecked") + Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, + new AsyncTaskResult(this, result)); + message.sendToTarget(); + return result; + } + + /** + * Returns the current status of this task. + * + * @return The current status. + */ + public final Status getStatus() { + return mStatus; + } + + /** + * Override this method to perform a computation on a background thread. The + * specified parameters are the parameters passed to {@link #execute} + * by the caller of this task. + * + * This method can call {@link #publishProgress} to publish updates + * on the UI thread. + * + * @param params The parameters of the task. + * + * @return A result, defined by the subclass of this task. + * + * @see #onPreExecute() + * @see #onPostExecute + * @see #publishProgress + */ + protected abstract Result doInBackground(Params... params); + + /** + * Runs on the UI thread before {@link #doInBackground}. + * + * @see #onPostExecute + * @see #doInBackground + */ + protected void onPreExecute() { + } + + /** + *

Runs on the UI thread after {@link #doInBackground}. The + * specified result is the value returned by {@link #doInBackground}.

+ * + *

This method won't be invoked if the task was cancelled.

+ * + * @param result The result of the operation computed by {@link #doInBackground}. + * + * @see #onPreExecute + * @see #doInBackground + * @see #onCancelled(Object) + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onPostExecute(Result result) { + } + + /** + * Runs on the UI thread after {@link #publishProgress} is invoked. + * The specified values are the values passed to {@link #publishProgress}. + * + * @param values The values indicating progress. + * + * @see #publishProgress + * @see #doInBackground + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void onProgressUpdate(Progress... values) { + } + + /** + *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.

+ * + *

The default implementation simply invokes {@link #onCancelled()} and + * ignores the result. If you write your own implementation, do not call + * super.onCancelled(result).

+ * + * @param result The result, if any, computed in + * {@link #doInBackground(Object[])}, can be null + * + * @see #cancel(boolean) + * @see #isCancelled() + */ + @SuppressWarnings({"UnusedParameters"}) + protected void onCancelled(Result result) { + onCancelled(); + } + + /** + *

Applications should preferably override {@link #onCancelled(Object)}. + * This method is invoked by the default implementation of + * {@link #onCancelled(Object)}.

+ * + *

Runs on the UI thread after {@link #cancel(boolean)} is invoked and + * {@link #doInBackground(Object[])} has finished.

+ * + * @see #onCancelled(Object) + * @see #cancel(boolean) + * @see #isCancelled() + */ + protected void onCancelled() { + } + + /** + * Returns true if this task was cancelled before it completed + * normally. If you are calling {@link #cancel(boolean)} on the task, + * the value returned by this method should be checked periodically from + * {@link #doInBackground(Object[])} to end the task as soon as possible. + * + * @return true if task was cancelled before it completed + * + * @see #cancel(boolean) + */ + public final boolean isCancelled() { + return mCancelled.get(); + } + + /** + *

Attempts to cancel execution of this task. This attempt will + * fail if the task has already completed, already been cancelled, + * or could not be cancelled for some other reason. If successful, + * and this task has not started when cancel is called, + * this task should never run. If the task has already started, + * then the mayInterruptIfRunning parameter determines + * whether the thread executing this task should be interrupted in + * an attempt to stop the task.

+ * + *

Calling this method will result in {@link #onCancelled(Object)} being + * invoked on the UI thread after {@link #doInBackground(Object[])} + * returns. Calling this method guarantees that {@link #onPostExecute(Object)} + * is never invoked. After invoking this method, you should check the + * value returned by {@link #isCancelled()} periodically from + * {@link #doInBackground(Object[])} to finish the task as early as + * possible.

+ * + * @param mayInterruptIfRunning true if the thread executing this + * task should be interrupted; otherwise, in-progress tasks are allowed + * to complete. + * + * @return false if the task could not be cancelled, + * typically because it has already completed normally; + * true otherwise + * + * @see #isCancelled() + * @see #onCancelled(Object) + */ + public final boolean cancel(boolean mayInterruptIfRunning) { + mCancelled.set(true); + return mFuture.cancel(mayInterruptIfRunning); + } + + /** + * Waits if necessary for the computation to complete, and then + * retrieves its result. + * + * @return The computed result. + * + * @throws CancellationException If the computation was cancelled. + * @throws ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + */ + public final Result get() throws InterruptedException, ExecutionException { + return mFuture.get(); + } + + /** + * Waits if necessary for at most the given time for the computation + * to complete, and then retrieves its result. + * + * @param timeout Time to wait before cancelling the operation. + * @param unit The time unit for the timeout. + * + * @return The computed result. + * + * @throws CancellationException If the computation was cancelled. + * @throws ExecutionException If the computation threw an exception. + * @throws InterruptedException If the current thread was interrupted + * while waiting. + * @throws TimeoutException If the wait timed out. + */ + public final Result get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + return mFuture.get(timeout, unit); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + *

Note: this function schedules the task on a queue for a single background + * thread or pool of threads depending on the platform version. When first + * introduced, AsyncTasks were executed serially on a single background thread. + * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed + * to a pool of threads allowing multiple tasks to operate in parallel. Starting + * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being + * executed on a single thread to avoid common application errors caused + * by parallel execution. If you truly want parallel execution, you can use + * the {@link #executeOnExecutor} version of this method + * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings + * on its use. + * + *

This method must be invoked on the UI thread. + * + * @param params The parameters of the task. + * + * @return This instance of AsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + * @see #execute(Runnable) + */ + public final AsyncTask execute(Params... params) { + return executeOnExecutor(sDefaultExecutor, params); + } + + /** + * Executes the task with the specified parameters. The task returns + * itself (this) so that the caller can keep a reference to it. + * + *

This method is typically used with {@link #THREAD_POOL_EXECUTOR} to + * allow multiple tasks to run in parallel on a pool of threads managed by + * AsyncTask, however you can also use your own {@link Executor} for custom + * behavior. + * + *

Warning: Allowing multiple tasks to run in parallel from + * a thread pool is generally not what one wants, because the order + * of their operation is not defined. For example, if these tasks are used + * to modify any state in common (such as writing a file due to a button click), + * there are no guarantees on the order of the modifications. + * Without careful work it is possible in rare cases for the newer version + * of the data to be over-written by an older one, leading to obscure data + * loss and stability issues. Such changes are best + * executed in serial; to guarantee such work is serialized regardless of + * platform version you can use this function with {@link #SERIAL_EXECUTOR}. + * + *

This method must be invoked on the UI thread. + * + * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a + * convenient process-wide thread pool for tasks that are loosely coupled. + * @param params The parameters of the task. + * + * @return This instance of AsyncTask. + * + * @throws IllegalStateException If {@link #getStatus()} returns either + * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. + * + * @see #execute(Object[]) + */ + public final AsyncTask executeOnExecutor(Executor exec, + Params... params) { + if (mStatus != Status.PENDING) { + switch (mStatus) { + case RUNNING: + throw new IllegalStateException("Cannot execute task:" + + " the task is already running."); + case FINISHED: + throw new IllegalStateException("Cannot execute task:" + + " the task has already been executed " + + "(a task can be executed only once)"); + } + } + + mStatus = Status.RUNNING; + + onPreExecute(); + + mWorker.mParams = params; + exec.execute(mFuture); + + return this; + } + + /** + * Convenience version of {@link #execute(Object...)} for use with + * a simple Runnable object. See {@link #execute(Object[])} for more + * information on the order of execution. + * + * @see #execute(Object[]) + * @see #executeOnExecutor(java.util.concurrent.Executor, Object[]) + */ + public static void execute(Runnable runnable) { + sDefaultExecutor.execute(runnable); + } + + /** + * This method can be invoked from {@link #doInBackground} to + * publish updates on the UI thread while the background computation is + * still running. Each call to this method will trigger the execution of + * {@link #onProgressUpdate} on the UI thread. + * + * {@link #onProgressUpdate} will note be called if the task has been + * canceled. + * + * @param values The progress values to update the UI with. + * + * @see #onProgressUpdate + * @see #doInBackground + */ + protected final void publishProgress(Progress... values) { + if (!isCancelled()) { + sHandler.obtainMessage(MESSAGE_POST_PROGRESS, + new AsyncTaskResult(this, values)).sendToTarget(); + } + } + + private void finish(Result result) { + if (isCancelled()) { + onCancelled(result); + } else { + onPostExecute(result); + } + mStatus = Status.FINISHED; + } + + private static class InternalHandler extends Handler { + @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) + @Override + public void handleMessage(Message msg) { + AsyncTaskResult result = (AsyncTaskResult) msg.obj; + switch (msg.what) { + case MESSAGE_POST_RESULT: + // There is only one result + result.mTask.finish(result.mData[0]); + break; + case MESSAGE_POST_PROGRESS: + result.mTask.onProgressUpdate(result.mData); + break; + } + } + } + + private static abstract class WorkerRunnable implements Callable { + Params[] mParams; + } + + @SuppressWarnings({"RawUseOfParameterizedType"}) + private static class AsyncTaskResult { + final AsyncTask mTask; + final Data[] mData; + + AsyncTaskResult(AsyncTask task, Data... data) { + mTask = task; + mData = data; + } + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/CancellableAsyncTask.java b/platform/android/viewer/src/com/artifex/mupdfdemo/CancellableAsyncTask.java new file mode 100644 index 00000000..fcb1b744 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/CancellableAsyncTask.java @@ -0,0 +1,79 @@ +package com.artifex.mupdfdemo; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +// Ideally this would be a subclass of AsyncTask, however the cancel() method is final, and cannot +// be overridden. I felt that having two different, but similar cancel methods was a bad idea. +public class CancellableAsyncTask +{ + private final AsyncTask asyncTask; + private final CancellableTaskDefinition ourTask; + + public void onPreExecute() + { + + } + + public void onPostExecute(Result result) + { + + } + + public CancellableAsyncTask(final CancellableTaskDefinition task) + { + if (task == null) + throw new IllegalArgumentException(); + + this.ourTask = task; + asyncTask = new AsyncTask() + { + @Override + protected Result doInBackground(Params... params) + { + return task.doInBackground(params); + } + + @Override + protected void onPreExecute() + { + CancellableAsyncTask.this.onPreExecute(); + } + + @Override + protected void onPostExecute(Result result) + { + CancellableAsyncTask.this.onPostExecute(result); + task.doCleanup(); + } + }; + } + + public void cancelAndWait() + { + this.asyncTask.cancel(true); + ourTask.doCancel(); + + try + { + this.asyncTask.get(); + } + catch (InterruptedException e) + { + } + catch (ExecutionException e) + { + } + catch (CancellationException e) + { + } + + ourTask.doCleanup(); + } + + public void execute(Params ... params) + { + asyncTask.execute(params); + } + +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java b/platform/android/viewer/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java new file mode 100644 index 00000000..62b04f30 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/CancellableTaskDefinition.java @@ -0,0 +1,8 @@ +package com.artifex.mupdfdemo; + +public interface CancellableTaskDefinition +{ + public Result doInBackground(Params ... params); + public void doCancel(); + public void doCleanup(); +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFActivity.java b/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFActivity.java new file mode 100644 index 00000000..f6068bac --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFActivity.java @@ -0,0 +1,227 @@ +package com.artifex.mupdfdemo; + +import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.FileObserver; +import android.os.Handler; +import android.view.View; +import android.widget.ListView; + +enum Purpose { + PickPDF, + PickKeyFile +} + +public class ChoosePDFActivity extends ListActivity { + static public final String PICK_KEY_FILE = "com.artifex.mupdfdemo.PICK_KEY_FILE"; + static private File mDirectory; + static private Map mPositions = new HashMap(); + private File mParent; + private File [] mDirs; + private File [] mFiles; + private Handler mHandler; + private Runnable mUpdateFiles; + private ChoosePDFAdapter adapter; + private Purpose mPurpose; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mPurpose = PICK_KEY_FILE.equals(getIntent().getAction()) ? Purpose.PickKeyFile : Purpose.PickPDF; + + + String storageState = Environment.getExternalStorageState(); + + if (!Environment.MEDIA_MOUNTED.equals(storageState) + && !Environment.MEDIA_MOUNTED_READ_ONLY.equals(storageState)) + { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.no_media_warning); + builder.setMessage(R.string.no_media_hint); + AlertDialog alert = builder.create(); + alert.setButton(AlertDialog.BUTTON_POSITIVE,getString(R.string.dismiss), + new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + alert.show(); + return; + } + + if (mDirectory == null) + mDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + + // Create a list adapter... + adapter = new ChoosePDFAdapter(getLayoutInflater()); + setListAdapter(adapter); + + // ...that is updated dynamically when files are scanned + mHandler = new Handler(); + mUpdateFiles = new Runnable() { + public void run() { + Resources res = getResources(); + String appName = res.getString(R.string.app_name); + String version = res.getString(R.string.version); + String title = res.getString(R.string.picker_title_App_Ver_Dir); + setTitle(String.format(title, appName, version, mDirectory)); + + mParent = mDirectory.getParentFile(); + + mDirs = mDirectory.listFiles(new FileFilter() { + + public boolean accept(File file) { + return file.isDirectory(); + } + }); + if (mDirs == null) + mDirs = new File[0]; + + mFiles = mDirectory.listFiles(new FileFilter() { + + public boolean accept(File file) { + if (file.isDirectory()) + return false; + String fname = file.getName().toLowerCase(); + switch (mPurpose) { + case PickPDF: + if (fname.endsWith(".pdf")) + return true; + if (fname.endsWith(".xps")) + return true; + if (fname.endsWith(".cbz")) + return true; + if (fname.endsWith(".epub")) + return true; + if (fname.endsWith(".png")) + return true; + if (fname.endsWith(".jpe")) + return true; + if (fname.endsWith(".jpeg")) + return true; + if (fname.endsWith(".jpg")) + return true; + if (fname.endsWith(".jfif")) + return true; + if (fname.endsWith(".jfif-tbnl")) + return true; + if (fname.endsWith(".tif")) + return true; + if (fname.endsWith(".tiff")) + return true; + return false; + case PickKeyFile: + if (fname.endsWith(".pfx")) + return true; + return false; + default: + return false; + } + } + }); + if (mFiles == null) + mFiles = new File[0]; + + Arrays.sort(mFiles, new Comparator() { + public int compare(File arg0, File arg1) { + return arg0.getName().compareToIgnoreCase(arg1.getName()); + } + }); + + Arrays.sort(mDirs, new Comparator() { + public int compare(File arg0, File arg1) { + return arg0.getName().compareToIgnoreCase(arg1.getName()); + } + }); + + adapter.clear(); + if (mParent != null) + adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.PARENT, getString(R.string.parent_directory))); + for (File f : mDirs) + adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DIR, f.getName())); + for (File f : mFiles) + adapter.add(new ChoosePDFItem(ChoosePDFItem.Type.DOC, f.getName())); + + lastPosition(); + } + }; + + // Start initial file scan... + mHandler.post(mUpdateFiles); + + // ...and observe the directory and scan files upon changes. + FileObserver observer = new FileObserver(mDirectory.getPath(), FileObserver.CREATE | FileObserver.DELETE) { + public void onEvent(int event, String path) { + mHandler.post(mUpdateFiles); + } + }; + observer.startWatching(); + } + + private void lastPosition() { + String p = mDirectory.getAbsolutePath(); + if (mPositions.containsKey(p)) + getListView().setSelection(mPositions.get(p)); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + + mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); + + if (position < (mParent == null ? 0 : 1)) { + mDirectory = mParent; + mHandler.post(mUpdateFiles); + return; + } + + position -= (mParent == null ? 0 : 1); + + if (position < mDirs.length) { + mDirectory = mDirs[position]; + mHandler.post(mUpdateFiles); + return; + } + + position -= mDirs.length; + + Uri uri = Uri.fromFile(mFiles[position]); + Intent intent = new Intent(this,MuPDFActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(uri); + switch (mPurpose) { + case PickPDF: + // Start an activity to display the PDF file + startActivity(intent); + break; + case PickKeyFile: + // Return the uri to the caller + setResult(RESULT_OK, intent); + finish(); + break; + } + } + + @Override + protected void onPause() { + super.onPause(); + if (mDirectory != null) + mPositions.put(mDirectory.getAbsolutePath(), getListView().getFirstVisiblePosition()); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java b/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java new file mode 100644 index 00000000..0b3c6418 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFAdapter.java @@ -0,0 +1,66 @@ +package com.artifex.mupdfdemo; + +import java.util.LinkedList; + +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +public class ChoosePDFAdapter extends BaseAdapter { + private final LinkedList mItems; + private final LayoutInflater mInflater; + + public ChoosePDFAdapter(LayoutInflater inflater) { + mInflater = inflater; + mItems = new LinkedList(); + } + + public void clear() { + mItems.clear(); + } + + public void add(ChoosePDFItem item) { + mItems.add(item); + notifyDataSetChanged(); + } + + public int getCount() { + return mItems.size(); + } + + public Object getItem(int i) { + return null; + } + + public long getItemId(int arg0) { + return 0; + } + + private int iconForType(ChoosePDFItem.Type type) { + switch (type) { + case PARENT: return R.drawable.ic_arrow_up; + case DIR: return R.drawable.ic_dir; + case DOC: return R.drawable.ic_doc; + default: return 0; + } + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v; + if (convertView == null) { + v = mInflater.inflate(R.layout.picker_entry, null); + } else { + v = convertView; + } + ChoosePDFItem item = mItems.get(position); + ((TextView)v.findViewById(R.id.name)).setText(item.name); + ((ImageView)v.findViewById(R.id.icon)).setImageResource(iconForType(item.type)); + ((ImageView)v.findViewById(R.id.icon)).setColorFilter(Color.argb(255, 0, 0, 0)); + return v; + } + +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFItem.java b/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFItem.java new file mode 100644 index 00000000..de6e1d52 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/ChoosePDFItem.java @@ -0,0 +1,15 @@ +package com.artifex.mupdfdemo; + +public class ChoosePDFItem { + enum Type { + PARENT, DIR, DOC + } + + final public Type type; + final public String name; + + public ChoosePDFItem (Type t, String n) { + type = t; + name = n; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java b/platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java new file mode 100644 index 00000000..4bb176b2 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/Deque.java @@ -0,0 +1,554 @@ +/* + * Written by Doug Lea and Josh Bloch with assistance from members of + * JCP JSR-166 Expert Group and released to the public domain, as explained + * at http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package com.artifex.mupdfdemo; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Stack; + +// BEGIN android-note +// removed link to collections framework docs +// END android-note + +/** + * A linear collection that supports element insertion and removal at + * both ends. The name deque is short for "double ended queue" + * and is usually pronounced "deck". Most Deque + * implementations place no fixed limits on the number of elements + * they may contain, but this interface supports capacity-restricted + * deques as well as those with no fixed size limit. + * + *

This interface defines methods to access the elements at both + * ends of the deque. Methods are provided to insert, remove, and + * examine the element. Each of these methods exists in two forms: + * one throws an exception if the operation fails, the other returns a + * special value (either null or false, depending on + * the operation). The latter form of the insert operation is + * designed specifically for use with capacity-restricted + * Deque implementations; in most implementations, insert + * operations cannot fail. + * + *

The twelve methods described above are summarized in the + * following table: + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
First Element (Head) Last Element (Tail)
Throws exceptionSpecial valueThrows exceptionSpecial value
Insert{@link #addFirst addFirst(e)}{@link #offerFirst offerFirst(e)}{@link #addLast addLast(e)}{@link #offerLast offerLast(e)}
Remove{@link #removeFirst removeFirst()}{@link #pollFirst pollFirst()}{@link #removeLast removeLast()}{@link #pollLast pollLast()}
Examine{@link #getFirst getFirst()}{@link #peekFirst peekFirst()}{@link #getLast getLast()}{@link #peekLast peekLast()}
+ * + *

This interface extends the {@link Queue} interface. When a deque is + * used as a queue, FIFO (First-In-First-Out) behavior results. Elements are + * added at the end of the deque and removed from the beginning. The methods + * inherited from the Queue interface are precisely equivalent to + * Deque methods as indicated in the following table: + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Queue Method Equivalent Deque Method
{@link java.util.Queue#add add(e)}{@link #addLast addLast(e)}
{@link java.util.Queue#offer offer(e)}{@link #offerLast offerLast(e)}
{@link java.util.Queue#remove remove()}{@link #removeFirst removeFirst()}
{@link java.util.Queue#poll poll()}{@link #pollFirst pollFirst()}
{@link java.util.Queue#element element()}{@link #getFirst getFirst()}
{@link java.util.Queue#peek peek()}{@link #peek peekFirst()}
+ * + *

Deques can also be used as LIFO (Last-In-First-Out) stacks. This + * interface should be used in preference to the legacy {@link Stack} class. + * When a deque is used as a stack, elements are pushed and popped from the + * beginning of the deque. Stack methods are precisely equivalent to + * Deque methods as indicated in the table below: + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Stack Method Equivalent Deque Method
{@link #push push(e)}{@link #addFirst addFirst(e)}
{@link #pop pop()}{@link #removeFirst removeFirst()}
{@link #peek peek()}{@link #peekFirst peekFirst()}
+ * + *

Note that the {@link #peek peek} method works equally well when + * a deque is used as a queue or a stack; in either case, elements are + * drawn from the beginning of the deque. + * + *

This interface provides two methods to remove interior + * elements, {@link #removeFirstOccurrence removeFirstOccurrence} and + * {@link #removeLastOccurrence removeLastOccurrence}. + * + *

Unlike the {@link List} interface, this interface does not + * provide support for indexed access to elements. + * + *

While Deque implementations are not strictly required + * to prohibit the insertion of null elements, they are strongly + * encouraged to do so. Users of any Deque implementations + * that do allow null elements are strongly encouraged not to + * take advantage of the ability to insert nulls. This is so because + * null is used as a special return value by various methods + * to indicated that the deque is empty. + * + *

Deque implementations generally do not define + * element-based versions of the equals and hashCode + * methods, but instead inherit the identity-based versions from class + * Object. + * + * @author Doug Lea + * @author Josh Bloch + * @since 1.6 + * @param the type of elements held in this collection + */ + +public interface Deque extends Queue { + /** + * Inserts the specified element at the front of this deque if it is + * possible to do so immediately without violating capacity restrictions. + * When using a capacity-restricted deque, it is generally preferable to + * use method {@link #offerFirst}. + * + * @param e the element to add + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + void addFirst(E e); + + /** + * Inserts the specified element at the end of this deque if it is + * possible to do so immediately without violating capacity restrictions. + * When using a capacity-restricted deque, it is generally preferable to + * use method {@link #offerLast}. + * + *

This method is equivalent to {@link #add}. + * + * @param e the element to add + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + void addLast(E e); + + /** + * Inserts the specified element at the front of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, + * this method is generally preferable to the {@link #addFirst} method, + * which can fail to insert an element only by throwing an exception. + * + * @param e the element to add + * @return true if the element was added to this deque, else + * false + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean offerFirst(E e); + + /** + * Inserts the specified element at the end of this deque unless it would + * violate capacity restrictions. When using a capacity-restricted deque, + * this method is generally preferable to the {@link #addLast} method, + * which can fail to insert an element only by throwing an exception. + * + * @param e the element to add + * @return true if the element was added to this deque, else + * false + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean offerLast(E e); + + /** + * Retrieves and removes the first element of this deque. This method + * differs from {@link #pollFirst pollFirst} only in that it throws an + * exception if this deque is empty. + * + * @return the head of this deque + * @throws NoSuchElementException if this deque is empty + */ + E removeFirst(); + + /** + * Retrieves and removes the last element of this deque. This method + * differs from {@link #pollLast pollLast} only in that it throws an + * exception if this deque is empty. + * + * @return the tail of this deque + * @throws NoSuchElementException if this deque is empty + */ + E removeLast(); + + /** + * Retrieves and removes the first element of this deque, + * or returns null if this deque is empty. + * + * @return the head of this deque, or null if this deque is empty + */ + E pollFirst(); + + /** + * Retrieves and removes the last element of this deque, + * or returns null if this deque is empty. + * + * @return the tail of this deque, or null if this deque is empty + */ + E pollLast(); + + /** + * Retrieves, but does not remove, the first element of this deque. + * + * This method differs from {@link #peekFirst peekFirst} only in that it + * throws an exception if this deque is empty. + * + * @return the head of this deque + * @throws NoSuchElementException if this deque is empty + */ + E getFirst(); + + /** + * Retrieves, but does not remove, the last element of this deque. + * This method differs from {@link #peekLast peekLast} only in that it + * throws an exception if this deque is empty. + * + * @return the tail of this deque + * @throws NoSuchElementException if this deque is empty + */ + E getLast(); + + /** + * Retrieves, but does not remove, the first element of this deque, + * or returns null if this deque is empty. + * + * @return the head of this deque, or null if this deque is empty + */ + E peekFirst(); + + /** + * Retrieves, but does not remove, the last element of this deque, + * or returns null if this deque is empty. + * + * @return the tail of this deque, or null if this deque is empty + */ + E peekLast(); + + /** + * Removes the first occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element e such that + * (o==null ? e==null : o.equals(e)) + * (if such an element exists). + * Returns true if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return true if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean removeFirstOccurrence(Object o); + + /** + * Removes the last occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the last element e such that + * (o==null ? e==null : o.equals(e)) + * (if such an element exists). + * Returns true if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + * @param o element to be removed from this deque, if present + * @return true if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean removeLastOccurrence(Object o); + + // *** Queue methods *** + + /** + * Inserts the specified element into the queue represented by this deque + * (in other words, at the tail of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * true upon success and throwing an + * IllegalStateException if no space is currently available. + * When using a capacity-restricted deque, it is generally preferable to + * use {@link #offer(Object) offer}. + * + *

This method is equivalent to {@link #addLast}. + * + * @param e the element to add + * @return true (as specified by {@link Collection#add}) + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean add(E e); + + /** + * Inserts the specified element into the queue represented by this deque + * (in other words, at the tail of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * true upon success and false if no space is currently + * available. When using a capacity-restricted deque, this method is + * generally preferable to the {@link #add} method, which can fail to + * insert an element only by throwing an exception. + * + *

This method is equivalent to {@link #offerLast}. + * + * @param e the element to add + * @return true if the element was added to this deque, else + * false + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + boolean offer(E e); + + /** + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque). + * This method differs from {@link #poll poll} only in that it throws an + * exception if this deque is empty. + * + *

This method is equivalent to {@link #removeFirst()}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + E remove(); + + /** + * Retrieves and removes the head of the queue represented by this deque + * (in other words, the first element of this deque), or returns + * null if this deque is empty. + * + *

This method is equivalent to {@link #pollFirst()}. + * + * @return the first element of this deque, or null if + * this deque is empty + */ + E poll(); + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque (in other words, the first element of this deque). + * This method differs from {@link #peek peek} only in that it throws an + * exception if this deque is empty. + * + *

This method is equivalent to {@link #getFirst()}. + * + * @return the head of the queue represented by this deque + * @throws NoSuchElementException if this deque is empty + */ + E element(); + + /** + * Retrieves, but does not remove, the head of the queue represented by + * this deque (in other words, the first element of this deque), or + * returns null if this deque is empty. + * + *

This method is equivalent to {@link #peekFirst()}. + * + * @return the head of the queue represented by this deque, or + * null if this deque is empty + */ + E peek(); + + + // *** Stack methods *** + + /** + * Pushes an element onto the stack represented by this deque (in other + * words, at the head of this deque) if it is possible to do so + * immediately without violating capacity restrictions, returning + * true upon success and throwing an + * IllegalStateException if no space is currently available. + * + *

This method is equivalent to {@link #addFirst}. + * + * @param e the element to push + * @throws IllegalStateException if the element cannot be added at this + * time due to capacity restrictions + * @throws ClassCastException if the class of the specified element + * prevents it from being added to this deque + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements + * @throws IllegalArgumentException if some property of the specified + * element prevents it from being added to this deque + */ + void push(E e); + + /** + * Pops an element from the stack represented by this deque. In other + * words, removes and returns the first element of this deque. + * + *

This method is equivalent to {@link #removeFirst()}. + * + * @return the element at the front of this deque (which is the top + * of the stack represented by this deque) + * @throws NoSuchElementException if this deque is empty + */ + E pop(); + + + // *** Collection methods *** + + /** + * Removes the first occurrence of the specified element from this deque. + * If the deque does not contain the element, it is unchanged. + * More formally, removes the first element e such that + * (o==null ? e==null : o.equals(e)) + * (if such an element exists). + * Returns true if this deque contained the specified element + * (or equivalently, if this deque changed as a result of the call). + * + *

This method is equivalent to {@link #removeFirstOccurrence}. + * + * @param o element to be removed from this deque, if present + * @return true if an element was removed as a result of this call + * @throws ClassCastException if the class of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean remove(Object o); + + /** + * Returns true if this deque contains the specified element. + * More formally, returns true if and only if this deque contains + * at least one element e such that + * (o==null ? e==null : o.equals(e)). + * + * @param o element whose presence in this deque is to be tested + * @return true if this deque contains the specified element + * @throws ClassCastException if the type of the specified element + * is incompatible with this deque (optional) + * @throws NullPointerException if the specified element is null and this + * deque does not permit null elements (optional) + */ + boolean contains(Object o); + + /** + * Returns the number of elements in this deque. + * + * @return the number of elements in this deque + */ + public int size(); + + /** + * Returns an iterator over the elements in this deque in proper sequence. + * The elements will be returned in order from first (head) to last (tail). + * + * @return an iterator over the elements in this deque in proper sequence + */ + Iterator iterator(); + + /** + * Returns an iterator over the elements in this deque in reverse + * sequential order. The elements will be returned in order from + * last (tail) to first (head). + * + * @return an iterator over the elements in this deque in reverse + * sequence + */ + Iterator descendingIterator(); + +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/FilePicker.java b/platform/android/viewer/src/com/artifex/mupdfdemo/FilePicker.java new file mode 100644 index 00000000..d1953531 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/FilePicker.java @@ -0,0 +1,21 @@ +package com.artifex.mupdfdemo; + +import android.net.Uri; + +public abstract class FilePicker { + public interface FilePickerSupport { + void performPickFor(FilePicker picker); + } + + private final FilePickerSupport support; + + FilePicker(FilePickerSupport _support) { + support = _support; + } + + void pick() { + support.performPickFor(this); + } + + abstract void onPick(Uri uri); +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfo.java b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfo.java new file mode 100644 index 00000000..5aeaccbe --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfo.java @@ -0,0 +1,14 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class LinkInfo { + final public RectF rect; + + public LinkInfo(float l, float t, float r, float b) { + rect = new RectF(l, t, r, b); + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoExternal.java b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoExternal.java new file mode 100644 index 00000000..574b6264 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoExternal.java @@ -0,0 +1,14 @@ +package com.artifex.mupdfdemo; + +public class LinkInfoExternal extends LinkInfo { + final public String url; + + public LinkInfoExternal(float l, float t, float r, float b, String u) { + super(l, t, r, b); + url = u; + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + visitor.visitExternal(this); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoInternal.java b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoInternal.java new file mode 100644 index 00000000..761bf87a --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoInternal.java @@ -0,0 +1,14 @@ +package com.artifex.mupdfdemo; + +public class LinkInfoInternal extends LinkInfo { + final public int pageNumber; + + public LinkInfoInternal(float l, float t, float r, float b, int p) { + super(l, t, r, b); + pageNumber = p; + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + visitor.visitInternal(this); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoRemote.java b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoRemote.java new file mode 100644 index 00000000..731e6408 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoRemote.java @@ -0,0 +1,18 @@ +package com.artifex.mupdfdemo; + +public class LinkInfoRemote extends LinkInfo { + final public String fileSpec; + final public int pageNumber; + final public boolean newWindow; + + public LinkInfoRemote(float l, float t, float r, float b, String f, int p, boolean n) { + super(l, t, r, b); + fileSpec = f; + pageNumber = p; + newWindow = n; + } + + public void acceptVisitor(LinkInfoVisitor visitor) { + visitor.visitRemote(this); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoVisitor.java b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoVisitor.java new file mode 100644 index 00000000..ecd093e4 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/LinkInfoVisitor.java @@ -0,0 +1,7 @@ +package com.artifex.mupdfdemo; + +abstract public class LinkInfoVisitor { + public abstract void visitInternal(LinkInfoInternal li); + public abstract void visitExternal(LinkInfoExternal li); + public abstract void visitRemote(LinkInfoRemote li); +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java new file mode 100644 index 00000000..91b1e8fe --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFActivity.java @@ -0,0 +1,1417 @@ +package com.artifex.mupdfdemo; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RectShape; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.TextWatcher; +import android.text.method.PasswordTransformationMethod; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.PopupMenu; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.ViewAnimator; + +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.Executor; + +class ThreadPerTaskExecutor implements Executor { + public void execute(Runnable r) { + new Thread(r).start(); + } +} + +public class MuPDFActivity extends Activity implements FilePicker.FilePickerSupport +{ + /* The core rendering instance */ + enum TopBarMode {Main, Search, Annot, Delete, More, Accept}; + enum AcceptMode {Highlight, Underline, StrikeOut, Ink, CopyText}; + + private final int OUTLINE_REQUEST=0; + private final int PRINT_REQUEST=1; + private final int FILEPICK_REQUEST=2; + private final int PROOF_REQUEST=3; + private MuPDFCore core; + private String mFileName; + private MuPDFReaderView mDocView; + private View mButtonsView; + private boolean mButtonsVisible; + private EditText mPasswordView; + private TextView mFilenameView; + private SeekBar mPageSlider; + private int mPageSliderRes; + private TextView mPageNumberView; + private TextView mInfoView; + private ImageButton mSearchButton; + private ImageButton mReflowButton; + private ImageButton mOutlineButton; + private ImageButton mMoreButton; + private TextView mAnnotTypeText; + private ImageButton mAnnotButton; + private ViewAnimator mTopBarSwitcher; + private ImageButton mLinkButton; + private TopBarMode mTopBarMode = TopBarMode.Main; + private AcceptMode mAcceptMode; + private ImageButton mSearchBack; + private ImageButton mSearchFwd; + private EditText mSearchText; + private SearchTask mSearchTask; + private ImageButton mProofButton; + private ImageButton mSepsButton; + private AlertDialog.Builder mAlertBuilder; + private boolean mLinkHighlight = false; + private final Handler mHandler = new Handler(); + private boolean mAlertsActive= false; + private boolean mReflow = false; + private AsyncTask mAlertTask; + private AlertDialog mAlertDialog; + private FilePicker mFilePicker; + private String mProofFile; + private boolean mSepEnabled[][]; + + static private AlertDialog.Builder gAlertBuilder; + static public AlertDialog.Builder getAlertBuilder() {return gAlertBuilder;} + + public void createAlertWaiter() { + mAlertsActive = true; + // All mupdf library calls are performed on asynchronous tasks to avoid stalling + // the UI. Some calls can lead to javascript-invoked requests to display an + // alert dialog and collect a reply from the user. The task has to be blocked + // until the user's reply is received. This method creates an asynchronous task, + // the purpose of which is to wait of these requests and produce the dialog + // in response, while leaving the core blocked. When the dialog receives the + // user's response, it is sent to the core via replyToAlert, unblocking it. + // Another alert-waiting task is then created to pick up the next alert. + if (mAlertTask != null) { + mAlertTask.cancel(true); + mAlertTask = null; + } + if (mAlertDialog != null) { + mAlertDialog.cancel(); + mAlertDialog = null; + } + mAlertTask = new AsyncTask() { + + @Override + protected MuPDFAlert doInBackground(Void... arg0) { + if (!mAlertsActive) + return null; + + return core.waitForAlert(); + } + + @Override + protected void onPostExecute(final MuPDFAlert result) { + // core.waitForAlert may return null when shutting down + if (result == null) + return; + final MuPDFAlert.ButtonPressed pressed[] = new MuPDFAlert.ButtonPressed[3]; + for(int i = 0; i < 3; i++) + pressed[i] = MuPDFAlert.ButtonPressed.None; + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mAlertDialog = null; + if (mAlertsActive) { + int index = 0; + switch (which) { + case AlertDialog.BUTTON1: index=0; break; + case AlertDialog.BUTTON2: index=1; break; + case AlertDialog.BUTTON3: index=2; break; + } + result.buttonPressed = pressed[index]; + // Send the user's response to the core, so that it can + // continue processing. + core.replyToAlert(result); + // Create another alert-waiter to pick up the next alert. + createAlertWaiter(); + } + } + }; + mAlertDialog = mAlertBuilder.create(); + mAlertDialog.setTitle(result.title); + mAlertDialog.setMessage(result.message); + switch (result.iconType) + { + case Error: + break; + case Warning: + break; + case Question: + break; + case Status: + break; + } + switch (result.buttonGroupType) + { + case OkCancel: + mAlertDialog.setButton(AlertDialog.BUTTON2, getString(R.string.cancel), listener); + pressed[1] = MuPDFAlert.ButtonPressed.Cancel; + case Ok: + mAlertDialog.setButton(AlertDialog.BUTTON1, getString(R.string.okay), listener); + pressed[0] = MuPDFAlert.ButtonPressed.Ok; + break; + case YesNoCancel: + mAlertDialog.setButton(AlertDialog.BUTTON3, getString(R.string.cancel), listener); + pressed[2] = MuPDFAlert.ButtonPressed.Cancel; + case YesNo: + mAlertDialog.setButton(AlertDialog.BUTTON1, getString(R.string.yes), listener); + pressed[0] = MuPDFAlert.ButtonPressed.Yes; + mAlertDialog.setButton(AlertDialog.BUTTON2, getString(R.string.no), listener); + pressed[1] = MuPDFAlert.ButtonPressed.No; + break; + } + mAlertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mAlertDialog = null; + if (mAlertsActive) { + result.buttonPressed = MuPDFAlert.ButtonPressed.None; + core.replyToAlert(result); + createAlertWaiter(); + } + } + }); + + mAlertDialog.show(); + } + }; + + mAlertTask.executeOnExecutor(new ThreadPerTaskExecutor()); + } + + public void destroyAlertWaiter() { + mAlertsActive = false; + if (mAlertDialog != null) { + mAlertDialog.cancel(); + mAlertDialog = null; + } + if (mAlertTask != null) { + mAlertTask.cancel(true); + mAlertTask = null; + } + } + + private MuPDFCore openFile(String path) + { + int lastSlashPos = path.lastIndexOf('/'); + mFileName = new String(lastSlashPos == -1 + ? path + : path.substring(lastSlashPos+1)); + System.out.println("Trying to open " + path); + try + { + core = new MuPDFCore(this, path); + // New file: drop the old outline data + OutlineActivityData.set(null); + } + catch (Exception e) + { + System.out.println(e); + return null; + } + catch (java.lang.OutOfMemoryError e) + { + // out of memory is not an Exception, so we catch it separately. + System.out.println(e); + return null; + } + return core; + } + + private MuPDFCore openBuffer(byte buffer[], String magic) + { + System.out.println("Trying to open byte buffer"); + try + { + core = new MuPDFCore(this, buffer, magic); + // New file: drop the old outline data + OutlineActivityData.set(null); + } + catch (Exception e) + { + System.out.println(e); + return null; + } + return core; + } + + // determine whether the current activity is a proofing activity. + public boolean isProofing() + { + String format = core.fileFormat(); + return (format.equals("GPROOF")); + } + + /** Called when the activity is first created. */ + @Override + public void onCreate(final Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mAlertBuilder = new AlertDialog.Builder(this); + gAlertBuilder = mAlertBuilder; // keep a static copy of this that other classes can use + + if (core == null) { + core = (MuPDFCore)getLastNonConfigurationInstance(); + + if (savedInstanceState != null && savedInstanceState.containsKey("FileName")) { + mFileName = savedInstanceState.getString("FileName"); + } + } + if (core == null) { + Intent intent = getIntent(); + byte buffer[] = null; + + if (Intent.ACTION_VIEW.equals(intent.getAction())) { + Uri uri = intent.getData(); + System.out.println("URI to open is: " + uri); + if (uri.toString().startsWith("content://")) { + String reason = null; + try { + InputStream is = getContentResolver().openInputStream(uri); + int len = is.available(); + buffer = new byte[len]; + is.read(buffer, 0, len); + is.close(); + } + catch (java.lang.OutOfMemoryError e) { + System.out.println("Out of memory during buffer reading"); + reason = e.toString(); + } + catch (Exception e) { + System.out.println("Exception reading from stream: " + e); + + // Handle view requests from the Transformer Prime's file manager + // Hopefully other file managers will use this same scheme, if not + // using explicit paths. + // I'm hoping that this case below is no longer needed...but it's + // hard to test as the file manager seems to have changed in 4.x. + try { + Cursor cursor = getContentResolver().query(uri, new String[]{"_data"}, null, null, null); + if (cursor.moveToFirst()) { + String str = cursor.getString(0); + if (str == null) { + reason = "Couldn't parse data in intent"; + } + else { + uri = Uri.parse(str); + } + } + } + catch (Exception e2) { + System.out.println("Exception in Transformer Prime file manager code: " + e2); + reason = e2.toString(); + } + } + if (reason != null) { + buffer = null; + Resources res = getResources(); + AlertDialog alert = mAlertBuilder.create(); + setTitle(String.format(res.getString(R.string.cannot_open_document_Reason), reason)); + alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.dismiss), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + alert.show(); + return; + } + } + if (buffer != null) { + core = openBuffer(buffer, intent.getType()); + } else { + String path = Uri.decode(uri.getEncodedPath()); + if (path == null) { + path = uri.toString(); + } + core = openFile(path); + } + SearchTaskResult.set(null); + } + if (core != null && core.needsPassword()) { + requestPassword(savedInstanceState); + return; + } + if (core != null && core.countPages() == 0) + { + core = null; + } + } + if (core == null) + { + AlertDialog alert = mAlertBuilder.create(); + alert.setTitle(R.string.cannot_open_document); + alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.dismiss), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + alert.setOnCancelListener(new OnCancelListener() { + + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }); + alert.show(); + return; + } + + createUI(savedInstanceState); + + // hide the proof button if this file can't be proofed + if (!core.canProof()) { + mProofButton.setVisibility(View.INVISIBLE); + } + + if (isProofing()) { + + // start the activity with a new array + mSepEnabled = null; + + // show the separations button + mSepsButton.setVisibility(View.VISIBLE); + + // hide some other buttons + mLinkButton.setVisibility(View.INVISIBLE); + mReflowButton.setVisibility(View.INVISIBLE); + mOutlineButton.setVisibility(View.INVISIBLE); + mSearchButton.setVisibility(View.INVISIBLE); + mMoreButton.setVisibility(View.INVISIBLE); + } + else { + // hide the separations button + mSepsButton.setVisibility(View.INVISIBLE); + } + + } + + public void requestPassword(final Bundle savedInstanceState) { + mPasswordView = new EditText(this); + mPasswordView.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + mPasswordView.setTransformationMethod(new PasswordTransformationMethod()); + + AlertDialog alert = mAlertBuilder.create(); + alert.setTitle(R.string.enter_password); + alert.setView(mPasswordView); + alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.okay), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (core.authenticatePassword(mPasswordView.getText().toString())) { + createUI(savedInstanceState); + } else { + requestPassword(savedInstanceState); + } + } + }); + alert.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.cancel), + new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + alert.show(); + } + + public void createUI(Bundle savedInstanceState) { + if (core == null) + return; + + // Now create the UI. + // First create the document view + mDocView = new MuPDFReaderView(this) { + @Override + protected void onMoveToChild(int i) { + if (core == null) + return; + + mPageNumberView.setText(String.format("%d / %d", i + 1, + core.countPages())); + mPageSlider.setMax((core.countPages() - 1) * mPageSliderRes); + mPageSlider.setProgress(i * mPageSliderRes); + super.onMoveToChild(i); + } + + @Override + protected void onTapMainDocArea() { + if (!mButtonsVisible) { + showButtons(); + } else { + if (mTopBarMode == TopBarMode.Main) + hideButtons(); + } + } + + @Override + protected void onDocMotion() { + hideButtons(); + } + + @Override + protected void onHit(Hit item) { + switch (mTopBarMode) { + case Annot: + if (item == Hit.Annotation) { + showButtons(); + mTopBarMode = TopBarMode.Delete; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + break; + case Delete: + mTopBarMode = TopBarMode.Annot; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + // fall through + default: + // Not in annotation editing mode, but the pageview will + // still select and highlight hit annotations, so + // deselect just in case. + MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); + if (pageView != null) + pageView.deselectAnnotation(); + break; + } + } + }; + mDocView.setAdapter(new MuPDFPageAdapter(this, this, core)); + + mSearchTask = new SearchTask(this, core) { + @Override + protected void onTextFound(SearchTaskResult result) { + SearchTaskResult.set(result); + // Ask the ReaderView to move to the resulting page + mDocView.setDisplayedViewIndex(result.pageNumber); + // Make the ReaderView act on the change to SearchTaskResult + // via overridden onChildSetup method. + mDocView.resetupChildren(); + } + }; + + // Make the buttons overlay, and store all its + // controls in variables + makeButtonsView(); + + // Set up the page slider + int smax = Math.max(core.countPages()-1,1); + mPageSliderRes = ((10 + smax - 1)/smax) * 2; + + // Set the file-name text + mFilenameView.setText(mFileName); + + // Activate the seekbar + mPageSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + public void onStopTrackingTouch(SeekBar seekBar) { + mDocView.setDisplayedViewIndex((seekBar.getProgress()+mPageSliderRes/2)/mPageSliderRes); + } + + public void onStartTrackingTouch(SeekBar seekBar) {} + + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromUser) { + updatePageNumView((progress+mPageSliderRes/2)/mPageSliderRes); + } + }); + + // Activate the search-preparing button + mSearchButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + searchModeOn(); + } + }); + + // Activate the reflow button + mReflowButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + toggleReflow(); + } + }); + + if (core.fileFormat().startsWith("PDF") && core.isUnencryptedPDF() && !core.wasOpenedFromBuffer()) + { + mAnnotButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mTopBarMode = TopBarMode.Annot; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + }); + } + else + { + mAnnotButton.setVisibility(View.GONE); + } + + // Search invoking buttons are disabled while there is no text specified + mSearchBack.setEnabled(false); + mSearchFwd.setEnabled(false); + mSearchBack.setColorFilter(Color.argb(255, 128, 128, 128)); + mSearchFwd.setColorFilter(Color.argb(255, 128, 128, 128)); + + // React to interaction with the text widget + mSearchText.addTextChangedListener(new TextWatcher() { + + public void afterTextChanged(Editable s) { + boolean haveText = s.toString().length() > 0; + setButtonEnabled(mSearchBack, haveText); + setButtonEnabled(mSearchFwd, haveText); + + // Remove any previous search results + if (SearchTaskResult.get() != null && !mSearchText.getText().toString().equals(SearchTaskResult.get().txt)) { + SearchTaskResult.set(null); + mDocView.resetupChildren(); + } + } + public void beforeTextChanged(CharSequence s, int start, int count, + int after) {} + public void onTextChanged(CharSequence s, int start, int before, + int count) {} + }); + + //React to Done button on keyboard + mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) + search(1); + return false; + } + }); + + mSearchText.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) + search(1); + return false; + } + }); + + // Activate search invoking buttons + mSearchBack.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + search(-1); + } + }); + mSearchFwd.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + search(1); + } + }); + + mLinkButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + setLinkHighlight(!mLinkHighlight); + } + }); + + if (core.hasOutline()) { + mOutlineButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + OutlineItem outline[] = core.getOutline(); + if (outline != null) { + OutlineActivityData.get().items = outline; + Intent intent = new Intent(MuPDFActivity.this, OutlineActivity.class); + startActivityForResult(intent, OUTLINE_REQUEST); + } + } + }); + } else { + mOutlineButton.setVisibility(View.GONE); + } + + // Reenstate last state if it was recorded + SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); + mDocView.setDisplayedViewIndex(prefs.getInt("page"+mFileName, 0)); + + if (savedInstanceState == null || !savedInstanceState.getBoolean("ButtonsHidden", false)) + showButtons(); + + if(savedInstanceState != null && savedInstanceState.getBoolean("SearchMode", false)) + searchModeOn(); + + if(savedInstanceState != null && savedInstanceState.getBoolean("ReflowMode", false)) + reflowModeSet(true); + + // Stick the document view and the buttons overlay into a parent view + RelativeLayout layout = new RelativeLayout(this); + layout.addView(mDocView); + layout.addView(mButtonsView); + setContentView(layout); + + if (isProofing()) { + // go to the current page + int currentPage = getIntent().getIntExtra("startingPage", 0); + mDocView.setDisplayedViewIndex(currentPage); + } + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case OUTLINE_REQUEST: + if (resultCode >= 0) + mDocView.setDisplayedViewIndex(resultCode); + break; + case PRINT_REQUEST: + if (resultCode == RESULT_CANCELED) + showInfo(getString(R.string.print_failed)); + break; + case FILEPICK_REQUEST: + if (mFilePicker != null && resultCode == RESULT_OK) + mFilePicker.onPick(data.getData()); + case PROOF_REQUEST: + // we're returning from a proofing activity + + if (mProofFile != null) + { + core.endProof(mProofFile); + mProofFile = null; + } + + // return the top bar to default + mTopBarMode = TopBarMode.Main; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + super.onActivityResult(requestCode, resultCode, data); + } + + public Object onRetainNonConfigurationInstance() + { + MuPDFCore mycore = core; + core = null; + return mycore; + } + + private void reflowModeSet(boolean reflow) + { + mReflow = reflow; + mDocView.setAdapter(mReflow ? new MuPDFReflowAdapter(this, core) : new MuPDFPageAdapter(this, this, core)); + mReflowButton.setColorFilter(mReflow ? Color.argb(0xFF, 172, 114, 37) : Color.argb(0xFF, 255, 255, 255)); + setButtonEnabled(mAnnotButton, !reflow); + setButtonEnabled(mSearchButton, !reflow); + if (reflow) setLinkHighlight(false); + setButtonEnabled(mLinkButton, !reflow); + setButtonEnabled(mMoreButton, !reflow); + mDocView.refresh(mReflow); + } + + private void toggleReflow() { + reflowModeSet(!mReflow); + showInfo(mReflow ? getString(R.string.entering_reflow_mode) : getString(R.string.leaving_reflow_mode)); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + if (mFileName != null && mDocView != null) { + outState.putString("FileName", mFileName); + + // Store current page in the prefs against the file name, + // so that we can pick it up each time the file is loaded + // Other info is needed only for screen-orientation change, + // so it can go in the bundle + SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); + edit.commit(); + } + + if (!mButtonsVisible) + outState.putBoolean("ButtonsHidden", true); + + if (mTopBarMode == TopBarMode.Search) + outState.putBoolean("SearchMode", true); + + if (mReflow) + outState.putBoolean("ReflowMode", true); + } + + @Override + protected void onPause() { + super.onPause(); + + if (mSearchTask != null) + mSearchTask.stop(); + + if (mFileName != null && mDocView != null) { + SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt("page"+mFileName, mDocView.getDisplayedViewIndex()); + edit.commit(); + } + } + + public void onDestroy() + { + if (mDocView != null) { + mDocView.applyToChildren(new ReaderView.ViewMapper() { + void applyToView(View view) { + ((MuPDFView)view).releaseBitmaps(); + } + }); + } + if (core != null) + core.onDestroy(); + if (mAlertTask != null) { + mAlertTask.cancel(true); + mAlertTask = null; + } + core = null; + super.onDestroy(); + } + + private void setButtonEnabled(ImageButton button, boolean enabled) { + button.setEnabled(enabled); + button.setColorFilter(enabled ? Color.argb(255, 255, 255, 255) : Color.argb(255, 128, 128, 128)); + } + + private void setLinkHighlight(boolean highlight) { + mLinkHighlight = highlight; + // LINK_COLOR tint + mLinkButton.setColorFilter(highlight ? Color.argb(0xFF, 172, 114, 37) : Color.argb(0xFF, 255, 255, 255)); + // Inform pages of the change. + mDocView.setLinksEnabled(highlight); + } + + private void showButtons() { + if (core == null) + return; + if (!mButtonsVisible) { + mButtonsVisible = true; + // Update page number text and slider + int index = mDocView.getDisplayedViewIndex(); + updatePageNumView(index); + mPageSlider.setMax((core.countPages()-1)*mPageSliderRes); + mPageSlider.setProgress(index * mPageSliderRes); + if (mTopBarMode == TopBarMode.Search) { + mSearchText.requestFocus(); + showKeyboard(); + } + + Animation anim = new TranslateAnimation(0, 0, -mTopBarSwitcher.getHeight(), 0); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) { + mTopBarSwitcher.setVisibility(View.VISIBLE); + } + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) {} + }); + mTopBarSwitcher.startAnimation(anim); + + anim = new TranslateAnimation(0, 0, mPageSlider.getHeight(), 0); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) { + mPageSlider.setVisibility(View.VISIBLE); + } + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) { + mPageNumberView.setVisibility(View.VISIBLE); + } + }); + mPageSlider.startAnimation(anim); + } + } + + private void hideButtons() { + if (mButtonsVisible) { + mButtonsVisible = false; + hideKeyboard(); + + Animation anim = new TranslateAnimation(0, 0, 0, -mTopBarSwitcher.getHeight()); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) {} + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) { + mTopBarSwitcher.setVisibility(View.INVISIBLE); + } + }); + mTopBarSwitcher.startAnimation(anim); + + anim = new TranslateAnimation(0, 0, 0, mPageSlider.getHeight()); + anim.setDuration(200); + anim.setAnimationListener(new Animation.AnimationListener() { + public void onAnimationStart(Animation animation) { + mPageNumberView.setVisibility(View.INVISIBLE); + } + public void onAnimationRepeat(Animation animation) {} + public void onAnimationEnd(Animation animation) { + mPageSlider.setVisibility(View.INVISIBLE); + } + }); + mPageSlider.startAnimation(anim); + } + } + + private void searchModeOn() { + if (mTopBarMode != TopBarMode.Search) { + mTopBarMode = TopBarMode.Search; + //Focus on EditTextWidget + mSearchText.requestFocus(); + showKeyboard(); + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + } + + private void searchModeOff() { + if (mTopBarMode == TopBarMode.Search) { + mTopBarMode = TopBarMode.Main; + hideKeyboard(); + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + SearchTaskResult.set(null); + // Make the ReaderView act on the change to mSearchTaskResult + // via overridden onChildSetup method. + mDocView.resetupChildren(); + } + } + + private void updatePageNumView(int index) { + if (core == null) + return; + mPageNumberView.setText(String.format("%d / %d", index + 1, core.countPages())); + } + + private void printDoc() { + if (!core.fileFormat().startsWith("PDF")) { + showInfo(getString(R.string.format_currently_not_supported)); + return; + } + + Intent myIntent = getIntent(); + Uri docUri = myIntent != null ? myIntent.getData() : null; + + if (docUri == null) { + showInfo(getString(R.string.print_failed)); + } + + if (docUri.getScheme() == null) + docUri = Uri.parse("file://"+docUri.toString()); + + Intent printIntent = new Intent(this, PrintDialogActivity.class); + printIntent.setDataAndType(docUri, "aplication/pdf"); + printIntent.putExtra("title", mFileName); + startActivityForResult(printIntent, PRINT_REQUEST); + } + + private void showInfo(String message) { + mInfoView.setText(message); + + int currentApiVersion = android.os.Build.VERSION.SDK_INT; + if (currentApiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { + SafeAnimatorInflater safe = new SafeAnimatorInflater((Activity)this, R.animator.info, (View)mInfoView); + } else { + mInfoView.setVisibility(View.VISIBLE); + mHandler.postDelayed(new Runnable() { + public void run() { + mInfoView.setVisibility(View.INVISIBLE); + } + }, 500); + } + } + + private void makeButtonsView() { + mButtonsView = getLayoutInflater().inflate(R.layout.buttons,null); + mFilenameView = (TextView)mButtonsView.findViewById(R.id.docNameText); + mPageSlider = (SeekBar)mButtonsView.findViewById(R.id.pageSlider); + mPageNumberView = (TextView)mButtonsView.findViewById(R.id.pageNumber); + mInfoView = (TextView)mButtonsView.findViewById(R.id.info); + mSearchButton = (ImageButton)mButtonsView.findViewById(R.id.searchButton); + mReflowButton = (ImageButton)mButtonsView.findViewById(R.id.reflowButton); + mOutlineButton = (ImageButton)mButtonsView.findViewById(R.id.outlineButton); + mAnnotButton = (ImageButton)mButtonsView.findViewById(R.id.editAnnotButton); + mAnnotTypeText = (TextView)mButtonsView.findViewById(R.id.annotType); + mTopBarSwitcher = (ViewAnimator)mButtonsView.findViewById(R.id.switcher); + mSearchBack = (ImageButton)mButtonsView.findViewById(R.id.searchBack); + mSearchFwd = (ImageButton)mButtonsView.findViewById(R.id.searchForward); + mSearchText = (EditText)mButtonsView.findViewById(R.id.searchText); + mLinkButton = (ImageButton)mButtonsView.findViewById(R.id.linkButton); + mMoreButton = (ImageButton)mButtonsView.findViewById(R.id.moreButton); + mProofButton = (ImageButton)mButtonsView.findViewById(R.id.proofButton); + mSepsButton = (ImageButton)mButtonsView.findViewById(R.id.sepsButton); + mTopBarSwitcher.setVisibility(View.INVISIBLE); + mPageNumberView.setVisibility(View.INVISIBLE); + mInfoView.setVisibility(View.INVISIBLE); + + mPageSlider.setVisibility(View.INVISIBLE); + if (!core.gprfSupported()) { + mProofButton.setVisibility(View.INVISIBLE); + } + mSepsButton.setVisibility(View.INVISIBLE); + } + + public void OnMoreButtonClick(View v) { + mTopBarMode = TopBarMode.More; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + public void OnCancelMoreButtonClick(View v) { + mTopBarMode = TopBarMode.Main; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + public void OnPrintButtonClick(View v) { + printDoc(); + } + + // start a proof activity with the given resolution. + public void proofWithResolution (int resolution) + { + mProofFile = core.startProof(resolution); + Uri uri = Uri.parse("file://"+mProofFile); + Intent intent = new Intent(this, MuPDFActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(uri); + // add the current page so it can be found when the activity is running + intent.putExtra("startingPage", mDocView.getDisplayedViewIndex()); + startActivityForResult(intent, PROOF_REQUEST); + } + + public void OnProofButtonClick(final View v) + { + // set up the menu or resolutions. + final PopupMenu popup = new PopupMenu(this, v); + popup.getMenu().add(0, 1, 0, "Select a resolution:"); + popup.getMenu().add(0, 72, 0, "72"); + popup.getMenu().add(0, 96, 0, "96"); + popup.getMenu().add(0, 150, 0, "150"); + popup.getMenu().add(0, 300, 0, "300"); + popup.getMenu().add(0, 600, 0, "600"); + popup.getMenu().add(0, 1200, 0, "1200"); + popup.getMenu().add(0, 2400, 0, "2400"); + + // prevent the first item from being dismissed. + // is there not a better way to do this? It requires minimum API 14 + MenuItem item = popup.getMenu().getItem(0); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + item.setActionView(new View(v.getContext())); + item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return false; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + return false; + } + }); + + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + int id = item.getItemId(); + if (id != 1) { + // it's a resolution. The id is also the resolution value + proofWithResolution(id); + return true; + } + return false; + } + }); + + popup.show(); + } + + public void OnSepsButtonClick(final View v) + { + if (isProofing()) { + + // get the current page + final int currentPage = mDocView.getDisplayedViewIndex(); + + // buid a popup menu based on the given separations + final PopupMenu menu = new PopupMenu(this, v); + + // This makes the popup menu display icons, which by default it does not do. + // I worry that this relies on the internals of PopupMenu, which could change. + try { + Field[] fields = menu.getClass().getDeclaredFields(); + for (Field field : fields) { + if ("mPopup".equals(field.getName())) { + field.setAccessible(true); + Object menuPopupHelper = field.get(menu); + Class classPopupHelper = Class.forName(menuPopupHelper + .getClass().getName()); + Method setForceIcons = classPopupHelper.getMethod( + "setForceShowIcon", boolean.class); + setForceIcons.invoke(menuPopupHelper, true); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + // get the maximum number of seps on any page. + // We use this to dimension an array further down + int maxSeps = 0; + int numPages = core.countPages(); + for (int page=0; pagemaxSeps) + maxSeps = numSeps; + } + + // if this is the first time, create the "enabled" array + if (mSepEnabled==null) { + mSepEnabled = new boolean[numPages][maxSeps]; + for (int page=0; page> 24) & 0xFF; + int red = (sep.rgba >> 16) & 0xFF; + int green = (sep.rgba >> 8 ) & 0xFF; + int blue = (sep.rgba >> 0 ) & 0xFF; + int color = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); + + ShapeDrawable swatch = new ShapeDrawable (new RectShape()); + swatch.setIntrinsicHeight(iconSize); + swatch.setIntrinsicWidth(iconSize); + swatch.setBounds(new Rect(0, 0, iconSize, iconSize)); + swatch.getPaint().setColor(color); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + item.setIcon(swatch); + + // check it (or not) + item.setChecked(mSepEnabled[currentPage][i]); + + // establishing a menu item listener + item.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // someone tapped a menu item. get the ID + int sep = item.getItemId(); + + // toggle the sep + mSepEnabled[currentPage][sep] = !mSepEnabled[currentPage][sep]; + item.setChecked(mSepEnabled[currentPage][sep]); + core.controlSepOnPage(currentPage, sep, !mSepEnabled[currentPage][sep]); + + // prevent the menu from being dismissed by these items + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + item.setActionView(new View(v.getContext())); + item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return false; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + return false; + } + }); + return false; + } + }); + + // tell core to enable or disable each sep as appropriate + // but don't refresh the page yet. + core.controlSepOnPage(currentPage, i, !mSepEnabled[currentPage][i]); + } + + // add one for done + MenuItem itemDone = menu.getMenu().add(0, 0, 0, "Done"); + itemDone.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // refresh the view + mDocView.refresh(false); + return true; + } + }); + + // show the menu + menu.show(); + } + + } + + public void OnCopyTextButtonClick(View v) { + mTopBarMode = TopBarMode.Accept; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + mAcceptMode = AcceptMode.CopyText; + mDocView.setMode(MuPDFReaderView.Mode.Selecting); + mAnnotTypeText.setText(getString(R.string.copy_text)); + showInfo(getString(R.string.select_text)); + } + + public void OnEditAnnotButtonClick(View v) { + mTopBarMode = TopBarMode.Annot; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + public void OnCancelAnnotButtonClick(View v) { + mTopBarMode = TopBarMode.More; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + public void OnHighlightButtonClick(View v) { + mTopBarMode = TopBarMode.Accept; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + mAcceptMode = AcceptMode.Highlight; + mDocView.setMode(MuPDFReaderView.Mode.Selecting); + mAnnotTypeText.setText(R.string.highlight); + showInfo(getString(R.string.select_text)); + } + + public void OnUnderlineButtonClick(View v) { + mTopBarMode = TopBarMode.Accept; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + mAcceptMode = AcceptMode.Underline; + mDocView.setMode(MuPDFReaderView.Mode.Selecting); + mAnnotTypeText.setText(R.string.underline); + showInfo(getString(R.string.select_text)); + } + + public void OnStrikeOutButtonClick(View v) { + mTopBarMode = TopBarMode.Accept; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + mAcceptMode = AcceptMode.StrikeOut; + mDocView.setMode(MuPDFReaderView.Mode.Selecting); + mAnnotTypeText.setText(R.string.strike_out); + showInfo(getString(R.string.select_text)); + } + + public void OnInkButtonClick(View v) { + mTopBarMode = TopBarMode.Accept; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + mAcceptMode = AcceptMode.Ink; + mDocView.setMode(MuPDFReaderView.Mode.Drawing); + mAnnotTypeText.setText(R.string.ink); + showInfo(getString(R.string.draw_annotation)); + } + + public void OnCancelAcceptButtonClick(View v) { + MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); + if (pageView != null) { + pageView.deselectText(); + pageView.cancelDraw(); + } + mDocView.setMode(MuPDFReaderView.Mode.Viewing); + switch (mAcceptMode) { + case CopyText: + mTopBarMode = TopBarMode.More; + break; + default: + mTopBarMode = TopBarMode.Annot; + break; + } + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + public void OnAcceptButtonClick(View v) { + MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); + boolean success = false; + switch (mAcceptMode) { + case CopyText: + if (pageView != null) + success = pageView.copySelection(); + mTopBarMode = TopBarMode.More; + showInfo(success?getString(R.string.copied_to_clipboard):getString(R.string.no_text_selected)); + break; + + case Highlight: + if (pageView != null) + success = pageView.markupSelection(Annotation.Type.HIGHLIGHT); + mTopBarMode = TopBarMode.Annot; + if (!success) + showInfo(getString(R.string.no_text_selected)); + break; + + case Underline: + if (pageView != null) + success = pageView.markupSelection(Annotation.Type.UNDERLINE); + mTopBarMode = TopBarMode.Annot; + if (!success) + showInfo(getString(R.string.no_text_selected)); + break; + + case StrikeOut: + if (pageView != null) + success = pageView.markupSelection(Annotation.Type.STRIKEOUT); + mTopBarMode = TopBarMode.Annot; + if (!success) + showInfo(getString(R.string.no_text_selected)); + break; + + case Ink: + if (pageView != null) + success = pageView.saveDraw(); + mTopBarMode = TopBarMode.Annot; + if (!success) + showInfo(getString(R.string.nothing_to_save)); + break; + } + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + mDocView.setMode(MuPDFReaderView.Mode.Viewing); + } + + public void OnCancelSearchButtonClick(View v) { + searchModeOff(); + } + + public void OnDeleteButtonClick(View v) { + MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); + if (pageView != null) + pageView.deleteSelectedAnnotation(); + mTopBarMode = TopBarMode.Annot; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + public void OnCancelDeleteButtonClick(View v) { + MuPDFView pageView = (MuPDFView) mDocView.getDisplayedView(); + if (pageView != null) + pageView.deselectAnnotation(); + mTopBarMode = TopBarMode.Annot; + mTopBarSwitcher.setDisplayedChild(mTopBarMode.ordinal()); + } + + private void showKeyboard() { + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.showSoftInput(mSearchText, 0); + } + + private void hideKeyboard() { + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.hideSoftInputFromWindow(mSearchText.getWindowToken(), 0); + } + + private void search(int direction) { + hideKeyboard(); + int displayPage = mDocView.getDisplayedViewIndex(); + SearchTaskResult r = SearchTaskResult.get(); + int searchPage = r != null ? r.pageNumber : -1; + mSearchTask.go(mSearchText.getText().toString(), direction, displayPage, searchPage); + } + + @Override + public boolean onSearchRequested() { + if (mButtonsVisible && mTopBarMode == TopBarMode.Search) { + hideButtons(); + } else { + showButtons(); + searchModeOn(); + } + return super.onSearchRequested(); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (mButtonsVisible && mTopBarMode != TopBarMode.Search) { + hideButtons(); + } else { + showButtons(); + searchModeOff(); + } + return super.onPrepareOptionsMenu(menu); + } + + @Override + protected void onStart() { + if (core != null) + { + core.startAlerts(); + createAlertWaiter(); + } + + super.onStart(); + } + + @Override + protected void onStop() { + if (core != null) + { + destroyAlertWaiter(); + core.stopAlerts(); + } + + super.onStop(); + } + + @Override + public void onBackPressed() { + if (core != null && core.hasChanges()) { + DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) + core.save(); + + finish(); + } + }; + AlertDialog alert = mAlertBuilder.create(); + alert.setTitle("MuPDF"); + alert.setMessage(getString(R.string.document_has_changes_save_them_)); + alert.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.yes), listener); + alert.setButton(AlertDialog.BUTTON_NEGATIVE, getString(R.string.no), listener); + alert.show(); + } else { + super.onBackPressed(); + } + } + + @Override + public void performPickFor(FilePicker picker) { + mFilePicker = picker; + Intent intent = new Intent(this, ChoosePDFActivity.class); + intent.setAction(ChoosePDFActivity.PICK_KEY_FILE); + startActivityForResult(intent, FILEPICK_REQUEST); + } + +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlert.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlert.java new file mode 100644 index 00000000..76ed3a65 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlert.java @@ -0,0 +1,21 @@ +package com.artifex.mupdfdemo; + +public class MuPDFAlert { + public enum IconType {Error,Warning,Question,Status}; + public enum ButtonPressed {None,Ok,Cancel,No,Yes}; + public enum ButtonGroupType {Ok,OkCancel,YesNo,YesNoCancel}; + + public final String message; + public final IconType iconType; + public final ButtonGroupType buttonGroupType; + public final String title; + public ButtonPressed buttonPressed; + + MuPDFAlert(String aMessage, IconType aIconType, ButtonGroupType aButtonGroupType, String aTitle, ButtonPressed aButtonPressed) { + message = aMessage; + iconType = aIconType; + buttonGroupType = aButtonGroupType; + title = aTitle; + buttonPressed = aButtonPressed; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java new file mode 100644 index 00000000..5d65768f --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFAlertInternal.java @@ -0,0 +1,30 @@ +package com.artifex.mupdfdemo; + +// Version of MuPDFAlert without enums to simplify JNI +public class MuPDFAlertInternal { + public final String message; + public final int iconType; + public final int buttonGroupType; + public final String title; + public int buttonPressed; + + MuPDFAlertInternal(String aMessage, int aIconType, int aButtonGroupType, String aTitle, int aButtonPressed) { + message = aMessage; + iconType = aIconType; + buttonGroupType = aButtonGroupType; + title = aTitle; + buttonPressed = aButtonPressed; + } + + MuPDFAlertInternal(MuPDFAlert alert) { + message = alert.message; + iconType = alert.iconType.ordinal(); + buttonGroupType = alert.buttonGroupType.ordinal(); + title = alert.message; + buttonPressed = alert.buttonPressed.ordinal(); + } + + MuPDFAlert toAlert() { + return new MuPDFAlert(message, MuPDFAlert.IconType.values()[iconType], MuPDFAlert.ButtonGroupType.values()[buttonGroupType], title, MuPDFAlert.ButtonPressed.values()[buttonPressed]); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java new file mode 100644 index 00000000..b95d8e93 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCancellableTaskDefinition.java @@ -0,0 +1,38 @@ +package com.artifex.mupdfdemo; + +public abstract class MuPDFCancellableTaskDefinition implements CancellableTaskDefinition +{ + private MuPDFCore.Cookie cookie; + + public MuPDFCancellableTaskDefinition(MuPDFCore core) + { + this.cookie = core.new Cookie(); + } + + @Override + public void doCancel() + { + if (cookie == null) + return; + + cookie.abort(); + } + + @Override + public void doCleanup() + { + if (cookie == null) + return; + + cookie.destroy(); + cookie = null; + } + + @Override + public final Result doInBackground(Params ... params) + { + return doInBackground(cookie, params); + } + + public abstract Result doInBackground(MuPDFCore.Cookie cookie, Params ... params); +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCore.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCore.java new file mode 100644 index 00000000..80174416 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFCore.java @@ -0,0 +1,402 @@ +package com.artifex.mupdfdemo; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.PointF; +import android.graphics.RectF; + +import java.util.ArrayList; + +public class MuPDFCore +{ + /* load our native library */ + private static boolean gs_so_available = false; + static { + System.out.println("Loading dll"); + System.loadLibrary("mupdf_java"); + System.out.println("Loaded dll"); + if (gprfSupportedInternal()) + { + try { + System.loadLibrary("gs"); + gs_so_available = true; + } + catch (UnsatisfiedLinkError e) { + gs_so_available = false; + } + } + } + + /* Readable members */ + private int numPages = -1; + private float pageWidth; + private float pageHeight; + private long globals; + private byte fileBuffer[]; + private String file_format; + private boolean isUnencryptedPDF; + private final boolean wasOpenedFromBuffer; + + /* The native functions */ + private static native boolean gprfSupportedInternal(); + private native long openFile(String filename); + private native long openBuffer(String magic); + private native String fileFormatInternal(); + private native boolean isUnencryptedPDFInternal(); + private native int countPagesInternal(); + private native void gotoPageInternal(int localActionPageNum); + private native float getPageWidth(); + private native float getPageHeight(); + private native void drawPage(Bitmap bitmap, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH, + long cookiePtr); + private native void updatePageInternal(Bitmap bitmap, + int page, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH, + long cookiePtr); + private native RectF[] searchPage(String text); + private native TextChar[][][][] text(); + private native byte[] textAsHtml(); + private native void addMarkupAnnotationInternal(PointF[] quadPoints, int type); + private native void addInkAnnotationInternal(PointF[][] arcs); + private native void deleteAnnotationInternal(int annot_index); + private native int passClickEventInternal(int page, float x, float y); + private native void setFocusedWidgetChoiceSelectedInternal(String [] selected); + private native String [] getFocusedWidgetChoiceSelected(); + private native String [] getFocusedWidgetChoiceOptions(); + private native int getFocusedWidgetSignatureState(); + private native String checkFocusedSignatureInternal(); + private native boolean signFocusedSignatureInternal(String keyFile, String password); + private native int setFocusedWidgetTextInternal(String text); + private native String getFocusedWidgetTextInternal(); + private native int getFocusedWidgetTypeInternal(); + private native LinkInfo [] getPageLinksInternal(int page); + private native RectF[] getWidgetAreasInternal(int page); + private native Annotation[] getAnnotationsInternal(int page); + private native OutlineItem [] getOutlineInternal(); + private native boolean hasOutlineInternal(); + private native boolean needsPasswordInternal(); + private native boolean authenticatePasswordInternal(String password); + private native MuPDFAlertInternal waitForAlertInternal(); + private native void replyToAlertInternal(MuPDFAlertInternal alert); + private native void startAlertsInternal(); + private native void stopAlertsInternal(); + private native void destroying(); + private native boolean hasChangesInternal(); + private native void saveInternal(); + private native long createCookie(); + private native void destroyCookie(long cookie); + private native void abortCookie(long cookie); + + private native String startProofInternal(int resolution); + private native void endProofInternal(String filename); + private native int getNumSepsOnPageInternal(int page); + private native int controlSepOnPageInternal(int page, int sep, boolean disable); + private native Separation getSepInternal(int page, int sep); + + public native boolean javascriptSupported(); + + public class Cookie + { + private final long cookiePtr; + + public Cookie() + { + cookiePtr = createCookie(); + if (cookiePtr == 0) + throw new OutOfMemoryError(); + } + + public void abort() + { + abortCookie(cookiePtr); + } + + public void destroy() + { + // We could do this in finalize, but there's no guarantee that + // a finalize will occur before the muPDF context occurs. + destroyCookie(cookiePtr); + } + } + + public MuPDFCore(Context context, String filename) throws Exception + { + globals = openFile(filename); + if (globals == 0) + { + throw new Exception(String.format(context.getString(R.string.cannot_open_file_Path), filename)); + } + file_format = fileFormatInternal(); + isUnencryptedPDF = isUnencryptedPDFInternal(); + wasOpenedFromBuffer = false; + } + + public MuPDFCore(Context context, byte buffer[], String magic) throws Exception { + fileBuffer = buffer; + globals = openBuffer(magic != null ? magic : ""); + if (globals == 0) + { + throw new Exception(context.getString(R.string.cannot_open_buffer)); + } + file_format = fileFormatInternal(); + isUnencryptedPDF = isUnencryptedPDFInternal(); + wasOpenedFromBuffer = true; + } + + public int countPages() + { + if (numPages < 0) + numPages = countPagesSynchronized(); + return numPages; + } + + public String fileFormat() + { + return file_format; + } + + public boolean isUnencryptedPDF() + { + return isUnencryptedPDF; + } + + public boolean wasOpenedFromBuffer() + { + return wasOpenedFromBuffer; + } + + private synchronized int countPagesSynchronized() { + return countPagesInternal(); + } + + /* Shim function */ + private void gotoPage(int page) + { + if (page > numPages-1) + page = numPages-1; + else if (page < 0) + page = 0; + gotoPageInternal(page); + this.pageWidth = getPageWidth(); + this.pageHeight = getPageHeight(); + } + + public synchronized PointF getPageSize(int page) { + gotoPage(page); + return new PointF(pageWidth, pageHeight); + } + + public MuPDFAlert waitForAlert() { + MuPDFAlertInternal alert = waitForAlertInternal(); + return alert != null ? alert.toAlert() : null; + } + + public void replyToAlert(MuPDFAlert alert) { + replyToAlertInternal(new MuPDFAlertInternal(alert)); + } + + public void stopAlerts() { + stopAlertsInternal(); + } + + public void startAlerts() { + startAlertsInternal(); + } + + public synchronized void onDestroy() { + destroying(); + globals = 0; + } + + public synchronized void drawPage(Bitmap bm, int page, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH, + MuPDFCore.Cookie cookie) { + gotoPage(page); + drawPage(bm, pageW, pageH, patchX, patchY, patchW, patchH, cookie.cookiePtr); + } + + public synchronized void updatePage(Bitmap bm, int page, + int pageW, int pageH, + int patchX, int patchY, + int patchW, int patchH, + MuPDFCore.Cookie cookie) { + updatePageInternal(bm, page, pageW, pageH, patchX, patchY, patchW, patchH, cookie.cookiePtr); + } + + public synchronized PassClickResult passClickEvent(int page, float x, float y) { + boolean changed = passClickEventInternal(page, x, y) != 0; + + switch (WidgetType.values()[getFocusedWidgetTypeInternal()]) + { + case TEXT: + return new PassClickResultText(changed, getFocusedWidgetTextInternal()); + case LISTBOX: + case COMBOBOX: + return new PassClickResultChoice(changed, getFocusedWidgetChoiceOptions(), getFocusedWidgetChoiceSelected()); + case SIGNATURE: + return new PassClickResultSignature(changed, getFocusedWidgetSignatureState()); + default: + return new PassClickResult(changed); + } + + } + + public synchronized boolean setFocusedWidgetText(int page, String text) { + boolean success; + gotoPage(page); + success = setFocusedWidgetTextInternal(text) != 0 ? true : false; + + return success; + } + + public synchronized void setFocusedWidgetChoiceSelected(String [] selected) { + setFocusedWidgetChoiceSelectedInternal(selected); + } + + public synchronized String checkFocusedSignature() { + return checkFocusedSignatureInternal(); + } + + public synchronized boolean signFocusedSignature(String keyFile, String password) { + return signFocusedSignatureInternal(keyFile, password); + } + + public synchronized LinkInfo [] getPageLinks(int page) { + return getPageLinksInternal(page); + } + + public synchronized RectF [] getWidgetAreas(int page) { + return getWidgetAreasInternal(page); + } + + public synchronized Annotation [] getAnnoations(int page) { + return getAnnotationsInternal(page); + } + + public synchronized RectF [] searchPage(int page, String text) { + gotoPage(page); + return searchPage(text); + } + + public synchronized byte[] html(int page) { + gotoPage(page); + return textAsHtml(); + } + + public synchronized TextWord [][] textLines(int page) { + gotoPage(page); + TextChar[][][][] chars = text(); + + // The text of the page held in a hierarchy (blocks, lines, spans). + // Currently we don't need to distinguish the blocks level or + // the spans, and we need to collect the text into words. + ArrayList lns = new ArrayList(); + + for (TextChar[][][] bl: chars) { + if (bl == null) + continue; + for (TextChar[][] ln: bl) { + ArrayList wds = new ArrayList(); + TextWord wd = new TextWord(); + + for (TextChar[] sp: ln) { + for (TextChar tc: sp) { + if (tc.c != ' ') { + wd.Add(tc); + } else if (wd.w.length() > 0) { + wds.add(wd); + wd = new TextWord(); + } + } + } + + if (wd.w.length() > 0) + wds.add(wd); + + if (wds.size() > 0) + lns.add(wds.toArray(new TextWord[wds.size()])); + } + } + + return lns.toArray(new TextWord[lns.size()][]); + } + + public synchronized void addMarkupAnnotation(int page, PointF[] quadPoints, Annotation.Type type) { + gotoPage(page); + addMarkupAnnotationInternal(quadPoints, type.ordinal()); + } + + public synchronized void addInkAnnotation(int page, PointF[][] arcs) { + gotoPage(page); + addInkAnnotationInternal(arcs); + } + + public synchronized void deleteAnnotation(int page, int annot_index) { + gotoPage(page); + deleteAnnotationInternal(annot_index); + } + + public synchronized boolean hasOutline() { + return hasOutlineInternal(); + } + + public synchronized OutlineItem [] getOutline() { + return getOutlineInternal(); + } + + public synchronized boolean needsPassword() { + return needsPasswordInternal(); + } + + public synchronized boolean authenticatePassword(String password) { + return authenticatePasswordInternal(password); + } + + public synchronized boolean hasChanges() { + return hasChangesInternal(); + } + + public synchronized void save() { + saveInternal(); + } + + public synchronized String startProof(int resolution) { + return startProofInternal(resolution); + } + + public synchronized void endProof(String filename) { + endProofInternal(filename); + } + + public static boolean gprfSupported() { + if (gs_so_available == false) + return false; + return gprfSupportedInternal(); + } + + public boolean canProof() + { + String format = fileFormat(); + if (format.contains("PDF")) + return true; + return false; + } + + public synchronized int getNumSepsOnPage(int page) { + return getNumSepsOnPageInternal(page); + } + + public synchronized int controlSepOnPage(int page, int sep, boolean disable) { + return controlSepOnPageInternal(page, sep, disable); + } + + public synchronized Separation getSep(int page, int sep) { + return getSepInternal(page, sep); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java new file mode 100644 index 00000000..abdac845 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageAdapter.java @@ -0,0 +1,87 @@ +package com.artifex.mupdfdemo; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.PointF; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +public class MuPDFPageAdapter extends BaseAdapter { + private final Context mContext; + private final FilePicker.FilePickerSupport mFilePickerSupport; + private final MuPDFCore mCore; + private final SparseArray mPageSizes = new SparseArray(); + private Bitmap mSharedHqBm; + + public MuPDFPageAdapter(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core) { + mContext = c; + mFilePickerSupport = filePickerSupport; + mCore = core; + } + + public int getCount() { + return mCore.countPages(); + } + + public Object getItem(int position) { + return null; + } + + public long getItemId(int position) { + return 0; + } + + public void releaseBitmaps() + { + // recycle and release the shared bitmap. + if (mSharedHqBm!=null) + mSharedHqBm.recycle(); + mSharedHqBm = null; + } + + public View getView(final int position, View convertView, ViewGroup parent) { + final MuPDFPageView pageView; + if (convertView == null) { + if (mSharedHqBm == null || mSharedHqBm.getWidth() != parent.getWidth() || mSharedHqBm.getHeight() != parent.getHeight()) + mSharedHqBm = Bitmap.createBitmap(parent.getWidth(), parent.getHeight(), Bitmap.Config.ARGB_8888); + + pageView = new MuPDFPageView(mContext, mFilePickerSupport, mCore, new Point(parent.getWidth(), parent.getHeight()), mSharedHqBm); + } else { + pageView = (MuPDFPageView) convertView; + } + + PointF pageSize = mPageSizes.get(position); + if (pageSize != null) { + // We already know the page size. Set it up + // immediately + pageView.setPage(position, pageSize); + } else { + // Page size as yet unknown. Blank it for now, and + // start a background task to find the size + pageView.blank(position); + AsyncTask sizingTask = new AsyncTask() { + @Override + protected PointF doInBackground(Void... arg0) { + return mCore.getPageSize(position); + } + + @Override + protected void onPostExecute(PointF result) { + super.onPostExecute(result); + // We now know the page size + mPageSizes.put(position, result); + // Check that this view hasn't been reused for + // another page since we started + if (pageView.getPage() == position) + pageView.setPage(position, result); + } + }; + + sizingTask.execute((Void)null); + } + return pageView; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java new file mode 100644 index 00000000..ff6b6bbb --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java @@ -0,0 +1,692 @@ +package com.artifex.mupdfdemo; + +import java.util.ArrayList; + +import com.artifex.mupdfdemo.MuPDFCore.Cookie; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.ClipData; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.net.Uri; +import android.os.Build; +import android.text.method.PasswordTransformationMethod; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; + +/* This enum should be kept in line with the cooresponding C enum in mupdf.c */ +enum SignatureState { + NoSupport, + Unsigned, + Signed +} + +abstract class PassClickResultVisitor { + public abstract void visitText(PassClickResultText result); + public abstract void visitChoice(PassClickResultChoice result); + public abstract void visitSignature(PassClickResultSignature result); +} + +class PassClickResult { + public final boolean changed; + + public PassClickResult(boolean _changed) { + changed = _changed; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + } +} + +class PassClickResultText extends PassClickResult { + public final String text; + + public PassClickResultText(boolean _changed, String _text) { + super(_changed); + text = _text; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + visitor.visitText(this); + } +} + +class PassClickResultChoice extends PassClickResult { + public final String [] options; + public final String [] selected; + + public PassClickResultChoice(boolean _changed, String [] _options, String [] _selected) { + super(_changed); + options = _options; + selected = _selected; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + visitor.visitChoice(this); + } +} + +class PassClickResultSignature extends PassClickResult { + public final SignatureState state; + + public PassClickResultSignature(boolean _changed, int _state) { + super(_changed); + state = SignatureState.values()[_state]; + } + + public void acceptVisitor(PassClickResultVisitor visitor) { + visitor.visitSignature(this); + } +} + +public class MuPDFPageView extends PageView implements MuPDFView { + final private FilePicker.FilePickerSupport mFilePickerSupport; + private final MuPDFCore mCore; + private AsyncTask mPassClick; + private RectF mWidgetAreas[]; + private Annotation mAnnotations[]; + private int mSelectedAnnotationIndex = -1; + private AsyncTask mLoadWidgetAreas; + private AsyncTask mLoadAnnotations; + private AlertDialog.Builder mTextEntryBuilder; + private AlertDialog.Builder mChoiceEntryBuilder; + private AlertDialog.Builder mSigningDialogBuilder; + private AlertDialog.Builder mSignatureReportBuilder; + private AlertDialog.Builder mPasswordEntryBuilder; + private EditText mPasswordText; + private AlertDialog mTextEntry; + private AlertDialog mPasswordEntry; + private EditText mEditText; + private AsyncTask mSetWidgetText; + private AsyncTask mSetWidgetChoice; + private AsyncTask mAddStrikeOut; + private AsyncTask mAddInk; + private AsyncTask mDeleteAnnotation; + private AsyncTask mCheckSignature; + private AsyncTask mSign; + private Runnable changeReporter; + + public MuPDFPageView(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core, Point parentSize, Bitmap sharedHqBm) { + super(c, parentSize, sharedHqBm); + mFilePickerSupport = filePickerSupport; + mCore = core; + mTextEntryBuilder = new AlertDialog.Builder(c); + mTextEntryBuilder.setTitle(getContext().getString(R.string.fill_out_text_field)); + LayoutInflater inflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mEditText = (EditText)inflater.inflate(R.layout.textentry, null); + mTextEntryBuilder.setView(mEditText); + mTextEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + mTextEntryBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mSetWidgetText = new AsyncTask () { + @Override + protected Boolean doInBackground(String... arg0) { + return mCore.setFocusedWidgetText(mPageNumber, arg0[0]); + } + @Override + protected void onPostExecute(Boolean result) { + changeReporter.run(); + if (!result) + invokeTextDialog(mEditText.getText().toString()); + } + }; + + mSetWidgetText.execute(mEditText.getText().toString()); + } + }); + mTextEntry = mTextEntryBuilder.create(); + + mChoiceEntryBuilder = new AlertDialog.Builder(c); + mChoiceEntryBuilder.setTitle(getContext().getString(R.string.choose_value)); + + mSigningDialogBuilder = new AlertDialog.Builder(c); + mSigningDialogBuilder.setTitle("Select certificate and sign?"); + mSigningDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + mSigningDialogBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + FilePicker picker = new FilePicker(mFilePickerSupport) { + @Override + void onPick(Uri uri) { + signWithKeyFile(uri); + } + }; + + picker.pick(); + } + }); + + mSignatureReportBuilder = new AlertDialog.Builder(c); + mSignatureReportBuilder.setTitle("Signature checked"); + mSignatureReportBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + mPasswordText = new EditText(c); + mPasswordText.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + mPasswordText.setTransformationMethod(new PasswordTransformationMethod()); + + mPasswordEntryBuilder = new AlertDialog.Builder(c); + mPasswordEntryBuilder.setTitle(R.string.enter_password); + mPasswordEntryBuilder.setView(mPasswordText); + mPasswordEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + mPasswordEntry = mPasswordEntryBuilder.create(); + } + + private void signWithKeyFile(final Uri uri) { + mPasswordEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + mPasswordEntry.setButton(AlertDialog.BUTTON_POSITIVE, "Sign", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + signWithKeyFileAndPassword(uri, mPasswordText.getText().toString()); + } + }); + + mPasswordEntry.show(); + } + + private void signWithKeyFileAndPassword(final Uri uri, final String password) { + mSign = new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + return mCore.signFocusedSignature(Uri.decode(uri.getEncodedPath()), password); + } + @Override + protected void onPostExecute(Boolean result) { + if (result) + { + changeReporter.run(); + } + else + { + mPasswordText.setText(""); + signWithKeyFile(uri); + } + } + + }; + + mSign.execute(); + } + + public LinkInfo hitLink(float x, float y) { + // Since link highlighting was implemented, the super class + // PageView has had sufficient information to be able to + // perform this method directly. Making that change would + // make MuPDFCore.hitLinkPage superfluous. + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + float docRelX = (x - getLeft())/scale; + float docRelY = (y - getTop())/scale; + + for (LinkInfo l: mLinks) + if (l.rect.contains(docRelX, docRelY)) + return l; + + return null; + } + + private void invokeTextDialog(String text) { + mEditText.setText(text); + mTextEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + mTextEntry.show(); + } + + private void invokeChoiceDialog(final String [] options) { + mChoiceEntryBuilder.setItems(options, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mSetWidgetChoice = new AsyncTask() { + @Override + protected Void doInBackground(String... params) { + String [] sel = {params[0]}; + mCore.setFocusedWidgetChoiceSelected(sel); + return null; + } + + @Override + protected void onPostExecute(Void result) { + changeReporter.run(); + } + }; + + mSetWidgetChoice.execute(options[which]); + } + }); + AlertDialog dialog = mChoiceEntryBuilder.create(); + dialog.show(); + } + + private void invokeSignatureCheckingDialog() { + mCheckSignature = new AsyncTask () { + @Override + protected String doInBackground(Void... params) { + return mCore.checkFocusedSignature(); + } + @Override + protected void onPostExecute(String result) { + AlertDialog report = mSignatureReportBuilder.create(); + report.setMessage(result); + report.show(); + } + }; + + mCheckSignature.execute(); + } + + private void invokeSigningDialog() { + AlertDialog dialog = mSigningDialogBuilder.create(); + dialog.show(); + } + + private void warnNoSignatureSupport() { + AlertDialog dialog = mSignatureReportBuilder.create(); + dialog.setTitle("App built with no signature support"); + dialog.show(); + } + + public void setChangeReporter(Runnable reporter) { + changeReporter = reporter; + } + + public Hit passClickEvent(float x, float y) { + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + final float docRelX = (x - getLeft())/scale; + final float docRelY = (y - getTop())/scale; + boolean hit = false; + int i; + + if (mAnnotations != null) { + for (i = 0; i < mAnnotations.length; i++) + if (mAnnotations[i].contains(docRelX, docRelY)) { + hit = true; + break; + } + + if (hit) { + switch (mAnnotations[i].type) { + case HIGHLIGHT: + case UNDERLINE: + case SQUIGGLY: + case STRIKEOUT: + case INK: + mSelectedAnnotationIndex = i; + setItemSelectBox(mAnnotations[i]); + return Hit.Annotation; + } + } + } + + mSelectedAnnotationIndex = -1; + setItemSelectBox(null); + + if (!mCore.javascriptSupported()) + return Hit.Nothing; + + if (mWidgetAreas != null) { + for (i = 0; i < mWidgetAreas.length && !hit; i++) + if (mWidgetAreas[i].contains(docRelX, docRelY)) + hit = true; + } + + if (hit) { + mPassClick = new AsyncTask() { + @Override + protected PassClickResult doInBackground(Void... arg0) { + return mCore.passClickEvent(mPageNumber, docRelX, docRelY); + } + + @Override + protected void onPostExecute(PassClickResult result) { + if (result.changed) { + changeReporter.run(); + } + + result.acceptVisitor(new PassClickResultVisitor() { + @Override + public void visitText(PassClickResultText result) { + invokeTextDialog(result.text); + } + + @Override + public void visitChoice(PassClickResultChoice result) { + invokeChoiceDialog(result.options); + } + + @Override + public void visitSignature(PassClickResultSignature result) { + switch (result.state) { + case NoSupport: + warnNoSignatureSupport(); + break; + case Unsigned: + invokeSigningDialog(); + break; + case Signed: + invokeSignatureCheckingDialog(); + break; + } + } + }); + } + }; + + mPassClick.execute(); + return Hit.Widget; + } + + return Hit.Nothing; + } + + @TargetApi(11) + public boolean copySelection() { + final StringBuilder text = new StringBuilder(); + + processSelectedText(new TextProcessor() { + StringBuilder line; + + public void onStartLine() { + line = new StringBuilder(); + } + + public void onWord(TextWord word) { + if (line.length() > 0) + line.append(' '); + line.append(word.w); + } + + public void onEndLine() { + if (text.length() > 0) + text.append('\n'); + text.append(line); + } + }); + + if (text.length() == 0) + return false; + + int currentApiVersion = android.os.Build.VERSION.SDK_INT; + if (currentApiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) { + android.content.ClipboardManager cm = (android.content.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE); + + cm.setPrimaryClip(ClipData.newPlainText("MuPDF", text)); + } else { + android.text.ClipboardManager cm = (android.text.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(text); + } + + deselectText(); + + return true; + } + + public boolean markupSelection(final Annotation.Type type) { + final ArrayList quadPoints = new ArrayList(); + processSelectedText(new TextProcessor() { + RectF rect; + + public void onStartLine() { + rect = new RectF(); + } + + public void onWord(TextWord word) { + rect.union(word); + } + + public void onEndLine() { + if (!rect.isEmpty()) { + quadPoints.add(new PointF(rect.left, rect.bottom)); + quadPoints.add(new PointF(rect.right, rect.bottom)); + quadPoints.add(new PointF(rect.right, rect.top)); + quadPoints.add(new PointF(rect.left, rect.top)); + } + } + }); + + if (quadPoints.size() == 0) + return false; + + mAddStrikeOut = new AsyncTask() { + @Override + protected Void doInBackground(PointF[]... params) { + addMarkup(params[0], type); + return null; + } + + @Override + protected void onPostExecute(Void result) { + loadAnnotations(); + update(); + } + }; + + mAddStrikeOut.execute(quadPoints.toArray(new PointF[quadPoints.size()])); + + deselectText(); + + return true; + } + + public void deleteSelectedAnnotation() { + if (mSelectedAnnotationIndex != -1) { + if (mDeleteAnnotation != null) + mDeleteAnnotation.cancel(true); + + mDeleteAnnotation = new AsyncTask() { + @Override + protected Void doInBackground(Integer... params) { + mCore.deleteAnnotation(mPageNumber, params[0]); + return null; + } + + @Override + protected void onPostExecute(Void result) { + loadAnnotations(); + update(); + } + }; + + mDeleteAnnotation.execute(mSelectedAnnotationIndex); + + mSelectedAnnotationIndex = -1; + setItemSelectBox(null); + } + } + + public void deselectAnnotation() { + mSelectedAnnotationIndex = -1; + setItemSelectBox(null); + } + + public boolean saveDraw() { + PointF[][] path = getDraw(); + + if (path == null) + return false; + + if (mAddInk != null) { + mAddInk.cancel(true); + mAddInk = null; + } + mAddInk = new AsyncTask() { + @Override + protected Void doInBackground(PointF[][]... params) { + mCore.addInkAnnotation(mPageNumber, params[0]); + return null; + } + + @Override + protected void onPostExecute(Void result) { + loadAnnotations(); + update(); + } + + }; + + mAddInk.execute(getDraw()); + cancelDraw(); + + return true; + } + + + @Override + protected CancellableTaskDefinition getDrawPageTask(final Bitmap bm, final int sizeX, final int sizeY, + final int patchX, final int patchY, final int patchWidth, final int patchHeight) { + return new MuPDFCancellableTaskDefinition(mCore) { + @Override + public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) { + // Workaround bug in Android Honeycomb 3.x, where the bitmap generation count + // is not incremented when drawing. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && + Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + bm.eraseColor(0); + mCore.drawPage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie); + return null; + } + }; + + } + + protected CancellableTaskDefinition getUpdatePageTask(final Bitmap bm, final int sizeX, final int sizeY, + final int patchX, final int patchY, final int patchWidth, final int patchHeight) + { + return new MuPDFCancellableTaskDefinition(mCore) { + + @Override + public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) { + // Workaround bug in Android Honeycomb 3.x, where the bitmap generation count + // is not incremented when drawing. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && + Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) + bm.eraseColor(0); + mCore.updatePage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie); + return null; + } + }; + } + + @Override + protected LinkInfo[] getLinkInfo() { + return mCore.getPageLinks(mPageNumber); + } + + @Override + protected TextWord[][] getText() { + return mCore.textLines(mPageNumber); + } + + @Override + protected void addMarkup(PointF[] quadPoints, Annotation.Type type) { + mCore.addMarkupAnnotation(mPageNumber, quadPoints, type); + } + + private void loadAnnotations() { + mAnnotations = null; + if (mLoadAnnotations != null) + mLoadAnnotations.cancel(true); + mLoadAnnotations = new AsyncTask () { + @Override + protected Annotation[] doInBackground(Void... params) { + return mCore.getAnnoations(mPageNumber); + } + + @Override + protected void onPostExecute(Annotation[] result) { + mAnnotations = result; + } + }; + + mLoadAnnotations.execute(); + } + + @Override + public void setPage(final int page, PointF size) { + loadAnnotations(); + + mLoadWidgetAreas = new AsyncTask () { + @Override + protected RectF[] doInBackground(Void... arg0) { + return mCore.getWidgetAreas(page); + } + + @Override + protected void onPostExecute(RectF[] result) { + mWidgetAreas = result; + } + }; + + mLoadWidgetAreas.execute(); + + super.setPage(page, size); + } + + public void setScale(float scale) { + // This type of view scales automatically to fit the size + // determined by the parent view groups during layout + } + + @Override + public void releaseResources() { + if (mPassClick != null) { + mPassClick.cancel(true); + mPassClick = null; + } + + if (mLoadWidgetAreas != null) { + mLoadWidgetAreas.cancel(true); + mLoadWidgetAreas = null; + } + + if (mLoadAnnotations != null) { + mLoadAnnotations.cancel(true); + mLoadAnnotations = null; + } + + if (mSetWidgetText != null) { + mSetWidgetText.cancel(true); + mSetWidgetText = null; + } + + if (mSetWidgetChoice != null) { + mSetWidgetChoice.cancel(true); + mSetWidgetChoice = null; + } + + if (mAddStrikeOut != null) { + mAddStrikeOut.cancel(true); + mAddStrikeOut = null; + } + + if (mDeleteAnnotation != null) { + mDeleteAnnotation.cancel(true); + mDeleteAnnotation = null; + } + + super.releaseResources(); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReaderView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReaderView.java new file mode 100644 index 00000000..f2d7f8fe --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReaderView.java @@ -0,0 +1,276 @@ +package com.artifex.mupdfdemo; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.WindowManager; + +public class MuPDFReaderView extends ReaderView { + public enum Mode {Viewing, Selecting, Drawing} + private final Context mContext; + private boolean mLinksEnabled = false; + private Mode mMode = Mode.Viewing; + private boolean tapDisabled = false; + private int tapPageMargin; + + protected void onTapMainDocArea() {} + protected void onDocMotion() {} + protected void onHit(Hit item) {}; + + public void setLinksEnabled(boolean b) { + mLinksEnabled = b; + resetupChildren(); + } + + public void setMode(Mode m) { + mMode = m; + } + + private void setup() + { + // Get the screen size etc to customise tap margins. + // We calculate the size of 1 inch of the screen for tapping. + // On some devices the dpi values returned are wrong, so we + // sanity check it: we first restrict it so that we are never + // less than 100 pixels (the smallest Android device screen + // dimension I've seen is 480 pixels or so). Then we check + // to ensure we are never more than 1/5 of the screen width. + DisplayMetrics dm = new DisplayMetrics(); + WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(dm); + tapPageMargin = (int)dm.xdpi; + if (tapPageMargin < 100) + tapPageMargin = 100; + if (tapPageMargin > dm.widthPixels/5) + tapPageMargin = dm.widthPixels/5; + } + + public MuPDFReaderView(Context context) { + super(context); + mContext = context; + setup(); + } + + public MuPDFReaderView(Context context, AttributeSet attrs) + { + super(context, attrs); + mContext = context; + setup(); + } + + public boolean onSingleTapUp(MotionEvent e) { + LinkInfo link = null; + + if (mMode == Mode.Viewing && !tapDisabled) { + MuPDFView pageView = (MuPDFView) getDisplayedView(); + Hit item = pageView.passClickEvent(e.getX(), e.getY()); + onHit(item); + if (item == Hit.Nothing) { + if (mLinksEnabled && pageView != null + && (link = pageView.hitLink(e.getX(), e.getY())) != null) { + link.acceptVisitor(new LinkInfoVisitor() { + @Override + public void visitInternal(LinkInfoInternal li) { + // Clicked on an internal (GoTo) link + setDisplayedViewIndex(li.pageNumber); + } + + @Override + public void visitExternal(LinkInfoExternal li) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri + .parse(li.url)); + mContext.startActivity(intent); + } + + @Override + public void visitRemote(LinkInfoRemote li) { + // Clicked on a remote (GoToR) link + } + }); + } else if (e.getX() < tapPageMargin) { + super.smartMoveBackwards(); + } else if (e.getX() > super.getWidth() - tapPageMargin) { + super.smartMoveForwards(); + } else if (e.getY() < tapPageMargin) { + super.smartMoveBackwards(); + } else if (e.getY() > super.getHeight() - tapPageMargin) { + super.smartMoveForwards(); + } else { + onTapMainDocArea(); + } + } + } + return super.onSingleTapUp(e); + } + + @Override + public boolean onDown(MotionEvent e) { + + return super.onDown(e); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + MuPDFView pageView = (MuPDFView)getDisplayedView(); + switch (mMode) { + case Viewing: + if (!tapDisabled) + onDocMotion(); + + return super.onScroll(e1, e2, distanceX, distanceY); + case Selecting: + if (pageView != null) + pageView.selectText(e1.getX(), e1.getY(), e2.getX(), e2.getY()); + return true; + default: + return true; + } + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + switch (mMode) { + case Viewing: + return super.onFling(e1, e2, velocityX, velocityY); + default: + return true; + } + } + + public boolean onScaleBegin(ScaleGestureDetector d) { + // Disabled showing the buttons until next touch. + // Not sure why this is needed, but without it + // pinch zoom can make the buttons appear + tapDisabled = true; + return super.onScaleBegin(d); + } + + public boolean onTouchEvent(MotionEvent event) { + + if ( mMode == Mode.Drawing ) + { + float x = event.getX(); + float y = event.getY(); + switch (event.getAction()) + { + case MotionEvent.ACTION_DOWN: + touch_start(x, y); + break; + case MotionEvent.ACTION_MOVE: + touch_move(x, y); + break; + case MotionEvent.ACTION_UP: + touch_up(); + break; + } + } + + if ((event.getAction() & event.getActionMasked()) == MotionEvent.ACTION_DOWN) + { + tapDisabled = false; + } + + return super.onTouchEvent(event); + } + + private float mX, mY; + + private static final float TOUCH_TOLERANCE = 2; + + private void touch_start(float x, float y) { + + MuPDFView pageView = (MuPDFView)getDisplayedView(); + if (pageView != null) + { + pageView.startDraw(x, y); + } + mX = x; + mY = y; + } + + private void touch_move(float x, float y) { + + float dx = Math.abs(x - mX); + float dy = Math.abs(y - mY); + if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) + { + MuPDFView pageView = (MuPDFView)getDisplayedView(); + if (pageView != null) + { + pageView.continueDraw(x, y); + } + mX = x; + mY = y; + } + } + + private void touch_up() { + + // NOOP + } + + protected void onChildSetup(int i, View v) { + if (SearchTaskResult.get() != null + && SearchTaskResult.get().pageNumber == i) + ((MuPDFView) v).setSearchBoxes(SearchTaskResult.get().searchBoxes); + else + ((MuPDFView) v).setSearchBoxes(null); + + ((MuPDFView) v).setLinkHighlighting(mLinksEnabled); + + ((MuPDFView) v).setChangeReporter(new Runnable() { + public void run() { + applyToChildren(new ReaderView.ViewMapper() { + @Override + void applyToView(View view) { + ((MuPDFView) view).update(); + } + }); + } + }); + } + + protected void onMoveToChild(int i) { + if (SearchTaskResult.get() != null + && SearchTaskResult.get().pageNumber != i) { + SearchTaskResult.set(null); + resetupChildren(); + } + } + + @Override + protected void onMoveOffChild(int i) { + View v = getView(i); + if (v != null) + ((MuPDFView)v).deselectAnnotation(); + } + + protected void onSettle(View v) { + // When the layout has settled ask the page to render + // in HQ + ((MuPDFView) v).updateHq(false); + } + + protected void onUnsettle(View v) { + // When something changes making the previous settled view + // no longer appropriate, tell the page to remove HQ + ((MuPDFView) v).removeHq(); + } + + @Override + protected void onNotInUse(View v) { + ((MuPDFView) v).releaseResources(); + } + + @Override + protected void onScaleChild(View v, Float scale) { + ((MuPDFView) v).setScale(scale); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java new file mode 100644 index 00000000..48625a7e --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowAdapter.java @@ -0,0 +1,43 @@ +package com.artifex.mupdfdemo; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.PointF; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +public class MuPDFReflowAdapter extends BaseAdapter { + private final Context mContext; + private final MuPDFCore mCore; + + public MuPDFReflowAdapter(Context c, MuPDFCore core) { + mContext = c; + mCore = core; + } + + public int getCount() { + return mCore.countPages(); + } + + public Object getItem(int arg0) { + return null; + } + + public long getItemId(int arg0) { + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + final MuPDFReflowView reflowView; + if (convertView == null) { + reflowView = new MuPDFReflowView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight())); + } else { + reflowView = (MuPDFReflowView) convertView; + } + + reflowView.setPage(position, new PointF()); + + return reflowView; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowView.java new file mode 100644 index 00000000..7d41a9b9 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFReflowView.java @@ -0,0 +1,182 @@ +package com.artifex.mupdfdemo; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.RectF; +import android.os.Handler; +import android.util.Base64; +import android.view.MotionEvent; +import android.view.View; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +public class MuPDFReflowView extends WebView implements MuPDFView { + private final MuPDFCore mCore; + private final Handler mHandler; + private final Point mParentSize; + private int mPage; + private float mScale; + private int mContentHeight; + AsyncTask mLoadHTML; + + public MuPDFReflowView(Context c, MuPDFCore core, Point parentSize) { + super(c); + mHandler = new Handler(); + mCore = core; + mParentSize = parentSize; + mScale = 1.0f; + mContentHeight = parentSize.y; + getSettings().setJavaScriptEnabled(true); + addJavascriptInterface(new Object(){ + public void reportContentHeight(String value) { + mContentHeight = (int)Float.parseFloat(value); + mHandler.post(new Runnable() { + public void run() { + requestLayout(); + } + }); + } + }, "HTMLOUT"); + setWebViewClient(new WebViewClient() { + @Override + public void onPageFinished(WebView view, String url) { + setScale(mScale); + } + }); + } + + private void requestHeight() { + // Get the webview to report the content height via the interface setup + // above. Workaround for getContentHeight not working + loadUrl("javascript:elem=document.getElementById('content');window.HTMLOUT.reportContentHeight("+mParentSize.x+"*elem.offsetHeight/elem.offsetWidth)"); + } + + public void setPage(int page, PointF size) { + mPage = page; + if (mLoadHTML != null) { + mLoadHTML.cancel(true); + } + mLoadHTML = new AsyncTask() { + @Override + protected byte[] doInBackground(Void... params) { + return mCore.html(mPage); + } + @Override + protected void onPostExecute(byte[] result) { + String b64 = Base64.encodeToString(result, Base64.DEFAULT); + loadData(b64, "text/html; charset=utf-8", "base64"); + } + }; + mLoadHTML.execute(); + } + + public int getPage() { + return mPage; + } + + public void setScale(float scale) { + mScale = scale; + loadUrl("javascript:document.getElementById('content').style.zoom=\""+(int)(mScale*100)+"%\""); + requestHeight(); + } + + public void blank(int page) { + } + + public Hit passClickEvent(float x, float y) { + return Hit.Nothing; + } + + public LinkInfo hitLink(float x, float y) { + return null; + } + + public void selectText(float x0, float y0, float x1, float y1) { + } + + public void deselectText() { + } + + public boolean copySelection() { + return false; + } + + public boolean markupSelection(Annotation.Type type) { + return false; + } + + public void startDraw(float x, float y) { + } + + public void continueDraw(float x, float y) { + } + + public void cancelDraw() { + } + + public boolean saveDraw() { + return false; + } + + public void setSearchBoxes(RectF[] searchBoxes) { + } + + public void setLinkHighlighting(boolean f) { + } + + public void deleteSelectedAnnotation() { + } + + public void deselectAnnotation() { + } + + public void setChangeReporter(Runnable reporter) { + } + + public void update() { + } + + public void updateHq(boolean update) { + } + + public void removeHq() { + } + + public void releaseResources() { + if (mLoadHTML != null) { + mLoadHTML.cancel(true); + mLoadHTML = null; + } + } + + public void releaseBitmaps() { + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int x, y; + switch(View.MeasureSpec.getMode(widthMeasureSpec)) { + case View.MeasureSpec.UNSPECIFIED: + x = mParentSize.x; + break; + default: + x = View.MeasureSpec.getSize(widthMeasureSpec); + } + switch(View.MeasureSpec.getMode(heightMeasureSpec)) { + case View.MeasureSpec.UNSPECIFIED: + y = mContentHeight; + break; + default: + y = View.MeasureSpec.getSize(heightMeasureSpec); + } + + setMeasuredDimension(x, y); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // TODO Auto-generated method stub + return false; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFView.java new file mode 100644 index 00000000..ec3d3d9a --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFView.java @@ -0,0 +1,33 @@ +package com.artifex.mupdfdemo; + +import android.graphics.PointF; +import android.graphics.RectF; + +enum Hit {Nothing, Widget, Annotation}; + +public interface MuPDFView { + public void setPage(int page, PointF size); + public void setScale(float scale); + public int getPage(); + public void blank(int page); + public Hit passClickEvent(float x, float y); + public LinkInfo hitLink(float x, float y); + public void selectText(float x0, float y0, float x1, float y1); + public void deselectText(); + public boolean copySelection(); + public boolean markupSelection(Annotation.Type type); + public void deleteSelectedAnnotation(); + public void setSearchBoxes(RectF searchBoxes[]); + public void setLinkHighlighting(boolean f); + public void deselectAnnotation(); + public void startDraw(float x, float y); + public void continueDraw(float x, float y); + public void cancelDraw(); + public boolean saveDraw(); + public void setChangeReporter(Runnable reporter); + public void update(); + public void updateHq(boolean update); + public void removeHq(); + public void releaseResources(); + public void releaseBitmaps(); +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivity.java b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivity.java new file mode 100644 index 00000000..52b0d410 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivity.java @@ -0,0 +1,31 @@ +package com.artifex.mupdfdemo; + +import android.app.ListActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; + +public class OutlineActivity extends ListActivity { + OutlineItem mItems[]; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mItems = OutlineActivityData.get().items; + setListAdapter(new OutlineAdapter(getLayoutInflater(),mItems)); + // Restore the position within the list from last viewing + getListView().setSelection(OutlineActivityData.get().position); + getListView().setDividerHeight(0); + setResult(-1); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + OutlineActivityData.get().position = getListView().getFirstVisiblePosition(); + setResult(mItems[position].page); + finish(); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java new file mode 100644 index 00000000..a703e61e --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineActivityData.java @@ -0,0 +1,17 @@ +package com.artifex.mupdfdemo; + +public class OutlineActivityData { + public OutlineItem items[]; + public int position; + static private OutlineActivityData singleton; + + static public void set(OutlineActivityData d) { + singleton = d; + } + + static public OutlineActivityData get() { + if (singleton == null) + singleton = new OutlineActivityData(); + return singleton; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java new file mode 100644 index 00000000..4251ed8e --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/OutlineAdapter.java @@ -0,0 +1,46 @@ +package com.artifex.mupdfdemo; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +public class OutlineAdapter extends BaseAdapter { + private final OutlineItem mItems[]; + private final LayoutInflater mInflater; + public OutlineAdapter(LayoutInflater inflater, OutlineItem items[]) { + mInflater = inflater; + mItems = items; + } + + public int getCount() { + return mItems.length; + } + + public Object getItem(int arg0) { + return null; + } + + public long getItemId(int arg0) { + return 0; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View v; + if (convertView == null) { + v = mInflater.inflate(R.layout.outline_entry, null); + } else { + v = convertView; + } + int level = mItems[position].level; + if (level > 8) level = 8; + String space = ""; + for (int i=0; i lines = new ArrayList(); + for (TextWord[] line : mText) + if (line[0].bottom > mSelectBox.top && line[0].top < mSelectBox.bottom) + lines.add(line); + + Iterator it = lines.iterator(); + while (it.hasNext()) { + TextWord[] line = it.next(); + boolean firstLine = line[0].top < mSelectBox.top; + boolean lastLine = line[0].bottom > mSelectBox.bottom; + float start = Float.NEGATIVE_INFINITY; + float end = Float.POSITIVE_INFINITY; + + if (firstLine && lastLine) { + start = Math.min(mSelectBox.left, mSelectBox.right); + end = Math.max(mSelectBox.left, mSelectBox.right); + } else if (firstLine) { + start = mSelectBox.left; + } else if (lastLine) { + end = mSelectBox.right; + } + + tp.onStartLine(); + + for (TextWord word : line) + if (word.right > start && word.left < end) + tp.onWord(word); + + tp.onEndLine(); + } + } +} + +public abstract class PageView extends ViewGroup { + private static final int HIGHLIGHT_COLOR = 0x802572AC; + private static final int LINK_COLOR = 0x80AC7225; + private static final int BOX_COLOR = 0xFF4444FF; + private static final int INK_COLOR = 0xFFFF0000; + private static final float INK_THICKNESS = 10.0f; + private static final int BACKGROUND_COLOR = 0xFFFFFFFF; + private static final int PROGRESS_DIALOG_DELAY = 200; + protected final Context mContext; + protected int mPageNumber; + private Point mParentSize; + protected Point mSize; // Size of page at minimum zoom + protected float mSourceScale; + + private ImageView mEntire; // Image rendered at minimum zoom + private Bitmap mEntireBm; + private Matrix mEntireMat; + private AsyncTask mGetText; + private AsyncTask mGetLinkInfo; + private CancellableAsyncTask mDrawEntire; + + private Point mPatchViewSize; // View size on the basis of which the patch was created + private Rect mPatchArea; + private ImageView mPatch; + private Bitmap mPatchBm; + private CancellableAsyncTask mDrawPatch; + private RectF mSearchBoxes[]; + protected LinkInfo mLinks[]; + private RectF mSelectBox; + private TextWord mText[][]; + private RectF mItemSelectBox; + protected ArrayList> mDrawing; + private View mSearchView; + private boolean mIsBlank; + private boolean mHighlightLinks; + + private ProgressBar mBusyIndicator; + private final Handler mHandler = new Handler(); + + public PageView(Context c, Point parentSize, Bitmap sharedHqBm) { + super(c); + mContext = c; + mParentSize = parentSize; + setBackgroundColor(BACKGROUND_COLOR); + mEntireBm = Bitmap.createBitmap(parentSize.x, parentSize.y, Config.ARGB_8888); + mPatchBm = sharedHqBm; + mEntireMat = new Matrix(); + } + + protected abstract CancellableTaskDefinition getDrawPageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); + protected abstract CancellableTaskDefinition getUpdatePageTask(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight); + protected abstract LinkInfo[] getLinkInfo(); + protected abstract TextWord[][] getText(); + protected abstract void addMarkup(PointF[] quadPoints, Annotation.Type type); + + private void reinit() { + // Cancel pending render task + if (mDrawEntire != null) { + mDrawEntire.cancelAndWait(); + mDrawEntire = null; + } + + if (mDrawPatch != null) { + mDrawPatch.cancelAndWait(); + mDrawPatch = null; + } + + if (mGetLinkInfo != null) { + mGetLinkInfo.cancel(true); + mGetLinkInfo = null; + } + + if (mGetText != null) { + mGetText.cancel(true); + mGetText = null; + } + + mIsBlank = true; + mPageNumber = 0; + + if (mSize == null) + mSize = mParentSize; + + if (mEntire != null) { + mEntire.setImageBitmap(null); + mEntire.invalidate(); + } + + if (mPatch != null) { + mPatch.setImageBitmap(null); + mPatch.invalidate(); + } + + mPatchViewSize = null; + mPatchArea = null; + + mSearchBoxes = null; + mLinks = null; + mSelectBox = null; + mText = null; + mItemSelectBox = null; + } + + public void releaseResources() { + reinit(); + + if (mBusyIndicator != null) { + removeView(mBusyIndicator); + mBusyIndicator = null; + } + } + + public void releaseBitmaps() { + reinit(); + + // recycle bitmaps before releasing them. + + if (mEntireBm!=null) + mEntireBm.recycle(); + mEntireBm = null; + + if (mPatchBm!=null) + mPatchBm.recycle(); + mPatchBm = null; + } + + public void blank(int page) { + reinit(); + mPageNumber = page; + + if (mBusyIndicator == null) { + mBusyIndicator = new ProgressBar(mContext); + mBusyIndicator.setIndeterminate(true); + mBusyIndicator.setBackgroundResource(R.drawable.busy); + addView(mBusyIndicator); + } + + setBackgroundColor(BACKGROUND_COLOR); + } + + public void setPage(int page, PointF size) { + // Cancel pending render task + if (mDrawEntire != null) { + mDrawEntire.cancelAndWait(); + mDrawEntire = null; + } + + mIsBlank = false; + // Highlights may be missing because mIsBlank was true on last draw + if (mSearchView != null) + mSearchView.invalidate(); + + mPageNumber = page; + if (mEntire == null) { + mEntire = new OpaqueImageView(mContext); + mEntire.setScaleType(ImageView.ScaleType.MATRIX); + addView(mEntire); + } + + // Calculate scaled size that fits within the screen limits + // This is the size at minimum zoom + mSourceScale = Math.min(mParentSize.x/size.x, mParentSize.y/size.y); + Point newSize = new Point((int)(size.x*mSourceScale), (int)(size.y*mSourceScale)); + mSize = newSize; + + mEntire.setImageBitmap(null); + mEntire.invalidate(); + + // Get the link info in the background + mGetLinkInfo = new AsyncTask() { + protected LinkInfo[] doInBackground(Void... v) { + return getLinkInfo(); + } + + protected void onPostExecute(LinkInfo[] v) { + mLinks = v; + if (mSearchView != null) + mSearchView.invalidate(); + } + }; + + mGetLinkInfo.execute(); + + // Render the page in the background + mDrawEntire = new CancellableAsyncTask(getDrawPageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) { + + @Override + public void onPreExecute() { + setBackgroundColor(BACKGROUND_COLOR); + mEntire.setImageBitmap(null); + mEntire.invalidate(); + + if (mBusyIndicator == null) { + mBusyIndicator = new ProgressBar(mContext); + mBusyIndicator.setIndeterminate(true); + mBusyIndicator.setBackgroundResource(R.drawable.busy); + addView(mBusyIndicator); + mBusyIndicator.setVisibility(INVISIBLE); + mHandler.postDelayed(new Runnable() { + public void run() { + if (mBusyIndicator != null) + mBusyIndicator.setVisibility(VISIBLE); + } + }, PROGRESS_DIALOG_DELAY); + } + } + + @Override + public void onPostExecute(Void result) { + removeView(mBusyIndicator); + mBusyIndicator = null; + mEntire.setImageBitmap(mEntireBm); + mEntire.invalidate(); + setBackgroundColor(Color.TRANSPARENT); + + } + }; + + mDrawEntire.execute(); + + if (mSearchView == null) { + mSearchView = new View(mContext) { + @Override + protected void onDraw(final Canvas canvas) { + super.onDraw(canvas); + // Work out current total scale factor + // from source to view + final float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + final Paint paint = new Paint(); + + if (!mIsBlank && mSearchBoxes != null) { + paint.setColor(HIGHLIGHT_COLOR); + for (RectF rect : mSearchBoxes) + canvas.drawRect(rect.left*scale, rect.top*scale, + rect.right*scale, rect.bottom*scale, + paint); + } + + if (!mIsBlank && mLinks != null && mHighlightLinks) { + paint.setColor(LINK_COLOR); + for (LinkInfo link : mLinks) + canvas.drawRect(link.rect.left*scale, link.rect.top*scale, + link.rect.right*scale, link.rect.bottom*scale, + paint); + } + + if (mSelectBox != null && mText != null) { + paint.setColor(HIGHLIGHT_COLOR); + processSelectedText(new TextProcessor() { + RectF rect; + + public void onStartLine() { + rect = new RectF(); + } + + public void onWord(TextWord word) { + rect.union(word); + } + + public void onEndLine() { + if (!rect.isEmpty()) + canvas.drawRect(rect.left*scale, rect.top*scale, rect.right*scale, rect.bottom*scale, paint); + } + }); + } + + if (mItemSelectBox != null) { + paint.setStyle(Paint.Style.STROKE); + paint.setColor(BOX_COLOR); + canvas.drawRect(mItemSelectBox.left*scale, mItemSelectBox.top*scale, mItemSelectBox.right*scale, mItemSelectBox.bottom*scale, paint); + } + + if (mDrawing != null) { + Path path = new Path(); + PointF p; + + paint.setAntiAlias(true); + paint.setDither(true); + paint.setStrokeJoin(Paint.Join.ROUND); + paint.setStrokeCap(Paint.Cap.ROUND); + + paint.setStyle(Paint.Style.FILL); + paint.setStrokeWidth(INK_THICKNESS * scale); + paint.setColor(INK_COLOR); + + Iterator> it = mDrawing.iterator(); + while (it.hasNext()) { + ArrayList arc = it.next(); + if (arc.size() >= 2) { + Iterator iit = arc.iterator(); + p = iit.next(); + float mX = p.x * scale; + float mY = p.y * scale; + path.moveTo(mX, mY); + while (iit.hasNext()) { + p = iit.next(); + float x = p.x * scale; + float y = p.y * scale; + path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); + mX = x; + mY = y; + } + path.lineTo(mX, mY); + } else { + p = arc.get(0); + canvas.drawCircle(p.x * scale, p.y * scale, INK_THICKNESS * scale / 2, paint); + } + } + + paint.setStyle(Paint.Style.STROKE); + canvas.drawPath(path, paint); + } + } + }; + + addView(mSearchView); + } + requestLayout(); + } + + public void setSearchBoxes(RectF searchBoxes[]) { + mSearchBoxes = searchBoxes; + if (mSearchView != null) + mSearchView.invalidate(); + } + + public void setLinkHighlighting(boolean f) { + mHighlightLinks = f; + if (mSearchView != null) + mSearchView.invalidate(); + } + + public void deselectText() { + mSelectBox = null; + mSearchView.invalidate(); + } + + public void selectText(float x0, float y0, float x1, float y1) { + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + float docRelX0 = (x0 - getLeft())/scale; + float docRelY0 = (y0 - getTop())/scale; + float docRelX1 = (x1 - getLeft())/scale; + float docRelY1 = (y1 - getTop())/scale; + // Order on Y but maintain the point grouping + if (docRelY0 <= docRelY1) + mSelectBox = new RectF(docRelX0, docRelY0, docRelX1, docRelY1); + else + mSelectBox = new RectF(docRelX1, docRelY1, docRelX0, docRelY0); + + mSearchView.invalidate(); + + if (mGetText == null) { + mGetText = new AsyncTask() { + @Override + protected TextWord[][] doInBackground(Void... params) { + return getText(); + } + @Override + protected void onPostExecute(TextWord[][] result) { + mText = result; + mSearchView.invalidate(); + } + }; + + mGetText.execute(); + } + } + + public void startDraw(float x, float y) { + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + float docRelX = (x - getLeft())/scale; + float docRelY = (y - getTop())/scale; + if (mDrawing == null) + mDrawing = new ArrayList>(); + + ArrayList arc = new ArrayList(); + arc.add(new PointF(docRelX, docRelY)); + mDrawing.add(arc); + mSearchView.invalidate(); + } + + public void continueDraw(float x, float y) { + float scale = mSourceScale*(float)getWidth()/(float)mSize.x; + float docRelX = (x - getLeft())/scale; + float docRelY = (y - getTop())/scale; + + if (mDrawing != null && mDrawing.size() > 0) { + ArrayList arc = mDrawing.get(mDrawing.size() - 1); + arc.add(new PointF(docRelX, docRelY)); + mSearchView.invalidate(); + } + } + + public void cancelDraw() { + mDrawing = null; + mSearchView.invalidate(); + } + + protected PointF[][] getDraw() { + if (mDrawing == null) + return null; + + PointF[][] path = new PointF[mDrawing.size()][]; + + for (int i = 0; i < mDrawing.size(); i++) { + ArrayList arc = mDrawing.get(i); + path[i] = arc.toArray(new PointF[arc.size()]); + } + + return path; + } + + protected void processSelectedText(TextProcessor tp) { + (new TextSelector(mText, mSelectBox)).select(tp); + } + + public void setItemSelectBox(RectF rect) { + mItemSelectBox = rect; + if (mSearchView != null) + mSearchView.invalidate(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int x, y; + switch(View.MeasureSpec.getMode(widthMeasureSpec)) { + case View.MeasureSpec.UNSPECIFIED: + x = mSize.x; + break; + default: + x = View.MeasureSpec.getSize(widthMeasureSpec); + } + switch(View.MeasureSpec.getMode(heightMeasureSpec)) { + case View.MeasureSpec.UNSPECIFIED: + y = mSize.y; + break; + default: + y = View.MeasureSpec.getSize(heightMeasureSpec); + } + + setMeasuredDimension(x, y); + + if (mBusyIndicator != null) { + int limit = Math.min(mParentSize.x, mParentSize.y)/2; + mBusyIndicator.measure(View.MeasureSpec.AT_MOST | limit, View.MeasureSpec.AT_MOST | limit); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int w = right-left; + int h = bottom-top; + + if (mEntire != null) { + if (mEntire.getWidth() != w || mEntire.getHeight() != h) { + mEntireMat.setScale(w/(float)mSize.x, h/(float)mSize.y); + mEntire.setImageMatrix(mEntireMat); + mEntire.invalidate(); + } + mEntire.layout(0, 0, w, h); + } + + if (mSearchView != null) { + mSearchView.layout(0, 0, w, h); + } + + if (mPatchViewSize != null) { + if (mPatchViewSize.x != w || mPatchViewSize.y != h) { + // Zoomed since patch was created + mPatchViewSize = null; + mPatchArea = null; + if (mPatch != null) { + mPatch.setImageBitmap(null); + mPatch.invalidate(); + } + } else { + mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); + } + } + + if (mBusyIndicator != null) { + int bw = mBusyIndicator.getMeasuredWidth(); + int bh = mBusyIndicator.getMeasuredHeight(); + + mBusyIndicator.layout((w-bw)/2, (h-bh)/2, (w+bw)/2, (h+bh)/2); + } + } + + public void updateHq(boolean update) { + Rect viewArea = new Rect(getLeft(),getTop(),getRight(),getBottom()); + if (viewArea.width() == mSize.x || viewArea.height() == mSize.y) { + // If the viewArea's size matches the unzoomed size, there is no need for an hq patch + if (mPatch != null) { + mPatch.setImageBitmap(null); + mPatch.invalidate(); + } + } else { + final Point patchViewSize = new Point(viewArea.width(), viewArea.height()); + final Rect patchArea = new Rect(0, 0, mParentSize.x, mParentSize.y); + + // Intersect and test that there is an intersection + if (!patchArea.intersect(viewArea)) + return; + + // Offset patch area to be relative to the view top left + patchArea.offset(-viewArea.left, -viewArea.top); + + boolean area_unchanged = patchArea.equals(mPatchArea) && patchViewSize.equals(mPatchViewSize); + + // If being asked for the same area as last time and not because of an update then nothing to do + if (area_unchanged && !update) + return; + + boolean completeRedraw = !(area_unchanged && update); + + // Stop the drawing of previous patch if still going + if (mDrawPatch != null) { + mDrawPatch.cancelAndWait(); + mDrawPatch = null; + } + + // Create and add the image view if not already done + if (mPatch == null) { + mPatch = new OpaqueImageView(mContext); + mPatch.setScaleType(ImageView.ScaleType.MATRIX); + addView(mPatch); + mSearchView.bringToFront(); + } + + CancellableTaskDefinition task; + + if (completeRedraw) + task = getDrawPageTask(mPatchBm, patchViewSize.x, patchViewSize.y, + patchArea.left, patchArea.top, + patchArea.width(), patchArea.height()); + else + task = getUpdatePageTask(mPatchBm, patchViewSize.x, patchViewSize.y, + patchArea.left, patchArea.top, + patchArea.width(), patchArea.height()); + + mDrawPatch = new CancellableAsyncTask(task) { + + public void onPostExecute(Void result) { + mPatchViewSize = patchViewSize; + mPatchArea = patchArea; + mPatch.setImageBitmap(mPatchBm); + mPatch.invalidate(); + //requestLayout(); + // Calling requestLayout here doesn't lead to a later call to layout. No idea + // why, but apparently others have run into the problem. + mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom); + } + }; + + mDrawPatch.execute(); + } + } + + public void update() { + // Cancel pending render task + if (mDrawEntire != null) { + mDrawEntire.cancelAndWait(); + mDrawEntire = null; + } + + if (mDrawPatch != null) { + mDrawPatch.cancelAndWait(); + mDrawPatch = null; + } + + + // Render the page in the background + mDrawEntire = new CancellableAsyncTask(getUpdatePageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) { + + public void onPostExecute(Void result) { + mEntire.setImageBitmap(mEntireBm); + mEntire.invalidate(); + } + }; + + mDrawEntire.execute(); + + updateHq(true); + } + + public void removeHq() { + // Stop the drawing of the patch if still going + if (mDrawPatch != null) { + mDrawPatch.cancelAndWait(); + mDrawPatch = null; + } + + // And get rid of it + mPatchViewSize = null; + mPatchArea = null; + if (mPatch != null) { + mPatch.setImageBitmap(null); + mPatch.invalidate(); + } + } + + public int getPage() { + return mPageNumber; + } + + @Override + public boolean isOpaque() { + return true; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/PrintDialogActivity.java b/platform/android/viewer/src/com/artifex/mupdfdemo/PrintDialogActivity.java new file mode 100644 index 00000000..d96322d5 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/PrintDialogActivity.java @@ -0,0 +1,145 @@ +package com.artifex.mupdfdemo; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.ContentResolver; +import android.content.Intent; +import android.os.Bundle; +import android.util.Base64; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +public class PrintDialogActivity extends Activity { + private static final String PRINT_DIALOG_URL = "https://www.google.com/cloudprint/dialog.html"; + private static final String JS_INTERFACE = "AndroidPrintDialog"; + private static final String CONTENT_TRANSFER_ENCODING = "base64"; + + private static final String ZXING_URL = "http://zxing.appspot.com"; + private static final int ZXING_SCAN_REQUEST = 65743; + + /** + * Post message that is sent by Print Dialog web page when the printing dialog + * needs to be closed. + */ + private static final String CLOSE_POST_MESSAGE_NAME = "cp-dialog-on-close"; + + /** + * Web view element to show the printing dialog in. + */ + private WebView dialogWebView; + + /** + * Intent that started the action. + */ + Intent cloudPrintIntent; + + private int resultCode; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + resultCode = RESULT_OK; + setContentView(R.layout.print_dialog); + dialogWebView = (WebView) findViewById(R.id.webview); + cloudPrintIntent = this.getIntent(); + + WebSettings settings = dialogWebView.getSettings(); + settings.setJavaScriptEnabled(true); + + dialogWebView.setWebViewClient(new PrintDialogWebClient()); + dialogWebView.addJavascriptInterface( + new PrintDialogJavaScriptInterface(), JS_INTERFACE); + + dialogWebView.loadUrl(PRINT_DIALOG_URL); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == ZXING_SCAN_REQUEST && resultCode == RESULT_OK) { + dialogWebView.loadUrl(intent.getStringExtra("SCAN_RESULT")); + } + } + + final class PrintDialogJavaScriptInterface { + public String getType() { + return cloudPrintIntent.getType(); + } + + public String getTitle() { + return cloudPrintIntent.getExtras().getString("title"); + } + + public String getContent() { + try { + ContentResolver contentResolver = getContentResolver(); + InputStream is = contentResolver.openInputStream(cloudPrintIntent.getData()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + byte[] buffer = new byte[4096]; + int n = is.read(buffer); + while (n >= 0) { + baos.write(buffer, 0, n); + n = is.read(buffer); + } + is.close(); + baos.flush(); + + return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT); + } catch (Throwable e) { + resultCode = RESULT_CANCELED; + setResult(resultCode); + finish(); + e.printStackTrace(); + } + return ""; + } + + public String getEncoding() { + return CONTENT_TRANSFER_ENCODING; + } + + public void onPostMessage(String message) { + if (message.startsWith(CLOSE_POST_MESSAGE_NAME)) { + setResult(resultCode); + finish(); + } + } + } + + private final class PrintDialogWebClient extends WebViewClient { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (url.startsWith(ZXING_URL)) { + Intent intentScan = new Intent("com.google.zxing.client.android.SCAN"); + intentScan.putExtra("SCAN_MODE", "QR_CODE_MODE"); + try { + startActivityForResult(intentScan, ZXING_SCAN_REQUEST); + } catch (ActivityNotFoundException error) { + view.loadUrl(url); + } + } else { + view.loadUrl(url); + } + return false; + } + + @Override + public void onPageFinished(WebView view, String url) { + if (PRINT_DIALOG_URL.equals(url)) { + // Submit print document. + view.loadUrl("javascript:printDialog.setPrintDocument(printDialog.createPrintDocument(" + + "window." + JS_INTERFACE + ".getType(),window." + JS_INTERFACE + ".getTitle()," + + "window." + JS_INTERFACE + ".getContent(),window." + JS_INTERFACE + ".getEncoding()))"); + + // Add post messages listener. + view.loadUrl("javascript:window.addEventListener('message'," + + "function(evt){window." + JS_INTERFACE + ".onPostMessage(evt.data)}, false)"); + } + } + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java b/platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java new file mode 100644 index 00000000..65d8f665 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/ReaderView.java @@ -0,0 +1,936 @@ +package com.artifex.mupdfdemo; + +import java.util.LinkedList; +import java.util.NoSuchElementException; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.DialogInterface; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.widget.Adapter; +import android.widget.AdapterView; +import android.widget.Scroller; + +public class ReaderView + extends AdapterView + implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable { + private static final int MOVING_DIAGONALLY = 0; + private static final int MOVING_LEFT = 1; + private static final int MOVING_RIGHT = 2; + private static final int MOVING_UP = 3; + private static final int MOVING_DOWN = 4; + + private static final int FLING_MARGIN = 100; + private static final int GAP = 20; + + private static final float MIN_SCALE = 1.0f; + private static final float MAX_SCALE = 5.0f; + private static final float REFLOW_SCALE_FACTOR = 0.5f; + + private static final boolean HORIZONTAL_SCROLLING = true; + + private Adapter mAdapter; + private int mCurrent; // Adapter's index for the current view + private boolean mResetLayout; + private final SparseArray + mChildViews = new SparseArray(3); + // Shadows the children of the adapter view + // but with more sensible indexing + private final LinkedList + mViewCache = new LinkedList(); + private boolean mUserInteracting; // Whether the user is interacting + private boolean mScaling; // Whether the user is currently pinch zooming + private float mScale = 1.0f; + private int mXScroll; // Scroll amounts recorded from events. + private int mYScroll; // and then accounted for in onLayout + private boolean mReflow = false; + private boolean mReflowChanged = false; + private final GestureDetector + mGestureDetector; + private final ScaleGestureDetector + mScaleGestureDetector; + private final Scroller mScroller; + private final Stepper mStepper; + private int mScrollerLastX; + private int mScrollerLastY; + private float mLastScaleFocusX; + private float mLastScaleFocusY; + + static abstract class ViewMapper { + abstract void applyToView(View view); + } + + public ReaderView(Context context) { + super(context); + mGestureDetector = new GestureDetector(this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + mStepper = new Stepper(this, this); + } + + public ReaderView(Context context, AttributeSet attrs) { + super(context, attrs); + + // "Edit mode" means when the View is being displayed in the Android GUI editor. (this class + // is instantiated in the IDE, so we need to be a bit careful what we do). + if (isInEditMode()) + { + mGestureDetector = null; + mScaleGestureDetector = null; + mScroller = null; + mStepper = null; + } + else + { + mGestureDetector = new GestureDetector(this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + mStepper = new Stepper(this, this); + } + } + + public ReaderView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mGestureDetector = new GestureDetector(this); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScroller = new Scroller(context); + mStepper = new Stepper(this, this); + } + + public int getDisplayedViewIndex() { + return mCurrent; + } + + public void setDisplayedViewIndex(int i) { + if (0 <= i && i < mAdapter.getCount()) { + onMoveOffChild(mCurrent); + mCurrent = i; + onMoveToChild(i); + mResetLayout = true; + requestLayout(); + } + } + + public void moveToNext() { + View v = mChildViews.get(mCurrent+1); + if (v != null) + slideViewOntoScreen(v); + } + + public void moveToPrevious() { + View v = mChildViews.get(mCurrent-1); + if (v != null) + slideViewOntoScreen(v); + } + + // When advancing down the page, we want to advance by about + // 90% of a screenful. But we'd be happy to advance by between + // 80% and 95% if it means we hit the bottom in a whole number + // of steps. + private int smartAdvanceAmount(int screenHeight, int max) { + int advance = (int)(screenHeight * 0.9 + 0.5); + int leftOver = max % advance; + int steps = max / advance; + if (leftOver == 0) { + // We'll make it exactly. No adjustment + } else if ((float)leftOver / steps <= screenHeight * 0.05) { + // We can adjust up by less than 5% to make it exact. + advance += (int)((float)leftOver/steps + 0.5); + } else { + int overshoot = advance - leftOver; + if ((float)overshoot / steps <= screenHeight * 0.1) { + // We can adjust down by less than 10% to make it exact. + advance -= (int)((float)overshoot/steps + 0.5); + } + } + if (advance > max) + advance = max; + return advance; + } + + public void smartMoveForwards() { + View v = mChildViews.get(mCurrent); + if (v == null) + return; + + // The following code works in terms of where the screen is on the views; + // so for example, if the currentView is at (-100,-100), the visible + // region would be at (100,100). If the previous page was (2000, 3000) in + // size, the visible region of the previous page might be (2100 + GAP, 100) + // (i.e. off the previous page). This is different to the way the rest of + // the code in this file is written, but it's easier for me to think about. + // At some point we may refactor this to fit better with the rest of the + // code. + + // screenWidth/Height are the actual width/height of the screen. e.g. 480/800 + int screenWidth = getWidth(); + int screenHeight = getHeight(); + // We might be mid scroll; we want to calculate where we scroll to based on + // where this scroll would end, not where we are now (to allow for people + // bashing 'forwards' very fast. + int remainingX = mScroller.getFinalX() - mScroller.getCurrX(); + int remainingY = mScroller.getFinalY() - mScroller.getCurrY(); + // right/bottom is in terms of pixels within the scaled document; e.g. 1000 + int top = -(v.getTop() + mYScroll + remainingY); + int right = screenWidth -(v.getLeft() + mXScroll + remainingX); + int bottom = screenHeight+top; + // docWidth/Height are the width/height of the scaled document e.g. 2000x3000 + int docWidth = v.getMeasuredWidth(); + int docHeight = v.getMeasuredHeight(); + + int xOffset, yOffset; + if (bottom >= docHeight) { + // We are flush with the bottom. Advance to next column. + if (right + screenWidth > docWidth) { + // No room for another column - go to next page + View nv = mChildViews.get(mCurrent+1); + if (nv == null) // No page to advance to + return; + int nextTop = -(nv.getTop() + mYScroll + remainingY); + int nextLeft = -(nv.getLeft() + mXScroll + remainingX); + int nextDocWidth = nv.getMeasuredWidth(); + int nextDocHeight = nv.getMeasuredHeight(); + + // Allow for the next page maybe being shorter than the screen is high + yOffset = (nextDocHeight < screenHeight ? ((nextDocHeight - screenHeight)>>1) : 0); + + if (nextDocWidth < screenWidth) { + // Next page is too narrow to fill the screen. Scroll to the top, centred. + xOffset = (nextDocWidth - screenWidth)>>1; + } else { + // Reset X back to the left hand column + xOffset = right % screenWidth; + // Adjust in case the previous page is less wide + if (xOffset + screenWidth > nextDocWidth) + xOffset = nextDocWidth - screenWidth; + } + xOffset -= nextLeft; + yOffset -= nextTop; + } else { + // Move to top of next column + xOffset = screenWidth; + yOffset = screenHeight - bottom; + } + } else { + // Advance by 90% of the screen height downwards (in case lines are partially cut off) + xOffset = 0; + yOffset = smartAdvanceAmount(screenHeight, docHeight - bottom); + } + mScrollerLastX = mScrollerLastY = 0; + mScroller.startScroll(0, 0, remainingX - xOffset, remainingY - yOffset, 400); + mStepper.prod(); + } + + public void smartMoveBackwards() { + View v = mChildViews.get(mCurrent); + if (v == null) + return; + + // The following code works in terms of where the screen is on the views; + // so for example, if the currentView is at (-100,-100), the visible + // region would be at (100,100). If the previous page was (2000, 3000) in + // size, the visible region of the previous page might be (2100 + GAP, 100) + // (i.e. off the previous page). This is different to the way the rest of + // the code in this file is written, but it's easier for me to think about. + // At some point we may refactor this to fit better with the rest of the + // code. + + // screenWidth/Height are the actual width/height of the screen. e.g. 480/800 + int screenWidth = getWidth(); + int screenHeight = getHeight(); + // We might be mid scroll; we want to calculate where we scroll to based on + // where this scroll would end, not where we are now (to allow for people + // bashing 'forwards' very fast. + int remainingX = mScroller.getFinalX() - mScroller.getCurrX(); + int remainingY = mScroller.getFinalY() - mScroller.getCurrY(); + // left/top is in terms of pixels within the scaled document; e.g. 1000 + int left = -(v.getLeft() + mXScroll + remainingX); + int top = -(v.getTop() + mYScroll + remainingY); + // docWidth/Height are the width/height of the scaled document e.g. 2000x3000 + int docHeight = v.getMeasuredHeight(); + + int xOffset, yOffset; + if (top <= 0) { + // We are flush with the top. Step back to previous column. + if (left < screenWidth) { + /* No room for previous column - go to previous page */ + View pv = mChildViews.get(mCurrent-1); + if (pv == null) /* No page to advance to */ + return; + int prevDocWidth = pv.getMeasuredWidth(); + int prevDocHeight = pv.getMeasuredHeight(); + + // Allow for the next page maybe being shorter than the screen is high + yOffset = (prevDocHeight < screenHeight ? ((prevDocHeight - screenHeight)>>1) : 0); + + int prevLeft = -(pv.getLeft() + mXScroll); + int prevTop = -(pv.getTop() + mYScroll); + if (prevDocWidth < screenWidth) { + // Previous page is too narrow to fill the screen. Scroll to the bottom, centred. + xOffset = (prevDocWidth - screenWidth)>>1; + } else { + // Reset X back to the right hand column + xOffset = (left > 0 ? left % screenWidth : 0); + if (xOffset + screenWidth > prevDocWidth) + xOffset = prevDocWidth - screenWidth; + while (xOffset + screenWidth*2 < prevDocWidth) + xOffset += screenWidth; + } + xOffset -= prevLeft; + yOffset -= prevTop-prevDocHeight+screenHeight; + } else { + // Move to bottom of previous column + xOffset = -screenWidth; + yOffset = docHeight - screenHeight + top; + } + } else { + // Retreat by 90% of the screen height downwards (in case lines are partially cut off) + xOffset = 0; + yOffset = -smartAdvanceAmount(screenHeight, top); + } + mScrollerLastX = mScrollerLastY = 0; + mScroller.startScroll(0, 0, remainingX - xOffset, remainingY - yOffset, 400); + mStepper.prod(); + } + + public void resetupChildren() { + for (int i = 0; i < mChildViews.size(); i++) + onChildSetup(mChildViews.keyAt(i), mChildViews.valueAt(i)); + } + + public void applyToChildren(ViewMapper mapper) { + for (int i = 0; i < mChildViews.size(); i++) + mapper.applyToView(mChildViews.valueAt(i)); + } + + public void refresh(boolean reflow) { + mReflow = reflow; + mReflowChanged = true; + mResetLayout = true; + + mScale = 1.0f; + mXScroll = mYScroll = 0; + + requestLayout(); + } + + protected void onChildSetup(int i, View v) {} + + protected void onMoveToChild(int i) {} + + protected void onMoveOffChild(int i) {} + + protected void onSettle(View v) {}; + + protected void onUnsettle(View v) {}; + + protected void onNotInUse(View v) {}; + + protected void onScaleChild(View v, Float scale) {}; + + public View getView(int i) { + return mChildViews.get(i); + } + + public View getDisplayedView() { + return mChildViews.get(mCurrent); + } + + public void run() { + if (!mScroller.isFinished()) { + mScroller.computeScrollOffset(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + mXScroll += x - mScrollerLastX; + mYScroll += y - mScrollerLastY; + mScrollerLastX = x; + mScrollerLastY = y; + requestLayout(); + mStepper.prod(); + } + else if (!mUserInteracting) { + // End of an inertial scroll and the user is not interacting. + // The layout is stable + View v = mChildViews.get(mCurrent); + if (v != null) + postSettle(v); + } + } + + public boolean onDown(MotionEvent arg0) { + mScroller.forceFinished(true); + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (mScaling) + return true; + + View v = mChildViews.get(mCurrent); + if (v != null) { + Rect bounds = getScrollBounds(v); + switch(directionOfTravel(velocityX, velocityY)) { + case MOVING_LEFT: + if (HORIZONTAL_SCROLLING && bounds.left >= 0) { + // Fling off to the left bring next view onto screen + View vl = mChildViews.get(mCurrent+1); + + if (vl != null) { + slideViewOntoScreen(vl); + return true; + } + } + break; + case MOVING_UP: + if (!HORIZONTAL_SCROLLING && bounds.top >= 0) { + // Fling off to the top bring next view onto screen + View vl = mChildViews.get(mCurrent+1); + + if (vl != null) { + slideViewOntoScreen(vl); + return true; + } + } + break; + case MOVING_RIGHT: + if (HORIZONTAL_SCROLLING && bounds.right <= 0) { + // Fling off to the right bring previous view onto screen + View vr = mChildViews.get(mCurrent-1); + + if (vr != null) { + slideViewOntoScreen(vr); + return true; + } + } + break; + case MOVING_DOWN: + if (!HORIZONTAL_SCROLLING && bounds.bottom <= 0) { + // Fling off to the bottom bring previous view onto screen + View vr = mChildViews.get(mCurrent-1); + + if (vr != null) { + slideViewOntoScreen(vr); + return true; + } + } + break; + } + mScrollerLastX = mScrollerLastY = 0; + // If the page has been dragged out of bounds then we want to spring back + // nicely. fling jumps back into bounds instantly, so we don't want to use + // fling in that case. On the other hand, we don't want to forgo a fling + // just because of a slightly off-angle drag taking us out of bounds other + // than in the direction of the drag, so we test for out of bounds only + // in the direction of travel. + // + // Also don't fling if out of bounds in any direction by more than fling + // margin + Rect expandedBounds = new Rect(bounds); + expandedBounds.inset(-FLING_MARGIN, -FLING_MARGIN); + + if(withinBoundsInDirectionOfTravel(bounds, velocityX, velocityY) + && expandedBounds.contains(0, 0)) { + mScroller.fling(0, 0, (int)velocityX, (int)velocityY, bounds.left, bounds.right, bounds.top, bounds.bottom); + mStepper.prod(); + } + } + + return true; + } + + public void onLongPress(MotionEvent e) { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + if (!mScaling) { + mXScroll -= distanceX; + mYScroll -= distanceY; + requestLayout(); + } + return true; + } + + public void onShowPress(MotionEvent e) { + } + + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + public boolean onScale(ScaleGestureDetector detector) { + float previousScale = mScale; + float scale_factor = mReflow ? REFLOW_SCALE_FACTOR : 1.0f; + float min_scale = MIN_SCALE * scale_factor; + float max_scale = MAX_SCALE * scale_factor; + mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), min_scale), max_scale); + + if (mReflow) { + View v = mChildViews.get(mCurrent); + if (v != null) + onScaleChild(v, mScale); + } else { + float factor = mScale/previousScale; + + View v = mChildViews.get(mCurrent); + if (v != null) { + float currentFocusX = detector.getFocusX(); + float currentFocusY = detector.getFocusY(); + // Work out the focus point relative to the view top left + int viewFocusX = (int)currentFocusX - (v.getLeft() + mXScroll); + int viewFocusY = (int)currentFocusY - (v.getTop() + mYScroll); + // Scroll to maintain the focus point + mXScroll += viewFocusX - viewFocusX * factor; + mYScroll += viewFocusY - viewFocusY * factor; + + if (mLastScaleFocusX>=0) + mXScroll+=currentFocusX-mLastScaleFocusX; + if (mLastScaleFocusY>=0) + mYScroll+=currentFocusY-mLastScaleFocusY; + + mLastScaleFocusX=currentFocusX; + mLastScaleFocusY=currentFocusY; + requestLayout(); + } + } + return true; + } + + public boolean onScaleBegin(ScaleGestureDetector detector) { + mScaling = true; + // Ignore any scroll amounts yet to be accounted for: the + // screen is not showing the effect of them, so they can + // only confuse the user + mXScroll = mYScroll = 0; + mLastScaleFocusX = mLastScaleFocusY = -1; + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) { + if (mReflow) { + applyToChildren(new ViewMapper() { + @Override + void applyToView(View view) { + onScaleChild(view, mScale); + } + }); + } + mScaling = false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + mScaleGestureDetector.onTouchEvent(event); + mGestureDetector.onTouchEvent(event); + + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { + mUserInteracting = true; + } + if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { + mUserInteracting = false; + + View v = mChildViews.get(mCurrent); + if (v != null) { + if (mScroller.isFinished()) { + // If, at the end of user interaction, there is no + // current inertial scroll in operation then animate + // the view onto screen if necessary + slideViewOntoScreen(v); + } + + if (mScroller.isFinished()) { + // If still there is no inertial scroll in operation + // then the layout is stable + postSettle(v); + } + } + } + + requestLayout(); + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int n = getChildCount(); + for (int i = 0; i < n; i++) + measureView(getChildAt(i)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + try { + onLayout2(changed, left, top, right, bottom); + } + catch (java.lang.OutOfMemoryError e) { + System.out.println("Out of memory during layout"); + + // we might get an out of memory error. + // so let's display an alert. + // TODO: a better message, in resources. + + if (!memAlert) { + memAlert = true; + AlertDialog alertDialog = MuPDFActivity.getAlertBuilder().create(); + alertDialog.setMessage("Out of memory during layout"); + alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + memAlert = false; + } + }); + alertDialog.show(); + } + } + } + + private boolean memAlert = false; + + private void onLayout2(boolean changed, int left, int top, int right, + int bottom) { + + // "Edit mode" means when the View is being displayed in the Android GUI editor. (this class + // is instantiated in the IDE, so we need to be a bit careful what we do). + if (isInEditMode()) + return; + + View cv = mChildViews.get(mCurrent); + Point cvOffset; + + if (!mResetLayout) { + // Move to next or previous if current is sufficiently off center + if (cv != null) { + boolean move; + cvOffset = subScreenSizeOffset(cv); + // cv.getRight() may be out of date with the current scale + // so add left to the measured width for the correct position + if (HORIZONTAL_SCROLLING) + move = cv.getLeft() + cv.getMeasuredWidth() + cvOffset.x + GAP/2 + mXScroll < getWidth()/2; + else + move = cv.getTop() + cv.getMeasuredHeight() + cvOffset.y + GAP/2 + mYScroll < getHeight()/2; + if (move && mCurrent + 1 < mAdapter.getCount()) { + postUnsettle(cv); + // post to invoke test for end of animation + // where we must set hq area for the new current view + mStepper.prod(); + + onMoveOffChild(mCurrent); + mCurrent++; + onMoveToChild(mCurrent); + } + + if (HORIZONTAL_SCROLLING) + move = cv.getLeft() - cvOffset.x - GAP/2 + mXScroll >= getWidth()/2; + else + move = cv.getTop() - cvOffset.y - GAP/2 + mYScroll >= getHeight()/2; + if (move && mCurrent > 0) { + postUnsettle(cv); + // post to invoke test for end of animation + // where we must set hq area for the new current view + mStepper.prod(); + + onMoveOffChild(mCurrent); + mCurrent--; + onMoveToChild(mCurrent); + } + } + + // Remove not needed children and hold them for reuse + int numChildren = mChildViews.size(); + int childIndices[] = new int[numChildren]; + for (int i = 0; i < numChildren; i++) + childIndices[i] = mChildViews.keyAt(i); + + for (int i = 0; i < numChildren; i++) { + int ai = childIndices[i]; + if (ai < mCurrent - 1 || ai > mCurrent + 1) { + View v = mChildViews.get(ai); + onNotInUse(v); + mViewCache.add(v); + removeViewInLayout(v); + mChildViews.remove(ai); + } + } + } else { + mResetLayout = false; + mXScroll = mYScroll = 0; + + // Remove all children and hold them for reuse + int numChildren = mChildViews.size(); + for (int i = 0; i < numChildren; i++) { + View v = mChildViews.valueAt(i); + onNotInUse(v); + mViewCache.add(v); + removeViewInLayout(v); + } + mChildViews.clear(); + + // Don't reuse cached views if the adapter has changed + if (mReflowChanged) { + mReflowChanged = false; + mViewCache.clear(); + } + + // post to ensure generation of hq area + mStepper.prod(); + } + + // Ensure current view is present + int cvLeft, cvRight, cvTop, cvBottom; + boolean notPresent = (mChildViews.get(mCurrent) == null); + cv = getOrCreateChild(mCurrent); + // When the view is sub-screen-size in either dimension we + // offset it to center within the screen area, and to keep + // the views spaced out + cvOffset = subScreenSizeOffset(cv); + if (notPresent) { + //Main item not already present. Just place it top left + cvLeft = cvOffset.x; + cvTop = cvOffset.y; + } else { + // Main item already present. Adjust by scroll offsets + cvLeft = cv.getLeft() + mXScroll; + cvTop = cv.getTop() + mYScroll; + } + // Scroll values have been accounted for + mXScroll = mYScroll = 0; + cvRight = cvLeft + cv.getMeasuredWidth(); + cvBottom = cvTop + cv.getMeasuredHeight(); + + if (!mUserInteracting && mScroller.isFinished()) { + Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); + cvRight += corr.x; + cvLeft += corr.x; + cvTop += corr.y; + cvBottom += corr.y; + } else if (HORIZONTAL_SCROLLING && cv.getMeasuredHeight() <= getHeight()) { + // When the current view is as small as the screen in height, clamp + // it vertically + Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); + cvTop += corr.y; + cvBottom += corr.y; + } else if (!HORIZONTAL_SCROLLING && cv.getMeasuredWidth() <= getWidth()) { + // When the current view is as small as the screen in width, clamp + // it horizontally + Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom)); + cvRight += corr.x; + cvLeft += corr.x; + } + + cv.layout(cvLeft, cvTop, cvRight, cvBottom); + + if (mCurrent > 0) { + View lv = getOrCreateChild(mCurrent - 1); + Point leftOffset = subScreenSizeOffset(lv); + if (HORIZONTAL_SCROLLING) + { + int gap = leftOffset.x + GAP + cvOffset.x; + lv.layout(cvLeft - lv.getMeasuredWidth() - gap, + (cvBottom + cvTop - lv.getMeasuredHeight())/2, + cvLeft - gap, + (cvBottom + cvTop + lv.getMeasuredHeight())/2); + } else { + int gap = leftOffset.y + GAP + cvOffset.y; + lv.layout((cvLeft + cvRight - lv.getMeasuredWidth())/2, + cvTop - lv.getMeasuredHeight() - gap, + (cvLeft + cvRight + lv.getMeasuredWidth())/2, + cvTop - gap); + } + } + + if (mCurrent + 1 < mAdapter.getCount()) { + View rv = getOrCreateChild(mCurrent + 1); + Point rightOffset = subScreenSizeOffset(rv); + if (HORIZONTAL_SCROLLING) + { + int gap = cvOffset.x + GAP + rightOffset.x; + rv.layout(cvRight + gap, + (cvBottom + cvTop - rv.getMeasuredHeight())/2, + cvRight + rv.getMeasuredWidth() + gap, + (cvBottom + cvTop + rv.getMeasuredHeight())/2); + } else { + int gap = cvOffset.y + GAP + rightOffset.y; + rv.layout((cvLeft + cvRight - rv.getMeasuredWidth())/2, + cvBottom + gap, + (cvLeft + cvRight + rv.getMeasuredWidth())/2, + cvBottom + gap + rv.getMeasuredHeight()); + } + } + + invalidate(); + } + + @Override + public Adapter getAdapter() { + return mAdapter; + } + + @Override + public View getSelectedView() { + return null; + } + + @Override + public void setAdapter(Adapter adapter) { + + // release previous adapter's bitmaps + if (null!=mAdapter && adapter!=mAdapter) { + if (adapter instanceof MuPDFPageAdapter){ + ((MuPDFPageAdapter) adapter).releaseBitmaps(); + } + } + + mAdapter = adapter; + + requestLayout(); + } + + @Override + public void setSelection(int arg0) { + throw new UnsupportedOperationException(getContext().getString(R.string.not_supported)); + } + + private View getCached() { + if (mViewCache.size() == 0) + return null; + else + return mViewCache.removeFirst(); + } + + private View getOrCreateChild(int i) { + View v = mChildViews.get(i); + if (v == null) { + v = mAdapter.getView(i, getCached(), this); + addAndMeasureChild(i, v); + onChildSetup(i, v); + onScaleChild(v, mScale); + } + + return v; + } + + private void addAndMeasureChild(int i, View v) { + LayoutParams params = v.getLayoutParams(); + if (params == null) { + params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + addViewInLayout(v, 0, params, true); + mChildViews.append(i, v); // Record the view against it's adapter index + measureView(v); + } + + private void measureView(View v) { + // See what size the view wants to be + v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + + if (!mReflow) { + // Work out a scale that will fit it to this view + float scale = Math.min((float)getWidth()/(float)v.getMeasuredWidth(), + (float)getHeight()/(float)v.getMeasuredHeight()); + // Use the fitting values scaled by our current scale factor + v.measure(View.MeasureSpec.EXACTLY | (int)(v.getMeasuredWidth()*scale*mScale), + View.MeasureSpec.EXACTLY | (int)(v.getMeasuredHeight()*scale*mScale)); + } else { + v.measure(View.MeasureSpec.EXACTLY | (int)(v.getMeasuredWidth()), + View.MeasureSpec.EXACTLY | (int)(v.getMeasuredHeight())); + } + } + + private Rect getScrollBounds(int left, int top, int right, int bottom) { + int xmin = getWidth() - right; + int xmax = -left; + int ymin = getHeight() - bottom; + int ymax = -top; + + // In either dimension, if view smaller than screen then + // constrain it to be central + if (xmin > xmax) xmin = xmax = (xmin + xmax)/2; + if (ymin > ymax) ymin = ymax = (ymin + ymax)/2; + + return new Rect(xmin, ymin, xmax, ymax); + } + + private Rect getScrollBounds(View v) { + // There can be scroll amounts not yet accounted for in + // onLayout, so add mXScroll and mYScroll to the current + // positions when calculating the bounds. + return getScrollBounds(v.getLeft() + mXScroll, + v.getTop() + mYScroll, + v.getLeft() + v.getMeasuredWidth() + mXScroll, + v.getTop() + v.getMeasuredHeight() + mYScroll); + } + + private Point getCorrection(Rect bounds) { + return new Point(Math.min(Math.max(0,bounds.left),bounds.right), + Math.min(Math.max(0,bounds.top),bounds.bottom)); + } + + private void postSettle(final View v) { + // onSettle and onUnsettle are posted so that the calls + // wont be executed until after the system has performed + // layout. + post (new Runnable() { + public void run () { + onSettle(v); + } + }); + } + + private void postUnsettle(final View v) { + post (new Runnable() { + public void run () { + onUnsettle(v); + } + }); + } + + private void slideViewOntoScreen(View v) { + Point corr = getCorrection(getScrollBounds(v)); + if (corr.x != 0 || corr.y != 0) { + mScrollerLastX = mScrollerLastY = 0; + mScroller.startScroll(0, 0, corr.x, corr.y, 400); + mStepper.prod(); + } + } + + private Point subScreenSizeOffset(View v) { + return new Point(Math.max((getWidth() - v.getMeasuredWidth())/2, 0), + Math.max((getHeight() - v.getMeasuredHeight())/2, 0)); + } + + private static int directionOfTravel(float vx, float vy) { + if (Math.abs(vx) > 2 * Math.abs(vy)) + return (vx > 0) ? MOVING_RIGHT : MOVING_LEFT; + else if (Math.abs(vy) > 2 * Math.abs(vx)) + return (vy > 0) ? MOVING_DOWN : MOVING_UP; + else + return MOVING_DIAGONALLY; + } + + private static boolean withinBoundsInDirectionOfTravel(Rect bounds, float vx, float vy) { + switch (directionOfTravel(vx, vy)) { + case MOVING_DIAGONALLY: return bounds.contains(0, 0); + case MOVING_LEFT: return bounds.left <= 0; + case MOVING_RIGHT: return bounds.right >= 0; + case MOVING_UP: return bounds.top <= 0; + case MOVING_DOWN: return bounds.bottom >= 0; + default: throw new NoSuchElementException(); + } + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java b/platform/android/viewer/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java new file mode 100644 index 00000000..7f715bb4 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/SafeAnimatorInflater.java @@ -0,0 +1,35 @@ +package com.artifex.mupdfdemo; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.animation.AnimatorSet; +import android.app.Activity; +import android.view.View; + +public class SafeAnimatorInflater +{ + private View mView; + + public SafeAnimatorInflater(Activity activity, int animation, View view) + { + AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(activity, R.animator.info); + mView = view; + set.setTarget(view); + set.addListener(new Animator.AnimatorListener() { + public void onAnimationStart(Animator animation) { + mView.setVisibility(View.VISIBLE); + } + + public void onAnimationRepeat(Animator animation) { + } + + public void onAnimationEnd(Animator animation) { + mView.setVisibility(View.INVISIBLE); + } + + public void onAnimationCancel(Animator animation) { + } + }); + set.start(); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java new file mode 100644 index 00000000..d3969f10 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTask.java @@ -0,0 +1,128 @@ +package com.artifex.mupdfdemo; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.RectF; +import android.os.Handler; + +class ProgressDialogX extends ProgressDialog { + public ProgressDialogX(Context context) { + super(context); + } + + private boolean mCancelled = false; + + public boolean isCancelled() { + return mCancelled; + } + + @Override + public void cancel() { + mCancelled = true; + super.cancel(); + } +} + +public abstract class SearchTask { + private static final int SEARCH_PROGRESS_DELAY = 200; + private final Context mContext; + private final MuPDFCore mCore; + private final Handler mHandler; + private final AlertDialog.Builder mAlertBuilder; + private AsyncTask mSearchTask; + + public SearchTask(Context context, MuPDFCore core) { + mContext = context; + mCore = core; + mHandler = new Handler(); + mAlertBuilder = new AlertDialog.Builder(context); + } + + protected abstract void onTextFound(SearchTaskResult result); + + public void stop() { + if (mSearchTask != null) { + mSearchTask.cancel(true); + mSearchTask = null; + } + } + + public void go(final String text, int direction, int displayPage, int searchPage) { + if (mCore == null) + return; + stop(); + + final int increment = direction; + final int startIndex = searchPage == -1 ? displayPage : searchPage + increment; + + final ProgressDialogX progressDialog = new ProgressDialogX(mContext); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setTitle(mContext.getString(R.string.searching_)); + progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + stop(); + } + }); + progressDialog.setMax(mCore.countPages()); + + mSearchTask = new AsyncTask() { + @Override + protected SearchTaskResult doInBackground(Void... params) { + int index = startIndex; + + while (0 <= index && index < mCore.countPages() && !isCancelled()) { + publishProgress(index); + RectF searchHits[] = mCore.searchPage(index, text); + + if (searchHits != null && searchHits.length > 0) + return new SearchTaskResult(text, index, searchHits); + + index += increment; + } + return null; + } + + @Override + protected void onPostExecute(SearchTaskResult result) { + progressDialog.cancel(); + if (result != null) { + onTextFound(result); + } else { + mAlertBuilder.setTitle(SearchTaskResult.get() == null ? R.string.text_not_found : R.string.no_further_occurrences_found); + AlertDialog alert = mAlertBuilder.create(); + alert.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getString(R.string.dismiss), + (DialogInterface.OnClickListener)null); + alert.show(); + } + } + + @Override + protected void onCancelled() { + progressDialog.cancel(); + } + + @Override + protected void onProgressUpdate(Integer... values) { + progressDialog.setProgress(values[0].intValue()); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mHandler.postDelayed(new Runnable() { + public void run() { + if (!progressDialog.isCancelled()) + { + progressDialog.show(); + progressDialog.setProgress(startIndex); + } + } + }, SEARCH_PROGRESS_DELAY); + } + }; + + mSearchTask.execute(); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java new file mode 100644 index 00000000..8fa3c3a2 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/SearchTaskResult.java @@ -0,0 +1,24 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class SearchTaskResult { + public final String txt; + public final int pageNumber; + public final RectF searchBoxes[]; + static private SearchTaskResult singleton; + + SearchTaskResult(String _txt, int _pageNumber, RectF _searchBoxes[]) { + txt = _txt; + pageNumber = _pageNumber; + searchBoxes = _searchBoxes; + } + + static public SearchTaskResult get() { + return singleton; + } + + static public void set(SearchTaskResult r) { + singleton = r; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/Separation.java b/platform/android/viewer/src/com/artifex/mupdfdemo/Separation.java new file mode 100644 index 00000000..eadda4ba --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/Separation.java @@ -0,0 +1,15 @@ +package com.artifex.mupdfdemo; + +public class Separation +{ + String name; + int rgba; + int cmyk; + + public Separation(String name, int rgba, int cmyk) + { + this.name = name; + this.rgba = rgba; + this.cmyk = cmyk; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/Stepper.java b/platform/android/viewer/src/com/artifex/mupdfdemo/Stepper.java new file mode 100644 index 00000000..d22240ef --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/Stepper.java @@ -0,0 +1,42 @@ +package com.artifex.mupdfdemo; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.view.View; + +public class Stepper { + protected final View mPoster; + protected final Runnable mTask; + protected boolean mPending; + + public Stepper(View v, Runnable r) { + mPoster = v; + mTask = r; + mPending = false; + } + + @SuppressLint("NewApi") + public void prod() { + if (!mPending) { + mPending = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mPoster.postOnAnimation(new Runnable() { + @Override + public void run() { + mPending = false; + mTask.run(); + } + }); + } else { + mPoster.post(new Runnable() { + @Override + public void run() { + mPending = false; + mTask.run(); + } + }); + + } + } + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/TextChar.java b/platform/android/viewer/src/com/artifex/mupdfdemo/TextChar.java new file mode 100644 index 00000000..aebf519f --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/TextChar.java @@ -0,0 +1,12 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class TextChar extends RectF { + public char c; + + public TextChar(float x0, float y0, float x1, float y1, char _c) { + super(x0, y0, x1, y1); + c = _c; + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/TextWord.java b/platform/android/viewer/src/com/artifex/mupdfdemo/TextWord.java new file mode 100644 index 00000000..d9672573 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/TextWord.java @@ -0,0 +1,17 @@ +package com.artifex.mupdfdemo; + +import android.graphics.RectF; + +public class TextWord extends RectF { + public String w; + + public TextWord() { + super(); + w = new String(); + } + + public void Add(TextChar tc) { + super.union(tc); + w = w.concat(new String(new char[]{tc.c})); + } +} diff --git a/platform/android/viewer/src/com/artifex/mupdfdemo/WidgetType.java b/platform/android/viewer/src/com/artifex/mupdfdemo/WidgetType.java new file mode 100644 index 00000000..882a38f2 --- /dev/null +++ b/platform/android/viewer/src/com/artifex/mupdfdemo/WidgetType.java @@ -0,0 +1,9 @@ +package com.artifex.mupdfdemo; + +public enum WidgetType { + NONE, + TEXT, + LISTBOX, + COMBOBOX, + SIGNATURE +} diff --git a/platform/java/Makefile b/platform/java/Makefile index 3e7df255..fc889c1a 100644 --- a/platform/java/Makefile +++ b/platform/java/Makefile @@ -1,22 +1,44 @@ -default: libmupdf_java.so libmupdf.jar +OS ?= $(shell uname) -MUPDF_CORE = ../../build/java/libmupdf.a ../../build/java/libmupdfthird.a +ifeq "$(OS)" "Darwin" +MUPDF_JAVA := libmupdf_java.jnilib +JAVA_CFLAGS := \ + -I /Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/include \ + -I /Library/Java/JavaVirtualMachines/jdk1.8.0_66.jdk/Contents/Home/include/darwin +else +MUPDF_JAVA := libmupdf_java.so +JAVA_CFLAGS := \ + -I /usr/lib/jvm/java-7-openjdk-i386/include \ + -I /usr/lib/jvm/java-7-openjdk-i386/include/linux \ + -I /usr/lib/jvm/java-7-openjdk-amd64/include \ + -I /usr/lib/jvm/java-7-openjdk-amd64/include/linux +JAVA_LIBS := \ + $(shell pkg-config --cflags freetype2) -lz +endif + +default: $(MUPDF_JAVA) libmupdf.jar + +MUPDF_CORE := ../../build/java/libmupdf.a ../../build/java/libmupdfthird.a +ifeq "$(OS)" "Linux" +$(MUPDF_CORE) : + $(MAKE) -C ../.. OUT=build/java XCFLAGS=-fPIC FREETYPE_DIR=/foo ZLIB_DIR=/foo build=release libs +else +$(MUPDF_CORE) : + $(MAKE) -C ../.. OUT=build/java XCFLAGS=-fPIC build=release libs +endif LIBRARY_JAVA_SOURCES := $(sort $(wildcard com/artifex/mupdf/fitz/*.java)) LIBRARY_JAVA_OBJECTS := $(LIBRARY_JAVA_SOURCES:%.java=%.class) LIBRARY_JAVA_CLASSES := $(subst com/artifex/mupdf/fitz/,com.artifex.mupdf.fitz.,$(LIBRARY_JAVA_SOURCES:%.java=%)) -VIEWER_JAVA_SOURCES := $(sort $(wildcard *.java)) -VIEWER_JAVA_OBJECTS := $(VIEWER_JAVA_SOURCES:%.java=%.class) +$(LIBRARY_JAVA_OBJECTS) : $(LIBRARY_JAVA_SOURCES) + javac -source 1.7 -target 1.7 $^ -$(MUPDF_CORE) : - $(MAKE) -C ../.. OUT=build/java XCFLAGS=-fPIC \ - FREETYPE_DIR=/use/system/library \ - ZLIB_DIR=/use/system/library \ - build=release libs +EXAMPLE_JAVA_SOURCES := $(sort $(wildcard example/*.java)) +EXAMPLE_JAVA_OBJECTS := $(EXAMPLE_JAVA_SOURCES:%.java=%.class) -$(LIBRARY_JAVA_OBJECTS) : $(LIBRARY_JAVA_SOURCES) - javac $^ +$(EXAMPLE_JAVA_OBJECTS) : $(EXAMPLE_JAVA_SOURCES) + javac -source 1.7 -target 1.7 $^ libmupdf.jar : $(LIBRARY_JAVA_OBJECTS) rm -f $@ @@ -27,30 +49,22 @@ mupdf_native.h : $(LIBRARY_JAVA_OBJECTS) javah -o $@ $(LIBRARY_JAVA_CLASSES) mupdf_native.o : mupdf_native.c mupdf_native.h - $(CC) -g -fPIC -Wall -Wextra -Wno-unused-parameter \ - -I /usr/lib/jvm/java-7-openjdk-i386/include \ - -I /usr/lib/jvm/java-7-openjdk-i386/include/linux \ - -I /usr/lib/jvm/java-7-openjdk-amd64/include \ - -I /usr/lib/jvm/java-7-openjdk-amd64/include/linux \ - -I ../../include \ - -o $@ -c $< - -libmupdf_java.so : mupdf_native.o $(MUPDF_CORE) - $(CC) -shared -o $@ $^ -lfreetype -lz + $(CC) -g -o $@ -c $< -fPIC -Wall -Wextra -Wno-unused-parameter -I ../../include \ + $(JAVA_CFLAGS) -$(VIEWER_JAVA_OBJECTS) : $(VIEWER_JAVA_SOURCES) - javac $^ +$(MUPDF_JAVA) : mupdf_native.o $(MUPDF_CORE) + $(CC) -shared -o $(MUPDF_JAVA) $^ $(JAVA_LIBS) -viewer: libmupdf_java.so $(LIBRARY_JAVA_OBJECTS) $(VIEWER_JAVA_OBJECTS) - LD_LIBRARY_PATH=. java Viewer +viewer: $(MUPDF_JAVA) $(EXAMPLE_JAVA_OBJECTS) + LD_LIBRARY_PATH=. java example.Viewer clean: rm -f com/artifex/mupdf/fitz/*.class - rm -f *.class + rm -f example/*.class rm -f mupdf_native.o - rm -f libmupdf_java.so + rm -f $(MUPDF_JAVA) nuke: clean - $(MAKE) -C ../.. build=release OUT=build/java clean + $(MAKE) -C ../.. OUT=build/java clean .NOTPARALLEL : # disable -j option (it breaks since javac compiles all class files in one command) diff --git a/platform/java/Makejar b/platform/java/Makejar index 7ab3e40f..3f98127a 100644 --- a/platform/java/Makejar +++ b/platform/java/Makejar @@ -1,3 +1,3 @@ default: - javac com/artifex/mupdf/fitz/*.java + javac -source 1.7 -target 1.7 com/artifex/mupdf/fitz/*.java jar cf libmupdf.jar com/artifex/mupdf/fitz/*.class diff --git a/platform/java/PageCanvas.java b/platform/java/PageCanvas.java deleted file mode 100644 index bf20afa8..00000000 --- a/platform/java/PageCanvas.java +++ /dev/null @@ -1,57 +0,0 @@ -import com.artifex.mupdf.fitz.*; -import java.awt.*; -import java.awt.image.*; - -public class PageCanvas extends java.awt.Canvas -{ - protected Page page; - protected BufferedImage image; - - public static BufferedImage imageFromPixmap(Pixmap pixmap) { - int w = pixmap.getWidth(); - int h = pixmap.getHeight(); - BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); - image.setRGB(0, 0, w, h, pixmap.getPixels(), 0, w); - return image; - } - - public static BufferedImage imageFromPageWithDevice(Page page, Matrix ctm) { - Rect bbox = page.getBounds(); - Pixmap pixmap = new Pixmap(ColorSpace.DeviceBGR, bbox); - pixmap.clear(255); - DrawDevice dev = new DrawDevice(pixmap); - page.run(dev, new Matrix()); - dev.destroy(); - BufferedImage image = imageFromPixmap(pixmap); - pixmap.destroy(); - return image; - } - - public static BufferedImage imageFromPage(Page page, Matrix ctm) { - Pixmap pixmap = page.toPixmap(ctm, ColorSpace.DeviceBGR); - BufferedImage image = imageFromPixmap(pixmap); - pixmap.destroy(); - return image; - } - - public PageCanvas(Page page_) { - this.page = page_; - image = imageFromPage(page, new Matrix()); - } - - public Dimension getPreferredSize() { - return new Dimension(image.getWidth(), image.getHeight()); - } - - public Dimension getMinimumSize() { - return getPreferredSize(); - } - - public Dimension getMaximumSize() { - return getPreferredSize(); - } - - public void paint(Graphics g) { - g.drawImage(image, 0, 0, null); - } -} diff --git a/platform/java/TraceDevice.java b/platform/java/TraceDevice.java deleted file mode 100644 index 36ed6cd3..00000000 --- a/platform/java/TraceDevice.java +++ /dev/null @@ -1,130 +0,0 @@ -import com.artifex.mupdf.fitz.*; - -public class TraceDevice extends Device implements PathWalker, TextWalker -{ - public String traceColor(ColorSpace cs, float color[], float alpha) { - String s = cs + " ["; - int i; - for (i = 0; i < color.length; ++i) { - if (i > 0) s += " "; - s += color[i]; - } - return s + "] " + alpha; - } - public String traceStroke(StrokeState stroke) { - return "c=" + stroke.getStartCap() + "," + stroke.getDashCap() + "," + stroke.getEndCap() + - " j=" + stroke.getLineJoin() + - " m=" + stroke.getMiterLimit() + - " l=" + stroke.getLineWidth(); - } - - public void moveTo(float x, float y) { - System.out.println("moveto " + x + " " + y); - } - public void lineTo(float x, float y) { - System.out.println("lineto " + x + " " + y); - } - public void curveTo(float cx1, float cy1, float cx2, float cy2, float ex, float ey) { - System.out.println("curveto " + cx1 + " " + cy1 + " " + cx2 + " " + cy2 + " " + ex + " " + ey); - } - public void closePath() { - System.out.println("closepath"); - } - - public void showGlyph(Font font, Matrix trm, int glyph, int unicode, boolean wmode) { - System.out.println("glyph '" + (char)unicode + "' " + glyph + "\t" + font + " " + trm); - } - - public void tracePath(Path path) { - path.walk(this); - } - public void traceText(Text text) { - text.walk(this); - } - - public void fillPath(Path path, boolean evenOdd, Matrix ctm, ColorSpace cs, float color[], float alpha) { - System.out.println("fillPath " + evenOdd + " " + ctm + " " + traceColor(cs, color, alpha)); - tracePath(path); - } - public void strokePath(Path path, StrokeState stroke, Matrix ctm, ColorSpace cs, float color[], float alpha) { - System.out.println("strokePath " + traceStroke(stroke) + " " + ctm + " " + traceColor(cs, color, alpha)); - tracePath(path); - } - public void clipPath(Path path, Rect rect, boolean evenOdd, Matrix ctm) { - System.out.println("clipPath " + evenOdd + " " + ctm); - tracePath(path); - } - public void clipStrokePath(Path path, Rect rect, StrokeState stroke, Matrix ctm) { - System.out.println("clipStrokePath " + traceStroke(stroke) + " " + ctm); - tracePath(path); - } - - public void fillText(Text text, Matrix ctm, ColorSpace cs, float color[], float alpha) { - System.out.println("fillText " + ctm + " " + traceColor(cs, color, alpha)); - traceText(text); - } - public void strokeText(Text text, StrokeState stroke, Matrix ctm, ColorSpace cs, float color[], float alpha) { - System.out.println("strokeText " + ctm + " " + traceStroke(stroke) + " " + traceColor(cs, color, alpha)); - traceText(text); - } - public void clipText(Text text, Matrix ctm) { - System.out.println("clipText " + ctm); - traceText(text); - } - public void clipStrokeText(Text text, StrokeState stroke, Matrix ctm) { - System.out.println("clipStrokeText " + ctm + " " + traceStroke(stroke)); - traceText(text); - } - public void ignoreText(Text text, Matrix ctm) { - System.out.println("ignoreText " + ctm); - traceText(text); - } - public void fillShade(Shade shade, Matrix ctm, float alpha) { - System.out.println("fillShade " + ctm + " " + alpha); - } - public void fillImage(Image img, Matrix ctm, float alpha) { - System.out.println("fillImage " + ctm + " " + alpha); - } - public void fillImageMask(Image img, Matrix ctm, ColorSpace cs, float color[], float alpha) { - System.out.println("fillImageMask " + ctm + " " + traceColor(cs, color, alpha)); - } - public void clipImageMask(Image img, Rect rect, Matrix ctm) { - System.out.println("clipImageMask " + ctm + " " + rect); - } - public void popClip() { - System.out.println("popClip"); - } - - public void beginMask(Rect rect, boolean luminosity, ColorSpace cs, float bc[]) { - System.out.println("beginMask r=" + rect + - " l=" + luminosity + - " " + traceColor(cs, bc, 1)); - } - public void endMask() { - System.out.println("endMask"); - } - public void beginGroup(Rect rect, boolean isolated, boolean knockout, int blendmode, float alpha) { - System.out.println("beginGroup r=" + rect + - " i=" + isolated + - " k=" + knockout + - " bm=" + blendmode + - " a=" + alpha); - } - public void endGroup() { - System.out.println("endGroup"); - } - public int beginTile(Rect area, Rect view, float xstep, float ystep, Matrix ctm, int id) { - System.out.println("beginTile"); - return 0; - } - public void endTile() { - System.out.println("endTile"); - } - - public static void main(String[] args) { - Document doc = new Document("pdfref17.pdf"); - Page page = doc.loadPage(1144); - TraceDevice dev = new TraceDevice(); - page.run(dev, new Matrix()); - } -} diff --git a/platform/java/Viewer.java b/platform/java/Viewer.java deleted file mode 100644 index a118e3d4..00000000 --- a/platform/java/Viewer.java +++ /dev/null @@ -1,108 +0,0 @@ -import com.artifex.mupdf.fitz.*; - -import java.awt.Frame; -import java.awt.Label; -import java.awt.Button; -import java.awt.Panel; -import java.awt.BorderLayout; -import java.awt.FlowLayout; -import java.awt.event.ActionListener; -import java.awt.event.WindowListener; -import java.awt.event.WindowEvent; -import java.awt.event.ActionEvent; - -public class Viewer extends Frame implements WindowListener, ActionListener -{ - protected Document doc; - protected Panel toolbar; - protected PageCanvas pageCanvas; - protected Label pageLabel; - protected Button firstButton, prevButton, nextButton, lastButton; - protected int pageCount; - protected int pageNumber; - - public Viewer(Document doc_) { - super("MuPDF"); - - this.doc = doc_; - - pageCount = doc.countPages(); - pageNumber = 0; - - setSize(600, 900); - setTitle("MuPDF: " + doc.getMetaData(Document.META_INFO_TITLE)); - - toolbar = new Panel(); - toolbar.setLayout(new FlowLayout(FlowLayout.LEFT)); - firstButton = new Button("|<"); - firstButton.addActionListener(this); - prevButton = new Button("<"); - prevButton.addActionListener(this); - nextButton = new Button(">"); - nextButton.addActionListener(this); - lastButton = new Button(">|"); - lastButton.addActionListener(this); - pageLabel = new Label(); - - toolbar.add(firstButton); - toolbar.add(prevButton); - toolbar.add(nextButton); - toolbar.add(lastButton); - toolbar.add(pageLabel); - - add(toolbar, BorderLayout.NORTH); - - addWindowListener(this); - - stuff(); - } - - public void stuff() { - pageLabel.setText("Page " + (pageNumber + 1) + " / " + pageCount); - if (pageCanvas != null) - remove(pageCanvas); - pageCanvas = new PageCanvas(doc.loadPage(pageNumber)); - add(pageCanvas, BorderLayout.CENTER); - validate(); - } - - public void actionPerformed(ActionEvent event) { - Object source = event.getSource(); - int oldPageNumber = pageNumber; - - if (source == firstButton) - pageNumber = 0; - if (source == lastButton) - pageNumber = pageCount - 1; - if (source == prevButton) { - pageNumber = pageNumber - 1; - if (pageNumber < 0) - pageNumber = 0; - } - if (source == nextButton) { - pageNumber = pageNumber + 1; - if (pageNumber >= pageCount) - pageNumber = pageCount - 1; - } - - if (pageNumber != oldPageNumber) - stuff(); - } - - public void windowClosing(WindowEvent event) { - System.exit(0); - } - - public void windowActivated(WindowEvent event) { } - public void windowDeactivated(WindowEvent event) { } - public void windowIconified(WindowEvent event) { } - public void windowDeiconified(WindowEvent event) { } - public void windowOpened(WindowEvent event) { } - public void windowClosed(WindowEvent event) { } - - public static void main(String[] args) { - Document doc = new Document("pdfref17.pdf"); - Viewer app = new Viewer(doc); - app.setVisible(true); - } -} diff --git a/platform/java/example/PageCanvas.java b/platform/java/example/PageCanvas.java new file mode 100644 index 00000000..9de1abdb --- /dev/null +++ b/platform/java/example/PageCanvas.java @@ -0,0 +1,59 @@ +package example; + +import com.artifex.mupdf.fitz.*; +import java.awt.*; +import java.awt.image.*; + +public class PageCanvas extends java.awt.Canvas +{ + protected Page page; + protected BufferedImage image; + + public static BufferedImage imageFromPixmap(Pixmap pixmap) { + int w = pixmap.getWidth(); + int h = pixmap.getHeight(); + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + image.setRGB(0, 0, w, h, pixmap.getPixels(), 0, w); + return image; + } + + public static BufferedImage imageFromPageWithDevice(Page page, Matrix ctm) { + Rect bbox = page.getBounds(); + Pixmap pixmap = new Pixmap(ColorSpace.DeviceBGR, bbox); + pixmap.clear(255); + DrawDevice dev = new DrawDevice(pixmap); + page.run(dev, new Matrix()); + dev.destroy(); + BufferedImage image = imageFromPixmap(pixmap); + pixmap.destroy(); + return image; + } + + public static BufferedImage imageFromPage(Page page, Matrix ctm) { + Pixmap pixmap = page.toPixmap(ctm, ColorSpace.DeviceBGR); + BufferedImage image = imageFromPixmap(pixmap); + pixmap.destroy(); + return image; + } + + public PageCanvas(Page page_) { + this.page = page_; + image = imageFromPage(page, new Matrix()); + } + + public Dimension getPreferredSize() { + return new Dimension(image.getWidth(), image.getHeight()); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + public Dimension getMaximumSize() { + return getPreferredSize(); + } + + public void paint(Graphics g) { + g.drawImage(image, 0, 0, null); + } +} diff --git a/platform/java/example/TraceDevice.java b/platform/java/example/TraceDevice.java new file mode 100644 index 00000000..49c97532 --- /dev/null +++ b/platform/java/example/TraceDevice.java @@ -0,0 +1,132 @@ +package example; + +import com.artifex.mupdf.fitz.*; + +public class TraceDevice extends Device implements PathWalker, TextWalker +{ + public String traceColor(ColorSpace cs, float color[], float alpha) { + String s = cs + " ["; + int i; + for (i = 0; i < color.length; ++i) { + if (i > 0) s += " "; + s += color[i]; + } + return s + "] " + alpha; + } + public String traceStroke(StrokeState stroke) { + return "c=" + stroke.getStartCap() + "," + stroke.getDashCap() + "," + stroke.getEndCap() + + " j=" + stroke.getLineJoin() + + " m=" + stroke.getMiterLimit() + + " l=" + stroke.getLineWidth(); + } + + public void moveTo(float x, float y) { + System.out.println("moveto " + x + " " + y); + } + public void lineTo(float x, float y) { + System.out.println("lineto " + x + " " + y); + } + public void curveTo(float cx1, float cy1, float cx2, float cy2, float ex, float ey) { + System.out.println("curveto " + cx1 + " " + cy1 + " " + cx2 + " " + cy2 + " " + ex + " " + ey); + } + public void closePath() { + System.out.println("closepath"); + } + + public void showGlyph(Font font, Matrix trm, int glyph, int unicode, boolean wmode) { + System.out.println("glyph '" + (char)unicode + "' " + glyph + "\t" + font + " " + trm); + } + + public void tracePath(Path path) { + path.walk(this); + } + public void traceText(Text text) { + text.walk(this); + } + + public void fillPath(Path path, boolean evenOdd, Matrix ctm, ColorSpace cs, float color[], float alpha) { + System.out.println("fillPath " + evenOdd + " " + ctm + " " + traceColor(cs, color, alpha)); + tracePath(path); + } + public void strokePath(Path path, StrokeState stroke, Matrix ctm, ColorSpace cs, float color[], float alpha) { + System.out.println("strokePath " + traceStroke(stroke) + " " + ctm + " " + traceColor(cs, color, alpha)); + tracePath(path); + } + public void clipPath(Path path, Rect rect, boolean evenOdd, Matrix ctm) { + System.out.println("clipPath " + evenOdd + " " + ctm); + tracePath(path); + } + public void clipStrokePath(Path path, Rect rect, StrokeState stroke, Matrix ctm) { + System.out.println("clipStrokePath " + traceStroke(stroke) + " " + ctm); + tracePath(path); + } + + public void fillText(Text text, Matrix ctm, ColorSpace cs, float color[], float alpha) { + System.out.println("fillText " + ctm + " " + traceColor(cs, color, alpha)); + traceText(text); + } + public void strokeText(Text text, StrokeState stroke, Matrix ctm, ColorSpace cs, float color[], float alpha) { + System.out.println("strokeText " + ctm + " " + traceStroke(stroke) + " " + traceColor(cs, color, alpha)); + traceText(text); + } + public void clipText(Text text, Matrix ctm) { + System.out.println("clipText " + ctm); + traceText(text); + } + public void clipStrokeText(Text text, StrokeState stroke, Matrix ctm) { + System.out.println("clipStrokeText " + ctm + " " + traceStroke(stroke)); + traceText(text); + } + public void ignoreText(Text text, Matrix ctm) { + System.out.println("ignoreText " + ctm); + traceText(text); + } + public void fillShade(Shade shade, Matrix ctm, float alpha) { + System.out.println("fillShade " + ctm + " " + alpha); + } + public void fillImage(Image img, Matrix ctm, float alpha) { + System.out.println("fillImage " + ctm + " " + alpha); + } + public void fillImageMask(Image img, Matrix ctm, ColorSpace cs, float color[], float alpha) { + System.out.println("fillImageMask " + ctm + " " + traceColor(cs, color, alpha)); + } + public void clipImageMask(Image img, Rect rect, Matrix ctm) { + System.out.println("clipImageMask " + ctm + " " + rect); + } + public void popClip() { + System.out.println("popClip"); + } + + public void beginMask(Rect rect, boolean luminosity, ColorSpace cs, float bc[]) { + System.out.println("beginMask r=" + rect + + " l=" + luminosity + + " " + traceColor(cs, bc, 1)); + } + public void endMask() { + System.out.println("endMask"); + } + public void beginGroup(Rect rect, boolean isolated, boolean knockout, int blendmode, float alpha) { + System.out.println("beginGroup r=" + rect + + " i=" + isolated + + " k=" + knockout + + " bm=" + blendmode + + " a=" + alpha); + } + public void endGroup() { + System.out.println("endGroup"); + } + public int beginTile(Rect area, Rect view, float xstep, float ystep, Matrix ctm, int id) { + System.out.println("beginTile"); + return 0; + } + public void endTile() { + System.out.println("endTile"); + } + + public static void main(String[] args) { + Document doc = new Document("pdfref17.pdf"); + Page page = doc.loadPage(1144); + TraceDevice dev = new TraceDevice(); + page.run(dev, new Matrix()); + } +} diff --git a/platform/java/example/Viewer.java b/platform/java/example/Viewer.java new file mode 100644 index 00000000..91b0fa1d --- /dev/null +++ b/platform/java/example/Viewer.java @@ -0,0 +1,203 @@ +package example; + +import com.artifex.mupdf.fitz.*; + +import java.io.File; + +import java.awt.Frame; +import java.awt.Label; +import java.awt.Button; +import java.awt.Panel; +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionListener; +import java.awt.event.WindowListener; +import java.awt.event.WindowEvent; +import java.awt.event.ActionEvent; + +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; +import javax.swing.JOptionPane; + +public class Viewer extends Frame implements WindowListener, ActionListener +{ + protected Document doc; + protected Panel toolbar; + protected PageCanvas pageCanvas; + protected Label pageLabel; + protected Button firstButton, prevButton, nextButton, lastButton; + protected int pageCount; + protected int pageNumber; + + public Viewer(Document doc_) { + super("MuPDF"); + + this.doc = doc_; + + pageCount = doc.countPages(); + pageNumber = 0; + + setSize(600, 900); + setTitle("MuPDF: " + doc.getMetaData(Document.META_INFO_TITLE)); + + toolbar = new Panel(); + toolbar.setLayout(new FlowLayout(FlowLayout.LEFT)); + firstButton = new Button("|<"); + firstButton.addActionListener(this); + prevButton = new Button("<"); + prevButton.addActionListener(this); + nextButton = new Button(">"); + nextButton.addActionListener(this); + lastButton = new Button(">|"); + lastButton.addActionListener(this); + pageLabel = new Label(); + + toolbar.add(firstButton); + toolbar.add(prevButton); + toolbar.add(nextButton); + toolbar.add(lastButton); + toolbar.add(pageLabel); + + add(toolbar, BorderLayout.NORTH); + + addWindowListener(this); + + stuff(); + } + + public void stuff() { + pageLabel.setText("Page " + (pageNumber + 1) + " / " + pageCount); + if (pageCanvas != null) + remove(pageCanvas); + pageCanvas = new PageCanvas(doc.loadPage(pageNumber)); + add(pageCanvas, BorderLayout.CENTER); + validate(); + } + + public void actionPerformed(ActionEvent event) { + Object source = event.getSource(); + int oldPageNumber = pageNumber; + + if (source == firstButton) + pageNumber = 0; + if (source == lastButton) + pageNumber = pageCount - 1; + if (source == prevButton) { + pageNumber = pageNumber - 1; + if (pageNumber < 0) + pageNumber = 0; + } + if (source == nextButton) { + pageNumber = pageNumber + 1; + if (pageNumber >= pageCount) + pageNumber = pageCount - 1; + } + + if (pageNumber != oldPageNumber) + stuff(); + } + + public void windowClosing(WindowEvent event) { + System.exit(0); + } + + public void windowActivated(WindowEvent event) { } + public void windowDeactivated(WindowEvent event) { } + public void windowIconified(WindowEvent event) { } + public void windowDeiconified(WindowEvent event) { } + public void windowOpened(WindowEvent event) { } + public void windowClosed(WindowEvent event) { } + + public static void main(String[] args) + { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Choose a file to open"); + fileChooser.setFileFilter(new FileFilter() + { + public String getDescription() + { + return "Supported files (*.pdf, *,xps, *.jpg, *.jpeg, *.png, *.epub, *.cbz, *.cbr)"; + } + + public boolean accept(File f) + { + if (f.isDirectory()) + return true; + + String filename = f.getName().toLowerCase(); + if (filename.endsWith(".pdf")) + return true; + if (filename.endsWith(".xps")) + return true; + if (filename.endsWith(".jpg")) + return true; + if (filename.endsWith(".jpeg")) + return true; + if (filename.endsWith(".png")) + return true; + if (filename.endsWith(".epub")) + return true; + if (filename.endsWith(".cbz")) + return true; + if (filename.endsWith(".cbr")) + return true; + + return false; + } + }); + + while (true) + { + try + { + // get a file to open + int result = fileChooser.showOpenDialog(null); + if (result == JFileChooser.APPROVE_OPTION) + { + // user selects a file + File selectedFile = fileChooser.getSelectedFile(); + if (selectedFile != null) + { + Document doc = new Document(selectedFile.getAbsolutePath()); + if (doc != null) + { + Viewer app = new Viewer(doc); + if (app != null) + { + app.setVisible(true); + return; + } + else + { + infoBox("Cannot create Viewer for "+selectedFile.getAbsolutePath(),"Error"); + } + } + else + { + infoBox("Cannot open "+selectedFile.getAbsolutePath(),"Error"); + } + } + else + { + infoBox("Selected file not found.","Error"); + } + } + else + { + infoBox("File selection cancelled.","Error"); + return; + } + + } + catch (Exception e) + { + infoBox("Exception: "+e.getMessage(),"Error"); + } + } + } + + private static void infoBox(String infoMessage, String titleBar) + { + JOptionPane.showMessageDialog(null, infoMessage, "InfoBox: " + titleBar, JOptionPane.INFORMATION_MESSAGE); + } +} diff --git a/platform/java/mupdf_native.c b/platform/java/mupdf_native.c index 38bd077b..55cb428f 100644 --- a/platform/java/mupdf_native.c +++ b/platform/java/mupdf_native.c @@ -2191,8 +2191,8 @@ FUN(Font_newNative)(JNIEnv *env, jobject self, jstring jname, jint index) fz_try(ctx) { - unsigned char *data; - unsigned int size; + const char *data; + int size; data = fz_lookup_base14_font(ctx, name, &size); if (data) font = fz_new_font_from_memory(ctx, name, data, size, index, 0); -- cgit v1.2.3