Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
7859 views
1
package com.artifex.mupdfdemo;
2
3
import java.util.ArrayList;
4
5
import com.artifex.mupdfdemo.MuPDFCore.Cookie;
6
7
import android.annotation.TargetApi;
8
import android.app.AlertDialog;
9
import android.content.ClipData;
10
import android.content.Context;
11
import android.content.DialogInterface;
12
import android.graphics.Bitmap;
13
import android.graphics.Point;
14
import android.graphics.PointF;
15
import android.graphics.RectF;
16
import android.net.Uri;
17
import android.os.Build;
18
import android.text.method.PasswordTransformationMethod;
19
import android.view.LayoutInflater;
20
import android.view.WindowManager;
21
import android.view.inputmethod.EditorInfo;
22
import android.widget.EditText;
23
24
/* This enum should be kept in line with the cooresponding C enum in mupdf.c */
25
enum SignatureState {
26
NoSupport,
27
Unsigned,
28
Signed
29
}
30
31
abstract class PassClickResultVisitor {
32
public abstract void visitText(PassClickResultText result);
33
public abstract void visitChoice(PassClickResultChoice result);
34
public abstract void visitSignature(PassClickResultSignature result);
35
}
36
37
class PassClickResult {
38
public final boolean changed;
39
40
public PassClickResult(boolean _changed) {
41
changed = _changed;
42
}
43
44
public void acceptVisitor(PassClickResultVisitor visitor) {
45
}
46
}
47
48
class PassClickResultText extends PassClickResult {
49
public final String text;
50
51
public PassClickResultText(boolean _changed, String _text) {
52
super(_changed);
53
text = _text;
54
}
55
56
public void acceptVisitor(PassClickResultVisitor visitor) {
57
visitor.visitText(this);
58
}
59
}
60
61
class PassClickResultChoice extends PassClickResult {
62
public final String [] options;
63
public final String [] selected;
64
65
public PassClickResultChoice(boolean _changed, String [] _options, String [] _selected) {
66
super(_changed);
67
options = _options;
68
selected = _selected;
69
}
70
71
public void acceptVisitor(PassClickResultVisitor visitor) {
72
visitor.visitChoice(this);
73
}
74
}
75
76
class PassClickResultSignature extends PassClickResult {
77
public final SignatureState state;
78
79
public PassClickResultSignature(boolean _changed, int _state) {
80
super(_changed);
81
state = SignatureState.values()[_state];
82
}
83
84
public void acceptVisitor(PassClickResultVisitor visitor) {
85
visitor.visitSignature(this);
86
}
87
}
88
89
public class MuPDFPageView extends PageView implements MuPDFView {
90
final private FilePicker.FilePickerSupport mFilePickerSupport;
91
private final MuPDFCore mCore;
92
private AsyncTask<Void,Void,PassClickResult> mPassClick;
93
private RectF mWidgetAreas[];
94
private Annotation mAnnotations[];
95
private int mSelectedAnnotationIndex = -1;
96
private AsyncTask<Void,Void,RectF[]> mLoadWidgetAreas;
97
private AsyncTask<Void,Void,Annotation[]> mLoadAnnotations;
98
private AlertDialog.Builder mTextEntryBuilder;
99
private AlertDialog.Builder mChoiceEntryBuilder;
100
private AlertDialog.Builder mSigningDialogBuilder;
101
private AlertDialog.Builder mSignatureReportBuilder;
102
private AlertDialog.Builder mPasswordEntryBuilder;
103
private EditText mPasswordText;
104
private AlertDialog mTextEntry;
105
private AlertDialog mPasswordEntry;
106
private EditText mEditText;
107
private AsyncTask<String,Void,Boolean> mSetWidgetText;
108
private AsyncTask<String,Void,Void> mSetWidgetChoice;
109
private AsyncTask<PointF[],Void,Void> mAddStrikeOut;
110
private AsyncTask<PointF[][],Void,Void> mAddInk;
111
private AsyncTask<Integer,Void,Void> mDeleteAnnotation;
112
private AsyncTask<Void,Void,String> mCheckSignature;
113
private AsyncTask<Void,Void,Boolean> mSign;
114
private Runnable changeReporter;
115
116
public MuPDFPageView(Context c, FilePicker.FilePickerSupport filePickerSupport, MuPDFCore core, Point parentSize, Bitmap sharedHqBm) {
117
super(c, parentSize, sharedHqBm);
118
mFilePickerSupport = filePickerSupport;
119
mCore = core;
120
mTextEntryBuilder = new AlertDialog.Builder(c);
121
mTextEntryBuilder.setTitle(getContext().getString(R.string.fill_out_text_field));
122
LayoutInflater inflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
123
mEditText = (EditText)inflater.inflate(R.layout.textentry, null);
124
mTextEntryBuilder.setView(mEditText);
125
mTextEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
126
public void onClick(DialogInterface dialog, int which) {
127
dialog.dismiss();
128
}
129
});
130
mTextEntryBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
131
public void onClick(DialogInterface dialog, int which) {
132
mSetWidgetText = new AsyncTask<String,Void,Boolean> () {
133
@Override
134
protected Boolean doInBackground(String... arg0) {
135
return mCore.setFocusedWidgetText(mPageNumber, arg0[0]);
136
}
137
@Override
138
protected void onPostExecute(Boolean result) {
139
changeReporter.run();
140
if (!result)
141
invokeTextDialog(mEditText.getText().toString());
142
}
143
};
144
145
mSetWidgetText.execute(mEditText.getText().toString());
146
}
147
});
148
mTextEntry = mTextEntryBuilder.create();
149
150
mChoiceEntryBuilder = new AlertDialog.Builder(c);
151
mChoiceEntryBuilder.setTitle(getContext().getString(R.string.choose_value));
152
153
mSigningDialogBuilder = new AlertDialog.Builder(c);
154
mSigningDialogBuilder.setTitle("Select certificate and sign?");
155
mSigningDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
156
@Override
157
public void onClick(DialogInterface dialog, int which) {
158
dialog.dismiss();
159
}
160
});
161
mSigningDialogBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
162
@Override
163
public void onClick(DialogInterface dialog, int which) {
164
FilePicker picker = new FilePicker(mFilePickerSupport) {
165
@Override
166
void onPick(Uri uri) {
167
signWithKeyFile(uri);
168
}
169
};
170
171
picker.pick();
172
}
173
});
174
175
mSignatureReportBuilder = new AlertDialog.Builder(c);
176
mSignatureReportBuilder.setTitle("Signature checked");
177
mSignatureReportBuilder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
178
@Override
179
public void onClick(DialogInterface dialog, int which) {
180
dialog.dismiss();
181
}
182
});
183
184
mPasswordText = new EditText(c);
185
mPasswordText.setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
186
mPasswordText.setTransformationMethod(new PasswordTransformationMethod());
187
188
mPasswordEntryBuilder = new AlertDialog.Builder(c);
189
mPasswordEntryBuilder.setTitle(R.string.enter_password);
190
mPasswordEntryBuilder.setView(mPasswordText);
191
mPasswordEntryBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
192
@Override
193
public void onClick(DialogInterface dialog, int which) {
194
dialog.dismiss();
195
}
196
});
197
198
mPasswordEntry = mPasswordEntryBuilder.create();
199
}
200
201
private void signWithKeyFile(final Uri uri) {
202
mPasswordEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
203
mPasswordEntry.setButton(AlertDialog.BUTTON_POSITIVE, "Sign", new DialogInterface.OnClickListener() {
204
@Override
205
public void onClick(DialogInterface dialog, int which) {
206
dialog.dismiss();
207
signWithKeyFileAndPassword(uri, mPasswordText.getText().toString());
208
}
209
});
210
211
mPasswordEntry.show();
212
}
213
214
private void signWithKeyFileAndPassword(final Uri uri, final String password) {
215
mSign = new AsyncTask<Void,Void,Boolean>() {
216
@Override
217
protected Boolean doInBackground(Void... params) {
218
return mCore.signFocusedSignature(Uri.decode(uri.getEncodedPath()), password);
219
}
220
@Override
221
protected void onPostExecute(Boolean result) {
222
if (result)
223
{
224
changeReporter.run();
225
}
226
else
227
{
228
mPasswordText.setText("");
229
signWithKeyFile(uri);
230
}
231
}
232
233
};
234
235
mSign.execute();
236
}
237
238
public LinkInfo hitLink(float x, float y) {
239
// Since link highlighting was implemented, the super class
240
// PageView has had sufficient information to be able to
241
// perform this method directly. Making that change would
242
// make MuPDFCore.hitLinkPage superfluous.
243
float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
244
float docRelX = (x - getLeft())/scale;
245
float docRelY = (y - getTop())/scale;
246
247
for (LinkInfo l: mLinks)
248
if (l.rect.contains(docRelX, docRelY))
249
return l;
250
251
return null;
252
}
253
254
private void invokeTextDialog(String text) {
255
mEditText.setText(text);
256
mTextEntry.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
257
mTextEntry.show();
258
}
259
260
private void invokeChoiceDialog(final String [] options) {
261
mChoiceEntryBuilder.setItems(options, new DialogInterface.OnClickListener() {
262
public void onClick(DialogInterface dialog, int which) {
263
mSetWidgetChoice = new AsyncTask<String,Void,Void>() {
264
@Override
265
protected Void doInBackground(String... params) {
266
String [] sel = {params[0]};
267
mCore.setFocusedWidgetChoiceSelected(sel);
268
return null;
269
}
270
271
@Override
272
protected void onPostExecute(Void result) {
273
changeReporter.run();
274
}
275
};
276
277
mSetWidgetChoice.execute(options[which]);
278
}
279
});
280
AlertDialog dialog = mChoiceEntryBuilder.create();
281
dialog.show();
282
}
283
284
private void invokeSignatureCheckingDialog() {
285
mCheckSignature = new AsyncTask<Void,Void,String> () {
286
@Override
287
protected String doInBackground(Void... params) {
288
return mCore.checkFocusedSignature();
289
}
290
@Override
291
protected void onPostExecute(String result) {
292
AlertDialog report = mSignatureReportBuilder.create();
293
report.setMessage(result);
294
report.show();
295
}
296
};
297
298
mCheckSignature.execute();
299
}
300
301
private void invokeSigningDialog() {
302
AlertDialog dialog = mSigningDialogBuilder.create();
303
dialog.show();
304
}
305
306
private void warnNoSignatureSupport() {
307
AlertDialog dialog = mSignatureReportBuilder.create();
308
dialog.setTitle("App built with no signature support");
309
dialog.show();
310
}
311
312
public void setChangeReporter(Runnable reporter) {
313
changeReporter = reporter;
314
}
315
316
public Hit passClickEvent(float x, float y) {
317
float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
318
final float docRelX = (x - getLeft())/scale;
319
final float docRelY = (y - getTop())/scale;
320
boolean hit = false;
321
int i;
322
323
if (mAnnotations != null) {
324
for (i = 0; i < mAnnotations.length; i++)
325
if (mAnnotations[i].contains(docRelX, docRelY)) {
326
hit = true;
327
break;
328
}
329
330
if (hit) {
331
switch (mAnnotations[i].type) {
332
case HIGHLIGHT:
333
case UNDERLINE:
334
case SQUIGGLY:
335
case STRIKEOUT:
336
case INK:
337
mSelectedAnnotationIndex = i;
338
setItemSelectBox(mAnnotations[i]);
339
return Hit.Annotation;
340
}
341
}
342
}
343
344
mSelectedAnnotationIndex = -1;
345
setItemSelectBox(null);
346
347
if (!mCore.javascriptSupported())
348
return Hit.Nothing;
349
350
if (mWidgetAreas != null) {
351
for (i = 0; i < mWidgetAreas.length && !hit; i++)
352
if (mWidgetAreas[i].contains(docRelX, docRelY))
353
hit = true;
354
}
355
356
if (hit) {
357
mPassClick = new AsyncTask<Void,Void,PassClickResult>() {
358
@Override
359
protected PassClickResult doInBackground(Void... arg0) {
360
return mCore.passClickEvent(mPageNumber, docRelX, docRelY);
361
}
362
363
@Override
364
protected void onPostExecute(PassClickResult result) {
365
if (result.changed) {
366
changeReporter.run();
367
}
368
369
result.acceptVisitor(new PassClickResultVisitor() {
370
@Override
371
public void visitText(PassClickResultText result) {
372
invokeTextDialog(result.text);
373
}
374
375
@Override
376
public void visitChoice(PassClickResultChoice result) {
377
invokeChoiceDialog(result.options);
378
}
379
380
@Override
381
public void visitSignature(PassClickResultSignature result) {
382
switch (result.state) {
383
case NoSupport:
384
warnNoSignatureSupport();
385
break;
386
case Unsigned:
387
invokeSigningDialog();
388
break;
389
case Signed:
390
invokeSignatureCheckingDialog();
391
break;
392
}
393
}
394
});
395
}
396
};
397
398
mPassClick.execute();
399
return Hit.Widget;
400
}
401
402
return Hit.Nothing;
403
}
404
405
@TargetApi(11)
406
public boolean copySelection() {
407
final StringBuilder text = new StringBuilder();
408
409
processSelectedText(new TextProcessor() {
410
StringBuilder line;
411
412
public void onStartLine() {
413
line = new StringBuilder();
414
}
415
416
public void onWord(TextWord word) {
417
if (line.length() > 0)
418
line.append(' ');
419
line.append(word.w);
420
}
421
422
public void onEndLine() {
423
if (text.length() > 0)
424
text.append('\n');
425
text.append(line);
426
}
427
});
428
429
if (text.length() == 0)
430
return false;
431
432
int currentApiVersion = android.os.Build.VERSION.SDK_INT;
433
if (currentApiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) {
434
android.content.ClipboardManager cm = (android.content.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE);
435
436
cm.setPrimaryClip(ClipData.newPlainText("MuPDF", text));
437
} else {
438
android.text.ClipboardManager cm = (android.text.ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE);
439
cm.setText(text);
440
}
441
442
deselectText();
443
444
return true;
445
}
446
447
public boolean markupSelection(final Annotation.Type type) {
448
final ArrayList<PointF> quadPoints = new ArrayList<PointF>();
449
processSelectedText(new TextProcessor() {
450
RectF rect;
451
452
public void onStartLine() {
453
rect = new RectF();
454
}
455
456
public void onWord(TextWord word) {
457
rect.union(word);
458
}
459
460
public void onEndLine() {
461
if (!rect.isEmpty()) {
462
quadPoints.add(new PointF(rect.left, rect.bottom));
463
quadPoints.add(new PointF(rect.right, rect.bottom));
464
quadPoints.add(new PointF(rect.right, rect.top));
465
quadPoints.add(new PointF(rect.left, rect.top));
466
}
467
}
468
});
469
470
if (quadPoints.size() == 0)
471
return false;
472
473
mAddStrikeOut = new AsyncTask<PointF[],Void,Void>() {
474
@Override
475
protected Void doInBackground(PointF[]... params) {
476
addMarkup(params[0], type);
477
return null;
478
}
479
480
@Override
481
protected void onPostExecute(Void result) {
482
loadAnnotations();
483
update();
484
}
485
};
486
487
mAddStrikeOut.execute(quadPoints.toArray(new PointF[quadPoints.size()]));
488
489
deselectText();
490
491
return true;
492
}
493
494
public void deleteSelectedAnnotation() {
495
if (mSelectedAnnotationIndex != -1) {
496
if (mDeleteAnnotation != null)
497
mDeleteAnnotation.cancel(true);
498
499
mDeleteAnnotation = new AsyncTask<Integer,Void,Void>() {
500
@Override
501
protected Void doInBackground(Integer... params) {
502
mCore.deleteAnnotation(mPageNumber, params[0]);
503
return null;
504
}
505
506
@Override
507
protected void onPostExecute(Void result) {
508
loadAnnotations();
509
update();
510
}
511
};
512
513
mDeleteAnnotation.execute(mSelectedAnnotationIndex);
514
515
mSelectedAnnotationIndex = -1;
516
setItemSelectBox(null);
517
}
518
}
519
520
public void deselectAnnotation() {
521
mSelectedAnnotationIndex = -1;
522
setItemSelectBox(null);
523
}
524
525
public boolean saveDraw() {
526
PointF[][] path = getDraw();
527
528
if (path == null)
529
return false;
530
531
if (mAddInk != null) {
532
mAddInk.cancel(true);
533
mAddInk = null;
534
}
535
mAddInk = new AsyncTask<PointF[][],Void,Void>() {
536
@Override
537
protected Void doInBackground(PointF[][]... params) {
538
mCore.addInkAnnotation(mPageNumber, params[0]);
539
return null;
540
}
541
542
@Override
543
protected void onPostExecute(Void result) {
544
loadAnnotations();
545
update();
546
}
547
548
};
549
550
mAddInk.execute(getDraw());
551
cancelDraw();
552
553
return true;
554
}
555
556
557
@Override
558
protected CancellableTaskDefinition<Void, Void> getDrawPageTask(final Bitmap bm, final int sizeX, final int sizeY,
559
final int patchX, final int patchY, final int patchWidth, final int patchHeight) {
560
return new MuPDFCancellableTaskDefinition<Void, Void>(mCore) {
561
@Override
562
public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) {
563
// Workaround bug in Android Honeycomb 3.x, where the bitmap generation count
564
// is not incremented when drawing.
565
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB &&
566
Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
567
bm.eraseColor(0);
568
mCore.drawPage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie);
569
return null;
570
}
571
};
572
573
}
574
575
protected CancellableTaskDefinition<Void, Void> getUpdatePageTask(final Bitmap bm, final int sizeX, final int sizeY,
576
final int patchX, final int patchY, final int patchWidth, final int patchHeight)
577
{
578
return new MuPDFCancellableTaskDefinition<Void, Void>(mCore) {
579
580
@Override
581
public Void doInBackground(MuPDFCore.Cookie cookie, Void ... params) {
582
// Workaround bug in Android Honeycomb 3.x, where the bitmap generation count
583
// is not incremented when drawing.
584
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB &&
585
Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
586
bm.eraseColor(0);
587
mCore.updatePage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie);
588
return null;
589
}
590
};
591
}
592
593
@Override
594
protected LinkInfo[] getLinkInfo() {
595
return mCore.getPageLinks(mPageNumber);
596
}
597
598
@Override
599
protected TextWord[][] getText() {
600
return mCore.textLines(mPageNumber);
601
}
602
603
@Override
604
protected void addMarkup(PointF[] quadPoints, Annotation.Type type) {
605
mCore.addMarkupAnnotation(mPageNumber, quadPoints, type);
606
}
607
608
private void loadAnnotations() {
609
mAnnotations = null;
610
if (mLoadAnnotations != null)
611
mLoadAnnotations.cancel(true);
612
mLoadAnnotations = new AsyncTask<Void,Void,Annotation[]> () {
613
@Override
614
protected Annotation[] doInBackground(Void... params) {
615
return mCore.getAnnoations(mPageNumber);
616
}
617
618
@Override
619
protected void onPostExecute(Annotation[] result) {
620
mAnnotations = result;
621
}
622
};
623
624
mLoadAnnotations.execute();
625
}
626
627
@Override
628
public void setPage(final int page, PointF size) {
629
loadAnnotations();
630
631
mLoadWidgetAreas = new AsyncTask<Void,Void,RectF[]> () {
632
@Override
633
protected RectF[] doInBackground(Void... arg0) {
634
return mCore.getWidgetAreas(page);
635
}
636
637
@Override
638
protected void onPostExecute(RectF[] result) {
639
mWidgetAreas = result;
640
}
641
};
642
643
mLoadWidgetAreas.execute();
644
645
super.setPage(page, size);
646
}
647
648
public void setScale(float scale) {
649
// This type of view scales automatically to fit the size
650
// determined by the parent view groups during layout
651
}
652
653
@Override
654
public void releaseResources() {
655
if (mPassClick != null) {
656
mPassClick.cancel(true);
657
mPassClick = null;
658
}
659
660
if (mLoadWidgetAreas != null) {
661
mLoadWidgetAreas.cancel(true);
662
mLoadWidgetAreas = null;
663
}
664
665
if (mLoadAnnotations != null) {
666
mLoadAnnotations.cancel(true);
667
mLoadAnnotations = null;
668
}
669
670
if (mSetWidgetText != null) {
671
mSetWidgetText.cancel(true);
672
mSetWidgetText = null;
673
}
674
675
if (mSetWidgetChoice != null) {
676
mSetWidgetChoice.cancel(true);
677
mSetWidgetChoice = null;
678
}
679
680
if (mAddStrikeOut != null) {
681
mAddStrikeOut.cancel(true);
682
mAddStrikeOut = null;
683
}
684
685
if (mDeleteAnnotation != null) {
686
mDeleteAnnotation.cancel(true);
687
mDeleteAnnotation = null;
688
}
689
690
super.releaseResources();
691
}
692
}
693
694