summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfredrossperry <fredrossperry@gmail.com>2016-06-23 13:32:36 -0700
committerfredrossperry <fredrossperry@gmail.com>2016-07-06 11:14:49 -0700
commit01d5f0bc4185c001179ca6fd36530516acc16eeb (patch)
tree53622c9ef5aea76a6e781fb18380138d9f70d9bb
parentcb894e713b2f60fef0a7e83ccb3a3d29589193bc (diff)
downloadmupdf-01d5f0bc4185c001179ca6fd36530516acc16eeb.tar.xz
Android example: modify to use new JNI, N-up page display
- uses AndroidDrawDevice for rendering - very simple sample app - mupdf-specific functionality in a module called "mupdf" - N-up page display - page rendering in a background task Signed-off-by: fredrossperry <fredrossperry@gmail.com>
-rw-r--r--platform/android/example/.gitignore10
-rw-r--r--platform/android/example/README12
-rw-r--r--platform/android/example/app/.gitignore1
-rw-r--r--platform/android/example/app/build.gradle64
-rw-r--r--platform/android/example/app/src/main/AndroidManifest.xml2
-rw-r--r--platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java93
-rw-r--r--platform/android/example/app/src/main/res/layout/activity_doc_view.xml60
-rw-r--r--platform/android/example/build.gradle27
-rw-r--r--platform/android/example/local.properties.sample5
-rw-r--r--platform/android/example/mupdf/.gitignore1
-rw-r--r--platform/android/example/mupdf/build.gradle80
-rw-r--r--platform/android/example/mupdf/src/main/AndroidManifest.xml18
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java462
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java918
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java61
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java6
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java42
-rw-r--r--platform/android/example/mupdf/src/main/java/com/artifex/mupdf/fitz/AndroidDrawDevice.java25
-rw-r--r--platform/android/example/mupdf/src/main/res/values/strings.xml2
-rw-r--r--platform/java/mupdf_native.c7
20 files changed, 1626 insertions, 270 deletions
diff --git a/platform/android/example/.gitignore b/platform/android/example/.gitignore
new file mode 100644
index 00000000..c474948d
--- /dev/null
+++ b/platform/android/example/.gitignore
@@ -0,0 +1,10 @@
+*.iml
+.gradle
+/local.properties
+/.idea/*
+.DS_Store
+/build
+/captures
+/gradle
+/gradlew
+/gradlew.bat
diff --git a/platform/android/example/README b/platform/android/example/README
deleted file mode 100644
index 9a2acc77..00000000
--- a/platform/android/example/README
+++ /dev/null
@@ -1,12 +0,0 @@
-This is a very basic example viewer using the new JNI classes.
-
-The build system is a bit incomplete.
-
-You need gradle 2.10 (exactly!).
-
-Copy the libmupdf_java.so file from the original viewer build:
- $ cd ../viewer && ndk-build
- $ cp ../viewer/libs/armeabi-v7a/libmupdf_java.so mupdf/libs/armeabi-v7a/libmupdf_java.so
-
-Build and install on device:
- $ gradle-2.10 installArmDebug
diff --git a/platform/android/example/app/.gitignore b/platform/android/example/app/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/platform/android/example/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/platform/android/example/app/build.gradle b/platform/android/example/app/build.gradle
index 5d433deb..3a1b25b3 100644
--- a/platform/android/example/app/build.gradle
+++ b/platform/android/example/app/build.gradle
@@ -1,46 +1,26 @@
-apply plugin: 'com.android.model.application'
-
-model {
-
- android {
- compileSdkVersion = 23
- buildToolsVersion = "23.0.2"
-
- defaultConfig.with {
- applicationId = "com.artifex.mupdf.example"
- minSdkVersion.apiLevel = 8
- targetSdkVersion.apiLevel = 16
- versionCode = 1
- versionName = "1.0"
- }
- }
-
- android.buildTypes {
- release {
- minifyEnabled = false
- proguardFiles.add(file('proguard-rules.pro'))
- }
- }
-
- android.productFlavors {
- create("arm") {
- ndk.with {
- // You can customize the NDK configurations for each
- // productFlavors and buildTypes.
- abiFilters.add("armeabi")
- }
-
- }
- }
-
- /* This is important, it will run lint checks but won't abort build */
- android.lintOptions {
- abortOnError false
- }
-
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ applicationId "com.artifex.mupdf.example"
+ minSdkVersion 16
+ targetSdkVersion 16
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar','*.so'])
- compile project(':mupdf')
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:23.4.0'
+ compile project(':mupdf')
}
diff --git a/platform/android/example/app/src/main/AndroidManifest.xml b/platform/android/example/app/src/main/AndroidManifest.xml
index c1dc4e52..bdd8e804 100644
--- a/platform/android/example/app/src/main/AndroidManifest.xml
+++ b/platform/android/example/app/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
package="com.artifex.mupdf.example">
<uses-sdk
- android:minSdkVersion="8"
+ android:minSdkVersion="16"
android:targetSdkVersion="16" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
diff --git a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java
index a0e989ff..32f2cecd 100644
--- a/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java
+++ b/platform/android/example/app/src/main/java/com/artifex/mupdf/example/DocViewActivity.java
@@ -1,105 +1,30 @@
package com.artifex.mupdf.example;
import android.app.Activity;
-import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-import com.artifex.mupdf.fitz.ColorSpace;
-import com.artifex.mupdf.fitz.Document;
-import com.artifex.mupdf.fitz.Matrix;
-import com.artifex.mupdf.fitz.Page;
-import com.artifex.mupdf.fitz.Pixmap;
+import com.artifex.mupdf.android.DocView;
public class DocViewActivity extends Activity
{
- private int mPageCount;
- private int mCurrentPage;
-
- Document mDocument;
- Page mPage;
- Bitmap mBitmap = null;
-
- ImageView mImageView;
- TextView mTextView;
+ private DocView mDocView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_doc_view);
- mImageView = (ImageView)findViewById(R.id.image_view);
- mTextView = (TextView)findViewById(R.id.page_text);
+ // set up UI
+ setContentView(R.layout.activity_doc_view);
+ mDocView = (DocView)findViewById(R.id.doc_view);
- // load the doc
+ // get the file path
Uri uri = getIntent().getData();
- String path = Uri.decode(uri.getEncodedPath());
- mDocument = new Document(path);
- mPageCount = mDocument.countPages();
-
- // show the first page
- mCurrentPage = 0;
- displayCurrentPage();
- }
+ final String path = Uri.decode(uri.getEncodedPath());
- public void onFirstPageButton(final View v)
- {
- mCurrentPage = 0;
- displayCurrentPage();
- }
-
- public void onPreviousPageButton(final View v)
- {
- if (mCurrentPage > 0)
- {
- mCurrentPage--;
- displayCurrentPage();
- }
+ // start the view
+ mDocView.start(path);
}
- public void onNextPageButton(final View v)
- {
- if (mCurrentPage < mPageCount-1)
- {
- mCurrentPage++;
- displayCurrentPage();
- }
- }
-
- public void onLastPageButton(final View v)
- {
- mCurrentPage = mPageCount-1;
- displayCurrentPage();
- }
-
- private void displayCurrentPage()
- {
- // report the page number
- mTextView.setText(String.format("page %d of %d",mCurrentPage+1,mPageCount));
-
- // get the page
- mPage = mDocument.loadPage(mCurrentPage);
-
- // create a matrix that renders at 300 DPI
- Matrix m = new Matrix();
- m.scale(300.0f/72.0f);
-
- // create a new bitmap for the page
- Bitmap old = mBitmap;
- Pixmap pixmap = mPage.toPixmap(m, ColorSpace.DeviceBGR);
- mBitmap = Bitmap.createBitmap(pixmap.getWidth(), pixmap.getHeight(), Bitmap.Config.ARGB_8888);
- int [] pixels = pixmap.getPixels();
- mBitmap.setPixels(pixels, 0, pixmap.getWidth(), 0, 0, pixmap.getWidth(), pixmap.getHeight());
-
- // set the bitmap in the UI
- mImageView.setImageBitmap(mBitmap);
-
- // recycle the old bitmap
- if (old!=null)
- old.recycle();
- }
}
diff --git a/platform/android/example/app/src/main/res/layout/activity_doc_view.xml b/platform/android/example/app/src/main/res/layout/activity_doc_view.xml
index 05cee21a..7f5ece24 100644
--- a/platform/android/example/app/src/main/res/layout/activity_doc_view.xml
+++ b/platform/android/example/app/src/main/res/layout/activity_doc_view.xml
@@ -4,64 +4,10 @@
android:layout_height="match_parent"
android:orientation="vertical">
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="|&lt;-"
- android:id="@+id/first_page_button"
- android:onClick="onFirstPageButton"
- android:minWidth="60dp" />
-
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="&lt;"
- android:id="@+id/previous_page_button"
- android:onClick="onPreviousPageButton"
- android:minWidth="60dp" />
-
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text=">"
- android:id="@+id/next_page_button"
- android:onClick="onNextPageButton"
- android:minWidth="60dp" />
-
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="->|"
- android:id="@+id/last_page_button"
- android:onClick="onLastPageButton"
- android:minWidth="60dp" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="Medium Text"
- android:textColor="@color/white"
- android:id="@+id/page_text" />
-
- </LinearLayout>
-
- <LinearLayout
- android:orientation="vertical"
+ <com.artifex.mupdf.android.DocView
+ android:id="@+id/doc_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
-
- <ImageView
- android:id="@+id/image_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- </ImageView>
-
- </LinearLayout>
+ </com.artifex.mupdf.android.DocView>
</LinearLayout>
diff --git a/platform/android/example/build.gradle b/platform/android/example/build.gradle
index d92ef895..ec337e49 100644
--- a/platform/android/example/build.gradle
+++ b/platform/android/example/build.gradle
@@ -1,18 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle-experimental:0.6.0-alpha9'
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.2'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
}
allprojects {
- repositories {
- jcenter()
- }
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
}
diff --git a/platform/android/example/local.properties.sample b/platform/android/example/local.properties.sample
deleted file mode 100644
index bc40e6ef..00000000
--- a/platform/android/example/local.properties.sample
+++ /dev/null
@@ -1,5 +0,0 @@
-# Uncomment and edit the appropriate line below.
-# Resave this file as local.properties.
-
-sdk.dir=/path/to/android/sdk
-ndk.dir=/path/to/android/ndk
diff --git a/platform/android/example/mupdf/.gitignore b/platform/android/example/mupdf/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/platform/android/example/mupdf/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/platform/android/example/mupdf/build.gradle b/platform/android/example/mupdf/build.gradle
index 0add44db..44a5a895 100644
--- a/platform/android/example/mupdf/build.gradle
+++ b/platform/android/example/mupdf/build.gradle
@@ -1,52 +1,38 @@
-apply plugin: 'com.android.model.library'
-
-model {
-
- android {
- compileSdkVersion = 23
- buildToolsVersion = "23.0.2"
- }
-
- android.buildTypes {
- release {
- minifyEnabled = false
- proguardFiles.add(file('proguard-rules.pro'))
- }
- }
-
- android.sources {
- main {
- java {
- source {
- srcDir '../../../java'
- exclude 'example'
- }
- }
- jniLibs {
- dependencies {
- library "libmupdf_java"
- }
- }
- }
- }
-
- repositories {
- prebuilt(PrebuiltLibraries) {
- libmupdf_java {
- binaries.withType(SharedLibraryBinary) {
- sharedLibraryFile = file("libs/armeabi-v7a/libmupdf_java.so")
- }
- }
- }
- }
-
- /* This is important, it will run lint checks but won't abort build */
- android.lintOptions {
- abortOnError false
- }
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 16
+ versionCode 1
+ versionName "1.0"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main {
+ java {
+ // we're getting java sources from two places
+ srcDirs = ["src/main/java", "../../../java/com"]
+ exclude "example/*"
+ }
+ jni.srcDirs = [] // This prevents the auto generation of Android.mk
+ jniLibs.srcDir 'libs' // where to find the .so file(s)
+ }
+ }
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar','*.so'])
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:23.4.0'
}
diff --git a/platform/android/example/mupdf/src/main/AndroidManifest.xml b/platform/android/example/mupdf/src/main/AndroidManifest.xml
index ee014902..af5dbc58 100644
--- a/platform/android/example/mupdf/src/main/AndroidManifest.xml
+++ b/platform/android/example/mupdf/src/main/AndroidManifest.xml
@@ -1,12 +1,16 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+<manifest package="com.artifex.mupdf.mupdf"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
package="com.artifex.mupdf.fitz">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
- <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16"/>
- <application
- android:allowBackup="true"
- android:label="@string/app_name"
- android:supportsRtl="true">
- </application>
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ >
+
+ </application>
+
</manifest>
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java
new file mode 100644
index 00000000..5ab52646
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java
@@ -0,0 +1,462 @@
+package com.artifex.mupdf.android;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.view.KeyEvent.Callback;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.artifex.mupdf.fitz.AndroidDrawDevice;
+import com.artifex.mupdf.fitz.Cookie;
+import com.artifex.mupdf.fitz.DisplayList;
+import com.artifex.mupdf.fitz.DisplayListDevice;
+import com.artifex.mupdf.fitz.Document;
+import com.artifex.mupdf.fitz.Matrix;
+import com.artifex.mupdf.fitz.Page;
+
+public class DocPageView extends View implements Callback
+{
+ private final Document mDoc;
+ private int mPageNum = -1;
+ private Page mPage;
+ private boolean mFinished = false;
+
+ private float mScale = 1.0f;
+ private float mZoom = 1.0f;
+
+ // drawing bitmap
+ private Bitmap mDrawBitmap = null;
+ private Rect mDrawSrcRect = new Rect();
+ private final Rect mDrawDstRect = new Rect();
+ private final Rect sourceRect = new Rect();
+ private final Rect renderRect = new Rect();
+ private float renderScale;
+
+ // current size of this view
+ private Point mSize;
+
+ // for drawing
+ private final Paint mPainter;
+ private final Rect mSrcRect = new Rect();
+ private final Rect mDstRect = new Rect();
+ private float drawScale;
+
+ private static final boolean DEBUG_PAGE_RENDERING = false;
+
+ private static final float mResolution = 160f;
+
+ DisplayList pageContents = null;
+ DisplayList annotContents = null;
+
+ public DocPageView(Context context, Document theDoc)
+ {
+ super(context);
+ setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ mDoc = theDoc;
+ mPainter = new Paint();
+
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ }
+
+ public void setupPage(final int thePageNum, int w, int h)
+ {
+ // if the page number has not yet been set, or has changed,
+ // make a new page object.
+ if (thePageNum != mPageNum)
+ {
+ mPageNum = thePageNum;
+
+ // de-cache contents and annotations
+ if (pageContents != null) {
+ pageContents.destroy();
+ pageContents = null;
+ }
+ if (annotContents != null) {
+ annotContents.destroy();
+ annotContents = null;
+ }
+
+ // destroy the page before making a new one.
+ if (mPage!=null)
+ mPage.destroy();
+ mPage = mDoc.loadPage(mPageNum);
+ }
+
+ // calculate zoom that makes page fit
+
+ com.artifex.mupdf.fitz.Rect pageBounds = mPage.getBounds();
+
+ float pagew = (pageBounds.x1 - pageBounds.x0)*mResolution/72f;
+ float pageH = (pageBounds.y1 - pageBounds.y0)*mResolution/72f;
+
+ mZoom = w/pagew;
+ mSize = new Point((int)(pagew*mZoom), (int)(pageH*mZoom));
+ }
+
+ public void setNewScale(float scale) {
+ mScale = scale;
+ }
+
+ public int getCalculatedWidth()
+ {
+ return (int)(mSize.x * mScale);
+ }
+
+ public int getCalculatedHeight()
+ {
+ return (int)(mSize.y * mScale);
+ }
+
+ // a test for real visibility
+ private static final Rect visRect = new Rect();
+ public boolean isReallyVisible() {
+ return getLocalVisibleRect(visRect);
+ }
+
+ // This function renders colored rectangles and text in place of the page.
+ // Use it to test layouts.
+ private void renderNoPage(Bitmap bitmap, final RenderListener listener, Rect localVisRect, Rect globalVisRect)
+ {
+ // specify where to draw to and from
+ mDrawBitmap = bitmap;
+ mDrawSrcRect.set(globalVisRect);
+ mDrawDstRect.set(localVisRect);
+
+ // make a rect representing the entire page in screen coordinates
+ int[] locations = new int[2];
+ getLocationOnScreen(locations);
+ Rect pageRect = new Rect(locations[0], locations[1], locations[0]+getWidth(), locations[1]+getHeight());
+
+ // draw a yellow page with a red border containing the page number
+
+ Paint p = new Paint();
+ Canvas c = new Canvas(bitmap);
+ p.setColor(Color.RED);
+ p.setStyle(Paint.Style.FILL);
+ c.drawRect(pageRect,p);
+
+ Rect smaller = new Rect(pageRect);
+ int inset = (int)(40*mScale);
+ smaller.inset(inset, inset);
+ p.setColor(Color.YELLOW);
+ p.setStyle(Paint.Style.FILL);
+ c.drawRect(smaller,p);
+
+ String s = "" + (mPageNum+1);
+ p.setColor(Color.BLACK);
+ p.setTextSize(200.0f*mScale);
+ c.drawText(s, pageRect.left+(90*mScale), pageRect.top+(290*mScale), p);
+
+ invalidate();
+ listener.progress(0);
+ }
+
+ public void render(Bitmap bitmap, final RenderListener listener)
+ {
+ if (mFinished)
+ return;
+
+ // get local visible rect
+ Rect localVisRect = new Rect();
+ if (!getLocalVisibleRect(localVisRect)) {
+ listener.progress(0);
+ return; // not visible
+ }
+
+ // get global visible rect
+ Rect globalVisRect = new Rect();
+ if (!getGlobalVisibleRect(globalVisRect)) {
+ listener.progress(0);
+ return; // not visible
+ }
+
+ // do the render.
+ if (DEBUG_PAGE_RENDERING)
+ renderNoPage(bitmap, listener, localVisRect, globalVisRect);
+ else
+ renderPage(bitmap, listener, localVisRect, globalVisRect);
+ }
+
+ // This function renders the document's page.
+ private void renderPage(final Bitmap bitmap, final RenderListener listener, final Rect localVisRect, final Rect globalVisRect)
+ {
+ // make a rect representing the entire page
+ // This might be outside the bounds of the bitmap
+ int[] locations = new int[2];
+ getLocationOnScreen(locations);
+ Rect pageRect = new Rect(locations[0], locations[1], locations[0]+getCalculatedWidth(), locations[1]+getCalculatedHeight());
+
+ // make a rect representing the patch
+ // clip this to the bitmap
+ Rect patchRect = new Rect(pageRect);
+ patchRect.left = Math.max(patchRect.left,0);
+ patchRect.top = Math.max(patchRect.top,0);
+ patchRect.right = Math.min(patchRect.right,bitmap.getWidth());
+ patchRect.bottom = Math.min(patchRect.bottom,bitmap.getHeight());
+
+ // set up the page and patch coordinates for the device
+ int pageX0 = pageRect.left;
+ int pageY0 = pageRect.top;
+ int pageX1 = pageRect.right;
+ int pageY1 = pageRect.bottom;
+
+ int patchX0 = patchRect.left;
+ int patchY0 = patchRect.top;
+ int patchX1 = patchRect.right;
+ int patchY1 = patchRect.bottom;
+
+ // set up a matrix for scaling
+ Matrix ctm = Matrix.Identity();
+ ctm.scale(mScale*mZoom*mResolution/72f);
+
+ // cache this page's display and annotation lists
+ cachePage();
+
+ sourceRect.set(globalVisRect);
+ renderRect.set(localVisRect);
+ renderScale = mScale;
+
+ // Render the page in the background
+ DrawTaskParams params = new DrawTaskParams(new RenderListener() {
+ @Override
+ public void progress(int error)
+ {
+ // specify where to draw to and from
+ mDrawBitmap = bitmap;
+ mDrawSrcRect.set(sourceRect);
+ mDrawDstRect.set(renderRect);
+ drawScale = renderScale;
+
+ invalidate();
+ listener.progress(0);
+ }
+ }, ctm, bitmap, pageX0, pageY0, pageX1, pageY1, patchX0, patchY0, patchX1, patchY1);
+
+ new DrawTask().execute(params, null, null);
+ }
+
+ private void cachePage()
+ {
+ Cookie cookie = new Cookie();
+
+ if (pageContents==null)
+ {
+ // run the display list
+ pageContents = new DisplayList();
+ DisplayListDevice dispDev = new DisplayListDevice(pageContents);
+ try {
+ mPage.run(dispDev, new Matrix(1, 0, 0, 1, 0, 0), cookie);
+ }
+ catch (RuntimeException e) {
+ pageContents.destroy();
+ dispDev.destroy();
+ throw(e);
+ }
+ finally {
+ dispDev.destroy();
+ }
+ }
+
+ if (annotContents==null)
+ {
+ // run the annotation list
+ annotContents = new DisplayList();
+ DisplayListDevice annotDev = new DisplayListDevice(annotContents);
+ try {
+ mPage.run(annotDev, new Matrix(1, 0, 0, 1, 0, 0), cookie);
+ }
+ catch (RuntimeException e) {
+ annotContents.destroy();
+ annotDev.destroy();
+ throw(e);
+ }
+ finally {
+ annotDev.destroy();
+ }
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+
+ if (mFinished)
+ return;
+
+ // get bitmap to draw
+ Bitmap bitmap = mDrawBitmap;
+ if (bitmap==null)
+ return; // not yet rendered
+
+ // set rectangles for drawing
+ mSrcRect.set(mDrawSrcRect);
+ mDstRect.set(mDrawDstRect);
+
+ // if the scale has changed, adjust the destination
+ if (drawScale != mScale)
+ {
+ mDstRect.left *= (mScale/drawScale);
+ mDstRect.top *= (mScale/drawScale);
+ mDstRect.right *= (mScale/drawScale);
+ mDstRect.bottom *= (mScale/drawScale);
+ }
+
+ // draw
+ canvas.drawBitmap(bitmap, mSrcRect, mDstRect, mPainter);
+ }
+
+ public boolean onSingleTap(int x, int y) {
+ // NOTE: when double-tapping, a single-tap will also happen first.
+ // so that must be safe to do.
+ return false;
+ }
+
+ public void onDoubleTap(int x, int y) {
+ }
+
+ private Point screenToPage(Point p)
+ {
+ return screenToPage(p.x, p.y);
+ }
+
+ private Point screenToPage(int screenX, int screenY)
+ {
+ // convert to view-relative
+ int viewX = screenX;
+ int viewY = screenY;
+ int loc[] = new int[2];
+ getLocationOnScreen(loc);
+ viewX -= loc[0];
+ viewY -= loc[1];
+
+ // convert to page-relative
+ double factor = mZoom * mScale;
+ int pageX = (int)(((double)viewX)/factor);
+ int pageY = (int)(((double)viewY)/factor);
+
+ return new Point(pageX,pageY);
+ }
+
+ public Point pageToView(int pageX, int pageY)
+ {
+ double factor = mZoom * mScale;
+
+ int viewX = (int)(((double)pageX)*factor);
+ int viewY = (int)(((double)pageY)*factor);
+
+ return new Point(viewX, viewY);
+ }
+
+ public Point viewToPage(int viewX, int viewY)
+ {
+ double factor = mZoom * mScale;
+
+ int pageX = (int)(((double)viewX)/factor);
+ int pageY = (int)(((double)viewY)/factor);
+
+ return new Point(pageX, pageY);
+ }
+
+ public void finish()
+ {
+ mFinished = true;
+
+ // destroy the page
+ if (mPage!=null) {
+ mPage.destroy();
+ mPage = null;
+ }
+ }
+
+ // during layout, a DocView-relative rect is calculated and stashed here.
+ private final Rect mChildRect = new Rect();
+ public void setChildRect(Rect r) {mChildRect.set(r);}
+ public Rect getChildRect() {return mChildRect;}
+
+ private class DrawTaskParams {
+ DrawTaskParams (RenderListener listener, Matrix ctm, Bitmap bitmap,
+ int pageX0, int pageY0, int pageX1, int pageY1,
+ int patchX0, int patchY0, int patchX1, int patchY1)
+ {
+ this.listener = listener;
+ this.ctm = ctm;
+ this.bitmap = bitmap;
+ this.pageX0 = pageX0;
+ this.pageY0 = pageY0;
+ this.pageX1 = pageX1;
+ this.pageY1 = pageY1;
+ this.patchX0 = patchX0;
+ this.patchY0 = patchY0;
+ this.patchX1 = patchX1;
+ this.patchY1 = patchY1;
+ }
+
+ public RenderListener listener;
+ public Matrix ctm;
+ public Bitmap bitmap;
+ public int pageX0;
+ public int pageY0;
+ public int pageX1;
+ public int pageY1;
+ public int patchX0;
+ public int patchY0;
+ public int patchX1;
+ public int patchY1;
+ }
+
+ // The definition of our task class
+ private class DrawTask extends AsyncTask<DrawTaskParams, Void, Void>
+ {
+ private DrawTaskParams params = null;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ }
+
+ @Override
+ protected Void doInBackground(DrawTaskParams... paramList) {
+ params = paramList[0];
+
+ Cookie cookie = new Cookie();
+ AndroidDrawDevice dev = new AndroidDrawDevice(params.bitmap, params.pageX0, params.pageY0, params.pageX1, params.pageY1, params.patchX0, params.patchY0, params.patchX1, params.patchY1);
+ try {
+ if (pageContents != null) {
+ pageContents.run(dev, params.ctm, cookie);
+ }
+ if (annotContents != null) {
+ annotContents.run(dev, params.ctm, cookie);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.e("mupdf", e.getMessage());
+ }
+ finally {
+ dev.destroy();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(Void... values) {
+ super.onProgressUpdate(values);
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ params.listener.progress(0);
+ }
+ }
+
+}
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java
new file mode 100644
index 00000000..34613dc3
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java
@@ -0,0 +1,918 @@
+package com.artifex.mupdf.android;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.SparseArray;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.Scroller;
+
+import com.artifex.mupdf.fitz.Document;
+
+public class DocView
+ extends AdapterView<Adapter>
+ implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener, Runnable
+{
+ private static final int GAP = 20;
+
+ private static final float MIN_SCALE = .15f;
+ private static final float MAX_SCALE = 5.0f;
+
+ private PageAdapter mAdapter;
+ private boolean mFinished = false;
+
+ private final SparseArray<View> mChildViews = new SparseArray<View>(3);
+
+ 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;
+
+ // bitmaps for rendering
+ // these are created by the activity and set using setBitmaps()
+ private final static double OVERSIZE_FACTOR = 1.1;
+ private final Bitmap[] bitmaps = {null,null};
+
+ private int bitmapIndex = 0;
+ private boolean renderRequested = false;
+ private int renderCount = 0;
+
+ // used during layout
+ private final Rect mChildRect = new Rect();
+ private final Rect mViewport = new Rect();
+ private final Point mViewportOrigin = new Point();
+ private final Rect mBlockRect = new Rect();
+ private final Rect mLastBlockRect = new Rect();
+ private int mLastLayoutColumns = 1;
+ protected int mPageCollectionHeight;
+ private int mPageCollectionWidth;
+
+ // for flinging
+ 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 float MIN_FLING_VELOCITY = 1500.0f;
+ private static final long FLING_THROTTLE_TIME = 20;
+
+ private Scroller mScroller;
+ private Stepper mStepper;
+ private int mScrollerLastX;
+ private int mScrollerLastY;
+ private long mFlingStartTime;
+
+ // for single- and double-tapping
+ private long mLastTapTime = 0;
+ private float lastTapX;
+ private float lastTapY;
+ private int mTapStatus = 0;
+
+ private static final int DOUBLE_TAP_TIME = 300;
+ private static final int SHOW_KEYBOARD_TIME = 500;
+
+ // the document.
+ private Document mDoc;
+
+ private boolean mStarted = false;
+
+ public DocView(Context context) {
+ super(context);
+ initialize(context);
+ }
+
+ public DocView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize(context);
+ }
+
+ public DocView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initialize(context);
+ }
+
+ protected Context mContext = null;
+
+ protected void initialize(Context context)
+ {
+ mContext = context;
+ mGestureDetector = new GestureDetector(context, this);
+ mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ mScroller = new Scroller(context);
+ mStepper = new Stepper(this, this);
+
+ // create bitmaps
+ makeBitmaps();
+ }
+
+ private void makeBitmaps()
+ {
+ // get current screen size
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ wm.getDefaultDisplay().getMetrics(metrics);
+ int screenW = metrics.widthPixels;
+ int screenH = metrics.heightPixels;
+
+ // make two bitmaps.
+ // make them large enough for both screen orientations, so we don't have to
+ // change them when the orientation changes.
+
+ int w = (int)(screenW*OVERSIZE_FACTOR);
+ int h = (int)(screenH*OVERSIZE_FACTOR);
+ int size = Math.max(w,h);
+ for (int i=0;i<bitmaps.length;i++)
+ bitmaps[i] = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ }
+
+ public void start(final String path)
+ {
+ // wait for the layout to finish
+ ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
+ {
+ @Override
+ public void onGlobalLayout()
+ {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+
+ mAdapter = new PageAdapter(mContext);
+ mAdapter.setWidth(getWidth());
+ mDoc = new Document(path);
+ mAdapter.setDocument(mDoc);
+ mScale = 1.0f;
+ mStarted = true;
+ triggerRender();
+ }
+ });
+ }
+
+ private void onScaleChild(View v, Float scale)
+ {
+ ((DocPageView)v).setNewScale(scale);
+ }
+
+ public void onOrientationChange()
+ {
+ triggerRender();
+ }
+
+ private void onSizeChange(float factor)
+ {
+ mScale *= factor;
+ scaleChildren();
+ requestLayout();
+ }
+
+ public boolean onDown(MotionEvent arg0) {
+ mScroller.forceFinished(true);
+ return true;
+ }
+
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+
+ // not while we're scaling
+ if (mScaling)
+ return true;
+
+ // not while a previous fling is underway
+ if (!mScroller.isFinished())
+ return true;
+
+ // must be really flinging
+ float vel = Math.max(Math.abs(velocityX),Math.abs(velocityY));
+ if (vel<MIN_FLING_VELOCITY)
+ return false;
+
+ // what direction?
+ int direction = directionOfTravel(velocityX,velocityY);
+
+ mFlingStartTime = System.currentTimeMillis();
+
+ switch (direction)
+ {
+ case MOVING_DOWN:
+ smoothScrollBy(0, getHeight()/2);
+ break;
+
+ case MOVING_UP:
+ smoothScrollBy(0, -getHeight()/2);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ 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;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ }
+
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+
+ // not if we're scaling
+ if (mScaling)
+ return true;
+
+ // not if a previous fling is underway
+ if (!mScroller.isFinished())
+ return true;
+
+ // accumulate scrolling amount.
+ mXScroll -= distanceX;
+ mYScroll -= distanceY;
+
+ requestLayout();
+
+ return true;
+ }
+
+ public void onShowPress(MotionEvent e) {
+ }
+
+ protected DocPageView findPageViewContainingPoint(int x, int y, boolean includeMargin)
+ {
+ for (int i = 0; i < getChildCount(); i++)
+ {
+ // get the rect for the page
+ View child = getChildAt(i);
+ Rect childRect = new Rect();
+ child.getGlobalVisibleRect(childRect);
+
+ // add in the margin
+ if (includeMargin)
+ {
+ childRect.left -= GAP/2;
+ childRect.right += GAP/2;
+ childRect.top -= GAP/2;
+ childRect.bottom += GAP/2;
+ }
+
+ // see if the rect contains the point
+ if (childRect.contains(x,y))
+ return (DocPageView)child;
+ }
+
+ return null;
+ }
+
+ protected Point eventToScreen(float fx, float fy)
+ {
+ int x = Math.round(fx);
+ int y = Math.round(fy);
+ Rect docRect = new Rect();
+ getGlobalVisibleRect(docRect);
+ x += docRect.left;
+ y += docRect.top;
+
+ return new Point(x,y);
+ }
+
+ protected void doSingleTap(float fx, float fy)
+ {
+ // find the page view that was tapped.
+ Point p = eventToScreen(fx,fy);
+ final DocPageView dpv = findPageViewContainingPoint(p.x, p.y, false);
+ if (dpv==null)
+ return;
+
+ // see if the age wants to handle the single tap
+ boolean handled = dpv.onSingleTap(p.x,p.y);
+
+ // if not, ...
+ if (!handled)
+ {
+ // schedule a task in the near future to check if we're still a single-tap.
+ final Handler handler = new Handler();
+ final Point tappedPoint = p;
+ handler.postDelayed(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if (mTapStatus==1)
+ {
+ // still single
+ }
+ else
+ {
+ // double
+ }
+ mTapStatus = 0;
+ }
+ }, SHOW_KEYBOARD_TIME);
+ }
+ }
+
+ protected void doDoubleTap(float fx, float fy)
+ {
+ Point p = eventToScreen(fx,fy);
+ DocPageView v = findPageViewContainingPoint(p.x, p.y, false);
+ if (v != null) {
+ v.onDoubleTap(p.x,p.y);
+ }
+ }
+
+ public boolean onSingleTapUp(final MotionEvent e)
+ {
+ long now = System.currentTimeMillis();
+ if (mLastTapTime!=0 && ((now-mLastTapTime)<DOUBLE_TAP_TIME))
+ {
+ mTapStatus = 2;
+ doDoubleTap(lastTapX,lastTapY);
+ mLastTapTime = 0;
+ }
+ else
+ {
+ mLastTapTime = now;
+ lastTapX = e.getX();
+ lastTapY = e.getY();
+ doSingleTap(lastTapX, lastTapY);
+ mTapStatus = 1;
+ }
+
+ return false;
+ }
+
+ private void scaleChildren()
+ {
+ // scale children
+ for (int i=0; i<getPageCount(); i++)
+ {
+ DocPageView cv = (DocPageView)getOrCreateChild(i);
+ cv.setNewScale(mScale);
+ }
+ }
+
+ public boolean onScale(ScaleGestureDetector detector)
+ {
+ // new scale factor
+ float previousScale = mScale;
+ mScale = Math.min(Math.max(mScale * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE);
+
+ // did we really scale?
+ if (mScale == previousScale)
+ return true;
+
+ // scale children
+ scaleChildren();
+
+ // maintain focus while scaling
+ float currentFocusX = detector.getFocusX();
+ float currentFocusY = detector.getFocusY();
+ int viewFocusX = (int)currentFocusX + getScrollX();
+ int viewFocusY = (int)currentFocusY + getScrollY();
+ mXScroll += viewFocusX - viewFocusX * detector.getScaleFactor();
+ mYScroll += viewFocusY - viewFocusY * detector.getScaleFactor();
+
+ requestLayout();
+
+ return true;
+ }
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+
+ mScaling = true;
+
+ // Ignore any scroll amounts yet to be accounted for: the
+ // screen is not showing the effect of them, so they can
+ // only confuse the user
+ mXScroll = mYScroll = 0;
+
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector)
+ {
+ // When a pinch-scale is done, we want to get n-across
+ // to fit properly.
+
+ // get current viewport
+ Rect viewport = new Rect();
+ getGlobalVisibleRect(viewport);
+
+ // if we're at one column and wider than the viewport,
+ // leave it alone.
+ if (mLastLayoutColumns==0 && mPageCollectionWidth>=viewport.width())
+ {
+ mScaling = false;
+ return;
+ }
+
+ // ratio of the viewport width to layout width
+ float ratio = ((float)(viewport.width()))/((float)(mPageCollectionWidth));
+
+ // set a new scale factor
+ mScale *= ratio;
+ scaleChildren();
+
+ // scroll so the left edged is flush to the viewport.
+ mXScroll +=getScrollX();
+
+ requestLayout();
+
+ mScaling = false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+
+ if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
+ // do something when user interaction begins
+ }
+
+ if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
+ // do something when user interaction ends
+ triggerRender();
+ }
+
+ mScaleGestureDetector.onTouchEvent(event);
+ mGestureDetector.onTouchEvent(event);
+
+ return true;
+ }
+
+ protected int getPageCount()
+ {
+ return getAdapter().getCount();
+ }
+
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (!mStarted)
+ return;
+
+ // not if there are no pages
+ if (getPageCount()==0)
+ return;
+
+ int numDocPages = getPageCount();
+
+ // not if we've been finished
+ if (finished())
+ return;
+
+ // perform any pending scrolling
+ scrollBy(-mXScroll, -mYScroll);
+ mXScroll = mYScroll = 0;
+
+ // get current viewport
+ mViewportOrigin.set(getScrollX(), getScrollY());
+ getGlobalVisibleRect(mViewport);
+ mViewport.offsetTo(mViewportOrigin.x, mViewportOrigin.y);
+
+ // find the widest child
+ int maxw = 0;
+ for (int i=0; i<getPageCount(); i++)
+ {
+ DocPageView cv = (DocPageView)getOrCreateChild(i);
+
+ int childWidth = cv.getCalculatedWidth();
+ if (childWidth>maxw)
+ maxw = childWidth;
+ }
+
+ // how many columns?
+ double dcol = (double)(mViewport.width()+GAP)/(double)(maxw+GAP);
+ int columns = (int) dcol;
+
+ // lay them out
+ int mostVisibleChildHeight = -1;
+ int childTop = 0;
+ mPageCollectionHeight = 0;
+ mPageCollectionWidth = 0;
+ int column = 0;
+ mBlockRect.setEmpty();
+
+ for (int i=0; i<getPageCount(); i++)
+ {
+ DocPageView cv = (DocPageView)getOrCreateChild(i);
+ int childWidth = cv.getCalculatedWidth();
+ int childHeight = cv.getCalculatedHeight();
+ int childLeft = column * (maxw + GAP);
+ int childRight = childLeft + childWidth;
+ int childBottom = childTop + childHeight;
+ mChildRect.set(childLeft, childTop, childRight, childBottom);
+
+ // stash the rect in the page view for later use.
+ cv.setChildRect(mChildRect);
+
+ // at each layout, we remember the entire width and height of the laid-out
+ // pages. This is used in applying constraints to scrolling amounts.
+ if (childBottom> mPageCollectionHeight)
+ mPageCollectionHeight = childBottom;
+ if (childRight>mPageCollectionWidth)
+ mPageCollectionWidth = childRight;
+
+ if (mBlockRect.isEmpty())
+ mBlockRect.set(mChildRect);
+ else
+ mBlockRect.union(mChildRect);
+
+ if (mChildRect.intersect(mViewport) && i<numDocPages)
+ {
+ // visible, so include in layout
+ if (cv.getParent()==null)
+ addChildToLayout(cv);
+ cv.layout(childLeft, childTop, childRight, childBottom);
+ cv.invalidate();
+ }
+ else
+ {
+ // not visible, so remove from layout
+ removeViewInLayout(cv);
+ }
+
+ column++;
+ if (column >= columns) {
+ column = 0;
+ childTop += childHeight;
+ childTop += GAP;
+ }
+ }
+
+ // if the number of columns has changed, do some scrolling to adjust
+ if (mScaling && columns>=1 && mLastLayoutColumns>=1 && mLastLayoutColumns!=columns)
+ {
+ // x - center in the viewport
+ int dx = mBlockRect.centerX() - mViewport.centerX();
+ scrollBy(dx,0);
+
+ // y - attempt to keep what's in the center of the viewport in view.
+ int oldy = mViewport.centerY() - mLastBlockRect.top;
+ int newy = (int)((float)oldy*mBlockRect.height()/mLastBlockRect.height());
+ scrollBy(0,newy-oldy);
+ }
+ mLastLayoutColumns = columns;
+ mLastBlockRect.set(mBlockRect);
+
+ // see if we're handling a start page
+ handleStartPage();
+
+ triggerRender();
+ }
+
+ // start page, get and set.
+ private int mStartPage = 0;
+ public void setStartPage(int page) {
+ mStartPage = page;
+ }
+ protected int getStartPage() {return mStartPage;}
+
+ // handle start page
+ public void handleStartPage()
+ {
+ // if we've been given a start page, go there.
+ final int start = getStartPage();
+ if (start>0)
+ {
+ setStartPage(0); // but just once
+
+ // post all of this so that we get an additional layout request
+ final Handler handler = new Handler();
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ DocPageView cv = (DocPageView)getOrCreateChild(start-1);
+ Rect r = cv.getChildRect();
+ scrollBy(0,r.top);
+ requestLayout();
+ }
+ });
+ }
+ }
+
+ // override the view's scrollBy() function so we can
+ // take the opportunity to apply some constraints
+
+ @Override
+ public void scrollBy(int dx, int dy)
+ {
+ Point p = constrainScrollBy(dx, dy);
+ super.scrollBy(p.x, p.y);
+ }
+
+ // apply contraints to every scroll request.
+
+ protected Point constrainScrollBy(int dx, int dy)
+ {
+ int vph;
+ int vpw;
+ {
+ Rect viewport = new Rect();
+ getGlobalVisibleRect(viewport);
+ vph = viewport.height();
+ vpw = viewport.width();
+ }
+ int sx = getScrollX();
+ int sy = getScrollY();
+
+ if (mPageCollectionWidth <= vpw)
+ {
+ // not too far to the right
+ if (mPageCollectionWidth-sx-dx > vpw)
+ dx = 0;
+
+ // not too far to the left
+ if (sx+dx>0)
+ dx = -sx;
+ }
+ else
+ {
+ // not too far to the right
+ if (mPageCollectionWidth < sx+vpw+dx)
+ dx = 0;
+
+ // not too far to the left
+ if (sx+dx < 0)
+ dx = -sx;
+ }
+
+ if (mPageCollectionHeight <= vph)
+ {
+ // not too far down
+ if (mPageCollectionHeight-sy-dy > vph)
+ dy = 0;
+
+ // not too far up
+ if (sy+dy>0)
+ dy = -sy;
+ }
+ else
+ {
+ // not too far down
+ if (sy+dy < 0)
+ dy = -sy;
+
+ // not too far up.
+ if (mPageCollectionHeight+2*vph/3 < sy+vph+dy)
+ dy = 0;
+ }
+
+ return new Point(dx, dy);
+ }
+
+ @Override
+ public Adapter getAdapter() {
+ return mAdapter;
+ }
+
+ @Override
+ public View getSelectedView() {
+ return null;
+ }
+
+ @Override
+ public void setAdapter(Adapter adapter) {
+ mAdapter = (PageAdapter)adapter;
+ requestLayout();
+ }
+
+ @Override
+ public void setSelection(int arg0) {
+ throw new UnsupportedOperationException("setSelection is not supported");
+ }
+
+ private View getCached() {
+ return null;
+ }
+
+ protected View getOrCreateChild(int i) {
+ View v = mChildViews.get(i);
+ if (v == null) {
+ v = getViewFromAdapter(i);
+ mChildViews.append(i, v); // Record the view against it's adapter index
+ onScaleChild(v, mScale);
+ }
+
+ return v;
+ }
+
+ protected View getViewFromAdapter(int index)
+ {
+ return getAdapter().getView(index, getCached(), this);
+ }
+
+ private void addChildToLayout(View v) {
+ LayoutParams params = v.getLayoutParams();
+ if (params == null) {
+ params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+ addViewInLayout(v, 0, params, true);
+ }
+
+ private void triggerRender()
+ {
+ // Note that a render is needed
+ renderRequested = true;
+
+ // If the previous render has completed, start a new one. Otherwise
+ // a new one will start as soon as the previous one completes.
+ if (renderCount == 0)
+ renderPages();
+ }
+
+ private void renderPages()
+ {
+ renderRequested = false;
+
+ if (mFinished)
+ return;
+
+ if (bitmaps == null)
+ return;
+
+ // Rotate to the next bitmap
+ bitmapIndex++;
+ if (bitmapIndex>=bitmaps.length)
+ bitmapIndex = 0;
+
+ // iterate through the children
+ for (int i = 0; i < getPageCount(); i++) {
+
+ if (mFinished)
+ return;
+
+ final DocPageView cv = (DocPageView) getOrCreateChild(i);
+ if (cv.getParent()!=null && cv.isReallyVisible()) {
+ // Count up as we kick off rendering of each visible page
+ renderCount++;
+ cv.render(bitmaps[bitmapIndex], new RenderListener() {
+ @Override
+ public void progress(int error) {
+
+ if (error==0)
+ cv.invalidate();
+
+ // Count down as they complete
+ renderCount--;
+
+ if (renderCount==0) {
+ if (renderRequested) {
+ // If this phase of rendering has completed and another has
+ // been requested, start it now
+ renderPages();
+ }
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ 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;
+
+ // limit the amount of repeated layouts.
+ long tNow = System.currentTimeMillis();
+ long diff = tNow - mFlingStartTime;
+ if (diff>FLING_THROTTLE_TIME)
+ {
+ requestLayout();
+ mFlingStartTime = tNow;
+ }
+
+ mStepper.prod();
+ }
+ else
+ {
+ // one more
+ long tNow = System.currentTimeMillis();
+ if (tNow != mFlingStartTime)
+ requestLayout();
+ }
+ }
+
+ public void finish()
+ {
+ // we're done with this view.
+ mFinished = true;
+
+ // first, hide and remove all the children
+ for (int i=0; i<getPageCount(); i++)
+ {
+ DocPageView cv = (DocPageView)getOrCreateChild(i);
+ cv.setVisibility(GONE);
+ removeViewInLayout(cv);
+ cv.finish();
+ }
+ }
+
+ public boolean finished() {return mFinished;}
+
+ protected void smoothScrollBy(int dx, int dy)
+ {
+ mScrollerLastX = mScrollerLastY = 0;
+ mScroller.startScroll(0, 0, dx, dy, 400);
+ mStepper.prod();
+ }
+
+ public void scrollToPage(int pageNumber)
+ {
+ // scroll to bring the page into view
+
+ // get current viewport
+ Rect viewport = new Rect();
+ getGlobalVisibleRect(viewport);
+
+ // offset it based on current scroll position
+ Point viewportOrigin = new Point();
+ viewportOrigin.set(getScrollX(), getScrollY());
+ viewport.offsetTo(viewportOrigin.x, viewportOrigin.y);
+
+ // get page rect from last layout
+ DocPageView cv = (DocPageView)getOrCreateChild(pageNumber);
+ Rect childRect = cv.getChildRect();
+
+ // scroll
+ if ((childRect.height()) > viewport.height())
+ {
+ // put the top of the page at the top and the left at 0
+ smoothScrollBy(getScrollX(),getScrollY()-childRect.top);
+ }
+ else
+ {
+ // if the whole page is not visible, move the center of the page at the center
+ if (childRect.top < viewport.top || childRect.bottom > viewport.bottom)
+ {
+ if (childRect.top==0)
+ smoothScrollBy(0, getScrollY());
+ else
+ smoothScrollBy(0, getScrollY() + viewport.height() / 2 - (childRect.bottom + childRect.top) / 2);
+ }
+ }
+ }
+
+ private Point viewToScreen(Point p)
+ {
+ Point newPoint = new Point(p);
+
+ Rect r = new Rect();
+ this.getGlobalVisibleRect(r);
+
+ newPoint.offset(r.left, r.top);
+
+ return newPoint;
+ }
+
+ public void scrollBoxIntoView (int pageNum, RectF box)
+ {
+ // get our viewport
+ Rect viewport = new Rect();
+ getGlobalVisibleRect(viewport);
+ viewport.offset(0,-viewport.top);
+
+ // get the location of the box's lower left corner,
+ // relative to the viewport
+ DocPageView cv = (DocPageView)getOrCreateChild(pageNum);
+ Point point = cv.pageToView((int)box.left,(int)box.bottom);
+ Rect childRect = cv.getChildRect();
+ point.y += childRect.top;
+ point.y -= getScrollY();
+
+ // if the point is outside the viewport, scroll so it is.
+ if (point.y<viewport.top || point.y>viewport.bottom)
+ {
+ int diff = (viewport.top + viewport.bottom)/2 - point.y;
+ smoothScrollBy(0,diff);
+ }
+ }
+}
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java
new file mode 100644
index 00000000..46a93068
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/PageAdapter.java
@@ -0,0 +1,61 @@
+package com.artifex.mupdf.android;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import com.artifex.mupdf.fitz.Document;
+
+public class PageAdapter extends BaseAdapter
+{
+ private final Context mContext;
+ private Document mDoc;
+
+ public PageAdapter(Context c) {
+ mContext = c;
+ }
+
+ public void setDocument(Document doc) {
+ mDoc = doc;
+ }
+ private int mWidth;
+ public void setWidth(int w) {mWidth=w;}
+
+ @Override
+ public int getCount() {
+ return mDoc.countPages();
+ }
+
+ public Object getItem(int position) {
+ return null; // not used
+ }
+
+ public long getItemId(int position) {
+ return 0; // not used
+ }
+
+ public View getView(final int position, View convertView, ViewGroup parent)
+ {
+ // make or reuse a view
+ DocPageView pageView;
+
+ final Activity activity = (Activity)mContext;
+ if (convertView == null)
+ {
+ // make a new one
+ pageView = new DocPageView(activity, mDoc);
+ }
+ else
+ {
+ // reuse an existing one
+ pageView = (DocPageView) convertView;
+ }
+
+ // set up the page
+ pageView.setupPage(position, mWidth, 1);
+
+ return pageView;
+ }
+}
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java
new file mode 100644
index 00000000..7030ef8d
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/RenderListener.java
@@ -0,0 +1,6 @@
+package com.artifex.mupdf.android;
+
+public interface RenderListener
+{
+ void progress(int error);
+} \ No newline at end of file
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java
new file mode 100644
index 00000000..8a6b29a5
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/Stepper.java
@@ -0,0 +1,42 @@
+package com.artifex.mupdf.android;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.view.View;
+
+public class Stepper {
+ private final View mPoster;
+ private final Runnable mTask;
+ private boolean mPending;
+
+ public Stepper(View v, Runnable r) {
+ mPoster = v;
+ mTask = r;
+ mPending = false;
+ }
+
+ @SuppressLint("NewApi")
+ public void prod() {
+ if (!mPending) {
+ mPending = true;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ mPoster.postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mPending = false;
+ mTask.run();
+ }
+ });
+ } else {
+ mPoster.post(new Runnable() {
+ @Override
+ public void run() {
+ mPending = false;
+ mTask.run();
+ }
+ });
+
+ }
+ }
+ }
+}
diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/fitz/AndroidDrawDevice.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/fitz/AndroidDrawDevice.java
new file mode 100644
index 00000000..407ddae1
--- /dev/null
+++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/fitz/AndroidDrawDevice.java
@@ -0,0 +1,25 @@
+package com.artifex.mupdf.fitz;
+
+import android.graphics.Bitmap;
+
+import com.artifex.mupdf.fitz.NativeDevice;
+import com.artifex.mupdf.fitz.RectI;
+
+public final class AndroidDrawDevice extends NativeDevice
+{
+ // NOT static.
+ private native long newNative(Bitmap bitmap, int pageX0, int pageY0, int pageX1, int pageY1, int patchX0, int patchY0, int patchX1, int patchY1);
+
+ // Construction
+ public AndroidDrawDevice(Bitmap bitmap, int pageX0, int pageY0, int pageX1, int pageY1, int patchX0, int patchY0, int patchX1, int patchY1)
+ {
+ super(0);
+ pointer = newNative(bitmap, pageX0, pageY0, pageX1, pageY1, patchX0, patchY0, patchX1, patchY1);
+ }
+
+ public AndroidDrawDevice(Bitmap bitmap, RectI page, RectI patch)
+ {
+ super(0);
+ pointer = newNative(bitmap, page.x0, page.y0, page.x1, page.y1, patch.x0, patch.y0, patch.x1, patch.y1);
+ }
+}
diff --git a/platform/android/example/mupdf/src/main/res/values/strings.xml b/platform/android/example/mupdf/src/main/res/values/strings.xml
index 0fba1bfb..0f541156 100644
--- a/platform/android/example/mupdf/src/main/res/values/strings.xml
+++ b/platform/android/example/mupdf/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
<resources>
- <string name="app_name">MuPDF</string>
+ <string name="app_name">mupdf</string>
</resources>
diff --git a/platform/java/mupdf_native.c b/platform/java/mupdf_native.c
index 7530b041..5cef1e70 100644
--- a/platform/java/mupdf_native.c
+++ b/platform/java/mupdf_native.c
@@ -1991,13 +1991,14 @@ newNativeAndroidDrawDevice(JNIEnv *env, jobject self, fz_context *ctx, jobject o
* match the pixels data */
pixbbox = clip;
pixbbox.x1 = pixbbox.x0 + width;
+
pixmap = fz_new_pixmap_with_bbox_and_data(ctx, fz_device_rgb(ctx), &pixbbox, 1, &dummy);
ninfo = fz_malloc(ctx, sizeof(*ninfo));
ninfo->pixmap = pixmap;
ninfo->lock = lock;
ninfo->unlock = unlock;
- ninfo->pageX0 = pageX0;
- ninfo->pageY0 = pageY0;
+ ninfo->pageX0 = patchX0;
+ ninfo->pageY0 = patchY0;
ninfo->width = width;
ninfo->object = obj;
(*env)->SetLongField(env, self, fid_NativeDevice_nativeInfo, jlong_cast(ninfo));
@@ -2030,7 +2031,7 @@ static void androidDrawDevice_lock(JNIEnv *env, NativeDeviceInfo *info)
}
/* Now offset pixels to allow for the page offsets */
- //pixels += sizeof(int32_t) * (info->pageX0 + info->width * info->pageY0);
+ pixels += sizeof(int32_t) * (info->pageX0 + info->width * info->pageY0);
info->pixmap->samples = pixels;
}