summaryrefslogtreecommitdiff
path: root/platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java')
-rw-r--r--platform/android/viewer/src/com/artifex/mupdfdemo/MuPDFPageView.java692
1 files changed, 692 insertions, 0 deletions
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<Void,Void,PassClickResult> mPassClick;
+ private RectF mWidgetAreas[];
+ private Annotation mAnnotations[];
+ private int mSelectedAnnotationIndex = -1;
+ private AsyncTask<Void,Void,RectF[]> mLoadWidgetAreas;
+ private AsyncTask<Void,Void,Annotation[]> 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<String,Void,Boolean> mSetWidgetText;
+ private AsyncTask<String,Void,Void> mSetWidgetChoice;
+ private AsyncTask<PointF[],Void,Void> mAddStrikeOut;
+ private AsyncTask<PointF[][],Void,Void> mAddInk;
+ private AsyncTask<Integer,Void,Void> mDeleteAnnotation;
+ private AsyncTask<Void,Void,String> mCheckSignature;
+ private AsyncTask<Void,Void,Boolean> 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<String,Void,Boolean> () {
+ @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<Void,Void,Boolean>() {
+ @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<String,Void,Void>() {
+ @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<Void,Void,String> () {
+ @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<Void,Void,PassClickResult>() {
+ @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<PointF> quadPoints = new ArrayList<PointF>();
+ 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<PointF[],Void,Void>() {
+ @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<Integer,Void,Void>() {
+ @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<PointF[][],Void,Void>() {
+ @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<Void, Void> 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<Void, Void>(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<Void, Void> 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<Void, Void>(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<Void,Void,Annotation[]> () {
+ @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<Void,Void,RectF[]> () {
+ @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();
+ }
+}