Repo created
This commit is contained in:
parent
e09986deae
commit
fa69fd81a1
48 changed files with 5156 additions and 0 deletions
68
lib/build.gradle
Normal file
68
lib/build.gradle
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
group = 'com.artifex.mupdf'
|
||||
version = '1.26.11a'
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.1.+'
|
||||
if (file('../jni/build.gradle').isFile())
|
||||
api project(':jni')
|
||||
else
|
||||
api 'com.artifex.mupdf:fitz:1.26.11'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.artifex.mupdf.viewer'
|
||||
compileSdkVersion 33
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
}
|
||||
publishing {
|
||||
singleVariant("release") {
|
||||
withSourcesJar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
release(MavenPublication) {
|
||||
artifactId 'viewer'
|
||||
artifact(bundleReleaseAar)
|
||||
|
||||
pom {
|
||||
name = 'viewer'
|
||||
url = 'http://www.mupdf.com'
|
||||
licenses {
|
||||
license {
|
||||
name = 'GNU Affero General Public License'
|
||||
url = 'https://www.gnu.org/licenses/agpl-3.0.html'
|
||||
}
|
||||
}
|
||||
}
|
||||
pom.withXml {
|
||||
final dependenciesNode = asNode().appendNode('dependencies')
|
||||
configurations.implementation.allDependencies.each {
|
||||
def dependencyNode = dependenciesNode.appendNode('dependency')
|
||||
dependencyNode.appendNode('groupId', it.group)
|
||||
dependencyNode.appendNode('artifactId', it.name)
|
||||
dependencyNode.appendNode('version', it.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
name 'Local'
|
||||
if (project.hasProperty('MAVEN_REPO')) {
|
||||
url = MAVEN_REPO
|
||||
} else {
|
||||
url = "file://${System.properties['user.home']}/MAVEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
lib/src/main/AndroidManifest.xml
Normal file
33
lib/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<application>
|
||||
<activity
|
||||
android:name=".DocumentActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:exported="true"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<!-- list the mime-types we know about -->
|
||||
<data android:mimeType="application/pdf" />
|
||||
<data android:mimeType="application/vnd.ms-xpsdocument" />
|
||||
<data android:mimeType="application/oxps" />
|
||||
<data android:mimeType="application/vnd.comicbook+zip" />
|
||||
<data android:mimeType="application/x-cbz" />
|
||||
<data android:mimeType="application/epub+zip" />
|
||||
<data android:mimeType="application/x-fictionbook" />
|
||||
<data android:mimeType="application/x-mobipocket-ebook" />
|
||||
<!-- list application/octet-stream to catch the ones android doesn't recognize -->
|
||||
<data android:mimeType="application/octet-stream" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".OutlineActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
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<Params, Result>
|
||||
{
|
||||
private final String APP = "MuPDF";
|
||||
|
||||
private final AsyncTask<Params, Void, Result> asyncTask;
|
||||
private final CancellableTaskDefinition<Params, Result> ourTask;
|
||||
|
||||
public void onPreExecute()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void onPostExecute(Result result)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public CancellableAsyncTask(final CancellableTaskDefinition<Params, Result> task)
|
||||
{
|
||||
if (task == null)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
this.ourTask = task;
|
||||
asyncTask = new AsyncTask<Params, Void, Result>()
|
||||
{
|
||||
@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();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(Result result)
|
||||
{
|
||||
task.doCleanup();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
this.asyncTask.cancel(true);
|
||||
ourTask.doCancel();
|
||||
|
||||
try
|
||||
{
|
||||
this.asyncTask.get();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
}
|
||||
catch (CancellationException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void execute(Params ... params)
|
||||
{
|
||||
asyncTask.execute(params);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
public interface CancellableTaskDefinition <Params, Result>
|
||||
{
|
||||
public Result doInBackground(Params ... params);
|
||||
public void doCancel();
|
||||
public void doCleanup();
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.SeekableInputStream;
|
||||
|
||||
import android.util.Log;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
public class ContentInputStream implements SeekableInputStream {
|
||||
private final String APP = "MuPDF";
|
||||
|
||||
protected ContentResolver cr;
|
||||
protected Uri uri;
|
||||
protected InputStream is;
|
||||
protected long length, p;
|
||||
protected boolean mustReopenStream;
|
||||
|
||||
public ContentInputStream(ContentResolver cr, Uri uri, long size) throws IOException {
|
||||
this.cr = cr;
|
||||
this.uri = uri;
|
||||
length = size;
|
||||
mustReopenStream = false;
|
||||
reopenStream();
|
||||
}
|
||||
|
||||
public long seek(long offset, int whence) throws IOException {
|
||||
long newp = p;
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
newp = offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
newp = p + offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
if (length < 0) {
|
||||
byte[] buf = new byte[16384];
|
||||
int k;
|
||||
while ((k = is.read(buf)) != -1)
|
||||
p += k;
|
||||
length = p;
|
||||
}
|
||||
newp = length + offset;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newp < p) {
|
||||
if (!mustReopenStream) {
|
||||
try {
|
||||
is.skip(newp - p);
|
||||
} catch (IOException x) {
|
||||
Log.i(APP, "Unable to skip backwards, reopening input stream");
|
||||
mustReopenStream = true;
|
||||
}
|
||||
}
|
||||
if (mustReopenStream) {
|
||||
reopenStream();
|
||||
is.skip(newp);
|
||||
}
|
||||
} else if (newp > p) {
|
||||
is.skip(newp - p);
|
||||
}
|
||||
return p = newp;
|
||||
}
|
||||
|
||||
public long position() throws IOException {
|
||||
return p;
|
||||
}
|
||||
|
||||
public int read(byte[] buf) throws IOException {
|
||||
int n = is.read(buf);
|
||||
if (n > 0)
|
||||
p += n;
|
||||
else if (n < 0 && length < 0)
|
||||
length = p;
|
||||
return n;
|
||||
}
|
||||
|
||||
public void reopenStream() throws IOException {
|
||||
if (is != null)
|
||||
{
|
||||
is.close();
|
||||
is = null;
|
||||
}
|
||||
is = cr.openInputStream(uri);
|
||||
p = 0;
|
||||
}
|
||||
|
||||
}
|
||||
844
lib/src/main/java/com/artifex/mupdf/viewer/DocumentActivity.java
Normal file
844
lib/src/main/java/com/artifex/mupdf/viewer/DocumentActivity.java
Normal file
|
|
@ -0,0 +1,844 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.SeekableInputStream;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
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.provider.OpenableColumns;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem.OnMenuItemClickListener;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
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.Toast;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
public class DocumentActivity extends Activity
|
||||
{
|
||||
private final String APP = "MuPDF";
|
||||
|
||||
/* The core rendering instance */
|
||||
enum TopBarMode {Main, Search, More};
|
||||
|
||||
private final int OUTLINE_REQUEST=0;
|
||||
private MuPDFCore core;
|
||||
private String mDocTitle;
|
||||
private String mDocKey;
|
||||
private ReaderView mDocView;
|
||||
private View mButtonsView;
|
||||
private boolean mButtonsVisible;
|
||||
private EditText mPasswordView;
|
||||
private TextView mDocNameView;
|
||||
private SeekBar mPageSlider;
|
||||
private int mPageSliderRes;
|
||||
private TextView mPageNumberView;
|
||||
private ImageButton mSearchButton;
|
||||
private ImageButton mOutlineButton;
|
||||
private ViewAnimator mTopBarSwitcher;
|
||||
private ImageButton mLinkButton;
|
||||
private TopBarMode mTopBarMode = TopBarMode.Main;
|
||||
private ImageButton mSearchBack;
|
||||
private ImageButton mSearchFwd;
|
||||
private ImageButton mSearchClose;
|
||||
private EditText mSearchText;
|
||||
private SearchTask mSearchTask;
|
||||
private AlertDialog.Builder mAlertBuilder;
|
||||
private boolean mLinkHighlight = false;
|
||||
private final Handler mHandler = new Handler();
|
||||
private boolean mAlertsActive= false;
|
||||
private AlertDialog mAlertDialog;
|
||||
private ArrayList<OutlineActivity.Item> mFlatOutline;
|
||||
private boolean mReturnToLibraryActivity = false;
|
||||
|
||||
protected int mDisplayDPI;
|
||||
private int mLayoutEM = 10;
|
||||
private int mLayoutW = 312;
|
||||
private int mLayoutH = 504;
|
||||
|
||||
protected View mLayoutButton;
|
||||
protected PopupMenu mLayoutPopupMenu;
|
||||
|
||||
private String toHex(byte[] digest) {
|
||||
StringBuilder builder = new StringBuilder(2 * digest.length);
|
||||
for (byte b : digest)
|
||||
builder.append(String.format("%02x", b));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private MuPDFCore openBuffer(byte buffer[], String magic)
|
||||
{
|
||||
try
|
||||
{
|
||||
core = new MuPDFCore(buffer, magic);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(APP, "Error opening document buffer: " + e);
|
||||
return null;
|
||||
}
|
||||
return core;
|
||||
}
|
||||
|
||||
private MuPDFCore openStream(SeekableInputStream stm, String magic)
|
||||
{
|
||||
try
|
||||
{
|
||||
core = new MuPDFCore(stm, magic);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(APP, "Error opening document stream: " + e);
|
||||
return null;
|
||||
}
|
||||
return core;
|
||||
}
|
||||
|
||||
private MuPDFCore openCore(Uri uri, long size, String mimetype) throws IOException {
|
||||
ContentResolver cr = getContentResolver();
|
||||
|
||||
Log.i(APP, "Opening document " + uri);
|
||||
|
||||
InputStream is = cr.openInputStream(uri);
|
||||
byte[] buf = null;
|
||||
int used = -1;
|
||||
try {
|
||||
final int limit = 8 * 1024 * 1024;
|
||||
if (size < 0) { // size is unknown
|
||||
buf = new byte[limit];
|
||||
used = is.read(buf);
|
||||
boolean atEOF = is.read() == -1;
|
||||
if (used < 0 || (used == limit && !atEOF)) // no or partial data
|
||||
buf = null;
|
||||
} else if (size <= limit) { // size is known and below limit
|
||||
buf = new byte[(int) size];
|
||||
used = is.read(buf);
|
||||
if (used < 0 || used < size) // no or partial data
|
||||
buf = null;
|
||||
}
|
||||
if (buf != null && buf.length != used) {
|
||||
byte[] newbuf = new byte[used];
|
||||
System.arraycopy(buf, 0, newbuf, 0, used);
|
||||
buf = newbuf;
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
buf = null;
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
|
||||
if (buf != null) {
|
||||
Log.i(APP, " Opening document from memory buffer of size " + buf.length);
|
||||
return openBuffer(buf, mimetype);
|
||||
} else {
|
||||
Log.i(APP, " Opening document from stream");
|
||||
return openStream(new ContentInputStream(cr, uri, size), mimetype);
|
||||
}
|
||||
}
|
||||
|
||||
private void showCannotOpenDialog(String reason) {
|
||||
Resources res = getResources();
|
||||
AlertDialog alert = mAlertBuilder.create();
|
||||
setTitle(String.format(Locale.ROOT, 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();
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
mDisplayDPI = (int)metrics.densityDpi;
|
||||
|
||||
mAlertBuilder = new AlertDialog.Builder(this);
|
||||
|
||||
if (core == null) {
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey("DocTitle")) {
|
||||
mDocTitle = savedInstanceState.getString("DocTitle");
|
||||
}
|
||||
}
|
||||
if (core == null) {
|
||||
Intent intent = getIntent();
|
||||
SeekableInputStream file;
|
||||
|
||||
mReturnToLibraryActivity = intent.getIntExtra(getComponentName().getPackageName() + ".ReturnToLibraryActivity", 0) != 0;
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
Uri uri = intent.getData();
|
||||
String mimetype = getIntent().getType();
|
||||
|
||||
if (uri == null) {
|
||||
showCannotOpenDialog("No document uri to open");
|
||||
return;
|
||||
}
|
||||
|
||||
mDocKey = uri.toString();
|
||||
|
||||
Log.i(APP, "OPEN URI " + uri.toString());
|
||||
Log.i(APP, " MAGIC (Intent) " + mimetype);
|
||||
|
||||
mDocTitle = null;
|
||||
long size = -1;
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = getContentResolver().query(uri, null, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int idx;
|
||||
|
||||
idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (idx >= 0 && cursor.getType(idx) == Cursor.FIELD_TYPE_STRING)
|
||||
mDocTitle = cursor.getString(idx);
|
||||
|
||||
idx = cursor.getColumnIndex(OpenableColumns.SIZE);
|
||||
if (idx >= 0 && cursor.getType(idx) == Cursor.FIELD_TYPE_INTEGER)
|
||||
size = cursor.getLong(idx);
|
||||
|
||||
if (size == 0)
|
||||
size = -1;
|
||||
}
|
||||
} catch (Exception x) {
|
||||
// Ignore any exception and depend on default values for title
|
||||
// and size (unless one was decoded
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
Log.i(APP, " NAME " + mDocTitle);
|
||||
Log.i(APP, " SIZE " + size);
|
||||
|
||||
if (mimetype == null || mimetype.equals("application/octet-stream")) {
|
||||
mimetype = getContentResolver().getType(uri);
|
||||
Log.i(APP, " MAGIC (Resolved) " + mimetype);
|
||||
}
|
||||
if (mimetype == null || mimetype.equals("application/octet-stream")) {
|
||||
mimetype = mDocTitle;
|
||||
Log.i(APP, " MAGIC (Filename) " + mimetype);
|
||||
}
|
||||
|
||||
try {
|
||||
core = openCore(uri, size, mimetype);
|
||||
SearchTaskResult.set(null);
|
||||
} catch (Exception x) {
|
||||
showCannotOpenDialog(x.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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 relayoutDocument() {
|
||||
int loc = core.layout(mDocView.mCurrent, mLayoutW, mLayoutH, mLayoutEM);
|
||||
mFlatOutline = null;
|
||||
mDocView.mHistory.clear();
|
||||
mDocView.refresh();
|
||||
mDocView.setDisplayedViewIndex(loc);
|
||||
}
|
||||
|
||||
public void createUI(Bundle savedInstanceState) {
|
||||
if (core == null)
|
||||
return;
|
||||
|
||||
// Now create the UI.
|
||||
// First create the document view
|
||||
mDocView = new ReaderView(this) {
|
||||
@Override
|
||||
protected void onMoveToChild(int i) {
|
||||
if (core == null)
|
||||
return;
|
||||
|
||||
mPageNumberView.setText(String.format(Locale.ROOT, "%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
|
||||
public void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
if (core.isReflowable()) {
|
||||
mLayoutW = w * 72 / mDisplayDPI;
|
||||
mLayoutH = h * 72 / mDisplayDPI;
|
||||
relayoutDocument();
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
};
|
||||
mDocView.setAdapter(new PageAdapter(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
|
||||
String docTitle = core.getTitle();
|
||||
if (docTitle != null)
|
||||
mDocNameView.setText(docTitle);
|
||||
else
|
||||
mDocNameView.setText(mDocTitle);
|
||||
|
||||
// Activate the seekbar
|
||||
mPageSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
mDocView.pushHistory();
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
mSearchClose.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
searchModeOff();
|
||||
}
|
||||
});
|
||||
|
||||
// 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.isReflowable()) {
|
||||
mLayoutButton.setVisibility(View.VISIBLE);
|
||||
mLayoutPopupMenu = new PopupMenu(this, mLayoutButton);
|
||||
mLayoutPopupMenu.getMenuInflater().inflate(R.menu.layout_menu, mLayoutPopupMenu.getMenu());
|
||||
mLayoutPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
float oldLayoutEM = mLayoutEM;
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_layout_6pt) mLayoutEM = 6;
|
||||
else if (id == R.id.action_layout_7pt) mLayoutEM = 7;
|
||||
else if (id == R.id.action_layout_8pt) mLayoutEM = 8;
|
||||
else if (id == R.id.action_layout_9pt) mLayoutEM = 9;
|
||||
else if (id == R.id.action_layout_10pt) mLayoutEM = 10;
|
||||
else if (id == R.id.action_layout_11pt) mLayoutEM = 11;
|
||||
else if (id == R.id.action_layout_12pt) mLayoutEM = 12;
|
||||
else if (id == R.id.action_layout_13pt) mLayoutEM = 13;
|
||||
else if (id == R.id.action_layout_14pt) mLayoutEM = 14;
|
||||
else if (id == R.id.action_layout_15pt) mLayoutEM = 15;
|
||||
else if (id == R.id.action_layout_16pt) mLayoutEM = 16;
|
||||
if (oldLayoutEM != mLayoutEM)
|
||||
relayoutDocument();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mLayoutButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mLayoutPopupMenu.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (core.hasOutline()) {
|
||||
mOutlineButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (mFlatOutline == null)
|
||||
mFlatOutline = core.getOutline();
|
||||
if (mFlatOutline != null) {
|
||||
Intent intent = new Intent(DocumentActivity.this, OutlineActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt("POSITION", mDocView.getDisplayedViewIndex());
|
||||
bundle.putSerializable("OUTLINE", mFlatOutline);
|
||||
intent.putExtra("PALLETBUNDLE", Pallet.sendBundle(bundle));
|
||||
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"+mDocKey, 0));
|
||||
|
||||
if (savedInstanceState == null || !savedInstanceState.getBoolean("ButtonsHidden", false))
|
||||
showButtons();
|
||||
|
||||
if(savedInstanceState != null && savedInstanceState.getBoolean("SearchMode", false))
|
||||
searchModeOn();
|
||||
|
||||
// Stick the document view and the buttons overlay into a parent view
|
||||
RelativeLayout layout = new RelativeLayout(this);
|
||||
layout.setBackgroundColor(Color.DKGRAY);
|
||||
layout.addView(mDocView);
|
||||
layout.addView(mButtonsView);
|
||||
setContentView(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case OUTLINE_REQUEST:
|
||||
if (resultCode >= RESULT_FIRST_USER && mDocView != null) {
|
||||
mDocView.pushHistory();
|
||||
mDocView.setDisplayedViewIndex(resultCode-RESULT_FIRST_USER);
|
||||
}
|
||||
break;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
if (mDocKey != null && mDocView != null) {
|
||||
if (mDocTitle != null)
|
||||
outState.putString("DocTitle", mDocTitle);
|
||||
|
||||
// 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"+mDocKey, mDocView.getDisplayedViewIndex());
|
||||
edit.apply();
|
||||
}
|
||||
|
||||
if (!mButtonsVisible)
|
||||
outState.putBoolean("ButtonsHidden", true);
|
||||
|
||||
if (mTopBarMode == TopBarMode.Search)
|
||||
outState.putBoolean("SearchMode", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (mSearchTask != null)
|
||||
mSearchTask.stop();
|
||||
|
||||
if (mDocKey != null && mDocView != null) {
|
||||
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
edit.putInt("page"+mDocKey, mDocView.getDisplayedViewIndex());
|
||||
edit.apply();
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy()
|
||||
{
|
||||
if (mDocView != null) {
|
||||
mDocView.applyToChildren(new ReaderView.ViewMapper() {
|
||||
@Override
|
||||
public void applyToView(View view) {
|
||||
((PageView)view).releaseBitmaps();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (core != null)
|
||||
core.onDestroy();
|
||||
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, 0x00, 0x66, 0xCC) : 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(Locale.ROOT, "%d / %d", index + 1, core.countPages()));
|
||||
}
|
||||
|
||||
private void makeButtonsView() {
|
||||
mButtonsView = getLayoutInflater().inflate(R.layout.document_activity, null);
|
||||
mDocNameView = (TextView)mButtonsView.findViewById(R.id.docNameText);
|
||||
mPageSlider = (SeekBar)mButtonsView.findViewById(R.id.pageSlider);
|
||||
mPageNumberView = (TextView)mButtonsView.findViewById(R.id.pageNumber);
|
||||
mSearchButton = (ImageButton)mButtonsView.findViewById(R.id.searchButton);
|
||||
mOutlineButton = (ImageButton)mButtonsView.findViewById(R.id.outlineButton);
|
||||
mTopBarSwitcher = (ViewAnimator)mButtonsView.findViewById(R.id.switcher);
|
||||
mSearchBack = (ImageButton)mButtonsView.findViewById(R.id.searchBack);
|
||||
mSearchFwd = (ImageButton)mButtonsView.findViewById(R.id.searchForward);
|
||||
mSearchClose = (ImageButton)mButtonsView.findViewById(R.id.searchClose);
|
||||
mSearchText = (EditText)mButtonsView.findViewById(R.id.searchText);
|
||||
mLinkButton = (ImageButton)mButtonsView.findViewById(R.id.linkButton);
|
||||
mLayoutButton = mButtonsView.findViewById(R.id.layoutButton);
|
||||
mTopBarSwitcher.setVisibility(View.INVISIBLE);
|
||||
mPageNumberView.setVisibility(View.INVISIBLE);
|
||||
|
||||
mPageSlider.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
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() {
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mDocView == null || (mDocView != null && !mDocView.popHistory())) {
|
||||
super.onBackPressed();
|
||||
if (mReturnToLibraryActivity) {
|
||||
Intent intent = getPackageManager().getLaunchIntentForPackage(getComponentName().getPackageName());
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.Cookie;
|
||||
|
||||
public abstract class MuPDFCancellableTaskDefinition<Params, Result> implements CancellableTaskDefinition<Params, Result>
|
||||
{
|
||||
private Cookie cookie;
|
||||
|
||||
public MuPDFCancellableTaskDefinition()
|
||||
{
|
||||
this.cookie = 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(Cookie cookie, Params ... params);
|
||||
}
|
||||
232
lib/src/main/java/com/artifex/mupdf/viewer/MuPDFCore.java
Normal file
232
lib/src/main/java/com/artifex/mupdf/viewer/MuPDFCore.java
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.Cookie;
|
||||
import com.artifex.mupdf.fitz.DisplayList;
|
||||
import com.artifex.mupdf.fitz.Document;
|
||||
import com.artifex.mupdf.fitz.Link;
|
||||
import com.artifex.mupdf.fitz.Matrix;
|
||||
import com.artifex.mupdf.fitz.Outline;
|
||||
import com.artifex.mupdf.fitz.Page;
|
||||
import com.artifex.mupdf.fitz.Quad;
|
||||
import com.artifex.mupdf.fitz.Rect;
|
||||
import com.artifex.mupdf.fitz.RectI;
|
||||
import com.artifex.mupdf.fitz.SeekableInputStream;
|
||||
import com.artifex.mupdf.fitz.android.AndroidDrawDevice;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.PointF;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MuPDFCore
|
||||
{
|
||||
private final String APP = "MuPDF";
|
||||
|
||||
private int resolution;
|
||||
private Document doc;
|
||||
private Outline[] outline;
|
||||
private int pageCount = -1;
|
||||
private boolean reflowable = false;
|
||||
private int currentPage;
|
||||
private Page page;
|
||||
private float pageWidth;
|
||||
private float pageHeight;
|
||||
private DisplayList displayList;
|
||||
|
||||
/* Default to "A Format" pocket book size. */
|
||||
private int layoutW = 312;
|
||||
private int layoutH = 504;
|
||||
private int layoutEM = 10;
|
||||
|
||||
private MuPDFCore(Document doc) {
|
||||
this.doc = doc;
|
||||
doc.layout(layoutW, layoutH, layoutEM);
|
||||
pageCount = doc.countPages();
|
||||
reflowable = doc.isReflowable();
|
||||
resolution = 160;
|
||||
currentPage = -1;
|
||||
}
|
||||
|
||||
public MuPDFCore(byte buffer[], String magic) {
|
||||
this(Document.openDocument(buffer, magic));
|
||||
}
|
||||
|
||||
public MuPDFCore(SeekableInputStream stm, String magic) {
|
||||
this(Document.openDocument(stm, magic));
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return doc.getMetaData(Document.META_INFO_TITLE);
|
||||
}
|
||||
|
||||
public int countPages() {
|
||||
return pageCount;
|
||||
}
|
||||
|
||||
public boolean isReflowable() {
|
||||
return reflowable;
|
||||
}
|
||||
|
||||
public synchronized int layout(int oldPage, int w, int h, int em) {
|
||||
if (w != layoutW || h != layoutH || em != layoutEM) {
|
||||
System.out.println("LAYOUT: " + w + "," + h);
|
||||
layoutW = w;
|
||||
layoutH = h;
|
||||
layoutEM = em;
|
||||
long mark = doc.makeBookmark(doc.locationFromPageNumber(oldPage));
|
||||
doc.layout(layoutW, layoutH, layoutEM);
|
||||
currentPage = -1;
|
||||
pageCount = doc.countPages();
|
||||
outline = null;
|
||||
try {
|
||||
outline = doc.loadOutline();
|
||||
} catch (Exception ex) {
|
||||
/* ignore error */
|
||||
}
|
||||
return doc.pageNumberFromLocation(doc.findBookmark(mark));
|
||||
}
|
||||
return oldPage;
|
||||
}
|
||||
|
||||
private synchronized void gotoPage(int pageNum) {
|
||||
/* TODO: page cache */
|
||||
if (pageNum > pageCount-1)
|
||||
pageNum = pageCount-1;
|
||||
else if (pageNum < 0)
|
||||
pageNum = 0;
|
||||
if (pageNum != currentPage) {
|
||||
if (page != null)
|
||||
page.destroy();
|
||||
page = null;
|
||||
if (displayList != null)
|
||||
displayList.destroy();
|
||||
displayList = null;
|
||||
page = null;
|
||||
pageWidth = 0;
|
||||
pageHeight = 0;
|
||||
currentPage = -1;
|
||||
|
||||
if (doc != null) {
|
||||
page = doc.loadPage(pageNum);
|
||||
Rect b = page.getBounds();
|
||||
pageWidth = b.x1 - b.x0;
|
||||
pageHeight = b.y1 - b.y0;
|
||||
}
|
||||
|
||||
currentPage = pageNum;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized PointF getPageSize(int pageNum) {
|
||||
gotoPage(pageNum);
|
||||
return new PointF(pageWidth, pageHeight);
|
||||
}
|
||||
|
||||
public synchronized void onDestroy() {
|
||||
if (displayList != null)
|
||||
displayList.destroy();
|
||||
displayList = null;
|
||||
if (page != null)
|
||||
page.destroy();
|
||||
page = null;
|
||||
if (doc != null)
|
||||
doc.destroy();
|
||||
doc = null;
|
||||
}
|
||||
|
||||
public synchronized void drawPage(Bitmap bm, int pageNum,
|
||||
int pageW, int pageH,
|
||||
int patchX, int patchY,
|
||||
int patchW, int patchH,
|
||||
Cookie cookie) {
|
||||
gotoPage(pageNum);
|
||||
|
||||
if (displayList == null && page != null)
|
||||
try {
|
||||
displayList = page.toDisplayList();
|
||||
} catch (Exception ex) {
|
||||
displayList = null;
|
||||
}
|
||||
|
||||
if (displayList == null || page == null)
|
||||
return;
|
||||
|
||||
float zoom = resolution / 72;
|
||||
Matrix ctm = new Matrix(zoom, zoom);
|
||||
RectI bbox = new RectI(page.getBounds().transform(ctm));
|
||||
float xscale = (float)pageW / (float)(bbox.x1-bbox.x0);
|
||||
float yscale = (float)pageH / (float)(bbox.y1-bbox.y0);
|
||||
ctm.scale(xscale, yscale);
|
||||
|
||||
AndroidDrawDevice dev = new AndroidDrawDevice(bm, patchX, patchY);
|
||||
try {
|
||||
displayList.run(dev, ctm, cookie);
|
||||
dev.close();
|
||||
} finally {
|
||||
dev.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void updatePage(Bitmap bm, int pageNum,
|
||||
int pageW, int pageH,
|
||||
int patchX, int patchY,
|
||||
int patchW, int patchH,
|
||||
Cookie cookie) {
|
||||
drawPage(bm, pageNum, pageW, pageH, patchX, patchY, patchW, patchH, cookie);
|
||||
}
|
||||
|
||||
public synchronized Link[] getPageLinks(int pageNum) {
|
||||
gotoPage(pageNum);
|
||||
return page != null ? page.getLinks() : null;
|
||||
}
|
||||
|
||||
public synchronized int resolveLink(Link link) {
|
||||
return doc.pageNumberFromLocation(doc.resolveLink(link));
|
||||
}
|
||||
|
||||
public synchronized Quad[][] searchPage(int pageNum, String text) {
|
||||
gotoPage(pageNum);
|
||||
return page.search(text);
|
||||
}
|
||||
|
||||
public synchronized boolean hasOutline() {
|
||||
if (outline == null) {
|
||||
try {
|
||||
outline = doc.loadOutline();
|
||||
} catch (Exception ex) {
|
||||
/* ignore error */
|
||||
}
|
||||
}
|
||||
return outline != null;
|
||||
}
|
||||
|
||||
private void flattenOutlineNodes(ArrayList<OutlineActivity.Item> result, Outline list[], String indent) {
|
||||
for (Outline node : list) {
|
||||
if (node.title != null) {
|
||||
int page = doc.pageNumberFromLocation(doc.resolveLink(node));
|
||||
result.add(new OutlineActivity.Item(indent + node.title, page));
|
||||
}
|
||||
if (node.down != null)
|
||||
flattenOutlineNodes(result, node.down, indent + " ");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized ArrayList<OutlineActivity.Item> getOutline() {
|
||||
ArrayList<OutlineActivity.Item> result = new ArrayList<OutlineActivity.Item>();
|
||||
flattenOutlineNodes(result, outline, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized boolean needsPassword() {
|
||||
return doc.needsPassword();
|
||||
}
|
||||
|
||||
public synchronized boolean authenticatePassword(String password) {
|
||||
boolean authenticated = doc.authenticatePassword(password);
|
||||
pageCount = doc.countPages();
|
||||
reflowable = doc.isReflowable();
|
||||
return authenticated;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class OutlineActivity extends ListActivity
|
||||
{
|
||||
private final String APP = "MuPDF";
|
||||
|
||||
public static class Item implements Serializable {
|
||||
public String title;
|
||||
public int page;
|
||||
public Item(String title, int page) {
|
||||
this.title = title;
|
||||
this.page = page;
|
||||
}
|
||||
public String toString() {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
protected ArrayAdapter<Item> adapter;
|
||||
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
|
||||
adapter = new ArrayAdapter<Item>(this, android.R.layout.simple_list_item_1);
|
||||
setListAdapter(adapter);
|
||||
|
||||
int idx = getIntent().getIntExtra("PALLETBUNDLE", -1);
|
||||
Bundle bundle = Pallet.receiveBundle(idx);
|
||||
if (bundle != null) {
|
||||
int currentPage = bundle.getInt("POSITION");
|
||||
ArrayList<Item> outline = (ArrayList<Item>)bundle.getSerializable("OUTLINE");
|
||||
int found = -1;
|
||||
for (int i = 0; i < outline.size(); ++i) {
|
||||
Item item = outline.get(i);
|
||||
if (found < 0 && item.page >= currentPage)
|
||||
found = i;
|
||||
adapter.add(item);
|
||||
}
|
||||
if (found >= 0)
|
||||
setSelection(found);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
Item item = adapter.getItem(position);
|
||||
setResult(RESULT_FIRST_USER + item.page);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
105
lib/src/main/java/com/artifex/mupdf/viewer/PageAdapter.java
Normal file
105
lib/src/main/java/com/artifex/mupdf/viewer/PageAdapter.java
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
public class PageAdapter extends BaseAdapter {
|
||||
private final String APP = "MuPDF";
|
||||
private final Context mContext;
|
||||
private final MuPDFCore mCore;
|
||||
private final SparseArray<PointF> mPageSizes = new SparseArray<PointF>();
|
||||
private Bitmap mSharedHqBm;
|
||||
|
||||
public PageAdapter(Context c, MuPDFCore core) {
|
||||
mContext = c;
|
||||
mCore = core;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
try {
|
||||
return mCore.countPages();
|
||||
} catch (RuntimeException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public synchronized void releaseBitmaps()
|
||||
{
|
||||
// recycle and release the shared bitmap.
|
||||
if (mSharedHqBm!=null)
|
||||
mSharedHqBm.recycle();
|
||||
mSharedHqBm = null;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
mPageSizes.clear();
|
||||
}
|
||||
|
||||
public synchronized View getView(final int position, View convertView, ViewGroup parent) {
|
||||
final PageView pageView;
|
||||
if (convertView == null) {
|
||||
if (mSharedHqBm == null || mSharedHqBm.getWidth() != parent.getWidth() || mSharedHqBm.getHeight() != parent.getHeight())
|
||||
{
|
||||
if (parent.getWidth() > 0 && parent.getHeight() > 0)
|
||||
mSharedHqBm = Bitmap.createBitmap(parent.getWidth(), parent.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
else
|
||||
mSharedHqBm = null;
|
||||
}
|
||||
|
||||
pageView = new PageView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight()), mSharedHqBm);
|
||||
} else {
|
||||
pageView = (PageView) 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<Void,Void,PointF> sizingTask = new AsyncTask<Void,Void,PointF>() {
|
||||
@Override
|
||||
protected PointF doInBackground(Void... arg0) {
|
||||
try {
|
||||
return mCore.getPageSize(position);
|
||||
} catch (RuntimeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
672
lib/src/main/java/com/artifex/mupdf/viewer/PageView.java
Normal file
672
lib/src/main/java/com/artifex/mupdf/viewer/PageView.java
Normal file
|
|
@ -0,0 +1,672 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.Cookie;
|
||||
import com.artifex.mupdf.fitz.Link;
|
||||
import com.artifex.mupdf.fitz.Quad;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.FileUriExposedException;
|
||||
import android.os.Handler;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
// Make our ImageViews opaque to optimize redraw
|
||||
class OpaqueImageView extends ImageView {
|
||||
|
||||
public OpaqueImageView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class PageView extends ViewGroup {
|
||||
private final String APP = "MuPDF";
|
||||
private final MuPDFCore mCore;
|
||||
|
||||
private static final int HIGHLIGHT_COLOR = 0x80cc6600;
|
||||
private static final int LINK_COLOR = 0x800066cc;
|
||||
private static final int BOX_COLOR = 0xFF4444FF;
|
||||
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<Void,Void,Link[]> mGetLinkInfo;
|
||||
private CancellableAsyncTask<Void, Boolean> 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<Void, Boolean> mDrawPatch;
|
||||
private Quad mSearchBoxes[][];
|
||||
protected Link mLinks[];
|
||||
private View mSearchView;
|
||||
private boolean mIsBlank;
|
||||
private boolean mHighlightLinks;
|
||||
|
||||
private ImageView mErrorIndicator;
|
||||
|
||||
private ProgressBar mBusyIndicator;
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
public PageView(Context c, MuPDFCore core, Point parentSize, Bitmap sharedHqBm) {
|
||||
super(c);
|
||||
mContext = c;
|
||||
mCore = core;
|
||||
mParentSize = parentSize;
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
mEntireBm = Bitmap.createBitmap(parentSize.x, parentSize.y, Config.ARGB_8888);
|
||||
mPatchBm = sharedHqBm;
|
||||
mEntireMat = new Matrix();
|
||||
}
|
||||
|
||||
private void reinit() {
|
||||
// Cancel pending render task
|
||||
if (mDrawEntire != null) {
|
||||
mDrawEntire.cancel();
|
||||
mDrawEntire = null;
|
||||
}
|
||||
|
||||
if (mDrawPatch != null) {
|
||||
mDrawPatch.cancel();
|
||||
mDrawPatch = null;
|
||||
}
|
||||
|
||||
if (mGetLinkInfo != null) {
|
||||
mGetLinkInfo.cancel(true);
|
||||
mGetLinkInfo = 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;
|
||||
|
||||
clearRenderError();
|
||||
}
|
||||
|
||||
public void releaseResources() {
|
||||
reinit();
|
||||
|
||||
if (mBusyIndicator != null) {
|
||||
removeView(mBusyIndicator);
|
||||
mBusyIndicator = null;
|
||||
}
|
||||
clearRenderError();
|
||||
}
|
||||
|
||||
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);
|
||||
addView(mBusyIndicator);
|
||||
}
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
}
|
||||
|
||||
protected void clearRenderError() {
|
||||
if (mErrorIndicator == null)
|
||||
return;
|
||||
|
||||
removeView(mErrorIndicator);
|
||||
mErrorIndicator = null;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
protected void setRenderError(String why) {
|
||||
|
||||
int page = mPageNumber;
|
||||
reinit();
|
||||
mPageNumber = page;
|
||||
|
||||
if (mBusyIndicator != null) {
|
||||
removeView(mBusyIndicator);
|
||||
mBusyIndicator = null;
|
||||
}
|
||||
if (mSearchView != null) {
|
||||
removeView(mSearchView);
|
||||
mSearchView = null;
|
||||
}
|
||||
|
||||
if (mErrorIndicator == null) {
|
||||
mErrorIndicator = new OpaqueImageView(mContext);
|
||||
mErrorIndicator.setScaleType(ImageView.ScaleType.CENTER);
|
||||
addView(mErrorIndicator);
|
||||
Drawable mErrorIcon = getResources().getDrawable(R.drawable.ic_error_red_24dp);
|
||||
mErrorIndicator.setImageDrawable(mErrorIcon);
|
||||
mErrorIndicator.setBackgroundColor(BACKGROUND_COLOR);
|
||||
}
|
||||
|
||||
setBackgroundColor(Color.TRANSPARENT);
|
||||
mErrorIndicator.bringToFront();
|
||||
mErrorIndicator.invalidate();
|
||||
}
|
||||
|
||||
public void setPage(int page, PointF size) {
|
||||
// Cancel pending render task
|
||||
if (mDrawEntire != null) {
|
||||
mDrawEntire.cancel();
|
||||
mDrawEntire = null;
|
||||
}
|
||||
|
||||
mIsBlank = false;
|
||||
// Highlights may be missing because mIsBlank was true on last draw
|
||||
if (mSearchView != null)
|
||||
mSearchView.invalidate();
|
||||
|
||||
mPageNumber = page;
|
||||
|
||||
if (size == null) {
|
||||
setRenderError("Error loading page");
|
||||
size = new PointF(612, 792);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (mErrorIndicator != null)
|
||||
return;
|
||||
|
||||
if (mEntire == null) {
|
||||
mEntire = new OpaqueImageView(mContext);
|
||||
mEntire.setScaleType(ImageView.ScaleType.MATRIX);
|
||||
addView(mEntire);
|
||||
}
|
||||
|
||||
mEntire.setImageBitmap(null);
|
||||
mEntire.invalidate();
|
||||
|
||||
// Get the link info in the background
|
||||
mGetLinkInfo = new AsyncTask<Void,Void,Link[]>() {
|
||||
protected Link[] doInBackground(Void... v) {
|
||||
return getLinkInfo();
|
||||
}
|
||||
|
||||
protected void onPostExecute(Link[] v) {
|
||||
mLinks = v;
|
||||
if (mSearchView != null)
|
||||
mSearchView.invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
mGetLinkInfo.execute();
|
||||
|
||||
// Render the page in the background
|
||||
mDrawEntire = new CancellableAsyncTask<Void, Boolean>(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);
|
||||
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(Boolean result) {
|
||||
removeView(mBusyIndicator);
|
||||
mBusyIndicator = null;
|
||||
if (result.booleanValue()) {
|
||||
clearRenderError();
|
||||
mEntire.setImageBitmap(mEntireBm);
|
||||
mEntire.invalidate();
|
||||
} else {
|
||||
setRenderError("Error rendering page");
|
||||
}
|
||||
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 (Quad[] searchBox : mSearchBoxes) {
|
||||
for (Quad q : searchBox) {
|
||||
Path path = new Path();
|
||||
path.moveTo(q.ul_x * scale, q.ul_y * scale);
|
||||
path.lineTo(q.ll_x * scale, q.ll_y * scale);
|
||||
path.lineTo(q.lr_x * scale, q.lr_y * scale);
|
||||
path.lineTo(q.ur_x * scale, q.ur_y * scale);
|
||||
path.close();
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mIsBlank && mLinks != null && mHighlightLinks) {
|
||||
paint.setColor(LINK_COLOR);
|
||||
for (Link link : mLinks)
|
||||
canvas.drawRect(link.getBounds().x0*scale, link.getBounds().y0*scale,
|
||||
link.getBounds().x1*scale, link.getBounds().y1*scale,
|
||||
paint);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addView(mSearchView);
|
||||
}
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setSearchBoxes(Quad searchBoxes[][]) {
|
||||
mSearchBoxes = searchBoxes;
|
||||
if (mSearchView != null)
|
||||
mSearchView.invalidate();
|
||||
}
|
||||
|
||||
public void setLinkHighlighting(boolean f) {
|
||||
mHighlightLinks = f;
|
||||
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);
|
||||
}
|
||||
if (mErrorIndicator != null) {
|
||||
int limit = Math.min(mParentSize.x, mParentSize.y)/2;
|
||||
mErrorIndicator.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);
|
||||
}
|
||||
|
||||
if (mErrorIndicator != null) {
|
||||
int bw = (int) (8.5 * mErrorIndicator.getMeasuredWidth());
|
||||
int bh = (int) (11 * mErrorIndicator.getMeasuredHeight());
|
||||
mErrorIndicator.layout((w-bw)/2, (h-bh)/2, (w+bw)/2, (h+bh)/2);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateHq(boolean update) {
|
||||
if (mErrorIndicator != null) {
|
||||
if (mPatch != null) {
|
||||
mPatch.setImageBitmap(null);
|
||||
mPatch.invalidate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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.cancel();
|
||||
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);
|
||||
if (mSearchView != null)
|
||||
mSearchView.bringToFront();
|
||||
}
|
||||
|
||||
CancellableTaskDefinition<Void, Boolean> 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<Void, Boolean>(task) {
|
||||
|
||||
public void onPostExecute(Boolean result) {
|
||||
if (result.booleanValue()) {
|
||||
mPatchViewSize = patchViewSize;
|
||||
mPatchArea = patchArea;
|
||||
clearRenderError();
|
||||
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);
|
||||
} else {
|
||||
setRenderError("Error rendering patch");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mDrawPatch.execute();
|
||||
}
|
||||
}
|
||||
|
||||
public void update() {
|
||||
// Cancel pending render task
|
||||
if (mDrawEntire != null) {
|
||||
mDrawEntire.cancel();
|
||||
mDrawEntire = null;
|
||||
}
|
||||
|
||||
if (mDrawPatch != null) {
|
||||
mDrawPatch.cancel();
|
||||
mDrawPatch = null;
|
||||
}
|
||||
|
||||
// Render the page in the background
|
||||
mDrawEntire = new CancellableAsyncTask<Void, Boolean>(getUpdatePageTask(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y)) {
|
||||
|
||||
public void onPostExecute(Boolean result) {
|
||||
if (result.booleanValue()) {
|
||||
clearRenderError();
|
||||
mEntire.setImageBitmap(mEntireBm);
|
||||
mEntire.invalidate();
|
||||
} else {
|
||||
setRenderError("Error updating page");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mDrawEntire.execute();
|
||||
|
||||
updateHq(true);
|
||||
}
|
||||
|
||||
public void removeHq() {
|
||||
// Stop the drawing of the patch if still going
|
||||
if (mDrawPatch != null) {
|
||||
mDrawPatch.cancel();
|
||||
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;
|
||||
}
|
||||
|
||||
public int hitLink(Link link) {
|
||||
if (link.isExternal()) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link.getURI()));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); // API>=21: FLAG_ACTIVITY_NEW_DOCUMENT
|
||||
try {
|
||||
mContext.startActivity(intent);
|
||||
} catch (FileUriExposedException x) {
|
||||
Log.e(APP, x.toString());
|
||||
Toast.makeText(getContext(), "Android does not allow following file:// link: " + link.getURI(), Toast.LENGTH_LONG).show();
|
||||
} catch (Throwable x) {
|
||||
Log.e(APP, x.toString());
|
||||
Toast.makeText(getContext(), x.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return mCore.resolveLink(link);
|
||||
}
|
||||
}
|
||||
|
||||
public int 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;
|
||||
|
||||
if (mLinks != null)
|
||||
for (Link l: mLinks)
|
||||
if (l.getBounds().contains(docRelX, docRelY))
|
||||
return hitLink(l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected CancellableTaskDefinition<Void, Boolean> 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, Boolean>() {
|
||||
@Override
|
||||
public Boolean doInBackground(Cookie cookie, Void ... params) {
|
||||
if (bm == null)
|
||||
return new Boolean(false);
|
||||
// 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);
|
||||
try {
|
||||
mCore.drawPage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie);
|
||||
return new Boolean(true);
|
||||
} catch (RuntimeException e) {
|
||||
return new Boolean(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
protected CancellableTaskDefinition<Void, Boolean> 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, Boolean>() {
|
||||
@Override
|
||||
public Boolean doInBackground(Cookie cookie, Void ... params) {
|
||||
if (bm == null)
|
||||
return new Boolean(false);
|
||||
// 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);
|
||||
try {
|
||||
mCore.updatePage(bm, mPageNumber, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight, cookie);
|
||||
return new Boolean(true);
|
||||
} catch (RuntimeException e) {
|
||||
return new Boolean(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected Link[] getLinkInfo() {
|
||||
try {
|
||||
return mCore.getPageLinks(mPageNumber);
|
||||
} catch (RuntimeException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
lib/src/main/java/com/artifex/mupdf/viewer/Pallet.java
Normal file
39
lib/src/main/java/com/artifex/mupdf/viewer/Pallet.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Pallet {
|
||||
private static Pallet instance = new Pallet();
|
||||
private final Map<Integer, Object> pallet = new HashMap<>();
|
||||
private int sequenceNumber = 0;
|
||||
|
||||
private Pallet() {
|
||||
}
|
||||
|
||||
private static Pallet getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static int sendBundle(Bundle bundle) {
|
||||
Pallet instance = getInstance();
|
||||
int i = instance.sequenceNumber++;
|
||||
if (instance.sequenceNumber < 0)
|
||||
instance.sequenceNumber = 0;
|
||||
instance.pallet.put(new Integer(i), bundle);
|
||||
return i;
|
||||
}
|
||||
|
||||
public static Bundle receiveBundle(int number) {
|
||||
Bundle bundle = (Bundle) getInstance().pallet.get(new Integer(number));
|
||||
if (bundle != null)
|
||||
getInstance().pallet.remove(new Integer(number));
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public static boolean hasBundle(int number) {
|
||||
return getInstance().pallet.containsKey(new Integer(number));
|
||||
}
|
||||
}
|
||||
980
lib/src/main/java/com/artifex/mupdf/viewer/ReaderView.java
Normal file
980
lib/src/main/java/com/artifex/mupdf/viewer/ReaderView.java
Normal file
|
|
@ -0,0 +1,980 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.Link;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Stack;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Scroller;
|
||||
|
||||
public class ReaderView
|
||||
extends AdapterView<Adapter>
|
||||
implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable {
|
||||
private final String APP = "MuPDF";
|
||||
|
||||
private Context mContext;
|
||||
private boolean mLinksEnabled = false;
|
||||
private boolean tapDisabled = false;
|
||||
private int tapPageMargin;
|
||||
|
||||
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 = 64.0f;
|
||||
|
||||
private static final boolean HORIZONTAL_SCROLLING = true;
|
||||
|
||||
private PageAdapter mAdapter;
|
||||
protected int mCurrent; // Adapter's index for the current view
|
||||
private boolean mResetLayout;
|
||||
private final SparseArray<View>
|
||||
mChildViews = new SparseArray<View>(3);
|
||||
// Shadows the children of the adapter view
|
||||
// but with more sensible indexing
|
||||
private final LinkedList<View>
|
||||
mViewCache = new LinkedList<View>();
|
||||
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 GestureDetector mGestureDetector;
|
||||
private ScaleGestureDetector mScaleGestureDetector;
|
||||
private Scroller mScroller;
|
||||
private Stepper mStepper;
|
||||
private int mScrollerLastX;
|
||||
private int mScrollerLastY;
|
||||
private float mLastScaleFocusX;
|
||||
private float mLastScaleFocusY;
|
||||
|
||||
protected Stack<Integer> mHistory;
|
||||
|
||||
public interface ViewMapper {
|
||||
void applyToView(View view);
|
||||
}
|
||||
|
||||
public ReaderView(Context context) {
|
||||
super(context);
|
||||
setup(context);
|
||||
}
|
||||
|
||||
public ReaderView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setup(context);
|
||||
}
|
||||
|
||||
public ReaderView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setup(context);
|
||||
}
|
||||
|
||||
private void setup(Context context)
|
||||
{
|
||||
mContext = context;
|
||||
mGestureDetector = new GestureDetector(context, this);
|
||||
mScaleGestureDetector = new ScaleGestureDetector(context, this);
|
||||
mScroller = new Scroller(context);
|
||||
mStepper = new Stepper(this, this);
|
||||
mHistory = new Stack<Integer>();
|
||||
|
||||
// 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 boolean popHistory() {
|
||||
if (mHistory.empty())
|
||||
return false;
|
||||
setDisplayedViewIndex(mHistory.pop());
|
||||
return true;
|
||||
}
|
||||
|
||||
public void pushHistory() {
|
||||
mHistory.push(mCurrent);
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
mHistory.clear();
|
||||
}
|
||||
|
||||
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() {
|
||||
mResetLayout = true;
|
||||
|
||||
mScale = 1.0f;
|
||||
mXScroll = mYScroll = 0;
|
||||
|
||||
/* All page views need recreating since both page and screen has changed size,
|
||||
* invalidating both sizes and bitmaps. */
|
||||
mAdapter.refresh();
|
||||
int numChildren = mChildViews.size();
|
||||
for (int i = 0; i < mChildViews.size(); i++) {
|
||||
View v = mChildViews.valueAt(i);
|
||||
onNotInUse(v);
|
||||
removeViewInLayout(v);
|
||||
}
|
||||
mChildViews.clear();
|
||||
mViewCache.clear();
|
||||
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
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) {
|
||||
PageView pageView = (PageView)getDisplayedView();
|
||||
if (!tapDisabled)
|
||||
onDocMotion();
|
||||
if (!mScaling) {
|
||||
mXScroll -= distanceX;
|
||||
mYScroll -= distanceY;
|
||||
requestLayout();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onShowPress(MotionEvent e) { }
|
||||
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
float previousScale = mScale;
|
||||
mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE);
|
||||
|
||||
{
|
||||
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) {
|
||||
tapDisabled = true;
|
||||
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) {
|
||||
mScaling = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if ((event.getAction() & event.getActionMasked()) == MotionEvent.ACTION_DOWN)
|
||||
{
|
||||
tapDisabled = false;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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) {
|
||||
if (mAdapter != null && mAdapter != adapter)
|
||||
mAdapter.releaseBitmaps();
|
||||
mAdapter = (PageAdapter) 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);
|
||||
}
|
||||
|
||||
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 its 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);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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
|
||||
// won't 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();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onTapMainDocArea() {}
|
||||
protected void onDocMotion() {}
|
||||
|
||||
public void setLinksEnabled(boolean b) {
|
||||
mLinksEnabled = b;
|
||||
resetupChildren();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
Link link = null;
|
||||
if (!tapDisabled) {
|
||||
PageView pageView = (PageView) getDisplayedView();
|
||||
if (mLinksEnabled && pageView != null) {
|
||||
int page = pageView.hitLink(e.getX(), e.getY());
|
||||
if (page > 0) {
|
||||
pushHistory();
|
||||
setDisplayedViewIndex(page);
|
||||
} else {
|
||||
onTapMainDocArea();
|
||||
}
|
||||
} else if (e.getX() < tapPageMargin) {
|
||||
smartMoveBackwards();
|
||||
} else if (e.getX() > super.getWidth() - tapPageMargin) {
|
||||
smartMoveForwards();
|
||||
} else if (e.getY() < tapPageMargin) {
|
||||
smartMoveBackwards();
|
||||
} else if (e.getY() > super.getHeight() - tapPageMargin) {
|
||||
smartMoveForwards();
|
||||
} else {
|
||||
onTapMainDocArea();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onChildSetup(int i, View v) {
|
||||
if (SearchTaskResult.get() != null
|
||||
&& SearchTaskResult.get().pageNumber == i)
|
||||
((PageView) v).setSearchBoxes(SearchTaskResult.get().searchBoxes);
|
||||
else
|
||||
((PageView) v).setSearchBoxes(null);
|
||||
|
||||
((PageView) v).setLinkHighlighting(mLinksEnabled);
|
||||
}
|
||||
|
||||
protected void onMoveToChild(int i) {
|
||||
if (SearchTaskResult.get() != null
|
||||
&& SearchTaskResult.get().pageNumber != i) {
|
||||
SearchTaskResult.set(null);
|
||||
resetupChildren();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMoveOffChild(int i) {
|
||||
}
|
||||
|
||||
protected void onSettle(View v) {
|
||||
// When the layout has settled ask the page to render
|
||||
// in HQ
|
||||
((PageView) 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
|
||||
((PageView) v).removeHq();
|
||||
}
|
||||
|
||||
protected void onNotInUse(View v) {
|
||||
((PageView) v).releaseResources();
|
||||
}
|
||||
}
|
||||
133
lib/src/main/java/com/artifex/mupdf/viewer/SearchTask.java
Normal file
133
lib/src/main/java/com/artifex/mupdf/viewer/SearchTask.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.Quad;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Handler;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
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 final String APP = "MuPDF";
|
||||
|
||||
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<Void,Integer,SearchTaskResult> 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<Void,Integer,SearchTaskResult>() {
|
||||
@Override
|
||||
protected SearchTaskResult doInBackground(Void... params) {
|
||||
int index = startIndex;
|
||||
|
||||
while (0 <= index && index < mCore.countPages() && !isCancelled()) {
|
||||
publishProgress(index);
|
||||
Quad 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import com.artifex.mupdf.fitz.Quad;
|
||||
|
||||
public class SearchTaskResult {
|
||||
public final String txt;
|
||||
public final int pageNumber;
|
||||
public final Quad searchBoxes[][];
|
||||
static private SearchTaskResult singleton;
|
||||
|
||||
SearchTaskResult(String _txt, int _pageNumber, Quad _searchBoxes[][]) {
|
||||
txt = _txt;
|
||||
pageNumber = _pageNumber;
|
||||
searchBoxes = _searchBoxes;
|
||||
}
|
||||
|
||||
static public SearchTaskResult get() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
static public void set(SearchTaskResult r) {
|
||||
singleton = r;
|
||||
}
|
||||
}
|
||||
44
lib/src/main/java/com/artifex/mupdf/viewer/Stepper.java
Normal file
44
lib/src/main/java/com/artifex/mupdf/viewer/Stepper.java
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package com.artifex.mupdf.viewer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
public class Stepper {
|
||||
private final String APP = "MuPDF";
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
lib/src/main/res/drawable/button.xml
Normal file
15
lib/src/main/res/drawable/button.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#a0a0a0" />
|
||||
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<padding android:left="8dp" android:top="8dp" android:right="8dp" android:bottom="8dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
9
lib/src/main/res/drawable/ic_chevron_left_white_24dp.xml
Normal file
9
lib/src/main/res/drawable/ic_chevron_left_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
||||
</vector>
|
||||
9
lib/src/main/res/drawable/ic_close_white_24dp.xml
Normal file
9
lib/src/main/res/drawable/ic_close_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
15
lib/src/main/res/drawable/ic_error_red_24dp.xml
Normal file
15
lib/src/main/res/drawable/ic_error_red_24dp.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="38.836"
|
||||
android:viewportHeight="38.836">
|
||||
<path
|
||||
android:fillColor="#FFFF0000"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M38.331,4.315 L34.521,0.505 19.418,15.609 4.315,0.505 0.505,4.315 15.609,19.418 0.505,34.521 4.315,38.331 19.418,23.228 34.521,38.331 38.331,34.521 23.228,19.418Z"
|
||||
android:strokeColor="#FFFF0000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.715"/>
|
||||
</vector>
|
||||
|
||||
9
lib/src/main/res/drawable/ic_format_size_white_24dp.xml
Normal file
9
lib/src/main/res/drawable/ic_format_size_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M9,4v3h5v12h3L17,7h5L22,4L9,4zM3,12h3v7h3v-7h3L12,9L3,9v3z"/>
|
||||
</vector>
|
||||
9
lib/src/main/res/drawable/ic_link_white_24dp.xml
Normal file
9
lib/src/main/res/drawable/ic_link_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
|
||||
</vector>
|
||||
9
lib/src/main/res/drawable/ic_search_white_24dp.xml
Normal file
9
lib/src/main/res/drawable/ic_search_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
9
lib/src/main/res/drawable/ic_toc_white_24dp.xml
Normal file
9
lib/src/main/res/drawable/ic_toc_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M3,9h14L17,7L3,7v2zM3,13h14v-2L3,11v2zM3,17h14v-2L3,15v2zM19,17h2v-2h-2v2zM19,7v2h2L21,7h-2zM19,13h2v-2h-2v2z"/>
|
||||
</vector>
|
||||
6
lib/src/main/res/drawable/page_indicator.xml
Normal file
6
lib/src/main/res/drawable/page_indicator.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
|
||||
<solid android:color="@color/page_indicator" />
|
||||
<padding android:left="12dp" android:top="4dp" android:right="12dp" android:bottom="4dp" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
4
lib/src/main/res/drawable/seek_line.xml
Normal file
4
lib/src/main/res/drawable/seek_line.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line" >
|
||||
<stroke android:width="2dp" android:color="@android:color/white" />
|
||||
</shape>
|
||||
5
lib/src/main/res/drawable/seek_thumb.xml
Normal file
5
lib/src/main/res/drawable/seek_thumb.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<size android:width="12dp" android:height="12dp" />
|
||||
<stroke android:width="2dp" android:color="@android:color/white" />
|
||||
</shape>
|
||||
165
lib/src/main/res/layout/document_activity.xml
Normal file
165
lib/src/main/res/layout/document_activity.xml
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true"
|
||||
>
|
||||
|
||||
<ViewAnimator
|
||||
android:id="@+id/switcher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/mainBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/toolbar"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/docNameText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="8dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/linkButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button"
|
||||
android:src="@drawable/ic_link_white_24dp"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/searchButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button"
|
||||
android:src="@drawable/ic_search_white_24dp"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/layoutButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button"
|
||||
android:src="@drawable/ic_format_size_white_24dp"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/outlineButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button"
|
||||
android:src="@drawable/ic_toc_white_24dp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/searchBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/toolbar"
|
||||
>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/searchClose"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/searchText"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center"
|
||||
android:inputType="text"
|
||||
android:imeOptions="actionSearch"
|
||||
android:singleLine="true"
|
||||
android:hint="@string/search"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textColorHighlight="#a0a0a0"
|
||||
android:textColorHint="#a0a0a0"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/searchBack"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button"
|
||||
android:src="@drawable/ic_chevron_left_white_24dp"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/searchForward"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button"
|
||||
android:src="@drawable/ic_chevron_right_white_24dp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ViewAnimator>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/lowerButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/pageSlider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_margin="0dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:background="@color/toolbar"
|
||||
android:thumb="@drawable/seek_thumb"
|
||||
android:progressDrawable="@drawable/seek_line"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pageNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/pageSlider"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/page_indicator"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
14
lib/src/main/res/menu/layout_menu.xml
Normal file
14
lib/src/main/res/menu/layout_menu.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/action_layout_6pt" android:title="6pt" />
|
||||
<item android:id="@+id/action_layout_7pt" android:title="7pt" />
|
||||
<item android:id="@+id/action_layout_8pt" android:title="8pt" />
|
||||
<item android:id="@+id/action_layout_9pt" android:title="9pt" />
|
||||
<item android:id="@+id/action_layout_10pt" android:title="10pt" />
|
||||
<item android:id="@+id/action_layout_11pt" android:title="11pt" />
|
||||
<item android:id="@+id/action_layout_12pt" android:title="12pt" />
|
||||
<item android:id="@+id/action_layout_13pt" android:title="13pt" />
|
||||
<item android:id="@+id/action_layout_14pt" android:title="14pt" />
|
||||
<item android:id="@+id/action_layout_15pt" android:title="15pt" />
|
||||
<item android:id="@+id/action_layout_16pt" android:title="16pt" />
|
||||
</menu>
|
||||
5
lib/src/main/res/values/colors.xml
Normal file
5
lib/src/main/res/values/colors.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="page_indicator">#C0202020</color>
|
||||
<color name="toolbar">#C0202020</color>
|
||||
</resources>
|
||||
14
lib/src/main/res/values/strings.xml
Normal file
14
lib/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="cannot_open_document">Cannot open document</string>
|
||||
<string name="cannot_open_document_Reason">Cannot open document: %1$s</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="enter_password">Enter password</string>
|
||||
<string name="no_further_occurrences_found">No further occurrences found</string>
|
||||
<string name="not_supported">Not supported</string>
|
||||
<string name="okay">Okay</string>
|
||||
<string name="search">Search…</string>
|
||||
<string name="searching_">Searching…</string>
|
||||
<string name="text_not_found">Text not found</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue