Compare commits

..

No commits in common. "master" and "feature/byte_array_support" have entirely different histories.

63 changed files with 6231 additions and 1517 deletions

View File

@ -1,7 +1,7 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
compileSdkVersion 21
buildToolsVersion "23.0.2"
defaultConfig {
minSdkVersion 14
@ -17,8 +17,5 @@ android {
}
dependencies {
provided 'com.android.support:appcompat-v7:25.0.0'
compile('com.github.chrisbanes:PhotoView:1.3.0') {
exclude group: 'com.android.support', module: 'support-v4'
}
compile project(':libraries:Android-Pdf-Viewer-Library:gestureimageview')
}

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,9 @@ import com.sun.pdfview.font.PDFFont;
*/
public class PDFParser extends BaseWatchable {
/** emit a file of DCT stream data. */
/**
* emit a file of DCT stream data.
*/
public final static String DEBUG_DCTDECODE_DATA = "debugdctdecode";
static final boolean RELEASE = true;
static final int PDF_CMDS_RANGE1_MIN = 1;
@ -76,11 +78,13 @@ public class PDFParser extends BaseWatchable {
private boolean resend = false;
private Tok tok;
private boolean catchexceptions; // Indicates state of BX...EX
/** a weak reference to the page we render into. For the page
/**
* a weak reference to the page we render into. For the page
* to remain available, some other code must retain a strong reference to it.
*/
private WeakReference pageRef;
/** the actual command, for use within a singe iteration. Note that
/**
* the actual command, for use within a singe iteration. Note that
* this must be released at the end of each iteration to assure the
* page can be collected if not in use
*/
@ -88,7 +92,7 @@ public class PDFParser extends BaseWatchable {
// ---- result variables
byte[] stream;
HashMap<String, PDFObject> resources;
// public static int debuglevel = 4000;
// public static int debuglevel = 4000;
// TODO [FHe]: changed for debugging
public static int debuglevel = -1;
@ -121,7 +125,7 @@ public class PDFParser extends BaseWatchable {
* on to a PDFParser.
*/
public PDFParser(PDFPage cmds, byte[] stream,
HashMap<String, PDFObject> resources) {
HashMap<String, PDFObject> resources) {
super();
this.pageRef = new WeakReference<PDFPage>(cmds);
@ -137,43 +141,76 @@ public class PDFParser extends BaseWatchable {
/////////////////////////////////////////////////////////////////
// B E G I N R E A D E R S E C T I O N
/////////////////////////////////////////////////////////////////
/**
* a token from a PDF Stream
*/
static class Tok {
/** begin bracket &lt; */
/**
* begin bracket &lt;
*/
public static final int BRKB = 11;
/** end bracket &gt; */
/**
* end bracket &gt;
*/
public static final int BRKE = 10;
/** begin array [ */
/**
* begin array [
*/
public static final int ARYB = 9;
/** end array ] */
/**
* end array ]
*/
public static final int ARYE = 8;
/** String (, readString looks for trailing ) */
/**
* String (, readString looks for trailing )
*/
public static final int STR = 7;
/** begin brace { */
/**
* begin brace {
*/
public static final int BRCB = 5;
/** end brace } */
/**
* end brace }
*/
public static final int BRCE = 4;
/** number */
/**
* number
*/
public static final int NUM = 3;
/** keyword */
/**
* keyword
*/
public static final int CMD = 2;
/** name (begins with /) */
/**
* name (begins with /)
*/
public static final int NAME = 1;
/** unknown token */
/**
* unknown token
*/
public static final int UNK = 0;
/** end of stream */
/**
* end of stream
*/
public static final int EOF = -1;
/** the string value of a STR, NAME, or CMD token */
/**
* the string value of a STR, NAME, or CMD token
*/
public String name;
/** the value of a NUM token */
/**
* the value of a NUM token
*/
public double value;
/** the type of the token */
/**
* the type of the token
*/
public int type;
/** a printable representation of the token */
/**
* a printable representation of the token
*/
@Override
public String toString() {
if (type == NUM) {
@ -372,7 +409,7 @@ public class PDFParser extends BaseWatchable {
* character, which has already been read, and end with a balanced ')'
* character. A '\' character starts an escape sequence of up
* to three octal digits.</p>
*
* <p/>
* <p>Parenthesis must be enclosed by a balanced set of parenthesis,
* so a string may enclose balanced parenthesis.</p>
*
@ -384,7 +421,7 @@ public class PDFParser extends BaseWatchable {
int parenLevel = 0;
final StringBuffer sb = new StringBuffer();
for (int to = stream_.length; loc < to;) {
for (int to = stream_.length; loc < to; ) {
int c = stream_[loc++];
if (c == ')') {
if (parenLevel-- == 0) {
@ -425,7 +462,7 @@ public class PDFParser extends BaseWatchable {
* character, which has already been read, and end with a '>'
* character. Each byte in the array is made up of two hex characters,
* the first being the high-order bit.
*
* <p/>
* We translate the byte arrays into char arrays by combining two bytes
* into a character, and then translate the character array into a string.
* [JK FIXME this is probably a really bad idea!]
@ -440,7 +477,7 @@ public class PDFParser extends BaseWatchable {
char w = (char) 0;
// read individual bytes and format into a character array
for (int to = stream_.length; (loc < to) && (stream_[loc] != '>');) {
for (int to = stream_.length; (loc < to) && (stream_[loc] != '>'); ) {
final char c = (char) stream_[loc];
byte b = (byte) 0;
@ -478,6 +515,7 @@ public class PDFParser extends BaseWatchable {
/////////////////////////////////////////////////////////////////
// B E G I N P A R S E R S E C T I O N
/////////////////////////////////////////////////////////////////
/**
* Called to prepare for some iterations
*/
@ -505,16 +543,16 @@ public class PDFParser extends BaseWatchable {
/**
* parse the stream. commands are added to the PDFPage initialized
* in the constructor as they are encountered.
* <p>
* <p/>
* Page numbers in comments refer to the Adobe PDF specification.<br>
* commands are listed in PDF spec 32000-1:2008 in Table A.1
*
* @return <ul><li>Watchable.RUNNING when there are commands to be processed
* <li>Watchable.COMPLETED when the page is done and all
* the commands have been processed
* <li>Watchable.STOPPED if the page we are rendering into is
* no longer available
* </ul>
* <li>Watchable.COMPLETED when the page is done and all
* the commands have been processed
* <li>Watchable.STOPPED if the page we are rendering into is
* no longer available
* </ul>
*/
public int iterate() throws Exception {
// make sure the page is still available, and create the reference
@ -662,7 +700,7 @@ public class PDFParser extends BaseWatchable {
path = new Path();
break;
case 'f':
// the fall-through is intended!
// the fall-through is intended!
case 'F':
// fill the path (close/not close identical)
cmds.addPath(path, PDFShapeCmd.FILL | clip);
@ -952,11 +990,11 @@ public class PDFParser extends BaseWatchable {
break;
case 'Q' + ('q' << 8):
processQCmd();
// 'q'-cmd
// push the parser state
parserStates.push((ParserState) state.clone());
// push graphics state
cmds.addPush();
// 'q'-cmd
// push the parser state
parserStates.push((ParserState) state.clone());
// push graphics state
cmds.addPush();
break;
default:
if (catchexceptions) {
@ -1025,6 +1063,7 @@ public class PDFParser extends BaseWatchable {
path = null;
cmds = null;
}
boolean errorwritten = false;
public void dumpStreamToError() {
@ -1068,10 +1107,12 @@ public class PDFParser extends BaseWatchable {
/////////////////////////////////////////////////////////////////
// H E L P E R S
/////////////////////////////////////////////////////////////////
/**
* get a property from a named dictionary in the resources of this
* content stream.
* @param name the name of the property in the dictionary
*
* @param name the name of the property in the dictionary
* @param inDict the name of the dictionary in the resources
* @return the value of the property in the dictionary
*/
@ -1092,6 +1133,7 @@ public class PDFParser extends BaseWatchable {
* Insert a PDF object into the command stream. The object must
* either be an Image or a Form, which is a set of PDF commands
* in a stream.
*
* @param obj the object to insert, an Image or a Form.
*/
private void doXObject(PDFObject obj) throws IOException {
@ -1111,8 +1153,9 @@ public class PDFParser extends BaseWatchable {
/**
* Parse image data into a Java BufferedImage and add the image
* command to the page.
*
* @param obj contains the image data, and a dictionary describing
* the width, height and color space of the image.
* the width, height and color space of the image.
*/
private void doImage(PDFObject obj) throws IOException {
cmds.addImage(PDFImage.createImage(obj, resources));
@ -1122,8 +1165,9 @@ public class PDFParser extends BaseWatchable {
* Inject a stream of PDF commands onto the page. Optimized to cache
* a parsed stream of commands, so that each Form object only needs
* to be parsed once.
*
* @param obj a stream containing the PDF commands, a transformation
* matrix, bounding box, and resources.
* matrix, bounding box, and resources.
*/
private void doForm(PDFObject obj) throws IOException {
// check to see if we've already parsed this sucker
@ -1186,6 +1230,7 @@ public class PDFParser extends BaseWatchable {
//
// return patternSpace.getPaint(pattern, components, resources);
// }
/**
* Parse the next object out of the PDF stream. This could be a
* Double, a String, a HashMap (dictionary), Object[] array, or
@ -1198,13 +1243,13 @@ public class PDFParser extends BaseWatchable {
case Tok.NUM:
return new Double(tok.value);
case Tok.STR:
// the fall-through is intended!
// the fall-through is intended!
case Tok.NAME:
return tok.name;
case Tok.BRKB: {
final HashMap<String, PDFObject> hm = new HashMap<String, PDFObject>();
String name = null;
for (Object obj = null; (obj = parseObject()) != null;) {
for (Object obj = null; (obj = parseObject()) != null; ) {
if (name == null) {
name = (String) obj;
} else {
@ -1220,7 +1265,7 @@ public class PDFParser extends BaseWatchable {
case Tok.ARYB: {
// build an array
final ArrayList<Object> ary = new ArrayList<Object>();
for (Object obj = null; (obj = parseObject()) != null;) {
for (Object obj = null; (obj = parseObject()) != null; ) {
ary.add(obj);
}
if (tok.type != Tok.ARYE) {
@ -1351,6 +1396,7 @@ public class PDFParser extends BaseWatchable {
/**
* add graphics state commands contained within a dictionary.
*
* @param name the resource name of the graphics state dictionary
*/
private void setGSState(String name) throws IOException {
@ -1408,11 +1454,15 @@ public class PDFParser extends BaseWatchable {
/**
* pop a single float value off the stack.
*
* @return the float value of the top of the stack
* @throws PDFParseException if the value on the top of the stack
* isn't a number
* isn't a number
*/
private float popFloat() throws PDFParseException {
if (stack.isEmpty()) {
return 0;
}
Object obj = stack.pop();
if (obj instanceof Double) {
return ((Double) obj).floatValue();
@ -1425,10 +1475,11 @@ public class PDFParser extends BaseWatchable {
* pop an array of float values off the stack. This is equivalent
* to filling an array from end to front by popping values off the
* stack.
*
* @param count the number of numbers to pop off the stack
* @return an array of length <tt>count</tt>
* @throws PDFParseException if any of the values popped off the
* stack are not numbers.
* stack are not numbers.
*/
private float[] popFloat(int count) throws PDFParseException {
float[] ary = new float[count];
@ -1440,6 +1491,7 @@ public class PDFParser extends BaseWatchable {
/**
* pop a single integer value off the stack.
*
* @return the integer value of the top of the stack
* @throws PDFParseException if the top of the stack isn't a number.
*/
@ -1456,10 +1508,11 @@ public class PDFParser extends BaseWatchable {
* pop an array of integer values off the stack. This is equivalent
* to filling an array from end to front by popping values off the
* stack.
*
* @param count the number of numbers to pop off the stack
* @return an array of length <tt>count</tt>
* @throws PDFParseException if any of the values popped off the
* stack are not numbers.
* stack are not numbers.
*/
private float[] popFloatArray() throws PDFParseException {
Object obj = stack.pop();
@ -1480,9 +1533,10 @@ public class PDFParser extends BaseWatchable {
/**
* pop a String off the stack.
*
* @return the String from the top of the stack
* @throws PDFParseException if the top of the stack is not a NAME
* or STR.
* or STR.
*/
private String popString() throws PDFParseException {
Object obj = stack.pop();
@ -1495,9 +1549,10 @@ public class PDFParser extends BaseWatchable {
/**
* pop a PDFObject off the stack.
*
* @return the PDFObject from the top of the stack
* @throws PDFParseException if the top of the stack does not contain
* a PDFObject.
* a PDFObject.
*/
private PDFObject popObject() throws PDFParseException {
Object obj = stack.pop();
@ -1509,9 +1564,10 @@ public class PDFParser extends BaseWatchable {
/**
* pop an array off the stack
*
* @return the array of objects that is the top element of the stack
* @throws PDFParseException if the top element of the stack does not
* contain an array.
* contain an array.
*/
private Object[] popArray() throws PDFParseException {
Object obj = stack.pop();
@ -1528,11 +1584,17 @@ public class PDFParser extends BaseWatchable {
*/
class ParserState implements Cloneable {
/** the fill color space */
/**
* the fill color space
*/
PDFColorSpace fillCS;
/** the stroke color space */
/**
* the stroke color space
*/
PDFColorSpace strokeCS;
/** the text paramters */
/**
* the text paramters
*/
PDFTextFormat textFormat;
/**

View File

@ -0,0 +1,60 @@
/*
* $Id: CalRGBColor.java,v 1.2 2007/12/20 18:33:34 rbair Exp $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.sun.pdfview.colorspace;
import android.graphics.Color;
public class ICCIColorSpace extends PDFColorSpace {
public ICCIColorSpace() {
}
/**
* get the number of components (3)
*/
@Override public int getNumComponents() {
return 3;
}
@Override public int toColor(float[] fcomp) {
return Color.rgb((int)(fcomp[0]*255),(int)(fcomp[1]*255),(int)(fcomp[2]*255));
}
@Override public int toColor(int[] icomp) {
return Color.rgb(icomp[0],icomp[1],icomp[2]);
}
/**
* get the type of this color space (TYPE_RGB)
*/
@Override public int getType() {
return COLORSPACE_GRAY;
}
@Override
public String getName() {
return "RGB";
}
}

View File

@ -28,187 +28,186 @@ import com.sun.pdfview.PDFPaint;
import com.sun.pdfview.PDFParseException;
import com.sun.pdfview.function.PDFFunction;
/**
* A color space that can convert a set of color components into
* PDFPaint.
*
* @author Mike Wessler
*/
public abstract class PDFColorSpace {
/** the name of the device-dependent gray color space */
public static final int COLORSPACE_GRAY = 0;
/** the name of the device-dependent gray color space */
public static final int COLORSPACE_GRAY = 0;
/** the name of the device-dependent RGB color space */
public static final int COLORSPACE_RGB = 1;
/** the name of the device-dependent RGB color space */
public static final int COLORSPACE_RGB = 1;
/** the name of the device-dependent CMYK color space */
public static final int COLORSPACE_CMYK = 2;
/** the name of the device-dependent CMYK color space */
public static final int COLORSPACE_CMYK = 2;
/** the name of the pattern color space */
public static final int COLORSPACE_PATTERN = 3;
/** the name of the pattern color space */
public static final int COLORSPACE_PATTERN = 3;
public static final int COLORSPACE_INDEXED = 4;
public static final int COLORSPACE_INDEXED = 4;
public static final int COLORSPACE_ALTERNATE = 5;
public static final int COLORSPACE_ALTERNATE = 5;
/** the device-dependent color spaces */
// private static PDFColorSpace graySpace =
// new PDFColorSpace(ColorSpace.getInstance(ColorSpace.CS_GRAY));
private static PDFColorSpace rgbSpace = new RGBColorSpace();
private static PDFColorSpace cmykSpace = new CMYKColorSpace();
/** the device-dependent color spaces */
// private static PDFColorSpace graySpace =
// new PDFColorSpace(ColorSpace.getInstance(ColorSpace.CS_GRAY));
private static PDFColorSpace rgbSpace = new RGBColorSpace();
private static PDFColorSpace cmykSpace = new CMYKColorSpace();
private static PDFColorSpace icciSpace = new ICCIColorSpace();
/** the pattern space */
private static PDFColorSpace patternSpace = new RGBColorSpace(); // TODO [FHe]
/** the pattern space */
private static PDFColorSpace patternSpace = new RGBColorSpace(); // TODO [FHe]
/** graySpace and the gamma correction for it. */
private static PDFColorSpace graySpace = new GrayColorSpace();
/** graySpace and the gamma correction for it. */
private static PDFColorSpace graySpace = new GrayColorSpace();
/**
* create a PDFColorSpace based on a Java ColorSpace
*
* @param cs the Java ColorSpace
*/
protected PDFColorSpace() {
}
/**
* Get a color space by name
*
* @param name the name of one of the device-dependent color spaces
*/
public static PDFColorSpace getColorSpace(int name) {
switch (name) {
case COLORSPACE_GRAY:
return graySpace;
/**
* create a PDFColorSpace based on a Java ColorSpace
* @param cs the Java ColorSpace
*/
protected PDFColorSpace() {
case COLORSPACE_RGB:
return rgbSpace;
case COLORSPACE_CMYK:
return cmykSpace;
case COLORSPACE_PATTERN:
return patternSpace;
default:
throw new IllegalArgumentException("Unknown Color Space name: " + name);
}
}
/**
* Get a color space specified in a PDFObject
*
* @param csobj the PDFObject with the colorspace information
*/
public static PDFColorSpace getColorSpace(PDFObject csobj, Map resources) throws IOException {
String name;
PDFObject colorSpaces = null;
if (resources != null) {
colorSpaces = (PDFObject) resources.get("ColorSpace");
}
/**
* Get a color space by name
*
* @param name the name of one of the device-dependent color spaces
*/
public static PDFColorSpace getColorSpace(int name) {
switch (name) {
case COLORSPACE_GRAY:
return graySpace;
if (csobj.getType() == PDFObject.NAME) {
name = csobj.getStringValue();
case COLORSPACE_RGB:
return rgbSpace;
case COLORSPACE_CMYK:
return cmykSpace;
case COLORSPACE_PATTERN:
return patternSpace;
default:
throw new IllegalArgumentException("Unknown Color Space name: " +
name);
}
if (name.equals("DeviceGray") || name.equals("G")) {
return getColorSpace(COLORSPACE_GRAY);
} else if (name.equals("DeviceRGB") || name.equals("RGB")) {
return getColorSpace(COLORSPACE_RGB);
} else if (name.equals("DeviceCMYK") || name.equals("CMYK")) {
return getColorSpace(COLORSPACE_CMYK);
} else if (name.equals("Pattern")) {
return getColorSpace(COLORSPACE_PATTERN);
} else if (colorSpaces != null) {
csobj = (PDFObject) colorSpaces.getDictRef(name);
}
}
/**
* Get a color space specified in a PDFObject
*
* @param csobj the PDFObject with the colorspace information
*/
public static PDFColorSpace getColorSpace(PDFObject csobj, Map resources)
throws IOException {
String name;
PDFObject colorSpaces = null;
if (resources != null) {
colorSpaces = (PDFObject) resources.get("ColorSpace");
}
if (csobj.getType() == PDFObject.NAME) {
name = csobj.getStringValue();
if (name.equals("DeviceGray") || name.equals("G")) {
return getColorSpace(COLORSPACE_GRAY);
} else if (name.equals("DeviceRGB") || name.equals("RGB")) {
return getColorSpace(COLORSPACE_RGB);
} else if (name.equals("DeviceCMYK") || name.equals("CMYK")) {
return getColorSpace(COLORSPACE_CMYK);
} else if (name.equals("Pattern")) {
return getColorSpace(COLORSPACE_PATTERN);
} else if (colorSpaces != null) {
csobj = (PDFObject) colorSpaces.getDictRef(name);
}
}
if (csobj == null) {
return null;
} else if (csobj.getCache() != null) {
return (PDFColorSpace) csobj.getCache();
}
PDFColorSpace value = null;
// csobj is [/name <<dict>>]
PDFObject[] ary = csobj.getArray();
name = ary[0].getStringValue();
if (name.equals("CalGray")) {
value = graySpace; // TODO [FHe]
} else if (name.equals("CalRGB")) {
value = rgbSpace; // TODO [FHe]
} else if (name.equals("Lab")) {
value = rgbSpace; // TODO [FHe]
} else if (name.equals("ICCBased")) {
value = rgbSpace; // TODO [FHe]
} else if (name.equals("Separation") || name.equals("DeviceN")) {
PDFColorSpace alternate = getColorSpace(ary[2], resources);
PDFFunction function = PDFFunction.getFunction(ary[3]);
value = new AlternateColorSpace(alternate, function);
} else if (name.equals("Indexed") || name.equals("I")) {
/**
* 4.5.5 [/Indexed baseColor hival lookup]
*/
PDFColorSpace refspace = getColorSpace(ary[1], resources);
// number of indices= ary[2], data is in ary[3];
int count = ary[2].getIntValue();
value = new IndexedColor(refspace, count, ary[3]);
} else if (name.equals("Pattern")) {
return rgbSpace; // TODO [FHe]
} else {
throw new PDFParseException("Unknown color space: " + name +
" with " + ary[1]);
}
csobj.setCache(value);
return value;
if (csobj == null) {
return null;
} else if (csobj.getCache() != null) {
return (PDFColorSpace) csobj.getCache();
}
/**
* get the number of components expected in the getPaint command
*/
public abstract int getNumComponents();
PDFColorSpace value = null;
/**
* get the PDFPaint representing the color described by the
* given color components
* @param components the color components corresponding to the given
* colorspace
* @return a PDFPaint object representing the closest Color to the
* given components.
*/
public PDFPaint getPaint(float[] components) {
return PDFPaint.getColorPaint(toColor(components));
}
public PDFPaint getFillPaint(float[] components) {
return PDFPaint.getPaint(toColor(components));
// csobj is [/name <<dict>>]
PDFObject[] ary = csobj.getArray();
name = ary[0].getStringValue();
if (name.equals("CalGray")) {
value = graySpace; // TODO [FHe]
} else if (name.equals("CalRGB")) {
value = rgbSpace; // TODO [FHe]
} else if (name.equals("Lab")) {
value = rgbSpace; // TODO [FHe]
} else if (name.equals("ICCBased")) {
value = rgbSpace;//icciSpace; // TODO [FHe]
} else if (name.equals("Separation") || name.equals("DeviceN")) {
PDFColorSpace alternate = getColorSpace(ary[2], resources);
PDFFunction function = PDFFunction.getFunction(ary[3]);
value = new AlternateColorSpace(alternate, function);
} else if (name.equals("Indexed") || name.equals("I")) {
/**
* 4.5.5 [/Indexed baseColor hival lookup]
*/
PDFColorSpace refspace = getColorSpace(ary[1], resources);
// number of indices= ary[2], data is in ary[3];
int count = ary[2].getIntValue();
value = new IndexedColor(refspace, count, ary[3]);
} else if (name.equals("Pattern")) {
return rgbSpace; // TODO [FHe]
} else {
throw new PDFParseException("Unknown color space: " + name +
" with " + ary[1]);
}
/**
* get the type of this color space
*/
public abstract int getType();
/**
* get the name of this color space
*/
public abstract String getName();
csobj.setCache(value);
public abstract int toColor(float[] fcomp);
return value;
}
public abstract int toColor(int[] icomp);
/**
* get the number of components expected in the getPaint command
*/
public abstract int getNumComponents();
@Override
public String toString() {
return "ColorSpace["+getName()+"]";
}
/**
* get the PDFPaint representing the color described by the
* given color components
*
* @param components the color components corresponding to the given
* colorspace
* @return a PDFPaint object representing the closest Color to the
* given components.
*/
public PDFPaint getPaint(float[] components) {
return PDFPaint.getColorPaint(toColor(components));
}
public PDFPaint getFillPaint(float[] components) {
return PDFPaint.getPaint(toColor(components));
}
/**
* get the type of this color space
*/
public abstract int getType();
/**
* get the name of this color space
*/
public abstract String getName();
public abstract int toColor(float[] fcomp);
public abstract int toColor(int[] icomp);
@Override public String toString() {
return "ColorSpace[" + getName() + "]";
}
}

View File

@ -21,19 +21,13 @@
package com.sun.pdfview.decode;
import java.io.IOException;
import java.nio.IntBuffer;
import net.sf.andpdf.nio.ByteBuffer;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.sun.pdfview.PDFObject;
import com.sun.pdfview.PDFParseException;
import com.sun.pdfview.colorspace.PDFColorSpace;
import net.sf.andpdf.nio.ByteBuffer;
/**
* decode a DCT encoded array into a byte array. This class uses Java's
@ -43,52 +37,49 @@ import com.sun.pdfview.colorspace.PDFColorSpace;
*/
public class DCTDecode {
/**
* decode an array of bytes in DCT format.
* <p>
* DCT is the format used by JPEG images, so this class simply
* loads the DCT-format bytes as an image, then reads the bytes out
* of the image to create the array. Unfortunately, their most
* likely use is to get turned BACK into an image, so this isn't
* terribly efficient... but is is general... don't hit, please.
* <p>
* The DCT-encoded stream may have 1, 3 or 4 samples per pixel, depending
* on the colorspace of the image. In decoding, we look for the colorspace
* in the stream object's dictionary to decide how to decode this image.
* If no colorspace is present, we guess 3 samples per pixel.
*
* @param dict the stream dictionary
* @param buf the DCT-encoded buffer
* @param params the parameters to the decoder (ignored)
* @return the decoded buffer
*/
protected static ByteBuffer decode(PDFObject dict, ByteBuffer buf,
PDFObject params) throws PDFParseException
{
// System.out.println("DCTDecode image info: "+params);
buf.rewind();
/**
* decode an array of bytes in DCT format.
* <p>
* DCT is the format used by JPEG images, so this class simply
* loads the DCT-format bytes as an image, then reads the bytes out
* of the image to create the array. Unfortunately, their most
* likely use is to get turned BACK into an image, so this isn't
* terribly efficient... but is is general... don't hit, please.
* <p>
* The DCT-encoded stream may have 1, 3 or 4 samples per pixel, depending
* on the colorspace of the image. In decoding, we look for the colorspace
* in the stream object's dictionary to decide how to decode this image.
* If no colorspace is present, we guess 3 samples per pixel.
*
* @param dict the stream dictionary
* @param buf the DCT-encoded buffer
* @param params the parameters to the decoder (ignored)
* @return the decoded buffer
*/
protected static ByteBuffer decode(PDFObject dict, ByteBuffer buf, PDFObject params) throws PDFParseException {
// System.out.println("DCTDecode image info: "+params);
buf.rewind();
// copy the data into a byte array required by createimage
byte[] ary = new byte[buf.remaining()];
buf.get(ary);
// copy the data into a byte array required by createimage
byte[] ary = new byte[buf.remaining()];
buf.get(ary);
Bitmap img = BitmapFactory.decodeByteArray(ary, 0, ary.length);
Bitmap img = BitmapFactory.decodeByteArray(ary, 0, ary.length);
if (img == null)
throw new PDFParseException("could not decode image of compressed size "+ary.length);
Config conf = img.getConfig();
Log.e("ANDPDF.dctdecode", "decoded image type"+conf);
int size = 4*img.getWidth()*img.getHeight();
if (conf == Config.RGB_565)
size = 2*img.getWidth()*img.getHeight();
// TODO [FHe]: else ... what do we get for gray? Config.ALPHA_8?
java.nio.ByteBuffer byteBuf = java.nio.ByteBuffer.allocate(size);
img.copyPixelsToBuffer(byteBuf);
ByteBuffer result = ByteBuffer.fromNIO(byteBuf);
result.rewind();
return result;
if (img == null) throw new PDFParseException("could not decode image of compressed size " + ary.length);
Config conf = img.getConfig();
Log.e("ANDPDF.dctdecode", "decoded image type" + conf);
int size = 4 * img.getWidth() * img.getHeight();
if (conf == Config.RGB_565) {
size = 2 * img.getWidth() * img.getHeight();
}
// TODO [FHe]: else ... what do we get for gray? Config.ALPHA_8?
java.nio.ByteBuffer byteBuf = java.nio.ByteBuffer.allocate(size);
img.copyPixelsToBuffer(byteBuf);
ByteBuffer result = ByteBuffer.fromNIO(byteBuf);
result.rewind();
return result;
}
}

View File

@ -111,6 +111,9 @@ public class HeadTable extends TrueTypeTable {
* Parse the data before it is set
*/
public void setData(ByteBuffer data) {
if (data.remaining() != 54) {
throw new IllegalArgumentException("Bad Head table size");
}
setVersion(data.getInt());
setFontRevision(data.getInt());
setChecksumAdjustment(data.getInt());

View File

@ -1,31 +0,0 @@
package net.sf.andpdf.pdfviewer;
import android.view.MotionEvent;
import uk.co.senab.photoview.PhotoViewAttacher;
public abstract class OnSwipeTouchListener implements PhotoViewAttacher.OnSingleFlingListener {
private static final int SWIPE_DISTANCE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) {
float distanceX = e2.getX() - e1.getX();
float distanceY = e2.getY() - e1.getY();
if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (distanceX > 0)
onSwipeRight();
else
onSwipeLeft();
return true;
}
return false;
}
public abstract void onSwipeLeft();
public abstract void onSwipeRight();
}

View File

@ -0,0 +1,639 @@
package net.sf.andpdf.pdfviewer;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFImage;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PDFPaint;
import com.sun.pdfview.decrypt.PDFAuthenticationFailureException;
import com.sun.pdfview.decrypt.PDFPassword;
import com.sun.pdfview.font.PDFFont;
import net.sf.andpdf.nio.ByteBuffer;
import net.sf.andpdf.pdfviewer.gui.FullScrollView;
import net.sf.andpdf.refs.HardReference;
import java.io.IOException;
/**
* U:\Android\android-sdk-windows-1.5_r1\tools\adb push u:\Android\simple_T.pdf /data/test.pdf
*
* @author ferenc.hechler
*/
public class PdfViewerActivity extends Activity {
private static final int STARTPAGE = 1;
private static final float STARTZOOM = 1.0f;
private static final float MIN_ZOOM = 0.25f;
private static final float MAX_ZOOM = 3.0f;
private static final float ZOOM_INCREMENT = 1.5f;
private static final String TAG = "PDFVIEWER";
public static final String EXTRA_SHOWIMAGES = "net.sf.andpdf.extra.SHOWIMAGES";
public static final String EXTRA_ANTIALIAS = "net.sf.andpdf.extra.ANTIALIAS";
public static final String EXTRA_USEFONTSUBSTITUTION = "net.sf.andpdf.extra.USEFONTSUBSTITUTION";
public static final boolean DEFAULTSHOWIMAGES = true;
public static final boolean DEFAULTANTIALIAS = true;
public static final boolean DEFAULTUSEFONTSUBSTITUTION = false;
private final static int MENU_NEXT_PAGE = 1;
private final static int MENU_PREV_PAGE = 2;
private final static int MENU_GOTO_PAGE = 3;
private final static int MENU_ZOOM_IN = 4;
private final static int MENU_ZOOM_OUT = 5;
private final static int MENU_BACK = 6;
private final static int MENU_CLEANUP = 7;
private final static int DIALOG_PAGENUM = 1;
private GraphView mOldGraphView;
private GraphView mGraphView;
private PDFFile mPdfFile;
public static byte[] byteArray;
private int mPage;
private float mZoom;
private ProgressDialog progress;
private PDFPage mPdfPage;
private Thread backgroundThread;
private Handler uiHandler;
/**
* restore member variables from previously saved instance
*
* @return true if instance to restore from was found
* @see
*/
private boolean restoreInstance() {
mOldGraphView = null;
Log.e(TAG, "restoreInstance");
if (getLastNonConfigurationInstance() == null) return false;
PdfViewerActivity inst = (PdfViewerActivity) getLastNonConfigurationInstance();
if (inst != this) {
Log.e(TAG, "restoring Instance");
mOldGraphView = inst.mGraphView;
mPage = inst.mPage;
mPdfFile = inst.mPdfFile;
mPdfPage = inst.mPdfPage;
mZoom = inst.mZoom;
backgroundThread = inst.backgroundThread;
}
return true;
}
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate");
uiHandler = new Handler();
restoreInstance();
if (mOldGraphView != null) {
mGraphView = new GraphView(this);
mGraphView.mBi = mOldGraphView.mBi;
mOldGraphView = null;
mGraphView.pdfView.setImageBitmap(mGraphView.mBi);
mGraphView.updateTexts();
setContentView(mGraphView);
} else {
mGraphView = new GraphView(this);
Intent intent = getIntent();
Log.i(TAG, "" + intent);
PDFImage.sShowImages = getIntent().getBooleanExtra(PdfViewerActivity.EXTRA_SHOWIMAGES, PdfViewerActivity.DEFAULTSHOWIMAGES);
PDFPaint.s_doAntiAlias = getIntent().getBooleanExtra(PdfViewerActivity.EXTRA_ANTIALIAS, PdfViewerActivity.DEFAULTANTIALIAS);
PDFFont.sUseFontSubstitution = getIntent().getBooleanExtra(PdfViewerActivity.EXTRA_USEFONTSUBSTITUTION,
PdfViewerActivity.DEFAULTUSEFONTSUBSTITUTION);
HardReference.sKeepCaches = true;
mPage = STARTPAGE;
mZoom = STARTZOOM;
setContent(null);
}
}
private void setContent(String password) {
try {
openFile(byteArray, password);
setContentView(mGraphView);
startRenderThread(mPage, mZoom);
} catch (PDFAuthenticationFailureException e) {
setContentView(getPdfPasswordLayoutResource());
final EditText etPW = (EditText) findViewById(getPdfPasswordEditField());
Button btOK = (Button) findViewById(getPdfPasswordOkButton());
Button btExit = (Button) findViewById(getPdfPasswordExitButton());
btOK.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
String pw = etPW.getText().toString();
setContent(pw);
}
});
btExit.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
finish();
}
});
} catch (Exception ex) {
Log.e(TAG, "an unexpected exception occurred");
}
}
private synchronized void startRenderThread(final int page, final float zoom) {
if (backgroundThread != null) return;
backgroundThread = new Thread(new Runnable() {
public void run() {
try {
if (mPdfFile != null) {
showPage(page, zoom);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
backgroundThread = null;
}
});
updateImageStatus();
backgroundThread.start();
}
private void updateImageStatus() {
if (backgroundThread == null) {
mGraphView.updateUi();
return;
}
mGraphView.updateUi();
mGraphView.postDelayed(new Runnable() {
public void run() {
updateImageStatus();
}
}, 1000);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(Menu.NONE, MENU_PREV_PAGE, Menu.NONE, "Previous Page").setIcon(getPreviousPageImageResource());
menu.add(Menu.NONE, MENU_NEXT_PAGE, Menu.NONE, "Next Page").setIcon(getNextPageImageResource());
menu.add(Menu.NONE, MENU_GOTO_PAGE, Menu.NONE, "Goto Page");
menu.add(Menu.NONE, MENU_ZOOM_OUT, Menu.NONE, "Zoom Out").setIcon(getZoomOutImageResource());
menu.add(Menu.NONE, MENU_ZOOM_IN, Menu.NONE, "Zoom In").setIcon(getZoomInImageResource());
if (HardReference.sKeepCaches) menu.add(Menu.NONE, MENU_CLEANUP, Menu.NONE, "Clear Caches");
return true;
}
/**
* Called when a menu item is selected.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case MENU_NEXT_PAGE: {
nextPage();
break;
}
case MENU_PREV_PAGE: {
prevPage();
break;
}
case MENU_GOTO_PAGE: {
gotoPage();
break;
}
case MENU_ZOOM_IN: {
zoomIn();
break;
}
case MENU_ZOOM_OUT: {
zoomOut();
break;
}
case MENU_BACK: {
finish();
break;
}
case MENU_CLEANUP: {
HardReference.cleanup();
break;
}
}
return true;
}
private void zoomIn() {
if (mPdfFile != null) {
if (mZoom < MAX_ZOOM) {
mZoom *= ZOOM_INCREMENT;
if (mZoom > MAX_ZOOM) mZoom = MAX_ZOOM;
if (mZoom >= MAX_ZOOM) {
Log.d(TAG, "Disabling zoom in button");
mGraphView.bZoomIn.setEnabled(false);
} else {
mGraphView.bZoomIn.setEnabled(true);
}
mGraphView.bZoomOut.setEnabled(true);
startRenderThread(mPage, mZoom);
}
}
}
private void zoomOut() {
if (mPdfFile != null) {
if (mZoom > MIN_ZOOM) {
mZoom /= ZOOM_INCREMENT;
if (mZoom < MIN_ZOOM) mZoom = MIN_ZOOM;
if (mZoom <= MIN_ZOOM) {
Log.d(TAG, "Disabling zoom out button");
mGraphView.bZoomOut.setEnabled(false);
} else {
mGraphView.bZoomOut.setEnabled(true);
}
mGraphView.bZoomIn.setEnabled(true);
startRenderThread(mPage, mZoom);
}
}
}
private void nextPage() {
if (mPdfFile != null) {
if (mPage < mPdfFile.getNumPages()) {
mPage += 1;
mGraphView.bZoomOut.setEnabled(true);
mGraphView.bZoomIn.setEnabled(true);
progress = ProgressDialog.show(PdfViewerActivity.this, "Loading", "Loading PDF Page " + mPage, true, true);
startRenderThread(mPage, mZoom);
}
}
}
private void prevPage() {
if (mPdfFile != null) {
if (mPage > 1) {
mPage -= 1;
mGraphView.bZoomOut.setEnabled(true);
mGraphView.bZoomIn.setEnabled(true);
progress = ProgressDialog.show(PdfViewerActivity.this, "Loading", "Loading PDF Page " + mPage, true, true);
startRenderThread(mPage, mZoom);
}
}
}
private void gotoPage() {
if (mPdfFile != null) {
showDialog(DIALOG_PAGENUM);
}
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_PAGENUM:
LayoutInflater factory = LayoutInflater.from(this);
final View pagenumView = factory.inflate(getPdfPageNumberResource(), null);
final EditText edPagenum = (EditText) pagenumView.findViewById(getPdfPageNumberEditField());
edPagenum.setText(Integer.toString(mPage));
edPagenum.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event == null || (event.getAction() == 1)) {
// Hide the keyboard
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(edPagenum.getWindowToken(), 0);
}
return true;
}
});
return new AlertDialog.Builder(this)
//.setIcon(R.drawable.icon)
.setTitle("Jump to page")
.setView(pagenumView)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String strPagenum = edPagenum.getText().toString();
int pageNum = mPage;
try {
pageNum = Integer.parseInt(strPagenum);
} catch (NumberFormatException ignore) {
}
if ((pageNum != mPage) && (pageNum >= 1) && (pageNum <= mPdfFile.getNumPages())) {
mPage = pageNum;
mGraphView.bZoomOut.setEnabled(true);
mGraphView.bZoomIn.setEnabled(true);
progress =
ProgressDialog.show(PdfViewerActivity.this, "Loading", "Loading PDF Page " + mPage, true, true);
startRenderThread(mPage, mZoom);
}
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
})
.create();
}
return null;
}
private class GraphView extends FullScrollView {
public Bitmap mBi;
public ImageView pdfView;
public Button mBtPage;
private Button mBtPage2;
ImageButton bZoomOut;
ImageButton bZoomIn;
public GraphView(Context context) {
super(context);
// LinearLayout.LayoutParams matchLp =
// new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
LinearLayout.LayoutParams lpWrap1 = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 1);
LinearLayout.LayoutParams lpWrap10 = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 10);
LinearLayout vl = new LinearLayout(context);
vl.setLayoutParams(lpWrap10);
vl.setOrientation(LinearLayout.VERTICAL);
if (mOldGraphView == null) {
progress = ProgressDialog.show(PdfViewerActivity.this, "Loading", "Loading PDF Page", true, true);
}
addNavButtons(vl);
// remember page button for updates
mBtPage2 = mBtPage;
pdfView = new ImageView(context);
setPageBitmap(null);
updateImage();
pdfView.setLayoutParams(lpWrap1);
pdfView.setPadding(5, 5, 5, 5);
vl.addView(pdfView);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 100));
setBackgroundColor(Color.LTGRAY);
setHorizontalScrollBarEnabled(true);
setHorizontalFadingEdgeEnabled(true);
setVerticalScrollBarEnabled(true);
setVerticalFadingEdgeEnabled(true);
addView(vl);
}
private void addNavButtons(ViewGroup vg) {
LinearLayout.LayoutParams lpChild1 = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 1);
LinearLayout.LayoutParams lpWrap10 = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 10);
Context context = vg.getContext();
LinearLayout hl = new LinearLayout(context);
hl.setLayoutParams(lpWrap10);
hl.setOrientation(LinearLayout.HORIZONTAL);
// zoom out button
bZoomOut = new ImageButton(context);
bZoomOut.setBackgroundDrawable(null);
bZoomOut.setLayoutParams(lpChild1);
bZoomOut.setImageResource(getZoomOutImageResource());
bZoomOut.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
zoomOut();
}
});
hl.addView(bZoomOut);
// zoom in button
bZoomIn = new ImageButton(context);
bZoomIn.setBackgroundDrawable(null);
bZoomIn.setLayoutParams(lpChild1);
bZoomIn.setImageResource(getZoomInImageResource());
bZoomIn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
zoomIn();
}
});
hl.addView(bZoomIn);
// prev button
ImageButton bPrev = new ImageButton(context);
bPrev.setBackgroundDrawable(null);
bPrev.setLayoutParams(lpChild1);
//bPrev.setText("<");
//bPrev.setWidth(40);
bPrev.setImageResource(getPreviousPageImageResource());
bPrev.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
prevPage();
}
});
hl.addView(bPrev);
// page button
mBtPage = new Button(context);
mBtPage.setLayoutParams(lpChild1);
mBtPage.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
gotoPage();
}
});
hl.addView(mBtPage);
// next button
ImageButton bNext = new ImageButton(context);
bNext.setBackgroundDrawable(null);
bNext.setLayoutParams(lpChild1);
bNext.setImageResource(getNextPageImageResource());
bNext.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
nextPage();
}
});
hl.addView(bNext);
vg.addView(hl);
addSpace(vg, 6, 6);
}
private void addSpace(ViewGroup vg, int width, int height) {
TextView tvSpacer = new TextView(vg.getContext());
tvSpacer.setLayoutParams(new LinearLayout.LayoutParams(width, height, 1));
tvSpacer.setText(null);
vg.addView(tvSpacer);
}
private void updateImage() {
uiHandler.post(new Runnable() {
public void run() {
pdfView.setImageBitmap(mBi);
}
});
}
private void updateUi() {
uiHandler.post(new Runnable() {
public void run() {
updateTexts();
}
});
}
private void setPageBitmap(Bitmap bi) {
if (bi != null) {
mBi = bi;
}
}
protected void updateTexts() {
if (mPdfPage != null) {
if (mBtPage != null)
mBtPage.setText(mPdfPage.getPageNumber() + "/" + mPdfFile.getNumPages());
if (mBtPage2 != null)
mBtPage2.setText(mPdfPage.getPageNumber() + "/" + mPdfFile.getNumPages());
}
}
}
private void showPage(int page, float zoom) throws Exception {
try {
// free memory from previous page
mGraphView.setPageBitmap(null);
mGraphView.updateImage();
// Only load the page if it's a different page (i.e. not just changing the zoom level)
if (mPdfPage == null || mPdfPage.getPageNumber() != page) {
mPdfPage = mPdfFile.getPage(page, true);
}
float width = mPdfPage.getWidth();
float height = mPdfPage.getHeight();
RectF clip = null;
Bitmap bi = mPdfPage.getImage((int) (width * zoom), (int) (height * zoom), clip, true, true);
mGraphView.setPageBitmap(bi);
mGraphView.updateImage();
if (progress != null) progress.dismiss();
} catch (Throwable e) {
Log.e(TAG, e.getMessage(), e);
}
}
/**
* <p>Open a specific pdf file. Creates a DocumentInfo from the file,
* and opens that.</p>
* <p/>
* <p><b>Note:</b> Mapping the file locks the file until the PDFFile
* is closed.</p>
*
* @param byteArray the file to open
* @throws IOException
*/
public void openFile(final byte[] byteArray, String password) throws IOException {
if (byteArray != null) {
// now memory-map a byte-buffer
ByteBuffer bb = ByteBuffer.NEW(byteArray);
// create a PDFFile from the data
if (password == null) {
mPdfFile = new PDFFile(bb);
} else {
mPdfFile = new PDFFile(bb, new PDFPassword(password));
}
} else {
Toast.makeText(this, "The error occurred", Toast.LENGTH_LONG).show();
}
}
@Override
public void onDestroy() {
super.onDestroy();
byteArray = null;
}
private int getPreviousPageImageResource() {
return R.drawable.left_arrow;
}
private int getNextPageImageResource() {
return R.drawable.right_arrow;
}
private int getZoomInImageResource() {
return R.drawable.zoom_in;
}
private int getZoomOutImageResource() {
return R.drawable.zoom_out;
}
private int getPdfPasswordLayoutResource() {
return R.layout.pdf_file_password;
}
private int getPdfPageNumberResource() {
return R.layout.dialog_pagenumber;
}
private int getPdfPasswordEditField() {
return R.id.etPassword;
}
private int getPdfPasswordOkButton() {
return R.id.btOK;
}
private int getPdfPasswordExitButton() {
return R.id.btExit;
}
private int getPdfPageNumberEditField() {
return R.id.pagenum_edit;
}
}

View File

@ -1,435 +0,0 @@
package net.sf.andpdf.pdfviewer;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFImage;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PDFPaint;
import com.sun.pdfview.decrypt.PDFAuthenticationFailureException;
import com.sun.pdfview.decrypt.PDFPassword;
import com.sun.pdfview.font.PDFFont;
import net.sf.andpdf.nio.ByteBuffer;
import net.sf.andpdf.refs.HardReference;
import java.io.IOException;
import uk.co.senab.photoview.PhotoViewAttacher;
import static android.content.Context.INPUT_METHOD_SERVICE;
/**
* U:\Android\android-sdk-windows-1.5_r1\tools\adb push u:\Android\simple_T.pdf /data/test.pdf
*
* @author ferenc.hechler
*/
public class PdfViewerFragment extends Fragment {
private static final int STARTPAGE = 1;
private static final String TAG = "PDFVIEWER";
public static final boolean DEFAULTSHOWIMAGES = true;
public static final boolean DEFAULTANTIALIAS = true;
public static final boolean DEFAULTUSEFONTSUBSTITUTION = false;
private GraphView mGraphView;
private PDFFile mPdfFile;
public static byte[] byteArray;
private int mPage;
private ProgressDialog progress;
private TextView pageNumbersView;
private PDFPage mPdfPage;
private Thread backgroundThread;
private Handler uiHandler;
private boolean passwordNeeded;
private String password;
@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
uiHandler = new Handler();
progress = ProgressDialog.show(getActivity(), "Loading", "Loading PDF Page", true, true);
mGraphView = new GraphView(getActivity());
PDFImage.sShowImages = PdfViewerFragment.DEFAULTSHOWIMAGES;
PDFPaint.s_doAntiAlias = PdfViewerFragment.DEFAULTANTIALIAS;
PDFFont.sUseFontSubstitution = PdfViewerFragment.DEFAULTUSEFONTSUBSTITUTION;
HardReference.sKeepCaches = true;
mPage = STARTPAGE;
final Toolbar toolbar = (Toolbar) LayoutInflater.from(getActivity()).inflate(R.layout.pfd_toolbar, null);
toolbar.setContentInsetsAbsolute(0, 0);
toolbar.findViewById(R.id.pdf_toolbar_close_image).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
getFragmentManager().popBackStack();
}
});
replaceView(((ActionBarHidden) getActivity()).getCurrentActivityToolbarReplacedBy(toolbar), toolbar);
pageNumbersView = (TextView) toolbar.findViewById(R.id.pdf_toolbar_page_numbers_text_view);
return setContent(password);
}
@Override
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (view == null) {
getFragmentManager().popBackStack();
return;
}
if (!passwordNeeded) {
startRenderThread(mPage);
} else {
hideProgressBar();
final EditText etPW = (EditText) view.findViewById(getPdfPasswordEditField());
Button btOK = (Button) view.findViewById(getPdfPasswordOkButton());
Button btExit = (Button) view.findViewById(getPdfPasswordExitButton());
btOK.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
password = etPW.getText().toString();
getFragmentManager()
.beginTransaction()
.detach(PdfViewerFragment.this)
.attach(PdfViewerFragment.this)
.commit();
hideSoftInput();
}
});
btExit.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput();
getFragmentManager().popBackStack();
}
});
}
}
@Nullable
private View setContent(@Nullable final String password) {
try {
openFile(byteArray, password);
passwordNeeded = false;
return mGraphView;
} catch (PDFAuthenticationFailureException e) {
passwordNeeded = true;
return LayoutInflater.from(getActivity()).inflate(R.layout.pdf_file_password, null);
} catch (Exception ex) {
Log.e(TAG, "an unexpected exception occurred");
}
return null;
}
private synchronized void startRenderThread(final int page) {
if (backgroundThread != null) return;
backgroundThread = new Thread(new Runnable() {
public void run() {
try {
if (mPdfFile != null) {
showPage(page);
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
backgroundThread = null;
}
});
updateImageStatus();
backgroundThread.start();
}
private void updateImageStatus() {
if (backgroundThread == null) {
return;
}
mGraphView.postDelayed(new Runnable() {
public void run() {
updateImageStatus();
}
}, 1000);
}
private void nextPage() {
if (mPdfFile != null) {
if (mPage < mPdfFile.getNumPages()) {
mPage += 1;
updatePageNumbersView();
progress = ProgressDialog.show(getActivity(), "Loading", "Loading PDF Page " + mPage, true, true);
startRenderThread(mPage);
}
}
}
private void prevPage() {
if (mPdfFile != null) {
if (mPage > 1) {
mPage -= 1;
updatePageNumbersView();
progress = ProgressDialog.show(getActivity(), "Loading", "Loading PDF Page " + mPage, true, true);
startRenderThread(mPage);
}
}
}
private void updatePageNumbersView() {
if (mPdfPage != null) {
if (mPdfPage.getPageNumber() == mPdfFile.getNumPages() && mPdfPage.getPageNumber() == 1) {
pageNumbersView.setVisibility(View.GONE);
} else {
pageNumbersView.setText(mPdfPage.getPageNumber() + "/" + mPdfFile.getNumPages());
}
}
}
/**
* Hides device keyboard that is showing over {@link Activity}.
* Do NOT use it if keyboard is over {@link android.app.Dialog} - it won't work as they have different {@link Activity#getWindow()}.
*/
public void hideSoftInput() {
if (getActivity().getCurrentFocus() == null) {
return;
}
final InputMethodManager inputManager = (InputMethodManager) getActivity().getSystemService(INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(), 0);
getActivity().getWindow().getDecorView().requestFocus();
}
private class GraphView extends FrameLayout {
public Bitmap mBi;
public ImageView pdfZoomedImageView;
public PhotoViewAttacher photoViewAttacher;
public GraphView(Context context) {
super(context);
final FrameLayout.LayoutParams frameLayout = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
frameLayout.gravity = Gravity.CENTER;
pdfZoomedImageView = new ImageView(context);
pdfZoomedImageView.setLayoutParams(frameLayout);
pdfZoomedImageView.setBackgroundColor(getContext().getResources().getColor(R.color.zoomed_image_view_background));
photoViewAttacher = new PhotoViewAttacher(pdfZoomedImageView);
photoViewAttacher.setOnSingleFlingListener(new OnSwipeTouchListener() {
@Override
public void onSwipeLeft() {
nextPage();
}
@Override
public void onSwipeRight() {
prevPage();
}
});
setPageBitmap(null);
//updateImage();
final LinearLayout.LayoutParams linearLayout = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
setLayoutParams(linearLayout);
addView(pdfZoomedImageView);
}
private void updateImage() {
uiHandler.post(new Runnable() {
public void run() {
pdfZoomedImageView.setImageBitmap(mBi);
photoViewAttacher.update();
updatePageNumbersView();
}
});
}
private void setPageBitmap(Bitmap bi) {
if (bi != null) {
mBi = bi;
}
}
}
// TODO: refactor
private void showPage(final int page) {
// on some Android getWidth() and getHeight() returns 0, so we need to wait until UI is ready
try {
// free memory from previous page
mGraphView.setPageBitmap(null);
mGraphView.updateImage();
// Only load the page if it's a different page (i.e. not just changing the zoom level)
if (mPdfPage == null || mPdfPage.getPageNumber() != page) {
mPdfPage = mPdfFile.getPage(page, true);
}
final int scale = 3;
double width = mPdfPage.getWidth() * scale;
double height = mPdfPage.getHeight() * scale;
int maxWidthToPopulate = mGraphView.getWidth();
int maxHeightToPopulate = mGraphView.getHeight();
if (maxWidthToPopulate == 0 || maxHeightToPopulate == 0) {
mGraphView.post(new Runnable() {
@Override
public void run() {
showPage(page);
}
});
return;
}
int calculatedWidth;
int calculatedHeight;
final double widthRatio = width / maxWidthToPopulate;
final double heightRatio = height / maxHeightToPopulate;
if (width < maxWidthToPopulate && height < maxHeightToPopulate) {
if (widthRatio > heightRatio) {
calculatedWidth = (int) (width / widthRatio);
calculatedHeight = (int) (height / widthRatio);
} else {
calculatedWidth = (int) (width / heightRatio);
calculatedHeight = (int) (height / heightRatio);
}
} else {
if (widthRatio > 1 && heightRatio > 1) {
if (widthRatio > heightRatio) {
calculatedHeight = (int) (height / widthRatio);
calculatedWidth = (int) (width / widthRatio);
} else {
calculatedHeight = (int) (height / heightRatio);
calculatedWidth = (int) (width / heightRatio);
}
} else {
if (widthRatio > heightRatio) {
calculatedHeight = (int) (height / widthRatio);
calculatedWidth = (int) (width / widthRatio);
} else {
calculatedHeight = (int) (height / heightRatio);
calculatedWidth = (int) (width / heightRatio);
}
}
}
final Bitmap bitmap = mPdfPage.getImage(calculatedWidth, calculatedHeight, null, true, true);
mGraphView.setPageBitmap(bitmap);
mGraphView.updateImage();
} catch (Throwable e) {
Log.e(TAG, e.getMessage(), e);
}
hideProgressBar();
}
private void hideProgressBar() {
if (progress != null) {
progress.dismiss();
}
}
/**
* <p>Open a specific pdf file. Creates a DocumentInfo from the file,
* and opens that.</p>
* <p/>
* <p><b>Note:</b> Mapping the file locks the file until the PDFFile
* is closed.</p>
*
* @param byteArray the file to open
* @throws IOException
*/
public void openFile(final byte[] byteArray, String password) throws IOException {
if (byteArray != null) {
// now memory-map a byte-buffer
ByteBuffer bb = ByteBuffer.NEW(byteArray);
// create a PDFFile from the data
if (password == null) {
mPdfFile = new PDFFile(bb);
} else {
mPdfFile = new PDFFile(bb, new PDFPassword(password));
}
} else {
hideProgressBar();
getFragmentManager().popBackStack();
}
}
@Override
public void onDestroy() {
super.onDestroy();
byteArray = null;
if (mGraphView != null) {
mGraphView.mBi = null;
mGraphView.photoViewAttacher.cleanup();
}
}
public static ViewGroup getParent(View view) {
return (ViewGroup) view.getParent();
}
public static void removeView(View view) {
ViewGroup parent = getParent(view);
if (parent != null) {
parent.removeView(view);
}
}
public static void replaceView(View currentView, View newView) {
ViewGroup parent = getParent(currentView);
if (parent == null) {
return;
}
final int index = parent.indexOfChild(currentView);
removeView(currentView);
parent.addView(newView, index);
}
private int getPdfPasswordEditField() {
return R.id.etPassword;
}
private int getPdfPasswordOkButton() {
return R.id.btOK;
}
private int getPdfPasswordExitButton() {
return R.id.btExit;
}
public interface ActionBarHidden {
Toolbar getCurrentActivityToolbarReplacedBy(@NonNull final Toolbar toolbar);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.andpdf.utils;
import android.graphics.PointF;
import android.util.FloatMath;
import android.view.MotionEvent;
public class MathUtils {
public static float distance(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
public static float distance(PointF p1, PointF p2) {
float x = p1.x - p2.x;
float y = p1.y - p2.y;
return FloatMath.sqrt(x * x + y * y);
}
public static float distance(float x1, float y1, float x2, float y2) {
float x = x1 - x2;
float y = y1 - y2;
return FloatMath.sqrt(x * x + y * y);
}
public static void midpoint(MotionEvent event, PointF point) {
float x1 = event.getX(0);
float y1 = event.getY(0);
float x2 = event.getX(1);
float y2 = event.getY(1);
midpoint(x1, y1, x2, y2, point);
}
public static void midpoint(float x1, float y1, float x2, float y2, PointF point) {
point.x = (x1 + x2) / 2.0f;
point.y = (y1 + y2) / 2.0f;
}
/**
* Rotates p1 around p2 by angle degrees.
*
* @param p1
* @param p2
* @param angle
*/
public void rotate(PointF p1, PointF p2, float angle) {
float px = p1.x;
float py = p1.y;
float ox = p2.x;
float oy = p2.y;
p1.x = (FloatMath.cos(angle) * (px - ox) - FloatMath.sin(angle) * (py - oy) + ox);
p1.y = (FloatMath.sin(angle) * (px - ox) + FloatMath.cos(angle) * (py - oy) + oy);
}
public static float angle(PointF p1, PointF p2) {
return angle(p1.x, p1.y, p2.x, p2.y);
}
public static float angle(float x1, float y1, float x2, float y2) {
return (float) Math.atan2(y2 - y1, x2 - x1);
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.andpdf.utils;
import android.graphics.PointF;
import android.util.FloatMath;
import android.view.MotionEvent;
public class VectorF {
public float angle;
public float length;
public final PointF start = new PointF();
public final PointF end = new PointF();
public void calculateEndPoint() {
end.x = FloatMath.cos(angle) * length + start.x;
end.y = FloatMath.sin(angle) * length + start.y;
}
public void setStart(PointF p) {
this.start.x = p.x;
this.start.y = p.y;
}
public void setEnd(PointF p) {
this.end.x = p.x;
this.end.y = p.y;
}
public void set(MotionEvent event) {
this.start.x = event.getX(0);
this.start.y = event.getY(0);
this.end.x = event.getX(1);
this.end.y = event.getY(1);
}
public float calculateLength() {
length = MathUtils.distance(start, end);
return length;
}
public float calculateAngle() {
angle = MathUtils.angle(start, end);
return angle;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:width="3dp"/>
<solid android:color="@android:color/black"/>
</shape>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#3f192d47"/>
</shape>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/global_close_button_shape" android:state_pressed="true"/>
</selector>

View File

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="#0c0d0e">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/pdf_toolbar_close_image"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginRight="31dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:scaleType="centerInside"
android:src="@drawable/global_close_button_normal"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Preview"
android:textColor="@android:color/white"
android:textSize="20sp"/>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<TextView
android:id="@+id/pdf_toolbar_page_numbers_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="21dp"
android:textColor="@android:color/white"
android:textSize="20sp"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>

View File

@ -20,51 +20,32 @@
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- filename -->
<!-- filename -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Password protected PDF-File:"
android:textColor="@android:color/black"/>
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0"
android:paddingBottom="0dip"
android:text="Password protected PDF-File:"/>
<EditText
android:id="@+id/etPassword"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:layout_weight="10"
android:hint="please enter password for PDF"
android:inputType="textPassword"
android:textColor="@android:color/black"
android:textColorHint="@android:color/darker_gray"
android:textCursorDrawable="@drawable/color_cursor">
<requestFocus/>
android:layout_height="60px"
android:text=""
android:layout_weight="10"
android:hint="please enter password for PDF">
<requestFocus />
</EditText>
<!-- Buttons 'OK' + 'Exit' -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/btExit"
android:layout_width="120dp"
android:layout_height="40dp"
android:text="Exit"/>
<Button
android:id="@+id/btOK"
android:layout_width="120dp"
android:layout_height="40dp"
android:text="OK"/>
</LinearLayout>
<!-- Buttons 'OK' + 'Exit' -->
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="horizontal" >
<Button android:id="@+id/btExit" android:layout_width="120px" android:layout_height="40px" android:text="Exit"></Button>
<Button android:id="@+id/btOK" android:layout_width="120px" android:layout_height="40px" android:text="OK"></Button>
</LinearLayout>
</LinearLayout>

View File

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="#0c0d0e">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/pdf_toolbar_close_image"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginRight="31dp"
android:background="@drawable/global_light_selector"
android:paddingLeft="7dp"
android:paddingRight="7dp"
android:scaleType="centerInside"
android:src="@drawable/global_close_button_normal"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Preview"
android:textColor="@android:color/white"
android:textSize="20sp"/>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<TextView
android:id="@+id/pdf_toolbar_page_numbers_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="21dp"
android:textColor="@android:color/white"
android:textSize="20sp"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<drawable name="white">#FFFFFFFF</drawable>
<drawable name="black">#000000</drawable>
<drawable name="blue">#000000FF</drawable>
<drawable name="violet">#9370DB</drawable>
<color name="zoomed_image_view_background">#1d2022</color>
</resources>

View File

@ -0,0 +1,18 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 23
buildToolsVersion '23.0.2'
defaultConfig {
minSdkVersion 7
targetSdkVersion 7
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.polites.android"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
</manifest>

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author Jason Polites
*
*/
public interface Animation {
/**
* Transforms the view.
* @param view
* @param diffTime
* @return true if this animation should remain active. False otherwise.
*/
public boolean update(GestureImageView view, long time);
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author Jason Polites
*
*/
public class Animator extends Thread {
private GestureImageView view;
private Animation animation;
private boolean running = false;
private boolean active = false;
private long lastTime = -1L;
public Animator(GestureImageView view, String threadName) {
super(threadName);
this.view = view;
}
@Override
public void run() {
running = true;
while(running) {
while(active && animation != null) {
long time = System.currentTimeMillis();
active = animation.update(view, time - lastTime);
view.redraw();
lastTime = time;
while(active) {
try {
if(view.waitForDraw(32)) { // 30Htz
break;
}
}
catch (InterruptedException ignore) {
active = false;
}
}
}
synchronized(this) {
if(running) {
try {
wait();
}
catch (InterruptedException ignore) {}
}
}
}
}
public synchronized void finish() {
running = false;
active = false;
notifyAll();
}
public void play(Animation transformer) {
if(active) {
cancel();
}
this.animation = transformer;
activate();
}
public synchronized void activate() {
lastTime = System.currentTimeMillis();
active = true;
notifyAll();
}
public void cancel() {
active = false;
}
}

View File

@ -0,0 +1,12 @@
package com.polites.android;
/**
* @author winney E-mail: weiyixiong@tigerbrokers.com
* @version 创建时间: 2015/11/18 下午4:49
*/
public class Direction {
public final static int LEFT = 1;
public final static int TOP = 2;
public final static int RIGHT = 3;
public final static int BOTTOM = 4;
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author Jason Polites
*
*/
public class FlingAnimation implements Animation {
private float velocityX;
private float velocityY;
private float factor = 0.95f;
private float threshold = 10;
private FlingAnimationListener listener;
/* (non-Javadoc)
* @see com.polites.android.Transformer#update(com.polites.android.GestureImageView, long)
*/
@Override
public boolean update(GestureImageView view, long time) {
float seconds = (float) time / 1000.0f;
float dx = velocityX * seconds;
float dy = velocityY * seconds;
velocityX *= factor;
velocityY *= factor;
boolean active = (Math.abs(velocityX) > threshold && Math.abs(velocityY) > threshold);
if(listener != null) {
listener.onMove(dx, dy);
if(!active) {
listener.onComplete();
}
}
return active;
}
public void setVelocityX(float velocityX) {
this.velocityX = velocityX;
}
public void setVelocityY(float velocityY) {
this.velocityY = velocityY;
}
public void setFactor(float factor) {
this.factor = factor;
}
public void setListener(FlingAnimationListener listener) {
this.listener = listener;
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author Jason Polites
*
*/
public interface FlingAnimationListener {
public void onMove(float x, float y);
public void onComplete();
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
/**
* @author Jason Polites
*
*/
public class FlingListener extends SimpleOnGestureListener {
private float velocityX;
private float velocityY;
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
this.velocityX = velocityX;
this.velocityY = velocityY;
return true;
}
public float getVelocityX() {
return velocityX;
}
public float getVelocityY() {
return velocityY;
}
}

View File

@ -0,0 +1,698 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
import android.content.Context;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import java.io.InputStream;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class GestureImageView extends ImageView {
public static final String GLOBAL_NS = "http://schemas.android.com/apk/res/android";
public static final String LOCAL_NS = "http://schemas.polites.com/android";
private final Semaphore drawLock = new Semaphore(0);
private Animator animator;
private Drawable drawable;
private int drawableWidth = 0;
private int drawableHeight = 0;
private float x = 0, y = 0;
private float scaleAdjust = 1.0f;
private float startingScale = -1.0f;
private float scale = 1.0f;
private float maxScale = 5.0f;
private float minScale = 0.75f;
private float fitScaleHorizontal = 1.0f;
private float fitScaleVertical = 1.0f;
private float rotation = 0.0f;
private float centerX;
private float centerY;
private Float startX, startY;
private int hWidth;
private int hHeight;
private int resId = -1;
private boolean recycle = false;
private boolean strict = false;
private int displayHeight;
private int displayWidth;
private int alpha = 255;
private ColorFilter colorFilter;
private int deviceOrientation = -1;
private int imageOrientation;
private GestureImageViewListener gestureImageViewListener;
private GestureImageViewTouchListener gestureImageViewTouchListener;
private GestureImageViewTouchListener.OnReachBoundListener onReachBoundListener;
private OnTouchListener customOnTouchListener;
private OnClickListener onClickListener;
public GestureImageView(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs);
}
public GestureImageView(Context context, AttributeSet attrs) {
super(context, attrs);
String scaleType = attrs.getAttributeValue(GLOBAL_NS, "scaleType");
if (scaleType == null || scaleType.trim().length() == 0) {
setScaleType(ScaleType.CENTER_INSIDE);
}
String strStartX = attrs.getAttributeValue(LOCAL_NS, "start-x");
String strStartY = attrs.getAttributeValue(LOCAL_NS, "start-y");
if (strStartX != null && strStartX.trim().length() > 0) {
startX = Float.parseFloat(strStartX);
}
if (strStartY != null && strStartY.trim().length() > 0) {
startY = Float.parseFloat(strStartY);
}
setStartingScale(attrs.getAttributeFloatValue(LOCAL_NS, "start-scale", startingScale));
setMinScale(attrs.getAttributeFloatValue(LOCAL_NS, "min-scale", minScale));
setMaxScale(attrs.getAttributeFloatValue(LOCAL_NS, "max-scale", maxScale));
setStrict(attrs.getAttributeBooleanValue(LOCAL_NS, "strict", strict));
setRecycle(attrs.getAttributeBooleanValue(LOCAL_NS, "recycle", recycle));
initImage();
}
public GestureImageView(Context context) {
super(context);
setScaleType(ScaleType.CENTER_INSIDE);
initImage();
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (drawable != null) {
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
displayHeight = MeasureSpec.getSize(heightMeasureSpec);
if (getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
float ratio = (float) getImageWidth() / (float) getImageHeight();
displayWidth = Math.round((float) displayHeight * ratio);
} else {
displayWidth = MeasureSpec.getSize(widthMeasureSpec);
}
} else {
displayWidth = MeasureSpec.getSize(widthMeasureSpec);
if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
float ratio = (float) getImageHeight() / (float) getImageWidth();
displayHeight = Math.round((float) displayWidth * ratio);
} else {
displayHeight = MeasureSpec.getSize(heightMeasureSpec);
}
}
} else {
displayHeight = MeasureSpec.getSize(heightMeasureSpec);
displayWidth = MeasureSpec.getSize(widthMeasureSpec);
}
if (displayWidth == 0 && getImageWidth() != 0) {
displayWidth = getImageWidth();
}
if (displayHeight == 0 && getImageHeight() != 0) {
displayHeight = getImageHeight();
}
setMeasuredDimension(displayWidth, displayHeight);
}
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
setupCanvas(displayWidth, displayHeight, getResources().getConfiguration().orientation);
}
protected void setupCanvas(int measuredWidth, int measuredHeight, int orientation) {
if (deviceOrientation != orientation) {
deviceOrientation = orientation;
}
if (drawable != null) {
int imageWidth = getImageWidth();
int imageHeight = getImageHeight();
hWidth = Math.round(((float) imageWidth / 2.0f));
hHeight = Math.round(((float) imageHeight / 2.0f));
measuredWidth -= (getPaddingLeft() + getPaddingRight());
measuredHeight -= (getPaddingTop() + getPaddingBottom());
computeCropScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
if (startingScale <= 0.0f) {
computeStartingScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
}
scaleAdjust = startingScale;
this.centerX = (float) measuredWidth / 2.0f;
this.centerY = (float) measuredHeight / 2.0f;
if (startX == null) {
x = centerX;
} else {
x = startX;
}
if (startY == null) {
y = centerY;
} else {
y = startY;
}
gestureImageViewTouchListener = new GestureImageViewTouchListener(this, measuredWidth, measuredHeight);
gestureImageViewTouchListener.setOnReachBoundListener(onReachBoundListener);
if (isLandscape()) {
gestureImageViewTouchListener.setMinScale(minScale * fitScaleHorizontal);
} else {
gestureImageViewTouchListener.setMinScale(minScale * fitScaleVertical);
}
gestureImageViewTouchListener.setMaxScale(maxScale * startingScale);
gestureImageViewTouchListener.setFitScaleHorizontal(fitScaleHorizontal);
gestureImageViewTouchListener.setFitScaleVertical(fitScaleVertical);
gestureImageViewTouchListener.setCanvasWidth(measuredWidth);
gestureImageViewTouchListener.setCanvasHeight(measuredHeight);
gestureImageViewTouchListener.setOnClickListener(onClickListener);
drawable.setBounds(-hWidth, -hHeight, hWidth, hHeight);
super.setOnTouchListener(new OnTouchListener() {
@Override public boolean onTouch(View v, MotionEvent event) {
if (customOnTouchListener != null) {
customOnTouchListener.onTouch(v, event);
}
return gestureImageViewTouchListener.onTouch(v, event);
}
});
}
}
protected void computeCropScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
fitScaleHorizontal = (float) measuredWidth / (float) imageWidth;
fitScaleVertical = (float) measuredHeight / (float) imageHeight;
}
protected void computeStartingScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
switch (getScaleType()) {
case CENTER:
// Center the image in the view, but perform no scaling.
startingScale = 1.0f;
break;
case CENTER_CROP:
// Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions
// (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
startingScale =
Math.max((float) measuredHeight / (float) imageHeight, (float) measuredWidth / (float) imageWidth);
break;
case CENTER_INSIDE:
// Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions
// (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).
float wRatio = (float) imageWidth / (float) measuredWidth;
float hRatio = (float) imageHeight / (float) measuredHeight;
if (wRatio > hRatio) {
startingScale = fitScaleHorizontal;
} else {
startingScale = fitScaleVertical;
}
break;
}
}
protected boolean isRecycled() {
if (drawable != null && drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
return bitmap.isRecycled();
}
}
return false;
}
protected void recycle() {
if (recycle && drawable != null && drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
bitmap.recycle();
}
}
}
@Override protected void onDraw(Canvas canvas) {
if (true) {
if (drawable != null && !isRecycled()) {
if (drawableHeight == 0 && drawableWidth == 0) {
return;
}
canvas.save();
float adjustedScale = scale * scaleAdjust;
canvas.translate(x, y);
if (rotation != 0.0f) {
canvas.rotate(rotation);
}
if (adjustedScale != 1.0f) {
canvas.scale(adjustedScale, adjustedScale);
}
//drawable.setBounds(0, 0, drawableWidth, drawableHeight);
drawable.draw(canvas);
canvas.restore();
}
if (drawLock.availablePermits() <= 0) {
drawLock.release();
}
}
}
/**
* Waits for a draw
*
* @param max time to wait for draw (ms)
* @throws InterruptedException
*/
public boolean waitForDraw(long timeout) throws InterruptedException {
return drawLock.tryAcquire(timeout, TimeUnit.MILLISECONDS);
}
@Override protected void onAttachedToWindow() {
animator = new Animator(this, "GestureImageViewAnimator");
animator.start();
if (resId >= 0 && drawable == null) {
setImageResource(resId);
}
super.onAttachedToWindow();
}
public void animationStart(Animation animation) {
if (animator != null) {
animator.play(animation);
}
}
public void animationStop() {
if (animator != null) {
animator.cancel();
}
}
@Override protected void onDetachedFromWindow() {
if (animator != null) {
animator.finish();
}
if (recycle && drawable != null && !isRecycled()) {
recycle();
drawable = null;
}
super.onDetachedFromWindow();
}
protected void initImage() {
if (this.drawable != null) {
drawableWidth = drawable.getIntrinsicWidth();
drawableHeight = drawable.getIntrinsicHeight();
this.drawable.setAlpha(alpha);
this.drawable.setFilterBitmap(true);
if (colorFilter != null) {
this.drawable.setColorFilter(colorFilter);
}
}
requestLayout();
redraw();
}
public void setImageBitmap(Bitmap image) {
this.drawable = new BitmapDrawable(getResources(), image);
initImage();
}
@Override public void setImageDrawable(Drawable drawable) {
this.drawable = drawable;
initImage();
}
public void setImageResource(int id) {
if (this.drawable != null) {
this.recycle();
}
if (id >= 0) {
this.resId = id;
setImageDrawable(getContext().getResources().getDrawable(id));
}
}
public int getScaledWidth() {
return Math.round(getImageWidth() * getScale());
}
public int getScaledHeight() {
return Math.round(getImageHeight() * getScale());
}
public int getImageWidth() {
if (drawable != null) {
return drawable.getIntrinsicWidth();
}
return 0;
}
public int getImageHeight() {
if (drawable != null) {
return drawable.getIntrinsicHeight();
}
return 0;
}
public void moveBy(float x, float y) {
this.x += x;
this.y += y;
}
public void setPosition(float x, float y) {
this.x = x;
this.y = y;
}
public void redraw() {
postInvalidate();
}
public void setMinScale(float min) {
this.minScale = min;
if (gestureImageViewTouchListener != null) {
gestureImageViewTouchListener.setMinScale(min * fitScaleHorizontal);
}
}
public void setMaxScale(float max) {
this.maxScale = max;
if (gestureImageViewTouchListener != null) {
gestureImageViewTouchListener.setMaxScale(max * startingScale);
}
}
public void setScale(float scale) {
scaleAdjust = scale;
}
public float getScale() {
return scaleAdjust;
}
public float getImageX() {
return x;
}
public float getImageY() {
return y;
}
public boolean isStrict() {
return strict;
}
public void setStrict(boolean strict) {
this.strict = strict;
}
public boolean isRecycle() {
return recycle;
}
public void setRecycle(boolean recycle) {
this.recycle = recycle;
}
public void reset() {
x = centerX;
y = centerY;
scaleAdjust = startingScale;
if (gestureImageViewTouchListener != null) {
gestureImageViewTouchListener.reset();
}
redraw();
}
public void setRotation(float rotation) {
this.rotation = rotation;
}
public void setGestureImageViewListener(GestureImageViewListener pinchImageViewListener) {
this.gestureImageViewListener = pinchImageViewListener;
}
public GestureImageViewListener getGestureImageViewListener() {
return gestureImageViewListener;
}
@Override public Drawable getDrawable() {
return drawable;
}
@Override public void setAlpha(int alpha) {
this.alpha = alpha;
if (drawable != null) {
drawable.setAlpha(alpha);
}
}
@Override public void setColorFilter(ColorFilter cf) {
this.colorFilter = cf;
if (drawable != null) {
drawable.setColorFilter(cf);
}
}
@Override public void setImageURI(Uri mUri) {
if ("content".equals(mUri.getScheme())) {
try {
String[] orientationColumn = { MediaStore.Images.Media.ORIENTATION };
Cursor cur = getContext().getContentResolver().query(mUri, orientationColumn, null, null, null);
if (cur != null && cur.moveToFirst()) {
imageOrientation = cur.getInt(cur.getColumnIndex(orientationColumn[0]));
}
InputStream in = null;
try {
in = getContext().getContentResolver().openInputStream(mUri);
Bitmap bmp = BitmapFactory.decodeStream(in);
if (imageOrientation != 0) {
Matrix m = new Matrix();
m.postRotate(imageOrientation);
Bitmap rotated = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
bmp.recycle();
setImageDrawable(new BitmapDrawable(getResources(), rotated));
} else {
setImageDrawable(new BitmapDrawable(getResources(), bmp));
}
} finally {
if (in != null) {
in.close();
}
if (cur != null) {
cur.close();
}
}
} catch (Exception e) {
Log.w("GestureImageView", "Unable to open content: " + mUri, e);
}
} else {
setImageDrawable(Drawable.createFromPath(mUri.toString()));
}
if (drawable == null) {
Log.e("GestureImageView", "resolveUri failed on bad bitmap uri: " + mUri);
// Don't try again.
mUri = null;
}
}
@Override public Matrix getImageMatrix() {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
return super.getImageMatrix();
}
@Override public void setScaleType(ScaleType scaleType) {
if (scaleType == ScaleType.CENTER ||
scaleType == ScaleType.CENTER_CROP ||
scaleType == ScaleType.CENTER_INSIDE) {
super.setScaleType(scaleType);
} else if (strict) {
throw new UnsupportedOperationException("Not supported");
}
}
@Override public void invalidateDrawable(Drawable dr) {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
super.invalidateDrawable(dr);
}
@Override public int[] onCreateDrawableState(int extraSpace) {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
return super.onCreateDrawableState(extraSpace);
}
@Override public void setAdjustViewBounds(boolean adjustViewBounds) {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
super.setAdjustViewBounds(adjustViewBounds);
}
@Override public void setImageLevel(int level) {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
super.setImageLevel(level);
}
@Override public void setImageMatrix(Matrix matrix) {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
}
@Override public void setImageState(int[] state, boolean merge) {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
}
@Override public void setSelected(boolean selected) {
if (strict) {
throw new UnsupportedOperationException("Not supported");
}
super.setSelected(selected);
}
@Override public void setOnTouchListener(OnTouchListener l) {
this.customOnTouchListener = l;
}
public void setOnReachBoundListener(GestureImageViewTouchListener.OnReachBoundListener onReachBoundListener) {
this.onReachBoundListener = onReachBoundListener;
}
public float getCenterX() {
return centerX;
}
public float getCenterY() {
return centerY;
}
public boolean isLandscape() {
return getImageWidth() >= getImageHeight();
}
public boolean isPortrait() {
return getImageWidth() <= getImageHeight();
}
public void setStartingScale(float startingScale) {
this.startingScale = startingScale;
}
public void setStartingPosition(float x, float y) {
this.startX = x;
this.startY = y;
}
@Override public void setOnClickListener(OnClickListener l) {
this.onClickListener = l;
if (gestureImageViewTouchListener != null) {
gestureImageViewTouchListener.setOnClickListener(l);
}
}
/**
* Returns true if the image dimensions are aligned with the orientation of the device.
*/
public boolean isOrientationAligned() {
if (deviceOrientation == Configuration.ORIENTATION_LANDSCAPE) {
return isLandscape();
} else if (deviceOrientation == Configuration.ORIENTATION_PORTRAIT) {
return isPortrait();
}
return true;
}
public int getDeviceOrientation() {
return deviceOrientation;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author jasonpolites
*
*/
public interface GestureImageViewListener {
public void onTouch(float x, float y);
public void onScale(float scale);
public void onPosition(float x, float y);
}

View File

@ -0,0 +1,549 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
public class GestureImageViewTouchListener implements OnTouchListener {
private GestureImageView image;
private OnClickListener onClickListener;
private OnReachBoundListener onReachBoundListener;
private final PointF current = new PointF();
private final PointF last = new PointF();
private final PointF next = new PointF();
private final PointF down = new PointF();
private final PointF midpoint = new PointF();
private final VectorF scaleVector = new VectorF();
private final VectorF pinchVector = new VectorF();
private boolean touched = false;
private boolean inZoom = false;
private float initialDistance;
private float lastScale = 1.0f;
private float currentScale = 1.0f;
private float boundaryLeft = 0;
private float boundaryTop = 0;
private float boundaryRight = 0;
private float boundaryBottom = 0;
private float maxScale = 5.0f;
private float minScale = 0.25f;
private float fitScaleHorizontal = 1.0f;
private float fitScaleVertical = 1.0f;
private int canvasWidth = 0;
private int canvasHeight = 0;
private float centerX = 0;
private float centerY = 0;
private float startingScale = 0;
private boolean canDragX = false;
private boolean canDragY = false;
private boolean multiTouch = false;
private int displayWidth;
private int displayHeight;
private int imageWidth;
private int imageHeight;
private FlingListener flingListener;
private FlingAnimation flingAnimation;
private ZoomAnimation zoomAnimation;
private MoveAnimation moveAnimation;
private GestureDetector tapDetector;
private GestureDetector flingDetector;
private GestureImageViewListener imageListener;
private int dragSlop = 48;
public interface OnReachBoundListener {
void onReach(View view, int direction);
}
public GestureImageViewTouchListener(final GestureImageView image, int displayWidth, int displayHeight) {
super();
this.image = image;
this.displayWidth = displayWidth;
this.displayHeight = displayHeight;
this.centerX = (float) displayWidth / 2.0f;
this.centerY = (float) displayHeight / 2.0f;
this.imageWidth = image.getImageWidth();
this.imageHeight = image.getImageHeight();
startingScale = image.getScale();
currentScale = startingScale;
lastScale = startingScale;
boundaryRight = displayWidth;
boundaryBottom = displayHeight;
boundaryLeft = 0;
boundaryTop = 0;
next.x = image.getImageX();
next.y = image.getImageY();
flingListener = new FlingListener();
flingAnimation = new FlingAnimation();
zoomAnimation = new ZoomAnimation();
moveAnimation = new MoveAnimation();
flingAnimation.setListener(new FlingAnimationListener() {
@Override public void onMove(float x, float y) {
handleDrag(current.x + x, current.y + y);
}
@Override public void onComplete() {
}
});
zoomAnimation.setZoom(2.0f);
zoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() {
@Override public void onZoom(float scale, float x, float y) {
if (scale <= maxScale && scale >= minScale) {
handleScale(scale, x, y);
}
}
@Override public void onComplete() {
inZoom = false;
handleUp();
}
});
moveAnimation.setMoveAnimationListener(new MoveAnimationListener() {
@Override public void onMove(float x, float y) {
image.setPosition(x, y);
image.redraw();
}
});
tapDetector = new GestureDetector(image.getContext(), new SimpleOnGestureListener() {
@Override public boolean onDoubleTap(MotionEvent e) {
startZoom(e);
return true;
}
@Override public boolean onSingleTapConfirmed(MotionEvent e) {
if (!inZoom) {
if (onClickListener != null) {
onClickListener.onClick(image);
return true;
}
}
return false;
}
});
flingDetector = new GestureDetector(image.getContext(), flingListener);
imageListener = image.getGestureImageViewListener();
calculateBoundaries();
}
private void startFling() {
flingAnimation.setVelocityX(flingListener.getVelocityX());
flingAnimation.setVelocityY(flingListener.getVelocityY());
image.animationStart(flingAnimation);
}
private void startZoom(MotionEvent e) {
inZoom = true;
zoomAnimation.reset();
float zoomTo;
if (image.isLandscape()) {
if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
int scaledHeight = image.getScaledHeight();
if (scaledHeight < canvasHeight) {
zoomTo = fitScaleVertical / currentScale;
zoomAnimation.setTouchX(e.getX());
zoomAnimation.setTouchY(image.getCenterY());
} else {
zoomTo = fitScaleHorizontal / currentScale;
zoomAnimation.setTouchX(image.getCenterX());
zoomAnimation.setTouchY(image.getCenterY());
}
} else {
int scaledWidth = image.getScaledWidth();
if (scaledWidth == canvasWidth) {
zoomTo = currentScale * 4.0f;
zoomAnimation.setTouchX(e.getX());
zoomAnimation.setTouchY(e.getY());
} else if (scaledWidth < canvasWidth) {
zoomTo = fitScaleHorizontal / currentScale;
zoomAnimation.setTouchX(image.getCenterX());
zoomAnimation.setTouchY(e.getY());
} else {
zoomTo = fitScaleHorizontal / currentScale;
zoomAnimation.setTouchX(image.getCenterX());
zoomAnimation.setTouchY(image.getCenterY());
}
}
} else {
if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
int scaledHeight = image.getScaledHeight();
if (scaledHeight == canvasHeight) {
zoomTo = currentScale * 4.0f;
zoomAnimation.setTouchX(e.getX());
zoomAnimation.setTouchY(e.getY());
} else if (scaledHeight < canvasHeight) {
zoomTo = fitScaleVertical / currentScale;
zoomAnimation.setTouchX(e.getX());
zoomAnimation.setTouchY(image.getCenterY());
} else {
zoomTo = fitScaleVertical / currentScale;
zoomAnimation.setTouchX(image.getCenterX());
zoomAnimation.setTouchY(image.getCenterY());
}
} else {
int scaledWidth = image.getScaledWidth();
if (scaledWidth < canvasWidth) {
zoomTo = fitScaleHorizontal / currentScale;
zoomAnimation.setTouchX(image.getCenterX());
zoomAnimation.setTouchY(e.getY());
} else {
zoomTo = fitScaleVertical / currentScale;
zoomAnimation.setTouchX(image.getCenterX());
zoomAnimation.setTouchY(image.getCenterY());
}
}
}
zoomAnimation.setZoom(zoomTo);
image.animationStart(zoomAnimation);
}
private void stopAnimations() {
image.animationStop();
}
@Override public boolean onTouch(View v, MotionEvent event) {
if (inZoom || inZoom && tapDetector.onTouchEvent(event)) {
//if (event.getAction() == MotionEvent.ACTION_DOWN) {
// last.x = event.getX();
// last.y = event.getY();
//} else if (event.getAction() == MotionEvent.ACTION_UP) {
// if (event.getX() - last.x > dragSlop) {
// callReachBound(Direction.LEFT);
// } else if (last.x - event.getX() > dragSlop) {
// callReachBound(Direction.RIGHT);
// }
//}
return true;
}
if (event.getPointerCount() == 1 && flingDetector.onTouchEvent(event)) {
startFling();
}
if (event.getAction() == MotionEvent.ACTION_UP) {
handleUp();
} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
stopAnimations();
down.x = event.getX();
down.y = event.getY();
last.x = event.getX();
last.y = event.getY();
if (imageListener != null) {
imageListener.onTouch(last.x, last.y);
}
touched = true;
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (event.getPointerCount() > 1) {
multiTouch = true;
if (initialDistance > 0) {
pinchVector.set(event);
pinchVector.calculateLength();
float distance = pinchVector.length;
if (initialDistance != distance) {
float newScale = (distance / initialDistance) * lastScale;
if (newScale <= maxScale) {
scaleVector.length *= newScale;
scaleVector.calculateEndPoint();
scaleVector.length /= newScale;
float newX = scaleVector.end.x;
float newY = scaleVector.end.y;
handleScale(newScale, newX, newY);
}
}
} else {
initialDistance = MathUtils.distance(event);
MathUtils.midpoint(event, midpoint);
scaleVector.setStart(midpoint);
scaleVector.setEnd(next);
scaleVector.calculateLength();
scaleVector.calculateAngle();
scaleVector.length /= lastScale;
}
} else {
if (!touched) {
touched = true;
last.x = event.getX();
last.y = event.getY();
next.x = image.getImageX();
next.y = image.getImageY();
} else if (!multiTouch) {
if (handleDrag(event.getX(), event.getY())) {
image.redraw();
}
}
}
}
return true;
}
protected void handleUp() {
multiTouch = false;
initialDistance = 0;
lastScale = currentScale;
//TODO can not work when Zoom state
if (!canDragX && current.x - down.x > dragSlop) {
callReachBound(Direction.LEFT);
} else if (!canDragX && down.x - current.x > dragSlop) {
callReachBound(Direction.RIGHT);
} else if (boundaryTop == 0 && current.x - down.y > dragSlop * 0.7) {
callReachBound(Direction.TOP);
} else if (boundaryBottom == 0 && down.y - current.x > dragSlop * 0.7) {
callReachBound(Direction.BOTTOM);
}
if (!canDragX) {
next.x = centerX;
}
if (!canDragY) {
next.y = centerY;
}
boundCoordinates();
if (!canDragX && !canDragY) {
if (image.isLandscape()) {
currentScale = fitScaleHorizontal;
lastScale = fitScaleHorizontal;
} else {
currentScale = fitScaleVertical;
lastScale = fitScaleVertical;
}
}
image.setScale(currentScale);
image.setPosition(next.x, next.y);
if (imageListener != null) {
imageListener.onScale(currentScale);
imageListener.onPosition(next.x, next.y);
}
image.redraw();
}
protected void handleScale(float scale, float x, float y) {
currentScale = scale;
if (currentScale > maxScale) {
currentScale = maxScale;
} else if (currentScale < minScale) {
currentScale = minScale;
} else {
next.x = x;
next.y = y;
}
calculateBoundaries();
image.setScale(currentScale);
image.setPosition(next.x, next.y);
if (imageListener != null) {
imageListener.onScale(currentScale);
imageListener.onPosition(next.x, next.y);
}
image.redraw();
}
protected boolean handleDrag(float x, float y) {
current.x = x;
current.y = y;
float diffX = (current.x - last.x);
float diffY = (current.y - last.y);
if (diffX != 0 || diffY != 0) {
if (canDragX) next.x += diffX;
if (canDragY) next.y += diffY;
boundCoordinates();
last.x = current.x;
last.y = current.y;
if (canDragX || canDragY) {
image.setPosition(next.x, next.y);
if (imageListener != null) {
imageListener.onPosition(next.x, next.y);
}
return true;
}
}
return false;
}
public void reset() {
currentScale = startingScale;
next.x = centerX;
next.y = centerY;
calculateBoundaries();
image.setScale(currentScale);
image.setPosition(next.x, next.y);
image.redraw();
}
public float getMaxScale() {
return maxScale;
}
public void setMaxScale(float maxScale) {
this.maxScale = maxScale;
}
public float getMinScale() {
return minScale;
}
public void setMinScale(float minScale) {
this.minScale = minScale;
}
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
public void setOnReachBoundListener(OnReachBoundListener onReachBoundListener) {
this.onReachBoundListener = onReachBoundListener;
}
protected void setCanvasWidth(int canvasWidth) {
this.canvasWidth = canvasWidth;
}
protected void setCanvasHeight(int canvasHeight) {
this.canvasHeight = canvasHeight;
}
protected void setFitScaleHorizontal(float fitScale) {
this.fitScaleHorizontal = fitScale;
}
protected void setFitScaleVertical(float fitScaleVertical) {
this.fitScaleVertical = fitScaleVertical;
}
protected void boundCoordinates() {
if (next.x < boundaryLeft) {
next.x = boundaryLeft;
} else if (next.x > boundaryRight) {
next.x = boundaryRight;
}
if (next.y < boundaryTop) {
next.y = boundaryTop;
} else if (next.y > boundaryBottom) {
next.y = boundaryBottom;
}
}
protected void callReachBound(int direction) {
if (onReachBoundListener != null) {
onReachBoundListener.onReach(image, direction);
}
}
protected void calculateBoundaries() {
int effectiveWidth = Math.round((float) imageWidth * currentScale);
int effectiveHeight = Math.round((float) imageHeight * currentScale);
canDragX = effectiveWidth > displayWidth;
canDragY = effectiveHeight > displayHeight;
if (canDragX) {
float diff = (float) (effectiveWidth - displayWidth) / 2.0f;
boundaryLeft = centerX - diff;
boundaryRight = centerX + diff;
}
if (canDragY) {
float diff = (float) (effectiveHeight - displayHeight) / 2.0f;
boundaryTop = centerY - diff;
boundaryBottom = centerY + diff;
}
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
import android.graphics.PointF;
import android.view.MotionEvent;
public class MathUtils {
public static float distance(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
public static float distance(PointF p1, PointF p2) {
float x = p1.x - p2.x;
float y = p1.y - p2.y;
return (float) Math.sqrt(x * x + y * y);
}
public static float distance(float x1, float y1, float x2, float y2) {
float x = x1 - x2;
float y = y1 - y2;
return (float) Math.sqrt(x * x + y * y);
}
public static void midpoint(MotionEvent event, PointF point) {
float x1 = event.getX(0);
float y1 = event.getY(0);
float x2 = event.getX(1);
float y2 = event.getY(1);
midpoint(x1, y1, x2, y2, point);
}
public static void midpoint(float x1, float y1, float x2, float y2, PointF point) {
point.x = (x1 + x2) / 2.0f;
point.y = (y1 + y2) / 2.0f;
}
/**
* Rotates p1 around p2 by angle degrees.
*/
public void rotate(PointF p1, PointF p2, float angle) {
float px = p1.x;
float py = p1.y;
float ox = p2.x;
float oy = p2.y;
p1.x = (float) (Math.cos(angle) * (px - ox) - Math.sin(angle) * (py - oy) + ox);
p1.y = (float) (Math.sin(angle) * (px - ox) + Math.cos(angle) * (py - oy) + oy);
}
public static float angle(PointF p1, PointF p2) {
return angle(p1.x, p1.y, p2.x, p2.y);
}
public static float angle(float x1, float y1, float x2, float y2) {
return (float) Math.atan2(y2 - y1, x2 - x1);
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author Jason Polites
*
*/
public class MoveAnimation implements Animation {
private boolean firstFrame = true;
private float startX;
private float startY;
private float targetX;
private float targetY;
private long animationTimeMS = 100;
private long totalTime = 0;
private MoveAnimationListener moveAnimationListener;
/* (non-Javadoc)
* @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long)
*/
@Override
public boolean update(GestureImageView view, long time) {
totalTime += time;
if(firstFrame) {
firstFrame = false;
startX = view.getImageX();
startY = view.getImageY();
}
if(totalTime < animationTimeMS) {
float ratio = (float) totalTime / animationTimeMS;
float newX = ((targetX - startX) * ratio) + startX;
float newY = ((targetY - startY) * ratio) + startY;
if(moveAnimationListener != null) {
moveAnimationListener.onMove(newX, newY);
}
return true;
}
else {
if(moveAnimationListener != null) {
moveAnimationListener.onMove(targetX, targetY);
}
}
return false;
}
public void reset() {
firstFrame = true;
totalTime = 0;
}
public float getTargetX() {
return targetX;
}
public void setTargetX(float targetX) {
this.targetX = targetX;
}
public float getTargetY() {
return targetY;
}
public void setTargetY(float targetY) {
this.targetY = targetY;
}
public long getAnimationTimeMS() {
return animationTimeMS;
}
public void setAnimationTimeMS(long animationTimeMS) {
this.animationTimeMS = animationTimeMS;
}
public void setMoveAnimationListener(MoveAnimationListener moveAnimationListener) {
this.moveAnimationListener = moveAnimationListener;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author Jason Polites
*
*/
public interface MoveAnimationListener {
public void onMove(float x, float y);
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
import android.graphics.PointF;
import android.view.MotionEvent;
public class VectorF {
public float angle;
public float length;
public final PointF start = new PointF();
public final PointF end = new PointF();
public void calculateEndPoint() {
end.x = (float) (Math.cos(angle) * length + start.x);
end.y = (float) (Math.sin(angle) * length + start.y);
}
public void setStart(PointF p) {
this.start.x = p.x;
this.start.y = p.y;
}
public void setEnd(PointF p) {
this.end.x = p.x;
this.end.y = p.y;
}
public void set(MotionEvent event) {
this.start.x = event.getX(0);
this.start.y = event.getY(0);
this.end.x = event.getX(1);
this.end.y = event.getY(1);
}
public float calculateLength() {
length = MathUtils.distance(start, end);
return length;
}
public float calculateAngle() {
angle = MathUtils.angle(start, end);
return angle;
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
import android.graphics.PointF;
/**
* @author Jason Polites
*
*/
public class ZoomAnimation implements Animation {
private boolean firstFrame = true;
private float touchX;
private float touchY;
private float zoom;
private float startX;
private float startY;
private float startScale;
private float xDiff;
private float yDiff;
private float scaleDiff;
private long animationLengthMS = 200;
private long totalTime = 0;
private ZoomAnimationListener zoomAnimationListener;
/* (non-Javadoc)
* @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long)
*/
@Override
public boolean update(GestureImageView view, long time) {
if(firstFrame) {
firstFrame = false;
startX = view.getImageX();
startY = view.getImageY();
startScale = view.getScale();
scaleDiff = (zoom * startScale) - startScale;
if(scaleDiff > 0) {
// Calculate destination for midpoint
VectorF vector = new VectorF();
// Set the touch point as start because we want to move the end
vector.setStart(new PointF(touchX, touchY));
vector.setEnd(new PointF(startX, startY));
vector.calculateAngle();
// Get the current length
float length = vector.calculateLength();
// Multiply length by zoom to get the new length
vector.length = length*zoom;
// Now deduce the new endpoint
vector.calculateEndPoint();
xDiff = vector.end.x - startX;
yDiff = vector.end.y - startY;
}
else {
// Zoom out to center
xDiff = view.getCenterX() - startX;
yDiff = view.getCenterY() - startY;
}
}
totalTime += time;
float ratio = (float) totalTime / (float) animationLengthMS;
if(ratio < 1) {
if(ratio > 0) {
// we still have time left
float newScale = (ratio * scaleDiff) + startScale;
float newX = (ratio * xDiff) + startX;
float newY = (ratio * yDiff) + startY;
if(zoomAnimationListener != null) {
zoomAnimationListener.onZoom(newScale, newX, newY);
}
}
return true;
}
else {
float newScale = scaleDiff + startScale;
float newX = xDiff + startX;
float newY = yDiff + startY;
if(zoomAnimationListener != null) {
zoomAnimationListener.onZoom(newScale, newX, newY);
zoomAnimationListener.onComplete();
}
return false;
}
}
public void reset() {
firstFrame = true;
totalTime = 0;
}
public float getZoom() {
return zoom;
}
public void setZoom(float zoom) {
this.zoom = zoom;
}
public float getTouchX() {
return touchX;
}
public void setTouchX(float touchX) {
this.touchX = touchX;
}
public float getTouchY() {
return touchY;
}
public void setTouchY(float touchY) {
this.touchY = touchY;
}
public long getAnimationLengthMS() {
return animationLengthMS;
}
public void setAnimationLengthMS(long animationLengthMS) {
this.animationLengthMS = animationLengthMS;
}
public ZoomAnimationListener getZoomAnimationListener() {
return zoomAnimationListener;
}
public void setZoomAnimationListener(ZoomAnimationListener zoomAnimationListener) {
this.zoomAnimationListener = zoomAnimationListener;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2012 Jason Polites
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.polites.android;
/**
* @author Jason Polites
*
*/
public interface ZoomAnimationListener {
public void onZoom(float scale, float x, float y);
public void onComplete();
}

1
pdfviewsample/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,27 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.wyx.pdf"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile project(':PdfView')
}

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":pdfviewsample" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Android-Pdf-Viewer-Library" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":pdfviewsample" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="support-v4-23.1.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.1.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.1.1" level="project" />
<orderEntry type="module" module-name="PdfView" exported="" />
</component>
</module>

17
pdfviewsample/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/winney/Documents/android-sdk-macosx/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -0,0 +1,13 @@
package com.wyx.pdfviewsample;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wyx.pdfviewsample"
>
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
>
<activity android:name=".MainActivity">
</activity>
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,242 @@
package com.wyx.pdfviewsample;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Log record tool
*/
@SuppressWarnings({ "unused", "ResultOfMethodCallIgnored" }) public class L {
private static final int LOG_CAT_MAX_LENGTH = 3900;
private static final String TAG_LINE_BREAK = "****";
private static final String EMPTY_LOG = "---";
private static final String ROOT = Environment.getExternalStorageDirectory().getAbsolutePath();
private static final String FILE_NAME = "logger.log";
private static final int WRITE_TO_SD_PRIORITY_LEVEL = Log.DEBUG;
private static String logFile = ROOT + "/" + FILE_NAME;
private static boolean write2SdCard = false;
private static int write2SdPriorityLevel = WRITE_TO_SD_PRIORITY_LEVEL;
private static boolean debug = true;
public static void setDebug(boolean debug) {
L.debug = debug;
}
public static void setWrite2SdCard(boolean sdCard) {
write2SdCard = sdCard;
}
public static void setWriteToSdPriorityLevel(int level) {
write2SdPriorityLevel = level;
}
public static void exception(Throwable e) {
if (debug && e != null) {
e.printStackTrace();
}
}
public static void exception(Throwable e, String s) {
if (debug && e != null) {
e.printStackTrace();
e(TAG_LINE_BREAK, s);
}
}
public static void w(Object object, Object msg) {
if (debug) {
print(Log.WARN, object, msg);
}
}
public static void w(Object msg) {
if (debug) {
print(Log.WARN, TAG_LINE_BREAK, msg);
}
}
public static void v(Object object, Object msg) {
if (debug) {
print(Log.VERBOSE, object, msg);
}
}
public static void v(Object msg) {
if (debug) {
print(Log.VERBOSE, TAG_LINE_BREAK, msg);
}
}
public static void d(Object object, Object msg) {
if (debug) {
print(Log.DEBUG, object, msg);
}
}
public static void d(Object msg) {
if (debug) {
print(Log.DEBUG, TAG_LINE_BREAK, msg);
}
}
public static void i(Object object, Object msg) {
if (debug) {
print(Log.INFO, object, msg);
}
}
public static void i(Object msg) {
if (debug) {
print(Log.INFO, TAG_LINE_BREAK, msg);
}
}
public static void e(Object object, Object msg) {
if (debug) {
print(Log.ERROR, object, msg);
}
}
public static void e(Object msg) {
if (debug) {
print(Log.ERROR, TAG_LINE_BREAK, msg);
}
}
private static void print(int priority, Object tag, Object msg) {
String s = toString(msg);
printToLogCat(priority, tag, s);
if (write2SdCard) {
writeLog(priority, tag, s);
}
}
private static void printToLogCat(int priority, Object tag, String s) {
if (s.length() > LOG_CAT_MAX_LENGTH) {
println(priority, tag, "log length - " + String.valueOf(s.length()));
int chunkCount = s.length() / LOG_CAT_MAX_LENGTH; // integer division
for (int i = 0; i <= chunkCount; i++) {
int max = LOG_CAT_MAX_LENGTH * (i + 1);
if (max >= s.length()) {
println(priority, "chunk " + i + " of " + chunkCount, s.substring(LOG_CAT_MAX_LENGTH * i, s.length()));
} else {
println(priority, "chunk " + i + " of " + chunkCount, s.substring(LOG_CAT_MAX_LENGTH * i, max));
}
}
} else {
println(priority, tag, s);
}
}
public static void resetLogFile() {
File file = new File(logFile);
file.delete();
try {
file.createNewFile();
} catch (IOException e) {
exception(e);
}
}
private static void writeLog(int priority, Object tag, String s) {
if (TextUtils.isEmpty(s)) {
return;
}
if (priority < write2SdPriorityLevel) {
return;
}
try {
File file = new File(logFile);
if (!file.exists()) {
file.createNewFile();
}
FileWriter writer = new FileWriter(file, true);
writer.flush();
writer.close();
} catch (IOException e) {
exception(e);
}
}
private static void println(int priority, Object tag, String s) {
Log.println(priority, getTagName(tag), s);
}
private static String getTagName(Object tag) {
if (tag instanceof String) {
return (String) tag;
}
if (tag instanceof Class<?>) {
return ((Class<?>) tag).getSimpleName();
} else {
return getTagName(tag.getClass());
}
}
private static String toString(Object msg) {
if (msg == null) {
return EMPTY_LOG;
}
String s = msg.toString();
if (s.isEmpty()) {
return EMPTY_LOG;
} else {
return s;
}
}
public static void printTouchEvent(MotionEvent ev) {
L.e("touch event", actionToString(ev.getAction()));
final int pointerCount = ev.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
L.d("point",
"id[" + i + "]=" + ev.getPointerId(i) + ", x[" + i + "]=" + ev.getX(i) + ", y[" + i + "]=" + ev.getY(i));
}
// L.d("pointer count", pointerCount);
}
public static String actionToString(int action) {
switch (action) {
case MotionEvent.ACTION_DOWN:
return "ACTION_DOWN";
case MotionEvent.ACTION_UP:
return "ACTION_UP";
case MotionEvent.ACTION_CANCEL:
return "ACTION_CANCEL";
case MotionEvent.ACTION_OUTSIDE:
return "ACTION_OUTSIDE";
case MotionEvent.ACTION_MOVE:
return "ACTION_MOVE";
case MotionEvent.ACTION_HOVER_MOVE:
return "ACTION_HOVER_MOVE";
case MotionEvent.ACTION_SCROLL:
return "ACTION_SCROLL";
case MotionEvent.ACTION_HOVER_ENTER:
return "ACTION_HOVER_ENTER";
case MotionEvent.ACTION_HOVER_EXIT:
return "ACTION_HOVER_EXIT";
}
int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
return "ACTION_POINTER_DOWN(" + index + ")";
case MotionEvent.ACTION_POINTER_UP:
return "ACTION_POINTER_UP(" + index + ")";
default:
return Integer.toString(action);
}
}
}

View File

@ -0,0 +1,41 @@
package com.wyx.pdfviewsample;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import java.io.IOException;
import net.sf.andpdf.pdfviewer.gui.PdfView;
import net.sf.andpdf.utils.FileUtils;
public class Main2Activity extends Activity {
PdfView pdfView;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
pdfView = (PdfView) findViewById(R.id.pdf_view);
//ViewGroup.LayoutParams params =
// new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
//GestureImageView view = pdfView.mImageView;
//view.setImageResource(R.drawable.back02);
//view.setLayoutParams(params);
//ViewGroup layout = (ViewGroup) findViewById(R.id.layout);
//layout.addView(view);
View view = pdfView.mImageView;
}
@Override protected void onStart() {
super.onStart();
try {
pdfView.parsePDF(FileUtils.fileFromAsset(this, "sample.pdf"), null);
} catch (IOException e) {
e.printStackTrace();
}
pdfView.startRenderThread(1, 1.0f);
}
}

View File

@ -0,0 +1,58 @@
package com.wyx.pdfviewsample;
import java.io.IOException;
import net.sf.andpdf.pdfviewer.PdfViewerActivity;
import net.sf.andpdf.utils.FileUtils;
public class MainActivity extends PdfViewerActivity {
@Override public String getFileName() {
try {
return FileUtils.fileFromAsset(this, "about.pdf").toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override public int getPreviousPageImageResource() {
return R.drawable.left_arrow;
}
@Override public int getNextPageImageResource() {
return R.drawable.right_arrow;
}
@Override public int getZoomInImageResource() {
return R.drawable.zoom_in;
}
@Override public int getZoomOutImageResource() {
return R.drawable.zoom_out;
}
@Override public int getPdfPasswordLayoutResource() {
return 0;
}
@Override public int getPdfPageNumberResource() {
return 0;
}
@Override public int getPdfPasswordEditField() {
return 0;
}
@Override public int getPdfPasswordOkButton() {
return 0;
}
@Override public int getPdfPasswordExitButton() {
return 0;
}
@Override public int getPdfPageNumberEditField() {
return 0;
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.wyx.pdfviewsample.MainActivity"
>
<TextView
android:text="Hello World!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/layout"
android:orientation="vertical"
>
<net.sf.andpdf.pdfviewer.gui.PdfView
android:id="@+id/pdf_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
/>
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View File

@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">PdfViewSample</string>
</resources>

View File

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@ -0,0 +1,14 @@
package com.wyx.pdfviewsample;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public class ExampleUnitTest {
@Test public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

@ -1 +1,2 @@
include ':PdfView'
include ':PdfView', ':pdfviewsample'
include ':gestureimageview'