diff options
author | fredrossperry <fredrossperry@gmail.com> | 2016-06-23 13:32:36 -0700 |
---|---|---|
committer | fredrossperry <fredrossperry@gmail.com> | 2016-07-06 11:14:49 -0700 |
commit | 01d5f0bc4185c001179ca6fd36530516acc16eeb (patch) | |
tree | 53622c9ef5aea76a6e781fb18380138d9f70d9bb /platform/android/example | |
parent | cb894e713b2f60fef0a7e83ccb3a3d29589193bc (diff) | |
download | mupdf-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>
Diffstat (limited to 'platform/android/example')
19 files changed, 1622 insertions, 267 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="|<-" - 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="<" - 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> |