diff --git a/Android-Pdf-Viewer-Library.iml b/Android-Pdf-Viewer-Library.iml
new file mode 100644
index 0000000..e6b0607
--- /dev/null
+++ b/Android-Pdf-Viewer-Library.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PdfView/PdfView.iml b/PdfView/PdfView.iml
new file mode 100644
index 0000000..3587982
--- /dev/null
+++ b/PdfView/PdfView.iml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PdfView/build.gradle b/PdfView/build.gradle
new file mode 100644
index 0000000..fef3f0b
--- /dev/null
+++ b/PdfView/build.gradle
@@ -0,0 +1,21 @@
+apply plugin: 'com.android.library'
+android {
+ compileSdkVersion 21
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 4
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+dependencies {
+ compile project(':gestureimageview')
+}
\ No newline at end of file
diff --git a/PdfView/src/main/AndroidManifest.xml b/PdfView/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4e2f248
--- /dev/null
+++ b/PdfView/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PdfView/src/main/java/androswing/tree/DefaultMutableTreeNode.java b/PdfView/src/main/java/androswing/tree/DefaultMutableTreeNode.java
new file mode 100644
index 0000000..0b961bc
--- /dev/null
+++ b/PdfView/src/main/java/androswing/tree/DefaultMutableTreeNode.java
@@ -0,0 +1,30 @@
+package androswing.tree;
+
+import java.util.ArrayList;
+
+public class DefaultMutableTreeNode {
+ private DefaultMutableTreeNode parent;
+ private Object userObject;
+ private ArrayList children;
+ protected DefaultMutableTreeNode(){
+ parent = null;
+ userObject = null;
+ children = new ArrayList();
+ }
+ protected Object getUserObject() {
+ return userObject;
+ }
+ protected void setUserObject(Object userObject) {
+ this.userObject = userObject;
+ }
+
+ public void add(DefaultMutableTreeNode newChild) {
+ newChild.parent = this;
+ children.add(newChild);
+ }
+ public DefaultMutableTreeNode getParent() {
+ return parent;
+ }
+
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/BaseWatchable.java b/PdfView/src/main/java/com/sun/pdfview/BaseWatchable.java
new file mode 100644
index 0000000..ca401cc
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/BaseWatchable.java
@@ -0,0 +1,378 @@
+/*
+ * $Id: BaseWatchable.java,v 1.5 2009/02/09 17:14:32 tomoke 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;
+
+/**
+ * An abstract implementation of the watchable interface, that is extended
+ * by the parser and renderer to do their thing.
+ */
+public abstract class BaseWatchable implements Watchable, Runnable {
+
+ /** the current status, from the list in Watchable */
+ private int status = Watchable.UNKNOWN;
+ /** a lock for status-related operations */
+ private Object statusLock = new Object();
+ /** a lock for parsing operations */
+ private Object parserLock = new Object();
+ /** when to stop */
+ private Gate gate;
+ /** suppress local stack trace on setError. */
+ private static boolean SuppressSetErrorStackTrace = false;
+ /** the thread we are running in */
+ private Thread thread;
+
+ /**
+ * Creates a new instance of BaseWatchable
+ */
+ protected BaseWatchable() {
+ setStatus(Watchable.NOT_STARTED);
+ }
+
+ /**
+ * Perform a single iteration of this watchable. This is the minimum
+ * granularity which the go() commands operate over.
+ *
+ * @return one of three values:
+ *
Watchable.RUNNING if there is still data to be processed
+ *
Watchable.NEEDS_DATA if there is no data to be processed but
+ * the execution is not yet complete
+ *
Watchable.COMPLETED if the execution is complete
+ *
+ */
+ protected abstract int iterate() throws Exception;
+
+ /**
+ * Prepare for a set of iterations. Called before the first iterate() call
+ * in a sequence. Subclasses should extend this method if they need to do
+ * anything to setup.
+ */
+ protected void setup() {
+ // do nothing
+ }
+
+ /**
+ * Clean up after a set of iterations. Called after iteration has stopped
+ * due to completion, manual stopping, or error.
+ */
+ protected void cleanup() {
+ // do nothing
+ }
+
+ public void run() {
+ // System.out.println(Thread.currentThread().getName() + " starting");
+
+ // call setup once we started
+ if (getStatus() == Watchable.NOT_STARTED) {
+ setup();
+ }
+
+ setStatus(Watchable.PAUSED);
+
+ synchronized (parserLock) {
+ while (!isFinished() && getStatus() != Watchable.STOPPED) {
+ if (isExecutable()) {
+ // set the status to running
+ setStatus(Watchable.RUNNING);
+
+ try {
+ // keep going until the status is no longer running,
+ // our gate tells us to stop, or no-one is watching
+ while ((getStatus() == Watchable.RUNNING) &&
+ (gate == null || !gate.iterate())) {
+ // update the status based on this iteration
+ setStatus(iterate());
+ }
+
+ // make sure we are paused
+ if (getStatus() == Watchable.RUNNING) {
+ setStatus(Watchable.PAUSED);
+ }
+ } catch (Exception ex) {
+ setError(ex);
+ }
+ } else {
+ // System.out.println(getName() + " waiting: status = " + getStatusString());
+ // wait for our status to change
+ synchronized (statusLock) {
+ if (!isExecutable()) {
+ try {
+ statusLock.wait();
+ } catch (InterruptedException ie) {
+ // ignore
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // System.out.println(Thread.currentThread().getName() + " exiting: status = " + getStatusString());
+
+ // call cleanup when we are done
+ if (getStatus() == Watchable.COMPLETED ||
+ getStatus() == Watchable.ERROR) {
+
+ cleanup();
+ }
+
+ // notify that we are no longer running
+ thread = null;
+ }
+
+ /**
+ * Get the status of this watchable
+ *
+ * @return one of the well-known statuses
+ */
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * Return whether this watchable has finished. A watchable is finished
+ * when its status is either COMPLETED, STOPPED or ERROR
+ */
+ public boolean isFinished() {
+ int s = getStatus();
+ return (s == Watchable.COMPLETED ||
+ s == Watchable.ERROR);
+ }
+
+ /**
+ * return true if this watchable is ready to be executed
+ */
+ public boolean isExecutable() {
+ return ((status == Watchable.PAUSED || status == Watchable.RUNNING) &&
+ (gate == null || !gate.stop()));
+ }
+
+ /**
+ * Stop this watchable. Stop will cause all processing to cease,
+ * and the watchable to be destroyed.
+ */
+ public void stop() {
+ setStatus(Watchable.STOPPED);
+ }
+
+ /**
+ * Start this watchable and run in a new thread until it is finished or
+ * stopped.
+ * Note the watchable may be stopped if go() with a
+ * different time is called during execution.
+ */
+ public synchronized void go() {
+ gate = null;
+
+ execute(false);
+ }
+
+ /**
+ * Start this watchable and run until it is finished or stopped.
+ * Note the watchable may be stopped if go() with a
+ * different time is called during execution.
+ *
+ * @param synchronous if true, run in this thread
+ */
+ public synchronized void go(boolean synchronous) {
+ gate = null;
+
+ execute(synchronous);
+ }
+
+ /**
+ * Start this watchable and run for the given number of steps or until
+ * finished or stopped.
+ *
+ * @param steps the number of steps to run for
+ */
+ public synchronized void go(int steps) {
+ gate = new Gate();
+ gate.setStopIterations(steps);
+
+ execute(false);
+ }
+
+ /**
+ * Start this watchable and run for the given amount of time, or until
+ * finished or stopped.
+ *
+ * @param millis the number of milliseconds to run for
+ */
+ public synchronized void go(long millis) {
+ gate = new Gate();
+ gate.setStopTime(millis);
+
+ execute(false);
+ }
+
+ /**
+ * Wait for this watchable to finish
+ */
+ public void waitForFinish() {
+ synchronized (statusLock) {
+ while (!isFinished() && getStatus() != Watchable.STOPPED) {
+ try {
+ statusLock.wait();
+ } catch (InterruptedException ex) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Start executing this watchable
+ *
+ * @param synchronous if true, run in this thread
+ */
+ protected synchronized void execute(boolean synchronous) {
+ // see if we're already running
+ if (thread != null) {
+ // we're already running. Make sure we wake up on any change.
+ synchronized (statusLock) {
+ statusLock.notifyAll();
+ }
+
+ return;
+ } else if (isFinished()) {
+ // we're all finished
+ return;
+ }
+
+ // we'return not running. Start up
+ if (synchronous) {
+ thread = Thread.currentThread();
+ run();
+ } else {
+ thread = new Thread(this);
+ thread.setName(getClass().getName());
+ thread.start();
+ }
+ }
+
+ /**
+ * Set the status of this watchable
+ */
+ protected void setStatus(int status) {
+ synchronized (statusLock) {
+ this.status = status;
+
+ // System.out.println(getName() + " status set to " + getStatusString());
+
+ statusLock.notifyAll();
+ }
+ }
+
+ /**
+ * return true if we would be suppressing setError stack traces.
+ *
+ * @return boolean
+ */
+ public static boolean isSuppressSetErrorStackTrace () {
+ return SuppressSetErrorStackTrace;
+ }
+
+ /**
+ * set suppression of stack traces from setError.
+ *
+ * @param suppressTrace
+ */
+ public static void setSuppressSetErrorStackTrace(boolean suppressTrace) {
+ SuppressSetErrorStackTrace = suppressTrace;
+ }
+
+ /**
+ * Set an error on this watchable
+ */
+ protected void setError(Exception error) {
+ if (!SuppressSetErrorStackTrace) {
+ error.printStackTrace();
+ }
+
+ setStatus(Watchable.ERROR);
+ }
+
+ private String getStatusString() {
+ switch (getStatus()) {
+ case Watchable.NOT_STARTED:
+ return "Not started";
+ case Watchable.RUNNING:
+ return "Running";
+ case Watchable.NEEDS_DATA:
+ return "Needs Data";
+ case Watchable.PAUSED:
+ return "Paused";
+ case Watchable.STOPPED:
+ return "Stopped";
+ case Watchable.COMPLETED:
+ return "Completed";
+ case Watchable.ERROR:
+ return "Error";
+ default:
+ return "Unknown";
+
+ }
+ }
+
+ /** A class that lets us give it a target time or number of steps,
+ * and will tell us to stop after that much time or that many steps
+ */
+ class Gate {
+
+ /** whether this is a time-based (true) or step-based (false) gate */
+ private boolean timeBased;
+ /** the next gate, whether time or iterations */
+ private long nextGate;
+
+ /** set the stop time */
+ public void setStopTime(long millisFromNow) {
+ timeBased = true;
+ nextGate = System.currentTimeMillis() + millisFromNow;
+ }
+
+ /** set the number of iterations until we stop */
+ public void setStopIterations(int iterations) {
+ timeBased = false;
+ nextGate = iterations;
+ }
+
+ /** check whether we should stop.
+ */
+ public boolean stop() {
+ if (timeBased) {
+ return (System.currentTimeMillis() >= nextGate);
+ } else {
+ return (nextGate < 0);
+ }
+ }
+
+ /** Notify the gate of one iteration. Returns true if we should
+ * stop or false if not
+ */
+ public boolean iterate() {
+ if (!timeBased) {
+ nextGate--;
+ }
+
+ return stop();
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/Cache.java b/PdfView/src/main/java/com/sun/pdfview/Cache.java
new file mode 100644
index 0000000..4e04cd0
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/Cache.java
@@ -0,0 +1,315 @@
+/*
+ * $Id: Cache.java,v 1.4 2009/02/12 13:53:56 tomoke 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;
+
+import net.sf.andpdf.refs.SoftReference;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.graphics.Bitmap;
+
+/**
+ * A cache of PDF pages and images.
+ */
+public class Cache {
+
+ /** the pages in the cache, mapped by page number */
+ private Map pages;
+
+ /** Creates a new instance of a Cache */
+ public Cache() {
+ pages = Collections.synchronizedMap(new HashMap());
+ }
+
+ /**
+ * Add a page to the cache. This method should be used for
+ * pages which have already been completely rendered.
+ *
+ * @param pageNumber the page number of this page
+ * @param page the page to add
+ */
+ public void addPage(Integer pageNumber, PDFPage page) {
+ addPageRecord(pageNumber, page, null);
+ }
+
+ /**
+ * Add a page to the cache. This method should be used for
+ * pages which are still in the process of being rendered.
+ *
+ * @param pageNumber the page number of this page
+ * @param page the page to add
+ * @param parser the parser which is parsing this page
+ */
+ public void addPage(Integer pageNumber, PDFPage page, PDFParser parser) {
+ addPageRecord(pageNumber, page, parser);
+ }
+
+ /**
+ * Add an image to the cache. This method should be used for images
+ * which have already been completely rendered
+ *
+ * @param page page this image is associated with
+ * @param info the image info associated with this image
+ * @param image the image to add
+ */
+ public void addImage(PDFPage page, ImageInfo info, Bitmap image) {
+ addImageRecord(page, info, image, null);
+ }
+
+ /**
+ * Add an image to the cache. This method should be used for images
+ * which are still in the process of being rendered.
+ *
+ * @param page the page this image is associated with
+ * @param info the image info associated with this image
+ * @param image the image to add
+ * @param renderer the renderer which is rendering this page
+ */
+ public void addImage(PDFPage page, ImageInfo info, Bitmap image,
+ PDFRenderer renderer) {
+ addImageRecord(page, info, image, renderer);
+ }
+
+ /**
+ * Get a page from the cache
+ *
+ * @param pageNumber the number of the page to get
+ * @return the page, if it is in the cache, or null if not
+ */
+ public PDFPage getPage(Integer pageNumber) {
+ PageRecord rec = getPageRecord(pageNumber);
+ if (rec != null) {
+ return (PDFPage) rec.value;
+ }
+
+ // not found
+ return null;
+ }
+
+ /**
+ * Get a page's parser from the cache
+ *
+ * @param pageNumber the number of the page to get the parser for
+ * @return the parser, or null if it is not in the cache
+ */
+ public PDFParser getPageParser(Integer pageNumber) {
+ PageRecord rec = getPageRecord(pageNumber);
+ if (rec != null) {
+ return (PDFParser) rec.generator;
+ }
+
+ // not found
+ return null;
+ }
+
+ /**
+ * Get an image from the cache
+ *
+ * @param page the page the image is associated with
+ * @param info the image info that describes the image
+ *
+ * @return the image if it is in the cache, or null if not
+ */
+ public Bitmap getImage(PDFPage page, ImageInfo info) {
+ Record rec = getImageRecord(page, info);
+ if (rec != null) {
+ return (Bitmap) rec.value;
+ }
+
+ // not found
+ return null;
+ }
+
+ /**
+ * Get an image's renderer from the cache
+ *
+ * @param page the page this image was generated from
+ * @param info the image info describing the image
+ * @return the renderer, or null if it is not in the cache
+ */
+ public PDFRenderer getImageRenderer(PDFPage page, ImageInfo info) {
+ Record rec = getImageRecord(page, info);
+ if (rec != null) {
+ return (PDFRenderer) rec.generator;
+ }
+
+ // not found
+ return null;
+ }
+
+ /**
+ * Remove a page and all its associated images, as well as its parser
+ * and renderers, from the cache
+ *
+ * @param pageNumber the number of the page to remove
+ */
+ public void removePage(Integer pageNumber) {
+ removePageRecord(pageNumber);
+ }
+
+ /**
+ * Remove an image and its associated renderer from the cache
+ *
+ * @param page the page the image is generated from
+ * @param info the image info of the image to remove
+ */
+ public void removeImage(PDFPage page, ImageInfo info) {
+ removeImageRecord(page, info);
+ }
+
+ /**
+ * The internal routine to add a page to the cache, and return the
+ * page record which was generated
+ */
+ PageRecord addPageRecord(Integer pageNumber, PDFPage page,
+ PDFParser parser) {
+ PageRecord rec = new PageRecord();
+ rec.value = page;
+ rec.generator = parser;
+
+ pages.put(pageNumber, new SoftReference(rec));
+
+ return rec;
+ }
+
+ /**
+ * Get a page's record from the cache
+ *
+ * @return the record, or null if it's not in the cache
+ */
+ PageRecord getPageRecord(Integer pageNumber) {
+ // System.out.println("Request for page " + pageNumber);
+ SoftReference ref = pages.get(pageNumber);
+ if (ref != null) {
+ String val = (ref.get() == null) ? " not in " : " in ";
+ // System.out.println("Page " + pageNumber + val + "cache");
+ return (PageRecord) ref.get();
+ }
+
+ // System.out.println("Page " + pageNumber + " not in cache");
+ // not in cache
+ return null;
+ }
+
+ /**
+ * Remove a page's record from the cache
+ */
+ PageRecord removePageRecord(Integer pageNumber) {
+ SoftReference ref = pages.remove(pageNumber);
+ if (ref != null) {
+ return (PageRecord) ref.get();
+ }
+
+ // not in cache
+ return null;
+ }
+
+ /**
+ * The internal routine to add an image to the cache and return the
+ * record that was generated.
+ */
+ Record addImageRecord(PDFPage page, ImageInfo info,
+ Bitmap image, PDFRenderer renderer) {
+ // first, find or create the relevant page record
+ Integer pageNumber = new Integer(page.getPageNumber());
+ PageRecord pageRec = getPageRecord(pageNumber);
+ if (pageRec == null) {
+ pageRec = addPageRecord(pageNumber, page, null);
+ }
+
+ // next, create the image record
+ Record rec = new Record();
+ rec.value = image;
+ rec.generator = renderer;
+
+ // add it to the cache
+ pageRec.images.put(info, new SoftReference(rec));
+
+ return rec;
+ }
+
+ /**
+ * Get an image's record from the cache
+ *
+ * @return the record, or null if it's not in the cache
+ */
+ Record getImageRecord(PDFPage page, ImageInfo info) {
+ // first find the relevant page record
+ Integer pageNumber = new Integer(page.getPageNumber());
+
+ // System.out.println("Request for image on page " + pageNumber);
+
+ PageRecord pageRec = getPageRecord(pageNumber);
+ if (pageRec != null) {
+ SoftReference ref = pageRec.images.get(info);
+ if (ref != null) {
+ String val = (ref.get() == null) ? " not in " : " in ";
+ // System.out.println("Image on page " + pageNumber + val + " cache");
+ return (Record) ref.get();
+ }
+ }
+
+ // System.out.println("Image on page " + pageNumber + " not in cache");
+ // not found
+ return null;
+ }
+
+ /**
+ * Remove an image's record from the cache
+ */
+ Record removeImageRecord(PDFPage page, ImageInfo info) {
+ // first find the relevant page record
+ Integer pageNumber = new Integer(page.getPageNumber());
+ PageRecord pageRec = getPageRecord(pageNumber);
+ if (pageRec != null) {
+ SoftReference ref = pageRec.images.remove(info);
+ if (ref != null) {
+ return (Record) ref.get();
+ }
+
+ }
+
+ return null;
+ }
+
+ /** the basic information about a page or image */
+ class Record {
+
+ /** the page or image itself */
+ Object value;
+ /** the thing generating the page, or null if done/not provided */
+ BaseWatchable generator;
+ }
+
+ /** the record stored for each page in the cache */
+ class PageRecord extends Record {
+
+ /** any images associated with the page */
+ Map> images;
+
+ /** create a new page record */
+ public PageRecord() {
+ images = Collections.synchronizedMap(new HashMap>());
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/HexDump.java b/PdfView/src/main/java/com/sun/pdfview/HexDump.java
new file mode 100644
index 0000000..73c28cb
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/HexDump.java
@@ -0,0 +1,86 @@
+/*
+ * $Id: HexDump.java,v 1.3 2009/01/16 16:26:12 tomoke 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;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+public class HexDump {
+
+ public static void printData(byte[] data) {
+ char[] parts = new char[17];
+ int partsloc = 0;
+ for (int i = 0; i < data.length; i++) {
+ int d = ((int) data[i]) & 0xff;
+ if (d == 0) {
+ parts[partsloc++] = '.';
+ } else if (d < 32 || d >= 127) {
+ parts[partsloc++] = '?';
+ } else {
+ parts[partsloc++] = (char) d;
+ }
+ if (i % 16 == 0) {
+ int start = Integer.toHexString(data.length).length();
+ int end = Integer.toHexString(i).length();
+
+ for (int j = start; j > end; j--) {
+ System.out.print("0");
+ }
+ System.out.print(Integer.toHexString(i) + ": ");
+ }
+ if (d < 16) {
+ System.out.print("0" + Integer.toHexString(d));
+ } else {
+ System.out.print(Integer.toHexString(d));
+ }
+ if ((i & 15) == 15 || i == data.length - 1) {
+ System.out.println(" " + new String(parts));
+ partsloc = 0;
+ } else if ((i & 7) == 7) {
+ System.out.print(" ");
+ parts[partsloc++] = ' ';
+ } else if ((i & 1) == 1) {
+ System.out.print(" ");
+ }
+ }
+ System.out.println();
+ }
+
+ public static void main(String args[]) {
+ if (args.length != 1) {
+ System.out.println("Usage: ");
+ System.out.println(" HexDump ");
+ System.exit(-1);
+ }
+
+ try {
+ RandomAccessFile raf = new RandomAccessFile(args[0], "r");
+
+ int size = (int) raf.length();
+ byte[] data = new byte[size];
+
+ raf.readFully(data);
+ printData(data);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/Identity8BitCharsetEncoder.java b/PdfView/src/main/java/com/sun/pdfview/Identity8BitCharsetEncoder.java
new file mode 100644
index 0000000..6fc610c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/Identity8BitCharsetEncoder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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;
+
+import com.sun.pdfview.PDFStringUtil;
+
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.Charset;
+import java.nio.CharBuffer;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * A {@link CharsetEncoder} that attempts to write out the lower 8 bits
+ * of any character. Characters >= 256 in value are regarded
+ * as unmappable.
+ *
+ * @author Luke Kirby
+ */
+public class Identity8BitCharsetEncoder extends CharsetEncoder {
+
+ public Identity8BitCharsetEncoder() {
+ super(null, 1, 1);
+ }
+
+ protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
+ while (in.remaining() > 0) {
+ if (out.remaining() < 1) {
+ return CoderResult.OVERFLOW;
+ }
+ final char c = in.get();
+ if (c >= 0 && c < 256) {
+ out.put((byte) c);
+ } else {
+ return CoderResult.unmappableForLength(1);
+ }
+ }
+ return CoderResult.UNDERFLOW;
+ }
+
+ @Override
+ public boolean isLegalReplacement(byte[] repl) {
+ // avoid referencing the non-existent character set
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/PdfView/src/main/java/com/sun/pdfview/ImageInfo.java b/PdfView/src/main/java/com/sun/pdfview/ImageInfo.java
new file mode 100644
index 0000000..cf9b478
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/ImageInfo.java
@@ -0,0 +1,78 @@
+/*
+ * $Id: ImageInfo.java,v 1.3 2009/01/16 16:26:11 tomoke 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;
+
+import android.graphics.Color;
+import android.graphics.RectF;
+
+
+
+public class ImageInfo {
+
+ int width;
+ int height;
+ RectF clip;
+ int bgColor;
+
+ public ImageInfo(int width, int height, RectF clip) {
+ this(width, height, clip, Color.WHITE);
+ }
+
+ public ImageInfo(int width, int height, RectF clip, int bgColor) {
+ this.width = width;
+ this.height = height;
+ this.clip = clip;
+ this.bgColor = bgColor;
+ }
+
+ // a hashcode that uses width, height and clip to generate its number
+ @Override
+ public int hashCode() {
+ int code = (width ^ height << 16);
+
+ if (clip != null) {
+ code ^= ((int) clip.width() | (int) clip.height()) << 8;
+ code ^= ((int) clip.left | (int) clip.top);
+ }
+
+ return code;
+ }
+
+ // an equals method that compares values
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ImageInfo)) {
+ return false;
+ }
+
+ ImageInfo ii = (ImageInfo) o;
+
+ if (width != ii.width || height != ii.height) {
+ return false;
+ } else if (clip != null && ii.clip != null) {
+ return clip.equals(ii.clip);
+ } else if (clip == null && ii.clip == null) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/NameTree.java b/PdfView/src/main/java/com/sun/pdfview/NameTree.java
new file mode 100644
index 0000000..a4110f0
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/NameTree.java
@@ -0,0 +1,126 @@
+/*
+ * $Id: NameTree.java,v 1.3 2009/01/16 16:26:09 tomoke 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;
+
+import java.io.IOException;
+
+/**
+ * A PDF name tree consists of three kinds of nodes:
+ *
+ *
The root node contains only a kids entry, pointing to many
+ * other objects
+ *
An intermediate node contains the limits of all the children in
+ * its subtree, and a kids entry for each child
+ *
A leaf node contains a set of name-to-object mappings in a dictionary,
+ * as well as the limits of the data contained in that child.
+ *
+ * A PDF name tree is sorted in accordance with the String.compareTo() method.
+ */
+public class NameTree {
+
+ /** the root object */
+ private PDFObject root;
+
+ /** Creates a new instance of NameTree */
+ public NameTree(PDFObject root) {
+ this.root = root;
+ }
+
+ /**
+ * Find the PDF object corresponding to the given String in a name tree
+ *
+ * @param key the key we are looking for in the name tree
+ * @return the object associated with str, if found, or null if not
+ */
+ public PDFObject find(String key) throws IOException {
+ return find(root, key);
+ }
+
+ /**
+ * Recursively walk the name tree looking for a given value
+ */
+ private PDFObject find(PDFObject root, String key)
+ throws IOException {
+ // first, look for a Names entry, meaning this is a leaf
+ PDFObject names = root.getDictRef("Names");
+ if (names != null) {
+ return findInArray(names.getArray(), key);
+ }
+
+ // no names given, look for kids
+ PDFObject kidsObj = root.getDictRef("Kids");
+ if (kidsObj != null) {
+ PDFObject[] kids = kidsObj.getArray();
+
+ for (int i = 0; i < kids.length; i++) {
+ // find the limits of this kid
+ PDFObject limitsObj = kids[i].getDictRef("Limits");
+ if (limitsObj != null) {
+ String lowerLimit = limitsObj.getAt(0).getStringValue();
+ String upperLimit = limitsObj.getAt(1).getStringValue();
+
+ // are we in range?
+ if ((key.compareTo(lowerLimit) >= 0) &&
+ (key.compareTo(upperLimit) <= 0)) {
+
+ // we are, so find in this child
+ return find(kids[i], key);
+ }
+ }
+ }
+ }
+
+ // no luck
+ return null;
+ }
+
+ /**
+ * Find an object in a (key,value) array. Do this by splitting in half
+ * repeatedly.
+ */
+ private PDFObject findInArray(PDFObject[] array, String key)
+ throws IOException {
+ int start = 0;
+ int end = array.length / 2;
+
+ while (end >= start && start >= 0 && end < array.length) {
+ // find the key at the midpoint
+ int pos = start + ((end - start) / 2);
+ String posKey = array[pos * 2].getStringValue();
+
+ // compare the key to the key we are looking for
+ int comp = key.compareTo(posKey);
+ if (comp == 0) {
+ // they match. Return the value
+ return array[(pos * 2) + 1];
+ } else if (comp > 0) {
+ // too big, search the top half of the tree
+ start = pos + 1;
+ } else if (comp < 0) {
+ // too small, search the bottom half of the tree
+ end = pos - 1;
+ }
+ }
+
+ // not found
+ return null;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/OutlineNode.java b/PdfView/src/main/java/com/sun/pdfview/OutlineNode.java
new file mode 100644
index 0000000..dfe72fc
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/OutlineNode.java
@@ -0,0 +1,62 @@
+/*
+ * $Id: OutlineNode.java,v 1.3 2009/01/16 16:26:10 tomoke 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;
+
+import androswing.tree.DefaultMutableTreeNode;
+
+import com.sun.pdfview.action.PDFAction;
+
+public class OutlineNode extends DefaultMutableTreeNode {
+ // the name of this node
+
+ private String title;
+
+ /**
+ * Create a new outline node
+ *
+ * @param title the node's visible name in the tree
+ */
+ public OutlineNode(String title) {
+ this.title = title;
+ }
+
+ /**
+ * Get the PDF action associated with this node
+ */
+ public PDFAction getAction() {
+ return (PDFAction) getUserObject();
+ }
+
+ /**
+ * Set the PDF action associated with this node
+ */
+ public void setAction(PDFAction action) {
+ setUserObject(action);
+ }
+
+ /**
+ * Return the node's visible name in the tree
+ */
+ @Override
+ public String toString() {
+ return title;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFCmd.java b/PdfView/src/main/java/com/sun/pdfview/PDFCmd.java
new file mode 100644
index 0000000..5a46277
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFCmd.java
@@ -0,0 +1,61 @@
+/*
+ * $Id: PDFCmd.java,v 1.3 2009/01/16 16:26:13 tomoke 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;
+
+import android.graphics.RectF;
+
+/**
+ * The abstract superclass of all drawing commands for a PDFPage.
+ * @author Mike Wessler
+ */
+public abstract class PDFCmd {
+
+ /**
+ * mark the page or change the graphics state
+ * @param state the current graphics state; may be modified during
+ * execution.
+ * @return the region of the page made dirty by executing this command
+ * or null if no region was touched. Note this value should be
+ * in the coordinates of the image touched, not the page.
+ */
+ public abstract RectF execute(PDFRenderer state);
+
+ /**
+ * a human readable representation of this command
+ */
+ @Override
+ public String toString() {
+ String name = getClass().getName();
+ int lastDot = name.lastIndexOf('.');
+ if (lastDot >= 0) {
+ return name.substring(lastDot + 1);
+ } else {
+ return name;
+ }
+ }
+
+ /**
+ * the details of this command
+ */
+ public String getDetails() {
+ return super.toString();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFDestination.java b/PdfView/src/main/java/com/sun/pdfview/PDFDestination.java
new file mode 100644
index 0000000..f029e7a
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFDestination.java
@@ -0,0 +1,284 @@
+/*
+ * $Id: PDFDestination.java,v 1.3 2009/01/16 16:26:09 tomoke 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;
+
+import java.io.IOException;
+
+/**
+ * Represents a destination in a PDF file. Destinations take 3 forms:
+ *
+ *
An explicit destination, which contains a reference to a page
+ * as well as some stuff about how to fit it into the window.
+ *
A named destination, which uses the PDF file's Dests entry
+ * in the document catalog to map a name to an explicit destination
+ *
A string destintation, which uses the PDF file's Dests entry.
+ * in the name directory to map a string to an explicit destination.
+ *
+ *
+ * All three of these cases are handled by the getDestination() method.
+ */
+public class PDFDestination {
+
+ /** The known types of destination */
+ public static final int XYZ = 0;
+ public static final int FIT = 1;
+ public static final int FITH = 2;
+ public static final int FITV = 3;
+ public static final int FITR = 4;
+ public static final int FITB = 5;
+ public static final int FITBH = 6;
+ public static final int FITBV = 7;
+ /** the type of this destination (from the list above) */
+ private int type;
+ /** the page we refer to */
+ private PDFObject pageObj;
+ /** the left coordinate of the fit area, if applicable */
+ private float left;
+ /** the right coordinate of the fit area, if applicable */
+ private float right;
+ /** the top coordinate of the fit area, if applicable */
+ private float top;
+ /** the bottom coordinate of the fit area, if applicable */
+ private float bottom;
+ /** the zoom, if applicable */
+ private float zoom;
+
+ /**
+ * Creates a new instance of PDFDestination
+ *
+ * @param pageObj the page object this destination refers to
+ * @param type the type of page this object refers to
+ */
+ protected PDFDestination(PDFObject pageObj, int type) {
+ this.pageObj = pageObj;
+ this.type = type;
+ }
+
+ /**
+ * Get a destination from either an array (explicit destination), a
+ * name (named destination) or a string (name tree destination).
+ *
+ * @param obj the PDFObject representing this destination
+ * @param root the root of the PDF object tree
+ */
+ public static PDFDestination getDestination(PDFObject obj, PDFObject root)
+ throws IOException {
+ // resolve string and name issues
+ if (obj.getType() == PDFObject.NAME) {
+ obj = getDestFromName(obj, root);
+ } else if (obj.getType() == PDFObject.STRING) {
+ obj = getDestFromString(obj, root);
+ }
+
+ // make sure we have the right kind of object
+ if (obj == null || obj.getType() != PDFObject.ARRAY) {
+ throw new PDFParseException("Can't create destination from: " + obj);
+ }
+
+ // the array is in the form [page type args ... ]
+ PDFObject[] destArray = obj.getArray();
+
+ // create the destination based on the type
+ PDFDestination dest = null;
+ String type = destArray[1].getStringValue();
+ if (type.equals("XYZ")) {
+ dest = new PDFDestination(destArray[0], XYZ);
+ } else if (type.equals("Fit")) {
+ dest = new PDFDestination(destArray[0], FIT);
+ } else if (type.equals("FitH")) {
+ dest = new PDFDestination(destArray[0], FITH);
+ } else if (type.equals("FitV")) {
+ dest = new PDFDestination(destArray[0], FITV);
+ } else if (type.equals("FitR")) {
+ dest = new PDFDestination(destArray[0], FITR);
+ } else if (type.equals("FitB")) {
+ dest = new PDFDestination(destArray[0], FITB);
+ } else if (type.equals("FitBH")) {
+ dest = new PDFDestination(destArray[0], FITBH);
+ } else if (type.equals("FitBV")) {
+ dest = new PDFDestination(destArray[0], FITBV);
+ } else {
+ throw new PDFParseException("Unknown destination type: " + type);
+ }
+
+ // now fill in the arguments based on the type
+ switch (dest.getType()) {
+ case XYZ:
+ dest.setLeft(destArray[2].getFloatValue());
+ dest.setTop(destArray[3].getFloatValue());
+ dest.setZoom(destArray[4].getFloatValue());
+ break;
+ case FITH:
+ dest.setTop(destArray[2].getFloatValue());
+ break;
+ case FITV:
+ dest.setLeft(destArray[2].getFloatValue());
+ break;
+ case FITR:
+ dest.setLeft(destArray[2].getFloatValue());
+ dest.setBottom(destArray[3].getFloatValue());
+ dest.setRight(destArray[4].getFloatValue());
+ dest.setTop(destArray[5].getFloatValue());
+ break;
+ case FITBH:
+ dest.setTop(destArray[2].getFloatValue());
+ break;
+ case FITBV:
+ dest.setLeft(destArray[2].getFloatValue());
+ break;
+ }
+
+ return dest;
+ }
+
+ /**
+ * Get the type of this destination
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Get the PDF Page object associated with this destination
+ */
+ public PDFObject getPage() {
+ return pageObj;
+ }
+
+ /**
+ * Get the left coordinate value
+ */
+ public float getLeft() {
+ return left;
+ }
+
+ /**
+ * Set the left coordinate value
+ */
+ public void setLeft(float left) {
+ this.left = left;
+ }
+
+ /**
+ * Get the right coordinate value
+ */
+ public float getRight() {
+ return right;
+ }
+
+ /**
+ * Set the right coordinate value
+ */
+ public void setRight(float right) {
+ this.right = right;
+ }
+
+ /**
+ * Get the top coordinate value
+ */
+ public float getTop() {
+ return top;
+ }
+
+ /**
+ * Set the top coordinate value
+ */
+ public void setTop(float top) {
+ this.top = top;
+ }
+
+ /**
+ * Get the bottom coordinate value
+ */
+ public float getBottom() {
+ return bottom;
+ }
+
+ /**
+ * Set the bottom coordinate value
+ */
+ public void setBottom(float bottom) {
+ this.bottom = bottom;
+ }
+
+ /**
+ * Get the zoom value
+ */
+ public float getZoom() {
+ return zoom;
+ }
+
+ /**
+ * Set the zoom value
+ */
+ public void setZoom(float zoom) {
+ this.zoom = zoom;
+ }
+
+ /**
+ * Get a destination, given a name. This means the destination is in
+ * the root node's dests dictionary.
+ */
+ private static PDFObject getDestFromName(PDFObject name, PDFObject root)
+ throws IOException {
+ // find the dests object in the root node
+ PDFObject dests = root.getDictRef("Dests");
+ if (dests != null) {
+ // find this name in the dests dictionary
+ return dests.getDictRef(name.getStringValue());
+ }
+
+ // not found
+ return null;
+ }
+
+ /**
+ * Get a destination, given a string. This means the destination is in
+ * the root node's names dictionary.
+ */
+ private static PDFObject getDestFromString(PDFObject str, PDFObject root)
+ throws IOException {
+ // find the names object in the root node
+ PDFObject names = root.getDictRef("Names");
+ if (names != null) {
+ // find the dests entry in the names dictionary
+ PDFObject dests = names.getDictRef("Dests");
+ if (dests != null) {
+ // create a name tree object
+ NameTree tree = new NameTree(dests);
+
+ // find the value we're looking for
+ PDFObject obj = tree.find(str.getStringValue());
+
+ // if we get back a dictionary, look for the /D value
+ if (obj != null && obj.getType() == PDFObject.DICTIONARY) {
+ obj = obj.getDictRef("D");
+ }
+
+ // found it
+ return obj;
+ }
+ }
+
+ // not found
+ return null;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFDocCharsetEncoder.java b/PdfView/src/main/java/com/sun/pdfview/PDFDocCharsetEncoder.java
new file mode 100644
index 0000000..1c91094
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFDocCharsetEncoder.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Encodes into a PDFDocEncoding representation. Note that only 256 characters
+ * (if that) are represented in the PDFDocEncoding, so users should be
+ * prepared to deal with unmappable character exceptions.
+ *
+ * @see "PDF Reference version 1.7, Appendix D"
+ *
+ * @author Luke Kirby
+ */
+public class PDFDocCharsetEncoder extends CharsetEncoder {
+
+ /**
+ * Identify whether a particular character preserves the same byte value
+ * upon encoding in PDFDocEncoding
+ * @param ch the character
+ * @return whether the character is identity encoded
+ */
+ public static boolean isIdentityEncoding(char ch) {
+ return ch >= 0 && ch <= 255 && IDENT_PDF_DOC_ENCODING_MAP[ch];
+
+ }
+
+ /**
+ * For each character that exists in PDFDocEncoding, identifies whether
+ * the byte value in UTF-16BE is the same as it is in PDFDocEncoding
+ */
+ final static boolean[] IDENT_PDF_DOC_ENCODING_MAP = new boolean[256];
+
+ /**
+ * For non-identity encoded characters, maps from the character to
+ * the byte value in PDFDocEncoding. If an entry for a non-identity
+ * coded character is absent from this map, that character is unmappable
+ * in the PDFDocEncoding.
+ */
+ final static Map EXTENDED_TO_PDF_DOC_ENCODING_MAP =
+ new HashMap();
+ static
+ {
+ for (byte i = 0; i < PDFStringUtil.PDF_DOC_ENCODING_MAP.length; ++i) {
+ final char c = PDFStringUtil.PDF_DOC_ENCODING_MAP[i];
+ final boolean identical = (c == i);
+ IDENT_PDF_DOC_ENCODING_MAP[i] = identical;
+ if (!identical) {
+ EXTENDED_TO_PDF_DOC_ENCODING_MAP.put(c, i);
+ }
+ }
+ }
+
+ public PDFDocCharsetEncoder() {
+ super(null, 1, 1);
+ }
+
+ protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
+ while (in.remaining() > 0) {
+ if (out.remaining() < 1) {
+ return CoderResult.OVERFLOW;
+ }
+ final char c = in.get();
+ if (c >= 0 && c < 256 && IDENT_PDF_DOC_ENCODING_MAP[c]) {
+ out.put((byte) c);
+ } else {
+ final Byte mapped = EXTENDED_TO_PDF_DOC_ENCODING_MAP.get(c);
+ if (mapped != null) {
+ out.put(mapped);
+ } else {
+ return CoderResult.unmappableForLength(1);
+ }
+ }
+ }
+ return CoderResult.UNDERFLOW;
+ }
+
+ @Override
+ public boolean isLegalReplacement(byte[] repl) {
+ // avoid referencing the non-existent character set
+ return true;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFFile.java b/PdfView/src/main/java/com/sun/pdfview/PDFFile.java
new file mode 100644
index 0000000..3be4614
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFFile.java
@@ -0,0 +1,1788 @@
+/*
+ * $Id: PDFFile.java,v 1.15 2009/03/12 12:25:25 tomoke 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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import android.graphics.RectF;
+
+import com.sun.pdfview.action.GoToAction;
+import com.sun.pdfview.action.PDFAction;
+import com.sun.pdfview.decrypt.EncryptionUnsupportedByPlatformException;
+import com.sun.pdfview.decrypt.EncryptionUnsupportedByProductException;
+import com.sun.pdfview.decrypt.IdentityDecrypter;
+import com.sun.pdfview.decrypt.PDFAuthenticationFailureException;
+import com.sun.pdfview.decrypt.PDFDecrypter;
+import com.sun.pdfview.decrypt.PDFDecrypterFactory;
+import com.sun.pdfview.decrypt.PDFPassword;
+import com.sun.pdfview.decrypt.UnsupportedEncryptionException;
+
+/**
+ * An encapsulation of a .pdf file. The methods of this class
+ * can parse the contents of a PDF file, but those methods are
+ * hidden. Instead, the public methods of this class allow
+ * access to the pages in the PDF file. Typically, you create
+ * a new PDFFile, ask it for the number of pages, and then
+ * request one or more PDFPages.
+ * @author Mike Wessler
+ */
+public class PDFFile {
+
+ public final static int NUL_CHAR = 0;
+ public final static int FF_CHAR = 12;
+
+ private String versionString = "1.1";
+ private int majorVersion = 1;
+ private int minorVersion = 1;
+ /** the end of line character */
+ /** the comment text to begin the file to determine it's version */
+ private final static String VERSION_COMMENT = "%PDF-";
+ /**
+ * A ByteBuffer containing the file data
+ */
+ ByteBuffer buf;
+ /**
+ * the cross reference table mapping object numbers to locations
+ * in the PDF file
+ */
+ PDFXref[] objIdx;
+ /** the root PDFObject, as specified in the PDF file */
+ PDFObject root = null;
+ /** the Encrypt PDFObject, from the trailer */
+ PDFObject encrypt = null;
+
+ /** The Info PDFPbject, from the trailer, for simple metadata */
+ PDFObject info = null;
+
+ /** a mapping of page numbers to parsed PDF commands */
+ Cache cache;
+ /**
+ * whether the file is printable or not (trailer -> Encrypt -> P & 0x4)
+ */
+ private boolean printable = true;
+ /**
+ * whether the file is saveable or not (trailer -> Encrypt -> P & 0x10)
+ */
+ private boolean saveable = true;
+
+ /**
+ * The default decrypter for streams and strings. By default, no
+ * encryption is expected, and thus the IdentityDecrypter is used.
+ */
+ private PDFDecrypter defaultDecrypter = IdentityDecrypter.getInstance();
+
+ /**
+ * get a PDFFile from a .pdf file. The file must me a random access file
+ * at the moment. It should really be a file mapping from the nio package.
+ *
+ * Use the getPage(...) methods to get a page from the PDF file.
+ * @param buf the RandomAccessFile containing the PDF.
+ * @throws IOException if there's a problem reading from the buffer
+ * @throws PDFParseException if the document appears to be malformed, or
+ * its features are unsupported. If the file is encrypted in a manner that
+ * the product or platform does not support then the exception's {@link
+ * PDFParseException#getCause() cause} will be an instance of {@link
+ * UnsupportedEncryptionException}.
+ * @throws PDFAuthenticationFailureException if the file is password
+ * protected and requires a password
+ */
+ public PDFFile(ByteBuffer buf) throws IOException {
+ this(buf, null);
+ }
+
+ /**
+ * get a PDFFile from a .pdf file. The file must me a random access file
+ * at the moment. It should really be a file mapping from the nio package.
+ *
+ * Use the getPage(...) methods to get a page from the PDF file.
+ * @param buf the RandomAccessFile containing the PDF.
+ * @param password the user or owner password
+ * @throws IOException if there's a problem reading from the buffer
+ * @throws PDFParseException if the document appears to be malformed, or
+ * its features are unsupported. If the file is encrypted in a manner that
+ * the product or platform does not support then the exception's {@link
+ * PDFParseException#getCause() cause} will be an instance of {@link
+ * UnsupportedEncryptionException}.
+ * @throws PDFAuthenticationFailureException if the file is password
+ * protected and the supplied password does not decrypt the document
+ */
+ public PDFFile(ByteBuffer buf, PDFPassword password) throws IOException {
+ this.buf = buf;
+
+ cache = new Cache();
+
+ parseFile(password);
+ }
+
+ /**
+ * Gets whether the owner of the file has given permission to print
+ * the file.
+ * @return true if it is okay to print the file
+ */
+ public boolean isPrintable() {
+ return printable;
+ }
+
+ /**
+ * Gets whether the owner of the file has given permission to save
+ * a copy of the file.
+ * @return true if it is okay to save the file
+ */
+ public boolean isSaveable() {
+ return saveable;
+ }
+
+ /**
+ * get the root PDFObject of this PDFFile. You generally shouldn't need
+ * this, but we've left it open in case you want to go spelunking.
+ */
+ public PDFObject getRoot() {
+ return root;
+ }
+
+ /**
+ * return the number of pages in this PDFFile. The pages will be
+ * numbered from 1 to getNumPages(), inclusive.
+ */
+ public int getNumPages() {
+ try {
+ return root.getDictRef("Pages").getDictRef("Count").getIntValue();
+ } catch (Exception ioe) {
+ ioe.printStackTrace();
+ return 0;
+ }
+ }
+
+ /**
+ * Get metadata (e.g., Author, Title, Creator) from the Info dictionary
+ * as a string.
+ * @param name the name of the metadata key (e.g., Author)
+ * @return the info
+ * @throws IOException if the metadata cannot be read
+ */
+ public String getStringMetadata(String name)
+ throws IOException {
+ if (info != null) {
+ final PDFObject meta = info.getDictRef(name);
+ return meta != null ? meta.getTextStringValue() : null;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the keys into the Info metadata, for use with
+ * {@link #getStringMetadata(String)}
+ * @return the keys present into the Info dictionary
+ * @throws IOException if the keys cannot be read
+ */
+ public Iterator getMetadataKeys()
+ throws IOException {
+ if (info != null) {
+ return info.getDictKeys();
+ } else {
+ return Collections.emptyList().iterator();
+ }
+ }
+
+
+ /**
+ * Used internally to track down PDFObject references. You should never
+ * need to call this.
+ *
+ * Since this is the only public method for tracking down PDF objects,
+ * it is synchronized. This means that the PDFFile can only hunt down
+ * one object at a time, preventing the file's location from getting
+ * messed around.
+ *
+ * This call stores the current buffer position before any changes are made
+ * and restores it afterwards, so callers need not know that the position
+ * has changed.
+ *
+ */
+ public synchronized PDFObject dereference(PDFXref ref, PDFDecrypter decrypter)
+ throws IOException {
+ int id = ref.getID();
+
+ // make sure the id is valid and has been read
+ if (id >= objIdx.length || objIdx[id] == null) {
+ return PDFObject.nullObj;
+ }
+
+ // check to see if this is already dereferenced
+ PDFObject obj = objIdx[id].getObject();
+ if (obj != null) {
+ return obj;
+ }
+
+ // store the current position in the buffer
+ int startPos = buf.position();
+
+ boolean compressed = objIdx[id].getCompressed();
+ if (!compressed) {
+ int loc = objIdx[id].getFilePos();
+ if (loc < 0) {
+ return PDFObject.nullObj;
+ }
+
+ // move to where this object is
+ buf.position(loc);
+
+ // read the object and cache the reference
+ obj= readObject(ref.getID(), ref.getGeneration(), decrypter);
+ }
+ else { // compressed
+ int compId = objIdx[id].getID();
+ int idx = objIdx[id].getIndex();
+ if (idx < 0)
+ return PDFObject.nullObj;
+ PDFXref compRef = new PDFXref(compId, 0);
+ PDFObject compObj = dereference(compRef, decrypter);
+ int first = compObj.getDictionary().get("First").getIntValue();
+ int length = compObj.getDictionary().get("Length").getIntValue();
+ int n = compObj.getDictionary().get("N").getIntValue();
+ if (idx >= n)
+ return PDFObject.nullObj;
+ ByteBuffer strm = compObj.getStreamBuffer();
+
+ ByteBuffer oldBuf = buf;
+ buf = strm;
+ // skip other nums
+ for (int i=0; i
+ *
+ * ISO 32000-1:2008 - Table 2
+ *
+ * @param c the character to test
+ */
+ public static boolean isDelimiter(int c) {
+ switch (c) {
+ case '(': // LEFT PARENTHESIS
+ case ')': // RIGHT PARENTHESIS
+ case '<': // LESS-THAN-SIGN
+ case '>': // GREATER-THAN-SIGN
+ case '[': // LEFT SQUARE BRACKET
+ case ']': // RIGHT SQUARE BRACKET
+ case '{': // LEFT CURLY BRACKET
+ case '}': // RIGHT CURLY BRACKET
+ case '/': // SOLIDUS
+ case '%': // PERCENT SIGN
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * return true if the character is neither a whitespace or a delimiter.
+ *
+ * @param c the character to test
+ * @return boolean
+ */
+ public static boolean isRegularCharacter (int c) {
+ return !(isWhiteSpace(c) || isDelimiter(c));
+ }
+
+ /**
+ * read the next object from the file
+ * @param objNum the object number of the object containing the object
+ * being read; negative only if the object number is unavailable (e.g., if
+ * reading from the trailer, or reading at the top level, in which
+ * case we can expect to be reading an object description)
+ * @param objGen the object generation of the object containing the object
+ * being read; negative only if the objNum is unavailable
+ * @param decrypter the decrypter to use
+ */
+ private PDFObject readObject(
+ int objNum, int objGen, PDFDecrypter decrypter) throws IOException {
+ return readObject(objNum, objGen, false, decrypter);
+ }
+
+ /**
+ * read the next object with a special catch for numbers
+ * @param numscan if true, don't bother trying to see if a number is
+ * an object reference (used when already in the middle of testing for
+ * an object reference, and not otherwise)
+ * @param objNum the object number of the object containing the object
+ * being read; negative only if the object number is unavailable (e.g., if
+ * reading from the trailer, or reading at the top level, in which
+ * case we can expect to be reading an object description)
+ * @param objGen the object generation of the object containing the object
+ * being read; negative only if the objNum is unavailable
+ * @param decrypter the decrypter to use
+ */
+ private PDFObject readObject(
+ int objNum, int objGen,
+ boolean numscan, PDFDecrypter decrypter) throws IOException {
+ // skip whitespace
+ int c;
+ PDFObject obj = null;
+ while (obj == null) {
+ while (isWhiteSpace(c = buf.get())) {
+ }
+ // check character for special punctuation:
+ if (c == '<') {
+ // could be start of , or start of <>
+ c = buf.get();
+ if (c == '<') {
+ // it's a dictionary
+ obj= readDictionary(objNum, objGen, decrypter);
+ } else {
+ buf.position(buf.position() - 1);
+ obj= readHexString(objNum, objGen, decrypter);
+ }
+ } else if (c == '(') {
+ obj= readLiteralString(objNum, objGen, decrypter);
+ } else if (c == '[') {
+ // it's an array
+ obj= readArray(objNum, objGen, decrypter);
+ } else if (c == '/') {
+ // it's a name
+ obj = readName();
+ } else if (c == '%') {
+ // it's a comment
+ readLine();
+ } else if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.') {
+ // it's a number
+ obj = readNumber((char) c);
+ if (!numscan) {
+ // It could be the start of a reference.
+ // Check to see if there's another number, then "R".
+ //
+ // We can't use mark/reset, since this could be called
+ // from dereference, which already is using a mark
+ int startPos = buf.position();
+
+ PDFObject testnum= readObject(-1, -1, true, decrypter);
+ if (testnum != null &&
+ testnum.getType() == PDFObject.NUMBER) {
+ PDFObject testR= readObject(-1, -1, true, decrypter);
+ if (testR != null &&
+ testR.getType() == PDFObject.KEYWORD &&
+ testR.getStringValue().equals("R")) {
+ // yup. it's a reference.
+ PDFXref xref = new PDFXref(obj.getIntValue(),
+ testnum.getIntValue());
+ // Create a placeholder that will be dereferenced
+ // as needed
+ obj = new PDFObject(this, xref);
+ } else if (testR != null &&
+ testR.getType() == PDFObject.KEYWORD &&
+ testR.getStringValue().equals("obj")) {
+ // it's an object description
+ obj= readObjectDescription(
+ obj.getIntValue(),
+ testnum.getIntValue(),
+ decrypter);
+ } else {
+ buf.position(startPos);
+ }
+ } else {
+ buf.position(startPos);
+ }
+ }
+ } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ // it's a keyword
+ obj = readKeyword((char) c);
+ } else {
+ // it's probably a closing character.
+ // throwback
+ buf.position(buf.position() - 1);
+ break;
+ }
+ }
+ return obj;
+ }
+
+ /**
+ * requires the next few characters (after whitespace) to match the
+ * argument.
+ * @param match the next few characters after any whitespace that
+ * must be in the file
+ * @return true if the next characters match; false otherwise.
+ */
+ private boolean nextItemIs(String match) throws IOException {
+ // skip whitespace
+ int c;
+ while (isWhiteSpace(c = buf.get())) {
+ }
+ for (int i = 0; i < match.length(); i++) {
+ if (i > 0) {
+ c = buf.get();
+ }
+ if (c != match.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * process a version string, to determine the major and minor versions
+ * of the file.
+ *
+ * @param versionString
+ */
+ private void processVersion(String versionString) {
+ try {
+ StringTokenizer tokens = new StringTokenizer(versionString, ".");
+ majorVersion = Integer.parseInt(tokens.nextToken());
+ minorVersion = Integer.parseInt(tokens.nextToken());
+ this.versionString = versionString;
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ /**
+ * return the major version of the PDF header.
+ *
+ * @return int
+ */
+ public int getMajorVersion() {
+ return majorVersion;
+ }
+
+ /**
+ * return the minor version of the PDF header.
+ *
+ * @return int
+ */
+ public int getMinorVersion() {
+ return minorVersion;
+ }
+
+ /**
+ * return the version string from the PDF header.
+ *
+ * @return String
+ */
+ public String getVersionString() {
+ return versionString;
+ }
+
+ /**
+ * read an entire << dictionary >>. The initial
+ * << has already been read.
+ * @param objNum the object number of the object containing the dictionary
+ * being read; negative only if the object number is unavailable, which
+ * should only happen if we're reading a dictionary placed directly
+ * in the trailer
+ * @param objGen the object generation of the object containing the object
+ * being read; negative only if the objNum is unavailable
+ * @param decrypter the decrypter to use
+ * @return the Dictionary as a PDFObject.
+ */
+ private PDFObject readDictionary(
+ int objNum, int objGen, PDFDecrypter decrypter) throws IOException {
+ HashMap hm = new HashMap();
+ // we've already read the <<. Now get /Name obj pairs until >>
+ PDFObject name;
+ while ((name= readObject(objNum, objGen, decrypter))!=null) {
+ // make sure first item is a NAME
+ if (name.getType() != PDFObject.NAME) {
+ throw new PDFParseException("First item in dictionary must be a /Name. (Was " + name + ")");
+ }
+ PDFObject value= readObject(objNum, objGen, decrypter);
+ if (value != null) {
+ hm.put(name.getStringValue(), value);
+ }
+ }
+ // System.out.println("End of dictionary at location "+raf.getFilePointer());
+ if (!nextItemIs(">>")) {
+ throw new PDFParseException("End of dictionary wasn't '>>'");
+ }
+ // System.out.println("Dictionary closed at location "+raf.getFilePointer());
+ return new PDFObject(this, PDFObject.DICTIONARY, hm);
+ }
+
+ /**
+ * read a character, and return its value as if it were a hexidecimal
+ * digit.
+ * @return a number between 0 and 15 whose value matches the next
+ * hexidecimal character. Returns -1 if the next character isn't in
+ * [0-9a-fA-F]
+ */
+ private int readHexDigit() throws IOException {
+ int a;
+ while (isWhiteSpace(a = buf.get())) {
+ }
+ switch (a) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ a -= '0';
+ break;
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ a -= 'a' - 10;
+ break;
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ a -= 'A' - 10;
+ break;
+ default:
+ a = -1;
+ break;
+ }
+ return a;
+ }
+
+ /**
+ * return the 8-bit value represented by the next two hex characters.
+ * If the next two characters don't represent a hex value, return -1
+ * and reset the read head. If there is only one hex character,
+ * return its value as if there were an implicit 0 after it.
+ */
+ private int readHexPair() throws IOException {
+ int first = readHexDigit();
+ if (first < 0) {
+ buf.position(buf.position() - 1);
+ return -1;
+ }
+ int second = readHexDigit();
+ if (second < 0) {
+ buf.position(buf.position() - 1);
+ return (first << 4);
+ } else {
+ return (first << 4) + second;
+ }
+ }
+
+ /**
+ * read a < hex string >. The initial < has already been read.
+ * @param objNum the object number of the object containing the dictionary
+ * being read; negative only if the object number is unavailable, which
+ * should only happen if we're reading a string placed directly
+ * in the trailer
+ * @param objGen the object generation of the object containing the object
+ * being read; negative only if the objNum is unavailable
+ * @param decrypter the decrypter to use
+ */
+ private PDFObject readHexString(
+ int objNum, int objGen, PDFDecrypter decrypter) throws IOException {
+ // we've already read the <. Now get the hex bytes until >
+ int val;
+ StringBuffer sb = new StringBuffer();
+ while ((val = readHexPair()) >= 0) {
+ sb.append((char) val);
+ }
+ if (buf.get() != '>') {
+ throw new PDFParseException("Bad character in Hex String");
+ }
+ return new PDFObject(this, PDFObject.STRING,
+ decrypter.decryptString(objNum, objGen, sb.toString()));
+ }
+
+
+ /**
+ *
read a ( character string ). The initial ( has already been read.
+ * Read until a *balanced* ) appears.
+ *
+ *
Section 3.2.3 of PDF Refernce version 1.7 defines the format of
+ * String objects. Regarding literal strings:
+ *
+ *
Within a literal string, the backslash (\) is used as an
+ * escape character for various purposes, such as to include newline
+ * characters, nonprinting ASCII characters, unbalanced parentheses, or
+ * the backslash character itself in the string. The character
+ * immediately following the backslash determines its precise
+ * interpretation (see Table 3.2). If the character following the
+ * backslash is not one of those shown in the table, the backslash
+ * is ignored.
+ *
+ * *
This only reads 8 bit basic character 'strings' so as to avoid a
+ * text string interpretation when one is not desired (e.g., for byte
+ * strings, as used by the decryption mechanism). For an interpretation of
+ * a string returned from this method, where the object type is defined
+ * as a 'text string' as per Section 3.8.1, Table 3.31 "PDF Data Types",
+ * {@link PDFStringUtil#asTextString} ()} or
+ * {@link PDFObject#getTextStringValue()} must be employed.
+ *
+ * @param objNum the object number of the object containing the dictionary
+ * being read; negative only if the object number is unavailable, which
+ * should only happen if we're reading a dictionary placed directly
+ * in the trailer
+ * @param objGen the object generation of the object containing the object
+ * being read; negative only if the objNum is unavailable
+ * @param decrypter the decrypter to use
+ */
+ private PDFObject readLiteralString(
+ int objNum, int objGen, PDFDecrypter decrypter) throws IOException {
+ int c;
+
+ // we've already read the (. now get the characters until a
+ // *balanced* ) appears. Translate \r \n \t \b \f \( \) \\ \ddd
+ // if a cr/lf follows a backslash, ignore the cr/lf
+ int parencount = 1;
+ StringBuffer sb = new StringBuffer();
+
+ while (parencount > 0) {
+ c = buf.get() & 0xFF;
+ // process unescaped parenthesis
+ if (c == '(') {
+ parencount++;
+ } else if (c == ')') {
+ parencount--;
+ if (parencount == 0) {
+ c = -1;
+ break;
+ }
+ } else if (c == '\\') {
+
+ // From the spec:
+ // Within a literal string, the backslash (\) is used as an
+ // escape character for various purposes, such as to include
+ // newline characters, nonprinting ASCII characters,
+ // unbalanced parentheses, or the backslash character itself
+ // in the string. The character immediately following the
+ // backslash determines its precise interpretation (see
+ // Table 3.2). If the character following the backslash is not
+ // one of those shown in the table, the backslash is ignored.
+ //
+ // summary of rules:
+ //
+ // \n \r \t \b \f 2-char sequences are used to represent their
+ // 1-char counterparts
+ //
+ // \( and \) are used to escape parenthesis
+ //
+ // \\ for a literal backslash
+ //
+ // \ddd (1-3 octal digits) for a character code
+ //
+ // \ is used to put formatting newlines into the
+ // file, but aren't actually part of the string; EOL may be
+ // CR, LF or CRLF
+ //
+ // any other sequence should see the backslash ignored
+
+ // grab the next character to see what we're dealing with
+ c = buf.get() & 0xFF;
+ if (c >= '0' && c < '8') {
+ // \ddd form - one to three OCTAL digits
+ int count = 0;
+ int val = 0;
+ while (c >= '0' && c < '8' && count < 3) {
+ val = val * 8 + c - '0';
+ c = buf.get() & 0xFF;
+ count++;
+ }
+ // we'll have read one character too many
+ buf.position(buf.position() - 1);
+ c = val;
+ } else if (c == 'n') {
+ c = '\n';
+ } else if (c == 'r') {
+ c = '\r';
+ } else if (c == 't') {
+ c = '\t';
+ } else if (c == 'b') {
+ c = '\b';
+ } else if (c == 'f') {
+ c = '\f';
+ } else if (c == '\r') {
+ // escaped CR to be ignored; look for a following LF
+ c = buf.get() & 0xFF;
+ if (c != '\n') {
+ // not an LF, we'll consume this character on
+ // the next iteration
+ buf.position(buf.position() - 1);
+ }
+ c = -1;
+ } else if (c == '\n') {
+ // escaped LF to be ignored
+ c = -1;
+ }
+ // any other c should be used as is, as it's either
+ // one of ()\ in which case it should be used literally,
+ // or the backslash should just be ignored
+ }
+ if (c >= 0) {
+ sb.append((char) c);
+ }
+ }
+ return new PDFObject(this, PDFObject.STRING,
+ decrypter.decryptString(objNum, objGen, sb.toString()));
+ }
+
+ /**
+ * Read a line of text. This follows the semantics of readLine() in
+ * DataInput -- it reads character by character until a '\n' is
+ * encountered. If a '\r' is encountered, it is discarded.
+ */
+ private String readLine() {
+ StringBuffer sb = new StringBuffer();
+
+ while (buf.remaining() > 0) {
+ char c = (char) buf.get();
+
+ if (c == '\r') {
+ if (buf.remaining() > 0) {
+ char n = (char) buf.get(buf.position());
+ if (n == '\n') {
+ buf.get();
+ }
+ }
+ break;
+ } else if (c == '\n') {
+ break;
+ }
+
+ sb.append(c);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * read an [ array ]. The initial [ has already been read. PDFObjects
+ * are read until ].
+ * @param objNum the object number of the object containing the dictionary
+ * being read; negative only if the object number is unavailable, which
+ * should only happen if we're reading an array placed directly
+ * in the trailer
+ * @param objGen the object generation of the object containing the object
+ * being read; negative only if the objNum is unavailable
+ * @param decrypter the decrypter to use
+ */
+ private PDFObject readArray(
+ int objNum, int objGen, PDFDecrypter decrypter) throws IOException {
+ // we've already read the [. Now read objects until ]
+ ArrayList ary = new ArrayList();
+ PDFObject obj;
+ while((obj= readObject(objNum, objGen, decrypter))!=null) {
+ ary.add(obj);
+ }
+ if (buf.get() != ']') {
+ throw new PDFParseException("Array should end with ']'");
+ }
+ PDFObject[] objlist = new PDFObject[ary.size()];
+ for (int i = 0; i < objlist.length; i++) {
+ objlist[i] = (PDFObject) ary.get(i);
+ }
+ return new PDFObject(this, PDFObject.ARRAY, objlist);
+ }
+
+ /**
+ * read a /name. The / has already been read.
+ */
+ private PDFObject readName() throws IOException {
+ // we've already read the / that begins the name.
+ // all we have to check for is #hh hex notations.
+ StringBuffer sb = new StringBuffer();
+ int c;
+ while (isRegularCharacter(c = buf.get())) {
+ if (c < '!' && c > '~') {
+ break; // out-of-range, should have been hex
+ }
+ // H.3.2.4 indicates version 1.1 did not do hex escapes
+ if (c == '#' && (majorVersion != 1 && minorVersion != 1)) {
+ int hex = readHexPair();
+ if (hex >= 0) {
+ c = hex;
+ } else {
+ throw new PDFParseException("Bad #hex in /Name");
+ }
+ }
+ sb.append((char) c);
+ }
+ buf.position(buf.position() - 1);
+ return new PDFObject(this, PDFObject.NAME, sb.toString());
+ }
+
+ /**
+ * read a number. The initial digit or . or - is passed in as the
+ * argument.
+ */
+ private PDFObject readNumber(char start) throws IOException {
+ // we've read the first digit (it's passed in as the argument)
+ boolean neg = start == '-';
+ boolean sawdot = start == '.';
+ double dotmult = sawdot ? 0.1 : 1;
+ double value = (start >= '0' && start <= '9') ? start - '0' : 0;
+ while (true) {
+ int c = buf.get();
+ if (c == '.') {
+ if (sawdot) {
+ throw new PDFParseException("Can't have two '.' in a number");
+ }
+ sawdot = true;
+ dotmult = 0.1;
+ } else if (c >= '0' && c <= '9') {
+ int val = c - '0';
+ if (sawdot) {
+ value += val * dotmult;
+ dotmult *= 0.1;
+ } else {
+ value = value * 10 + val;
+ }
+ } else {
+ buf.position(buf.position() - 1);
+ break;
+ }
+ }
+ if (neg) {
+ value = -value;
+ }
+ return new PDFObject(this, PDFObject.NUMBER, new Double(value));
+ }
+
+ /**
+ * read a bare keyword. The initial character is passed in as the
+ * argument.
+ */
+ private PDFObject readKeyword(char start) throws IOException {
+ // we've read the first character (it's passed in as the argument)
+ StringBuffer sb = new StringBuffer(String.valueOf(start));
+ int c;
+ while (isRegularCharacter(c = buf.get())) {
+ sb.append((char) c);
+ }
+ buf.position(buf.position() - 1);
+ return new PDFObject(this, PDFObject.KEYWORD, sb.toString());
+ }
+
+ /**
+ * read an entire PDFObject. The intro line, which looks something
+ * like "4 0 obj" has already been read.
+ * @param objNum the object number of the object being read, being
+ * the first number in the intro line (4 in "4 0 obj")
+ * @param objGen the object generation of the object being read, being
+ * the second number in the intro line (0 in "4 0 obj").
+ * @param decrypter the decrypter to use
+ */
+ private PDFObject readObjectDescription(
+ int objNum, int objGen, PDFDecrypter decrypter) throws IOException {
+ // we've already read the 4 0 obj bit. Next thing up is the object.
+ // object descriptions end with the keyword endobj
+ long debugpos = buf.position();
+ PDFObject obj= readObject(objNum, objGen, decrypter);
+ // see if it's a dictionary. If so, this could be a stream.
+ PDFObject endkey= readObject(objNum, objGen, decrypter);
+ if (endkey.getType() != PDFObject.KEYWORD) {
+ throw new PDFParseException("Expected 'stream' or 'endobj'");
+ }
+ if (obj.getType() == PDFObject.DICTIONARY && endkey.getStringValue().equals("stream")) {
+ // skip until we see \n
+ readLine();
+ ByteBuffer data = readStream(obj);
+ if (data == null) {
+ data = ByteBuffer.allocate(0);
+ }
+ obj.setStream(data);
+ endkey= readObject(objNum, objGen, decrypter);
+ }
+ // at this point, obj is the object, keyword should be "endobj"
+ String endcheck = endkey.getStringValue();
+ if (endcheck == null || !endcheck.equals("endobj")) {
+ System.out.println("WARNING: object at " + debugpos + " didn't end with 'endobj'");
+ //throw new PDFParseException("Object musst end with 'endobj'");
+ }
+ obj.setObjectId(objNum, objGen);
+ return obj;
+ }
+
+ /**
+ * read the stream portion of a PDFObject. Calls decodeStream to
+ * un-filter the stream as necessary.
+ *
+ * @param dict the dictionary associated with this stream.
+ * @return a ByteBuffer with the encoded stream data
+ */
+ private ByteBuffer readStream(PDFObject dict) throws IOException {
+ // pointer is at the start of a stream. read the stream and
+ // decode, based on the entries in the dictionary
+ PDFObject lengthObj = dict.getDictRef("Length");
+ int length = -1;
+ if (lengthObj != null) {
+ length = lengthObj.getIntValue();
+ }
+ if (length < 0) {
+ throw new PDFParseException("Unknown length for stream");
+ }
+
+ // slice the data
+ int start = buf.position();
+ ByteBuffer streamBuf = buf.slice();
+ streamBuf.limit(length);
+
+ // move the current position to the end of the data
+ buf.position(buf.position() + length);
+ int ending = buf.position();
+
+ if (!nextItemIs("endstream")) {
+ System.out.println("read " + length + " chars from " + start + " to " +
+ ending);
+ throw new PDFParseException("Stream ended inappropriately");
+ }
+
+ return streamBuf;
+ // now decode stream
+ // return PDFDecoder.decodeStream(dict, streamBuf);
+ }
+
+ /**
+ * read the cross reference table from a PDF file. When this method
+ * is called, the file pointer must point to the start of the word
+ * "xref" in the file. Reads the xref table and the trailer dictionary.
+ * If dictionary has a /Prev entry, move file pointer
+ * and read new trailer
+ * @param password
+ */
+ /**
+ * read the cross reference table from a PDF file. When this method
+ * is called, the file pointer must point to the start of the word
+ * "xref" in the file. Reads the xref table and the trailer dictionary.
+ * If dictionary has a /Prev entry, move file pointer
+ * and read new trailer
+ * @param password
+ */
+ private void readTrailer(PDFPassword password)
+ throws
+ IOException,
+ PDFAuthenticationFailureException,
+ EncryptionUnsupportedByProductException,
+ EncryptionUnsupportedByPlatformException {
+ // the table of xrefs
+ objIdx = new PDFXref[50];
+
+ int pos = buf.position();
+
+ PDFDecrypter newDefaultDecrypter = null;
+
+ // read a bunch of nested trailer tables
+ while (true) {
+ // make sure we are looking at an xref table
+ if (!nextItemIs("xref")) {
+ buf.position(pos);
+ readTrailer15(password);
+ return;
+// throw new PDFParseException("Expected 'xref' at start of table");
+ }
+
+ // read a bunch of linked tabled
+ while (true) {
+ // read until the word "trailer"
+ PDFObject obj=readObject(-1, -1, IdentityDecrypter.getInstance());
+ if (obj.getType() == PDFObject.KEYWORD &&
+ obj.getStringValue().equals("trailer")) {
+ break;
+ }
+
+ // read the starting position of the reference
+ if (obj.getType() != PDFObject.NUMBER) {
+ throw new PDFParseException("Expected number for first xref entry");
+ }
+ int refstart = obj.getIntValue();
+
+ // read the size of the reference table
+ obj = readObject(-1, -1, IdentityDecrypter.getInstance());
+ if (obj.getType() != PDFObject.NUMBER) {
+ throw new PDFParseException("Expected number for length of xref table");
+ }
+ int reflen = obj.getIntValue();
+
+ // skip a line
+ readLine();
+
+ // extend the objIdx table, if necessary
+ if (refstart + reflen >= objIdx.length) {
+ PDFXref nobjIdx[] = new PDFXref[refstart + reflen];
+ System.arraycopy(objIdx, 0, nobjIdx, 0, objIdx.length);
+ objIdx = nobjIdx;
+ }
+
+ // read reference lines
+ for (int refID = refstart; refID < refstart + reflen; refID++) {
+ // each reference line is 20 bytes long
+ byte[] refline = new byte[20];
+ buf.get(refline);
+
+ // ignore this line if the object ID is already defined
+ if (objIdx[refID] != null) {
+ continue;
+ }
+
+ // see if it's an active object
+ if (refline[17] == 'n') {
+ objIdx[refID] = new PDFXref(refline);
+ } else {
+ objIdx[refID] = new PDFXref(null);
+ }
+ }
+ }
+
+ // at this point, the "trailer" word (not EOL) has been read.
+ PDFObject trailerdict = readObject(-1, -1, IdentityDecrypter.getInstance());
+ if (trailerdict.getType() != PDFObject.DICTIONARY) {
+ throw new IOException("Expected dictionary after \"trailer\"");
+ }
+
+ // read the root object location
+ if (root == null) {
+ root = trailerdict.getDictRef("Root");
+ if (root != null) {
+ root.setObjectId(PDFObject.OBJ_NUM_TRAILER,
+ PDFObject.OBJ_NUM_TRAILER);
+ }
+ }
+
+ // read the encryption information
+ if (encrypt == null) {
+ encrypt = trailerdict.getDictRef("Encrypt");
+ if (encrypt != null) {
+ encrypt.setObjectId(PDFObject.OBJ_NUM_TRAILER,
+ PDFObject.OBJ_NUM_TRAILER);
+ }
+ newDefaultDecrypter =
+ PDFDecrypterFactory.createDecryptor(
+ encrypt,
+ trailerdict.getDictRef("ID"),
+ password);
+ }
+
+
+ if (info == null) {
+ info = trailerdict.getDictRef("Info");
+ if (info != null) {
+ if (!info.isIndirect()) {
+ throw new PDFParseException(
+ "Info in trailer must be an indirect reference");
+ }
+ info.setObjectId(PDFObject.OBJ_NUM_TRAILER,
+ PDFObject.OBJ_NUM_TRAILER);
+ }
+ }
+
+ PDFObject xrefstmPos = trailerdict.getDictRef("XRefStm");
+ if (xrefstmPos != null) {
+ System.out.println("XRefStm:" + xrefstmPos.getIntValue());
+ int pos14 = buf.position();
+ buf.position(xrefstmPos.getIntValue());
+ readTrailer15(password);
+ buf.position(pos14);
+ }
+
+ // read the location of the previous xref table
+ PDFObject prevloc = trailerdict.getDictRef("Prev");
+ if (prevloc != null) {
+ buf.position(prevloc.getIntValue());
+ } else {
+ break;
+ }
+ // see if we have an optional Version entry
+
+
+ if (root.getDictRef("Version") != null) {
+ processVersion(root.getDictRef("Version").getStringValue());
+ }
+ }
+
+ // make sure we found a root
+ if (root == null) {
+ throw new PDFParseException("No /Root key found in trailer dictionary");
+ }
+
+ // check what permissions are relevant
+ if (encrypt != null) {
+ PDFObject permissions = encrypt.getDictRef("P");
+ if (permissions!=null && !newDefaultDecrypter.isOwnerAuthorised()) {
+ int perms= permissions != null ? permissions.getIntValue() : 0;
+ if (permissions!=null) {
+ printable = (perms & 4) != 0;
+ saveable = (perms & 16) != 0;
+ }
+ }
+ // Install the new default decrypter only after the trailer has
+ // been read, as nothing we're reading passing through is encrypted
+ defaultDecrypter = newDefaultDecrypter;
+ }
+
+ // dereference the root object
+ root.dereference();
+ }
+
+ /**
+ * read the cross reference table from a PDF file. When this method
+ * is called, the file pointer must point to the start of the word
+ * "xref" in the file. Reads the xref table and the trailer dictionary.
+ * If dictionary has a /Prev entry, move file pointer
+ * and read new trailer
+ * @param password
+ */
+ private void readTrailer15(PDFPassword password)
+ throws
+ IOException,
+ PDFAuthenticationFailureException,
+ EncryptionUnsupportedByProductException,
+ EncryptionUnsupportedByPlatformException {
+
+ // the table of xrefs is already initialized and perhaps filled in readTrailer()
+ // objIdx = new PDFXref[50];
+ PDFDecrypter newDefaultDecrypter = null;
+
+ while (true) {
+ PDFObject xrefObj=readObject(-1, -1, IdentityDecrypter.getInstance());
+// System.out.println(xrefObj.getDictionary().toString());
+// System.out.println(xrefObj.toString());
+
+ PDFObject[] wNums = xrefObj.getDictionary().get("W").getArray();
+ int l1 = wNums[0].getIntValue();
+ int l2 = wNums[1].getIntValue();
+ int l3 = wNums[2].getIntValue();
+ int entrySize = l1+l2+l3;
+// System.out.println("["+l1+","+l2+","+l3+"]");
+
+ int size = xrefObj.getDictionary().get("Size").getIntValue();
+// System.out.println("Size = " + size);
+
+ byte[] strmbuf = xrefObj.getStream();
+ int strmEntries = strmbuf.length / entrySize;
+ int strmPos = 0;
+// System.out.println("strmEntries = " + strmEntries);
+
+ PDFObject idxNums = xrefObj.getDictionary().get("Index");
+ int[] idxArray;
+ if (idxNums == null) {
+ idxArray = new int[]{0, size};
+ }
+ else {
+ PDFObject[] idxNumArr = idxNums.getArray();
+ idxArray = new int[idxNumArr.length];
+ for (int i = 0; i < idxNumArr.length; i++) {
+ idxArray[i] = idxNumArr[i].getIntValue();
+ }
+ }
+ int idxLen = idxArray.length;
+ int idxPos = 0;
+
+
+ while (idxPos= objIdx.length) {
+ PDFXref nobjIdx[] = new PDFXref[refstart + reflen];
+ System.arraycopy(objIdx, 0, nobjIdx, 0, objIdx.length);
+ objIdx = nobjIdx;
+ }
+
+// System.out.println("Index["+refstart+","+reflen+"]");
+ // read reference lines
+ for (int refID = refstart; refID < refstart + reflen; refID++) {
+
+ int type = readNum(strmbuf, strmPos, l1);
+ strmPos += l1;
+ int id = readNum(strmbuf, strmPos, l2);
+ strmPos += l2;
+ int gen = readNum(strmbuf, strmPos, l3);
+ strmPos += l3;
+
+ // ignore this line if the object ID is already defined
+ if (objIdx[refID] != null) {
+ continue;
+ }
+
+ // see if it's an active object
+ if (type == 0) { // inactive
+ objIdx[refID] = new PDFXref(null);
+ } else if (type == 1) { // active uncompressed
+ objIdx[refID] = new PDFXref(id, gen);
+ } else { // active compressed
+ objIdx[refID] = new PDFXref(id, gen, true);
+ }
+
+
+// System.out.println(refID+"="+type+":"+id+","+gen);
+ }
+ }
+
+ HashMap trailerdict = xrefObj.getDictionary();
+
+ // read the root object location
+ if (root == null) {
+ root = trailerdict.get("Root");
+ if (root != null) {
+ root.setObjectId(PDFObject.OBJ_NUM_TRAILER,
+ PDFObject.OBJ_NUM_TRAILER);
+ }
+ }
+
+ // read the encryption information
+ if (encrypt == null) {
+ encrypt = trailerdict.get("Encrypt");
+ if (encrypt != null) {
+ encrypt.setObjectId(PDFObject.OBJ_NUM_TRAILER,
+ PDFObject.OBJ_NUM_TRAILER);
+ }
+ newDefaultDecrypter =
+ PDFDecrypterFactory.createDecryptor(
+ encrypt,
+ trailerdict.get("ID"),
+ password);
+ }
+
+
+ if (info == null) {
+ info = trailerdict.get("Info");
+ if (info != null) {
+ if (!info.isIndirect()) {
+ throw new PDFParseException(
+ "Info in trailer must be an indirect reference");
+ }
+ info.setObjectId(PDFObject.OBJ_NUM_TRAILER,
+ PDFObject.OBJ_NUM_TRAILER);
+ }
+ }
+
+ // read the location of the previous xref table
+ PDFObject prevloc = trailerdict.get("Prev");
+ if (prevloc != null) {
+ buf.position(prevloc.getIntValue());
+ } else {
+ break;
+ }
+ // see if we have an optional Version entry
+
+
+ if (root.getDictRef("Version") != null) {
+ processVersion(root.getDictRef("Version").getStringValue());
+ }
+ }
+
+ // make sure we found a root
+ if (root == null) {
+ throw new PDFParseException("No /Root key found in trailer dictionary");
+ }
+
+ // check what permissions are relevant
+ if (encrypt != null) {
+ PDFObject permissions = encrypt.getDictRef("P");
+ if (permissions!=null && !newDefaultDecrypter.isOwnerAuthorised()) {
+ int perms= permissions != null ? permissions.getIntValue() : 0;
+ if (permissions!=null) {
+ printable = (perms & 4) != 0;
+ saveable = (perms & 16) != 0;
+ }
+ }
+ // Install the new default decrypter only after the trailer has
+ // been read, as nothing we're reading passing through is encrypted
+ defaultDecrypter = newDefaultDecrypter;
+ }
+
+ // dereference the root object
+ root.dereference();
+ }
+
+ private int readNum(byte[] sbuf, int pos, int numBytes) {
+ int result = 0;
+ for (int i=0; i= 0) {
+ buf.position(scanPos);
+ buf.get(scan);
+
+ // find startxref in scan
+ String scans = new String(scan);
+ loc = scans.indexOf("startxref");
+ if (loc > 0) {
+ if (scanPos + loc + scan.length <= buf.limit()) {
+ scanPos = scanPos + loc;
+ loc = 0;
+ }
+
+ break;
+ }
+ scanPos -= scan.length - 10;
+ }
+
+ if (scanPos < 0) {
+ throw new IOException("This may not be a PDF File");
+ }
+
+ buf.position(scanPos);
+ buf.get(scan);
+ String scans = new String(scan);
+
+ loc += 10; // skip over "startxref" and first EOL char
+ if (scans.charAt(loc) < 32) {
+ loc++;
+ } // skip over possible 2nd EOL char
+ while (scans.charAt(loc) == 32) {
+ loc++;
+ } // skip over possible leading blanks
+ // read number
+ int numstart = loc;
+ while (loc < scans.length() &&
+ scans.charAt(loc) >= '0' &&
+ scans.charAt(loc) <= '9') {
+ loc++;
+ }
+ int xrefpos = Integer.parseInt(scans.substring(numstart, loc));
+ buf.position(xrefpos);
+
+ try {
+ readTrailer(password);
+ } catch (UnsupportedEncryptionException e) {
+ throw new PDFParseException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Gets the outline tree as a tree of OutlineNode, which is a subclass
+ * of DefaultMutableTreeNode. If there is no outline tree, this method
+ * returns null.
+ */
+ public OutlineNode getOutline() throws IOException {
+ // find the outlines entry in the root object
+ PDFObject oroot = root.getDictRef("Outlines");
+ OutlineNode work = null;
+ OutlineNode outline = null;
+ if (oroot != null) {
+ // find the first child of the outline root
+ PDFObject scan = oroot.getDictRef("First");
+ outline = work = new OutlineNode("");
+
+ // scan each sibling in turn
+ while (scan != null) {
+ // add the new node with it's name
+ String title = scan.getDictRef("Title").getTextStringValue();
+ OutlineNode build = new OutlineNode(title);
+ work.add(build);
+
+ // find the action
+ PDFAction action = null;
+
+ PDFObject actionObj = scan.getDictRef("A");
+ if (actionObj != null) {
+ action = PDFAction.getAction(actionObj, getRoot());
+ } else {
+ // try to create an action from a destination
+ PDFObject destObj = scan.getDictRef("Dest");
+ if (destObj != null) {
+ try {
+ PDFDestination dest =
+ PDFDestination.getDestination(destObj, getRoot());
+
+ action = new GoToAction(dest);
+ } catch (IOException ioe) {
+ // oh well
+ }
+ }
+ }
+
+ // did we find an action? If so, add it
+ if (action != null) {
+ build.setAction(action);
+ }
+
+ // find the first child of this node
+ PDFObject kid = scan.getDictRef("First");
+ if (kid != null) {
+ work = build;
+ scan = kid;
+ } else {
+ // no child. Process the next sibling
+ PDFObject next = scan.getDictRef("Next");
+ while (next == null) {
+ scan = scan.getDictRef("Parent");
+ next = scan.getDictRef("Next");
+ work = (OutlineNode) work.getParent();
+ if (work == null) {
+ break;
+ }
+ }
+ scan = next;
+ }
+ }
+ }
+
+ return outline;
+ }
+
+ /**
+ * Gets the page number (starting from 1) of the page represented by
+ * a particular PDFObject. The PDFObject must be a Page dictionary or
+ * a destination description (or an action).
+ * @return a number between 1 and the number of pages indicating the
+ * page number, or 0 if the PDFObject is not in the page tree.
+ */
+ public int getPageNumber(PDFObject page) throws IOException {
+ if (page.getType() == PDFObject.ARRAY) {
+ page = page.getAt(0);
+ }
+
+ // now we've got a page. Make sure.
+ PDFObject typeObj = page.getDictRef("Type");
+ if (typeObj == null || !typeObj.getStringValue().equals("Page")) {
+ return 0;
+ }
+
+ int count = 0;
+ while (true) {
+ PDFObject parent = page.getDictRef("Parent");
+ if (parent == null) {
+ break;
+ }
+ PDFObject kids[] = parent.getDictRef("Kids").getArray();
+ for (int i = 0; i < kids.length; i++) {
+ if (kids[i].equals(page)) {
+ break;
+ } else {
+ PDFObject kcount = kids[i].getDictRef("Count");
+ if (kcount != null) {
+ count += kcount.getIntValue();
+ } else {
+ count += 1;
+ }
+ }
+ }
+ page = parent;
+ }
+ return count;
+ }
+
+ /**
+ * Get the page commands for a given page in a separate thread.
+ *
+ * @param pagenum the number of the page to get commands for
+ */
+ public PDFPage getPage(int pagenum) {
+ return getPage(pagenum, false);
+ }
+
+ /**
+ * Get the page commands for a given page.
+ *
+ * @param pagenum the number of the page to get commands for
+ * @param wait if true, do not exit until the page is complete.
+ */
+ public PDFPage getPage(int pagenum, boolean wait) {
+ Integer key = new Integer(pagenum);
+ HashMap resources = null;
+ PDFObject pageObj = null;
+ boolean needread = false;
+
+ PDFPage page = cache.getPage(key);
+ PDFParser parser = cache.getPageParser(key);
+ if (page == null) {
+ try {
+ // hunt down the page!
+ resources = new HashMap();
+
+ PDFObject topPagesObj = root.getDictRef("Pages");
+ pageObj = findPage(topPagesObj, 0, pagenum, resources);
+
+ if (pageObj == null) {
+ return null;
+ }
+
+ page = createPage(pagenum, pageObj);
+
+ byte[] stream = getContents(pageObj);
+ parser = new PDFParser(page, stream, resources);
+
+ cache.addPage(key, page, parser);
+ } catch (IOException ioe) {
+ System.out.println("GetPage inner loop:");
+ ioe.printStackTrace();
+ return null;
+ }
+ }
+
+ if (parser != null && !parser.isFinished()) {
+ parser.go(wait);
+ }
+
+ return page;
+ }
+
+ /**
+ * Stop the rendering of a particular image on this page
+ */
+ public void stop(int pageNum) {
+ PDFParser parser = cache.getPageParser(new Integer(pageNum));
+ if (parser != null) {
+ // stop it
+ parser.stop();
+ }
+ }
+
+ /**
+ * get the stream representing the content of a particular page.
+ *
+ * @param pageObj the page object to get the contents of
+ * @return a concatenation of any content streams for the requested
+ * page.
+ */
+ private byte[] getContents(PDFObject pageObj) throws IOException {
+ // concatenate all the streams
+ PDFObject contentsObj = pageObj.getDictRef("Contents");
+ if (contentsObj == null) {
+ throw new IOException("No page contents!");
+ }
+
+ PDFObject contents[] = contentsObj.getArray();
+
+ // see if we have only one stream (the easy case)
+ if (contents.length == 1) {
+ return contents[0].getStream();
+ }
+
+ // first get the total length of all the streams
+ int len = 0;
+ for (int i = 0; i < contents.length; i++) {
+ byte[] data = contents[i].getStream();
+ if (data == null) {
+ throw new PDFParseException("No stream on content " + i +
+ ": " + contents[i]);
+ }
+ len += data.length;
+ }
+
+ // now assemble them all into one object
+ byte[] stream = new byte[len];
+ len = 0;
+ for (int i = 0; i < contents.length; i++) {
+ byte data[] = contents[i].getStream();
+ System.arraycopy(data, 0, stream, len, data.length);
+ len += data.length;
+ }
+
+ return stream;
+ }
+
+ /**
+ * Create a PDF Page object by finding the relevant inherited
+ * properties
+ *
+ * @param pageObj the PDF object for the page to be created
+ */
+ private PDFPage createPage(int pagenum, PDFObject pageObj)
+ throws IOException {
+ int rotation = 0;
+ RectF mediabox = null; // second choice, if no crop
+ RectF cropbox = null; // first choice
+
+ PDFObject mediaboxObj = getInheritedValue(pageObj, "MediaBox");
+ if (mediaboxObj != null) {
+ mediabox = parseRect(mediaboxObj);
+ }
+
+ PDFObject cropboxObj = getInheritedValue(pageObj, "CropBox");
+ if (cropboxObj != null) {
+ cropbox = parseRect(cropboxObj);
+ }
+
+ PDFObject rotateObj = getInheritedValue(pageObj, "Rotate");
+ if (rotateObj != null) {
+ rotation = rotateObj.getIntValue();
+ }
+
+ RectF bbox = ((cropbox == null) ? mediabox : cropbox);
+
+ return new PDFPage(pagenum, bbox, rotation, cache);
+ }
+
+ /**
+ * Get the PDFObject representing the content of a particular page. Note
+ * that the number of the page need not have anything to do with the
+ * label on that page. If there are two blank pages, and then roman
+ * numerals for the page number, then passing in 6 will get page (iv).
+ *
+ * @param pagedict the top of the pages tree
+ * @param start the page number of the first page in this dictionary
+ * @param getPage the number of the page to find; NOT the page's label.
+ * @param resources a HashMap that will be filled with any resource
+ * definitions encountered on the search for the page
+ */
+ private PDFObject findPage(PDFObject pagedict, int start, int getPage,
+ Map resources) throws IOException {
+ PDFObject rsrcObj = pagedict.getDictRef("Resources");
+ if (rsrcObj != null) {
+ resources.putAll(rsrcObj.getDictionary());
+ }
+
+ PDFObject typeObj = pagedict.getDictRef("Type");
+ if (typeObj != null && typeObj.getStringValue().equals("Page")) {
+ // we found our page!
+ return pagedict;
+ }
+
+ // find the first child for which (start + count) > getPage
+ PDFObject kidsObj = pagedict.getDictRef("Kids");
+ if (kidsObj != null) {
+ PDFObject[] kids = kidsObj.getArray();
+ for (int i = 0; i < kids.length; i++) {
+ int count = 1;
+ // BUG: some PDFs (T1Format.pdf) don't have the Type tag.
+ // use the Count tag to indicate a Pages dictionary instead.
+ PDFObject countItem = kids[i].getDictRef("Count");
+ // if (kids[i].getDictRef("Type").getStringValue().equals("Pages")) {
+ if (countItem != null) {
+ count = countItem.getIntValue();
+ }
+
+ if (start + count >= getPage) {
+ return findPage(kids[i], start, getPage, resources);
+ }
+
+ start += count;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Find a property value in a page that may be inherited. If the value
+ * is not defined in the page itself, follow the page's "parent" links
+ * until the value is found or the top of the tree is reached.
+ *
+ * @param pageObj the object representing the page
+ * @param propName the name of the property we are looking for
+ */
+ private PDFObject getInheritedValue(PDFObject pageObj, String propName)
+ throws IOException {
+ // see if we have the property
+ PDFObject propObj = pageObj.getDictRef(propName);
+ if (propObj != null) {
+ return propObj;
+ }
+
+ // recursively see if any of our parent have it
+ PDFObject parentObj = pageObj.getDictRef("Parent");
+ if (parentObj != null) {
+ return getInheritedValue(parentObj, propName);
+ }
+
+ // no luck
+ return null;
+ }
+
+ /**
+ * get a Rectangle2D.Float representation for a PDFObject that is an
+ * array of four Numbers.
+ * @param obj a PDFObject that represents an Array of exactly four
+ * Numbers.
+ */
+ public RectF parseRect(PDFObject obj) throws IOException {
+ if (obj.getType() == PDFObject.ARRAY) {
+ PDFObject bounds[] = obj.getArray();
+ if (bounds.length == 4) {
+ return new RectF(bounds[0].getFloatValue(),
+ bounds[1].getFloatValue(),
+ bounds[2].getFloatValue(),
+ bounds[3].getFloatValue());
+ } else {
+ throw new PDFParseException("Rectangle definition didn't have 4 elements");
+ }
+ } else {
+ throw new PDFParseException("Rectangle definition not an array");
+ }
+ }
+
+ /**
+ * Get the default decrypter for the document
+ * @return the default decrypter; never null, even for documents that
+ * aren't encrypted
+ */
+ public PDFDecrypter getDefaultDecrypter() {
+ return defaultDecrypter;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFImage.java b/PdfView/src/main/java/com/sun/pdfview/PDFImage.java
new file mode 100644
index 0000000..a7c0db8
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFImage.java
@@ -0,0 +1,713 @@
+/*
+ * $Id: PDFImage.java,v 1.9 2009/03/12 13:23:54 tomoke 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;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.Log;
+import com.sun.pdfview.colorspace.PDFColorSpace;
+import com.sun.pdfview.function.FunctionType0;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates a PDF Image
+ */
+public class PDFImage {
+
+ private static final String TAG = "AWTPDF.pdfimage";
+
+ public static boolean sShowImages;
+
+ public static void dump(PDFObject obj) throws IOException {
+ p("dumping PDF object: " + obj);
+ if (obj == null) {
+ return;
+ }
+ HashMap dict = obj.getDictionary();
+ p(" dict = " + dict);
+ for (Object key : dict.keySet()) {
+ p("key = " + key + " value = " + dict.get(key));
+ }
+ }
+
+ public static void p(String string) {
+ System.out.println(string);
+ }
+
+ /**
+ * color key mask. Array of start/end pairs of ranges of color components to
+ * mask out. If a component falls within any of the ranges it is clear.
+ */
+ private int[] colorKeyMask = null;
+ /** the width of this image in pixels */
+ private int width;
+ /** the height of this image in pixels */
+ private int height;
+ /** the colorspace to interpret the samples in */
+ private PDFColorSpace colorSpace;
+ /** the number of bits per sample component */
+ private int bpc;
+ /** whether this image is a mask or not */
+ private boolean imageMask = false;
+ /** the SMask image, if any */
+ private PDFImage sMask;
+ /** the decode array */
+ private float[] decode;
+ /** the actual image data */
+ private PDFObject imageObj;
+
+ /**
+ * Create an instance of a PDFImage
+ */
+ protected PDFImage(PDFObject imageObj) {
+ this.imageObj = imageObj;
+ }
+
+ /**
+ * Read a PDFImage from an image dictionary and stream
+ *
+ * @param obj the PDFObject containing the image's dictionary and stream
+ * @param resources the current resources
+ */
+ public static PDFImage createImage(PDFObject obj, Map resources) throws IOException {
+ // create the image
+ PDFImage image = new PDFImage(obj);
+
+ // get the width (required)
+ PDFObject widthObj = obj.getDictRef("Width");
+ if (widthObj == null) {
+ throw new PDFParseException("Unable to read image width: " + obj);
+ }
+ image.setWidth(widthObj.getIntValue());
+
+ // get the height (required)
+ PDFObject heightObj = obj.getDictRef("Height");
+ if (heightObj == null) {
+ throw new PDFParseException("Unable to get image height: " + obj);
+ }
+ image.setHeight(heightObj.getIntValue());
+
+ // figure out if we are an image mask (optional)
+ PDFObject imageMaskObj = obj.getDictRef("ImageMask");
+ if (imageMaskObj != null) {
+ image.setImageMask(imageMaskObj.getBooleanValue());
+ }
+
+ // read the bpc and colorspace (required except for masks)
+ if (image.isImageMask()) {
+ image.setBitsPerComponent(1);
+
+ // create the indexed color space for the mask
+ // [PATCHED by michal.busta@gmail.com] - default value od Decode according to PDF spec. is [0, 1]
+ // so the color arry should be:
+ int[] colors = { Color.BLACK, Color.WHITE };
+
+ PDFObject imageMaskDecode = obj.getDictRef("Decode");
+ if (imageMaskDecode != null) {
+ PDFObject[] array = imageMaskDecode.getArray();
+ float decode0 = array[0].getFloatValue();
+ if (decode0 == 1.0f) {
+ colors = new int[] { Color.WHITE, Color.BLACK };
+ }
+ }
+ // TODO [FHe]: support for indexed colorspace
+ image.setColorSpace(PDFColorSpace.getColorSpace(PDFColorSpace.COLORSPACE_GRAY));
+ //image.setColorSpace(new IndexedColor(colors));
+ } else {
+ // get the bits per component (required)
+ PDFObject bpcObj = obj.getDictRef("BitsPerComponent");
+ if (bpcObj == null) {
+ throw new PDFParseException("Unable to get bits per component: " + obj);
+ }
+ image.setBitsPerComponent(bpcObj.getIntValue());
+
+ // get the color space (required)
+ PDFObject csObj = obj.getDictRef("ColorSpace");
+ if (csObj == null) {
+ throw new PDFParseException("No ColorSpace for image: " + obj);
+ }
+
+ PDFColorSpace cs = PDFColorSpace.getColorSpace(csObj, resources);
+ image.setColorSpace(cs);
+ }
+
+ // read the decode array
+ PDFObject decodeObj = obj.getDictRef("Decode");
+ if (decodeObj != null) {
+ PDFObject[] decodeArray = decodeObj.getArray();
+
+ float[] decode = new float[decodeArray.length];
+ for (int i = 0; i < decodeArray.length; i++) {
+ decode[i] = decodeArray[i].getFloatValue();
+ }
+
+ image.setDecode(decode);
+ }
+
+ // read the soft mask.
+ // If ImageMask is true, this entry must not be present.
+ // (See implementation note 52 in Appendix H.)
+ if (imageMaskObj == null) {
+ PDFObject sMaskObj = obj.getDictRef("SMask");
+ if (sMaskObj == null) {
+ // try the explicit mask, if there is no SoftMask
+ sMaskObj = obj.getDictRef("Mask");
+ }
+
+ if (sMaskObj != null) {
+ if (sMaskObj.getType() == PDFObject.STREAM) {
+ try {
+ PDFImage sMaskImage = PDFImage.createImage(sMaskObj, resources);
+ image.setSMask(sMaskImage);
+ } catch (IOException ex) {
+ p("ERROR: there was a problem parsing the mask for this object");
+ dump(obj);
+ ex.printStackTrace(System.out);
+ }
+ } else if (sMaskObj.getType() == PDFObject.ARRAY) {
+ // retrieve the range of the ColorKeyMask
+ // colors outside this range will not be painted.
+ try {
+ image.setColorKeyMask(sMaskObj);
+ } catch (IOException ex) {
+ p("ERROR: there was a problem parsing the color mask for this object");
+ dump(obj);
+ ex.printStackTrace(System.out);
+ }
+ }
+ }
+ }
+
+ return image;
+ }
+
+ /**
+ * Get the image that this PDFImage generates.
+ *
+ * @return a buffered image containing the decoded image data
+ */
+ public Bitmap getImage() {
+ try {
+ Bitmap bi = (Bitmap) imageObj.getCache();
+
+ if (bi == null) {
+ if (!sShowImages) throw new UnsupportedOperationException("do not show images");
+ byte[] imgBytes = imageObj.getStream();
+ bi = parseData(imgBytes);
+ // TODO [FHe]: is the cache useful on Android?
+ imageObj.setCache(bi);
+ }
+ // if(bi != null)
+ // ImageIO.write(bi, "png", new File("/tmp/test/" + System.identityHashCode(this) + ".png"));
+ return bi;
+ } catch (IOException ioe) {
+ System.out.println("Error reading image");
+ ioe.printStackTrace();
+ return null;
+ } catch (OutOfMemoryError e) {
+ // fix for too large images
+ Log.e(TAG, "image too large (OutOfMemoryError)");
+ int size = 15;
+ int max = size - 1;
+ int half = size / 2 - 1;
+ Bitmap bi = Bitmap.createBitmap(size, size, Config.RGB_565);
+ Canvas c = new Canvas(bi);
+ c.drawColor(Color.RED);
+ Paint p = new Paint();
+ p.setColor(Color.WHITE);
+ c.drawLine(0, 0, max, max, p);
+ c.drawLine(0, max, max, 0, p);
+ c.drawLine(half, 0, half, max, p);
+ c.drawLine(0, half, max, half, p);
+ return bi;
+ }
+ }
+
+ private Bitmap parseData(byte[] imgBytes) {
+ Bitmap bi;
+ long startTime = System.currentTimeMillis();
+ // parse the stream data into an actual image
+ Log.i(TAG, "Creating Image width="
+ + getWidth()
+ + ", Height="
+ + getHeight()
+ + ", bpc="
+ + getBitsPerComponent()
+ + ",cs="
+ + colorSpace);
+ if (colorSpace == null) {
+ throw new UnsupportedOperationException("image without colorspace");
+ } else if (colorSpace.getType() == PDFColorSpace.COLORSPACE_RGB) {
+ int maxH = getHeight();
+ int maxW = getWidth();
+ if (imgBytes.length == 2 * maxW * maxH) {
+ // decoded JPEG as RGB565
+ bi = Bitmap.createBitmap(maxW, maxH, Config.RGB_565);
+ bi.copyPixelsFromBuffer(ByteBuffer.wrap(imgBytes));
+ } else if (imgBytes.length == 4 * maxW * maxH) {
+ // create RGB image
+ bi = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
+ bi.copyPixelsFromBuffer(ByteBuffer.wrap(imgBytes));
+
+ } else {
+ bi = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
+ int[] line = new int[maxW];
+ int n = 0;
+ for (int h = 0; h < maxH; h++) {
+ for (int w = 0; w < getWidth(); w++) {
+ line[w] = ((0xff & (int) imgBytes[n]) << 8 | (0xff & (int) imgBytes[n + 1])) << 8
+ | (0xff & (int) imgBytes[n + 2])
+ | 0xFF000000;
+ // line[w] = Color.rgb(0xff&(int)imgBytes[n], 0xff&(int)imgBytes[n+1],0xff&(int)imgBytes[n+2]);
+ if (-line[w] == 0xFFFFFF + 1) {
+ line[w] = 0xFFFFFFFF;
+ }
+ n += 3;
+ }
+ bi.setPixels(line, 0, maxW, 0, h, maxW, 1);
+ }
+ }
+ } else if (colorSpace.getType() == PDFColorSpace.COLORSPACE_GRAY) {
+ // create gray image
+ bi = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
+ int maxH = getHeight();
+ int maxW = getWidth();
+ int[] line = new int[maxW];
+ int n = 0;
+ for (int h = 0; h < maxH; h++) {
+ for (int w = 0; w < getWidth(); w++) {
+ int gray = 0xff & (int) imgBytes[n];
+ line[w] = (gray << 8 | gray) << 8 | gray | 0xFF000000;
+ n += 1;
+ }
+ bi.setPixels(line, 0, maxW, 0, h, maxW, 1);
+ }
+ } else if (colorSpace.getType() == PDFColorSpace.COLORSPACE_INDEXED) {
+ // create indexed image
+ bi = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
+ int maxH = getHeight();
+ int maxW = getWidth();
+ int[] line = new int[maxW];
+ int[] comps = new int[1];
+ int n = 0;
+ for (int h = 0; h < maxH; h++) {
+ for (int w = 0; w < getWidth(); w++) {
+ comps[0] = imgBytes[n] & 0xff;
+ line[w] = colorSpace.toColor(comps);
+ n += 1;
+ }
+ bi.setPixels(line, 0, maxW, 0, h, maxW, 1);
+ }
+ } else {
+ throw new UnsupportedOperationException("image with unsupported colorspace " + colorSpace);
+ }
+ long stopTime = System.currentTimeMillis();
+ Log.i(TAG, "millis for converting image=" + (stopTime - startTime));
+ return bi;
+ }
+
+ // /**
+ // *
Parse the image stream into a buffered image. Note that this is
+ // * guaranteed to be called after all the other setXXX methods have been
+ // * called.
+ // *
+ // *
NOTE: the color convolving is extremely slow on large images.
+ // * It would be good to see if it could be moved out into the rendering
+ // * phases, where we might be able to scale the image down first.
data.length) {
+ // byte[] tempLargerData = new byte[tempExpectedSize];
+ // System.arraycopy (data, 0, tempLargerData, 0, data.length);
+ // db = new DataBufferByte (tempLargerData, tempExpectedSize);
+ // raster =
+ // Raster.createWritableRaster (sm, db, new Point (0, 0));
+ // } else {
+ // throw e;
+ // }
+ // }
+ //
+ // /*
+ // * Workaround for a bug on the Mac -- a class cast exception in
+ // * drawImage() due to the wrong data buffer type (?)
+ // */
+ // BufferedImage bi = null;
+ // if (cm instanceof IndexColorModel) {
+ // IndexColorModel icm = (IndexColorModel) cm;
+ //
+ // // choose the image type based on the size
+ // int type = BufferedImage.TYPE_BYTE_BINARY;
+ // if (getBitsPerComponent() == 8) {
+ // type = BufferedImage.TYPE_BYTE_INDEXED;
+ // }
+ //
+ // // create the image with an explicit indexed color model.
+ // bi = new BufferedImage(getWidth(), getHeight(), type, icm);
+ //
+ // // set the data explicitly as well
+ // bi.setData(raster);
+ // } else {
+ // bi = new BufferedImage(cm, raster, true, null);
+ // }
+ //
+ // // hack to avoid *very* slow conversion
+ // ColorSpace cs = cm.getColorSpace();
+ // ColorSpace rgbCS = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ //
+ // // add in the alpha data supplied by the SMask, if any
+ // PDFImage sMaskImage = getSMask();
+ // if (sMaskImage != null) {
+ // BufferedImage si = sMaskImage.getImage();
+ //
+ // BufferedImage outImage = new BufferedImage(getWidth(),
+ // getHeight(), BufferedImage.TYPE_INT_ARGB);
+ //
+ // int[] srcArray = new int[width];
+ // int[] maskArray = new int[width];
+ //
+ // for (int i = 0; i < height; i++) {
+ // bi.getRGB(0, i, width, 1, srcArray, 0, width);
+ // si.getRGB(0, i, width, 1, maskArray, 0, width);
+ //
+ // for (int j = 0; j < width; j++) {
+ // int ac = 0xff000000;
+ //
+ // maskArray[j] = ((maskArray[j] & 0xff) << 24) | (srcArray[j] & ~ac);
+ // }
+ //
+ // outImage.setRGB(0, i, width, 1, maskArray, 0, width);
+ // }
+ //
+ // bi = outImage;
+ // }
+ //
+ // return (bi);
+ // }
+
+ /**
+ * Get the image's width
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Set the image's width
+ */
+ protected void setWidth(int width) {
+ this.width = width;
+ }
+
+ /**
+ * Get the image's height
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Set the image's height
+ */
+ protected void setHeight(int height) {
+ this.height = height;
+ }
+
+ /**
+ * set the color key mask. It is an array of start/end entries
+ * to indicate ranges of color indicies that should be masked out.
+ */
+ private void setColorKeyMask(PDFObject maskArrayObject) throws IOException {
+ PDFObject[] maskObjects = maskArrayObject.getArray();
+ colorKeyMask = null;
+ int[] masks = new int[maskObjects.length];
+ for (int i = 0; i < masks.length; i++) {
+ masks[i] = maskObjects[i].getIntValue();
+ }
+ colorKeyMask = masks;
+ }
+
+ /**
+ * Get the colorspace associated with this image, or null if there
+ * isn't one
+ */
+ protected PDFColorSpace getColorSpace() {
+ return colorSpace;
+ }
+
+ /**
+ * Set the colorspace associated with this image
+ */
+ protected void setColorSpace(PDFColorSpace colorSpace) {
+ this.colorSpace = colorSpace;
+ }
+
+ /**
+ * Get the number of bits per component sample
+ */
+ protected int getBitsPerComponent() {
+ return bpc;
+ }
+
+ /**
+ * Set the number of bits per component sample
+ */
+ protected void setBitsPerComponent(int bpc) {
+ this.bpc = bpc;
+ }
+
+ /**
+ * Return whether or not this is an image mask
+ */
+ public boolean isImageMask() {
+ return imageMask;
+ }
+
+ /**
+ * Set whether or not this is an image mask
+ */
+ public void setImageMask(boolean imageMask) {
+ this.imageMask = imageMask;
+ }
+
+ /**
+ * Return the soft mask associated with this image
+ */
+ public PDFImage getSMask() {
+ return sMask;
+ }
+
+ /**
+ * Set the soft mask image
+ */
+ protected void setSMask(PDFImage sMask) {
+ this.sMask = sMask;
+ }
+
+ /**
+ * Get the decode array
+ */
+ protected float[] getDecode() {
+ return decode;
+ }
+
+ /**
+ * Set the decode array
+ */
+ protected void setDecode(float[] decode) {
+ this.decode = decode;
+ }
+
+ // /**
+ // * get a Java ColorModel consistent with the current color space,
+ // * number of bits per component and decode array
+ // *
+ // * @param bpc the number of bits per component
+ // */
+ // private ColorModel getColorModel() {
+ // PDFColorSpace cs = getColorSpace();
+ //
+ // if (cs instanceof IndexedColor) {
+ // IndexedColor ics = (IndexedColor) cs;
+ //
+ // byte[] components = ics.getColorComponents();
+ // int num = ics.getCount();
+ //
+ // // process the decode array
+ // if (decode != null) {
+ // byte[] normComps = new byte[components.length];
+ //
+ // // move the components array around
+ // for (int i = 0; i < num; i++) {
+ // byte[] orig = new byte[1];
+ // orig[0] = (byte) i;
+ //
+ // float[] res = normalize(orig, null, 0);
+ // int idx = (int) res[0];
+ //
+ // normComps[i * 3] = components[idx * 3];
+ // normComps[(i * 3) + 1] = components[(idx * 3) + 1];
+ // normComps[(i * 3) + 2] = components[(idx * 3) + 2];
+ // }
+ //
+ // components = normComps;
+ // }
+ //
+ // // make sure the size of the components array is 2 ^ numBits
+ // // since if it's not, Java will complain
+ // int correctCount = 1 << getBitsPerComponent();
+ // if (correctCount < num) {
+ // byte[] fewerComps = new byte[correctCount * 3];
+ //
+ // System.arraycopy(components, 0, fewerComps, 0, correctCount * 3);
+ //
+ // components = fewerComps;
+ // num = correctCount;
+ // }
+ // if (colorKeyMask == null || colorKeyMask.length == 0) {
+ // return new IndexColorModel(getBitsPerComponent(), num, components,
+ // 0, false);
+ // } else {
+ // byte[] aComps = new byte[num * 4];
+ // int idx = 0;
+ // for (int i = 0; i < num; i++) {
+ // aComps[idx++] = components[(i * 3)];
+ // aComps[idx++] = components[(i * 3) + 1];
+ // aComps[idx++] = components[(i * 3) + 2];
+ // aComps[idx++] = (byte) 0xFF;
+ // }
+ // for (int i = 0; i < colorKeyMask.length; i += 2) {
+ // for (int j = colorKeyMask[i]; j <= colorKeyMask[i + 1]; j++) {
+ // aComps[(j * 4) + 3] = 0; // make transparent
+ // }
+ // }
+ // return new IndexColorModel(getBitsPerComponent(), num, aComps,
+ // 0, true);
+ // }
+ // } else {
+ // int[] bits = new int[cs.getNumComponents()];
+ // for (int i = 0; i < bits.length; i++) {
+ // bits[i] = getBitsPerComponent();
+ // }
+ //
+ // return new DecodeComponentColorModel(cs.getColorSpace(), bits);
+ // }
+ // }
+
+ /**
+ * Normalize an array of values to match the decode array
+ */
+ private float[] normalize(byte[] pixels, float[] normComponents, int normOffset) {
+ if (normComponents == null) {
+ normComponents = new float[normOffset + pixels.length];
+ }
+
+ float[] decodeArray = getDecode();
+
+ for (int i = 0; i < pixels.length; i++) {
+ int val = pixels[i] & 0xff;
+ int pow = ((int) Math.pow(2, getBitsPerComponent())) - 1;
+ float ymin = decodeArray[i * 2];
+ float ymax = decodeArray[(i * 2) + 1];
+
+ normComponents[normOffset + i] = FunctionType0.interpolate(val, 0, pow, ymin, ymax);
+ }
+
+ return normComponents;
+ }
+
+ // /**
+ // * A wrapper for ComponentColorSpace which normalizes based on the
+ // * decode array.
+ // */
+ // class DecodeComponentColorModel extends ComponentColorModel {
+ //
+ // public DecodeComponentColorModel(ColorSpace cs, int[] bpc) {
+ // super(cs, bpc, false, false, Transparency.OPAQUE,
+ // DataBuffer.TYPE_BYTE);
+ //
+ // if (bpc != null) {
+ // pixel_bits = bpc.length * bpc[0];
+ // }
+ // }
+ //
+ // @Override
+ // public SampleModel createCompatibleSampleModel(int width, int height) {
+ // // workaround -- create a MultiPixelPackedSample models for
+ // // single-sample, less than 8bpp color models
+ // if (getNumComponents() == 1 && getPixelSize() < 8) {
+ // return new MultiPixelPackedSampleModel(getTransferType(),
+ // width,
+ // height,
+ // getPixelSize());
+ // }
+ //
+ // return super.createCompatibleSampleModel(width, height);
+ // }
+ //
+ // @Override
+ // public boolean isCompatibleRaster(Raster raster) {
+ // if (getNumComponents() == 1 && getPixelSize() < 8) {
+ // SampleModel sm = raster.getSampleModel();
+ //
+ // if (sm instanceof MultiPixelPackedSampleModel) {
+ // return (sm.getSampleSize(0) == getPixelSize());
+ // } else {
+ // return false;
+ // }
+ // }
+ //
+ // return super.isCompatibleRaster(raster);
+ // }
+ //
+ // @Override
+ // public float[] getNormalizedComponents(Object pixel,
+ // float[] normComponents, int normOffset) {
+ // if (getDecode() == null) {
+ // return super.getNormalizedComponents(pixel, normComponents,
+ // normOffset);
+ // }
+ //
+ // return normalize((byte[]) pixel, normComponents, normOffset);
+ // }
+ // }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFNativeTextCmd.java b/PdfView/src/main/java/com/sun/pdfview/PDFNativeTextCmd.java
new file mode 100644
index 0000000..9546bd0
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFNativeTextCmd.java
@@ -0,0 +1,200 @@
+/*
+ * $Id: PDFShapeCmd.java,v 1.3 2009/01/16 16:26:15 tomoke 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;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Paint.FontMetrics;
+import android.util.Log;
+
+
+/**
+ * Encapsulates a path. Also contains extra fields and logic to check
+ * for consecutive abutting anti-aliased regions. We stroke the shared
+ * line between these regions again with a 1-pixel wide line so that
+ * the background doesn't show through between them.
+ *
+ * @author Mike Wessler
+ */
+public class PDFNativeTextCmd extends PDFCmd {
+
+ private static final String TAG = "ANDPDF.natTXT";
+
+ /** stroke the outline of the path with the stroke paint */
+ public static final int STROKE = 1;
+ /** fill the path with the fill paint */
+ public static final int FILL = 2;
+ /** perform both stroke and fill */
+ public static final int BOTH = 3;
+ /** set the clip region to the path */
+ public static final int CLIP = 4;
+
+ /** the style */
+ private int style;
+
+ /** the bounding box of the path */
+ private RectF bounds;
+
+ private Matrix mat;
+ private float x;
+ private float y;
+ private float w;
+ private float h;
+ private String text;
+
+ /**
+ * create a new PDFNativeTextCmd
+ */
+ public PDFNativeTextCmd(String text, Matrix mat) {
+
+// Log.i(TAG, "NATIVETEXTCMD['"+text+"',"+mat+"]");
+ this.text = text;
+ this.mat = mat;
+ float[] values = new float[9];
+ mat.getValues(values);
+ this.x = values[2];
+ this.y = values[5];
+ this.w = values[0];
+ this.h = values[4];
+ bounds = new RectF(x, y, x+w, y+h);
+ }
+
+ /**
+ * perform the stroke and record the dirty region
+ */
+ public RectF execute(PDFRenderer state) {
+ RectF rect = state.drawNativeText(text, bounds);
+ return rect;
+ }
+
+// /**
+// * Check for overlap with the previous shape to make anti-aliased shapes
+// * that are near each other look good
+// */
+// private Path checkOverlap(PDFRenderer state) {
+// if (style == FILL && gp != null && state.getLastShape() != null) {
+// float mypoints[] = new float[16];
+// float prevpoints[] = new float[16];
+//
+// int mycount = getPoints(gp, mypoints);
+// int prevcount = getPoints(state.getLastShape(), prevpoints);
+//
+// // now check mypoints against prevpoints for opposite pairs:
+// if (mypoints != null && prevpoints != null) {
+// for (int i = 0; i < prevcount; i += 4) {
+// for (int j = 0; j < mycount; j += 4) {
+// if ((Math.abs(mypoints[j + 2] - prevpoints[i]) < 0.01 &&
+// Math.abs(mypoints[j + 3] - prevpoints[i + 1]) < 0.01 &&
+// Math.abs(mypoints[j] - prevpoints[i + 2]) < 0.01 &&
+// Math.abs(mypoints[j + 1] - prevpoints[i + 3]) < 0.01)) {
+// GeneralPath strokeagain = new GeneralPath();
+// strokeagain.moveTo(mypoints[j], mypoints[j + 1]);
+// strokeagain.lineTo(mypoints[j + 2], mypoints[j + 3]);
+// return strokeagain;
+// }
+// }
+// }
+// }
+// }
+//
+// // no issues
+// return null;
+// }
+//
+// /**
+// * Get an array of 16 points from a path
+// * @return the number of points we actually got
+// */
+// private int getPoints(GeneralPath path, float[] mypoints) {
+// int count = 0;
+// float x = 0;
+// float y = 0;
+// float startx = 0;
+// float starty = 0;
+// float[] coords = new float[6];
+//
+// PathIterator pi = path.getPathIterator(new AffineTransform());
+// while (!pi.isDone()) {
+// if (count >= mypoints.length) {
+// mypoints = null;
+// break;
+// }
+//
+// int pathtype = pi.currentSegment(coords);
+// switch (pathtype) {
+// case PathIterator.SEG_MOVETO:
+// startx = x = coords[0];
+// starty = y = coords[1];
+// break;
+// case PathIterator.SEG_LINETO:
+// mypoints[count++] = x;
+// mypoints[count++] = y;
+// x = mypoints[count++] = coords[0];
+// y = mypoints[count++] = coords[1];
+// break;
+// case PathIterator.SEG_QUADTO:
+// x = coords[2];
+// y = coords[3];
+// break;
+// case PathIterator.SEG_CUBICTO:
+// x = mypoints[4];
+// y = mypoints[5];
+// break;
+// case PathIterator.SEG_CLOSE:
+// mypoints[count++] = x;
+// mypoints[count++] = y;
+// x = mypoints[count++] = startx;
+// y = mypoints[count++] = starty;
+// break;
+// }
+//
+// pi.next();
+// }
+//
+// return count;
+// }
+
+ /** Get detailed information about this shape
+ */
+ @Override
+ public String getDetails() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("NativeTextCommand Text: '" + text + "'\n");
+ sb.append("matrix: " + mat + "\n");
+ sb.append("Mode: ");
+ if ((style & FILL) != 0) {
+ sb.append("FILL ");
+ }
+ if ((style & STROKE) != 0) {
+ sb.append("STROKE ");
+ }
+ if ((style & CLIP) != 0) {
+ sb.append("CLIP");
+ }
+ return sb.toString();
+ }
+
+ public float getWidth() {
+ return 6.0f*text.length();
+// return 0.5f*text.length()*w;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFObject.java b/PdfView/src/main/java/com/sun/pdfview/PDFObject.java
new file mode 100644
index 0000000..ce5cde5
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFObject.java
@@ -0,0 +1,761 @@
+/*
+ * $Id: PDFObject.java,v 1.8 2009/03/15 20:47:38 tomoke 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;
+
+import java.io.IOException;
+
+import net.sf.andpdf.refs.SoftReference;
+
+import java.util.*;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.decode.PDFDecoder;
+import com.sun.pdfview.decrypt.PDFDecrypter;
+import com.sun.pdfview.decrypt.IdentityDecrypter;
+
+/**
+ * a class encapsulating all the possibilities of content for
+ * an object in a PDF file.
+ *
+ * A PDF object can be a simple type, like a Boolean, a Number,
+ * a String, or the Null value. It can also be a NAME, which
+ * looks like a string, but is a special type in PDF files, like
+ * "/Name".
+ *
+ * A PDF object can also be complex types, including Array;
+ * Dictionary; Stream, which is a Dictionary plus an array of
+ * bytes; or Indirect, which is a reference to some other
+ * PDF object. Indirect references will always be dereferenced
+ * by the time any data is returned from one of the methods
+ * in this class.
+ *
+ * @author Mike Wessler
+ */
+public class PDFObject {
+
+ /** an indirect reference*/
+ public static final int INDIRECT = 0; // PDFXref
+ /** a Boolean */
+ public static final int BOOLEAN = 1; // Boolean
+ /** a Number, represented as a double */
+ public static final int NUMBER = 2; // Double
+ /** a String */
+ public static final int STRING = 3; // String
+ /** a special string, seen in PDF files as /Name */
+ public static final int NAME = 4; // String
+ /** an array of PDFObjects */
+ public static final int ARRAY = 5; // Array of PDFObject
+ /** a Hashmap that maps String names to PDFObjects */
+ public static final int DICTIONARY = 6; // HashMap(String->PDFObject)
+ /** a Stream: a Hashmap with a byte array */
+ public static final int STREAM = 7; // HashMap + byte[]
+ /** the NULL object (there is only one) */
+ public static final int NULL = 8; // null
+ /** a special PDF bare word, like R, obj, true, false, etc */
+ public static final int KEYWORD = 9; // String
+ /**
+ * When a value of {@link #getObjGen objNum} or {@link #getObjGen objGen},
+ * indicates that the object is not top-level, and is embedded in another
+ * object
+ */
+ public static final int OBJ_NUM_EMBEDDED = -2;
+
+ /**
+ * When a value of {@link #getObjGen objNum} or {@link #getObjGen objGen},
+ * indicates that the object is not top-level, and is embedded directly
+ * in the trailer.
+ */
+ public static final int OBJ_NUM_TRAILER = -1;
+
+ /** the NULL PDFObject */
+ public static final PDFObject nullObj = new PDFObject(null, NULL, null);
+ /** the type of this object */
+ private int type;
+ /** the value of this object. It can be a wide number of things, defined by type */
+ private Object value;
+ /** the encoded stream, if this is a STREAM object */
+ private ByteBuffer stream;
+ /** a cached version of the decoded stream */
+ private SoftReference decodedStream;
+ /**
+ * the PDFFile from which this object came, used for
+ * dereferences
+ */
+ private PDFFile owner;
+ /**
+ * a cache of translated data. This data can be
+ * garbage collected at any time, after which it will
+ * have to be rebuilt.
+ */
+ private SoftReference cache;
+
+ /** @see #getObjNum() */
+ private int objNum = OBJ_NUM_EMBEDDED;
+
+ /** @see #getObjGen() */
+ private int objGen = OBJ_NUM_EMBEDDED;
+
+ /**
+ * create a new simple PDFObject with a type and a value
+ * @param owner the PDFFile in which this object resides, used
+ * for dereferencing. This may be null.
+ * @param type the type of object
+ * @param value the value. For DICTIONARY, this is a HashMap.
+ * for ARRAY it's an ArrayList. For NUMBER, it's a Double.
+ * for BOOLEAN, it's Boolean.TRUE or Boolean.FALSE. For
+ * everything else, it's a String.
+ */
+ public PDFObject(PDFFile owner, int type, Object value) {
+ this.type = type;
+ if (type == NAME) {
+ value = ((String) value).intern();
+ } else if (type == KEYWORD && value.equals("true")) {
+ this.type = BOOLEAN;
+ value = Boolean.TRUE;
+ } else if (type == KEYWORD && value.equals("false")) {
+ this.type = BOOLEAN;
+ value = Boolean.FALSE;
+ }
+ this.value = value;
+ this.owner = owner;
+ }
+
+ /**
+ * create a new PDFObject that is the closest match to a
+ * given Java object. Possibilities include Double, String,
+ * PDFObject[], HashMap, Boolean, or PDFParser.Tok,
+ * which should be "true" or "false" to turn into a BOOLEAN.
+ *
+ * @param obj the sample Java object to convert to a PDFObject.
+ * @throws PDFParseException if the object isn't one of the
+ * above examples, and can't be turned into a PDFObject.
+ */
+ public PDFObject(Object obj) throws PDFParseException {
+ this.owner = null;
+ this.value = obj;
+ if ((obj instanceof Double) || (obj instanceof Integer)) {
+ this.type = NUMBER;
+ } else if (obj instanceof String) {
+ this.type = NAME;
+ } else if (obj instanceof PDFObject[]) {
+ this.type = ARRAY;
+ } else if (obj instanceof Object[]) {
+ Object[] srcary = (Object[]) obj;
+ PDFObject[] dstary = new PDFObject[srcary.length];
+ for (int i = 0; i < srcary.length; i++) {
+ dstary[i] = new PDFObject(srcary[i]);
+ }
+ value = dstary;
+ this.type = ARRAY;
+ } else if (obj instanceof HashMap) {
+ this.type = DICTIONARY;
+ } else if (obj instanceof Boolean) {
+ this.type = BOOLEAN;
+ } else if (obj instanceof PDFParser.Tok) {
+ PDFParser.Tok tok = (PDFParser.Tok) obj;
+ if (tok.name.equals("true")) {
+ this.value = Boolean.TRUE;
+ this.type = BOOLEAN;
+ } else if (tok.name.equals("false")) {
+ this.value = Boolean.FALSE;
+ this.type = BOOLEAN;
+ } else {
+ this.value = tok.name;
+ this.type = NAME;
+ }
+ } else {
+ throw new PDFParseException("Bad type for raw PDFObject: " + obj);
+ }
+ }
+
+ /**
+ * create a new PDFObject based on a PDFXref
+ * @param owner the PDFFile from which the PDFXref was drawn
+ * @param xref the PDFXref to turn into a PDFObject
+ */
+ public PDFObject(PDFFile owner, PDFXref xref) {
+ this.type = INDIRECT;
+ this.value = xref;
+ this.owner = owner;
+ }
+
+ /**
+ * get the type of this object. The object will be
+ * dereferenced, so INDIRECT will never be returned.
+ * @return the type of the object
+ */
+ public int getType() throws IOException {
+ if (type == INDIRECT) {
+ return dereference().getType();
+ }
+
+ return type;
+ }
+
+ /**
+ * set the stream of this object. It should have been
+ * a DICTIONARY before the call.
+ * @param data the data, as a ByteBuffer.
+ */
+ public void setStream(ByteBuffer data) {
+ this.type = STREAM;
+ this.stream = data;
+ }
+
+ /**
+ * get the value in the cache. May become null at any time.
+ * @return the cached value, or null if the value has been
+ * garbage collected.
+ */
+ public Object getCache() throws IOException {
+ if (type == INDIRECT) {
+ return dereference().getCache();
+ } else if (cache != null) {
+ return cache.get();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * set the cached value. The object may be garbage collected
+ * if no other reference exists to it.
+ * @param obj the object to be cached
+ */
+ public void setCache(Object obj) throws IOException {
+ if (type == INDIRECT) {
+ dereference().setCache(obj);
+ return;
+ } else {
+ cache = new SoftReference
+ *
+ * @param basicString the basic PDF string, as offered by {@link
+ * PDFObject#getStringValue()}
+ * @return either the original input, or the input decoded as UTF-16
+ */
+ public static String asTextString(String basicString) {
+ if (basicString == null) {
+ return null;
+ }
+
+ if (basicString.length() >= 2) {
+ if ((basicString.charAt(0) == (char) 0xFE
+ && basicString.charAt(1) == (char) 0xFF)) {
+ // found the BOM!
+ return asUTF16BEEncoded(basicString);
+ }
+ }
+
+ // it's not UTF16-BE encoded, so it must be
+ return asPDFDocEncoded(basicString);
+ }
+
+ /**
+ * Take a basic PDF string and produce a string of its bytes as encoded in
+ * PDFDocEncoding. The PDFDocEncoding is described in the PDF Reference.
+ *
+ * @param basicString the basic PDF string, as offered by {@link
+ * PDFObject#getStringValue()}
+ * @return the decoding of the string's bytes in PDFDocEncoding
+ */
+ public static String asPDFDocEncoded(String basicString) {
+ final StringBuilder buf = new StringBuilder(basicString.length());
+ for (int i = 0; i < basicString.length(); ++i) {
+ final char c = PDF_DOC_ENCODING_MAP[basicString.charAt(i) & 0xFF];
+ buf.append(c);
+ }
+ return buf.toString();
+ }
+
+ public byte[] toPDFDocEncoded(String string)
+ throws CharacterCodingException {
+ // we can just grab array since we know that if charset completes
+ // without error then there's the output buffer will be exactly
+ // correct in size, since there's only ever 1 byte for one char.
+ return new PDFDocCharsetEncoder().encode(CharBuffer.wrap(string)).
+ array();
+ }
+
+ /**
+ * Take a basic PDF string and produce a string from its bytes as an
+ * UTF16-BE encoding. The first 2 bytes are presumed to be the big-endian
+ * byte markers, 0xFE and 0xFF; that is not checked by this method.
+ *
+ * @param basicString the basic PDF string, as offered by {@link
+ * PDFObject#getStringValue()}
+ * @return the decoding of the string's bytes in UTF16-BE
+ */
+ public static String asUTF16BEEncoded(String basicString) {
+ try {
+ return new String(asBytes(basicString),
+ 2, basicString.length() - 2, "UTF-16BE");
+ } catch (UnsupportedEncodingException e) {
+ // UTF-16BE should always be available
+ throw new RuntimeException("No UTF-16BE charset!");
+ }
+ }
+
+ /**
+ * Get the corresponding byte array for a basic string. This is effectively
+ * the char[] array cast to bytes[], as chars in basic strings only use the
+ * least significant byte.
+ *
+ * @param basicString the basic PDF string, as offered by {@link
+ * PDFObject#getStringValue()}
+ * @return the bytes corresponding to its characters
+ */
+ public static byte[] asBytes(String basicString) {
+ final byte[] b = new byte[basicString.length()];
+ for (int i = 0; i < b.length; ++i) {
+ b[i] = (byte) basicString.charAt(i);
+ }
+ return b;
+ }
+
+ /**
+ * Create a basic string from bytes. This is effectively the byte array
+ * cast to a char array and turned into a String.
+ * @param bytes the source of the bytes for the basic string
+ * @param offset the offset into butes where the string starts
+ * @param length the number of bytes to turn into a string
+ * @return the corresponding string
+ */
+ public static String asBasicString(
+ byte[] bytes, int offset, int length) {
+ final char[] c = new char[length];
+ for (int i = 0; i < c.length; ++i) {
+ c[i] = (char) bytes[i + offset];
+ }
+ return new String(c);
+ }
+
+ /**
+ * Create a basic string from bytes. This is effectively the byte array
+ * cast to a char array and turned into a String.
+ * @param bytes the bytes, all of which are used
+ * @return the corresponding string
+ */
+ public static String asBasicString(byte[] bytes) {
+ return asBasicString(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Maps from PDFDocEncoding bytes to unicode characters. Table generated
+ * by PDFDocEncodingMapGenerator.
+ */
+ final static char[] PDF_DOC_ENCODING_MAP = new char[] {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, //00-07
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, //08-0F
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, //10-17
+ 0x02D8, 0x02C7, 0x02C6, 0x02D9, 0x02DD, 0x02DB, 0x02DA, 0x02DC, //18-1F
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, //20-27
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, //28-2F
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, //30-37
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, //38-3F
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, //40-47
+ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, //48-4F
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, //50-57
+ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, //58-5F
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, //60-67
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, //68-6F
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, //70-77
+ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0xFFFD, //78-7F
+ 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x0192, 0x2044, //80-87
+ 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, 0x201D, 0x2018, //88-8F
+ 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x0141, 0x0152, 0x0160, //90-97
+ 0x0178, 0x017D, 0x0131, 0x0142, 0x0153, 0x0161, 0x017E, 0xFFFD, //98-9F
+ 0x20AC, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, //A0-A7
+ 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0xFFFD, 0x00AE, 0x00AF, //A8-AF
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, //B0-B7
+ 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, //B8-BF
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, //C0-C7
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, //C8-CF
+ 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, //D0-D7
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, //D8-DF
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, //E0-E7
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, //E8-EF
+ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, //F0-F7
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, //F8-FF
+ };
+
+
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFTextFormat.java b/PdfView/src/main/java/com/sun/pdfview/PDFTextFormat.java
new file mode 100644
index 0000000..748cf3a
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFTextFormat.java
@@ -0,0 +1,396 @@
+/*
+ * $Id: PDFTextFormat.java,v 1.3 2009/01/16 16:26:11 tomoke 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;
+
+import java.util.List;
+
+import net.sf.andpdf.utils.Utils;
+
+import android.graphics.Matrix;
+import android.graphics.PointF;
+
+import com.sun.pdfview.font.OutlineFont;
+import com.sun.pdfview.font.PDFFont;
+import com.sun.pdfview.font.PDFGlyph;
+
+/**
+ * a class encapsulating the text state
+ *
+ * @author Mike Wessler
+ * @author Ferenc Hechler (ferenc@hechler.de)
+ * @author Joerg Jahnke (joergjahnke@users.sourceforge.net)
+ */
+public class PDFTextFormat implements Cloneable {
+
+ /** character spacing */
+ private float tc = 0;
+ /** word spacing */
+ private float tw = 0;
+ /** horizontal scaling */
+ private float th = 1;
+ /** leading */
+ private float tl = 0;
+ /** rise amount */
+ private float tr = 0;
+ /** text mode */
+ private int tm = PDFShapeCmd.FILL;
+ /** text knockout */
+ private float tk = 0;
+ /** current matrix transform */
+ private final Matrix cur;
+ /** matrix transform at start of line */
+ private Matrix line;
+ /** font */
+ private PDFFont font;
+ /** font size */
+ private float fsize = 1;
+ /** are we between BT and ET? */
+ private boolean inuse = false;
+ // private Object array[]= new Object[1];
+ /** build text rep of word */
+ private StringBuffer word = new StringBuffer();
+ // this is where we build and keep the word list for this page.
+ /** start location of the hunk of text */
+ private PointF wordStart;
+ /** location of the end of the previous hunk of text */
+ private final PointF prevEnd;
+
+ /**
+ * create a new PDFTextFormat, with initial values
+ */
+ public PDFTextFormat() {
+ cur = new Matrix();
+ line = new Matrix();
+ wordStart = new PointF(-100, -100);
+ prevEnd = new PointF(-100, -100);
+
+ tc = tw = tr = tk = 0;
+ tm = PDFShapeCmd.FILL;
+ th = 1;
+ }
+
+ /**
+ * reset the PDFTextFormat for a new run
+ */
+ public void reset() {
+ cur.reset();
+ line.reset();
+ inuse = true;
+ word.setLength(0);
+ }
+
+ /**
+ * end a span of text
+ */
+ public void end() {
+ inuse = false;
+ }
+
+ /** get the char spacing */
+ public float getCharSpacing() {
+ return tc;
+ }
+
+ /** set the character spacing */
+ public void setCharSpacing(float spc) {
+ this.tc = spc;
+ }
+
+ /** get the word spacing */
+ public float getWordSpacing() {
+ return tw;
+ }
+
+ /** set the word spacing */
+ public void setWordSpacing(float spc) {
+ this.tw = spc;
+ }
+
+ /**
+ * Get the horizontal scale
+ * @return the horizontal scale, in percent
+ */
+ public float getHorizontalScale() {
+ return th * 100;
+ }
+
+ /**
+ * set the horizontal scale.
+ * @param scl the horizontal scale, in percent (100=normal)
+ */
+ public void setHorizontalScale(float scl) {
+ this.th = scl / 100;
+ }
+
+ /** get the leading */
+ public float getLeading() {
+ return tl;
+ }
+
+ /** set the leading */
+ public void setLeading(float spc) {
+ this.tl = spc;
+ }
+
+ /** get the font */
+ public PDFFont getFont() {
+ return font;
+ }
+
+ /** get the font size */
+ public float getFontSize() {
+ return fsize;
+ }
+
+ /** set the font and size */
+ public void setFont(PDFFont f, float size) {
+ this.font = f;
+ this.fsize = size;
+ }
+
+ /**
+ * Get the mode of the text
+ */
+ public int getMode() {
+ return tm;
+ }
+
+ /**
+ * set the mode of the text. The correspondence of m to mode is
+ * show in the following table. m is a value from 0-7 in binary:
+ *
+ * 000 Fill
+ * 001 Stroke
+ * 010 Fill + Stroke
+ * 011 Nothing
+ * 100 Fill + Clip
+ * 101 Stroke + Clip
+ * 110 Fill + Stroke + Clip
+ * 111 Clip
+ *
+ * Therefore: Fill corresponds to the low bit being 0; Clip
+ * corresponds to the hight bit being 1; and Stroke corresponds
+ * to the middle xor low bit being 1.
+ */
+ public void setMode(int m) {
+ int mode = 0;
+
+ if ((m & 0x1) == 0) {
+ mode |= PDFShapeCmd.FILL;
+ }
+ if ((m & 0x4) != 0) {
+ mode |= PDFShapeCmd.CLIP;
+ }
+ if (((m & 0x1) ^ ((m & 0x2) >> 1)) != 0) {
+ mode |= PDFShapeCmd.STROKE;
+ }
+
+ this.tm = mode;
+ }
+
+ /**
+ * Set the mode from another text format mode
+ *
+ * @param mode the text render mode using the
+ * codes from PDFShapeCmd and not the wacky PDF codes
+ */
+ public void setTextFormatMode(int mode) {
+ this.tm = mode;
+ }
+
+ /**
+ * Get the rise
+ */
+ public float getRise() {
+ return tr;
+ }
+
+ /**
+ * set the rise
+ */
+ public void setRise(float spc) {
+ this.tr = spc;
+ }
+
+ /**
+ * perform a carriage return
+ */
+ public void carriageReturn() {
+ carriageReturn(0, -tl);
+ }
+
+ /**
+ * perform a carriage return by translating by x and y. The next
+ * carriage return will be relative to the new location.
+ */
+ public void carriageReturn(float x, float y) {
+ Matrix trans = new Matrix();
+ trans.setTranslate(x, y);
+ line.preConcat(trans);
+ cur.set(line);
+ }
+
+ /**
+ * Get the current transform
+ */
+ public Matrix getTransform() {
+ return cur;
+ }
+
+ /**
+ * set the transform matrix directly
+ */
+ public void setMatrix(float[] matrix) {
+ line = new Matrix();
+ Utils.setMatValues(line, matrix);
+ cur.set(line);
+ }
+
+ public void doText(final PDFPage cmds, final String text) {
+ if (PDFFont.sUseFontSubstitution)
+ doTextFontSubst(cmds, text);
+ else
+ doTextNormal(cmds, text);
+ }
+
+ /**
+ * add some text to the page.
+ * @param cmds the PDFPage to add the commands to
+ * @param text the text to add
+ */
+ private void doTextFontSubst(final PDFPage cmds, final String text) {
+ final PointF zero = new PointF();
+ final Matrix scale = new Matrix();
+ Utils.setMatValues(scale, fsize, 0, 0, fsize * th, 0, tr);
+ final Matrix at = new Matrix();
+
+ at.set(cur);
+ at.preConcat(scale);
+
+ PDFNativeTextCmd ntx = new PDFNativeTextCmd(text, at);
+ cmds.addCommand(ntx);
+
+ // calc widths
+ for (int i=0; i l = (List) font.getGlyphs(text);
+
+ for (final PDFGlyph glyph : l) {
+ at.set(cur);
+ at.preConcat(scale);
+ final PointF advance = glyph.addCommands(cmds, at, tm);
+
+ float advanceX = (advance.x * fsize) + tc;
+ if (glyph.getChar() == ' ') {
+ advanceX += tw;
+ }
+ advanceX *= th;
+
+ cur.preTranslate(advanceX, advance.y);
+ }
+
+ final float[] src = {zero.x, zero.y};
+ final float[] dst = new float[src.length];
+ cur.mapPoints(dst, src);
+ prevEnd.set(dst[0], dst[1]);
+ }
+
+ /**
+ * add some text to the page.
+ * @param cmds the PDFPage to add the commands to
+ * @param ary an array of Strings and Doubles, where the Strings
+ * represent text to be added, and the Doubles represent kerning
+ * amounts.
+ */
+ public void doText(final PDFPage cmds, final Object ary[]) throws PDFParseException {
+ for (int i = 0, to = ary.length; i < to; ++i) {
+ if (ary[i] instanceof String) {
+ doText(cmds, (String) ary[i]);
+ } else if (ary[i] instanceof Double) {
+ float val = ((Double) ary[i]).floatValue() / 1000f;
+ cur.preTranslate(-val * fsize * th, 0);
+ } else {
+ throw new PDFParseException("Bad element in TJ array");
+ }
+ }
+ }
+
+ /**
+ * finish any unfinished words. TODO: write this!
+ */
+ public void flush() {
+ // TODO: finish any unfinished words
+ }
+
+ /**
+ * Clone the text format
+ */
+ @Override
+ public Object clone() {
+ PDFTextFormat newFormat = new PDFTextFormat();
+
+ // copy values
+ newFormat.setCharSpacing(getCharSpacing());
+ newFormat.setWordSpacing(getWordSpacing());
+ newFormat.setHorizontalScale(getHorizontalScale());
+ newFormat.setLeading(getLeading());
+ newFormat.setTextFormatMode(getMode());
+ newFormat.setRise(getRise());
+
+ // copy immutable fields
+ newFormat.setFont(getFont(), getFontSize());
+
+ // clone transform (mutable)
+ // newFormat.getTransform().setTransform(getTransform());
+
+ return newFormat;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/PDFXref.java b/PdfView/src/main/java/com/sun/pdfview/PDFXref.java
new file mode 100644
index 0000000..3b1beea
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/PDFXref.java
@@ -0,0 +1,143 @@
+/*
+ * $Id: PDFXref.java,v 1.4 2009/02/12 13:53:56 tomoke 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;
+
+import net.sf.andpdf.refs.SoftReference;
+
+/**
+ * a cross reference representing a line in the PDF cross referencing
+ * table.
+ *
+ * There are two forms of the PDFXref, destinguished by absolutely nothing.
+ * The first type of PDFXref is used as indirect references in a PDFObject.
+ * In this type, the id is an index number into the object cross reference
+ * table. The id will range from 0 to the size of the cross reference
+ * table.
+ *
+ * The second form is used in the Java representation of the cross reference
+ * table. In this form, the id is the file position of the start of the
+ * object in the PDF file. See the use of both of these in the
+ * PDFFile.dereference() method, which takes a PDFXref of the first form,
+ * and uses (internally) a PDFXref of the second form.
+ *
+ * This is an unhappy state of affairs, and should be fixed. Fortunatly,
+ * the two uses have already been factored out as two different methods.
+ *
+ * @author Mike Wessler
+ */
+public class PDFXref {
+
+ private int id;
+ private int generation;
+ private boolean compressed;
+
+ // this field is only used in PDFFile.objIdx
+ private SoftReference reference = null;
+
+ /**
+ * create a new PDFXref, given a parsed id and generation.
+ */
+ public PDFXref(int id, int gen) {
+ this.id = id;
+ this.generation = gen;
+ this.compressed = false;
+ }
+
+ /**
+ * create a new PDFXref, given a parsed id, compressedObjId and index
+ */
+ public PDFXref(int id, int gen, boolean compressed) {
+ this.id = id;
+ this.generation = gen;
+ this.compressed = compressed;
+ }
+
+ /**
+ * create a new PDFXref, given a sequence of bytes representing the
+ * fixed-width cross reference table line
+ */
+ public PDFXref(byte[] line) {
+ if (line == null) {
+ id = -1;
+ generation = -1;
+ } else {
+ id = Integer.parseInt(new String(line, 0, 10));
+ generation = Integer.parseInt(new String(line, 11, 5));
+ }
+ compressed = false;
+ }
+
+ /**
+ * get the character index into the file of the start of this object
+ */
+ public int getFilePos() {
+ return id;
+ }
+
+ /**
+ * get the generation of this object
+ */
+ public int getGeneration() {
+ return generation;
+ }
+
+ /**
+ * get the generation of this object
+ */
+ public int getIndex() {
+ return generation;
+ }
+
+ /**
+ * get the object number of this object
+ */
+ public int getID() {
+ return id;
+ }
+
+ /**
+ * get compressed flag of this object
+ */
+ public boolean getCompressed() {
+ return compressed;
+ }
+
+
+ /**
+ * Get the object this reference refers to, or null if it hasn't been
+ * set.
+ * @return the object if it exists, or null if not
+ */
+ public PDFObject getObject() {
+ if (reference != null) {
+ return (PDFObject) reference.get();
+ }
+
+ return null;
+ }
+
+ /**
+ * Set the object this reference refers to.
+ */
+ public void setObject(PDFObject obj) {
+ this.reference = new SoftReference(obj);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/RefImage.java b/PdfView/src/main/java/com/sun/pdfview/RefImage.java
new file mode 100644
index 0000000..df7738c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/RefImage.java
@@ -0,0 +1,57 @@
+/*
+ * $Id: RefImage.java,v 1.3 2009/01/16 16:26:10 tomoke 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;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Bitmap.Config;
+
+
+/**
+ * A BufferedImage subclass that holds a strong reference to its graphics
+ * object. This means that the graphics will never go away as long as
+ * someone holds a reference to this image, and createGraphics() and
+ * getGraphics() can be called multiple times safely, and will always return
+ * the same graphics object.
+ */
+public class RefImage {
+
+ /** a strong reference to the graphics object */
+ private Bitmap bi;
+ private Canvas g;
+
+ /** Creates a new instance of RefImage */
+ public RefImage(int width, int height, Config config) {
+ bi = Bitmap.createBitmap(width, height, config);
+ }
+
+ /**
+ * Create a graphics object only if it is currently null, otherwise
+ * return the existing graphics object.
+ */
+ public Canvas createGraphics() {
+ if (g == null) {
+ g = new Canvas(bi);
+ }
+
+ return g;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/Watchable.java b/PdfView/src/main/java/com/sun/pdfview/Watchable.java
new file mode 100644
index 0000000..26baeb9
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/Watchable.java
@@ -0,0 +1,73 @@
+/*
+ * $Id: Watchable.java,v 1.3 2009/01/16 16:26:15 tomoke 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;
+
+/**
+ * An interface for rendering or parsing, which can be stopped and started.
+ */
+public interface Watchable {
+
+ /** the possible statuses */
+ public static final int UNKNOWN = 0;
+ public static final int NOT_STARTED = 1;
+ public static final int PAUSED = 2;
+ public static final int NEEDS_DATA = 3;
+ public static final int RUNNING = 4;
+ public static final int STOPPED = 5;
+ public static final int COMPLETED = 6;
+ public static final int ERROR = 7;
+
+ /**
+ * Get the status of this watchable
+ *
+ * @return one of the well-known statuses
+ */
+ public int getStatus();
+
+ /**
+ * Stop this watchable. Stop will cause all processing to cease,
+ * and the watchable to be destroyed.
+ */
+ public void stop();
+
+ /**
+ * Start this watchable and run until it is finished or stopped.
+ * Note the watchable may be stopped if go() with a
+ * different time is called during execution.
+ */
+ public void go();
+
+ /**
+ * Start this watchable and run for the given number of steps or until
+ * finished or stopped.
+ *
+ * @param steps the number of steps to run for
+ */
+ public void go(int steps);
+
+ /**
+ * Start this watchable and run for the given amount of time, or until
+ * finished or stopped.
+ *
+ * @param millis the number of milliseconds to run for
+ */
+ public void go(long millis);
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/action/GoToAction.java b/PdfView/src/main/java/com/sun/pdfview/action/GoToAction.java
new file mode 100644
index 0000000..817f484
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/action/GoToAction.java
@@ -0,0 +1,70 @@
+/*
+ * $Id: GoToAction.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.action;
+
+import java.io.IOException;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFDestination;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * An action which specifies going to a particular destination
+ */
+public class GoToAction extends PDFAction {
+ /** the destination to go to */
+ private PDFDestination dest;
+
+ /**
+ * Creates a new instance of GoToAction from an object
+ *
+ * @param obj the PDFObject with the action information
+ */
+ public GoToAction(PDFObject obj, PDFObject root) throws IOException {
+ super("GoTo");
+
+ // find the destination
+ PDFObject destObj = obj.getDictRef("D");
+ if (destObj == null) {
+ throw new PDFParseException("No destination in GoTo action " + obj);
+ }
+
+ // parse it
+ dest = PDFDestination.getDestination(destObj, root);
+ }
+
+ /**
+ * Create a new GoToAction from a destination
+ */
+ public GoToAction(PDFDestination dest) {
+ super("GoTo");
+
+ this.dest = dest;
+ }
+
+ /**
+ * Get the destination this action refers to
+ */
+ public PDFDestination getDestination() {
+ return dest;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/action/PDFAction.java b/PdfView/src/main/java/com/sun/pdfview/action/PDFAction.java
new file mode 100644
index 0000000..8a2d6e8
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/action/PDFAction.java
@@ -0,0 +1,100 @@
+/*
+ * $Id: PDFAction.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.action;
+
+import java.io.IOException;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * The common super-class of all PDF actions.
+ */
+public class PDFAction {
+ /** the type of this action */
+ private String type;
+
+ /** the next action or array of actions */
+ private PDFObject next;
+
+ /** Creates a new instance of PDFAction */
+ public PDFAction(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Get an action of the appropriate type from a PDFObject
+ *
+ * @param obj the PDF object containing the action to parse
+ * @param root the root of the PDF object tree
+ */
+ public static PDFAction getAction(PDFObject obj, PDFObject root)
+ throws IOException
+ {
+ // figure out the action type
+ PDFObject typeObj = obj.getDictRef("S");
+ if (typeObj == null) {
+ throw new PDFParseException("No action type in object: " + obj);
+ }
+
+ // create the action based on the type
+ PDFAction action = null;
+ String type = typeObj.getStringValue();
+ if (type.equals("GoTo")) {
+ action = new GoToAction(obj, root);
+ } else {
+ /** [JK FIXME: Implement other action types! ] */
+ throw new PDFParseException("Unknown Action type: " + type);
+ }
+
+ // figure out if there is a next action
+ PDFObject nextObj = obj.getDictRef("Next");
+ if (nextObj != null) {
+ action.setNext(nextObj);
+ }
+
+ // return the action
+ return action;
+ }
+
+ /**
+ * Get the type of this action
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Get the next action or array of actions
+ */
+ public PDFObject getNext() {
+ return next;
+ }
+
+ /**
+ * Set the next action or array of actions
+ */
+ public void setNext(PDFObject next) {
+ this.next = next;
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/colorspace/AlternateColorSpace.java b/PdfView/src/main/java/com/sun/pdfview/colorspace/AlternateColorSpace.java
new file mode 100644
index 0000000..d316ce3
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/colorspace/AlternateColorSpace.java
@@ -0,0 +1,88 @@
+/*
+ * $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 com.sun.pdfview.function.PDFFunction;
+
+import android.graphics.Color;
+
+
+public class AlternateColorSpace extends PDFColorSpace {
+
+ /** The alternate color space */
+ private PDFColorSpace alternate;
+
+ /** The function */
+ private PDFFunction function;
+
+ /** Creates a new instance of AlternateColorSpace */
+ public AlternateColorSpace(PDFColorSpace alternate, PDFFunction function) {
+ this.alternate = alternate;
+ this.function = function;
+ }
+
+ /**
+ * get the number of components expected in the getPaint command
+ */
+ @Override public int getNumComponents() {
+ if (function != null) {
+ return function.getNumInputs();
+ } else {
+ return alternate.getNumComponents();
+ }
+ }
+
+ @Override public int toColor(float[] fcomp) {
+ if (function != null) {
+ // translate values using function
+ fcomp = function.calculate(fcomp);
+ }
+ float k = fcomp[3];
+ float w = 255*(1-k);
+ float r = w*(1-fcomp[0]);
+ float g = w*(1-fcomp[1]);
+ float b = w*(1-fcomp[2]);
+ return Color.rgb((int)r,(int)g,(int)b);
+ }
+
+ @Override public int toColor(int[] icomp) {
+ float[] fcomp = new float[icomp.length];
+ for (int i = 0; i < fcomp.length; i++)
+ fcomp[i] = icomp[i]/255;
+ return toColor(fcomp);
+ }
+
+
+ /**
+ * get the type of this color space (TYPE_CMYK)
+ */
+ @Override public int getType() {
+ return COLORSPACE_ALTERNATE;
+ }
+
+ @Override
+ public String getName() {
+ return "ALTERNATE";
+ }
+
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/colorspace/CMYKColorSpace.java b/PdfView/src/main/java/com/sun/pdfview/colorspace/CMYKColorSpace.java
new file mode 100644
index 0000000..3d5e92b
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/colorspace/CMYKColorSpace.java
@@ -0,0 +1,71 @@
+/*
+ * $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 CMYKColorSpace extends PDFColorSpace {
+
+ public CMYKColorSpace() {
+ }
+
+ /**
+ * get the number of components (4)
+ */
+ @Override public int getNumComponents() {
+ return 4;
+ }
+
+ @Override public int toColor(float[] fcomp) {
+ float k = fcomp[3];
+ float w = 255*(1-k);
+ float r = w*(1-fcomp[0]);
+ float g = w*(1-fcomp[1]);
+ float b = w*(1-fcomp[2]);
+ return Color.rgb((int)r,(int)g,(int)b);
+ }
+
+ @Override public int toColor(int[] icomp) {
+ int k = icomp[3];
+ int w = 255-k;
+ int r = w*(255-icomp[0])/255;
+ int g = w*(255-icomp[1])/255;
+ int b = w*(255-icomp[2])/255;
+ return Color.rgb(r,g,b);
+ }
+
+
+ /**
+ * get the type of this color space (TYPE_CMYK)
+ */
+ @Override public int getType() {
+ return COLORSPACE_CMYK;
+ }
+
+ @Override
+ public String getName() {
+ return "CMYK";
+ }
+
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/colorspace/GrayColorSpace.java b/PdfView/src/main/java/com/sun/pdfview/colorspace/GrayColorSpace.java
new file mode 100644
index 0000000..0aec22f
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/colorspace/GrayColorSpace.java
@@ -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 GrayColorSpace extends PDFColorSpace {
+
+ public GrayColorSpace() {
+ }
+
+ /**
+ * get the number of components (3)
+ */
+ @Override public int getNumComponents() {
+ return 1;
+ }
+
+ @Override public int toColor(float[] fcomp) {
+ return Color.rgb((int)(fcomp[0]*255),(int)(fcomp[0]*255),(int)(fcomp[0]*255));
+ }
+
+ @Override public int toColor(int[] icomp) {
+ return Color.rgb(icomp[0],icomp[0],icomp[0]);
+ }
+
+
+ /**
+ * get the type of this color space (TYPE_RGB)
+ */
+ @Override public int getType() {
+ return COLORSPACE_GRAY;
+ }
+
+ @Override
+ public String getName() {
+ return "G";
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/colorspace/ICCIColorSpace.java b/PdfView/src/main/java/com/sun/pdfview/colorspace/ICCIColorSpace.java
new file mode 100644
index 0000000..b97ce52
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/colorspace/ICCIColorSpace.java
@@ -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";
+ }
+
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/colorspace/IndexedColor.java b/PdfView/src/main/java/com/sun/pdfview/colorspace/IndexedColor.java
new file mode 100644
index 0000000..e80cae6
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/colorspace/IndexedColor.java
@@ -0,0 +1,125 @@
+/*
+ * $Id: IndexedColor.java,v 1.4 2009/01/26 05:40:42 tomoke 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 java.io.IOException;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFPaint;
+
+/**
+ * A PDFColorSpace for an IndexedColor model
+ *
+ * @author Mike Wessler
+ */
+public class IndexedColor extends PDFColorSpace {
+
+ /** the color table */
+ int table[];
+ /** size of the color table */
+ int count;
+
+ /**
+ * create a new IndexColor PDFColorSpace based on another PDFColorSpace,
+ * a count of colors, and a stream of values. Every consecutive n bytes
+ * of the stream is interpreted as a color in the base ColorSpace, where
+ * n is the number of components in that color space.
+ *
+ * @param base the color space in which the data is interpreted
+ * @param count the number of colors in the table
+ * @param stream a stream of bytes. The number of bytes must be count*n,
+ * where n is the number of components in the base colorspace.
+ */
+ public IndexedColor(PDFColorSpace base, int count, PDFObject stream) throws IOException {
+ count++;
+ this.count = count;
+ byte[] data = stream.getStream();
+ int nchannels = base.getNumComponents();
+ boolean offSized = (data.length / nchannels) < count;
+ table = new int[count];
+ float comps[] = new float[nchannels];
+ int loc = 0;
+ int finalloc = 0;
+ for (int i = 0; i < count; i++) {
+ for (int j = 0; j < comps.length; j++) {
+ if (loc < data.length) {
+ comps[j] = (((int) data[loc++]) & 0xff) / 255f;
+ } else {
+ comps[j] = 1.0f;
+ }
+ }
+ table[i] = base.toColor(comps);
+ }
+ }
+
+ /**
+ * create a new IndexColor PDFColorSpace based on a table of colors.
+ *
+ * @param table an array of colors
+ */
+ public IndexedColor(int[] table) throws IOException {
+ this.count = table.length;
+ this.table = table;
+ }
+
+ /**
+ * Get the number of indices
+ */
+ public int getCount() {
+ return count;
+ }
+
+ /**
+ * Get the table of color components
+ */
+ public int[] getColorTable() {
+ return table;
+ }
+
+ /**
+ * get the number of components of this colorspace (1)
+ */
+ @Override
+ public int getNumComponents() {
+ return 1;
+ }
+
+ @Override
+ public String getName() {
+ return "I";
+ }
+
+ @Override
+ public int getType() {
+ return COLORSPACE_INDEXED;
+ }
+
+ @Override
+ public int toColor(float[] comp) {
+ return table[(int)(255*comp[0])];
+ }
+
+ @Override
+ public int toColor(int[] comp) {
+ return table[comp[0]];
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/colorspace/PDFColorSpace.java b/PdfView/src/main/java/com/sun/pdfview/colorspace/PDFColorSpace.java
new file mode 100644
index 0000000..8a7df61
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/colorspace/PDFColorSpace.java
@@ -0,0 +1,213 @@
+/*
+ * $Id: PDFColorSpace.java,v 1.5 2009/03/08 20:46:16 tomoke 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 java.io.IOException;
+import java.util.Map;
+
+import com.sun.pdfview.PDFObject;
+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 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 pattern color space */
+ public static final int COLORSPACE_PATTERN = 3;
+
+ public static final int COLORSPACE_INDEXED = 4;
+
+ 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();
+ private static PDFColorSpace icciSpace = new ICCIColorSpace();
+ /** the pattern space */
+ private static PDFColorSpace patternSpace = new RGBColorSpace(); // TODO [FHe]
+
+ /** 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;
+
+ 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");
+ }
+
+ 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 <>]
+ 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]);
+ }
+
+ csobj.setCache(value);
+
+ return value;
+ }
+
+ /**
+ * get the number of components expected in the getPaint command
+ */
+ public abstract int getNumComponents();
+
+ /**
+ * 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() + "]";
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/colorspace/RGBColorSpace.java b/PdfView/src/main/java/com/sun/pdfview/colorspace/RGBColorSpace.java
new file mode 100644
index 0000000..736d82f
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/colorspace/RGBColorSpace.java
@@ -0,0 +1,61 @@
+/*
+ * $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 RGBColorSpace extends PDFColorSpace {
+
+ public RGBColorSpace() {
+ }
+
+ /**
+ * 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_RGB;
+ }
+
+ @Override
+ public String getName() {
+ return "RGB";
+ }
+
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/ASCII85Decode.java b/PdfView/src/main/java/com/sun/pdfview/decode/ASCII85Decode.java
new file mode 100644
index 0000000..1a3ee6b
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/ASCII85Decode.java
@@ -0,0 +1,154 @@
+/*
+ * $Id: ASCII85Decode.java,v 1.3 2009/02/22 00:52:35 tomoke 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.decode;
+
+import java.io.ByteArrayOutputStream;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFFile;
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * decode ASCII85 text into a byte array.
+ *
+ * @author Mike Wessler
+ */
+public class ASCII85Decode {
+
+ private ByteBuffer buf;
+
+ /**
+ * initialize the decoder with byte buffer in ASCII85 format
+ */
+ private ASCII85Decode(ByteBuffer buf) {
+ this.buf = buf;
+ }
+
+ /**
+ * get the next character from the input.
+ * @return the next character, or -1 if at end of stream
+ */
+ private int nextChar() {
+ // skip whitespace
+ // returns next character, or -1 if end of stream
+ while (buf.remaining() > 0) {
+ char c = (char) buf.get();
+
+ if (!PDFFile.isWhiteSpace(c)) {
+ return c;
+ }
+ }
+
+ // EOF reached
+ return -1;
+ }
+
+ /**
+ * decode the next five ASCII85 characters into up to four decoded
+ * bytes. Return false when finished, or true otherwise.
+ *
+ * @param baos the ByteArrayOutputStream to write output to, set to the
+ * correct position
+ * @return false when finished, or true otherwise.
+ */
+ private boolean decode5(ByteArrayOutputStream baos)
+ throws PDFParseException {
+ // stream ends in ~>
+ int[] five = new int[5];
+ int i;
+ for (i = 0; i < 5; i++) {
+ five[i] = nextChar();
+ if (five[i] == '~') {
+ if (nextChar() == '>') {
+ break;
+ } else {
+ throw new PDFParseException("Bad character in ASCII85Decode: not ~>");
+ }
+ } else if (five[i] >= '!' && five[i] <= 'u') {
+ five[i] -= '!';
+ } else if (five[i] == 'z') {
+ if (i == 0) {
+ five[i] = 0;
+ i = 4;
+ } else {
+ throw new PDFParseException("Inappropriate 'z' in ASCII85Decode");
+ }
+ } else {
+ throw new PDFParseException("Bad character in ASCII85Decode: " + five[i] + " (" + (char) five[i] + ")");
+ }
+ }
+
+ if (i > 0) {
+ i -= 1;
+ }
+
+ int value =
+ five[0] * 85 * 85 * 85 * 85 +
+ five[1] * 85 * 85 * 85 +
+ five[2] * 85 * 85 +
+ five[3] * 85 +
+ five[4];
+
+ for (int j = 0; j < i; j++) {
+ int shift = 8 * (3 - j);
+ baos.write((byte) ((value >> shift) & 0xff));
+ }
+
+ return (i == 4);
+ }
+
+ /**
+ * decode the bytes
+ * @return the decoded bytes
+ */
+ private ByteBuffer decode() throws PDFParseException {
+ // start from the beginning of the data
+ buf.rewind();
+
+ // allocate the output buffer
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ // decode the bytes
+ while (decode5(baos)) {
+ }
+
+ return ByteBuffer.wrap(baos.toByteArray());
+ }
+
+ /**
+ * decode an array of bytes in ASCII85 format.
+ *
+ * In ASCII85 format, every 5 characters represents 4 decoded
+ * bytes in base 85. The entire stream can contain whitespace,
+ * and ends in the characters '~>'.
+ *
+ * @param buf the encoded ASCII85 characters in a byte buffer
+ * @param params parameters to the decoder (ignored)
+ * @return the decoded bytes
+ */
+ public static ByteBuffer decode(ByteBuffer buf, PDFObject params)
+ throws PDFParseException {
+ ASCII85Decode me = new ASCII85Decode(buf);
+ return me.decode();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/ASCIIHexDecode.java b/PdfView/src/main/java/com/sun/pdfview/decode/ASCIIHexDecode.java
new file mode 100644
index 0000000..6a80b8d
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/ASCIIHexDecode.java
@@ -0,0 +1,128 @@
+/*
+ * $Id: ASCIIHexDecode.java,v 1.2 2007/12/20 18:33:32 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.decode;
+
+import java.io.ByteArrayOutputStream;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+import com.sun.pdfview.PDFFile;
+
+/**
+ * decode an array of hex nybbles into a byte array
+ *
+ * @author Mike Wessler
+ */
+public class ASCIIHexDecode {
+ private ByteBuffer buf;
+
+ /**
+ * initialize the decoder with an array of bytes in ASCIIHex format
+ */
+ private ASCIIHexDecode(ByteBuffer buf) {
+ this.buf = buf;
+ }
+
+ /**
+ * get the next character from the input
+ * @return a number from 0-15, or -1 for the end character
+ */
+ private int readHexDigit() throws PDFParseException {
+ // read until we hit a non-whitespace character or the
+ // end of the stream
+ while (buf.remaining() > 0) {
+ int c = (int) buf.get();
+
+ // see if we found a useful character
+ if (!PDFFile.isWhiteSpace((char) c)) {
+ if (c >= '0' && c <= '9') {
+ c -= '0';
+ } else if (c >= 'a' && c <= 'f') {
+ c -= 'a' - 10;
+ } else if (c >= 'A' && c <= 'F') {
+ c -= 'A' - 10;
+ } else if (c == '>') {
+ c = -1;
+ } else {
+ // unknown character
+ throw new PDFParseException("Bad character " + c +
+ "in ASCIIHex decode");
+ }
+
+ // return the useful character
+ return c;
+ }
+ }
+
+ // end of stream reached
+ throw new PDFParseException("Short stream in ASCIIHex decode");
+ }
+
+ /**
+ * decode the array
+ * @return the decoded bytes
+ */
+ private ByteBuffer decode() throws PDFParseException {
+ // start at the beginning of the buffer
+ buf.rewind();
+
+ // allocate the output buffer
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ while (true) {
+ int first = readHexDigit();
+ int second = readHexDigit();
+
+ if (first == -1) {
+ break;
+ } else if (second == -1) {
+ baos.write((byte) (first << 4));
+ break;
+ } else {
+ baos.write((byte) ((first << 4) + second));
+ }
+ }
+
+ return ByteBuffer.wrap(baos.toByteArray());
+ }
+
+ /**
+ * decode an array of bytes in ASCIIHex format.
+ *
+ * ASCIIHex format consists of a sequence of Hexidecimal
+ * digits, with possible whitespace, ending with the
+ * '>' character.
+ *
+ * @param buf the encoded ASCII85 characters in a byte
+ * buffer
+ * @param params parameters to the decoder (ignored)
+ * @return the decoded bytes
+ */
+ public static ByteBuffer decode(ByteBuffer buf, PDFObject params)
+ throws PDFParseException
+ {
+ ASCIIHexDecode me = new ASCIIHexDecode(buf);
+ return me.decode();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/CCITTFaxDecode.java b/PdfView/src/main/java/com/sun/pdfview/decode/CCITTFaxDecode.java
new file mode 100644
index 0000000..fae1bf6
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/CCITTFaxDecode.java
@@ -0,0 +1,96 @@
+package com.sun.pdfview.decode;
+
+import java.io.IOException;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+
+public class CCITTFaxDecode {
+
+
+
+ protected static ByteBuffer decode(PDFObject dict, ByteBuffer buf,
+ PDFObject params) throws IOException {
+
+ byte[] bytes = new byte[buf.remaining()];
+ buf.get(bytes, 0, bytes.length);
+ return ByteBuffer.wrap(decode(dict, bytes));
+ }
+
+
+ protected static byte[] decode(PDFObject dict, byte[] source) throws IOException {
+ int width = 1728;
+ PDFObject widthDef = dict.getDictRef("Width");
+ if (widthDef == null) {
+ widthDef = dict.getDictRef("W");
+ }
+ if (widthDef != null) {
+ width = widthDef.getIntValue();
+ }
+ int height = 0;
+ PDFObject heightDef = dict.getDictRef("Height");
+ if (heightDef == null) {
+ heightDef = dict.getDictRef("H");
+ }
+ if (heightDef != null) {
+ height = heightDef.getIntValue();
+ }
+
+ //
+ int columns = getOptionFieldInt(dict, "Columns", width);
+ int rows = getOptionFieldInt(dict, "Rows", height);
+ int k = getOptionFieldInt(dict, "K", 0);
+ int size = rows * ((columns + 7) >> 3);
+ byte[] destination = new byte[size];
+
+ boolean align = getOptionFieldBoolean(dict, "EncodedByteAlign", false);
+
+ CCITTFaxDecoder decoder = new CCITTFaxDecoder(1, columns, rows);
+ decoder.setAlign(align);
+ if (k == 0) {
+ decoder.decodeT41D(destination, source, 0, rows);
+ } else if (k > 0) {
+ decoder.decodeT42D(destination, source, 0, rows);
+ } else if (k < 0) {
+ decoder.decodeT6(destination, source, 0, rows);
+ }
+ if (!getOptionFieldBoolean(dict, "BlackIs1", false)) {
+ for (int i = 0; i < destination.length; i++) {
+ // bitwise not
+ destination[i] = (byte) ~destination[i];
+ }
+ }
+
+ return destination;
+ }
+
+ public static int getOptionFieldInt(PDFObject dict, String name, int defaultValue) throws IOException {
+
+ PDFObject dictParams = dict.getDictRef("DecodeParms");
+
+ if (dictParams == null) {
+ return defaultValue;
+ }
+ PDFObject value = dictParams.getDictRef(name);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value.getIntValue();
+ }
+
+ public static boolean getOptionFieldBoolean(PDFObject dict, String name, boolean defaultValue) throws IOException {
+
+ PDFObject dictParams = dict.getDictRef("DecodeParms");
+
+ if (dictParams == null) {
+ return defaultValue;
+ }
+ PDFObject value = dictParams.getDictRef(name);
+ if (value == null) {
+ return defaultValue;
+ }
+ return value.getBooleanValue();
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/CCITTFaxDecoder.java b/PdfView/src/main/java/com/sun/pdfview/decode/CCITTFaxDecoder.java
new file mode 100644
index 0000000..04726c0
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/CCITTFaxDecoder.java
@@ -0,0 +1,1575 @@
+/*
+ * Based on the SUN code (see license beyond) changes are made to handle CCITTFax encoded
+ * data in a PDF image. This may or may not apply to real world CCITT documents.
+ *
+ * Copyright (c) 2007, intarsys consulting GmbH
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of intarsys nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2001 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduct the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
+ * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
+ * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
+ * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
+ * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
+ * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
+ * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
+ * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
+ * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that Software is not designed,licensed or intended for use in
+ * the design, construction, operation or maintenance of any nuclear facility.
+ */
+package com.sun.pdfview.decode;
+
+public class CCITTFaxDecoder {
+ static int[] table1 = { 0x00, // 0 bits are left in first byte - SHOULD
+ // NOT HAPPEN
+ 0x01, // 1 bits are left in first byte
+ 0x03, // 2 bits are left in first byte
+ 0x07, // 3 bits are left in first byte
+ 0x0f, // 4 bits are left in first byte
+ 0x1f, // 5 bits are left in first byte
+ 0x3f, // 6 bits are left in first byte
+ 0x7f, // 7 bits are left in first byte
+ 0xff // 8 bits are left in first byte
+ };
+
+ static int[] table2 = { 0x00, // 0
+ 0x80, // 1
+ 0xc0, // 2
+ 0xe0, // 3
+ 0xf0, // 4
+ 0xf8, // 5
+ 0xfc, // 6
+ 0xfe, // 7
+ 0xff // 8
+ };
+
+ // Table to be used when fillOrder = 2, for flipping bytes.
+ static byte[] flipTable = { 0, -128, 64, -64, 32, -96, 96, -32, 16, -112,
+ 80, -48, 48, -80, 112, -16, 8, -120, 72, -56, 40, -88, 104, -24,
+ 24, -104, 88, -40, 56, -72, 120, -8, 4, -124, 68, -60, 36, -92,
+ 100, -28, 20, -108, 84, -44, 52, -76, 116, -12, 12, -116, 76, -52,
+ 44, -84, 108, -20, 28, -100, 92, -36, 60, -68, 124, -4, 2, -126,
+ 66, -62, 34, -94, 98, -30, 18, -110, 82, -46, 50, -78, 114, -14,
+ 10, -118, 74, -54, 42, -86, 106, -22, 26, -102, 90, -38, 58, -70,
+ 122, -6, 6, -122, 70, -58, 38, -90, 102, -26, 22, -106, 86, -42,
+ 54, -74, 118, -10, 14, -114, 78, -50, 46, -82, 110, -18, 30, -98,
+ 94, -34, 62, -66, 126, -2, 1, -127, 65, -63, 33, -95, 97, -31, 17,
+ -111, 81, -47, 49, -79, 113, -15, 9, -119, 73, -55, 41, -87, 105,
+ -23, 25, -103, 89, -39, 57, -71, 121, -7, 5, -123, 69, -59, 37,
+ -91, 101, -27, 21, -107, 85, -43, 53, -75, 117, -11, 13, -115, 77,
+ -51, 45, -83, 109, -19, 29, -99, 93, -35, 61, -67, 125, -3, 3,
+ -125, 67, -61, 35, -93, 99, -29, 19, -109, 83, -45, 51, -77, 115,
+ -13, 11, -117, 75, -53, 43, -85, 107, -21, 27, -101, 91, -37, 59,
+ -69, 123, -5, 7, -121, 71, -57, 39, -89, 103, -25, 23, -105, 87,
+ -41, 55, -73, 119, -9, 15, -113, 79, -49, 47, -81, 111, -17, 31,
+ -97, 95, -33, 63, -65, 127, -1, };
+
+ // The main 10 bit white runs lookup table
+ static short white[] = {
+ // 0 - 7
+ 6430, 6400, 6400, 6400, 3225, 3225, 3225, 3225,
+ // 8 - 15
+ 944, 944, 944, 944, 976, 976, 976, 976,
+ // 16 - 23
+ 1456, 1456, 1456, 1456, 1488, 1488, 1488, 1488,
+ // 24 - 31
+ 718, 718, 718, 718, 718, 718, 718, 718,
+ // 32 - 39
+ 750, 750, 750, 750, 750, 750, 750, 750,
+ // 40 - 47
+ 1520, 1520, 1520, 1520, 1552, 1552, 1552, 1552,
+ // 48 - 55
+ 428, 428, 428, 428, 428, 428, 428, 428,
+ // 56 - 63
+ 428, 428, 428, 428, 428, 428, 428, 428,
+ // 64 - 71
+ 654, 654, 654, 654, 654, 654, 654, 654,
+ // 72 - 79
+ 1072, 1072, 1072, 1072, 1104, 1104, 1104, 1104,
+ // 80 - 87
+ 1136, 1136, 1136, 1136, 1168, 1168, 1168, 1168,
+ // 88 - 95
+ 1200, 1200, 1200, 1200, 1232, 1232, 1232, 1232,
+ // 96 - 103
+ 622, 622, 622, 622, 622, 622, 622, 622,
+ // 104 - 111
+ 1008, 1008, 1008, 1008, 1040, 1040, 1040, 1040,
+ // 112 - 119
+ 44, 44, 44, 44, 44, 44, 44, 44,
+ // 120 - 127
+ 44, 44, 44, 44, 44, 44, 44, 44,
+ // 128 - 135
+ 396, 396, 396, 396, 396, 396, 396, 396,
+ // 136 - 143
+ 396, 396, 396, 396, 396, 396, 396, 396,
+ // 144 - 151
+ 1712, 1712, 1712, 1712, 1744, 1744, 1744, 1744,
+ // 152 - 159
+ 846, 846, 846, 846, 846, 846, 846, 846,
+ // 160 - 167
+ 1264, 1264, 1264, 1264, 1296, 1296, 1296, 1296,
+ // 168 - 175
+ 1328, 1328, 1328, 1328, 1360, 1360, 1360, 1360,
+ // 176 - 183
+ 1392, 1392, 1392, 1392, 1424, 1424, 1424, 1424,
+ // 184 - 191
+ 686, 686, 686, 686, 686, 686, 686, 686,
+ // 192 - 199
+ 910, 910, 910, 910, 910, 910, 910, 910,
+ // 200 - 207
+ 1968, 1968, 1968, 1968, 2000, 2000, 2000, 2000,
+ // 208 - 215
+ 2032, 2032, 2032, 2032, 16, 16, 16, 16,
+ // 216 - 223
+ 10257, 10257, 10257, 10257, 12305, 12305, 12305, 12305,
+ // 224 - 231
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 232 - 239
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 240 - 247
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 248 - 255
+ 330, 330, 330, 330, 330, 330, 330, 330,
+ // 256 - 263
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 264 - 271
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 272 - 279
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 280 - 287
+ 362, 362, 362, 362, 362, 362, 362, 362,
+ // 288 - 295
+ 878, 878, 878, 878, 878, 878, 878, 878,
+ // 296 - 303
+ 1904, 1904, 1904, 1904, 1936, 1936, 1936, 1936,
+ // 304 - 311
+ -18413, -18413, -16365, -16365, -14317, -14317, -10221, -10221,
+ // 312 - 319
+ 590, 590, 590, 590, 590, 590, 590, 590,
+ // 320 - 327
+ 782, 782, 782, 782, 782, 782, 782, 782,
+ // 328 - 335
+ 1584, 1584, 1584, 1584, 1616, 1616, 1616, 1616,
+ // 336 - 343
+ 1648, 1648, 1648, 1648, 1680, 1680, 1680, 1680,
+ // 344 - 351
+ 814, 814, 814, 814, 814, 814, 814, 814,
+ // 352 - 359
+ 1776, 1776, 1776, 1776, 1808, 1808, 1808, 1808,
+ // 360 - 367
+ 1840, 1840, 1840, 1840, 1872, 1872, 1872, 1872,
+ // 368 - 375
+ 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157,
+ // 376 - 383
+ 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157,
+ // 384 - 391
+ -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275,
+ // 392 - 399
+ -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275,
+ // 400 - 407
+ 14353, 14353, 14353, 14353, 16401, 16401, 16401, 16401,
+ // 408 - 415
+ 22547, 22547, 24595, 24595, 20497, 20497, 20497, 20497,
+ // 416 - 423
+ 18449, 18449, 18449, 18449, 26643, 26643, 28691, 28691,
+ // 424 - 431
+ 30739, 30739, -32749, -32749, -30701, -30701, -28653, -28653,
+ // 432 - 439
+ -26605, -26605, -24557, -24557, -22509, -22509, -20461, -20461,
+ // 440 - 447
+ 8207, 8207, 8207, 8207, 8207, 8207, 8207, 8207,
+ // 448 - 455
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 456 - 463
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 464 - 471
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 472 - 479
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 480 - 487
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 488 - 495
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 496 - 503
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 504 - 511
+ 72, 72, 72, 72, 72, 72, 72, 72,
+ // 512 - 519
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 520 - 527
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 528 - 535
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 536 - 543
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 544 - 551
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 552 - 559
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 560 - 567
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 568 - 575
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ // 576 - 583
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 584 - 591
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 592 - 599
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 600 - 607
+ 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107,
+ // 608 - 615
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 616 - 623
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 624 - 631
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 632 - 639
+ 266, 266, 266, 266, 266, 266, 266, 266,
+ // 640 - 647
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 648 - 655
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 656 - 663
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 664 - 671
+ 298, 298, 298, 298, 298, 298, 298, 298,
+ // 672 - 679
+ 524, 524, 524, 524, 524, 524, 524, 524,
+ // 680 - 687
+ 524, 524, 524, 524, 524, 524, 524, 524,
+ // 688 - 695
+ 556, 556, 556, 556, 556, 556, 556, 556,
+ // 696 - 703
+ 556, 556, 556, 556, 556, 556, 556, 556,
+ // 704 - 711
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 712 - 719
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 720 - 727
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 728 - 735
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 736 - 743
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 744 - 751
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 752 - 759
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 760 - 767
+ 136, 136, 136, 136, 136, 136, 136, 136,
+ // 768 - 775
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 776 - 783
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 784 - 791
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 792 - 799
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 800 - 807
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 808 - 815
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 816 - 823
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 824 - 831
+ 168, 168, 168, 168, 168, 168, 168, 168,
+ // 832 - 839
+ 460, 460, 460, 460, 460, 460, 460, 460,
+ // 840 - 847
+ 460, 460, 460, 460, 460, 460, 460, 460,
+ // 848 - 855
+ 492, 492, 492, 492, 492, 492, 492, 492,
+ // 856 - 863
+ 492, 492, 492, 492, 492, 492, 492, 492,
+ // 864 - 871
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 872 - 879
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 880 - 887
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 888 - 895
+ 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,
+ // 896 - 903
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 904 - 911
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 912 - 919
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 920 - 927
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 928 - 935
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 936 - 943
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 944 - 951
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 952 - 959
+ 200, 200, 200, 200, 200, 200, 200, 200,
+ // 960 - 967
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 968 - 975
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 976 - 983
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 984 - 991
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 992 - 999
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 1000 - 1007
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 1008 - 1015
+ 232, 232, 232, 232, 232, 232, 232, 232,
+ // 1016 - 1023
+ 232, 232, 232, 232, 232, 232, 232, 232, };
+
+ // Additional make up codes for both White and Black runs
+ static short[] additionalMakeup = { 28679, 28679, 31752, (short) 32777,
+ (short) 33801, (short) 34825, (short) 35849, (short) 36873,
+ (short) 29703, (short) 29703, (short) 30727, (short) 30727,
+ (short) 37897, (short) 38921, (short) 39945, (short) 40969 };
+
+ // Initial black run look up table, uses the first 4 bits of a code
+ static short[] initBlack = {
+ // 0 - 7
+ 3226, 6412, 200, 168, 38, 38, 134, 134, // 8 - 15
+ 100, 100, 100, 100, 68, 68, 68, 68 };
+
+ //
+ static short[] twoBitBlack = { 292, 260, 226, 226 }; // 0 - 3
+
+ // Main black run table, using the last 9 bits of possible 13 bit code
+ static short black[] = {
+ // 0 - 7
+ 62, 62, 30, 30, 0, 0, 0, 0,
+ // 8 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // 16 - 23
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // 24 - 31
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ // 32 - 39
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 40 - 47
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 48 - 55
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 56 - 63
+ 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225,
+ // 64 - 71
+ 588, 588, 588, 588, 588, 588, 588, 588,
+ // 72 - 79
+ 1680, 1680, 20499, 22547, 24595, 26643, 1776, 1776,
+ // 80 - 87
+ 1808, 1808, -24557, -22509, -20461, -18413, 1904, 1904,
+ // 88 - 95
+ 1936, 1936, -16365, -14317, 782, 782, 782, 782,
+ // 96 - 103
+ 814, 814, 814, 814, -12269, -10221, 10257, 10257,
+ // 104 - 111
+ 12305, 12305, 14353, 14353, 16403, 18451, 1712, 1712,
+ // 112 - 119
+ 1744, 1744, 28691, 30739, -32749, -30701, -28653, -26605,
+ // 120 - 127
+ 2061, 2061, 2061, 2061, 2061, 2061, 2061, 2061,
+ // 128 - 135
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 136 - 143
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 144 - 151
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 152 - 159
+ 424, 424, 424, 424, 424, 424, 424, 424,
+ // 160 - 167
+ 750, 750, 750, 750, 1616, 1616, 1648, 1648,
+ // 168 - 175
+ 1424, 1424, 1456, 1456, 1488, 1488, 1520, 1520,
+ // 176 - 183
+ 1840, 1840, 1872, 1872, 1968, 1968, 8209, 8209,
+ // 184 - 191
+ 524, 524, 524, 524, 524, 524, 524, 524,
+ // 192 - 199
+ 556, 556, 556, 556, 556, 556, 556, 556,
+ // 200 - 207
+ 1552, 1552, 1584, 1584, 2000, 2000, 2032, 2032,
+ // 208 - 215
+ 976, 976, 1008, 1008, 1040, 1040, 1072, 1072,
+ // 216 - 223
+ 1296, 1296, 1328, 1328, 718, 718, 718, 718,
+ // 224 - 231
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 232 - 239
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 240 - 247
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 248 - 255
+ 456, 456, 456, 456, 456, 456, 456, 456,
+ // 256 - 263
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 264 - 271
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 272 - 279
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 280 - 287
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 288 - 295
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 296 - 303
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 304 - 311
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 312 - 319
+ 326, 326, 326, 326, 326, 326, 326, 326,
+ // 320 - 327
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 328 - 335
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 336 - 343
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 344 - 351
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 352 - 359
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 360 - 367
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 368 - 375
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 376 - 383
+ 358, 358, 358, 358, 358, 358, 358, 358,
+ // 384 - 391
+ 490, 490, 490, 490, 490, 490, 490, 490,
+ // 392 - 399
+ 490, 490, 490, 490, 490, 490, 490, 490,
+ // 400 - 407
+ 4113, 4113, 6161, 6161, 848, 848, 880, 880,
+ // 408 - 415
+ 912, 912, 944, 944, 622, 622, 622, 622,
+ // 416 - 423
+ 654, 654, 654, 654, 1104, 1104, 1136, 1136,
+ // 424 - 431
+ 1168, 1168, 1200, 1200, 1232, 1232, 1264, 1264,
+ // 432 - 439
+ 686, 686, 686, 686, 1360, 1360, 1392, 1392,
+ // 440 - 447
+ 12, 12, 12, 12, 12, 12, 12, 12,
+ // 448 - 455
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 456 - 463
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 464 - 471
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 472 - 479
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 480 - 487
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 488 - 495
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 496 - 503
+ 390, 390, 390, 390, 390, 390, 390, 390,
+ // 504 - 511
+ 390, 390, 390, 390, 390, 390, 390, 390, };
+
+ static byte[] twoDCodes = {
+ // 0 - 7
+ 80, 88, 23, 71, 30, 30, 62, 62, // 8 - 15
+ 4, 4, 4, 4, 4, 4, 4, 4, // 16 - 23
+ 11, 11, 11, 11, 11, 11, 11, 11, // 24 - 31
+ 11, 11, 11, 11, 11, 11, 11, 11, // 32 - 39
+ 35, 35, 35, 35, 35, 35, 35, 35, // 40 - 47
+ 35, 35, 35, 35, 35, 35, 35, 35, // 48 - 55
+ 51, 51, 51, 51, 51, 51, 51, 51, // 56 - 63
+ 51, 51, 51, 51, 51, 51, 51, 51, // 64 - 71
+ 41, 41, 41, 41, 41, 41, 41, 41, // 72 - 79
+ 41, 41, 41, 41, 41, 41, 41, 41, // 80 - 87
+ 41, 41, 41, 41, 41, 41, 41, 41, // 88 - 95
+ 41, 41, 41, 41, 41, 41, 41, 41, // 96 - 103
+ 41, 41, 41, 41, 41, 41, 41, 41, // 104 - 111
+ 41, 41, 41, 41, 41, 41, 41, 41, // 112 - 119
+ 41, 41, 41, 41, 41, 41, 41, 41, // 120 - 127
+ 41, 41, 41, 41, 41, 41, 41, 41, };
+
+ private int bitPointer;
+
+ private int bytePointer;
+
+ private byte[] data;
+
+ private int w;
+
+ private boolean align = false;
+
+ private int fillOrder;
+
+ // Data structures needed to store changing elements for the previous
+ // and the current scanline
+ private int changingElemSize = 0;
+
+ private int[] prevChangingElems;
+
+ private int[] currChangingElems;
+
+ // Element at which to start search in getNextChangingElement
+ private int lastChangingElement = 0;
+
+ private boolean fillBits = false;
+
+ /**
+ * @param fillOrder
+ * The fill order of the compressed data bytes.
+ * @param w
+ * @param h
+ */
+ public CCITTFaxDecoder(int fillOrder, int w, int h) {
+ this.fillOrder = fillOrder;
+ this.w = w;
+
+ this.bitPointer = 0;
+ this.bytePointer = 0;
+ this.prevChangingElems = new int[w];
+ this.currChangingElems = new int[w];
+ }
+
+ private boolean align() {
+ if (align && bitPointer != 0) {
+ bytePointer++;
+ bitPointer = 0;
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean consumeEOL() {
+ // Get the next 12 bits.
+ int next12Bits = nextNBits(12);
+ if (next12Bits == 1) {
+ // EOL found & consumed
+ return true;
+ }
+ // no EOL - unread and return
+ updatePointer(12);
+ return false;
+ }
+
+ // Returns run length
+ private int decodeBlackCodeWord() {
+ int current;
+ int entry;
+ int bits;
+ int isT;
+ int code = -1;
+ int runLength = 0;
+ boolean isWhite = false;
+
+ while (!isWhite) {
+ current = nextLesserThan8Bits(4);
+ entry = initBlack[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (code == 100) {
+ current = nextNBits(9);
+ entry = black[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (bits == 12) {
+ // Additional makeup codes
+ updatePointer(5);
+ current = nextLesserThan8Bits(4);
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+ runLength += code;
+
+ updatePointer(4 - bits);
+ } else if (bits == 15) {
+ // EOL code
+ throw new RuntimeException(
+ "EOL code word encountered in Black run."); //$NON-NLS-1$
+ } else {
+ runLength += code;
+ updatePointer(9 - bits);
+ if (isT == 0) {
+ isWhite = true;
+ }
+ }
+ } else if (code == 200) {
+ // Is a Terminating code
+ current = nextLesserThan8Bits(2);
+ entry = twoBitBlack[current];
+ code = (entry >>> 5) & 0x07ff;
+ runLength += code;
+ bits = (entry >>> 1) & 0x0f;
+ updatePointer(2 - bits);
+ isWhite = true;
+ } else {
+ // Is a Terminating code
+ runLength += code;
+ updatePointer(4 - bits);
+ isWhite = true;
+ }
+ }
+
+ return runLength;
+ }
+
+ protected void decodeNextScanline(byte[] buffer, int lineOffset,
+ int bitOffset) {
+ int bits = 0;
+ int code = 0;
+ int isT = 0;
+ int current;
+ int entry;
+ int twoBits;
+ boolean isWhite = true;
+
+ // Initialize starting of the changing elements array
+ changingElemSize = 0;
+
+ // While scanline not complete
+ while (bitOffset < w) {
+ while (isWhite) {
+ // White run
+ current = nextNBits(10);
+ entry = white[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x0f;
+
+ if (bits == 12) { // Additional Make up code
+ // Get the next 2 bits
+ twoBits = nextLesserThan8Bits(2);
+ // Consolidate the 2 new bits and last 2 bits into 4 bits
+ current = ((current << 2) & 0x000c) | twoBits;
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+ bitOffset += code; // Skip white run
+
+ updatePointer(4 - bits);
+ } else if (bits == 0) { // ERROR
+ throw new RuntimeException("Invalid code encountered.");
+ } else if (bits == 15) {
+ // EOL recover
+ // move bits back...
+ updatePointer(10);
+ return;
+ } else {
+ // 11 bits - 0000 0111 1111 1111 = 0x07ff
+ code = (entry >>> 5) & 0x07ff;
+ bitOffset += code;
+
+ updatePointer(10 - bits);
+ if (isT == 0) {
+ isWhite = false;
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+ }
+ }
+
+ // Check whether this run completed one width, if so
+ // advance to next byte boundary for compression = 2.
+ if (bitOffset == w) {
+ align();
+ break;
+ }
+
+ while (isWhite == false) {
+ // Black run
+ current = nextLesserThan8Bits(4);
+ entry = initBlack[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (code == 100) {
+ current = nextNBits(9);
+ entry = black[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x000f;
+ code = (entry >>> 5) & 0x07ff;
+
+ if (bits == 12) {
+ // Additional makeup codes
+ updatePointer(5);
+ current = nextLesserThan8Bits(4);
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(4 - bits);
+ } else if (bits == 15) {
+ // EOL recover
+ // unread bits ???
+ updatePointer(9);
+ return;
+ } else {
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(9 - bits);
+ if (isT == 0) {
+ isWhite = true;
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+ }
+ } else if (code == 200) {
+ // Is a Terminating code
+ current = nextLesserThan8Bits(2);
+ entry = twoBitBlack[current];
+ code = (entry >>> 5) & 0x07ff;
+ bits = (entry >>> 1) & 0x0f;
+
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(2 - bits);
+ isWhite = true;
+ currChangingElems[changingElemSize++] = bitOffset;
+ } else {
+ // Is a Terminating code
+ setToBlack(buffer, lineOffset, bitOffset, code);
+ bitOffset += code;
+
+ updatePointer(4 - bits);
+ isWhite = true;
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+ }
+
+ // Check whether this run completed one width
+ if (bitOffset == w) {
+ align();
+ break;
+ }
+ }
+
+ currChangingElems[changingElemSize++] = bitOffset;
+ }
+
+ // One-dimensional decoding methods
+ public void decodeT41D(byte[] buffer, byte[] compData, int startX,
+ int height) {
+ this.data = compData;
+ int scanlineStride = (w + 7) / 8;
+ bitPointer = 0;
+ bytePointer = 0;
+
+ int lineOffset = 0;
+ for (int i = 0; i < height; i++) {
+ consumeEOL();
+ decodeNextScanline(buffer, lineOffset, startX);
+ lineOffset += scanlineStride;
+ }
+ }
+
+ // Two-dimensional decoding methods
+ public void decodeT42D(byte[] buffer, byte[] compData, int startX,
+ int height) {
+ this.data = compData;
+ int scanlineStride = (w + 7) / 8;
+ bitPointer = 0;
+ bytePointer = 0;
+
+ int a0;
+ int a1;
+ int b1;
+ int b2;
+ int[] b = new int[2];
+ int entry;
+ int code;
+ int bits;
+ boolean isWhite;
+ int currIndex = 0;
+ int[] temp;
+
+ // The data must start with an EOL code
+ if (readEOL(true) != 1) {
+ throw new RuntimeException("First scanline must be 1D encoded."); //$NON-NLS-1$
+ }
+
+ int lineOffset = 0;
+ int bitOffset;
+
+ // Then the 1D encoded scanline data will occur, changing elements
+ // array gets set.
+ decodeNextScanline(buffer, lineOffset, startX);
+ lineOffset += scanlineStride;
+
+ for (int lines = 1; lines < height; lines++) {
+ // Every line must begin with an EOL followed by a bit which
+ // indicates whether the following scanline is 1D or 2D encoded.
+ if (readEOL(false) == 0) {
+ // 2D encoded scanline follows
+
+ // Initialize previous scanlines changing elements, and
+ // initialize current scanline's changing elements array
+ temp = prevChangingElems;
+ prevChangingElems = currChangingElems;
+ currChangingElems = temp;
+ currIndex = 0;
+
+ // a0 has to be set just before the start of this scanline.
+ a0 = -1;
+ isWhite = true;
+ bitOffset = startX;
+
+ lastChangingElement = 0;
+
+ while (bitOffset < w) {
+ // Get the next changing element
+ getNextChangingElement(a0, isWhite, b);
+
+ b1 = b[0];
+ b2 = b[1];
+
+ // Get the next seven bits
+ entry = nextLesserThan8Bits(7);
+
+ // Run these through the 2DCodes table
+ entry = (twoDCodes[entry] & 0xff);
+
+ // Get the code and the number of bits used up
+ code = (entry & 0x78) >>> 3;
+ bits = entry & 0x07;
+
+ if (code == 0) {
+ if (!isWhite) {
+ setToBlack(buffer, lineOffset, bitOffset, b2
+ - bitOffset);
+ }
+ bitOffset = a0 = b2;
+
+ // Set pointer to consume the correct number of bits.
+ updatePointer(7 - bits);
+ } else if (code == 1) {
+ // Horizontal
+ updatePointer(7 - bits);
+
+ // identify the next 2 codes.
+ int number;
+ if (isWhite) {
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+
+ number = decodeBlackCodeWord();
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+ } else {
+ number = decodeBlackCodeWord();
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ currChangingElems[currIndex++] = bitOffset;
+ }
+
+ a0 = bitOffset;
+ } else if (code <= 8) {
+ // Vertical
+ a1 = b1 + (code - 5);
+
+ currChangingElems[currIndex++] = a1;
+
+ // We write the current color till a1 - 1 pos,
+ // since a1 is where the next color starts
+ if (!isWhite) {
+ setToBlack(buffer, lineOffset, bitOffset, a1
+ - bitOffset);
+ }
+ bitOffset = a0 = a1;
+ isWhite = !isWhite;
+
+ updatePointer(7 - bits);
+ } else {
+ throw new RuntimeException(
+ "Invalid code encountered while decoding 2D group 3 compressed data."); //$NON-NLS-1$
+ }
+ }
+
+ // Add the changing element beyond the current scanline for the
+ // other color too
+ currChangingElems[currIndex++] = bitOffset;
+ changingElemSize = currIndex;
+ } else {
+ // 1D encoded scanline follows
+ decodeNextScanline(buffer, lineOffset, startX);
+ }
+
+ lineOffset += scanlineStride;
+ }
+ }
+
+ public synchronized void decodeT6(byte[] buffer, byte[] compData,
+ int startX, int height) {
+ this.data = compData;
+ int scanlineStride = (w + 7) / 8;
+ bitPointer = 0;
+ bytePointer = 0;
+
+ int a0;
+ int a1;
+ int b1;
+ int b2;
+ int entry;
+ int code;
+ int bits;
+ boolean isWhite;
+ int currIndex;
+ int[] temp;
+
+ // Return values from getNextChangingElement
+ int[] b = new int[2];
+
+ // uncompressedMode - have written some code for this, but this
+ // has not been tested due to lack of test images using this optional
+
+ // Local cached reference
+ int[] cce = currChangingElems;
+
+ // Assume invisible preceding row of all white pixels and insert
+ // both black and white changing elements beyond the end of this
+ // imaginary scanline.
+ changingElemSize = 0;
+ cce[changingElemSize++] = w;
+ cce[changingElemSize++] = w;
+
+ int lineOffset = 0;
+ int bitOffset;
+
+ for (int lines = 0; lines < height; lines++) {
+ // a0 has to be set just before the start of the scanline.
+ a0 = -1;
+ isWhite = true;
+
+ // Assign the changing elements of the previous scanline to
+ // prevChangingElems and start putting this new scanline's
+ // changing elements into the currChangingElems.
+ temp = prevChangingElems;
+ prevChangingElems = currChangingElems;
+ cce = currChangingElems = temp;
+ currIndex = 0;
+
+ // Start decoding the scanline at startX in the raster
+ bitOffset = startX;
+
+ // Reset search start position for getNextChangingElement
+ lastChangingElement = 0;
+
+ // Till one whole scanline is decoded
+ while (bitOffset < w) {
+ // Get the next changing element
+ getNextChangingElement(a0, isWhite, b);
+ b1 = b[0];
+ b2 = b[1];
+
+ // Get the next seven bits
+ entry = nextLesserThan8Bits(7);
+ // Run these through the 2DCodes table
+ entry = (twoDCodes[entry] & 0xff);
+
+ // Get the code and the number of bits used up
+ code = (entry & 0x78) >>> 3;
+ bits = entry & 0x07;
+
+ if (code == 0) { // Pass
+ // We always assume WhiteIsZero format for fax.
+ if (!isWhite) {
+ if (b2 > w) {
+ b2 = w;
+ }
+ setToBlack(buffer, lineOffset, bitOffset, b2
+ - bitOffset);
+ }
+ bitOffset = a0 = b2;
+
+ // Set pointer to only consume the correct number of bits.
+ updatePointer(7 - bits);
+ } else if (code == 1) { // Horizontal
+ // Set pointer to only consume the correct number of bits.
+ updatePointer(7 - bits);
+
+ // identify the next 2 alternating color codes.
+ int number;
+ if (isWhite) {
+ // Following are white and black runs
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+
+ number = decodeBlackCodeWord();
+ if (number > w - bitOffset) {
+ number = w - bitOffset;
+ }
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+ } else {
+ // First a black run and then a white run follows
+ number = decodeBlackCodeWord();
+ if (number > w - bitOffset) {
+ number = w - bitOffset;
+ }
+ setToBlack(buffer, lineOffset, bitOffset, number);
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+
+ number = decodeWhiteCodeWord();
+ bitOffset += number;
+ cce[currIndex++] = bitOffset;
+ }
+
+ a0 = bitOffset;
+ } else if (code <= 8) { // Vertical
+ a1 = b1 + (code - 5);
+ cce[currIndex++] = a1;
+
+ // We write the current color till a1 - 1 pos,
+ // since a1 is where the next color starts
+ if (!isWhite) {
+ if (a1 > w) {
+ a1 = w;
+ }
+ setToBlack(buffer, lineOffset, bitOffset, a1
+ - bitOffset);
+ }
+ bitOffset = a0 = a1;
+ isWhite = !isWhite;
+
+ updatePointer(7 - bits);
+ } else if (code == 11) {
+ if (nextLesserThan8Bits(3) != 7) {
+ throw new RuntimeException(
+ "Invalid code encountered while decoding 2D group 4 compressed data."); //$NON-NLS-1$
+ }
+
+ int zeros = 0;
+ boolean exit = false;
+
+ while (!exit) {
+ while (nextLesserThan8Bits(1) != 1) {
+ zeros++;
+ }
+
+ if (zeros > 5) {
+ // Exit code
+
+ // Zeros before exit code
+ zeros = zeros - 6;
+
+ if (!isWhite && (zeros > 0)) {
+ cce[currIndex++] = bitOffset;
+ }
+
+ // Zeros before the exit code
+ bitOffset += zeros;
+ if (zeros > 0) {
+ // Some zeros have been written
+ isWhite = true;
+ }
+
+ // Read in the bit which specifies the color of
+ // the following run
+ if (nextLesserThan8Bits(1) == 0) {
+ if (!isWhite) {
+ cce[currIndex++] = bitOffset;
+ }
+ isWhite = true;
+ } else {
+ if (isWhite) {
+ cce[currIndex++] = bitOffset;
+ }
+ isWhite = false;
+ }
+
+ exit = true;
+ }
+
+ if (zeros == 5) {
+ if (!isWhite) {
+ cce[currIndex++] = bitOffset;
+ }
+ bitOffset += zeros;
+
+ // Last thing written was white
+ isWhite = true;
+ } else {
+ bitOffset += zeros;
+
+ cce[currIndex++] = bitOffset;
+ setToBlack(buffer, lineOffset, bitOffset, 1);
+ ++bitOffset;
+
+ // Last thing written was black
+ isWhite = false;
+ }
+ }
+ } else {
+ // break line - seems to be a common failure
+ // unread
+ updatePointer(7 - bits);
+ // and mark lines as complete
+ bitOffset = w;
+ // throw new RuntimeException(
+ // "Invalid code encountered while decoding 2D group 4
+ // compressed data."); //$NON-NLS-1$
+ }
+ }
+
+ align();
+
+ // Add the changing element beyond the current scanline for the
+ // other color too
+ // make sure that the index does not exceed the bounds of the array
+ if (currIndex <= w) {
+ cce[currIndex++] = bitOffset;
+ }
+
+ // Number of changing elements in this scanline.
+ changingElemSize = currIndex;
+
+ lineOffset += scanlineStride;
+ }
+ }
+
+ // Returns run length
+ private int decodeWhiteCodeWord() {
+ int current;
+ int entry;
+ int bits;
+ int isT;
+ int twoBits;
+ int code = -1;
+ int runLength = 0;
+ boolean isWhite = true;
+
+ while (isWhite) {
+ current = nextNBits(10);
+ entry = white[current];
+
+ // Get the 3 fields from the entry
+ isT = entry & 0x0001;
+ bits = (entry >>> 1) & 0x0f;
+
+ if (bits == 12) { // Additional Make up code
+ // Get the next 2 bits
+ twoBits = nextLesserThan8Bits(2);
+ // Consolidate the 2 new bits and last 2 bits into 4 bits
+ current = ((current << 2) & 0x000c) | twoBits;
+ entry = additionalMakeup[current];
+ bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111
+ code = (entry >>> 4) & 0x0fff; // 12 bits
+ runLength += code;
+ updatePointer(4 - bits);
+ } else if (bits == 0) { // ERROR
+ throw new RuntimeException("Invalid code encountered."); //$NON-NLS-1$
+ } else if (bits == 15) { // EOL
+ throw new RuntimeException(
+ "EOL code word encountered in White run."); //$NON-NLS-1$
+ } else {
+ // 11 bits - 0000 0111 1111 1111 = 0x07ff
+ code = (entry >>> 5) & 0x07ff;
+ runLength += code;
+ updatePointer(10 - bits);
+ if (isT == 0) {
+ isWhite = false;
+ }
+ }
+ }
+
+ return runLength;
+ }
+
+ private void getNextChangingElement(int a0, boolean isWhite, int[] ret) {
+ // Local copies of instance variables
+ int[] pce = this.prevChangingElems;
+ int ces = this.changingElemSize;
+
+ // If the previous match was at an odd element, we still
+ // have to search the preceeding element.
+ // int start = lastChangingElement & ~0x1;
+ int start = (lastChangingElement > 0) ? (lastChangingElement - 1) : 0;
+ if (isWhite) {
+ start &= ~0x1; // Search even numbered elements
+ } else {
+ start |= 0x1; // Search odd numbered elements
+ }
+
+ int i = start;
+ for (; i < ces; i += 2) {
+ int temp = pce[i];
+ if (temp > a0) {
+ lastChangingElement = i;
+ ret[0] = temp;
+ break;
+ }
+ }
+
+ if ((i + 1) < ces) {
+ ret[1] = pce[i + 1];
+ }
+ }
+
+ public boolean isAlign() {
+ return align;
+ }
+
+ public boolean isFillBits() {
+ return fillBits;
+ }
+
+ private int nextLesserThan8Bits(int bitsToGet) {
+ byte b;
+ byte next;
+ int l = data.length - 1;
+ int bp = this.bytePointer;
+
+ if (fillOrder == 1) {
+ b = data[bp];
+ if (bp == l) {
+ next = 0x00;
+ } else {
+ next = data[bp + 1];
+ }
+ } else if (fillOrder == 2) {
+ b = flipTable[data[bp] & 0xff];
+ if (bp == l) {
+ next = 0x00;
+ } else {
+ next = flipTable[data[bp + 1] & 0xff];
+ }
+ } else {
+ throw new RuntimeException("tag must be either 1 or 2."); //$NON-NLS-1$
+ }
+
+ int bitsLeft = 8 - bitPointer;
+ int bitsFromNextByte = bitsToGet - bitsLeft;
+
+ int shift = bitsLeft - bitsToGet;
+ int i1;
+ int i2;
+ if (shift >= 0) {
+ i1 = (b & table1[bitsLeft]) >>> shift;
+ bitPointer += bitsToGet;
+ if (bitPointer == 8) {
+ bitPointer = 0;
+ bytePointer++;
+ }
+ } else {
+ i1 = (b & table1[bitsLeft]) << (-shift);
+ i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte);
+
+ i1 |= i2;
+ bytePointer++;
+ bitPointer = bitsFromNextByte;
+ }
+
+ return i1;
+ }
+
+ private int nextNBits(int bitsToGet) {
+ byte b;
+ byte next;
+ byte next2next;
+ int l = data.length - 1;
+ int bp = this.bytePointer;
+
+ if (fillOrder == 1) {
+ b = data[bp];
+
+ if (bp == l) {
+ next = 0x00;
+ next2next = 0x00;
+ } else if ((bp + 1) == l) {
+ next = data[bp + 1];
+ next2next = 0x00;
+ } else {
+ next = data[bp + 1];
+ next2next = data[bp + 2];
+ }
+ } else if (fillOrder == 2) {
+ b = flipTable[data[bp] & 0xff];
+
+ if (bp == l) {
+ next = 0x00;
+ next2next = 0x00;
+ } else if ((bp + 1) == l) {
+ next = flipTable[data[bp + 1] & 0xff];
+ next2next = 0x00;
+ } else {
+ next = flipTable[data[bp + 1] & 0xff];
+ next2next = flipTable[data[bp + 2] & 0xff];
+ }
+ } else {
+ throw new RuntimeException("tag must be either 1 or 2."); //$NON-NLS-1$
+ }
+
+ int bitsLeft = 8 - bitPointer;
+ int bitsFromNextByte = bitsToGet - bitsLeft;
+ int bitsFromNext2NextByte = 0;
+ if (bitsFromNextByte > 8) {
+ bitsFromNext2NextByte = bitsFromNextByte - 8;
+ bitsFromNextByte = 8;
+ }
+
+ bytePointer++;
+
+ int i1 = (b & table1[bitsLeft]) << (bitsToGet - bitsLeft);
+ int i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte);
+
+ int i3 = 0;
+ if (bitsFromNext2NextByte != 0) {
+ i2 <<= bitsFromNext2NextByte;
+ i3 = (next2next & table2[bitsFromNext2NextByte]) >>> (8 - bitsFromNext2NextByte);
+ i2 |= i3;
+ bytePointer++;
+ bitPointer = bitsFromNext2NextByte;
+ } else {
+ if (bitsFromNextByte == 8) {
+ bitPointer = 0;
+ bytePointer++;
+ } else {
+ bitPointer = bitsFromNextByte;
+ }
+ }
+
+ int i = i1 | i2;
+ return i;
+ }
+
+ private int readEOL(boolean isFirstEOL) {
+ // Seek to the next EOL.
+ if (!seekEOL()) {
+ throw new RuntimeException("EOL not found");
+ }
+
+ if (!fillBits) {
+ int next12Bits = nextNBits(12);
+ if (isFirstEOL && (next12Bits == 0)) {
+ // Might have the case of EOL padding being used even
+ // though it was not flagged.
+ // This was observed to be the case in TIFFs produced
+ // by a well known vendor who shall remain nameless.
+ if (nextNBits(4) == 1) {
+ // EOL must be padded: reset the fillBits flag.
+ fillBits = true;
+ return 1;
+ }
+ }
+ if (next12Bits != 1) {
+ throw new RuntimeException(
+ "Scanline must begin with EOL code word."); //$NON-NLS-1$
+ }
+ } else {
+ // First EOL code word xxxx 0000 0000 0001 will occur
+ // As many fill bits will be present as required to make
+ // the EOL code of 12 bits end on a byte boundary.
+ int bitsLeft = 8 - bitPointer;
+
+ if (nextNBits(bitsLeft) != 0) {
+ throw new RuntimeException(
+ "All fill bits preceding EOL code must be 0."); //$NON-NLS-1$
+ }
+
+ // If the number of bitsLeft is less than 8, then to have a 12
+ // bit EOL sequence, two more bytes are certainly going to be
+ // required. The first of them has to be all zeros, so ensure
+ // that.
+ if (bitsLeft < 4) {
+ if (nextNBits(8) != 0) {
+ throw new RuntimeException(
+ "All fill bits preceding EOL code must be 0."); //$NON-NLS-1$
+ }
+ }
+
+ //
+ // Some encoders under Group 3 Fax compression 1D writes TIFF
+ // files without the fill bits, but say otherwise.
+ // Need to check for this here.
+ //
+ int next8 = nextNBits(8);
+
+ if (isFirstEOL && (next8 & 0xf0) == 0x10) {
+ //
+ // Fill bits are not actually used despite what the flag
+ // says. So switch fillBits off and then rewind so that
+ // only 12 bits have effectively been read.
+ //
+ fillBits = false;
+ updatePointer(4);
+ } else {
+ //
+ // This is the normal case.
+ // There might be a random number of fill bytes with 0s, so
+ // loop till the EOL of 0000 0001 is found, as long as all
+ // the bytes preceding it are 0's.
+ //
+ while (next8 != 1) {
+ // If not all zeros
+ if (next8 != 0) {
+ throw new RuntimeException("0 bits expected before EOL");
+ }
+ next8 = nextNBits(8);
+ }
+ }
+ }
+ // The next one bit signifies 1D/2D encoding of next line.
+ return nextLesserThan8Bits(1);
+ }
+
+ // Seeks to the next EOL in the compressed bitstream.
+ // Returns 'true' if and only if an EOL is found; if 'false'
+ // is returned it may be inferred that the EOF was reached first.
+ private boolean seekEOL() {
+ // Set maximum and current bit index into the compressed data.
+ int bitIndexMax = data.length * 8 - 1;
+ int bitIndex = bytePointer * 8 + bitPointer;
+
+ // Loop while at least 12 bits are available.
+ while (bitIndex <= bitIndexMax - 12) {
+ // Get the next 12 bits.
+ int next12Bits = nextNBits(12);
+ bitIndex += 12;
+
+ // Loop while the 12 bits are not unity, i.e., while the EOL
+ // has not been reached, and there is at least one bit left.
+ while (next12Bits != 1 && bitIndex < bitIndexMax) {
+ next12Bits = ((next12Bits & 0x000007ff) << 1)
+ | (nextLesserThan8Bits(1) & 0x00000001);
+ bitIndex++;
+ }
+
+ // If EOL reached, rewind the pointers and return 'true'.
+ if (next12Bits == 1) {
+ updatePointer(12);
+ return true;
+ }
+ }
+
+ // EOL not found: return 'false'.
+ return false;
+ }
+
+ public void setAlign(boolean align) {
+ this.align = align;
+ }
+
+ public void setFillBits(boolean fillBits) {
+ this.fillBits = fillBits;
+ }
+
+ private void setToBlack(byte[] buffer, int lineOffset, int bitOffset,
+ int numBits) {
+ int bitNum = (8 * lineOffset) + bitOffset;
+ int lastBit = bitNum + numBits;
+
+ int byteNum = bitNum >> 3;
+
+ // Handle bits in first byte
+ int shift = bitNum & 0x7;
+ if (shift > 0) {
+ int maskVal = 1 << (7 - shift);
+ byte val = buffer[byteNum];
+ while ((maskVal > 0) && (bitNum < lastBit)) {
+ val |= maskVal;
+ maskVal >>= 1;
+ ++bitNum;
+ }
+ buffer[byteNum] = val;
+ }
+
+ // Fill in 8 bits at a time
+ byteNum = bitNum >> 3;
+ while (bitNum < (lastBit - 7)) {
+ buffer[byteNum++] = (byte) 255;
+ bitNum += 8;
+ }
+
+ // Fill in remaining bits
+ while (bitNum < lastBit) {
+ byteNum = bitNum >> 3;
+ buffer[byteNum] |= (1 << (7 - (bitNum & 0x7)));
+ ++bitNum;
+ }
+ }
+
+ // Move pointer backwards by given amount of bits
+ private void updatePointer(int bitsToMoveBack) {
+ if (bitsToMoveBack > 8) {
+ bytePointer -= bitsToMoveBack / 8;
+ bitsToMoveBack %= 8;
+ }
+
+ int i = bitPointer - bitsToMoveBack;
+
+ if (i < 0) {
+ bytePointer--;
+ bitPointer = 8 + i;
+ } else {
+ bitPointer = i;
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/DCTDecode.java b/PdfView/src/main/java/com/sun/pdfview/decode/DCTDecode.java
new file mode 100644
index 0000000..7e13e2c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/DCTDecode.java
@@ -0,0 +1,85 @@
+/*
+ * $Id: DCTDecode.java,v 1.2 2007/12/20 18:33:33 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.decode;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * decode a DCT encoded array into a byte array. This class uses Java's
+ * built-in JPEG image class to do the decoding.
+ *
+ * @author Mike Wessler
+ */
+public class DCTDecode {
+
+ /**
+ * decode an array of bytes in DCT format.
+ *
+ * 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.
+ *
+ * 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);
+ 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;
+ }
+}
+
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/FlateDecode.java b/PdfView/src/main/java/com/sun/pdfview/decode/FlateDecode.java
new file mode 100644
index 0000000..7c69258
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/FlateDecode.java
@@ -0,0 +1,111 @@
+/*
+ * $Id: FlateDecode.java,v 1.4 2009/01/03 17:23:30 tomoke 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.decode;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * decode a deFlated byte array
+ *
+ * @author Mike Wessler
+ * @author Joerg Jahnke (joerg.jahnke@users.sourceforge.net)
+ */
+public class FlateDecode {
+
+ /**
+ * decode a byte buffer in Flate format.
+ *
+ * Flate is a built-in Java algorithm. It's part of the java.util.zip
+ * package.
+ *
+ * @param buf the deflated input buffer
+ * @param params parameters to the decoder (unused)
+ * @return the decoded (inflated) bytes
+ */
+ public static ByteBuffer decode(PDFObject dict, ByteBuffer buf,
+ PDFObject params) throws IOException {
+ Inflater inf = new Inflater(false);
+
+ int bufSize = buf.remaining();
+
+ // set the input for the inflater
+ byte[] data = null;
+
+ if (buf.hasArray()) {
+ data = buf.array();
+ inf.setInput(data, buf.arrayOffset() + buf.position(), bufSize);
+ buf.position(buf.position() + bufSize);
+ } else {
+ // copy the data, since the array() method is not supported
+ // on raf-based ByteBuffers
+ data = new byte[bufSize];
+ buf.get(data);
+ inf.setInput(data);
+ }
+
+
+ // output to a byte-array output stream, since we don't
+ // know how big the output will be
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] decomp = new byte[bufSize];
+ int read = 0;
+
+ try {
+ while (!inf.finished()) {
+ read = inf.inflate(decomp);
+ if (read <= 0) {
+// System.out.println("Read = " + read + "! Params: " + params);
+ if (inf.needsDictionary()) {
+ throw new PDFParseException("Don't know how to ask for a dictionary in FlateDecode");
+ } else {
+// System.out.println("Inflate data length=" + buf.remaining());
+ return ByteBuffer.allocate(0);
+ // throw new PDFParseException("Inflater wants more data... but it's already here!");
+ }
+ }
+ baos.write(decomp, 0, read);
+ }
+ } catch (DataFormatException dfe) {
+ throw new PDFParseException("Data format exception:" + dfe.getMessage());
+ }
+
+ // return the output as a byte buffer
+ ByteBuffer outBytes = ByteBuffer.wrap(baos.toByteArray());
+
+ // undo a predictor algorithm, if any was used
+ if (params != null && params.getDictionary().containsKey("Predictor")) {
+ Predictor predictor = Predictor.getPredictor(params);
+ if (predictor != null) {
+ outBytes = predictor.unpredict(outBytes);
+ }
+ }
+
+ return outBytes;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/LZWDecode.java b/PdfView/src/main/java/com/sun/pdfview/decode/LZWDecode.java
new file mode 100644
index 0000000..3072c9b
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/LZWDecode.java
@@ -0,0 +1,204 @@
+/*
+ * $Id: LZWDecode.java,v 1.4 2009/02/22 00:45:32 tomoke 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.decode;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * decode an LZW-encoded array of bytes. LZW is a patented algorithm.
+ *
+ *
Feb 21, 2009 Legal statement on Intellectual Property from Unisys
+ * LZW Patent Information (http://www.unisys.com/about__unisys/lzw)
+ * License Information on GIF and Other LZW-based Technologies
+ *
+ * Unisys U.S. LZW Patent No. 4,558,302 expired on June 20, 2003,
+ * the counterpart patents in the United Kingdom, France, Germany and
+ * Italy expired on June 18, 2004, the Japanese counterpart patents
+ * expired on June 20, 2004 and the counterpart Canadian patent
+ * expired on July 7, 2004.
+ *
+ * Unisys Corporation holds and has patents pending on a number of
+ * improvements on the inventions claimed in the above-expired patents.
+ * Information on these improvement patents and terms under which they
+ * may be licensed can be obtained by contacting the following:
+ *
+ * Unisys Corporation
+ * Welch Patent Licensing Department
+ * Mail Stop E8-114
+ * Unisys Way
+ * Blue Bell, PA 19424
+ *
+ * Via the Internet, send email to Robert.Marley@unisys.com.
+ *
+ * Via facsimile, send inquiries to Welch Patent Licensing Department at
+ * 215-986-3090.
+ *
+ * The above is presented for information purposes only, and is subject
+ * to change by Unisys. Additionally, this information should not be
+ * considered as legally obligating Unisys in any way with regard to license
+ * availability, or as to the terms and conditions offered for a license,
+ * or with regard to the interpretation of any license agreements.
+ * You should consult with your own legal counsel regarding your
+ * particular situation.
+ *
+ *
+ * @author Mike Wessler
+ */
+public class LZWDecode {
+
+ ByteBuffer buf;
+ int bytepos;
+ int bitpos;
+ byte[] dict[] = new byte[4096][];
+ int dictlen = 0;
+ int bitspercode = 9;
+ static int STOP = 257;
+ static int CLEARDICT = 256;
+
+ /**
+ * initialize this decoder with an array of encoded bytes
+ * @param buf the buffer of bytes
+ */
+ private LZWDecode(ByteBuffer buf) throws PDFParseException {
+ for (int i = 0; i < 256; i++) {
+ dict[i] = new byte[1];
+ dict[i][0] = (byte) i;
+ }
+ dictlen = 258;
+ bitspercode = 9;
+ this.buf = buf;
+ bytepos = 0;
+ bitpos = 0;
+ }
+
+ /**
+ * reset the dictionary to the initial 258 entries
+ */
+ private void resetDict() {
+ dictlen = 258;
+ bitspercode = 9;
+ }
+
+ /**
+ * get the next code from the input stream
+ */
+ private int nextCode() {
+ int fillbits = bitspercode;
+ int value = 0;
+ if (bytepos >= buf.limit() - 1) {
+ return -1;
+ }
+ while (fillbits > 0) {
+ int nextbits = buf.get(bytepos); // bitsource
+ int bitsfromhere = 8 - bitpos; // how many bits can we take?
+ if (bitsfromhere > fillbits) { // don't take more than we need
+ bitsfromhere = fillbits;
+ }
+ value |= ((nextbits >> (8 - bitpos - bitsfromhere)) &
+ (0xff >> (8 - bitsfromhere))) << (fillbits - bitsfromhere);
+ fillbits -= bitsfromhere;
+ bitpos += bitsfromhere;
+ if (bitpos >= 8) {
+ bitpos = 0;
+ bytepos++;
+ }
+ }
+ return value;
+ }
+
+ /**
+ * decode the array.
+ * @return the uncompressed byte array
+ */
+ private ByteBuffer decode() throws PDFParseException {
+ // algorithm derived from:
+ // http://www.rasip.fer.hr/research/compress/algorithms/fund/lz/lzw.html
+ // and the PDFReference
+ int cW = CLEARDICT;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ while (true) {
+ int pW = cW;
+ cW = nextCode();
+ if (cW == -1) {
+ throw new PDFParseException("Missed the stop code in LZWDecode!");
+ }
+ if (cW == STOP) {
+ break;
+ } else if (cW == CLEARDICT) {
+ resetDict();
+ // pW= -1;
+ } else if (pW == CLEARDICT) {
+ baos.write(dict[cW], 0, dict[cW].length);
+ } else {
+ if (cW < dictlen) { // it's a code in the dictionary
+ baos.write(dict[cW], 0, dict[cW].length);
+ byte[] p = new byte[dict[pW].length + 1];
+ System.arraycopy(dict[pW], 0, p, 0, dict[pW].length);
+ p[dict[pW].length] = dict[cW][0];
+ dict[dictlen++] = p;
+ } else { // not in the dictionary (should==dictlen)
+ // if (cW!=dictlen) {
+ // System.out.println("Got a bouncy code: "+cW+" (dictlen="+dictlen+")");
+ // }
+ byte[] p = new byte[dict[pW].length + 1];
+ System.arraycopy(dict[pW], 0, p, 0, dict[pW].length);
+ p[dict[pW].length] = p[0];
+ baos.write(p, 0, p.length);
+ dict[dictlen++] = p;
+ }
+ if (dictlen >= (1 << bitspercode) - 1 && bitspercode < 12) {
+ bitspercode++;
+ }
+ }
+ }
+ return ByteBuffer.wrap(baos.toByteArray());
+ }
+
+ /**
+ * decode an array of LZW-encoded bytes to a byte array.
+ *
+ * @param buf the buffer of encoded bytes
+ * @param params parameters for the decoder (unused)
+ * @return the decoded uncompressed bytes
+ */
+ public static ByteBuffer decode(ByteBuffer buf, PDFObject params)
+ throws IOException {
+ // decode the array
+ LZWDecode me = new LZWDecode(buf);
+ ByteBuffer outBytes = me.decode();
+
+ // undo a predictor algorithm, if any was used
+ if (params != null && params.getDictionary().containsKey("Predictor")) {
+ Predictor predictor = Predictor.getPredictor(params);
+ if (predictor != null) {
+ outBytes = predictor.unpredict(outBytes);
+ }
+ }
+
+ return outBytes;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/PDFDecoder.java b/PdfView/src/main/java/com/sun/pdfview/decode/PDFDecoder.java
new file mode 100644
index 0000000..94f848c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/PDFDecoder.java
@@ -0,0 +1,122 @@
+/*
+ * $Id: PDFDecoder.java,v 1.5 2009/03/12 12:26:19 tomoke 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.decode;
+
+import java.io.IOException;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+import com.sun.pdfview.decrypt.PDFDecrypterFactory;
+
+/**
+ * A PDF Decoder encapsulates all the methods of decoding a stream of bytes
+ * based on all the various encoding methods.
+ *
+ * You should use the decodeStream() method of this object rather than using
+ * any of the decoders directly.
+ */
+public class PDFDecoder {
+
+ /** Creates a new instance of PDFDecoder */
+ private PDFDecoder() {
+ }
+
+ /**
+ * decode a byte[] stream using the filters specified in the object's
+ * dictionary (passed as argument 1).
+ * @param dict the dictionary associated with the stream
+ * @param streamBuf the data in the stream, as a byte buffer
+ */
+ public static ByteBuffer decodeStream(PDFObject dict, ByteBuffer streamBuf)
+ throws IOException {
+
+ PDFObject filter = dict.getDictRef("Filter");
+ if (filter == null) {
+ // just apply default decryption
+ return dict.getDecrypter().decryptBuffer(null, dict, streamBuf);
+ } else {
+ // apply filters
+ PDFObject ary[];
+ PDFObject params[];
+ if (filter.getType() == PDFObject.NAME) {
+ ary = new PDFObject[1];
+ ary[0] = filter;
+ params = new PDFObject[1];
+ params[0] = dict.getDictRef("DecodeParms");
+ } else {
+ ary = filter.getArray();
+ PDFObject parmsobj = dict.getDictRef("DecodeParms");
+ if (parmsobj != null) {
+ params = parmsobj.getArray();
+ } else {
+ params = new PDFObject[ary.length];
+ }
+ }
+
+ // determine whether default encryption applies or if there's a
+ // specific Crypt filter; it must be the first filter according to
+ // the errata for PDF1.7
+ boolean specificCryptFilter =
+ ary.length != 0 && ary[0].getStringValue().equals("Crypt");
+ if (!specificCryptFilter) {
+ // No Crypt filter, so should apply default decryption (if
+ // present!)
+ streamBuf = dict.getDecrypter().decryptBuffer(
+ null, dict, streamBuf);
+ }
+
+ for (int i = 0; i < ary.length; i++) {
+ String enctype = ary[i].getStringValue();
+ if (enctype == null) {
+ } else if (enctype.equals("FlateDecode") || enctype.equals("Fl")) {
+ streamBuf = FlateDecode.decode(dict, streamBuf, params[i]);
+ } else if (enctype.equals("LZWDecode") || enctype.equals("LZW")) {
+ streamBuf = LZWDecode.decode(streamBuf, params[i]);
+ } else if (enctype.equals("ASCII85Decode") || enctype.equals("A85")) {
+ streamBuf = ASCII85Decode.decode(streamBuf, params[i]);
+ } else if (enctype.equals("ASCIIHexDecode") || enctype.equals("AHx")) {
+ streamBuf = ASCIIHexDecode.decode(streamBuf, params[i]);
+ } else if (enctype.equals("RunLengthDecode") || enctype.equals("RL")) {
+ streamBuf = RunLengthDecode.decode(streamBuf, params[i]);
+ } else if (enctype.equals("DCTDecode") || enctype.equals("DCT")) {
+ streamBuf = DCTDecode.decode(dict, streamBuf, params[i]);
+ } else if (enctype.equals("CCITTFaxDecode") || enctype.equals("CCF")) {
+ streamBuf = CCITTFaxDecode.decode(dict, streamBuf, params[i]);
+ } else if (enctype.equals("Crypt")) {
+ String cfName = PDFDecrypterFactory.CF_IDENTITY;
+ if (params[i] != null) {
+ final PDFObject nameObj = params[i].getDictRef("Name");
+ if (nameObj != null && nameObj.getType() == PDFObject.NAME) {
+ cfName = nameObj.getStringValue();
+ }
+ }
+ streamBuf = dict.getDecrypter().decryptBuffer(cfName, null, streamBuf);
+ } else {
+ throw new PDFParseException("Unknown coding method:" + ary[i].getStringValue());
+ }
+ }
+ }
+
+ return streamBuf;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/PNGPredictor.java b/PdfView/src/main/java/com/sun/pdfview/decode/PNGPredictor.java
new file mode 100644
index 0000000..7c0333c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/PNGPredictor.java
@@ -0,0 +1,213 @@
+/*
+ * $Id: PNGPredictor.java,v 1.3 2009/02/12 13:53:58 tomoke 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.decode;
+
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * Undo prediction based on the PNG algorithm.
+ */
+public class PNGPredictor extends Predictor {
+ /** Creates a new instance of PNGPredictor */
+ public PNGPredictor() {
+ super (PNG);
+ }
+
+ /**
+ * Undo data based on the png algorithm
+ */
+ public ByteBuffer unpredict(ByteBuffer imageData)
+ throws IOException
+ {
+ List rows = new ArrayList();
+
+ byte[] curLine = null;
+ byte[] prevLine = null;
+
+ // get the number of bytes per row
+ int rowSize = getColumns() * getColors() * getBitsPerComponent();
+ rowSize = (int) Math.ceil(rowSize / 8.0);
+
+ while(imageData.remaining() >= rowSize + 1) {
+ // the first byte determines the algorithm
+ int algorithm = (int) (imageData.get() & 0xff);
+
+ // read the rest of the line
+ curLine = new byte[rowSize];
+ imageData.get(curLine);
+
+ // use the algorithm, Luke
+ switch (algorithm) {
+ case 0:
+ // none
+ break;
+ case 1:
+ doSubLine(curLine);
+ break;
+ case 2:
+ doUpLine(curLine, prevLine);
+ break;
+ case 3:
+ doAverageLine(curLine, prevLine);
+ break;
+ case 4:
+ doPaethLine(curLine, prevLine);
+ break;
+ }
+
+ rows.add(curLine);
+ prevLine = curLine;
+ }
+
+ // turn into byte array
+ ByteBuffer outBuf = ByteBuffer.allocate(rows.size() * rowSize);
+ for (Iterator i = rows.iterator(); i.hasNext();) {
+ outBuf.put((byte[]) i.next());
+ }
+
+ // reset start pointer
+ outBuf.flip();
+
+ // return
+ return outBuf;
+
+ }
+
+ /**
+ * Return the value of the Sub algorithm on the line (compare bytes to
+ * the previous byte of the same color on this line).
+ */
+ protected void doSubLine(byte[] curLine) {
+ // get the number of bytes per sample
+ int sub = (int) Math.ceil((getBitsPerComponent() * getColors()) / 8.0);
+
+ for (int i = 0; i < curLine.length; i++) {
+ int prevIdx = i - sub;
+ if (prevIdx >= 0) {
+ curLine[i] += curLine[prevIdx];
+ }
+ }
+ }
+
+ /**
+ * Return the value of the up algorithm on the line (compare bytes to
+ * the same byte in the previous line)
+ */
+ protected void doUpLine(byte[] curLine, byte[] prevLine) {
+ if (prevLine == null) {
+ // do nothing if this is the first line
+ return;
+ }
+
+ for (int i = 0; i < curLine.length; i++) {
+ curLine[i] += prevLine[i];
+ }
+ }
+
+ /**
+ * Return the value of the average algorithm on the line (compare
+ * bytes to the average of the previous byte of the same color and
+ * the same byte on the previous line)
+ */
+ protected void doAverageLine(byte[] curLine, byte[] prevLine) {
+ // get the number of bytes per sample
+ int sub = (int) Math.ceil((getBitsPerComponent() * getColors()) / 8.0);
+
+ for (int i = 0; i < curLine.length; i++) {
+ int raw = 0;
+ int prior = 0;
+
+ // get the last value of this color
+ int prevIdx = i - sub;
+ if (prevIdx >= 0) {
+ raw = curLine[prevIdx] & 0xff;
+ }
+
+ // get the value on the previous line
+ if (prevLine != null) {
+ prior = prevLine[i] & 0xff;
+ }
+
+ // add the average
+ curLine[i] += (byte) Math.floor((raw + prior) / 2);
+ }
+ }
+
+ /**
+ * Return the value of the average algorithm on the line (compare
+ * bytes to the average of the previous byte of the same color and
+ * the same byte on the previous line)
+ */
+ protected void doPaethLine(byte[] curLine, byte[] prevLine) {
+ // get the number of bytes per sample
+ int sub = (int) Math.ceil((getBitsPerComponent() * getColors()) / 8.0);
+
+ for (int i = 0; i < curLine.length; i++) {
+ int left = 0;
+ int up = 0;
+ int upLeft = 0;
+
+ // get the last value of this color
+ int prevIdx = i - sub;
+ if (prevIdx >= 0) {
+ left = curLine[prevIdx] & 0xff;
+ }
+
+ // get the value on the previous line
+ if (prevLine != null) {
+ up = prevLine[i] & 0xff;
+ }
+
+ if (prevIdx > 0 && prevLine != null) {
+ upLeft = prevLine[prevIdx] & 0xff;
+ }
+
+ // add the average
+ curLine[i] += (byte) paeth(left, up, upLeft);
+ }
+ }
+
+ /**
+ * The paeth algorithm
+ */
+ protected int paeth(int left, int up, int upLeft) {
+ int p = left + up - upLeft;
+ int pa = Math.abs(p - left);
+ int pb = Math.abs(p - up);
+ int pc = Math.abs(p - upLeft);
+
+ if ((pa <= pb) && (pa <= pc)) {
+ return left;
+ } else if (pb <= pc) {
+ return up;
+ } else {
+ return upLeft;
+ }
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/Predictor.java b/PdfView/src/main/java/com/sun/pdfview/decode/Predictor.java
new file mode 100644
index 0000000..50d5642
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/Predictor.java
@@ -0,0 +1,175 @@
+/*
+ * $Id: Predictor.java,v 1.2 2007/12/20 18:33:33 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.decode;
+
+import java.io.IOException;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * The abstract superclass of various predictor objects that undo well-known
+ * prediction algorithms.
+ */
+public abstract class Predictor {
+ /** well known algorithms */
+ public static final int TIFF = 0;
+ public static final int PNG = 1;
+
+ /** the algorithm to use */
+ private int algorithm;
+
+ /** the number of colors per sample */
+ private int colors = 1;
+
+ /** the number of bits per color component */
+ private int bpc = 8;
+
+ /** the number of columns per row */
+ private int columns = 1;
+
+ /**
+ * Create an instance of a predictor. Use getPredictor()
+ * instead of this.
+ */
+ protected Predictor(int algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ /**
+ * Actually perform this algorithm on decoded image data.
+ * Subclasses must implement this method
+ */
+ public abstract ByteBuffer unpredict(ByteBuffer imageData)
+ throws IOException;
+
+ /**
+ * Get an instance of a predictor
+ *
+ * @param params the filter parameters
+ */
+ public static Predictor getPredictor(PDFObject params)
+ throws IOException
+ {
+ // get the algorithm (required)
+ PDFObject algorithmObj = params.getDictRef("Predictor");
+ if (algorithmObj == null) {
+ // no predictor
+ return null;
+ }
+ int algorithm = algorithmObj.getIntValue();
+
+ // create the predictor object
+ Predictor predictor = null;
+ switch (algorithm) {
+ case 1:
+ // no predictor
+ return null;
+ case 2:
+ throw new PDFParseException("Tiff Predictor not supported");
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ predictor = new PNGPredictor();
+ break;
+ default:
+ throw new PDFParseException("Unknown predictor: " + algorithm);
+ }
+
+ // read the colors (optional)
+ PDFObject colorsObj = params.getDictRef("Colors");
+ if (colorsObj != null) {
+ predictor.setColors(colorsObj.getIntValue());
+ }
+
+ // read the bits per component (optional)
+ PDFObject bpcObj = params.getDictRef("BitsPerComponent");
+ if (bpcObj != null) {
+ predictor.setBitsPerComponent(bpcObj.getIntValue());
+ }
+
+ // read the columns (optional)
+ PDFObject columnsObj = params.getDictRef("Columns");
+ if (columnsObj != null) {
+ predictor.setColumns(columnsObj.getIntValue());
+ }
+
+ // all set
+ return predictor;
+ }
+
+ /**
+ * Get the algorithm in use
+ *
+ * @return one of the known algorithm types
+ */
+ public int getAlgorithm() {
+ return algorithm;
+ }
+
+ /**
+ * Get the number of colors per sample
+ */
+ public int getColors() {
+ return colors;
+ }
+
+ /**
+ * Set the number of colors per sample
+ */
+ protected void setColors(int colors) {
+ this.colors = colors;
+ }
+
+ /**
+ * Get the number of bits per color component
+ */
+ public int getBitsPerComponent() {
+ return bpc;
+ }
+
+ /**
+ * Set the number of bits per color component
+ */
+ public void setBitsPerComponent(int bpc) {
+ this.bpc = bpc;
+ }
+
+ /**
+ * Get the number of columns
+ */
+ public int getColumns() {
+ return columns;
+ }
+
+ /**
+ * Set the number of columns
+ */
+ public void setColumns(int columns) {
+ this.columns = columns;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decode/RunLengthDecode.java b/PdfView/src/main/java/com/sun/pdfview/decode/RunLengthDecode.java
new file mode 100644
index 0000000..eada63a
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decode/RunLengthDecode.java
@@ -0,0 +1,99 @@
+/*
+ * $Id: RunLengthDecode.java,v 1.1 2009/02/21 20:04:52 tomoke Exp $
+ *
+ * Copyright 2009 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.decode;
+
+import java.io.ByteArrayOutputStream;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * decode an array of Run Length encoded bytes into a byte array
+ *
+ * @author Mike Wessler
+ */
+public class RunLengthDecode {
+ /** the end of data in the RunLength encoding. */
+ private static final int RUN_LENGTH_EOD = 128;
+
+ private ByteBuffer buf;
+
+ /**
+ * initialize the decoder with an array of bytes in RunLength format
+ */
+ private RunLengthDecode(ByteBuffer buf) {
+ this.buf = buf;
+ }
+
+ /**
+ * decode the array
+ * @return the decoded bytes
+ */
+ private ByteBuffer decode() throws PDFParseException {
+ // start at the beginning of the buffer
+ buf.rewind();
+
+ // allocate the output buffer
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte dupAmount = -1;
+ byte[] buffer = new byte[128];
+ while ((dupAmount = buf.get()) != -1 &&
+ dupAmount != RUN_LENGTH_EOD) {
+ if (dupAmount <= 127) {
+ int amountToCopy = dupAmount + 1;
+ while (amountToCopy > 0) {
+ buf.get(buffer, 0, amountToCopy);
+ baos.write(buffer, 0, amountToCopy);
+ }
+ } else {
+ byte dupByte = buf.get();
+ for (int i = 0; i < 257 - (int) (dupAmount & 0xFF); i++) {
+ baos.write(dupByte);
+ }
+ }
+ }
+ return ByteBuffer.wrap(baos.toByteArray());
+ }
+
+ /**
+ * decode an array of bytes in RunLength format.
+ *
+ * RunLength format consists of a sequence of a byte-oriented format
+ * based on run length. There are a series of "runs", where
+ * a run is a length byte followed by 1 to 128 bytes of data.
+ * If the length is 0-127, the following length+1 (1 to 128) bytes are
+ * to be copied. If the length is 129 through 255, the following
+ * single byte is copied 257-length (2 to 128) times.
+ * A length value of 128 means and End of Data (EOD).
+ *
+ * @param buf the RUnLEngth encoded bytes in a byte buffer
+ *
+ * @param params parameters to the decoder (ignored)
+ * @return the decoded bytes
+ */
+ public static ByteBuffer decode(ByteBuffer buf, PDFObject params)
+ throws PDFParseException {
+ RunLengthDecode me = new RunLengthDecode(buf);
+ return me.decode();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/CryptFilterDecrypter.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/CryptFilterDecrypter.java
new file mode 100644
index 0000000..7f036e3
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/CryptFilterDecrypter.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+import java.util.Map;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * Implements Version 4 standard decryption, whereby the Encrypt dictionary
+ * contains a list of named 'crypt filters', each of which is the equivalent
+ * of a {@link PDFDecrypter}. In addition to this list of crypt filters,
+ * the name of the filter to use for streams and the default filter to use
+ * for strings is specified. Requests to decode a stream with a named
+ * decrypter (typically Identity) instead of the default decrypter
+ * are honoured.
+ *
+ * @author Luke Kirby
+ */
+public class CryptFilterDecrypter implements PDFDecrypter {
+
+ /** Maps from crypt filter names to their corresponding decrypters */
+ private Map decrypters;
+ /** The default decrypter for stream content */
+ private PDFDecrypter defaultStreamDecrypter;
+ /** The default decrypter for string content */
+ private PDFDecrypter defaultStringDecrypter;
+
+ /**
+ * Class constructor
+ * @param decrypters a map of crypt filter names to their corresponding
+ * decrypters. Must already contain the Identity filter.
+ * @param defaultStreamCryptName the crypt filter name of the default
+ * stream decrypter
+ * @param defaultStringCryptName the crypt filter name of the default
+ * string decrypter
+ * @throws PDFParseException if one of the named defaults is not
+ * present in decrypters
+ */
+ public CryptFilterDecrypter(
+ Map decrypters,
+ String defaultStreamCryptName,
+ String defaultStringCryptName)
+ throws PDFParseException {
+
+ this.decrypters = decrypters;
+ assert this.decrypters.containsKey("Identity") :
+ "Crypt Filter map does not contain required Identity filter";
+ defaultStreamDecrypter = this.decrypters.get(defaultStreamCryptName);
+ if (defaultStreamDecrypter == null) {
+ throw new PDFParseException(
+ "Unknown crypt filter specified as default for streams: " +
+ defaultStreamCryptName);
+ }
+ defaultStringDecrypter = this.decrypters.get(defaultStringCryptName);
+ if (defaultStringDecrypter == null) {
+ throw new PDFParseException(
+ "Unknown crypt filter specified as default for strings: " +
+ defaultStringCryptName);
+ }
+ }
+
+ public ByteBuffer decryptBuffer(
+ String cryptFilterName, PDFObject streamObj, ByteBuffer streamBuf)
+ throws PDFParseException {
+ final PDFDecrypter decrypter;
+ if (cryptFilterName == null) {
+ decrypter = defaultStreamDecrypter;
+ } else {
+ decrypter = decrypters.get(cryptFilterName);
+ if (decrypter == null) {
+ throw new PDFParseException("Unknown CryptFilter: " +
+ cryptFilterName);
+ }
+ }
+ return decrypter.decryptBuffer(
+ // elide the filter name to prevent V2 decrypters from
+ // complaining about a crypt filter name
+ null,
+ // if there's a specific crypt filter being used then objNum
+ // and objGen shouldn't contribute to the key, so we
+ // should make sure that no streamObj makes its way through
+ cryptFilterName != null ? null : streamObj,
+ streamBuf);
+ }
+
+ public String decryptString(int objNum, int objGen, String inputBasicString)
+ throws PDFParseException {
+ return defaultStringDecrypter.decryptString(objNum, objGen, inputBasicString);
+ }
+
+ public boolean isEncryptionPresent() {
+ for (final PDFDecrypter decrypter : decrypters.values()) {
+ if (decrypter.isEncryptionPresent()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isOwnerAuthorised() {
+ for (final PDFDecrypter decrypter : decrypters.values()) {
+ if (decrypter.isOwnerAuthorised()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/EncryptionUnsupportedByPlatformException.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/EncryptionUnsupportedByPlatformException.java
new file mode 100644
index 0000000..b58fbac
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/EncryptionUnsupportedByPlatformException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+/**
+ * Identifies that the specified encryption mechanism, though supported by the
+ * product, is not supported by the platform that it is running on; i.e., that
+ * either the JCE does not support a required cipher or that its policy is
+ * such that a key of a given length can not be used.
+ *
+ * @author Luke Kirby
+ */
+public class EncryptionUnsupportedByPlatformException
+ extends UnsupportedEncryptionException {
+
+ public EncryptionUnsupportedByPlatformException(String message) {
+ super(message);
+ }
+
+ public EncryptionUnsupportedByPlatformException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/EncryptionUnsupportedByProductException.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/EncryptionUnsupportedByProductException.java
new file mode 100644
index 0000000..3cc0184
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/EncryptionUnsupportedByProductException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+/**
+ * Identifies that the specified encryption mechanism is not
+ * supported by this product, that is, PDFRenderer, as opposed to
+ * a {@link EncryptionUnsupportedByPlatformException limitation in
+ * the platform}.
+ *
+ * @author Luke Kirby
+ */
+public class EncryptionUnsupportedByProductException
+ extends UnsupportedEncryptionException {
+
+ public EncryptionUnsupportedByProductException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/IdentityDecrypter.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/IdentityDecrypter.java
new file mode 100644
index 0000000..e1459f1
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/IdentityDecrypter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+
+/**
+ * Performs identity decryption; that is, inputs aren't encrypted and
+ * are returned right back.
+ *
+ * @Author Luke Kirby
+ */
+public class IdentityDecrypter implements PDFDecrypter {
+
+ private static IdentityDecrypter INSTANCE = new IdentityDecrypter();
+
+ public ByteBuffer decryptBuffer(String cryptFilterName,
+ PDFObject streamObj, ByteBuffer streamBuf)
+ throws PDFParseException {
+
+ if (cryptFilterName != null) {
+ throw new PDFParseException("This Encryption version does not support Crypt filters");
+ }
+
+ return streamBuf;
+ }
+
+ public String decryptString(int objNum, int objGen, String inputBasicString) {
+ return inputBasicString;
+ }
+
+ public static IdentityDecrypter getInstance() {
+ return INSTANCE;
+ }
+
+ public boolean isEncryptionPresent() {
+ return false;
+ }
+
+ public boolean isOwnerAuthorised() {
+ return false;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFAuthenticationFailureException.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFAuthenticationFailureException.java
new file mode 100644
index 0000000..b705bfc
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFAuthenticationFailureException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * Identifies that the supplied password was incorrect or non-existent
+ * and required.
+ * @author Luke Kirby
+ */
+// TODO - consider having this not extend PDFParseException so that
+// it will be handled more explicitly?
+public class PDFAuthenticationFailureException extends PDFParseException {
+ public PDFAuthenticationFailureException(String message) {
+ super(message);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFDecrypter.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFDecrypter.java
new file mode 100644
index 0000000..47b3493
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFDecrypter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+import com.sun.pdfview.PDFStringUtil;
+
+
+/**
+ * A decrypter decrypts streams and strings in a PDF document. {@link
+ * #decryptBuffer(String, PDFObject, ByteBuffer)} } should be used for decoding
+ * streams, and {@link #decryptString(int, int, String)} for string values in
+ * the PDF. It is possible for strings and streams to be encrypted with
+ * different mechanisms, so the appropriate method must alwayus be used.
+ *
+ * @see "PDFReference 1.7, Section 3.5 Encryption"
+ * @author Luke Kirby
+ */
+public interface PDFDecrypter {
+
+ /**
+ * Decrypt a buffer of data
+ * @param cryptFilterName the name of the crypt filter, if V4
+ * encryption is being used, where individual crypt filters may
+ * be specified for individual streams. If encryption is not using
+ * V4 encryption (indicated by V=4 in the Encrypt dictionary) then
+ * this must be null. Null may also be specified with V4 encryption
+ * to indicate that the default filter should be used.
+ * @param streamObj the object whose stream is being decrypted. The
+ * containing object's number and generation contribute to the key used for
+ * stream encrypted with the document's default encryption, so this is
+ * typically required. Should be null only if a cryptFilterName is
+ * specified, as objects with specific stream filters use the general
+ * document key, rather than a stream-specific key.
+ * @param streamBuf the buffer to decrypt
+ * @return a buffer containing the decrypted stream, positioned at its
+ * beginning; will only be the same buffer as streamBuf if the identity
+ * decrypter is being used
+ * @throws PDFParseException if the named crypt filter does not exist, or
+ * if a crypt filter is named when named crypt filters are not supported.
+ * Problems due to incorrect passwords are revealed prior to this point.
+ */
+ public ByteBuffer decryptBuffer(
+ String cryptFilterName,
+ PDFObject streamObj,
+ ByteBuffer streamBuf)
+ throws PDFParseException;
+
+ /**
+ * Decrypt a {@link PDFStringUtil basic string}.
+ * @param objNum the object number of the containing object
+ * @param objGen the generation number of the containing object
+ * @param inputBasicString the string to be decrypted
+ * @return the decrypted string
+ * @throws PDFParseException if the named crypt filter does not exist, or
+ * if a crypt filter is named when named crypt filters are not supported.
+ * Problems due to incorrect passwords are revealed prior to this point.
+ */
+ public String decryptString(int objNum, int objGen, String inputBasicString)
+ throws PDFParseException;
+
+ /**
+ * Determine whether the password known by the decrypter indicates that
+ * the user is the owner of the document. Can be used, in conjunction
+ * with {@link #isEncryptionPresent()} to determine whether any
+ * permissions apply.
+ * @return whether owner authentication is being used to decrypt the
+ * document
+ */
+ public boolean isOwnerAuthorised();
+
+ /**
+ * Determine whether this actually applies a decryption other than
+ * identity decryption.
+ * @return whether encryption is present
+ */
+ public boolean isEncryptionPresent();
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFDecrypterFactory.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFDecrypterFactory.java
new file mode 100644
index 0000000..ca5ac10
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFDecrypterFactory.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Produces a {@link PDFDecrypter} for documents given a (possibly non-existent)
+ * Encrypt dictionary. Supports decryption of versions 1, 2 and 4 of the
+ * password-based encryption mechanisms as described in PDF Reference version
+ * 1.7. This means that it supports RC4 and AES encryption with keys of
+ * 40-128 bits; esentially, password-protected documents with compatibility
+ * up to Acrobat 8.
+ *
+ * @See "PDF Reference version 1.7, section 3.5: Encryption"
+ * @author Luke Kirby
+ */
+public class PDFDecrypterFactory {
+
+ /** The name of the standard Identity CryptFilter */
+ public static final String CF_IDENTITY = "Identity";
+
+ /** Default key length for versions where key length is optional */
+ private static final int DEFAULT_KEY_LENGTH = 40;
+
+ /**
+ * Create a decryptor for a given encryption dictionary. A check is
+ * immediately performed that the supplied password decrypts content
+ * described by the encryption specification.
+ *
+ * @param encryptDict the Encrypt dict as found in the document's trailer.
+ * May be null, in which case the {@link IdentityDecrypter} will
+ * be returned.
+ * @param documentId the object with key "ID" in the trailer's dictionary.
+ * Should always be present if Encrypt is.
+ * @param password the password to use; may be null
+ * @return The decryptor that should be used for all encrypted data in the
+ * PDF
+ * @throws IOException will typically be a {@link
+ * com.sun.pdfview.PDFParseException}, indicating an IO problem, an error
+ * in the structure of the document, or a failure to obtain various ciphers
+ * from the installed JCE providers
+ * @throws EncryptionUnsupportedByPlatformException if the encryption
+ * is not supported by the environment in which the code is executing
+ * @throws EncryptionUnsupportedByProductException if PDFRenderer does
+ * not currently support the specified encryption
+ * @throws PDFAuthenticationFailureException if the supplied password
+ * was not able to
+ */
+ public static PDFDecrypter createDecryptor
+ (PDFObject encryptDict, PDFObject documentId, PDFPassword password)
+ throws
+ IOException,
+ EncryptionUnsupportedByPlatformException,
+ EncryptionUnsupportedByProductException,
+ PDFAuthenticationFailureException {
+
+ // none of the classes beyond us want to see a null PDFPassword
+ password = PDFPassword.nonNullPassword(password);
+
+ if (encryptDict == null) {
+ // No encryption specified
+ return IdentityDecrypter.getInstance();
+ } else {
+ PDFObject filter = encryptDict.getDictRef("Filter");
+ // this means that we'll fail if, for example, public key
+ // encryption is employed
+ if (filter != null && "Standard".equals(filter.getStringValue())) {
+ final PDFObject vObj = encryptDict.getDictRef("V");
+ int v = vObj != null ? vObj.getIntValue() : 0;
+ if (v == 1 || v == 2) {
+ final PDFObject lengthObj =
+ encryptDict.getDictRef("Length");
+ final Integer length =
+ lengthObj != null ? lengthObj.getIntValue() : null;
+ return createStandardDecrypter(
+ encryptDict, documentId, password, length, false,
+ StandardDecrypter.EncryptionAlgorithm.RC4);
+ } else if (v == 4) {
+ return createCryptFilterDecrypter(
+ encryptDict, documentId, password, v);
+ } else {
+ throw new EncryptionUnsupportedByPlatformException(
+ "Unsupported encryption version: " + v);
+ }
+ } else if (filter == null) {
+ throw new PDFParseException(
+ "No Filter specified in Encrypt dictionary");
+ } else {
+ throw new EncryptionUnsupportedByPlatformException(
+ "Unsupported encryption Filter: " + filter +
+ "; only Standard is supported.");
+ }
+ }
+ }
+
+ /**
+ * Create a decrypter working from a crypt filter dictionary, as in
+ * version 4 encryption
+ *
+ * @param encryptDict the Encrypt dictionary
+ * @param documentId the document ID
+ * @param password the provided password
+ * @param v the version of encryption being used; must be at least 4
+ * @return the decrypter corresponding to the scheme expressed in
+ * encryptDict
+ * @throws PDFAuthenticationFailureException if the provided password
+ * does not decrypt this document
+ * @throws IOException if there is a problem reading the PDF, an invalid
+ * document structure, or an inability to obtain the required ciphers
+ * from the platform's JCE
+ * @throws EncryptionUnsupportedByPlatformException if the encryption
+ * is not supported by the environment in which the code is executing
+ * @throws EncryptionUnsupportedByProductException if PDFRenderer does
+ * not currently support the specified encryption
+ */
+ private static PDFDecrypter createCryptFilterDecrypter(
+ PDFObject encryptDict,
+ PDFObject documentId,
+ PDFPassword password,
+ int v)
+ throws
+ PDFAuthenticationFailureException,
+ IOException,
+ EncryptionUnsupportedByPlatformException,
+ EncryptionUnsupportedByProductException {
+
+ assert v >= 4 : "crypt filter decrypter not supported for " +
+ "standard encryption prior to version 4";
+
+ // encryptMetadata is true if not present. Note that we don't actually
+ // use this to change our reading of metadata streams (that's all done
+ // internally by the document specifying a Crypt filter of None if
+ // appropriate), but it does affect the encryption key.
+ boolean encryptMetadata = true;
+ final PDFObject encryptMetadataObj =
+ encryptDict.getDictRef("EncryptMetadata");
+ if (encryptMetadataObj != null
+ && encryptMetadataObj.getType() == PDFObject.BOOLEAN) {
+ encryptMetadata = encryptMetadataObj.getBooleanValue();
+ }
+
+ // Assemble decrypters for each filter in the
+ // crypt filter (CF) dictionary
+ final Map cfDecrypters =
+ new HashMap();
+ final PDFObject cfDict = encryptDict.getDictRef("CF");
+ if (cfDict == null) {
+ throw new PDFParseException(
+ "No CF value present in Encrypt dict for V4 encryption");
+ }
+ final Iterator cfNameIt = cfDict.getDictKeys();
+ while (cfNameIt.hasNext()) {
+ final String cfName = cfNameIt.next();
+ final PDFObject cryptFilter = cfDict.getDictRef(cfName);
+
+ final PDFObject lengthObj = cryptFilter.getDictRef("Length");
+ // The Errata for PDF 1.7 explains that the value of
+ // Length in CF dictionaries is in bytes
+ final Integer length = lengthObj != null ?
+ lengthObj.getIntValue() * 8 : null;
+
+ // CFM is the crypt filter method, describing whether RC4,
+ // AES, or None (i.e., identity) is the encryption mechanism
+ // used for the name crypt filter
+ final PDFObject cfmObj = cryptFilter.getDictRef("CFM");
+ final String cfm = cfmObj != null ?
+ cfmObj.getStringValue() : "None";
+ final PDFDecrypter cfDecrypter;
+ if ("None".equals(cfm)) {
+ cfDecrypter = IdentityDecrypter.getInstance();
+ } else if ("V2".equals(cfm)) {
+ cfDecrypter = createStandardDecrypter(
+ encryptDict, documentId, password, length,
+ encryptMetadata,
+ StandardDecrypter.EncryptionAlgorithm.RC4);
+ } else if ("AESV2".equals(cfm)) {
+ cfDecrypter = createStandardDecrypter(
+ encryptDict, documentId, password, length,
+ encryptMetadata,
+ StandardDecrypter.EncryptionAlgorithm.AESV2);
+ } else {
+ throw new UnsupportedOperationException(
+ "Unknown CryptFilter method: " + cfm);
+ }
+ cfDecrypters.put(cfName, cfDecrypter);
+ }
+
+ // always put Identity in last so that it will override any
+ // Identity filter sneakily declared in the CF entry
+ cfDecrypters.put(CF_IDENTITY, IdentityDecrypter.getInstance());
+
+ PDFObject stmFObj = encryptDict.getDictRef("StmF");
+ final String defaultStreamFilter =
+ stmFObj != null ? stmFObj.getStringValue() : CF_IDENTITY;
+
+ PDFObject strFObj = encryptDict.getDictRef("StrF");
+ final String defaultStringFilter =
+ strFObj != null ? strFObj.getStringValue() : CF_IDENTITY;
+
+ return new CryptFilterDecrypter(
+ cfDecrypters, defaultStreamFilter, defaultStringFilter);
+
+ }
+
+ /**
+ * Create a standard single-algorithm AES or RC4 decrypter. The Encrypt
+ * dictionary is used where possible, but where different encryption
+ * versions employ different mechanisms of specifying configuration or may
+ * be specified via a CF entry (e.g. key length), the value is specified as
+ * a parameter.
+ *
+ * @param encryptDict the Encrypt dictionary
+ * @param documentId the document ID
+ * @param password the password
+ * @param keyLength the key length, in bits; may be null
+ * to use a {@link #DEFAULT_KEY_LENGTH default}
+ * @param encryptMetadata whether metadata is being encrypted
+ * @param encryptionAlgorithm, the encryption algorithm
+ * @return the decrypter
+ * @throws PDFAuthenticationFailureException if the provided password
+ * is not the one expressed by the encryption dictionary
+ * @throws IOException if there is a problem reading the PDF content,
+ * if the content does not comply with the PDF specification
+ * @throws EncryptionUnsupportedByPlatformException if the encryption
+ * is not supported by the environment in which the code is executing
+ * @throws EncryptionUnsupportedByProductException if PDFRenderer does
+ * not currently support the specified encryption
+ *
+ */
+ private static PDFDecrypter createStandardDecrypter(
+ PDFObject encryptDict,
+ PDFObject documentId,
+ PDFPassword password,
+ Integer keyLength,
+ boolean encryptMetadata,
+ StandardDecrypter.EncryptionAlgorithm encryptionAlgorithm)
+ throws
+ PDFAuthenticationFailureException,
+ IOException,
+ EncryptionUnsupportedByPlatformException,
+ EncryptionUnsupportedByProductException {
+
+ if (keyLength == null) {
+ keyLength = DEFAULT_KEY_LENGTH;
+ }
+
+ // R describes the revision of the security handler
+ final PDFObject rObj = encryptDict.getDictRef("R");
+ if (rObj == null) {
+ throw new PDFParseException(
+ "No R entry present in Encrypt dictionary");
+ }
+
+ final int revision = rObj.getIntValue();
+ if (revision < 2 || revision > 4) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "Unsupported Standard security handler revision; R=" +
+ revision);
+ }
+
+ // O describes validation details for the owner key
+ final PDFObject oObj = encryptDict.getDictRef("O");
+ if (oObj == null) {
+ throw new PDFParseException(
+ "No O entry present in Encrypt dictionary");
+ }
+ final byte[] o = oObj.getStream();
+ if (o.length != 32) {
+ throw new PDFParseException("Expected owner key O " +
+ "value of 32 bytes; found " + o.length);
+ }
+
+ // U describes validation details for the user key
+ final PDFObject uObj = encryptDict.getDictRef("U");
+ if (uObj == null) {
+ throw new PDFParseException(
+ "No U entry present in Encrypt dictionary");
+ }
+ final byte[] u = uObj.getStream();
+ if (u.length != 32) {
+ throw new PDFParseException(
+ "Expected user key U value of 32 bytes; found " + o.length);
+ }
+
+ // P describes the permissions regarding document usage
+ final PDFObject pObj = encryptDict.getDictRef("P");
+ if (pObj == null) {
+ throw new PDFParseException(
+ "Required P entry in Encrypt dictionary not found");
+ }
+
+ return new StandardDecrypter(
+ encryptionAlgorithm, documentId, keyLength,
+ revision, o, u, pObj.getIntValue(), encryptMetadata, password);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFPassword.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFPassword.java
new file mode 100644
index 0000000..7718dd7
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/PDFPassword.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+import com.sun.pdfview.PDFDocCharsetEncoder;
+import com.sun.pdfview.Identity8BitCharsetEncoder;
+import com.sun.pdfview.PDFStringUtil;
+
+import java.util.*;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.CharBuffer;
+import java.nio.ByteBuffer;
+
+/**
+ *
Identifies a PDF Password, expressible either as a string or a
+ * byte sequence.
+ *
+ *
In revisions up to version 1.e Expansion 3, the mapping between a string
+ * and the bytes corresponding to the password was poorly specified, meaning
+ * that the safest manner in which to specify a password was via a byte array.
+ * With 1.7 expansion 3, a still slightly problematic mapping was given for the
+ * Standard encryption algorithms through to version 4, and a very well
+ * specified mapping for the new version 5 encryption.
+ *
+ *
So, for passwords specified in versions up to and including 4, a byte[]
+ * representation is the most accurate, but not necessarily the most convenient
+ * manner to provide passwords. For version 5, allowing passwords to be
+ * specified as Strings will be the preferred mechanism. Rather than specify two
+ * interfaces whenever a password can be provided - one for byte[] and one for
+ * String - we express the password as a class. This class can also offer a best
+ * guess at a String representation for a password for encryption versions up to
+ * and including 4.
+ *
+ * @author Luke Kirby
+ */
+public class PDFPassword {
+
+ /** The empty password */
+ public static final PDFPassword EMPTY_PASSWORD =
+ new PDFPassword(new byte[0]);
+
+ /**
+ * Ensure a non-null PDFPassword by substituting the empty password
+ * for a null password
+ * @param password the password, may be null
+ * @return a non-null password
+ */
+ public static PDFPassword nonNullPassword(PDFPassword password) {
+ return password != null ? password : EMPTY_PASSWORD;
+ }
+
+ /** the password in bytes, if specified as such */
+ private byte[] passwordBytes = null;
+ /** the passwird as a string, if specified as such */
+ private String passwordString = null;
+
+ /**
+ * Construct a byte-based password
+ * @param passwordBytes the password bytes
+ */
+ public PDFPassword(byte[] passwordBytes) {
+ this.passwordBytes =
+ passwordBytes != null ? passwordBytes : new byte[0];
+ }
+
+ /**
+ * Construct a string-based password
+ * @param passwordString the password
+ */
+ public PDFPassword(String passwordString) {
+ this.passwordString = passwordString != null ? passwordString : "";
+ }
+
+ /**
+ * Get the password bytes.
+ *
+ * @param unicodeConversion whether the specific conversion from a unicode
+ * String, as present for version 5 encryption, should be used
+ * @return a list of possible password bytes
+ */
+ List getPasswordBytes(boolean unicodeConversion) {
+ // TODO - handle unicodeConversion when we support version 5
+ if (this.passwordBytes != null || this.passwordString == null) {
+ return Collections.singletonList(this.passwordBytes);
+ } else {
+ if (isAlphaNum7BitString(this.passwordString)) {
+ // there's no reasonthat this string would get encoded
+ // in any other way
+ return Collections.singletonList(
+ PDFStringUtil.asBytes(passwordString));
+ } else {
+ return generatePossiblePasswordBytes(passwordString);
+ }
+ }
+ }
+
+ /**
+ * An array of password byte generators that attempts to enumerate the
+ * possible strategies that an encrypting application might take to convert
+ * a string to an array of bytes
+ */
+ private final static PasswordByteGenerator[] PASSWORD_BYTE_GENERATORS =
+ new PasswordByteGenerator[]{
+
+ // The best option, and that recommended by the spec, is
+ // straight PDFDocEncoding of the string but its not
+ // mentioned what to do with undefined characters
+ // (presumably, an encryption generating app should not
+ // allow them, but there are no guarantees!). Plus, that
+ // hasn't always been the case. There's also a possiblity
+ // that we'll be presented with the byte encoding from
+ // whatever code page is default on the system that
+ // generated the password. I don't think we're going to try
+ // all different code pages, though. Here are
+ // a few ideas, anyway!
+
+ // skip undefined chars
+ new PDFDocEncodingByteGenerator(null),
+ // replace undefined chars with 0
+ new PDFDocEncodingByteGenerator(Byte.valueOf((byte) 0)),
+ // replace undefined chars with ?
+ new PDFDocEncodingByteGenerator(Byte.valueOf((byte) '?')),
+ // just strip the higher 8 bits!
+ new PasswordByteGenerator() {
+ public byte[] generateBytes(String password) {
+ return PDFStringUtil.asBytes(password);
+ }
+ },
+ // skip 2-byte chars
+ new IdentityEncodingByteGenerator(null),
+ // replace 2-byte chars with 0
+ new IdentityEncodingByteGenerator(Byte.valueOf((byte) 0)),
+ // replace 2-byte chars with ?
+ new IdentityEncodingByteGenerator(Byte.valueOf((byte) '?'))
+ };
+
+ /**
+ * Generate some possible byte representations of a string password
+ *
+ * @param passwordString the string password
+ * @return a list of unique possible byte representations
+ */
+ private static List generatePossiblePasswordBytes(
+ String passwordString) {
+
+ final List possibilties = new ArrayList();
+ for (final PasswordByteGenerator generator : PASSWORD_BYTE_GENERATORS) {
+ byte[] generated = generator.generateBytes(passwordString);
+ // avoid duplicates
+ boolean alreadyGenerated = false;
+ for (int i = 0; !alreadyGenerated && i < possibilties.size(); ++i) {
+ if (Arrays.equals(possibilties.get(i), generated)) {
+ alreadyGenerated = true;
+ }
+ }
+ if (!alreadyGenerated) {
+ possibilties.add(generated);
+ }
+ }
+ return possibilties;
+ }
+
+ private boolean isAlphaNum7BitString(String string) {
+ for (int i = 0; i < string.length(); ++i) {
+ final char c = string.charAt(i);
+ if (c >= 127 || !Character.isLetterOrDigit(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Converts a string password to a byte[] representation
+ */
+ private static interface PasswordByteGenerator {
+ byte[] generateBytes(String password);
+ }
+
+ /**
+ * Converts strings to byte by employing a {@link CharsetEncoder} and a
+ * configurable mechanism to replace or ignore characters that are
+ * unrepresentable according to the encoder.
+ */
+ private static abstract class CharsetEncoderGenerator
+ implements PasswordByteGenerator {
+
+ private Byte replacementByte;
+
+ /**
+ * Class constructor
+ *
+ * @param replacementByte the byte to replace to use to represent any
+ * unrepresentable character, or null if unrepresentable characters
+ * should just be ignored
+ */
+ protected CharsetEncoderGenerator(Byte replacementByte) {
+ this.replacementByte = replacementByte;
+ }
+
+
+ public byte[] generateBytes(String password) {
+ final CharsetEncoder encoder = createCharsetEncoder();
+ if (replacementByte != null) {
+ encoder.replaceWith(new byte[]{replacementByte});
+ encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ } else {
+ encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
+ }
+ try {
+ final ByteBuffer b = encoder.encode(CharBuffer.wrap(password));
+ final byte[] bytes = new byte[b.remaining()];
+ b.get(bytes);
+ return bytes;
+ } catch (CharacterCodingException e) {
+ // shouldn't happen: unmappable characters should be the only
+ // problem, and we're not handling them with a report
+ return null;
+ }
+ }
+
+ protected abstract CharsetEncoder createCharsetEncoder();
+
+ }
+
+ /**
+ * Generate byte[] representations based on the PDFDocEncoding
+ */
+ private static class PDFDocEncodingByteGenerator
+ extends CharsetEncoderGenerator {
+
+ private PDFDocEncodingByteGenerator(Byte replacementByte) {
+ super(replacementByte);
+ }
+
+ protected CharsetEncoder createCharsetEncoder() {
+ return new PDFDocCharsetEncoder();
+ }
+ }
+
+ /**
+ * Generate byte[] representations based on a Unicode code point identity
+ * encoding; characters over 255 in value are considered unrepresentable
+ */
+ private static class IdentityEncodingByteGenerator
+ extends CharsetEncoderGenerator {
+
+ private IdentityEncodingByteGenerator(Byte replacementByte) {
+ super(replacementByte);
+ }
+
+ protected CharsetEncoder createCharsetEncoder() {
+ return new Identity8BitCharsetEncoder();
+ }
+ }
+
+}
+
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/StandardDecrypter.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/StandardDecrypter.java
new file mode 100644
index 0000000..b67eb4c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/StandardDecrypter.java
@@ -0,0 +1,1131 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import net.sf.andpdf.nio.ByteBuffer;
+import net.sf.andpdf.crypto.Cipher;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+import com.sun.pdfview.PDFStringUtil;
+
+/**
+ * Standard simple decrypter for versions 1, 2 and 4 of the Standard
+ * password-based decryption mechanisms, as described in section 3.5 of
+ * the PDF Reference version 1.7.
+ *
+ * @author Luke Kirby
+ */
+public class StandardDecrypter implements PDFDecrypter {
+
+ /**
+ * Extra salt to add to AES-based decryption keys, as per PDF Reference 1.7
+ */
+ private static final byte[] AESV2_SALT = {'s', 'A', 'l', 'T'};
+
+ /**
+ * Describes an encryption algorithm to be used, declaring not only the
+ * cipher type, but also key generation techniques
+ */
+ public enum EncryptionAlgorithm {
+ RC4, AESV2;
+
+ boolean isRC4() {
+ return this == RC4;
+ }
+
+ boolean isAES() {
+ return this == AESV2;
+ }
+
+ }
+
+ /**
+ * Padding used to bring passwords up to 32 bytes, as specified by the
+ * first step of Algorithm 3.2 in the PDF Reference version 1.7.
+ */
+ private final static byte[] PW_PADDING = new byte[]{
+ 0x28, (byte) 0xBF, 0x4E, 0x5E, 0x4E, 0x75, (byte) 0x8A, 0x41,
+ 0x64, 0x00, 0x4E, 0x56, (byte) 0xFF, (byte) 0xFA, 0x01, 0x08,
+ 0x2E, 0x2E, 0x00, (byte) 0xB6, (byte) 0xD0, 0x68, 0x3E, (byte) 0x80,
+ 0x2F, 0x0C, (byte) 0xA9, (byte) 0xFE, 0x64, 0x53, 0x69, 0x7A
+ };
+
+ /**
+ * The specification of the RC4 cipher for JCE interactions
+ */
+ private static final String CIPHER_RC4 = "RC4";
+ /**
+ * The key type for RC4 keys
+ */
+ private static final String KEY_RC4 = "RC4";
+
+ /**
+ * The specification of the AES cipher for JCE interactions. As per the
+ * spec, cipher-block chanining (CBC) mode and PKCS5 padding are used
+ */
+ private static final String CIPHER_AES = "AES/CBC/PKCS5Padding";
+ /**
+ * The key type for AES keys
+ */
+ private static final String KEY_AES = "AES";
+
+ /**
+ * Whether the owner password was specified
+ */
+ private boolean ownerAuthorised = false;
+
+ /**
+ * The general encryption key; may be mutated to form individual
+ * stream/string encryption keys
+ */
+ private byte[] generalKeyBytes;
+
+ /**
+ * The encryption algorithm being employed
+ */
+ private EncryptionAlgorithm encryptionAlgorithm;
+
+ /**
+ * Class constructor
+ *
+ * @param encryptionAlgorithm the algorithm used for encryption
+ * @param documentId the contents of the ID entry of the document's trailer
+ * dictionary; can be null, but according to the spec, shouldn't be. Is
+ * expected to be an array of two byte sequences.
+ * @param keyBitLength the length of the key in bits; should be a multiple
+ * of 8 between 40 and 128
+ * @param revision the revision of the Standard encryption security handler
+ * being employed. Should be 2, 3 or 4.
+ * @param oValue the value of the O entry from the Encrypt dictionary
+ * @param uValue the value of the U entry from the Encrypt dictionary
+ * @param pValue the value of the P entry from the Encrypt dictionary
+ * @param encryptMetadata whether metadata is being encrypted, as identified
+ * by the Encrypt dict (with default true if not explicitly identified)
+ * @param password the password; not null
+ * @throws IOException if there's a problem reading the file
+ * @throws EncryptionUnsupportedByPlatformException if the encryption is not
+ * supported by the environment in which the code is executing
+ * @throws EncryptionUnsupportedByProductException if PDFRenderer does not
+ * currently support the specified encryption
+ */
+ public StandardDecrypter(
+ EncryptionAlgorithm encryptionAlgorithm,
+ PDFObject documentId, int keyBitLength,
+ int revision, byte[] oValue, byte[] uValue, int pValue,
+ boolean encryptMetadata, PDFPassword password)
+ throws
+ IOException,
+ EncryptionUnsupportedByProductException,
+ EncryptionUnsupportedByPlatformException {
+
+ this.encryptionAlgorithm = encryptionAlgorithm;
+
+ // The spec (sensibly) demands that the documentId be present,
+ // but we'll play it safe
+ final byte[] firstDocIdValue;
+ if (documentId == null) {
+ firstDocIdValue = null;
+ } else {
+ firstDocIdValue = documentId.getAt(0).getStream();
+ }
+
+ testJceAvailability(keyBitLength);
+
+ try {
+ final List passwordBytePossibilities =
+ password.getPasswordBytes(false);
+ for (int i = 0;
+ generalKeyBytes == null && i < passwordBytePossibilities.size();
+ ++i) {
+ final byte[] passwordBytes = passwordBytePossibilities.get(i);
+ generalKeyBytes = checkOwnerPassword(
+ passwordBytes, firstDocIdValue, keyBitLength,
+ revision, oValue, uValue, pValue, encryptMetadata);
+ if (generalKeyBytes != null) {
+ // looks like the password was the owner password!
+ ownerAuthorised = true;
+ } else {
+ // try it as the user password
+ generalKeyBytes = checkUserPassword(
+ passwordBytes, firstDocIdValue, keyBitLength,
+ revision, oValue, uValue, pValue, encryptMetadata);
+
+ }
+ }
+ } catch (GeneralSecurityException e) {
+ // Unexpected, as our test of JCE availability should have caught
+ // problems with cipher availability.
+ // It may well be a problem with document content?
+ throw new PDFParseException("Unable to check passwords: " +
+ e.getMessage(), e);
+ }
+
+ if (generalKeyBytes == null) {
+ throw new PDFAuthenticationFailureException(
+ "Password failed authentication for both " +
+ "owner and user password");
+ }
+
+ }
+
+ public ByteBuffer decryptBuffer(
+ String cryptFilterName, PDFObject streamObj, ByteBuffer streamBuf)
+ throws PDFParseException {
+
+ if (cryptFilterName != null) {
+ throw new PDFParseException(
+ "This encryption version does not support Crypt filters");
+ }
+
+ if (streamObj != null) {
+ checkNums(streamObj.getObjNum(), streamObj.getObjGen());
+ }
+
+ final byte[] decryptionKeyBytes;
+ if (streamObj == null) {
+ // lack of a stream object indicates the unsalted key should be
+ // used
+ decryptionKeyBytes = getUnsaltedDecryptionKey();
+ } else {
+ decryptionKeyBytes = getObjectSaltedDecryptionKey(
+ streamObj.getObjNum(), streamObj.getObjGen());
+ }
+ return decryptBuffer(streamBuf, decryptionKeyBytes);
+ }
+
+ public String decryptString(int objNum, int objGen, String inputBasicString)
+ throws PDFParseException {
+ final byte[] crypted = PDFStringUtil.asBytes(inputBasicString);
+ final byte[] decryptionKey = getObjectSaltedDecryptionKey(objNum, objGen);
+ final ByteBuffer decrypted = decryptBuffer(ByteBuffer.wrap(crypted), decryptionKey);
+ return PDFStringUtil.asBasicString(decrypted.array(), decrypted.arrayOffset(), decrypted.limit());
+ }
+
+ public boolean isOwnerAuthorised() {
+ return ownerAuthorised;
+ }
+
+ public boolean isEncryptionPresent() {
+ return true;
+ }
+
+ /**
+ * Test that the platform (i.e., the JCE) can offer us all of the ciphers at
+ * the key length we need for content decryption. This shouldn't be a
+ * problem on the Java 5 platform unless a particularly restrictive policy
+ * file is in place. Calling this on construction should avoid problems like
+ * these being exposed as PDFParseExceptions as they're used during
+ * decryption and key establishment.
+ *
+ * @param keyBitLength the length of the content key, in bits
+ * @throws EncryptionUnsupportedByPlatformException if the platform does not
+ * support the required ciphers and key lengths
+ * @throws PDFParseException if there's an internal error while testing
+ * cipher availability
+ */
+ private void testJceAvailability(int keyBitLength)
+ throws
+ EncryptionUnsupportedByPlatformException, PDFParseException {
+
+ // we need to supply a little buffer for AES, which will look
+ // for an initialisation vector of 16 bytes
+ final byte[] junkBuffer = new byte[16];
+ Arrays.fill(junkBuffer, (byte) 0xAE);
+ // test using the longer key length for salted content so that
+ // we can check for maximum key length problems
+ final byte[] junkKey =
+ new byte[getSaltedContentKeyByteLength(keyBitLength / 8)];
+ Arrays.fill(junkKey, (byte) 0xAE);
+
+ try {
+ createAndInitialiseContentCipher(
+ ByteBuffer.wrap(junkBuffer),
+ junkKey);
+ } catch (PDFParseException e) {
+ throw new PDFParseException("Internal error; " +
+ "failed to produce test cipher: " + e.getMessage());
+ } catch (NoSuchAlgorithmException e) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "JCE does not offer required cipher", e);
+ } catch (NoSuchPaddingException e) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "JCE does not offer required padding", e);
+ } catch (InvalidKeyException e) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "JCE does accept key size of " +
+ (getSaltedContentKeyByteLength() * 8) +
+ " bits- could it be a policy restriction?", e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "JCE did not accept cipher parameter", e);
+ }
+
+ try {
+ createMD5Digest();
+ } catch (NoSuchAlgorithmException e) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "No MD5 digest available from JCE", e);
+ }
+
+ if (encryptionAlgorithm != EncryptionAlgorithm.RC4) {
+ // we still need RC4 for U and O value checks. Check again!
+ final Cipher rc4;
+ try {
+ rc4 = createRC4Cipher();
+ } catch (GeneralSecurityException e) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "JCE did not offer RC4 cipher", e);
+ }
+ // 40 byte key is used for base U and O ciphers
+ final byte[] rc4JunkKey = new byte[5];
+ Arrays.fill(junkKey, (byte) 0xAE);
+ try {
+ initDecryption(rc4, createRC4Key(rc4JunkKey));
+ } catch (InvalidKeyException ex) {
+ throw new EncryptionUnsupportedByPlatformException(
+ "JCE did not accept 40-bit RC4 key; " +
+ "policy problem?",
+ ex);
+ }
+ }
+ }
+
+ /**
+ * Decrypt a buffer
+ *
+ * @param encrypted the encrypted content
+ * @param decryptionKeyBytes the key to use for decryption
+ * @return a freshly allocated buffer containing the decrypted content
+ * @throws PDFParseException if there's a problem decrypting the content
+ */
+ private ByteBuffer decryptBuffer(
+ ByteBuffer encrypted, byte[] decryptionKeyBytes)
+ throws PDFParseException {
+
+ final Cipher cipher;
+ try {
+ cipher = createAndInitialiseContentCipher(
+ encrypted, decryptionKeyBytes);
+ } catch (GeneralSecurityException e) {
+ // we should have caught this earlier in testCipherAvailability
+ throw new PDFParseException(
+ "Unable to create cipher due to platform limitation: " +
+ e.getMessage(), e);
+ }
+
+ try {
+ // the decrypted content will never be more than the encrypted
+ // content. Thanks to padding, this buffer will be at most 16
+ // bytes bigger than the encrypted content
+ final java.nio.ByteBuffer decryptedBuf =
+ java.nio.ByteBuffer.allocate(encrypted.remaining());
+ cipher.doFinal(encrypted.toNIO(), decryptedBuf);
+ decryptedBuf.flip();
+ return ByteBuffer.fromNIO(decryptedBuf);
+// return decryptedBuf;
+ } catch (GeneralSecurityException e) {
+ throw new PDFParseException(
+ "Could not decrypt: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Setup the cipher for decryption
+ *
+ * @param encrypted the encrypted content; required by AES encryption so
+ * that the initialisation vector can be established
+ * @param decryptionKeyBytes the bytes for the decryption key
+ * @return a content decryption cypher, ready to accept input
+ * @throws PDFParseException if the encrypted buffer is malformed or on an
+ * internal error
+ * @throws NoSuchAlgorithmException if the cipher algorithm is not supported
+ * by the platform
+ * @throws NoSuchPaddingException if the cipher padding is not supported by
+ * the platform
+ * @throws InvalidKeyException if the key is invalid according to the
+ * cipher, or too long
+ * @throws InvalidAlgorithmParameterException if the cipher parameters are
+ * bad
+ */
+ private Cipher createAndInitialiseContentCipher(
+ ByteBuffer encrypted,
+ byte[] decryptionKeyBytes)
+ throws
+ PDFParseException,
+ NoSuchAlgorithmException,
+ NoSuchPaddingException,
+ InvalidKeyException,
+ InvalidAlgorithmParameterException {
+
+ final Cipher cipher;
+ if (encryptionAlgorithm.isRC4()) {
+ cipher = Cipher.getInstance(CIPHER_RC4);
+ cipher.init(Cipher.DECRYPT_MODE, createRC4Key(decryptionKeyBytes));
+ } else if (encryptionAlgorithm.isAES()) {
+ cipher = createAESCipher();
+ final byte[] initialisationVector = new byte[16];
+ if (encrypted.remaining() >= initialisationVector.length) {
+ encrypted.get(initialisationVector);
+ } else {
+ throw new PDFParseException(
+ "AES encrypted stream too short - " +
+ "no room for initialisation vector");
+ }
+
+ final SecretKeySpec aesKey =
+ new SecretKeySpec(decryptionKeyBytes, KEY_AES);
+ final IvParameterSpec aesIv =
+ new IvParameterSpec(initialisationVector);
+ cipher.init(Cipher.DECRYPT_MODE, aesKey, aesIv);
+ } else {
+ throw new PDFParseException(
+ "Internal error - unhandled cipher type: " +
+ encryptionAlgorithm);
+ }
+ return cipher;
+ }
+
+ /**
+ * Get the unsalted content decryption key, used for streams with specific
+ * crypt filters, which aren't specific to particular objects
+ *
+ * @return the general key
+ */
+ private byte[] getUnsaltedDecryptionKey() {
+ return generalKeyBytes;
+ }
+
+ /**
+ * Get a decryption key salted with an object number and object generation,
+ * for use when decrypting a string or stream within an object numbered so
+ *
+ * @param objNum the object number
+ * @param objGen the object generation
+ * @return the key to be used for decrypting data associated with the object
+ * numbered so
+ * @throws PDFParseException if the MD5 digest is not available
+ */
+ private byte[] getObjectSaltedDecryptionKey(int objNum, int objGen)
+ throws PDFParseException {
+
+ byte[] decryptionKeyBytes;
+ final MessageDigest md5;
+ try {
+ md5 = createMD5Digest();
+ } catch (NoSuchAlgorithmException e) {
+ // unexpected, as we will already have tested availability
+ throw new PDFParseException("Unable to get MD5 digester", e);
+ }
+ md5.update(this.generalKeyBytes);
+ md5.update((byte) objNum);
+ md5.update((byte) (objNum >> 8));
+ md5.update((byte) (objNum >> 16));
+ md5.update((byte) objGen);
+ md5.update((byte) (objGen >> 8));
+ if (encryptionAlgorithm == EncryptionAlgorithm.AESV2) {
+ md5.update(AESV2_SALT);
+ }
+ final byte[] hash = md5.digest();
+ final int keyLen = getSaltedContentKeyByteLength();
+ decryptionKeyBytes = new byte[keyLen];
+ System.arraycopy(hash, 0, decryptionKeyBytes, 0, keyLen);
+ return decryptionKeyBytes;
+ }
+
+ /**
+ * Get the length of a salted key
+ *
+ * @return length in bytes
+ */
+ private int getSaltedContentKeyByteLength() {
+ return getSaltedContentKeyByteLength(generalKeyBytes.length);
+ }
+
+ /**
+ * Get the length of salted keys, in bytes. Unsalted keys will be the same
+ * length as {@link #generalKeyBytes}
+ *
+ * @param generalKeyByteLength the length of the general key, in bytes
+ * @return byte length of salted keys
+ */
+ private int getSaltedContentKeyByteLength(int generalKeyByteLength) {
+ return Math.min(generalKeyByteLength + 5, 16);
+ }
+
+ /**
+ * Check that object number and object generations are well-formed. It is
+ * possible for some {@link PDFObject}s to have uninitialised object numbers
+ * and generations, but such objects should not required decryption
+ *
+ * @param objNum the object number
+ * @param objGen the object generation
+ * @throws PDFParseException if the object numbering indicates that they
+ * aren't true object numbers
+ */
+ private void checkNums(int objNum, int objGen)
+ throws PDFParseException {
+ if (objNum < 0) {
+ throw new PDFParseException(
+ "Internal error: Object has bogus object number");
+ } else if (objGen < 0) {
+ throw new PDFParseException(
+ "Internal error: Object has bogus generation number");
+ }
+ }
+
+ /**
+ * Calculate what the U value should consist of given a particular key and
+ * document configuration. Correponds to Algorithms 3.4 and 3.5 of the
+ * PDF Reference version 1.7
+ *
+ * @param generalKey the general encryption key
+ * @param firstDocIdValue the value of the first element in the document's
+ * ID entry in the trailer dictionary
+ * @param revision the revision of the security handler
+ * @return the U value for the given configuration
+ * @throws GeneralSecurityException if there's an error getting required
+ * ciphers, etc. (unexpected, since a check for algorithm availability is
+ * performed on construction)
+ * @throws EncryptionUnsupportedByProductException if the revision is not
+ * supported
+ */
+ private byte[] calculateUValue(
+ byte[] generalKey, byte[] firstDocIdValue, int revision)
+ throws
+ GeneralSecurityException,
+ EncryptionUnsupportedByProductException {
+
+ if (revision == 2) {
+
+ // Algorithm 3.4: Computing the encryption dictionary’s U (user
+ // password) value (Revision 2)
+
+ // Step 1 is provided to us as the parameter generalKey:
+ // Create an encryption key based on the user password string, as
+ // described in Algorithm 3.2
+
+ // Step 2: Encrypt the 32-byte padding string shown in step 1 of
+ // Algorithm 3.2, using an RC4 encryption function with the
+ // encryption key from the preceding step.
+
+ Cipher rc4 = createRC4Cipher();
+ SecretKey key = createRC4Key(generalKey);
+ initEncryption(rc4, key);
+ return crypt(rc4, PW_PADDING);
+
+ } else if (revision >= 3) {
+
+ // Algorithm 3.5: Computing the encryption dictionary’s U (user
+ // password) value (Revision 3 or greater)
+
+ // Step 1 is provided to us as the parameter generalKey:
+ // Create an encryption key based on the user password string, as
+ // described in Algorithm 3.2
+
+ // Step 2: Initialize the MD5 hash function and pass the 32-byte
+ // padding string shown in step 1 of Algorithm 3.2 as input to this
+ // function
+ MessageDigest md5 = createMD5Digest();
+ md5.update(PW_PADDING);
+
+ // Step 3: Pass the first element of the file’s file identifier
+ // array (the value of the ID entry in the document’s trailer
+ // dictionary; see Table 3.13 on page 97) to the hash function and
+ // finish the hash. (See implementation note 26 in Appendix H.)
+ if (firstDocIdValue != null) {
+ md5.update(firstDocIdValue);
+ }
+ final byte[] hash = md5.digest();
+
+ // Step 4: Encrypt the 16-byte result of the hash, using an RC4
+ // encryption function with the encryption key from step 1.
+ Cipher rc4 = createRC4Cipher();
+ SecretKey key = createRC4Key(generalKey);
+ initEncryption(rc4, key);
+ final byte[] v = crypt(rc4, hash);
+
+ // Step 5: Do the following 19 times: Take the output from the
+ // previous invocation of the RC4 function and pass it as input to
+ // a new invocation of the function; use an encryption key generated
+ // by taking each byte of the original encryption key (obtained in
+ // step 1) and performing an XOR (exclusive or) operation between
+ // that byte and the single-byte value of the iteration counter
+ // (from 1 to 19).
+ rc4shuffle(v, generalKey, rc4);
+
+ // Step 6: Append 16 bytes of arbitrary padding to the output from
+ // the final invocation of the RC4 function and store the 32-byte
+ // result as the value of the U entry in the encryption dictionary.
+ assert v.length == 16;
+ final byte[] entryValue = new byte[32];
+ System.arraycopy(v, 0, entryValue, 0, v.length);
+ System.arraycopy(v, 0, entryValue, 16, v.length);
+ return entryValue;
+
+ } else {
+ throw new EncryptionUnsupportedByProductException(
+ "Unsupported standard security handler revision " +
+ revision);
+ }
+ }
+
+ /**
+ * Calculate what the O value of the Encrypt dict should look like given a
+ * particular configuration. Not used, but useful for reference; this
+ * process is reversed to determine whether a given password is the
+ * owner password. Corresponds to Algorithm 3.3 of the PDF Reference
+ * version 1.7.
+ *
+ * @see #checkOwnerPassword
+ * @param ownerPassword the owner password
+ * @param userPassword the user password
+ * @param keyBitLength the key length in bits (40-128)
+ * @param revision the security handler revision
+ * @return the O value entry
+ * @throws GeneralSecurityException if ciphers are unavailable or
+ * inappropriately used
+ */
+ private byte[] calculuateOValue(
+ byte[] ownerPassword, byte[] userPassword,
+ int keyBitLength, int revision)
+ throws GeneralSecurityException {
+
+ // Steps 1-4
+ final byte[] rc4KeyBytes =
+ getInitialOwnerPasswordKeyBytes(
+ ownerPassword, keyBitLength, revision);
+ final Cipher rc4 = createRC4Cipher();
+ initEncryption(rc4, createRC4Key(rc4KeyBytes));
+
+ // Step 5: Pad or truncate the user password string as described in step
+ // 1 of Algorithm 3.2.
+ // Step 6: Encrypt the result of step 5, using an RC4 encryption
+ // function with the encryption key obtained in step 4.
+ byte[] pwvalue = crypt(rc4, padPassword(userPassword));
+
+ // Step 7: (Revision 3 or greater) Do the following 19 times: Take the
+ // output from the previous invocation of the RC4 function and pass it
+ // as input to a new invocation of the function; use an encryption key
+ // generated by taking each byte of the encryption key obtained in step
+ // 4 and performing an XOR (exclusive or) operation between
+ if (revision >= 3) {
+ rc4shuffle(pwvalue, rc4KeyBytes, rc4);
+ }
+ assert pwvalue.length == 32;
+ return pwvalue;
+
+ }
+
+ /**
+ * Check to see whether a given password is the owner password. Corresponds
+ * to algorithm 3.6 of PDF Reference version 1.7.
+ *
+ * @param ownerPassword the suggested owner password (may be null or
+ * empty)
+ * @param firstDocIdValue the byte stream from the first element of the
+ * value of the ID entry in the trailer dictionary
+ * @param keyBitLength the key length in bits
+ * @param revision the security handler revision
+ * @param oValue the O value from the Encrypt dictionary
+ * @param uValue the U value from the Encrypt dictionary
+ * @param pValue the P value from the Encrypt dictionary
+ * @param encryptMetadata the EncryptMetadata entry from the Encrypt dictionary
+ * (or false if not present or revision <= 3)
+ * @return the general/user key bytes if the owner password is currect,
+ * null otherwise
+ * @throws GeneralSecurityException if there's a problem with
+ * cipher or digest usage; unexpected
+ * @throws EncryptionUnsupportedByProductException if PDFRenderer doesn't
+ * support the security handler revision
+ * @throws PDFParseException if the document is malformed
+ */
+ private byte[] checkOwnerPassword(
+ byte[] ownerPassword, byte[] firstDocIdValue, int keyBitLength,
+ int revision, byte[] oValue, byte[] uValue, int pValue,
+ boolean encryptMetadata)
+ throws
+ GeneralSecurityException,
+ EncryptionUnsupportedByProductException,
+ PDFParseException {
+
+ // Step 1: Compute an encryption key from the supplied password string,
+ // as described in steps 1 to 4 of Algorithm 3.3.
+ final byte[] rc4KeyBytes =
+ getInitialOwnerPasswordKeyBytes(ownerPassword,
+ keyBitLength, revision);
+ final Cipher rc4 = createRC4Cipher();
+ initDecryption(rc4, createRC4Key(rc4KeyBytes));
+
+ // Step 2:
+ final byte[] possibleUserPassword;
+ if (revision == 2) {
+ // (Revision 2 only) Decrypt the value of the encryption
+ // dictionary’s O entry, using an RC4 encryption function with the
+ // encryption key computed in step 1.
+
+ possibleUserPassword = crypt(rc4, oValue);
+ } else if (revision >= 3) {
+ // (Revision 3 or greater) Do the following 20 times: Decrypt the
+ // value of the encryption dictionary’s O entry (first iteration) or
+ // the output from the previous iteration (all subsequent
+ // iterations), using an RC4 encryption function with a different
+ // encryption key at each iteration. The key is generated by taking
+ // the original key (obtained in step 1) and performing an XOR
+ // (exclusive or) operation between each byte of the key and the
+ // single-byte value of the iteration counter (from 19 to 0).
+
+ // unshuffle the O entry; the unshuffle operation also
+ // contains the final decryption with the original key
+ possibleUserPassword = new byte[32];
+ System.arraycopy(oValue, 0, possibleUserPassword, 0,
+ possibleUserPassword.length);
+ rc4unshuffle(rc4, possibleUserPassword, rc4KeyBytes);
+ } else {
+ throw new EncryptionUnsupportedByProductException(
+ "Unsupported revision: " + revision);
+ }
+
+ // Step 3: The result of step 2 purports to be the user password.
+ // Authenticate this user password using Algorithm 3.6. If it is
+ // correct, the password supplied is the correct owner password.
+ return checkUserPassword(
+ possibleUserPassword, firstDocIdValue, keyBitLength,
+ revision, oValue, uValue, pValue, encryptMetadata);
+
+ }
+
+ /**
+ * Establish the key to be used for the generation and validation
+ * of the user password via the O entry. Corresponds to steps 1-4 in
+ * Algorithm 3.3 of the PDF Reference version 1.7.
+ * @param ownerPassword the owner password
+ * @param keyBitLength the length of the key in bits
+ * @param revision the security handler revision
+ * @return the key bytes to use for generation/validation of the O entry
+ * @throws GeneralSecurityException if there's a problem wranling ciphers
+ */
+ private byte[] getInitialOwnerPasswordKeyBytes(
+ byte[] ownerPassword, int keyBitLength, int revision)
+ throws GeneralSecurityException {
+
+ final MessageDigest md5 = createMD5Digest();
+
+ // Step 1: Pad or truncate the owner password string as described in
+ // step 1 of Algorithm 3.2. If there is no owner password, use the user
+ // password instead. (See implementation note 27 in Appendix H.)
+ // Step 2: Initialize the MD5 hash function and pass the result of step 1 as
+ // input to this function.
+ md5.update(padPassword(ownerPassword));
+
+ // Step 3.(Revision 3 or greater) Do the following 50 times: Take the
+ // output from the previous MD5 hash and pass it as input into a new MD5
+ // hash
+ final byte[] hash = md5.digest();
+ if (revision >= 3) {
+ for (int i = 0; i < 50; ++i) {
+ md5.update(hash);
+ digestTo(md5, hash);
+ }
+ }
+
+ // Step 4: Create an RC4 encryption key using the first n bytes of
+ // the output from the final MD5 hash, where n is always 5 for revision
+ // 2 but, for revision 3 or greater, depends on the value of the
+ // encryption dictionary’s Length entry
+ final byte[] rc4KeyBytes = new byte[keyBitLength / 8];
+ System.arraycopy(hash, 0, rc4KeyBytes, 0, rc4KeyBytes.length);
+ return rc4KeyBytes;
+ }
+
+ /**
+ * Check to see whether a provided user password is correct with respect
+ * to an Encrypt dict configuration. Corresponds to algorithm 3.6 of
+ * the PDF Reference version 1.7
+ * @param userPassword the user password to test; may be null or empty
+ * @param firstDocIdValue the byte stream from the first element of the
+ * value of the ID entry in the trailer dictionary
+ * @param keyBitLength the length of the key in bits
+ * @param revision the security handler revision
+ * @param oValue the O value from the Encrypt dictionary
+ * @param uValue the U value from the Encrypt dictionary
+ * @param pValue the P value from the Encrypt dictionary
+ * @param encryptMetadata the EncryptMetadata entry from the Encrypt dictionary
+ * (or false if not present or revision <= 3)
+ * @return the general/user encryption key if the user password is correct,
+ * or null if incorrect
+ * @throws GeneralSecurityException if there's a problem with
+ * cipher or digest usage; unexpected
+ * @throws EncryptionUnsupportedByProductException if PDFRenderer doesn't
+ * support the security handler revision
+ * @throws PDFParseException if the document is improperly constructed
+ */
+ private byte[] checkUserPassword(
+ byte[] userPassword, byte[] firstDocIdValue, int keyBitLength,
+ int revision, byte[] oValue, byte[] uValue, int pValue,
+ boolean encryptMetadata)
+ throws
+ GeneralSecurityException,
+ EncryptionUnsupportedByProductException,
+ PDFParseException {
+
+ // Algorithm 3.6: Authenticating the user password
+
+ // Step 1: Perform all but the last step of Algorithm 3.4 (Revision 2)
+ // or Algorithm 3.5 (Revision 3 or greater) using the supplied password
+ // string
+ //
+ // I.e., figure out what the general key would be with the
+ // given password
+ // Algorithm 3.4/5,Step1:
+ // Determine general key based on user password, as per Algorithm 3.2
+ final byte[] generalKey = calculateGeneralEncryptionKey(
+ userPassword, firstDocIdValue, keyBitLength,
+ revision, oValue, pValue, encryptMetadata);
+ // Algorithm 3.4/5,RemainingSteps:
+ final byte[] calculatedUValue =
+ calculateUValue(generalKey, firstDocIdValue, revision);
+
+ // Step 2: If the result of step 1 is equal to the value of the
+ // encryption dictionary’s U entry (comparing on the first 16 bytes in
+ // the case of Revision 3 or greater), the password supplied is the
+ // correct user password. The key obtained in step 1 (that is, in the
+ // first step of Algorithm 3.4 or 3.5) can be used to decrypt the
+ // document using Algorithm 3.1 on page 119.
+ assert calculatedUValue.length == 32;
+ if (uValue.length != calculatedUValue.length) {
+ throw new PDFParseException("Improper U entry length; " +
+ "expected 32, is " + uValue.length);
+ }
+ // Only the first 16 bytes are significant if using revision > 2
+ final int numSignificantBytes = revision == 2 ? 32 : 16;
+ for (int i = 0; i < numSignificantBytes; ++i) {
+ if (uValue[i] != calculatedUValue[i]) {
+ return null;
+ }
+ }
+ return generalKey;
+ }
+
+
+ /**
+ * Determine what the general encryption key is, given a configuration. This
+ * corresponds to Algorithm 3.2 of PDF Reference version 1.7.
+ *
+ * @param userPassword the desired user password; may be null or empty
+ * @param firstDocIdValue the byte stream from the first element of the
+ * value of the ID entry in the trailer dictionary
+ * @param keyBitLength the length of the key in bits
+ * @param revision the security handler revision
+ * @param oValue the O value from the Encrypt dictionary
+ * @param pValue the P value from the Encrypt dictionary
+ * @param encryptMetadata the EncryptMetadata entry from the Encrypt
+ * dictionary (or false if not present or revision <= 3)
+ * @return the general encryption key
+ * @throws GeneralSecurityException if an error occurs when obtaining
+ * and operating ciphers/digests
+ */
+ private byte[] calculateGeneralEncryptionKey(
+ byte[] userPassword, byte[] firstDocIdValue, int keyBitLength,
+ int revision, byte[] oValue, int pValue, boolean encryptMetadata)
+ throws GeneralSecurityException {
+
+ // Algorithm 3.2: Computing an encryption key
+
+ // Step 1: Pad or truncate the password string to exactly 32 bytes...
+ final byte[] paddedPassword = padPassword(userPassword);
+
+ // Step 2: Initialize the MD5 hash function and pass the result of step
+ // 1 as input to this function.
+ MessageDigest md5 = createMD5Digest();
+ md5.reset();
+ md5.update(paddedPassword);
+
+ // Step 3: Pass the value of the encryption dictionary’s O entry to the
+ // MD5 hash function. (Algorithm 3.3 shows how the O value is computed.)
+ md5.update(oValue);
+
+ // Step 4: Treat the value of the P entry as an unsigned 4-byte integer
+ // and pass these bytes to the MD5 hash function, low-order byte first
+ md5.update((byte) (pValue & 0xFF));
+ md5.update((byte) ((pValue >> 8) & 0xFF));
+ md5.update((byte) ((pValue >> 16) & 0xFF));
+ md5.update((byte) (pValue >> 24));
+
+ // Step 5: Pass the first element of the file’s file identifier array
+ // (the value of the ID entry in the document’s trailer dictionary; see
+ // Table 3.13 on page 97) to the MD5 hash function. (See implementation
+ // note 26 in Appendix H.)
+ if (firstDocIdValue != null) {
+ md5.update(firstDocIdValue);
+ }
+
+ // Step 6: (Revision 4 or greater) If document metadata is not being
+ // encrypted, pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash
+ // function
+ if (revision >= 4 && !encryptMetadata) {
+ for (int i = 0; i < 4; ++i) {
+ md5.update((byte) 0xFF);
+ }
+ }
+
+ // Step 7: finish the hash
+ byte[] hash = md5.digest();
+
+ final int keyLen = revision == 2 ? 5 : (keyBitLength / 8);
+ final byte[] key = new byte[keyLen];
+
+ // Step 8: (Revision 3 or greater) Do the following 50 times: Take the
+ // output from the previous MD5 hash and pass the first n bytes of the
+ // output as input into a new MD5 hash, where n is the number of bytes
+ // of the encryption key as defined by the value of the encryption
+ // dictionary’s Length entry
+ if (revision >= 3) {
+ for (int i = 0; i < 50; ++i) {
+ md5.update(hash, 0, key.length);
+ digestTo(md5, hash);
+ }
+ }
+
+ // Set the encryption key to the first n bytes of the output from the
+ // final MD5 hash, where n is always 5 for revision 2 but, for revision
+ // 3 or greater, depends on the value of the encryption dictionary’s
+ // Length entry.
+ System.arraycopy(hash, 0, key, 0, key.length);
+ return key;
+ }
+
+ /**
+ * Pad a password as per step 1 of Algorithm 3.2 of the PDF Reference
+ * version 1.7
+ * @param password the password, may be null or empty
+ * @return the padded password, always 32 bytes long
+ */
+ private byte[] padPassword(byte[] password) {
+
+ if (password == null) {
+ password = new byte[0];
+ }
+
+ // Step 1: Pad or truncate the password string to exactly 32 bytes. If
+ // the password string is more than 32 bytes long, use only its first 32
+ // bytes; if it is less than 32 bytes long, pad it by appending the
+ // required number of additional bytes from the beginning of the
+ // following padding string:
+ // < 28 BF 4E 5E 4E 75 8A 41 64 00 4E 56 FF FA 01 08
+ // 2E 2E 00 B6 D0 68 3E 80 2F 0C A9 FE 64 53 69 7A >
+ // That is, if the password string is n bytes long, append the first 32
+ // − n bytes of the padding string to the end of the password string. If
+ // the password string is empty (zero-length), meaning there is no user
+ // password, substitute the entire padding string in its place.
+
+ byte[] padded = new byte[32];
+ // limit password to 32 bytes
+ final int numContributingPasswordBytes =
+ password.length > padded.length ?
+ padded.length : password.length;
+ System.arraycopy(password, 0, padded, 0, numContributingPasswordBytes);
+ // Copy padding
+ if (password.length < padded.length) {
+ System.arraycopy(PW_PADDING, 0, padded, password.length,
+ padded.length - password.length);
+ }
+ return padded;
+ }
+
+ /**
+ * Encrypt some bytes
+ *
+ * @param cipher the cipher
+ * @param input the plaintext
+ * @return the crypt text
+ * @throws BadPaddingException if there's bad padding
+ * @throws IllegalBlockSizeException if the block size is bad
+ */
+ private byte[] crypt(Cipher cipher, byte[] input)
+ throws IllegalBlockSizeException, BadPaddingException {
+ return cipher.doFinal(input);
+ }
+
+ /**
+ * Initialise a cipher for encryption
+ *
+ * @param cipher the cipher
+ * @param key the encryption key
+ * @throws InvalidKeyException if the key is invalid for the cipher
+ */
+ private void initEncryption(Cipher cipher, SecretKey key)
+ throws InvalidKeyException {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ }
+
+ /**
+ * Shuffle some input using a series of RC4 encryptions with slight
+ * mutations of an given key per iteration. Shuffling happens in place.
+ * Refer to the documentation of the algorithm steps where this is called.
+ *
+ * @param shuffle the bytes to be shuffled
+ * @param key the original key
+ * @param rc4 the cipher to use
+ * @throws GeneralSecurityException if there's a problem with cipher
+ * operation
+ */
+ private void rc4shuffle(byte[] shuffle, byte[] key, Cipher rc4)
+ throws GeneralSecurityException {
+
+ final byte[] shuffleKey = new byte[key.length];
+ for (int i = 1; i <= 19; ++i) {
+ for (int j = 0; j < shuffleKey.length; ++j) {
+ shuffleKey[j] = (byte) (key[j] ^ i);
+ }
+ initEncryption(rc4, createRC4Key(shuffleKey));
+ cryptInPlace(rc4, shuffle);
+ }
+ }
+
+ /**
+ * Reverse the {@link #rc4shuffle} operation, and the operation
+ * that invariable preceeds it, thereby obtaining an original message
+ * @param rc4 the RC4 cipher to use
+ * @param shuffle the bytes in which shuffling will take place; unshuffling
+ * happens in place
+ * @param key the encryption key
+ * @throws GeneralSecurityException if there's a problem with cipher
+ * operation
+ */
+ private void rc4unshuffle(Cipher rc4, byte[] shuffle, byte[] key)
+ throws GeneralSecurityException {
+
+ // there's an extra unshuffle at the end with the original key -
+ // this is why we end with i == 0, where the shuffle key will be the key
+ final byte[] shuffleKeyBytes = new byte[key.length];
+ for (int i = 19; i >= 0; --i) {
+ for (int j = 0; j < shuffleKeyBytes.length; ++j) {
+ shuffleKeyBytes[j] = (byte) (key[j] ^ i);
+ }
+ initDecryption(rc4, createRC4Key(shuffleKeyBytes));
+ cryptInPlace(rc4, shuffle);
+ }
+ }
+
+ /**
+ * Encrypt/decrypt something in place
+ * @param rc4 the cipher to use; must be a stream cipher producing
+ * identical output length to input (e.g., RC4)
+ * @param buffer the buffer to read input from and write output to
+ * @throws IllegalBlockSizeException if an inappropriate cipher is used
+ * @throws ShortBufferException if an inappropriate cipher is used
+ * @throws BadPaddingException if an inappropriate cipher is used
+ */
+ private void cryptInPlace(Cipher rc4, byte[] buffer)
+ throws IllegalBlockSizeException, ShortBufferException, BadPaddingException {
+ rc4.doFinal(buffer, 0, buffer.length, buffer);
+ }
+
+ /**
+ * Setup a cipher for decryption
+ * @param cipher the cipher
+ * @param aKey the cipher key
+ * @throws InvalidKeyException if the key is of an unacceptable size or
+ * doesn't belong to the cipher
+ */
+ private void initDecryption(Cipher cipher, Key aKey)
+ throws InvalidKeyException {
+ cipher.init(Cipher.DECRYPT_MODE, aKey);
+ }
+
+ /**
+ * Create a new RC4 cipher. Should always be available for supported
+ * platforms.
+ * @return the cipher
+ * @throws NoSuchAlgorithmException if the RC4 cipher is unavailable
+ * @throws NoSuchPaddingException should not happen, as no padding
+ * is specified
+ */
+ private Cipher createRC4Cipher()
+ throws NoSuchAlgorithmException, NoSuchPaddingException {
+ return Cipher.getInstance(CIPHER_RC4);
+ }
+
+ /**
+ * Create a new AES cipher. Should always be available for supported
+ * platforms.
+ * @return the new cipher
+ * @throws NoSuchAlgorithmException if the AES cipher is unavailable
+ * @throws NoSuchPaddingException if the required padding is unavailable
+ */
+ private Cipher createAESCipher()
+ throws NoSuchAlgorithmException, NoSuchPaddingException {
+ return Cipher.getInstance(CIPHER_AES);
+ }
+
+ /**
+ * Create an MD5 digest. Should always be available for supported
+ * platforms.
+ * @return the MD5 digest
+ * @throws NoSuchAlgorithmException if the digest is not available
+ */
+ private MessageDigest createMD5Digest()
+ throws NoSuchAlgorithmException {
+ return MessageDigest.getInstance("MD5");
+ }
+
+ /**
+ * Create an RC4 key
+ *
+ * @param keyBytes the bytes for the key
+ * @return the key
+ */
+ private SecretKeySpec createRC4Key(byte[] keyBytes) {
+ return new SecretKeySpec(keyBytes, KEY_RC4);
+ }
+
+ /**
+ * Hash into an existing byte array
+ * @param md5 the MD5 digest
+ * @param hash the hash destination
+ * @throws GeneralSecurityException if there's a problem hashing; e.g.,
+ * if the buffer is too small
+ */
+ private void digestTo(MessageDigest md5, byte[] hash)
+ throws GeneralSecurityException {
+ md5.digest(hash, 0, hash.length);
+ }
+
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/decrypt/UnsupportedEncryptionException.java b/PdfView/src/main/java/com/sun/pdfview/decrypt/UnsupportedEncryptionException.java
new file mode 100644
index 0000000..d9bae05
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/decrypt/UnsupportedEncryptionException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 Pirion Systems Pty Ltd, 139 Warry St,
+ * Fortitude Valley, Queensland, Australia
+ *
+ * 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.decrypt;
+
+/**
+ * Identifies that the specified encryption mechanism is not
+ * supported by this product or platform.
+ *
+ * @see EncryptionUnsupportedByPlatformException
+ * @see EncryptionUnsupportedByProductException
+ * @author Luke Kirby
+ */
+public abstract class UnsupportedEncryptionException extends Exception {
+
+ protected UnsupportedEncryptionException(String message) {
+ super(message);
+ }
+
+ protected UnsupportedEncryptionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/BuiltinFont.java b/PdfView/src/main/java/com/sun/pdfview/font/BuiltinFont.java
new file mode 100644
index 0000000..5f90afe
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/BuiltinFont.java
@@ -0,0 +1,243 @@
+/*
+ * $Id: BuiltinFont.java,v 1.4 2009/03/15 20:47:38 tomoke 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.font;
+
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import com.sun.pdfview.PDFObject;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * This class represents the 14 built-in fonts. It reads these fonts
+ * from files in the "res" directory, as specified in
+ * BaseNames.properties.
+ *
+ * @author Ferenc Hechler (ferenc@hechler.de)
+ * @author Joerg Jahnke (joergjahnke@users.sourceforge.net)
+ */
+public class BuiltinFont extends Type1Font {
+
+ /** the properties file */
+ private static Properties props;
+ /**
+ * Android resources
+ */
+ private static Resources resources;
+ /**
+ * package of the resources
+ */
+ private static String pkg;
+ /** the names of the 14 base fonts */
+ private static final String[] baseFonts = {
+ "Courier", "Courier-Bold", "Courier-BoldOblique", "Courier-Oblique",
+ "Helvetica", "Helvetica-Bold", "Helvetica-BoldOblique",
+ "Helvetica-Oblique", "Times-Roman", "Times-Bold", "Times-BoldItalic",
+ "Times-Italic", "Symbol", "ZapfDingbats"
+ };
+ /** fonts others (e.g. Acrobad PDFWriter 3.02 for Windows) assume
+ * are there, even though they're not in the spec. Grrr...
+ *
+ * the format is
+ */
+ private static final String[] mappedFonts = {
+ // map arial to helvetica
+ "Arial", "Helvetica",
+ "Arial,Bold", "Helvetica-Bold",
+ "Arial,BoldItalic", "Helvetica-BoldOblique",
+ "Arial,Italic", "Helvetica-Oblique",
+ // map TimesNewRoman to Times
+ "TimesNewRoman", "Times-Roman",
+ "TimesNewRoman,Bold", "Times-Bold",
+ "TimesNewRoman,BoldItalic", "Times-BoldItalic",
+ "TimesNewRoman,Italic", "Times-Italic",};
+
+ /**
+ * Create a new Builtin object based on the name of a built-in font
+ *
+ * This must be the name of one of the 14 built-in fonts!
+ *
+ * @param baseFont the name of the font, from the PDF file
+ * @param fontObj the object containing font information
+ */
+ public BuiltinFont(String baseFont, PDFObject fontObj) throws IOException {
+ super(baseFont, fontObj, null);
+
+ parseFont(baseFont);
+ }
+
+ /**
+ * create a new BuiltingFont object based on a description of the
+ * font from the PDF file. Parse the description for key information
+ * and use that to generate an appropriate font.
+ */
+ public BuiltinFont(String baseFont, PDFObject fontObj,
+ PDFFontDescriptor descriptor)
+ throws IOException {
+ super(baseFont, fontObj, descriptor);
+
+ String fontName = descriptor.getFontName();
+
+ // check if it's one of the 14 base fonts
+ for (int i = 0; i < baseFonts.length; i++) {
+ if (fontName.equalsIgnoreCase(baseFonts[i])) {
+ parseFont(fontName);
+ return;
+ }
+ }
+
+ // check if it's a mapped font
+ for (int i = 0; i < mappedFonts.length; i += 2) {
+ if (fontName.equalsIgnoreCase(mappedFonts[i])) {
+ parseFont(mappedFonts[i + 1]);
+ return;
+ }
+ }
+
+ int flags = descriptor.getFlags();
+ int style = ((flags & PDFFontDescriptor.FORCEBOLD) != 0) ? Typeface.BOLD : Typeface.NORMAL;
+
+ if (fontName.indexOf("Bold") > 0) {
+ style |= Typeface.BOLD;
+ }
+ if ((descriptor.getItalicAngle() != 0) ||
+ ((flags & PDFFontDescriptor.NONSYMBOLIC) != 0)) {
+ style |= Typeface.ITALIC;
+ }
+
+ String name = null;
+
+ if ((flags & PDFFontDescriptor.FIXED_PITCH) != 0) { // fixed width
+ if (((style & Typeface.BOLD) > 0) && ((style & Typeface.ITALIC) > 0)) {
+ name = "Courier-BoldOblique";
+ } else if ((style & Typeface.BOLD) > 0) {
+ name = "Courier-Bold";
+ } else if ((style & Typeface.ITALIC) > 0) {
+ name = "Courier-Oblique";
+ } else {
+ name = "Courier";
+ }
+ } else if ((flags & PDFFontDescriptor.SERIF) != 0) { // serif font
+ if (((style & Typeface.BOLD) > 0) && ((style & Typeface.ITALIC) > 0)) {
+ name = "Times-BoldItalic";
+ } else if ((style & Typeface.BOLD) > 0) {
+ name = "Times-Bold";
+ } else if ((style & Typeface.ITALIC) > 0) {
+ name = "Times-Italic";
+ } else {
+ name = "Times-Roman";
+ }
+ } else {
+ if (((style & Typeface.BOLD) > 0) && ((style & Typeface.ITALIC) > 0)) {
+ name = "Helvetica-BoldOblique";
+ } else if ((style & Typeface.BOLD) > 0) {
+ name = "Helvetica-Bold";
+ } else if ((style & Typeface.ITALIC) > 0) {
+ name = "Helvetica-Oblique";
+ } else {
+ name = "Helvetica";
+ }
+ }
+
+ parseFont(name);
+ }
+
+ /**
+ * Set the resources instance which we can use to access further raw resources
+ *
+ * @param res context's resources instance
+ * @param p package where to find the resources
+ */
+ public static void setResources(final Resources res, final String p) {
+ resources = res;
+ pkg = p;
+ }
+
+ /**
+ * Parse a font given only the name of a builtin font
+ */
+ private void parseFont(String baseFont) throws IOException {
+ // load the base fonts properties files, if it isn't already loaded
+ if (props == null) {
+ props = new Properties();
+ try {
+ props.load(BuiltinFont.class.getResourceAsStream("res/BaseFonts.properties"));
+ } catch (NullPointerException e) {
+ props.load(resources.openRawResource(resources.getIdentifier("basefonts", "raw", pkg)));
+ }
+ }
+
+ // make sure we're a known font
+ if (!props.containsKey(baseFont + ".file")) {
+ throw new IllegalArgumentException("Unknown Base Font: " + baseFont);
+ }
+
+ // get the font information from the properties file
+ String file = props.getProperty(baseFont + ".file");
+
+ // the size of the file
+ int length = Integer.parseInt(props.getProperty(baseFont + ".length"));
+ // the size of the unencrypted portion
+ int length1 = 0;
+ // the size of the encrypted portion
+ int length2 = 0;
+
+ // read the data from the file
+ byte[] data = new byte[length];
+ InputStream fontStream = BuiltinFont.class.getResourceAsStream("res/" + file);
+ if (fontStream == null) {
+ fontStream = resources.openRawResource(resources.getIdentifier(file.substring(0, file.indexOf('.')), "raw", pkg));
+ }
+ int cur = 0;
+ while (cur < length) {
+ cur += fontStream.read(data, cur, length - cur);
+ }
+ fontStream.close();
+
+ // are we a pfb file?
+ if ((data[0] & 0xff) == 0x80) {
+ // read lengths from the file
+ length1 = (data[2] & 0xff);
+ length1 |= (data[3] & 0xff) << 8;
+ length1 |= (data[4] & 0xff) << 16;
+ length1 |= (data[5] & 0xff) << 24;
+ length1 += 6;
+
+ length2 = (data[length1 + 2] & 0xff);
+ length2 |= (data[length1 + 3] & 0xff) << 8;
+ length2 |= (data[length1 + 4] & 0xff) << 16;
+ length2 |= (data[length1 + 5] & 0xff) << 24;
+ length1 += 6;
+ } else {
+ // get the values from the properties file
+ length1 = Integer.parseInt(props.getProperty(baseFont + ".length1"));
+
+ if (props.containsKey(baseFont + ".length2")) {
+ length2 = Integer.parseInt(props.getProperty(baseFont + ".lenth2"));
+ } else {
+ length2 = length - length1;
+ }
+ }
+
+ parseFont(data, length1, length2);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/CIDFontType2.java b/PdfView/src/main/java/com/sun/pdfview/font/CIDFontType2.java
new file mode 100644
index 0000000..f91cb9d
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/CIDFontType2.java
@@ -0,0 +1,274 @@
+/*
+ * $Id: CIDFontType2.java,v 1.5 2009/03/15 20:47:38 tomoke 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.font;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import android.graphics.Path;
+
+import com.sun.pdfview.PDFObject;
+
+/**
+ * a font object derived from a CID font.
+ *
+ * @author Jonathan Kaplan
+ */
+public class CIDFontType2 extends TTFFont {
+
+ /**
+ * The width of each glyph from the DW and W arrays
+ */
+ private Map widths = null;
+ /**
+ * The vertical width of each glyph from the DW2 and W2 arrays
+ */
+ private Map widthsVertical = null;
+
+ /*
+ * the default width
+ */
+ private int defaultWidth = 1000;
+ /*
+ * the default vertical width
+ */
+ private int defaultWidthVertical = 1000;
+ /** the CIDtoGID map, if any */
+ private ByteBuffer cidToGidMap;
+
+ /**
+ * create a new CIDFontType2 object based on the name of a built-in font
+ * and the font descriptor
+ * @param baseName the name of the font, from the PDF file
+ * @param fontObj a dictionary that contains the DW (defaultWidth) and
+ * W (width) parameters
+ * @param descriptor a descriptor for the font
+ */
+ public CIDFontType2(String baseName, PDFObject fontObj,
+ PDFFontDescriptor descriptor) throws IOException {
+ super(baseName, fontObj, descriptor);
+
+ parseWidths(fontObj);
+
+ // read the CIDSystemInfo dictionary (required)
+ PDFObject systemInfoObj = fontObj.getDictRef("CIDSystemInfo");
+ // read the cid to gid map (optional)
+ PDFObject mapObj = fontObj.getDictRef("CIDToGIDMap");
+
+
+ // only read the map if it is a stream (if it is a name, it
+ // is "Identity" and can be ignored
+ if (mapObj != null && (mapObj.getType() == PDFObject.STREAM)) {
+ cidToGidMap = mapObj.getStreamBuffer();
+ }
+ }
+
+ /** Parse the Widths array and DW object */
+ private void parseWidths(PDFObject fontObj)
+ throws IOException {
+ // read the default width (otpional)
+ PDFObject defaultWidthObj = fontObj.getDictRef("DW");
+ if (defaultWidthObj != null) {
+ defaultWidth = defaultWidthObj.getIntValue();
+ }
+
+ int entryIdx = 0;
+ int first = 0;
+ int last = 0;
+ PDFObject[] widthArray;
+
+ // read the widths table
+ PDFObject widthObj = fontObj.getDictRef("W");
+ if (widthObj != null) {
+
+ // initialize the widths array
+ widths = new HashMap();
+
+ // parse the width array
+ widthArray = widthObj.getArray();
+
+ /* an entry can be in one of two forms:
+ * or
+ * [ array of values ]
+ * we use the entryIdx to differentitate between them
+ */
+ for (int i = 0; i < widthArray.length; i++) {
+ if (entryIdx == 0) {
+ // first value in an entry. Just store it
+ first = widthArray[i].getIntValue();
+ } else if (entryIdx == 1) {
+ // second value -- is it an int or array?
+ if (widthArray[i].getType() == PDFObject.ARRAY) {
+ // add all the entries in the array to the width array
+ PDFObject[] entries = widthArray[i].getArray();
+ for (int c = 0; c < entries.length; c++) {
+ Character key = new Character((char) (c + first));
+
+ // value is width / default width
+ float value = entries[c].getIntValue();
+ widths.put(key, new Float(value));
+ }
+ // all done
+ entryIdx = -1;
+ } else {
+ last = widthArray[i].getIntValue();
+ }
+ } else {
+ // third value. Set a range
+ int value = widthArray[i].getIntValue();
+
+ // set the range
+ for (int c = first; c <= last; c++) {
+ widths.put(new Character((char) c), new Float(value));
+ }
+
+ // all done
+ entryIdx = -1;
+ }
+
+ entryIdx++;
+ }
+ }
+
+ // read the optional vertical default width
+ defaultWidthObj = fontObj.getDictRef("DW2");
+ if (defaultWidthObj != null) {
+ defaultWidthVertical = defaultWidthObj.getIntValue();
+ }
+
+ // read the vertical widths table
+ widthObj = fontObj.getDictRef("W2");
+ if (widthObj != null) {
+
+ // initialize the widths array
+ widthsVertical = new HashMap();
+
+ // parse the width2 array
+ widthArray = widthObj.getArray();
+
+ /* an entry can be in one of two forms:
+ * or
+ * [ array of values ]
+ * we use the entryIdx to differentitate between them
+ */
+ entryIdx = 0;
+ first = 0;
+ last = 0;
+
+ for (int i = 0; i < widthArray.length; i++) {
+ if (entryIdx == 0) {
+ // first value in an entry. Just store it
+ first = widthArray[i].getIntValue();
+ } else if (entryIdx == 1) {
+ // second value -- is it an int or array?
+ if (widthArray[i].getType() == PDFObject.ARRAY) {
+ // add all the entries in the array to the width array
+ PDFObject[] entries = widthArray[i].getArray();
+ for (int c = 0; c < entries.length; c++) {
+ Character key = new Character((char) (c + first));
+
+ // value is width / default width
+ float value = entries[c].getIntValue();
+ widthsVertical.put(key, new Float(value));
+ }
+ // all done
+ entryIdx = -1;
+ } else {
+ last = widthArray[i].getIntValue();
+ }
+ } else {
+ // third value. Set a range
+ int value = widthArray[i].getIntValue();
+
+ // set the range
+ for (int c = first; c <= last; c++) {
+ widthsVertical.put(new Character((char) c), new Float(value));
+ }
+
+ // all done
+ entryIdx = -1;
+ }
+
+ entryIdx++;
+ }
+ }
+ }
+
+ /** Get the default width in text space */
+ @Override
+ public int getDefaultWidth() {
+ return defaultWidth;
+ }
+
+ /** Get the width of a given character */
+ @Override
+ public float getWidth(char code, String name) {
+ if (widths == null) {
+ return 1f;
+ }
+ Float w = widths.get(new Character(code));
+ if (w == null) {
+ return 1f;
+ }
+
+ return w.floatValue() / getDefaultWidth();
+ }
+
+ /** Get the default vertical width in text space */
+ public int getDefaultWidthVertical() {
+ return defaultWidthVertical;
+ }
+
+ /** Get the vertical width of a given character */
+ public float getWidthVertical(char code, String name) {
+ if (widthsVertical == null) {
+ return 1f;
+ }
+ Float w = widthsVertical.get(new Character(code));
+ if (w == null) {
+ return 1f;
+ }
+
+ return w.floatValue() / getDefaultWidth();
+ }
+
+ /**
+ * Get the outline of a character given the character code. We
+ * interpose here in order to avoid using the CMap of the font in
+ * a CID mapped font.
+ */
+ @Override
+ protected synchronized Path getOutline(char src, float width) {
+ int glyphId = (int) (src & 0xffff);
+
+ // check if there is a cidToGidMap
+ if (cidToGidMap != null) {
+ // read the map
+ glyphId = cidToGidMap.getChar(glyphId * 2);
+ }
+
+ // call getOutline on the glyphId
+ return getOutline(glyphId, width);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/FlPoint.java b/PdfView/src/main/java/com/sun/pdfview/font/FlPoint.java
new file mode 100644
index 0000000..6ee461b
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/FlPoint.java
@@ -0,0 +1,50 @@
+/*
+ * $Id: FlPoint.java,v 1.2 2007/12/20 18:33:31 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.font;
+
+/**
+ * A floating-point Point, with public fields. Also contains a flag
+ * for "open" to indicate that the path this point is a member of has
+ * or hasn't been closed.
+ *
+ * @author Mike Wessler
+ */
+public class FlPoint {
+ /** x coordinate of the point */
+ public float x= 0;
+
+ /** y coordinate of the point */
+ public float y= 0;
+
+ /**
+ * whether the path this point is a part of is open or closed.
+ * used in Type1CFont.java.
+ */
+ public boolean open= false;
+
+ /** reset the values to (0,0) and closed */
+ public final void reset() {
+ x= 0;
+ y= 0;
+ open= false;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/FontSupport.java b/PdfView/src/main/java/com/sun/pdfview/font/FontSupport.java
new file mode 100644
index 0000000..39914bd
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/FontSupport.java
@@ -0,0 +1,397 @@
+/*
+ * $Id: FontSupport.java,v 1.3 2009/03/15 20:47:38 tomoke 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.font;
+
+/**
+ * some constants and utility functions for font support.
+ * @author Mike Wessler
+ */
+public class FontSupport {
+
+ /**
+ * names for glyphs in the standard Adobe order. This is the ordering
+ * of the glyphs in a font, not the mapping of character number to
+ * character.
+ */
+ public static final String stdNames[] = {
+ ".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar",
+ "percent", "ampersand", "quoteright", "parenleft", "parenright",
+ "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero",
+ "one", "two", "three", "four", "five", "six", "seven", "eight",
+ "nine", "colon", "semicolon", "less", "equal", "greater", "question",
+ "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+ "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+ "bracketleft", "backslash", "bracketright", "asciicircum",
+ "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h",
+ "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
+ "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde",
+ "exclamdown", "cent", "sterling", "fraction", "yen", "florin",
+ "section", "currency", "quotesingle", "quotedblleft", "guillemotleft",
+ "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger",
+ "daggerdbl", "periodcentered", "paragraph", "bullet",
+ "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright",
+ "ellipsis", "perthousand", "questiondown", "grave", "acute",
+ "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis",
+ "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE",
+ "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae",
+ "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior",
+ "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
+ "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
+ "threequarters", "twosuperior", "registered", "minus", "eth",
+ "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex",
+ "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute",
+ "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex",
+ "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis",
+ "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis",
+ "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex",
+ "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
+ "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex",
+ "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis",
+ "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis",
+ "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall",
+ "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior",
+ "ampersandsmall", "Acutesmall", "parenleftsuperior",
+ "parenrightsuperior", "twodotenleader", "onedotenleader",
+ "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle",
+ "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle",
+ "eightoldstyle", "nineoldstyle", "commasuperior",
+ "threequartersemdash", "periodsuperior", "questionsmall", "asuperior",
+ "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
+ "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior",
+ "ssuperior", "tsuperior", "ff", "ffi", "ffl", "parenleftinferior",
+ "parenrightinferior", "Circumflexsmall", "hyphensuperior",
+ "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall",
+ "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall",
+ "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall",
+ "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall",
+ "colonmonetary", "onefitted", "rupiah", "Tildesmall",
+ "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall",
+ "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall",
+ "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior",
+ "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall",
+ "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
+ "onethird", "twothirds", "zerosuperior", "foursuperior",
+ "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior",
+ "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
+ "threeinferior", "fourinferior", "fiveinferior", "sixinferior",
+ "seveninferior", "eightinferior", "nineinferior", "centinferior",
+ "dollarinferior", "periodinferior", "commainferior", "Agravesmall",
+ "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall",
+ "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
+ "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
+ "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall",
+ "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall",
+ "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall",
+ "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall",
+ "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000", "001.001",
+ "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium",
+ "Regular", "Roman", "Semibold"
+ };
+
+ /**
+ * characters for glyphs in the standard order. These are string "values"
+ * to go with the names in stdNames. Not all glyphs have been translated
+ * to their unicode values. In many cases, the name of the glyph has
+ * been appended to an ASCII approximation of the glyph. Strings longer
+ * than 3 characters have this characteristic. To get the character,
+ * use the string if it contains 3 or fewer characters; otherwise,
+ * grab the first character off the string and use that.
+ */
+ static final String stdValues[] = {
+ "", " ", "!", "\"", "#", "$",
+ "%", "&", "'", "(", ")",
+ "*", "+", ",", "-", ".", "/", "0",
+ "1", "2", "3", "4", "5", "6", "7", "8",
+ "9", ":", ";", "<", "=", ">", "?",
+ "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+ "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+ "[", "\\", "]", "^",
+ "_", "`", "a", "b", "c", "d", "e", "f", "g", "h",
+ "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
+ "w", "x", "y", "z", "{", "|", "}", "~",
+ "\u00a1", "\u00a2", "\u00a3", "/fraction", "\u00a5", "Fflorin",
+ "\u00a7", "\u00a4", "\u00b4quotesingle", "\u201c", "?guillemotleft",
+ "\u2039", "\u203a", "fi", "fl", "--", "\u2020",
+ "\u2021", "\u00b7", "\u00b6", "\u2022",
+ "'quotesinglbase", "\"quotedblbase", "\"quotedblright", "?guillemotright",
+ "...ellipsis", "%perthousand", "?questiondown", "`grave", "'acute",
+ "^circumflex", "~tilde", "-macron", "?breve", "?dotaccent", "?dieresis",
+ "oring", "ccedilla", ":hungarumlaut", "?ogonek", ",caron", "---emdash", "AE",
+ "aordfeminine", "LLslash", "OOslash", "OE", "oordmasculine", "ae",
+ "idotlessi", "llslash", "ooslash", "oe", "Bgermandbls", "1onesuperior",
+ "~logicalnot", "?mu", "(TM)trademark", "?Eth", "1/2", "+/-",
+ "?Thorn", "1/4", "/divide", "|brokenbar", "*degree", "?thorn",
+ "3/4", "2twosuperior", "(R)", "-minus", "?eth",
+ "*multiply", "3threesuperior", "(C)", "AAacute", "AAcircumflex",
+ "AAdieresis", "AAgrave", "AAring", "AAtilde", "CCcedilla", "EEacute",
+ "EEcircumflex", "EEdieresis", "EEgrave", "IIacute", "IIcircumflex",
+ "IIdieresis", "IIgrave", "NNtilde", "OOacute", "OOcircumflex", "OOdieresis",
+ "OOgrave", "OOtilde", "SScaron", "UUacute", "UUcircumflex", "UUdieresis",
+ "UUgrave", "YYacute", "YYdieresis", "ZZcaron", "aaacute", "aacircumflex",
+ "aadieresis", "aagrave", "aaring", "aatilde", "cccedilla", "eeacute",
+ "eecircumflex", "eedieresis", "eegrave", "iiacute", "iicircumflex",
+ "iidieresis", "iigrave", "nntilde", "ooacute", "oocircumflex", "oodieresis",
+ "oograve", "ootilde", "sscaron", "uuacute", "uucircumflex", "uudieresis",
+ "uugrave", "yyacute", "yydieresis", "zzcaron", "!exclamsmall",
+ "?Hungarumlautsmall", "$dollaroldstyle", "$dollarsuperior",
+ "&ersandsmall", "'Acutesmall", "/parenleftsuperior",
+ "\\parenrightsuperior", "?twodotenleader", "?onedotenleader",
+ "0zerooldstyle", "1oneoldstyle", "2twooldstyle", "3threeoldstyle",
+ "4fouroldstyle", "5fiveoldstyle", "6sixoldstyle", "7sevenoldstyle",
+ "8eightoldstyle", "9nineoldstyle", "'commasuperior",
+ "--threequartersemdash", ".periodsuperior", "?questionsmall", "aasuperior",
+ "bbsuperior", "ccentsuperior", "ddsuperior", "eesuperior", "iisuperior",
+ "llsuperior", "mmsuperior", "nnsuperior", "oosuperior", "rrsuperior",
+ "sssuperior", "ttsuperior", "ff", "ffi", "ffl", "\\parenleftinferior",
+ "/parenrightinferior", "^Circumflexsmall", "-hyphensuperior",
+ "`Gravesmall", "AAsmall", "BBsmall", "CCsmall", "DDsmall", "EEsmall",
+ "FFsmall", "GGsmall", "HHsmall", "IIsmall", "JJsmall", "KKsmall", "LLsmall",
+ "MMsmall", "NNsmall", "OOsmall", "PPsmall", "QQsmall", "RRsmall", "SSsmall",
+ "TTsmall", "UUsmall", "VVsmall", "WWsmall", "XXsmall", "YYsmall", "ZZsmall",
+ ":colonmonetary", "1onefitted", "?rupiah", "~Tildesmall",
+ "!exclamdownsmall", "ccentoldstyle", "LLslashsmall", "SScaronsmall",
+ "ZZcaronsmall", "?Dieresissmall", "?Brevesmall", "^Caronsmall",
+ "?Dotaccentsmall", "?Macronsmall", "--figuredash", "-hypheninferior",
+ "?Ogoneksmall", "oRingsmall", ",Cedillasmall", "?questiondownsmall",
+ "1/8oneeighth", "3/8threeeighths", "5/8fiveeighths", "7/8seveneighths",
+ "1/3onethird", "2/3twothirds", "0zerosuperior", "4foursuperior",
+ "5fivesuperior", "6sixsuperior", "7sevensuperior", "8eightsuperior",
+ "9ninesuperior", "0zeroinferior", "1oneinferior", "2twoinferior",
+ "3threeinferior", "4fourinferior", "5fiveinferior", "6sixinferior",
+ "7seveninferior", "8eightinferior", "9nineinferior", "ccentinferior",
+ "$dollarinferior", ".periodinferior", ",commainferior", "AAgravesmall",
+ "AAacutesmall", "AAcircumflexsmall", "AAtildesmall", "AAdieresissmall",
+ "AAringsmall", "AEAEsmall", "CCcedillasmall", "EEgravesmall",
+ "EEacutesmall", "EEcircumflexsmall", "EEdieresissmall", "IIgravesmall",
+ "IIacutesmall", "IIcircumflexsmall", "IIdieresissmall", "EthEthsmall",
+ "NNtildesmall", "OOgravesmall", "OOacutesmall", "OOcircumflexsmall",
+ "OOtildesmall", "OOdieresissmall", "OEOEsmall", "OOslashsmall",
+ "UUgravesmall", "UUacutesmall", "UUcircumflexsmall", "UUdieresissmall",
+ "YYacutesmall", "?Thornsmall", "YYdieresissmall", "?001.000", "?001.001",
+ "?001.002", "?001.003", " Black", " Bold", " Book", " Light", " Medium",
+ " Regular", " Roman", " Semibold",
+ /* extra mac stuff */
+ "?NUL", "?HT", " LF", " CR", "?DLE", "?DC1", "?DC2", "?DC3", "?DC4", "?RS",
+ "?US", "!=", "?DEL", "?infinity", "<=", ">=",
+ "?partialdiff", "?summation", "xproduct", "?pi", "?integral", "?Omega",
+ "?radical", "~=", "?Delta", " nbspace", "?lozenge", "?apple"
+ };
+
+ /**
+ * glyph order of the glyphs for the Type1C Expert character set. These
+ * are indices into the glyph name array.
+ */
+ public static final int type1CExpertCharset[] = {
+ 1, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 13, 14, 15, 99,
+ 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 27, 28, 249, 250,
+ 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 109, 110, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276,
+ 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290,
+ 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304,
+ 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318,
+ 158, 155, 163, 319, 320, 321, 322, 323, 324, 325, 326, 150, 164, 169,
+ 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340,
+ 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354,
+ 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368,
+ 369, 370, 371, 372, 373, 374, 375, 376, 377, 378
+ };
+
+ /**
+ * glyph order of the glyphs for the Type1C Expert Sub character set.
+ * These are indices into the glyph name array.
+ */
+ public static final int type1CExpertSubCharset[] = {
+ 1, 231, 232, 235, 236, 237, 238, 13, 14, 15, 99, 239, 240, 241, 242,
+ 243, 244, 245, 246, 247, 248, 27, 28, 249, 250, 251, 253, 254, 255,
+ 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 109, 110, 267,
+ 268, 269, 270, 272, 300, 301, 302, 305, 314, 315, 158, 155, 163, 320,
+ 321, 322, 323, 324, 325, 326, 150, 164, 169, 327, 328, 329, 330, 331,
+ 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345,
+ 346
+ };
+
+ /**
+ * extra names for the Macintosh glyph set. This array should be
+ * considered to be appended to the stdNames array. The stdValues array
+ * already contains values for this set.
+ */
+ public static final String macExtras[] = { // index starts at 391=NUL
+ "NUL", "HT", "LF", "CR", "DLE", "DC1", "DC2", "DC3", "DC4", "RS",
+ "US", "notequal", "DEL", "infinity", "lessequal", "greaterequal",
+ "partialdiff", "summation", "product", "pi", "integral", "Omega",
+ "radical", "approxequal", "Delta", "nbspace", "lozenge", "apple"
+ };
+
+ /**
+ * character mapping from values to glyphs for the Macintosh MacRoman
+ * encoding
+ */
+ public static final int macRomanEncoding[] = {
+ 391, 154, 167, 140, 146, 192, 221, 197, 226, 392, 393, 157, 162, 394,
+ 199, 228, 395, 396, 397, 398, 399, 155, 158, 150, 163, 169, 164, 160,
+ 166, 168, 400, 401, 1, 2, 3, 4, 5, 6, 7, 104, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 124,
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
+ 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 403, 173, 175,
+ 177, 178, 186, 189, 195, 200, 203, 201, 202, 205, 204, 206, 207, 210,
+ 208, 209, 211, 214, 212, 213, 215, 216, 219, 217, 218, 220, 222, 225,
+ 223, 224, 112, 161, 97, 98, 102, 116, 115, 149, 165, 170, 153, 125,
+ 131, 402, 138, 141, 404, 156, 405, 406, 100, 152, 407, 408, 409, 410,
+ 411, 139, 143, 412, 144, 147, 123, 96, 151, 413, 101, 414, 415, 106,
+ 120, 121, 416, 174, 176, 191, 142, 148, 111, 137, 105, 119, 65, 8,
+ 159, 417, 227, 198, 99, 103, 107, 108, 109, 110, 113, 114, 117, 118,
+ 122, 172, 179, 171, 180, 181, 182, 183, 184, 185, 187, 188, 418, 190,
+ 193, 194, 196, 145, 126, 127, 128, 129, 130, 132, 133, 134, 135, 136
+ };
+
+ /**
+ * character mapping from values to glyphs for the isoLatin1Encoding
+ */
+ public static final int isoLatin1Encoding[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 166, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
+ 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 124, 125, 126, 127, 128,
+ 129, 130, 131, 0, 132, 133, 0, 134, 135, 136, 1, 96, 97, 98, 103,
+ 100, 160, 102, 131, 170, 139, 106, 151, 14, 165, 128, 161, 156, 164,
+ 169, 125, 152, 115, 114, 133, 150, 143, 120, 158, 155, 163, 123, 174,
+ 171, 172, 176, 173, 175, 138, 177, 181, 178, 179, 180, 185, 182, 183,
+ 184, 154, 186, 190, 187, 188, 191, 189, 168, 141, 196, 193, 194, 195,
+ 197, 157, 149, 203, 200, 201, 205, 202, 204, 144, 206, 210, 207, 208,
+ 209, 214, 211, 212, 213, 167, 215, 219, 216, 217, 220, 218, 159, 147,
+ 225, 222, 223, 224, 226, 162, 227
+ };
+
+ /**
+ * character mapping from values to glyphs for the Windows winAnsi
+ * character encoding
+ */
+ public static final int winAnsiEncoding[] = {
+ 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 145,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
+ 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
+ 92, 93, 94, 95, 0, 0, 0, 117, 101, 118, 121, 112, 113, 0, 122, 192,
+ 107, 142, 0, 0, 0, 0, 65, 8, 105, 119, 116, 111, 137, 0, 153, 221,
+ 108, 148, 0, 0, 198, 1, 96, 97, 98, 103, 100, 160, 102, 131, 170,
+ 139, 106, 151, 14, 165, 128, 161, 156, 164, 169, 125, 152, 115, 114,
+ 133, 150, 143, 120, 158, 155, 163, 123, 174, 171, 172, 176, 173, 175,
+ 138, 177, 181, 178, 179, 180, 185, 182, 183, 184, 154, 186, 190, 187,
+ 188, 191, 189, 168, 141, 196, 193, 194, 195, 197, 157, 149, 203, 200,
+ 201, 205, 202, 204, 144, 206, 210, 207, 208, 209, 214, 211, 212, 213,
+ 167, 215, 219, 216, 217, 220, 218, 159, 147, 225, 222, 223, 224, 226,
+ 162, 227
+ };
+
+ /**
+ * character mapping from values to glyphs for Adobe's standard
+ * character encoding
+ */
+ public static final int standardEncoding[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
+ 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
+ 106, 107, 108, 109, 110, 0, 111, 112, 113, 114, 0, 115, 116, 117,
+ 118, 119, 120, 121, 122, 0, 123, 0, 124, 125, 126, 127, 128, 129,
+ 130, 131, 0, 132, 133, 0, 134, 135, 136, 137, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 139, 0, 0, 0, 0, 140, 141, 142, 143,
+ 0, 0, 0, 0, 0, 144, 0, 0, 0, 145, 0, 0, 146, 147, 148, 149, 0, 0, 0,
+ 0
+ };
+
+ /**
+ * get the name of a glyph from its encoding value (NOT the character
+ * value), using the standard encoding.
+ */
+ public static String getName (int i) {
+ if (i < stdNames.length) {
+ return stdNames[i];
+ } else {
+ i -= stdNames.length;
+ if (i < macExtras.length) {
+ return macExtras[i];
+ }
+ }
+ return ".notdef";
+ }
+
+ /**
+ * get the encoding value a glyph given its name and a name table.
+ * @param name the name of the glyph
+ * @param table the charset as an array of names
+ * @return the index of the name in the table, or -1 if the name
+ * cannot be found in the table
+ */
+ public static int findName (String name, String[] table) {
+ for (int i = 0; i < table.length; i++) {
+ if (name.equals (table[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * get the encoding value of a glyph given its name and a charset.
+ * @param name the name of the glyph
+ * @param table the charset table
+ * @return the index of the name in the charset.
+ */
+ public static int findName (String name, int[] table) {
+ for (int i = 0; i < table.length; i++) {
+ if (name.equals (getName (table[i]))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * get the encoding value of a glyph given its name, in the standard
+ * charset. This is equivalent to findName(name, FontSupport.stdNames).
+ * @param name the name of the glyph
+ * @return the index of the name in stdNames, or -1 if the name doesn't
+ * appear in stdNames.
+ */
+ public static int getStrIndex (String name) {
+ for (int i = 0; i < stdNames.length; i++) {
+ if (name.equals (stdNames[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/OutlineFont.java b/PdfView/src/main/java/com/sun/pdfview/font/OutlineFont.java
new file mode 100644
index 0000000..9c998e2
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/OutlineFont.java
@@ -0,0 +1,156 @@
+/*
+ * $Id: OutlineFont.java,v 1.3 2009/02/09 16:29:58 tomoke 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.font;
+
+import java.io.IOException;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+
+import com.sun.pdfview.PDFObject;
+
+/**
+ * Supports width operations for Type1, Type1C, TrueType and Type3 fonts
+ */
+public abstract class OutlineFont extends PDFFont {
+
+ /** the first character code */
+ private int firstChar = -1;
+ /** the last character code */
+ private int lastChar = -1;
+ /** the widths for each character code */
+ private float[] widths;
+
+ /** Creates a new instance of OutlineFont */
+ public OutlineFont(String baseFont, PDFObject fontObj,
+ PDFFontDescriptor descriptor) throws IOException {
+ super(baseFont, descriptor);
+
+ PDFObject firstCharObj = fontObj.getDictRef("FirstChar");
+ PDFObject lastCharObj = fontObj.getDictRef("LastChar");
+ PDFObject widthArrayObj = fontObj.getDictRef("Widths");
+
+ if (firstCharObj != null) {
+ firstChar = firstCharObj.getIntValue();
+ }
+ if (lastCharObj != null) {
+ lastChar = lastCharObj.getIntValue();
+ }
+
+ if (widthArrayObj != null) {
+ PDFObject[] widthArray = widthArrayObj.getArray();
+
+ widths = new float[widthArray.length];
+
+ for (int i = 0; i < widthArray.length; i++) {
+ widths[i] = widthArray[i].getFloatValue() / getDefaultWidth();
+ }
+ }
+ }
+
+ /** Get the first character code */
+ public int getFirstChar() {
+ return firstChar;
+ }
+
+ /** Get the last character code */
+ public int getLastChar() {
+ return lastChar;
+ }
+
+ /** Get the default width in text space */
+ public int getDefaultWidth() {
+ return 1000;
+ }
+
+ /** Get the number of characters */
+ public int getCharCount() {
+ return (getLastChar() - getFirstChar()) + 1;
+ }
+
+ /** Get the width of a given character */
+ public float getWidth(char code, String name) {
+ int idx = (code & 0xff) - getFirstChar();
+
+ // make sure we're in range
+ if (idx < 0 || widths == null || idx >= widths.length) {
+ // try to get the missing width from the font descriptor
+ if (getDescriptor() != null) {
+ return getDescriptor().getMissingWidth();
+ } else {
+ return 0;
+ }
+ }
+
+ return widths[idx];
+ }
+
+ /**
+ * Get the glyph for a given character code and name
+ *
+ * The preferred method of getting the glyph should be by name. If the
+ * name is null or not valid, then the character code should be used.
+ * If the both the code and the name are invalid, the undefined glyph
+ * should be returned.
+ *
+ * Note this method must *always* return a glyph.
+ *
+ * @param src the character code of this glyph
+ * @param name the name of this glyph or null if unknown
+ * @return a glyph for this character
+ */
+ protected PDFGlyph getGlyph(char src, String name) {
+ Path outline = null;
+ float width = getWidth(src, name);
+
+ // first try by name
+ if (name != null) {
+ outline = getOutline(name, width);
+ }
+
+ // now try by character code (guaranteed to return)
+ if (outline == null) {
+ outline = getOutline(src, width);
+ }
+
+ // calculate the advance
+ PointF advance = new PointF(width, 0);
+ return new PDFGlyph(src, name, outline, advance);
+ }
+
+ /**
+ * Get a glyph outline by name
+ *
+ * @param name the name of the desired glyph
+ * @return the glyph outline, or null if unavailable
+ */
+ protected abstract Path getOutline(String name, float width);
+
+ /**
+ * Get a glyph outline by character code
+ *
+ * Note this method must always return an outline
+ *
+ * @param src the character code of the desired glyph
+ * @return the glyph outline
+ */
+ protected abstract Path getOutline(char src, float width);
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/PDFCMap.java b/PdfView/src/main/java/com/sun/pdfview/font/PDFCMap.java
new file mode 100644
index 0000000..0e8caa1
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/PDFCMap.java
@@ -0,0 +1,106 @@
+/*
+ * $Id: PDFCMap.java,v 1.3 2009/02/12 13:53:54 tomoke 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.font;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import com.sun.pdfview.PDFObject;
+
+/**
+ * A CMap maps from a character in a composite font to a font/glyph number
+ * pair in a CID font.
+ *
+ * @author jkaplan
+ */
+public abstract class PDFCMap {
+ /**
+ * A cache of known CMaps by name
+ */
+ private static HashMap cache;
+
+ /** Creates a new instance of CMap */
+ protected PDFCMap() {}
+
+ /**
+ * Get a CMap, given a PDF object containing one of the following:
+ * a string name of a known CMap
+ * a stream containing a CMap definition
+ */
+ public static PDFCMap getCMap(PDFObject map) throws IOException {
+ if (map.getType() == PDFObject.NAME) {
+ return getCMap(map.getStringValue());
+ } else if (map.getType() == PDFObject.STREAM) {
+ return parseCMap(map);
+ } else {
+ throw new IOException("CMap type not Name or Stream!");
+ }
+ }
+
+ /**
+ * Get a CMap, given a string name
+ */
+ public static PDFCMap getCMap(String mapName) throws IOException {
+ if (cache == null) {
+ populateCache();
+ }
+
+ if (!cache.containsKey(mapName)) {
+ throw new IOException("Unknown CMap: " + mapName);
+ }
+
+ return (PDFCMap) cache.get(mapName);
+ }
+
+ /**
+ * Populate the cache with well-known types
+ */
+ protected static void populateCache() {
+ cache = new HashMap();
+
+ // add the Identity-H map
+ cache.put("Identity-H", new PDFCMap() {
+ public char map(char src) {
+ return src;
+ }
+ });
+ }
+
+ /**
+ * Parse a CMap from a CMap stream
+ */
+ protected static PDFCMap parseCMap(PDFObject map) throws IOException {
+ throw new IOException("Parsing CMap Files Unsupported!");
+ }
+
+ /**
+ * Map a given source character to a destination character
+ */
+ public abstract char map(char src);
+
+ /**
+ * Get the font number assoicated with a given source character
+ */
+ public int getFontID(char src) {
+ return 0;
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/PDFFont.java b/PdfView/src/main/java/com/sun/pdfview/font/PDFFont.java
new file mode 100644
index 0000000..9f8e639
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/PDFFont.java
@@ -0,0 +1,370 @@
+/*
+ * $Id: PDFFont.java,v 1.6 2009/03/15 20:47:38 tomoke 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.font;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * a Font definition for PDF files
+ * @author Mike Wessler
+ */
+public abstract class PDFFont {
+
+ public static boolean sUseFontSubstitution = false;
+
+ /** the font SubType of this font */
+ private String subtype;
+ /** the postscript name of this font */
+ private String baseFont;
+ /** the font encoding (maps character ids to glyphs) */
+ private PDFFontEncoding encoding;
+ /** the font descriptor */
+ private PDFFontDescriptor descriptor;
+ /** the CMap that maps this font to unicode values */
+ private PDFCMap unicodeMap;
+ /** a cache of glyphs indexed by character */
+ private Map charCache;
+
+ /**
+ * get the PDFFont corresponding to the font described in a PDFObject.
+ * The object is actually a dictionary containing the following keys:
+ * Type = "Font"
+ * Subtype = (Type1 | TrueType | Type3 | Type0 | MMType1 | CIDFontType0 |
+ * CIDFontType2)
+ * FirstChar = #
+ * LastChar = #
+ * Widths = array of #
+ * Encoding = (some name representing a dictionary in the resources | an
+ * inline dictionary)
+ *
+ * For Type1 and TrueType fonts, the dictionary also contains:
+ * BaseFont = (some name, or XXXXXX+Name as a subset of font Name)
+ *
+ * For Type3 font, the dictionary contains:
+ * FontBBox = (rectangle)
+ * FontMatrix = (array, typically [0.001, 0, 0, 0.001, 0, 0])
+ * CharProcs = (dictionary)
+ * Resources = (dictionary)
+ */
+ public synchronized static PDFFont getFont(PDFObject obj,
+ HashMap resources)
+ throws IOException {
+ // the obj is actually a dictionary containing:
+ // Type (=Font)
+ // Subtype (Type1, TrueType, Type3, Type0, MMType1, CIDFontType0,2)
+ // FirstChar (int)
+ // LastChar (int)
+ // Widths (array)
+ // Encoding (name or dict) : assumes StandardEncoding
+ // and........
+ // Type1 and TrueType fonts:
+ // BaseFont (name) // may be XXXXXX+Fontname as a subset.
+ // FontDescriptor (dict)
+ // Type3 fonts:
+ // FontBBox (rectangle)
+ // FontMatrix (array) // e.g. [0.001 0 0 0.001 0 0]
+ // CharProcs (dict)
+ // Resources (dict)
+ //
+ // Font descriptor (Type1 and TrueType fonts):
+ // FontName (name)
+ // Flags (1=monospace, 2=serif, 4=script, 7=italic, 19=bold)
+ // FontBBox (rectangle)
+ // ItalicAngle (float)
+ // Ascent (float)
+ // Descent (float)
+ // CapHeight (float)
+ // StemV (float)
+ // FontFile (stream for Type1 fonts)
+ // FontFile2 (stream for TrueType fonts)
+ // FontFile3 (stream for CFF/Type1C fonts)
+ //
+ // Font data can be Type1, TrueType(native), or Type1C
+ PDFFont font = (PDFFont) obj.getCache();
+ if (font != null) {
+ return font;
+ }
+
+ String baseFont = null;
+ PDFFontEncoding encoding = null;
+ PDFFontDescriptor descriptor = null;
+
+ String subType = obj.getDictRef("Subtype").getStringValue();
+ if (subType == null) {
+ subType = obj.getDictRef("S").getStringValue();
+ }
+ PDFObject baseFontObj = obj.getDictRef("BaseFont");
+ PDFObject encodingObj = obj.getDictRef("Encoding");
+ PDFObject descObj = obj.getDictRef("FontDescriptor");
+
+ if (baseFontObj != null) {
+ baseFont = baseFontObj.getStringValue();
+ } else {
+ baseFontObj = obj.getDictRef("Name");
+ if (baseFontObj != null) {
+ baseFont = baseFontObj.getStringValue();
+ }
+ }
+
+ if (encodingObj != null) {
+ encoding = new PDFFontEncoding(subType, encodingObj);
+ }
+
+ if (descObj != null) {
+ descriptor = new PDFFontDescriptor(descObj);
+ } else {
+ descriptor = new PDFFontDescriptor(baseFont);
+ }
+
+ if (subType.equals("Type0")) {
+ font = new Type0Font(baseFont, obj, descriptor);
+ } else if (subType.equals("Type1")) {
+ // load a type1 font
+ if (descriptor == null) {
+ // it's one of the built-in fonts
+ font = new BuiltinFont(baseFont, obj);
+ } else if (descriptor.getFontFile() != null) {
+ // it's a Type1 font, included.
+ font = new Type1Font(baseFont, obj, descriptor);
+ } else if (descriptor.getFontFile3() != null) {
+ // it's a CFF (Type1C) font
+ font = new Type1CFont(baseFont, obj, descriptor);
+ } else {
+ // no font info. Fake it based on the FontDescriptor
+ // System.out.println("Fakeout native font");
+ font = new BuiltinFont(baseFont, obj, descriptor);
+ }
+ } else if (subType.equals("TrueType")) {
+ if (descriptor.getFontFile2() != null) {
+ // load a TrueType font
+ font = new TTFFont(baseFont, obj, descriptor);
+ } else {
+ // fake it with a built-in font
+ font = new BuiltinFont(baseFont, obj, descriptor);
+ }
+ } else if (subType.equals("Type3")) {
+ // load a type 3 font
+ font = new Type3Font(baseFont, obj, resources, descriptor);
+ } else if (subType.equals("CIDFontType2")) {
+ font = new CIDFontType2(baseFont, obj, descriptor);
+ } else if (subType.equals("CIDFontType0")) {
+ font = new CIDFontType2(baseFont, obj, descriptor);
+// font = new CIDFontType0(baseFont, obj, descriptor);
+// throw new IOException ("CIDFontType0 is unimplemented. " + obj);
+ } else {
+ throw new PDFParseException("Don't know how to handle a '" +
+ subType + "' font");
+ }
+
+ font.setSubtype(subType);
+ font.setEncoding(encoding);
+
+ obj.setCache(font);
+ return font;
+ }
+
+ /**
+ * Get the subtype of this font.
+ * @return the subtype, one of: Type0, Type1, TrueType or Type3
+ */
+ public String getSubtype() {
+ return subtype;
+ }
+
+ /**
+ * Set the font subtype
+ */
+ public void setSubtype(String subtype) {
+ this.subtype = subtype;
+ }
+
+ /**
+ * Get the postscript name of this font
+ * @return the postscript name of this font
+ */
+ public String getBaseFont() {
+ return baseFont;
+ }
+
+ /**
+ * Set the postscript name of this font
+ * @param baseFont the postscript name of the font
+ */
+ public void setBaseFont(String baseFont) {
+ this.baseFont = baseFont;
+ }
+
+ /**
+ * Get the encoding for this font
+ * @return the encoding which maps from this font to actual characters
+ */
+ public PDFFontEncoding getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Set the encoding for this font
+ */
+ public void setEncoding(PDFFontEncoding encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * Get the descriptor for this font
+ * @return the font descriptor
+ */
+ public PDFFontDescriptor getDescriptor() {
+ return descriptor;
+ }
+
+ /**
+ * Set the descriptor font descriptor
+ */
+ public void setDescriptor(PDFFontDescriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ /**
+ * Get the CMap which maps the characters in this font to unicode names
+ */
+ public PDFCMap getUnicodeMap() {
+ return unicodeMap;
+ }
+
+ /**
+ * Set the CMap which maps the characters in this font to unicode names
+ */
+ public void setUnicodeMap(PDFCMap unicodeMap) {
+ this.unicodeMap = unicodeMap;
+ }
+
+ /**
+ * Get the glyphs associated with a given String in this font
+ *
+ * @param text the text to translate into glyphs
+ */
+ public List getGlyphs(String text) {
+ List outList = null;
+
+ // if we have an encoding, use it to get the commands
+ if (encoding != null) {
+ outList = encoding.getGlyphs(this, text);
+ } else {
+ // use the default mapping
+ char[] arry = text.toCharArray();
+ outList = new ArrayList(arry.length);
+
+ for (int i = 0; i < arry.length; i++) {
+ // only look at 2 bytes when there is no encoding
+ char src = (char) (arry[i] & 0xff);
+ outList.add(getCachedGlyph(src, null));
+ }
+ }
+
+ return outList;
+ }
+
+ /**
+ * Get a glyph for a given character code. The glyph is returned
+ * from the cache if available, or added to the cache if not
+ *
+ * @param src the character code of this glyph
+ * @param name the name of the glyph, or null if the name is unknown
+ * @return a glyph for this character
+ */
+ public PDFGlyph getCachedGlyph(char src, String name) {
+ if (charCache == null) {
+ charCache = new HashMap();
+ }
+
+ // try the cache
+ PDFGlyph glyph = (PDFGlyph) charCache.get(new Character(src));
+
+ // if it's not there, add it to the cache
+ if (glyph == null) {
+ glyph = getGlyph(src, name);
+ charCache.put(new Character(src), glyph);
+ }
+
+ return glyph;
+ }
+
+ /**
+ * Create a PDFFont given the base font name and the font descriptor
+ * @param baseFont the postscript name of this font
+ * @param descriptor the descriptor for the font
+ */
+ protected PDFFont(String baseFont, PDFFontDescriptor descriptor) {
+ setBaseFont(baseFont);
+ setDescriptor(descriptor);
+ }
+
+ /**
+ * Get the glyph for a given character code and name
+ *
+ * The preferred method of getting the glyph should be by name. If the
+ * name is null or not valid, then the character code should be used.
+ * If the both the code and the name are invalid, the undefined glyph
+ * should be returned.
+ *
+ * Note this method must *always* return a glyph.
+ *
+ * @param src the character code of this glyph
+ * @param name the name of this glyph or null if unknown
+ * @return a glyph for this character
+ */
+ protected abstract PDFGlyph getGlyph(char src, String name);
+
+ /**
+ * Turn this font into a pretty String
+ */
+ @Override
+ public String toString() {
+ return getBaseFont();
+ }
+
+ /**
+ * Compare two fonts base on the baseFont
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PDFFont)) {
+ return false;
+ }
+
+ return ((PDFFont) o).getBaseFont().equals(getBaseFont());
+ }
+
+ /**
+ * Hash a font based on its base font
+ */
+ @Override
+ public int hashCode() {
+ return getBaseFont().hashCode();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/PDFFontDescriptor.java b/PdfView/src/main/java/com/sun/pdfview/font/PDFFontDescriptor.java
new file mode 100644
index 0000000..ba1f07e
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/PDFFontDescriptor.java
@@ -0,0 +1,507 @@
+/*
+ * $Id: PDFFontDescriptor.java,v 1.4 2009/03/15 20:47:38 tomoke 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.font;
+
+import java.io.IOException;
+
+import android.graphics.RectF;
+
+import com.sun.pdfview.PDFObject;
+
+/**
+ *
+ * @author jkaplan
+ */
+public class PDFFontDescriptor {
+
+ /** All glyphs have the same width. */
+ public final static int FIXED_PITCH = 1 << (1-1);
+ /** Glyphs have serifs. */
+ public final static int SERIF = 1 << (2-1);
+ /** Font contains glyphs outside the Adobe standard Latin. */
+ public final static int SYMBOLIC = 1 << (3-1);
+ /** Glyphs resemble cursive handwriting. */
+ public final static int SCRIPT = 1 << (4-1);
+ /** Font uses the Adobe standard Latic character set. */
+ public final static int NONSYMBOLIC = 1 << (6-1);
+ /** Glyphs have dominant vertical strokes that are slanted. */
+ public final static int ITALIC = 1 << (7-1);
+ /** Font contains no lowercase letters. */
+ public final static int ALLCAP = 1 << (17-1);
+ /** Font contains both uppercase and lowercase letters.. */
+ public final static int SMALLCAP = 1 << (18-1);
+ /** Determines whether bold glyphs shall be painted with
+ * extra pixels even at very small text sizes. */
+ public final static int FORCEBOLD = 1 << (19-1);
+ /** Holds value of property ascent. */
+ private int ascent;
+ /** Holds value of property capHeight. */
+ private int capHeight;
+ /** Holds value of property descent. */
+ private int descent;
+ /** Holds value of property flags. */
+ private int flags;
+ /** Holds the optional FontFamily (PDF 1.5) */
+ private String fontFamily;
+ /** Holds value of property fontName. */
+ private String fontName;
+ /** Holds the optional FontStretch (PDF 1.5) */
+ private String fontStretch;
+ /** Holds the optional FontWeight (PDF 1.5) */
+ private int fontWeight;
+ /** Holds value of property italicAngle. */
+ private int italicAngle;
+ /** Holds value of property stemV. */
+ private int stemV;
+ /** Holds value of property avgWidth. */
+ private int avgWidth = 0;
+ /** Holds value of property fontFile. */
+ private PDFObject fontFile;
+ /** Holds value of property fontFile2. */
+ private PDFObject fontFile2;
+ /** Holds value of property fontFile3. */
+ private PDFObject fontFile3;
+ /** Holds value of property leading. */
+ private int leading = 0;
+ /** Holds value of property maxWidth. */
+ private int maxWidth = 0;
+ /** Holds value of property misingWidth. */
+ private int missingWidth = 0;
+ /** Holds value of property stemH. */
+ private int stemH = 0;
+ /** Holds value of property xHeight. */
+ private int xHeight = 0;
+ /** Holds value of property charSet. */
+ private PDFObject charSet;
+ /** Holds value of property fontBBox. */
+ private RectF fontBBox;
+
+ /** Creates a new instance of PDFFontDescriptor */
+ public PDFFontDescriptor(String basefont) {
+ setFontName(basefont);
+ // [[MW TODO: find basefont info and fill in the rest of the
+ // descriptor?]]
+ }
+
+ /** Creates a new instance of PDFFontDescriptor */
+ public PDFFontDescriptor(PDFObject obj) throws IOException {
+ // required parameters
+ setAscent(obj.getDictRef("Ascent").getIntValue());
+ setCapHeight(obj.getDictRef("CapHeight").getIntValue());
+ setDescent(obj.getDictRef("Descent").getIntValue());
+ setFlags(obj.getDictRef("Flags").getIntValue());
+ setFontName(obj.getDictRef("FontName").getStringValue());
+ setItalicAngle(obj.getDictRef("ItalicAngle").getIntValue());
+ setStemV(obj.getDictRef("StemV").getIntValue());
+
+ // font bounding box
+ PDFObject[] bboxdef = obj.getDictRef("FontBBox").getArray();
+ float[] bboxfdef = new float[4];
+ for (int i = 0; i < 4; i++) {
+ bboxfdef[i] = bboxdef[i].getFloatValue();
+ }
+ setFontBBox(new RectF(bboxfdef[0], bboxfdef[1],
+ bboxfdef[2] - bboxfdef[0],
+ bboxfdef[3] - bboxfdef[1]));
+
+ // optional parameters
+ if (obj.getDictionary().containsKey("AvgWidth")) {
+ setAvgWidth(obj.getDictRef("AvgWidth").getIntValue());
+ }
+ if (obj.getDictionary().containsKey("FontFile")) {
+ setFontFile(obj.getDictRef("FontFile"));
+ }
+ if (obj.getDictionary().containsKey("FontFile2")) {
+ setFontFile2(obj.getDictRef("FontFile2"));
+ }
+ if (obj.getDictionary().containsKey("FontFile3")) {
+ setFontFile3(obj.getDictRef("FontFile3"));
+ }
+ if (obj.getDictionary().containsKey("Leading")) {
+ setLeading(obj.getDictRef("Leading").getIntValue());
+ }
+ if (obj.getDictionary().containsKey("MaxWidth")) {
+ setMaxWidth(obj.getDictRef("MaxWidth").getIntValue());
+ }
+ if (obj.getDictionary().containsKey("MissingWidth")) {
+ setMissingWidth(obj.getDictRef("MissingWidth").getIntValue());
+ }
+ if (obj.getDictionary().containsKey("StemH")) {
+ setStemH(obj.getDictRef("StemH").getIntValue());
+ }
+ if (obj.getDictionary().containsKey("XHeight")) {
+ setXHeight(obj.getDictRef("XHeight").getIntValue());
+ }
+ if (obj.getDictionary().containsKey("CharSet")) {
+ setCharSet(obj.getDictRef("CharSet"));
+ }
+ if (obj.getDictionary().containsKey("FontFamily")) {
+ setFontFamily(obj.getDictRef("FontFamily").getStringValue());
+ }
+ if (obj.getDictionary().containsKey("FontWeight")) {
+ setFontWeight(obj.getDictRef("FontWeight").getIntValue());
+ }
+ if (obj.getDictionary().containsKey("FontStretch")) {
+ setFontStretch(obj.getDictRef("FontStretch").getStringValue());
+ }
+ }
+
+ /** Getter for property ascent.
+ * @return Value of property ascent.
+ *
+ */
+ public int getAscent() {
+ return this.ascent;
+ }
+
+ /** Setter for property ascent.
+ * @param ascent New value of property ascent.
+ *
+ */
+ public void setAscent(int ascent) {
+ this.ascent = ascent;
+ }
+
+ /** Getter for property capHeight.
+ * @return Value of property capHeight.
+ *
+ */
+ public int getCapHeight() {
+ return this.capHeight;
+ }
+
+ /** Setter for property capHeight.
+ * @param capHeight New value of property capHeight.
+ *
+ */
+ public void setCapHeight(int capHeight) {
+ this.capHeight = capHeight;
+ }
+
+ /** Getter for property descent.
+ * @return Value of property descent.
+ *
+ */
+ public int getDescent() {
+ return this.descent;
+ }
+
+ /** Setter for property descent.
+ * @param descent New value of property descent.
+ *
+ */
+ public void setDescent(int descent) {
+ this.descent = descent;
+ }
+
+ /** Getter for property flags.
+ * @return Value of property flags.
+ *
+ */
+ public int getFlags() {
+ return this.flags;
+ }
+
+ /** Setter for property flags.
+ * @param flags New value of property flags.
+ *
+ */
+ public void setFlags(int flags) {
+ this.flags = flags;
+ }
+
+ /** Getter for property fontFamily. Option (PDF 1.5)
+ * @return Value of the property fontFamily
+ */
+ public String getFontFamily() {
+ return this.fontFamily;
+ }
+
+ /** Setter for property fontFamily.
+ * @param fontFamily New value of property fontFamily.
+ *
+ */
+ public void setFontFamily(String fontFamily) {
+ this.fontFamily = fontFamily;
+ }
+
+ /** Getter for property fontName.
+ * @return Value of property fontName.
+ *
+ */
+ public String getFontName() {
+ return this.fontName;
+ }
+
+ /** Setter for property fontName.
+ * @param fontName New value of property fontName.
+ *
+ */
+ public void setFontName(String fontName) {
+ this.fontName = fontName;
+ }
+
+ /** Getter for property fontStretch. Option (PDF 1.5)
+ *
+ * @return Value of the property fontStretch
+ */
+ public String getFontStretch() {
+ return this.fontStretch;
+ }
+
+ /** Setter for property fontStretch. Possible values are:
+ * UltraCondensed, ExtraCondensed, Condensed, SemiCondensed,
+ * Normal, SemiExpanded, Expanded, ExtraExpanded or UltraExpanded
+ * We do not check at this time.
+ *
+ * @param fontStretch New value of property fontStretch.
+ *
+ */
+ public void setFontStretch(String fontStretch) {
+ this.fontStretch = fontStretch;
+ }
+
+ /** Getter for property fontWeight. Option (PDF 1.5)
+ *
+ * @return Value of the property fontWeight
+ */
+ public int getFontWeight() {
+ return this.fontWeight;
+ }
+
+ /** Setter for property fontWeight. Possible values are:
+ * 100, 200, 300, 400, 500, 600, 700, 800, 900
+ * We do not check at this time.
+ *
+ * @param fontWeight New value of property fontWeight.
+ *
+ */
+ public void setFontWeight(int fontWeight) {
+ this.fontWeight = fontWeight;
+ }
+
+ /** Getter for property italicAngle.
+ * @return Value of property italicAngle.
+ *
+ */
+ public int getItalicAngle() {
+ return this.italicAngle;
+ }
+
+ /** Setter for property italicAngle.
+ * @param italicAngle New value of property italicAngle.
+ *
+ */
+ public void setItalicAngle(int italicAngle) {
+ this.italicAngle = italicAngle;
+ }
+
+ /** Getter for property stemV.
+ * @return Value of property stemV.
+ *
+ */
+ public int getStemV() {
+ return this.stemV;
+ }
+
+ /** Setter for property stemV.
+ * @param stemV New value of property stemV.
+ *
+ */
+ public void setStemV(int stemV) {
+ this.stemV = stemV;
+ }
+
+ /** Getter for property avgWidth.
+ * @return Value of property avgWidth.
+ *
+ */
+ public int getAvgWidth() {
+ return this.avgWidth;
+ }
+
+ /** Setter for property avgWidth.
+ * @param avgWidth New value of property avgWidth.
+ *
+ */
+ public void setAvgWidth(int avgWidth) {
+ this.avgWidth = avgWidth;
+ }
+
+ /** Getter for property fontFile.
+ * @return Value of property fontFile.
+ *
+ */
+ public PDFObject getFontFile() {
+ return this.fontFile;
+ }
+
+ /** Setter for property fontFile.
+ * @param fontFile New value of property fontFile.
+ *
+ */
+ public void setFontFile(PDFObject fontFile) {
+ this.fontFile = fontFile;
+ }
+
+ /** Getter for property fontFile2.
+ * @return Value of property fontFile2.
+ *
+ */
+ public PDFObject getFontFile2() {
+ return this.fontFile2;
+ }
+
+ /** Setter for property fontFile2.
+ * @param fontFile2 New value of property fontFile2.
+ *
+ */
+ public void setFontFile2(PDFObject fontFile2) {
+ this.fontFile2 = fontFile2;
+ }
+
+ /** Getter for property fontFile3.
+ * @return Value of property fontFile3.
+ *
+ */
+ public PDFObject getFontFile3() {
+ return this.fontFile3;
+ }
+
+ /** Setter for property fontFile3.
+ * @param fontFile3 New value of property fontFile3.
+ *
+ */
+ public void setFontFile3(PDFObject fontFile3) {
+ this.fontFile3 = fontFile3;
+ }
+
+ /** Getter for property leading.
+ * @return Value of property leading.
+ *
+ */
+ public int getLeading() {
+ return this.leading;
+ }
+
+ /** Setter for property leading.
+ * @param leading New value of property leading.
+ *
+ */
+ public void setLeading(int leading) {
+ this.leading = leading;
+ }
+
+ /** Getter for property maxWidth.
+ * @return Value of property maxWidth.
+ *
+ */
+ public int getMaxWidth() {
+ return this.maxWidth;
+ }
+
+ /** Setter for property maxWidth.
+ * @param maxWidth New value of property maxWidth.
+ *
+ */
+ public void setMaxWidth(int maxWidth) {
+ this.maxWidth = maxWidth;
+ }
+
+ /** Getter for property misingWidth.
+ * @return Value of property misingWidth.
+ *
+ */
+ public int getMissingWidth() {
+ return this.missingWidth;
+ }
+
+ /** Setter for property misingWidth.
+ * @param missingWidth New value of property misingWidth.
+ */
+ public void setMissingWidth(int missingWidth) {
+ this.missingWidth = missingWidth;
+ }
+
+ /** Getter for property stemH.
+ * @return Value of property stemH.
+ *
+ */
+ public int getStemH() {
+ return this.stemH;
+ }
+
+ /** Setter for property stemH.
+ * @param stemH New value of property stemH.
+ *
+ */
+ public void setStemH(int stemH) {
+ this.stemH = stemH;
+ }
+
+ /** Getter for property xHeight.
+ * @return Value of property xHeight.
+ *
+ */
+ public int getXHeight() {
+ return this.xHeight;
+ }
+
+ /** Setter for property xHeight.
+ * @param xHeight New value of property xHeight.
+ *
+ */
+ public void setXHeight(int xHeight) {
+ this.xHeight = xHeight;
+ }
+
+ /** Getter for property charSet.
+ * @return Value of property charSet.
+ *
+ */
+ public PDFObject getCharSet() {
+ return this.charSet;
+ }
+
+ /** Setter for property charSet.
+ * @param charSet New value of property charSet.
+ *
+ */
+ public void setCharSet(PDFObject charSet) {
+ this.charSet = charSet;
+ }
+
+ /** Getter for property fontBBox.
+ * @return Value of property fontBBox.
+ *
+ */
+ public RectF getFontBBox() {
+ return this.fontBBox;
+ }
+
+ /** Setter for property fontBBox.
+ * @param fontBBox New value of property fontBBox.
+ *
+ */
+ public void setFontBBox(RectF fontBBox) {
+ this.fontBBox = fontBBox;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/PDFFontEncoding.java b/PdfView/src/main/java/com/sun/pdfview/font/PDFFontEncoding.java
new file mode 100644
index 0000000..cb40e9d
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/PDFFontEncoding.java
@@ -0,0 +1,202 @@
+/*
+ * $Id: PDFFontEncoding.java,v 1.4 2009/02/12 13:53:54 tomoke 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.font;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.sun.pdfview.PDFObject;
+
+/**
+ * The PDFFont encoding encapsulates the mapping from character codes
+ * in the PDF document to glyphs of the font.
+ *
+ * Encodings take two basic forms. For Type1, TrueType, and Type3 fonts,
+ * the encoding maps from character codes to Strings, which represent the
+ * glyphs of the font. For Type0 fonts, the mapping is a CMap which maps
+ * character codes to characters in one of many descendant fonts.
+ *
+ * Note that the data in the PDF might be ASCII characters (bytes) or it might
+ * be a multi-byte format such as unicode. For now we will assume all
+ * glyph ids fit into at most the two bytes of a character.
+ */
+public class PDFFontEncoding {
+
+ /** Encoding types */
+ private static final int TYPE_ENCODING = 0;
+ private static final int TYPE_CMAP = 1;
+ /**
+ * the base encoding (an array of integers which can be mapped to names
+ * using the methods on FontSupport
+ */
+ private int[] baseEncoding;
+ /** any differences from the base encoding */
+ private Map differences;
+ /**
+ * a CMap for fonts encoded by CMap
+ */
+ private PDFCMap cmap;
+ /**
+ * the type of this encoding (encoding or CMap)
+ */
+ private int type;
+
+ /** Creates a new instance of PDFFontEncoding */
+ public PDFFontEncoding(String fontType, PDFObject encoding)
+ throws IOException {
+ if (encoding.getType() == PDFObject.NAME) {
+ // if the encoding is a String, it is the name of an encoding
+ // or the name of a CMap, depending on the type of the font
+ if (fontType.equals("Type0")) {
+ type = TYPE_CMAP;
+ cmap = PDFCMap.getCMap(encoding.getStringValue());
+ } else {
+ type = TYPE_ENCODING;
+
+ differences = new HashMap();
+ baseEncoding = this.getBaseEncoding(encoding.getStringValue());
+ }
+ } else {
+ // loook at the "Type" entry of the encoding to determine the type
+ String typeStr = encoding.getDictRef("Type").getStringValue();
+
+ if (typeStr.equals("Encoding")) {
+ // it is an encoding
+ type = TYPE_ENCODING;
+ parseEncoding(encoding);
+ } else if (typeStr.equals("CMap")) {
+ // it is a CMap
+ type = TYPE_CMAP;
+ cmap = PDFCMap.getCMap(encoding);
+ } else {
+ throw new IllegalArgumentException("Uknown encoding type: " + type);
+ }
+ }
+ }
+
+ /** Get the glyphs associated with a given String */
+ public List getGlyphs(PDFFont font, String text) {
+ List outList = new ArrayList(text.length());
+
+ // go character by character through the text
+ char[] arry = text.toCharArray();
+ for (int i = 0; i < arry.length; i++) {
+ switch (type) {
+ case TYPE_ENCODING:
+ outList.add(getGlyphFromEncoding(font, arry[i]));
+ break;
+ case TYPE_CMAP:
+ // 2 bytes -> 1 character in a CMap
+ char c = (char) ((arry[i] & 0xff) << 8);
+ if (i < arry.length - 1) {
+ c |= (char) (arry[++i] & 0xff);
+ }
+ outList.add(getGlyphFromCMap(font, c));
+ break;
+ }
+ }
+
+ return outList;
+ }
+
+ /**
+ * Get a glyph from an encoding, given a font and character
+ */
+ private PDFGlyph getGlyphFromEncoding(PDFFont font, char src) {
+ String charName = null;
+
+ // only deal with one byte of source
+ src &= 0xff;
+
+ // see if this character is in the differences list
+ if (differences.containsKey(new Character(src))) {
+ charName = (String) differences.get(new Character(src));
+ } else if (baseEncoding != null) {
+ // get the character name from the base encoding
+ int charID = baseEncoding[src];
+ charName = FontSupport.getName(charID);
+ }
+
+ return font.getCachedGlyph(src, charName);
+ }
+
+ /**
+ * Get a glyph from a CMap, given a Type0 font and a character
+ */
+ private PDFGlyph getGlyphFromCMap(PDFFont font, char src) {
+ int fontID = cmap.getFontID(src);
+ char charID = cmap.map(src);
+
+ if (font instanceof Type0Font) {
+ font = ((Type0Font) font).getDescendantFont(fontID);
+ }
+
+ return font.getCachedGlyph(charID, null);
+ }
+
+ /**
+ * Parse a PDF encoding object for the actual encoding
+ */
+ public void parseEncoding(PDFObject encoding) throws IOException {
+ differences = new HashMap();
+
+ // figure out the base encoding, if one exists
+ PDFObject baseEncObj = encoding.getDictRef("BaseEncoding");
+ if (baseEncObj != null) {
+ baseEncoding = getBaseEncoding(baseEncObj.getStringValue());
+ }
+
+ // parse the differences array
+ PDFObject diffArrayObj = encoding.getDictRef("Differences");
+ if (diffArrayObj != null) {
+ PDFObject[] diffArray = diffArrayObj.getArray();
+ int curPosition = -1;
+
+ for (int i = 0; i < diffArray.length; i++) {
+ if (diffArray[i].getType() == PDFObject.NUMBER) {
+ curPosition = diffArray[i].getIntValue();
+ } else if (diffArray[i].getType() == PDFObject.NAME) {
+ Character key = new Character((char) curPosition);
+ differences.put(key, diffArray[i].getStringValue());
+ curPosition++;
+ } else {
+ throw new IllegalArgumentException("Unexpected type in diff array: " + diffArray[i]);
+ }
+ }
+ }
+ }
+
+ /** Get the base encoding for a given name */
+ private int[] getBaseEncoding(String encodingName) {
+ if (encodingName.equals("MacRomanEncoding")) {
+ return FontSupport.macRomanEncoding;
+ } else if (encodingName.equals("MacExpertEncoding")) {
+ return FontSupport.type1CExpertCharset;
+ } else if (encodingName.equals("WinAnsiEncoding")) {
+ return FontSupport.winAnsiEncoding;
+ } else {
+ throw new IllegalArgumentException("Unknown encoding: " + encodingName);
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/PDFGlyph.java b/PdfView/src/main/java/com/sun/pdfview/font/PDFGlyph.java
new file mode 100644
index 0000000..9a6748a
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/PDFGlyph.java
@@ -0,0 +1,107 @@
+/*
+ * $Id: PDFGlyph.java,v 1.3 2009/02/09 16:35:01 tomoke 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.font;
+
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PointF;
+
+import com.sun.pdfview.PDFPage;
+import com.sun.pdfview.PDFShapeCmd;
+
+/**
+ * A single glyph in a stream of PDF text, which knows how to write itself
+ * onto a PDF command stream
+ */
+public class PDFGlyph {
+ /** the character code of this glyph */
+ private char src;
+
+ /** the name of this glyph */
+ private String name;
+
+ /** the advance from this glyph */
+ private PointF advance;
+
+ /** the shape represented by this glyph (for all fonts but type 3) */
+ private Path shape;
+
+ /** the PDFPage storing this glyph's commands (for type 3 fonts) */
+ private PDFPage page;
+
+ /** Creates a new instance of PDFGlyph based on a shape */
+ public PDFGlyph(char src, String name, Path shape,
+ PointF advance) {
+ this.shape = shape;
+ this.advance = advance;
+ this.src = src;
+ this.name = name;
+ }
+
+ /** Creates a new instance of PDFGlyph based on a page */
+ public PDFGlyph(char src, String name, PDFPage page, PointF advance) {
+ this.page = page;
+ this.advance = advance;
+ this.src = src;
+ this.name = name;
+ }
+
+ /** Get the character code of this glyph */
+ public char getChar() {
+ return src;
+ }
+
+ /** Get the name of this glyph */
+ public String getName() {
+ return name;
+ }
+
+ /** Get the shape of this glyph */
+ public Path getShape() {
+ return shape;
+ }
+
+ /** Get the PDFPage for a type3 font glyph */
+ public PDFPage getPage() {
+ return page;
+ }
+
+ /** Add commands for this glyph to a page */
+ public PointF addCommands(PDFPage cmds, Matrix transform, int mode) {
+ if (shape != null) {
+ Path outline= new Path();
+ shape.transform(transform, outline);
+ cmds.addCommand(new PDFShapeCmd(outline, mode));
+ } else if (page != null) {
+ cmds.addCommands(page, transform);
+ }
+
+ return advance;
+ }
+
+ public String toString () {
+ StringBuffer str = new StringBuffer ();
+ str.append(name);
+ return str.toString();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/TTFFont.java b/PdfView/src/main/java/com/sun/pdfview/font/TTFFont.java
new file mode 100644
index 0000000..987d56e
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/TTFFont.java
@@ -0,0 +1,386 @@
+/*
+ * $Id: TTFFont.java,v 1.10 2009/02/23 15:29:19 tomoke 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.font;
+
+import java.io.IOException;
+
+import net.sf.andpdf.utils.Utils;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.font.ttf.AdobeGlyphList;
+import com.sun.pdfview.font.ttf.CMap;
+import com.sun.pdfview.font.ttf.CmapTable;
+import com.sun.pdfview.font.ttf.Glyf;
+import com.sun.pdfview.font.ttf.GlyfCompound;
+import com.sun.pdfview.font.ttf.GlyfSimple;
+import com.sun.pdfview.font.ttf.GlyfTable;
+import com.sun.pdfview.font.ttf.HeadTable;
+import com.sun.pdfview.font.ttf.HmtxTable;
+import com.sun.pdfview.font.ttf.PostTable;
+import com.sun.pdfview.font.ttf.TrueTypeFont;
+
+
+/**
+ * A true-type font
+ */
+public class TTFFont extends OutlineFont {
+
+ /** the truetype font itself */
+ private TrueTypeFont font;
+ /** the number of units per em in the font */
+ private float unitsPerEm;
+
+ /**
+ * create a new TrueTypeFont object based on a description of the
+ * font from the PDF file. If the description happens to contain
+ * an in-line true-type font file (under key "FontFile2"), use the
+ * true type font. Otherwise, parse the description for key information
+ * and use that to generate an appropriate font.
+ */
+ public TTFFont(String baseFont, PDFObject fontObj,
+ PDFFontDescriptor descriptor)
+ throws IOException {
+ super(baseFont, fontObj, descriptor);
+
+ String fontName = descriptor.getFontName();
+ PDFObject ttfObj = descriptor.getFontFile2();
+
+ // try {
+ // byte[] fontData = ttfObj.getStream();
+ // java.io.FileOutputStream fis = new java.io.FileOutputStream("/tmp/" + fontName + ".ttf");
+ // fis.write(fontData);
+ // fis.flush();
+ // fis.close();
+ // } catch (Exception ex) {
+ // ex.printStackTrace();
+ // }
+ if (ttfObj != null) {
+ font = TrueTypeFont.parseFont(ttfObj.getStreamBuffer());
+ // read the units per em from the head table
+ HeadTable head = (HeadTable) font.getTable("head");
+ unitsPerEm = head.getUnitsPerEm();
+ } else {
+ font = null;
+ }
+// System.out.println ("TTFFont: ttfObj: " + ttfObj + ", fontName: " + fontName);
+
+ }
+
+ /**
+ * Get the outline of a character given the character code
+ */
+ protected synchronized Path getOutline(char src, float width) {
+ // find the cmaps
+ CmapTable cmap = (CmapTable) font.getTable("cmap");
+
+ // if there are no cmaps, this is (hopefully) a cid-mapped font,
+ // so just trust the value we were given for src
+ if (cmap == null) {
+ return getOutline((int) src, width);
+ }
+
+ CMap[] maps = cmap.getCMaps();
+
+ // try the maps in order
+ for (int i = 0; i < maps.length; i++) {
+ int idx = maps[i].map(src);
+ if (idx != 0) {
+ return getOutline(idx, width);
+ }
+ }
+
+ // not found, return the empty glyph
+ return getOutline(0, width);
+ }
+
+ /**
+ * lookup the outline using the CMAPs, as specified in 32000-1:2008,
+ * 9.6.6.4, when an Encoding is specified.
+ *
+ * @param val
+ * @param width
+ * @return GeneralPath
+ */
+ protected synchronized Path getOutlineFromCMaps(char val, float width) {
+ // find the cmaps
+ CmapTable cmap = (CmapTable) font.getTable("cmap");
+
+ if (cmap == null) {
+ return null;
+ }
+
+ // try maps in required order of (3, 1), (1, 0)
+ CMap map = cmap.getCMap((short) 3, (short) 1);
+ if (map == null) {
+ map = cmap.getCMap((short) 1, (short) 0);
+ }
+ int idx = map.map(val);
+ if (idx != 0) {
+ return getOutline(idx, width);
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the outline of a character given the character name
+ */
+ protected synchronized Path getOutline(String name, float width) {
+ int idx;
+ PostTable post = (PostTable) font.getTable("post");
+ if (post != null) {
+ idx = post.getGlyphNameIndex(name);
+ if (idx != 0) {
+ return getOutline(idx, width);
+ }
+ return null;
+ }
+
+ Integer res = AdobeGlyphList.getGlyphNameIndex(name);
+ if(res != null) {
+ idx = res;
+ return getOutlineFromCMaps((char)idx, width);
+ }
+ return null;
+ }
+
+ /**
+ * Get the outline of a character given the glyph id
+ */
+ protected synchronized Path getOutline(int glyphId, float width) {
+ // find the glyph itself
+ GlyfTable glyf = (GlyfTable) font.getTable("glyf");
+ Glyf g = glyf.getGlyph(glyphId);
+
+ Path gp = null;
+ if (g instanceof GlyfSimple) {
+ gp = renderSimpleGlyph((GlyfSimple) g);
+ } else if (g instanceof GlyfCompound) {
+ gp = renderCompoundGlyph(glyf, (GlyfCompound) g);
+ } else {
+ gp = new Path();
+ }
+
+ // calculate the advance
+ HmtxTable hmtx = (HmtxTable) font.getTable("hmtx");
+ float advance = (float) hmtx.getAdvance(glyphId) / (float) unitsPerEm;
+
+ // scale the glyph to match the desired advance
+ float widthfactor = width / advance;
+
+ // the base transform scales the glyph to 1x1
+ Matrix at = new Matrix();
+ at.setScale(1 / unitsPerEm, 1 / unitsPerEm);
+ Matrix tmp = new Matrix();
+ tmp.setScale(widthfactor, 1);
+ at.preConcat(tmp);
+
+ gp.transform(at);
+
+ return gp;
+ }
+
+ /**
+ * Render a simple glyf
+ */
+ protected Path renderSimpleGlyph(GlyfSimple g) {
+ // the current contour
+ int curContour = 0;
+
+ // the render state
+ RenderState rs = new RenderState();
+ rs.gp = new Path();
+
+ for (int i = 0; i < g.getNumPoints(); i++) {
+ PointRec rec = new PointRec(g, i);
+
+ if (rec.onCurve) {
+ addOnCurvePoint(rec, rs);
+ } else {
+ addOffCurvePoint(rec, rs);
+ }
+
+ // see if we just ended a contour
+ if (i == g.getContourEndPoint(curContour)) {
+ curContour++;
+
+ if (rs.firstOff != null) {
+ addOffCurvePoint(rs.firstOff, rs);
+ }
+
+ if (rs.firstOn != null) {
+ addOnCurvePoint(rs.firstOn, rs);
+ }
+
+ rs.firstOn = null;
+ rs.firstOff = null;
+ rs.prevOff = null;
+ }
+ }
+
+ return rs.gp;
+ }
+
+// --- OLD
+//
+
+// protected GeneralPath renderCompoundGlyph(GlyfTable glyf, GlyfCompound g) {
+// GeneralPath gp = new GeneralPath();
+//
+// for (int i = 0; i < g.getNumComponents(); i++) {
+// // find and render the component glyf
+// GlyfSimple gs = (GlyfSimple) glyf.getGlyph(g.getGlyphIndex(i));
+// GeneralPath path = renderSimpleGlyph(gs);
+//
+// // multiply the translations by units per em
+// double[] matrix = g.getTransform(i);
+//
+// // transform the path
+// path.transform(new AffineTransform(matrix));
+//
+// // add it to the global path
+// gp.append(path, false);
+// }
+//
+// return gp;
+// }
+//
+// --- NEW
+//
+// protected GeneralPath renderCompoundGlyph (GlyfTable glyf, GlyfCompound g) {
+// GeneralPath gp = new GeneralPath ();
+//
+// for (int i = 0; i < g.getNumComponents (); i++) {
+// // find and render the component glyf
+// Glyf gl = glyf.getGlyph (g.getGlyphIndex (i));
+// GeneralPath path = null;
+// if (gl instanceof GlyfSimple) {
+// path = renderSimpleGlyph ((GlyfSimple) gl);
+// } else if (gl instanceof GlyfCompound) {
+// path = renderCompoundGlyph (glyf, (GlyfCompound) gl);
+// } else {
+// throw new RuntimeException (
+// "Unsupported glyph type " + gl.getClass ().getCanonicalName ());
+// }
+//
+// // multiply the translations by units per em
+// double[] matrix = g.getTransform (i);
+//
+// // transform the path
+// path.transform (new AffineTransform (matrix));
+//
+// // add it to the global path
+// gp.append (path, false);
+// }
+
+ /**
+ * Render a compound glyf
+ */
+ protected Path renderCompoundGlyph(GlyfTable glyf, GlyfCompound g) {
+ Path gp = new Path();
+
+ for (int i = 0; i < g.getNumComponents(); i++) {
+ // find and render the component glyf
+ Glyf gl = glyf.getGlyph (g.getGlyphIndex (i));
+ Path path = null;
+ if (gl instanceof GlyfSimple) {
+ path = renderSimpleGlyph ((GlyfSimple) gl);
+ } else if (gl instanceof GlyfCompound) {
+ path = renderCompoundGlyph (glyf, (GlyfCompound) gl);
+ } else {
+ throw new RuntimeException (
+ "Unsupported glyph type " + gl.getClass ().getCanonicalName ());
+ }
+
+ // multiply the translations by units per em
+ float[] matrix = g.getTransform(i);
+
+ // transform and add path to the global path
+ Matrix mat = new Matrix();
+ Utils.setMatValues(mat, matrix);
+ gp.addPath(path, mat);
+ }
+
+ return gp;
+ }
+
+ /** add a point on the curve */
+ private void addOnCurvePoint(PointRec rec, RenderState rs) {
+ // if the point is on the curve, either move to it,
+ // or draw a line from the previous point
+ if (rs.firstOn == null) {
+ rs.firstOn = rec;
+ rs.gp.moveTo(rec.x, rec.y);
+ } else if (rs.prevOff != null) {
+ rs.gp.quadTo(rs.prevOff.x, rs.prevOff.y, rec.x, rec.y);
+ rs.prevOff = null;
+ } else {
+ rs.gp.lineTo(rec.x, rec.y);
+ }
+ }
+
+ /** add a point off the curve */
+ private void addOffCurvePoint(PointRec rec, RenderState rs) {
+ if (rs.prevOff != null) {
+ PointRec oc = new PointRec((rec.x + rs.prevOff.x) / 2,
+ (rec.y + rs.prevOff.y) / 2,
+ true);
+ addOnCurvePoint(oc, rs);
+ } else if (rs.firstOn == null) {
+ rs.firstOff = rec;
+ }
+ rs.prevOff = rec;
+ }
+
+ class RenderState {
+ // the shape itself
+ Path gp;
+ // the first off and on-curve points in the current segment
+ PointRec firstOn;
+ PointRec firstOff;
+ // the previous off and on-curve points in the current segment
+ PointRec prevOff;
+ }
+
+ /** a point on the stack of points */
+ class PointRec {
+
+ int x;
+ int y;
+ boolean onCurve;
+
+ public PointRec(int x, int y, boolean onCurve) {
+ this.x = x;
+ this.y = y;
+ this.onCurve = onCurve;
+ }
+
+ public PointRec(GlyfSimple g, int idx) {
+ x = g.getXCoord(idx);
+ y = g.getYCoord(idx);
+ onCurve = g.onCurve(idx);
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/Type0Font.java b/PdfView/src/main/java/com/sun/pdfview/font/Type0Font.java
new file mode 100644
index 0000000..c278756
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/Type0Font.java
@@ -0,0 +1,68 @@
+/*
+ * $Id: Type0Font.java,v 1.2 2007/12/20 18:33:32 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.font;
+
+import java.io.IOException;
+
+import com.sun.pdfview.PDFObject;
+
+/**
+ * Type 0 fonts are composite fonts with a CMAP to map between
+ * source character codes and destination fonts/codes
+ *
+ * @author Jonathan Kaplan
+ */
+public class Type0Font extends PDFFont {
+
+ /**
+ * The decendant fonts, indexed by font number from the CMAP
+ */
+ PDFFont[] fonts;
+
+ /** Creates a new instance of Type0Font */
+ public Type0Font(String baseFont, PDFObject fontObj,
+ PDFFontDescriptor descriptor) throws IOException {
+ super (baseFont, descriptor);
+
+ PDFObject[] descendantFonts = fontObj.getDictRef("DescendantFonts").getArray();
+
+ fonts = new PDFFont[descendantFonts.length];
+
+ for (int i = 0; i < descendantFonts.length; i++) {
+ fonts[i] = PDFFont.getFont(descendantFonts[i], null);
+ }
+ }
+
+ /**
+ * Get a descendant font of this font by fontId
+ */
+ public PDFFont getDescendantFont(int fontID) {
+ return fonts[fontID];
+ }
+
+ /**
+ * Get a character from the first font in the descendant fonts array
+ */
+ protected PDFGlyph getGlyph(char src, String name) {
+ return (getDescendantFont(0).getGlyph(src, name));
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/Type1CFont.java b/PdfView/src/main/java/com/sun/pdfview/font/Type1CFont.java
new file mode 100644
index 0000000..d192171
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/Type1CFont.java
@@ -0,0 +1,1202 @@
+/*
+ * $Id: Type1CFont.java,v 1.3 2009/03/09 10:18:03 tomoke 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.font;
+
+import java.io.IOException;
+
+import net.sf.andpdf.utils.Utils;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+
+import com.sun.pdfview.PDFObject;
+
+
+/**
+ * A representation, with parser, of an Adobe Type 1C font.
+ * @author Mike Wessler
+ */
+public class Type1CFont extends OutlineFont {
+
+ String chr2name[] = new String[256];
+
+ byte[] data;
+
+ int pos;
+
+ byte[] subrs;
+
+ float[] stack = new float[100];
+
+ int stackptr = 0;
+
+ String names[];
+
+ int glyphnames[];
+
+ int encoding[] = new int[256];
+
+ String fontname;
+
+ Matrix at = Utils.createMatrix(0.001f, 0, 0, 0.001f, 0, 0);
+
+ int num;
+
+ float fnum;
+
+ int type;
+
+ static int CMD = 0;
+
+ static int NUM = 1;
+
+ static int FLT = 2;
+
+ /**
+ * create a new Type1CFont based on a font data stream and a descriptor
+ * @param baseFont the postscript name of this font
+ * @param src a stream containing the font
+ * @param descriptor the descriptor for this font
+ */
+ public Type1CFont (String baseFont, PDFObject src,
+ PDFFontDescriptor descriptor) throws IOException {
+ super (baseFont, src, descriptor);
+
+ if (!PDFFont.sUseFontSubstitution) {
+ PDFObject dataObj = descriptor.getFontFile3 ();
+ data = dataObj.getStream ();
+ }
+ pos = 0;
+ if (!PDFFont.sUseFontSubstitution) {
+ parse ();
+ }
+
+ // TODO: free up (set to null) unused structures (data, subrs, stack)
+ }
+
+ /**
+ * a debug method for printing the data
+ */
+ private void printData () {
+ char[] parts = new char[17];
+ int partsloc = 0;
+ for (int i = 0; i < data.length; i++) {
+ int d = ((int) data[i]) & 0xff;
+ if (d == 0) {
+ parts[partsloc++] = '.';
+ } else if (d < 32 || d >= 127) {
+ parts[partsloc++] = '?';
+ } else {
+ parts[partsloc++] = (char) d;
+ }
+ if (d < 16) {
+ System.out.print ("0" + Integer.toHexString (d));
+ } else {
+ System.out.print (Integer.toHexString (d));
+ }
+ if ((i & 15) == 15) {
+ System.out.println (" " + new String (parts));
+ partsloc = 0;
+ } else if ((i & 7) == 7) {
+ System.out.print (" ");
+ parts[partsloc++] = ' ';
+ } else if ((i & 1) == 1) {
+ System.out.print (" ");
+ }
+ }
+ System.out.println ();
+ }
+
+ /**
+ * read the next decoded value from the stream
+ * @param charstring ????
+ */
+ private int readNext (boolean charstring) {
+ num = (int) (data[pos++]) & 0xff;
+ if (num == 30 && !charstring) { // goofy floatingpoint rep
+ readFNum ();
+ return type = FLT;
+ } else if (num == 28) {
+ num = (((int) data[pos]) << 8) + (((int) data[pos + 1]) & 0xff);
+ pos += 2;
+ return type = NUM;
+ } else if (num == 29 && !charstring) {
+ num = (((int) data[pos] & 0xff) << 24) |
+ (((int) data[pos + 1] & 0xff) << 16) |
+ (((int) data[pos + 2] & 0xff) << 8) |
+ (((int) data[pos + 3] & 0xff));
+ pos += 4;
+ return type = NUM;
+ } else if (num == 12) { // two-byte command
+ num = 1000 + ((int) (data[pos++]) & 0xff);
+ return type = CMD;
+ } else if (num < 32) {
+ return type = CMD;
+ } else if (num < 247) {
+ num -= 139;
+ return type = NUM;
+ } else if (num < 251) {
+ num = (num - 247) * 256 + (((int) data[pos++]) & 0xff) + 108;
+ return type = NUM;
+ } else if (num < 255) {
+ num = -(num - 251) * 256 - (((int) data[pos++]) & 0xff) - 108;
+ return type = NUM;
+ } else if (!charstring) { // dict shouldn't have a 255 code
+ printData ();
+ throw new RuntimeException ("Got a 255 code while reading dict");
+ } else { // num was 255
+ fnum = ((((int) data[pos] & 0xff) << 24) |
+ (((int) data[pos + 1] & 0xff) << 16) |
+ (((int) data[pos + 2] & 0xff) << 8) |
+ (((int) data[pos + 3] & 0xff))) / 65536f;
+ pos += 4;
+ return type = FLT;
+ }
+ }
+
+ /**
+ * read the next funky floating point number from the input stream.
+ * value gets put into the fnum field.
+ */
+ public void readFNum () {
+ // work in nybbles: 0-9=0-9, a=. b=E, c=E-, d=rsvd e=neg f=end
+ float f = 0;
+ boolean neg = false;
+ int exp = 0;
+ int eval = 0;
+ float mul = 1;
+ byte work = data[pos++];
+ while (true) {
+ if (work == (byte) 0xdd) {
+ work = data[pos++];
+ }
+ int nyb = (work >> 4) & 0xf;
+ work = (byte) ((work << 4) | 0xd);
+ if (nyb < 10) {
+ if (exp != 0) { // working on the exponent
+ eval = eval * 10 + nyb;
+ } else if (mul == 1) { // working on an int
+ f = f * 10 + nyb;
+ } else { // working on decimal part
+ f += nyb * mul;
+ mul /= 10f;
+ }
+ } else if (nyb == 0xa) { // decimal
+ mul = 0.1f;
+ } else if (nyb == 0xb) { // E+
+ exp = 1;
+ } else if (nyb == 0xc) { // E-
+ exp = -1;
+ } else if (nyb == 0xe) { // neg
+ neg = true;
+ } else {
+ break;
+ }
+ }
+ fnum = (neg ? -1 : 1) * f * (float) Math.pow (10, eval * exp);
+ }
+
+ /**
+ * read an integer from the input stream
+ * @param len the number of bytes in the integer
+ * @return the integer
+ */
+ private int readInt (int len) {
+ int n = 0;
+ for (int i = 0; i < len; i++) {
+ n = (n << 8) | (((int) data[pos++]) & 0xff);
+ }
+ return n;
+ }
+
+ /**
+ * read the next byte from the stream
+ * @return the byte
+ */
+ private int readByte () {
+ return ((int) data[pos++]) & 0xff;
+ }
+
+ // DICT structure:
+ // operand operator operand operator ...
+ // INDEX structure:
+ // count(2) offsize [offset offset ... offset] data
+ // offset array has count+1 entries
+ // data starts at 3+(count+1)*offsize
+ // offset for data is offset+2+(count+1)*offsize
+ /**
+ * get the size of the dictionary located within the stream at
+ * some offset.
+ * @param loc the index of the start of the dictionary
+ * @return the size of the dictionary, in bytes.
+ */
+ public int getIndexSize (int loc) {
+ // System.out.println("Getting size of index at "+loc);
+ int hold = pos;
+ pos = loc;
+ int count = readInt (2);
+ if (count <= 0) {
+ return 2;
+ }
+ int encsz = readByte ();
+ if (encsz < 1 || encsz > 4) {
+ throw new RuntimeException ("Offsize: " + encsz +
+ ", must be in range 1-4.");
+ }
+ // pos is now at the first offset. last offset is at count*encsz
+ pos += count * encsz;
+ int end = readInt (encsz);
+ pos = hold;
+ return 2 + (count + 1) * encsz + end;
+ }
+
+ /**
+ * return the number of entries in an Index table.
+ *
+ * @param loc
+ * @return
+ */
+ public int getTableLength (int loc) {
+ int hold = pos;
+ pos = loc;
+ int count = readInt (2);
+ if (count <= 0) {
+ return 2;
+ }
+ pos = hold;
+ return count;
+ }
+
+ /**
+ * A range. There's probably a version of this class floating around
+ * somewhere already in Java.
+ */
+ class Range {
+
+ private int start;
+
+ private int len;
+
+ public Range (int start, int len) {
+ this.start = start;
+ this.len = len;
+ }
+
+ public final int getStart () {
+ return start;
+ }
+
+ public final int getLen () {
+ return len;
+ }
+
+ public final int getEnd () {
+ return start + len;
+ }
+
+ public String toString () {
+ return "Range: start: " + start + ", len: " + len;
+ }
+ }
+
+ /**
+ * Get the range of a particular index in a dictionary.
+ * @param index the start of the dictionary.
+ * @param id the index of the entry in the dictionary
+ * @return a range describing the offsets of the start and end of
+ * the entry from the start of the file, not the dictionary
+ */
+ Range getIndexEntry (int index, int id) {
+ int hold = pos;
+ pos = index;
+ int count = readInt (2);
+ int encsz = readByte ();
+ if (encsz < 1 || encsz > 4) {
+ throw new RuntimeException ("Offsize: " + encsz +
+ ", must be in range 1-4.");
+ }
+ pos += encsz * id;
+ int from = readInt (encsz);
+ Range r = new Range (from + 2 + index + encsz * (count + 1), readInt (
+ encsz) - from);
+ pos = hold;
+ return r;
+ }
+ // Top DICT: NAME CODE DEFAULT
+ // charstringtype 12 6 2
+ // fontmatrix 12 7 0.001 0 0 0.001
+ // charset 15 - (offset) names of glyphs (ref to name idx)
+ // encoding 16 - (offset) array of codes
+ // CharStrings 17 - (offset)
+ // Private 18 - (size, offset)
+ // glyph at position i in CharStrings has name charset[i]
+ // and code encoding[i]
+ int charstringtype = 2;
+
+ float temps[] = new float[32];
+
+ int charsetbase = 0;
+
+ int encodingbase = 0;
+
+ int charstringbase = 0;
+
+ int privatebase = 0;
+
+ int privatesize = 0;
+
+ int gsubrbase = 0;
+
+ int lsubrbase = 0;
+
+ int gsubrsoffset = 0;
+
+ int lsubrsoffset = 0;
+
+ int nglyphs = 1;
+
+ /**
+ * read a dictionary that exists within some range, parsing the entries
+ * within the dictionary.
+ */
+ private void readDict (Range r) {
+ // System.out.println("reading dictionary from "+r.getStart()+" to "+r.getEnd());
+ pos = r.getStart ();
+ while (pos < r.getEnd ()) {
+ int cmd = readCommand (false);
+ if (cmd == 1006) { // charstringtype, default=2
+ charstringtype = (int) stack[0];
+ } else if (cmd == 1007) { // fontmatrix
+ if (stackptr == 4) {
+ at = Utils.createMatrix((float) stack[0], (float) stack[1],
+ (float) stack[2], (float) stack[3],
+ 0, 0);
+ } else {
+ at = Utils.createMatrix((float) stack[0], (float) stack[1],
+ (float) stack[2], (float) stack[3],
+ (float) stack[4], (float) stack[5]);
+ }
+ } else if (cmd == 15) { // charset
+ charsetbase = (int) stack[0];
+ } else if (cmd == 16) { // encoding
+ encodingbase = (int) stack[0];
+ } else if (cmd == 17) { // charstrings
+ charstringbase = (int) stack[0];
+ } else if (cmd == 18) { // private
+ privatesize = (int) stack[0];
+ privatebase = (int) stack[1];
+ } else if (cmd == 19) { // subrs (in Private dict)
+ lsubrbase = privatebase + (int) stack[0];
+ lsubrsoffset = calcoffset (lsubrbase);
+ }
+ stackptr = 0;
+ }
+ }
+
+ /**
+ * read a complete command. this may involve several numbers
+ * which go onto a stack before an actual command is read.
+ * @param charstring ????
+ * @return the command. Some numbers may also be on the stack.
+ */
+ private int readCommand (boolean charstring) {
+ while (true) {
+ int t = readNext (charstring);
+ if (t == CMD) {
+ /*
+ System.out.print("CMD= "+num+", args=");
+ for (int i=0; i" where ## is the value of the byte.
+ */
+ private String safe (String src) {
+ StringBuffer sb = new StringBuffer ();
+ for (int i = 0; i < src.length (); i++) {
+ char c = src.charAt (i);
+ if (c >= 32 && c < 128) {
+ sb.append (c);
+ } else {
+ sb.append ("<" + (int) c + ">");
+ }
+ }
+ return sb.toString ();
+ }
+
+ /**
+ * Read the data for a glyph from the glyph table, and transform
+ * it based on the current transform.
+ *
+ * @param base the start of the glyph table
+ * @param offset the index of this glyph in the glyph table
+ */
+ private synchronized Path readGlyph (int base, int offset) {
+ FlPoint pt = new FlPoint ();
+
+ // find this entry
+ Range r = getIndexEntry (base, offset);
+
+ // create a path
+ Path gp = new Path ();
+
+
+ // rember the start position (for recursive calls due to seac)
+ int hold = pos;
+
+ // read the glyph itself
+ stackptr = 0;
+ parseGlyph (r, gp, pt);
+
+ // restore the start position
+ pos = hold;
+
+ gp.transform (at);
+
+ return gp;
+ }
+
+ /**
+ * calculate an offset code for a dictionary. Uses the count of entries
+ * to determine what the offset should be.
+ *
+ * @param base the index of the start of the dictionary
+ */
+ public int calcoffset (int base) {
+ int len = getTableLength (base);
+ if (len < 1240) {
+ return 107;
+ } else if (len < 33900) {
+ return 1131;
+ } else {
+ return 32768;
+ }
+ }
+
+ /**
+ * get the name associated with an ID.
+ * @param id the index of the name
+ * @return the name from the FontSupport.stdNames table augmented
+ * by the local name table
+ */
+ public String getSID (int id) {
+ if (id < FontSupport.stdNames.length) {
+ return FontSupport.stdNames[id];
+ } else {
+ id -= FontSupport.stdNames.length;
+ return names[id];
+ }
+ }
+
+ /**
+ * build an accented character out of two pre-defined glyphs.
+ * @param x the x offset of the accent
+ * @param y the y offset of the accent
+ * @param b the index of the base glyph
+ * @param a the index of the accent glyph
+ * @param gp the GeneralPath into which the combined glyph will be
+ * written.
+ */
+ private void buildAccentChar (float x, float y, char b, char a,
+ Path gp) {
+ // get the outline of the accent
+ Path pathA = getOutline (a, getWidth (a, null));
+
+ // undo the effect of the transform applied in read
+ Matrix xformA = new Matrix();
+ xformA.setTranslate(x, y);
+ Matrix tmp = new Matrix(at);
+ if (at.invert(tmp)) {
+ xformA.preConcat(tmp);
+ }
+ else {
+ // oh well ...
+ }
+ pathA.transform (xformA);
+
+ Path pathB = getOutline (b, getWidth (b, null));
+
+ Matrix xformB = new Matrix();
+ if (at.invert(xformB)) {
+ pathB.transform(xformB);
+ } else {
+ // ignore
+ }
+
+ gp.addPath(pathB);
+ gp.addPath(pathA);
+ }
+
+ /**
+ * parse a glyph defined in a particular range
+ * @param r the range of the glyph definition
+ * @param gp a GeneralPath in which to store the glyph outline
+ * @param pt a FlPoint representing the end of the current path
+ */
+ void parseGlyph (Range r, Path gp, FlPoint pt) {
+ pos = r.getStart ();
+ int i;
+ float x1, y1, x2, y2, x3, y3, ybase;
+ int hold;
+ int stemhints = 0;
+ while (pos < r.getEnd ()) {
+ int cmd = readCommand (true);
+ hold = 0;
+ switch (cmd) {
+ case 1: // hstem
+ case 3: // vstem
+ stackptr = 0;
+ break;
+ case 4: // vmoveto
+ if (stackptr > 1) { // this is the first call, arg1 is width
+ stack[0] = stack[1];
+ }
+ pt.y += stack[0];
+ if (pt.open) {
+ gp.close();
+ }
+ pt.open = false;
+ gp.moveTo (pt.x, pt.y);
+ stackptr = 0;
+ break;
+ case 5: // rlineto
+ for (i = 0; i < stackptr;) {
+ pt.x += stack[i++];
+ pt.y += stack[i++];
+ gp.lineTo (pt.x, pt.y);
+ }
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 6: // hlineto
+ for (i = 0; i < stackptr;) {
+ if ((i & 1) == 0) {
+ pt.x += stack[i++];
+ } else {
+ pt.y += stack[i++];
+ }
+ gp.lineTo (pt.x, pt.y);
+ }
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 7: // vlineto
+ for (i = 0; i < stackptr;) {
+ if ((i & 1) == 0) {
+ pt.y += stack[i++];
+ } else {
+ pt.x += stack[i++];
+ }
+ gp.lineTo (pt.x, pt.y);
+ }
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 8: // rrcurveto
+ for (i = 0; i < stackptr;) {
+ x1 = pt.x + stack[i++];
+ y1 = pt.y + stack[i++];
+ x2 = x1 + stack[i++];
+ y2 = y1 + stack[i++];
+ pt.x = x2 + stack[i++];
+ pt.y = y2 + stack[i++];
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ }
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 10: // callsubr
+ hold = pos;
+ i = (int) stack[--stackptr] + lsubrsoffset;
+ Range lsubr = getIndexEntry (lsubrbase, i);
+ parseGlyph (lsubr, gp, pt);
+ pos = hold;
+ break;
+ case 11: // return
+ return;
+ case 14: // endchar
+ // width x y achar bchar endchar == x y achar bchar seac
+ if (stackptr == 5) {
+ buildAccentChar (stack[1], stack[2], (char) stack[3],
+ (char) stack[4], gp);
+ }
+ if (pt.open) {
+ gp.close();
+ }
+ pt.open = false;
+ stackptr = 0;
+ break;
+ case 18: // hstemhm
+ stemhints += stackptr / 2;
+ stackptr = 0;
+ break;
+ case 19: // hintmask
+ case 20: // cntrmask
+ stemhints += stackptr / 2;
+ pos += (stemhints - 1) / 8 + 1;
+ stackptr = 0;
+ break;
+ case 21: // rmoveto
+ if (stackptr > 2) {
+ stack[0] = stack[1];
+ stack[1] = stack[2];
+ }
+ pt.x += stack[0];
+ pt.y += stack[1];
+ if (pt.open) {
+ gp.close();
+ }
+ gp.moveTo (pt.x, pt.y);
+ pt.open = false;
+ stackptr = 0;
+ break;
+ case 22: // hmoveto
+ if (stackptr > 1) {
+ stack[0] = stack[1];
+ }
+ pt.x += stack[0];
+ if (pt.open) {
+ gp.close();
+ }
+ gp.moveTo (pt.x, pt.y);
+ pt.open = false;
+ stackptr = 0;
+ break;
+ case 23: // vstemhm
+ stemhints += stackptr / 2;
+ stackptr = 0;
+ break;
+ case 24: // rcurveline
+ for (i = 0; i < stackptr - 2;) {
+ x1 = pt.x + stack[i++];
+ y1 = pt.y + stack[i++];
+ x2 = x1 + stack[i++];
+ y2 = y1 + stack[i++];
+ pt.x = x2 + stack[i++];
+ pt.y = y2 + stack[i++];
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ }
+ pt.x += stack[i++];
+ pt.y += stack[i++];
+ gp.lineTo (pt.x, pt.y);
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 25: // rlinecurve
+ for (i = 0; i < stackptr - 6;) {
+ pt.x += stack[i++];
+ pt.y += stack[i++];
+ gp.lineTo (pt.x, pt.y);
+ }
+ x1 = pt.x + stack[i++];
+ y1 = pt.y + stack[i++];
+ x2 = x1 + stack[i++];
+ y2 = y1 + stack[i++];
+ pt.x = x2 + stack[i++];
+ pt.y = y2 + stack[i++];
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 26: // vvcurveto
+ i = 0;
+ if ((stackptr & 1) == 1) { // odd number of arguments
+ pt.x += stack[i++];
+ }
+ while (i < stackptr) {
+ x1 = pt.x;
+ y1 = pt.y + stack[i++];
+ x2 = x1 + stack[i++];
+ y2 = y1 + stack[i++];
+ pt.x = x2;
+ pt.y = y2 + stack[i++];
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ }
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 27: // hhcurveto
+ i = 0;
+ if ((stackptr & 1) == 1) { // odd number of arguments
+ pt.y += stack[i++];
+ }
+ while (i < stackptr) {
+ x1 = pt.x + stack[i++];
+ y1 = pt.y;
+ x2 = x1 + stack[i++];
+ y2 = y1 + stack[i++];
+ pt.x = x2 + stack[i++];
+ pt.y = y2;
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ }
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 29: // callgsubr
+ hold = pos;
+ i = (int) stack[--stackptr] + gsubrsoffset;
+ Range gsubr = getIndexEntry (gsubrbase, i);
+ parseGlyph (gsubr, gp, pt);
+ pos = hold;
+ break;
+ case 30: // vhcurveto
+ hold = 4;
+ case 31: // hvcurveto
+ for (i = 0; i < stackptr;) {
+ boolean hv = (((i + hold) & 4) == 0);
+ x1 = pt.x + (hv ? stack[i++] : 0);
+ y1 = pt.y + (hv ? 0 : stack[i++]);
+ x2 = x1 + stack[i++];
+ y2 = y1 + stack[i++];
+ pt.x = x2 + (hv ? 0 : stack[i++]);
+ pt.y = y2 + (hv ? stack[i++] : 0);
+ if (i == stackptr - 1) {
+ if (hv) {
+ pt.x += stack[i++];
+ } else {
+ pt.y += stack[i++];
+ }
+ }
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ }
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 1000: // old dotsection command. ignore.
+ stackptr = 0;
+ break;
+ case 1003: // and
+ x1 = stack[--stackptr];
+ y1 = stack[--stackptr];
+ stack[stackptr++] = ((x1 != 0) && (y1 != 0)) ? 1 : 0;
+ break;
+ case 1004: // or
+ x1 = stack[--stackptr];
+ y1 = stack[--stackptr];
+ stack[stackptr++] = ((x1 != 0) || (y1 != 0)) ? 1 : 0;
+ break;
+ case 1005: // not
+ x1 = stack[--stackptr];
+ stack[stackptr++] = (x1 == 0) ? 1 : 0;
+ break;
+ case 1009: // abs
+ stack[stackptr - 1] = Math.abs (stack[stackptr - 1]);
+ break;
+ case 1010: // add
+ x1 = stack[--stackptr];
+ y1 = stack[--stackptr];
+ stack[stackptr++] = x1 + y1;
+ break;
+ case 1011: // sub
+ x1 = stack[--stackptr];
+ y1 = stack[--stackptr];
+ stack[stackptr++] = y1 - x1;
+ break;
+ case 1012: // div
+ x1 = stack[--stackptr];
+ y1 = stack[--stackptr];
+ stack[stackptr++] = y1 / x1;
+ break;
+ case 1014: // neg
+ stack[stackptr - 1] = -stack[stackptr - 1];
+ break;
+ case 1015: // eq
+ x1 = stack[--stackptr];
+ y1 = stack[--stackptr];
+ stack[stackptr++] = (x1 == y1) ? 1 : 0;
+ break;
+ case 1018: // drop
+ stackptr--;
+ break;
+ case 1020: // put
+ i = (int) stack[--stackptr];
+ x1 = stack[--stackptr];
+ temps[i] = x1;
+ break;
+ case 1021: // get
+ i = (int) stack[--stackptr];
+ stack[stackptr++] = temps[i];
+ break;
+ case 1022: // ifelse
+ if (stack[stackptr - 2] > stack[stackptr - 1]) {
+ stack[stackptr - 4] = stack[stackptr - 3];
+ }
+ stackptr -= 3;
+ break;
+ case 1023: // random
+ stack[stackptr++] = (float) Math.random ();
+ break;
+ case 1024: // mul
+ x1 = stack[--stackptr];
+ y1 = stack[--stackptr];
+ stack[stackptr++] = y1 * x1;
+ break;
+ case 1026: // sqrt
+ stack[stackptr - 1] = (float) Math.sqrt (stack[stackptr - 1]);
+ break;
+ case 1027: // dup
+ x1 = stack[stackptr - 1];
+ stack[stackptr++] = x1;
+ break;
+ case 1028: // exch
+ x1 = stack[stackptr - 1];
+ stack[stackptr - 1] = stack[stackptr - 2];
+ stack[stackptr - 2] = x1;
+ break;
+ case 1029: // index
+ i = (int) stack[stackptr - 1];
+ if (i < 0) {
+ i = 0;
+ }
+ stack[stackptr - 1] = stack[stackptr - 2 - i];
+ break;
+ case 1030: // roll
+ i = (int) stack[--stackptr];
+ int n = (int) stack[--stackptr];
+ // roll n number by i (+ = upward)
+ if (i > 0) {
+ i = i % n;
+ } else {
+ i = n - (-i % n);
+ }
+ // x x x x i y y y -> y y y x x x x i (where i=3)
+ if (i > 0) {
+ float roll[] = new float[n];
+ System.arraycopy (stack, stackptr - 1 - i, roll, 0, i);
+ System.arraycopy (stack, stackptr - 1 - n, roll, i,
+ n - i);
+ System.arraycopy (roll, 0, stack, stackptr - 1 - n, n);
+ }
+ break;
+ case 1034: // hflex
+ x1 = pt.x + stack[0];
+ y1 = ybase = pt.y;
+ x2 = x1 + stack[1];
+ y2 = y1 + stack[2];
+ pt.x = x2 + stack[3];
+ pt.y = y2;
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ x1 = pt.x + stack[4];
+ y1 = pt.y;
+ x2 = x1 + stack[5];
+ y2 = ybase;
+ pt.x = x2 + stack[6];
+ pt.y = y2;
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 1035: // flex
+ x1 = pt.x + stack[0];
+ y1 = pt.y + stack[1];
+ x2 = x1 + stack[2];
+ y2 = y1 + stack[3];
+ pt.x = x2 + stack[4];
+ pt.y = y2 + stack[5];
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ x1 = pt.x + stack[6];
+ y1 = pt.y + stack[7];
+ x2 = x1 + stack[8];
+ y2 = y1 + stack[9];
+ pt.x = x2 + stack[10];
+ pt.y = y2 + stack[11];
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 1036: // hflex1
+ ybase = pt.y;
+ x1 = pt.x + stack[0];
+ y1 = pt.y + stack[1];
+ x2 = x1 + stack[2];
+ y2 = y1 + stack[3];
+ pt.x = x2 + stack[4];
+ pt.y = y2;
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ x1 = pt.x + stack[5];
+ y1 = pt.y;
+ x2 = x1 + stack[6];
+ y2 = y1 + stack[7];
+ pt.x = x2 + stack[8];
+ pt.y = ybase;
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ pt.open = true;
+ stackptr = 0;
+ break;
+ case 1037: // flex1
+ ybase = pt.y;
+ float xbase = pt.x;
+ x1 = pt.x + stack[0];
+ y1 = pt.y + stack[1];
+ x2 = x1 + stack[2];
+ y2 = y1 + stack[3];
+ pt.x = x2 + stack[4];
+ pt.y = y2 + stack[5];
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ x1 = pt.x + stack[6];
+ y1 = pt.y + stack[7];
+ x2 = x1 + stack[8];
+ y2 = y1 + stack[9];
+ if (Math.abs (x2 - xbase) > Math.abs (y2 - ybase)) {
+ pt.x = x2 + stack[10];
+ pt.y = ybase;
+ } else {
+ pt.x = xbase;
+ pt.y = y2 + stack[10];
+ }
+ gp.cubicTo(x1, y1, x2, y2, pt.x, pt.y);
+ pt.open = true;
+ stackptr = 0;
+ break;
+ default:
+ System.out.println ("ERROR! TYPE1C CHARSTRING CMD IS " + cmd);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Get a glyph outline by name
+ *
+ * @param name the name of the desired glyph
+ * @return the glyph outline, or null if unavailable
+ */
+ protected Path getOutline (String name, float width) {
+ // first find the index of this name
+ int index = getNameIndex (name);
+
+ // now find the glyph with that name
+ for (int i = 0; i < glyphnames.length; i++) {
+ if (glyphnames[i] == index) {
+ return readGlyph (charstringbase, i);
+ }
+ }
+
+ // not found -- return the unknown glyph
+ return readGlyph (charstringbase, 0);
+ }
+
+ /**
+ * Get a glyph outline by character code
+ *
+ * Note this method must always return an outline
+ *
+ * @param src the character code of the desired glyph
+ * @return the glyph outline
+ */
+ protected Path getOutline (char src, float width) {
+ // ignore high bits
+ int index = (int) (src & 0xff);
+
+ // if we use a standard encoding, the mapping is from glyph to SID
+ // therefore we must find the glyph index in the name table
+ if (encodingbase == 0 || encodingbase == 1) {
+ for (int i = 0; i < glyphnames.length; i++) {
+ if (glyphnames[i] == encoding[index]) {
+ return readGlyph (charstringbase, i);
+ }
+ }
+ } else {
+ // for a custom encoding, the mapping is from glyph to GID, so
+ // we can just map the glyph directly
+ if (index > 0 && index < encoding.length) {
+ return readGlyph (charstringbase, encoding[index]);
+ }
+ }
+
+ // for some reason the glyph was not found, return the empty glyph
+ return readGlyph (charstringbase, 0);
+ }
+}
+
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/Type1Font.java b/PdfView/src/main/java/com/sun/pdfview/font/Type1Font.java
new file mode 100644
index 0000000..2e937a3
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/Type1Font.java
@@ -0,0 +1,887 @@
+/*
+ * $Id: Type1Font.java,v 1.5 2009/02/12 13:53:54 tomoke 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.font;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import net.sf.andpdf.utils.Utils;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PointF;
+
+import com.sun.pdfview.PDFFile;
+import com.sun.pdfview.PDFObject;
+
+
+/**
+ * A representation, with parser, of an Adobe Type 1 font.
+ * @author Mike Wessler
+ *
+ * @todo
+ * The parsing of the encrypted section needs work. A fix has just gone into
+ * readArray, but it is very heuristic (as is all of readArray) and simply
+ * stops the read at any invalid input.
+ */
+public class Type1Font extends OutlineFont {
+
+ String chr2name[];
+ int password;
+ byte[] subrs[];
+ int lenIV;
+ Map name2outline;
+ Map name2width;
+ Matrix at;
+ /** the Type1 stack of command values */
+ float stack[] = new float[100];
+ /** the current position in the Type1 stack */
+ int sloc = 0;
+ /** the stack of postscript commands (used by callothersubr) */
+ float psStack[] = new float[3];
+ /** the current position in the postscript stack */
+ int psLoc = 0;
+
+ /**
+ * create a new Type1Font based on a font data stream and an encoding.
+ * @param baseName the postscript name of this font
+ * @param src the Font object as a stream with a dictionary
+ * @param descriptor the descriptor for this font
+ */
+ public Type1Font(String baseName, PDFObject src,
+ PDFFontDescriptor descriptor) throws IOException {
+ super(baseName, src, descriptor);
+
+ if (descriptor != null && descriptor.getFontFile() != null) {
+ // parse that file, filling name2outline and chr2name
+ int start = descriptor.getFontFile().getDictRef("Length1").getIntValue();
+ int len = descriptor.getFontFile().getDictRef("Length2").getIntValue();
+ byte font[] = descriptor.getFontFile().getStream();
+
+ parseFont(font, start, len);
+ }
+ }
+
+ /** Read a font from it's data, start position and length */
+ protected void parseFont(byte[] font, int start, int len) {
+ name2width = new HashMap();
+
+ byte data[] = null;
+
+ if (isASCII(font, start)) {
+ byte[] bData = readASCII(font, start, start + len);
+ data = decrypt(bData, 0, bData.length, 55665, 4);
+ } else {
+ data = decrypt(font, start, start + len, 55665, 4);
+ }
+
+ // encoding is in cleartext area
+ chr2name = readEncoding(font);
+ int lenIVLoc = findSlashName(data, "lenIV");
+ PSParser psp = new PSParser(data, 0);
+ if (lenIVLoc < 0) {
+ lenIV = 4;
+ } else {
+ psp.setLoc(lenIVLoc + 6);
+ lenIV = Integer.parseInt(psp.readThing());
+ }
+ password = 4330;
+ int matrixloc = findSlashName(font, "FontMatrix");
+ if (matrixloc < 0) {
+ System.out.println("No FontMatrix!");
+ at = Utils.createMatrix(0.001f, 0, 0, 0.001f, 0, 0);
+ } else {
+ PSParser psp2 = new PSParser(font, matrixloc + 11);
+ // read [num num num num num num]
+ float xf[] = psp2.readArray(6);
+ // System.out.println("FONT MATRIX: "+xf);
+ at = Utils.createMatrix(xf);
+ }
+
+ subrs = readSubrs(data);
+ name2outline = new TreeMap(readChars(data));
+ // at this point, name2outline holds name -> byte[].
+ }
+
+ /**
+ * parse the encoding portion of the font definition
+ * @param d the font definition stream
+ * @return an array of the glyphs corresponding to each byte
+ */
+ private String[] readEncoding(byte[] d) {
+ byte[][] ary = readArray(d, "Encoding", "def");
+ String res[] = new String[256];
+ for (int i = 0; i < ary.length; i++) {
+ if (ary[i] != null) {
+ if (ary[i][0] == '/') {
+ res[i] = new String(ary[i]).substring(1);
+ } else {
+ res[i] = new String(ary[i]);
+ }
+ } else {
+ res[i] = null;
+ }
+ }
+ return res;
+ }
+
+ /**
+ * read the subroutines out of the font definition
+ * @param d the font definition stream
+ * @return an array of the subroutines, each as a byte array.
+ */
+ private byte[][] readSubrs(byte[] d) {
+ return readArray(d, "Subrs", "index");
+ }
+
+ /**
+ * read a named array out of the font definition.
+ *
+ * this function attempts to parse an array out of a postscript
+ * definition without doing any postscript. It's actually looking
+ * for things that look like "dup idelt put", and
+ * placing the elt at the ith position in the array.
+ * @param d the font definition stream
+ * @param key the name of the array
+ * @param end a string that appears at the end of the array
+ * @return an array consisting of a byte array for each entry
+ */
+ private byte[][] readArray(byte[] d, String key, String end) {
+ int i = findSlashName(d, key);
+ if (i < 0) {
+ // not found.
+ return new byte[0][];
+ }
+ // now find things that look like "dup id elt put"
+ // end at "def"
+ PSParser psp = new PSParser(d, i);
+ String type = psp.readThing(); // read the key (i is the start of the key)
+ double val;
+ type = psp.readThing();
+ if (type.equals("StandardEncoding")) {
+ byte[] stdenc[] = new byte[FontSupport.standardEncoding.length][];
+ for (i = 0; i < stdenc.length; i++) {
+ stdenc[i] = FontSupport.getName(FontSupport.standardEncoding[i]).getBytes();
+ }
+ return stdenc;
+ }
+ int len = Integer.parseInt(type);
+ byte[] out[] = new byte[len][];
+ byte[] line;
+ while (true) {
+ String s = psp.readThing();
+ if (s.equals("dup")) {
+ String thing = psp.readThing();
+ int id = 0;
+ try {
+ id = Integer.parseInt(thing);
+ } catch (Exception e) {
+ break;
+ }
+ String elt = psp.readThing();
+ line = elt.getBytes();
+ if (Character.isDigit(elt.charAt(0))) {
+ int hold = Integer.parseInt(elt);
+ String special = psp.readThing();
+ if (special.equals("-|") || special.equals("RD")) {
+ psp.setLoc(psp.getLoc() + 1);
+ line = psp.getNEncodedBytes(hold, password, lenIV);
+ }
+ }
+ out[id] = line;
+ } else if (s.equals(end)) {
+ break;
+ }
+ }
+ return out;
+ }
+
+ /**
+ * decrypt an array using the Adobe Type 1 Font decryption algorithm.
+ * @param d the input array of bytes
+ * @param start where in the array to start decoding
+ * @param end where in the array to stop decoding
+ * @param key the decryption key
+ * @param skip how many bytes to skip initially
+ * @return the decrypted bytes. The length of this array will be
+ * (start-end-skip) bytes long
+ */
+ private byte[] decrypt(byte[] d, int start, int end, int key, int skip) {
+ if (end - start - skip < 0) {
+ skip = 0;
+ }
+ byte[] o = new byte[end - start - skip];
+ int r = key;
+ int ipos;
+ int c1 = 52845;
+ int c2 = 22719;
+ for (ipos = start; ipos < end; ipos++) {
+ int c = d[ipos] & 0xff;
+ int p = (c ^ (r >> 8)) & 0xff;
+ r = ((c + r) * c1 + c2) & 0xffff;
+ if (ipos - start - skip >= 0) {
+ o[ipos - start - skip] = (byte) p;
+ }
+ }
+ return o;
+ }
+
+ /**
+ * Read data formatted as ASCII strings as binary data
+ *
+ * @param data the data, formatted as ASCII strings
+ * @param start where in the array to start decrypting
+ * @param end where in the array to stop decrypting
+ */
+ private byte[] readASCII(byte[] data, int start, int end) {
+ // each byte of output is derived from one character (two bytes) of
+ // input
+ byte[] o = new byte[(end - start) / 2];
+
+ int count = 0;
+ int bit = 0;
+
+ for (int loc = start; loc < end; loc++) {
+ char c = (char) (data[loc] & 0xff);
+ byte b = (byte) 0;
+
+ if (c >= '0' && c <= '9') {
+ b = (byte) (c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ b = (byte) (10 + (c - 'a'));
+ } else if (c >= 'A' && c <= 'F') {
+ b = (byte) (10 + (c - 'A'));
+ } else {
+ // linefeed or something. Skip.
+ continue;
+ }
+
+ // which half of the byte are we?
+ if ((bit++ % 2) == 0) {
+ o[count] = (byte) (b << 4);
+ } else {
+ o[count++] |= b;
+ }
+ }
+
+ return o;
+ }
+
+ /**
+ * Determine if data is in ASCII or binary format. According to the spec,
+ * if any of the first 4 bytes are not character codes ('0' - '9' or
+ * 'A' - 'F' or 'a' - 'f'), then the data is binary. Otherwise it is
+ * ASCII
+ */
+ private boolean isASCII(byte[] data, int start) {
+ // look at the first 4 bytes
+ for (int i = start; i < start + 4; i++) {
+ // get the byte as a character
+ char c = (char) (data[i] & 0xff);
+
+ if (c >= '0' && c <= '9') {
+ continue;
+ } else if (c >= 'a' && c <= 'f') {
+ continue;
+ } else if (c >= 'A' && c <= 'F') {
+ continue;
+ } else {
+ // out of range
+ return false;
+ }
+ }
+
+ // all were in range, so it is ASCII
+ return true;
+ }
+
+ /**
+ * PostScript reader (not a parser, as the name would seem to indicate).
+ */
+ class PSParser {
+
+ byte[] data;
+ int loc;
+
+ /**
+ * create a PostScript reader given some data and an initial offset
+ * into that data.
+ * @param data the bytes of the postscript information
+ * @param start an initial offset into the data
+ */
+ public PSParser(byte[] data, int start) {
+ this.data = data;
+ this.loc = start;
+// System.out.println("PSParser.constructor: start: " + start +
+// ", Length: " + data.length);
+// System.out.print (new String(data, loc, data.length - loc));
+// System.out.println(" - end -\n");
+ }
+
+ /**
+ * get the next postscript "word". This is basically the next
+ * non-whitespace block between two whitespace delimiters.
+ * This means that something like " [2 4 53]" will produce
+ * three items, while " [2 4 56 ]" will produce four.
+ */
+ public String readThing() {
+ // skip whitespace
+// System.out.println("PAParser: whitespace: \"");
+ while (PDFFile.isWhiteSpace(data[loc])) {
+// System.out.print (new String(data, loc, 1));
+ loc++;
+ }
+// System.out.print("\": thing: ");
+ // read thing
+ int start = loc;
+ while (!PDFFile.isWhiteSpace(data[loc])) {
+ loc++;
+ if (!PDFFile.isRegularCharacter(data[loc])) {
+ break; // leave with the delimiter included
+ }
+ }
+ String s = new String(data, start, loc - start);
+ // System.out.println("Read: "+s);
+ return s;
+ }
+
+ /**
+ * read a set of numbers from the input. This method doesn't
+ * pay any attention to "[" or "]" delimiters, and reads any
+ * non-numeric items as the number 0.
+ * @param count the number of items to read
+ * @return an array of count floats
+ */
+ public float[] readArray(int count) {
+ float[] ary = new float[count];
+ int idx = 0;
+ while (idx < count) {
+ String thing = readThing();
+ if (thing.charAt(0) == '[') {
+ thing = thing.substring(1);
+ }
+ if (thing.endsWith("]")) {
+ thing = thing.substring(0, thing.length() - 1);
+ }
+ if (thing.length() > 0) {
+ ary[idx++] = Float.valueOf(thing).floatValue();
+ }
+ }
+ return ary;
+ }
+
+ /**
+ * get the current location within the input stream
+ */
+ public int getLoc() {
+ return loc;
+ }
+
+ /**
+ * set the current location within the input stream
+ */
+ public void setLoc(int loc) {
+ this.loc = loc;
+ }
+
+ /**
+ * treat the next n bytes of the input stream as encoded
+ * information to be decrypted.
+ * @param n the number of bytes to decrypt
+ * @param key the decryption key
+ * @param skip the number of bytes to skip at the beginning of the
+ * decryption
+ * @return an array of decrypted bytes. The length of the array
+ * will be n-skip.
+ */
+ public byte[] getNEncodedBytes(int n, int key, int skip) {
+ byte[] result = decrypt(data, loc, loc + n, key, skip);
+ loc += n;
+ return result;
+ }
+ }
+
+ /**
+ * get the index into the byte array of a slashed name, like "/name".
+ * @param d the search array
+ * @param name the name to look for, without the initial /
+ * @return the index of the first occurance of /name in the array.
+ */
+ private int findSlashName(byte[] d, String name) {
+ int i;
+ for (i = 0; i < d.length; i++) {
+ if (d[i] == '/') {
+ // check for key
+ boolean found = true;
+ for (int j = 0; j < name.length(); j++) {
+ if (d[i + j + 1] != name.charAt(j)) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * get the character definitions of the font.
+ * @param d the font data
+ * @return a HashMap that maps string glyph names to byte arrays of
+ * decoded font data.
+ */
+ private HashMap readChars(byte[] d) {
+ // skip thru data until we find "/"+key
+ HashMap hm = new HashMap();
+ int i = findSlashName(d, "CharStrings");
+ if (i < 0) {
+ // not found
+ return hm;
+ }
+ PSParser psp = new PSParser(d, i);
+ // read /name len -| [len bytes] |-
+ // until "end"
+ while (true) {
+ String s = psp.readThing();
+ char c = s.charAt(0);
+ if (c == '/') {
+ int len = Integer.parseInt(psp.readThing());
+ String go = psp.readThing(); // it's -| or RD
+ if (go.equals("-|") || go.equals("RD")) {
+ psp.setLoc(psp.getLoc() + 1);
+ byte[] line = psp.getNEncodedBytes(len, password, lenIV);
+ hm.put(s.substring(1), line);
+ }
+ } else if (s.equals("end")) {
+ break;
+ }
+ }
+ return hm;
+ }
+
+ /**
+ * pop the next item off the stack
+ */
+ private float pop() {
+ float val = 0;
+ if (sloc > 0) {
+ val = stack[--sloc];
+ }
+ return val;
+ }
+ int callcount = 0;
+
+ /**
+ * parse glyph data into a GeneralPath, and return the advance width.
+ * The working point is passed in as a parameter in order to allow
+ * recursion.
+ * @param cs the decrypted glyph data
+ * @param gp a GeneralPath into which the glyph shape will be stored
+ * @param pt a FlPoint object that will be used to generate the path
+ * @param wid a FlPoint into which the advance width will be placed.
+ */
+ private void parse(byte[] cs, Path gp, FlPoint pt, FlPoint wid) {
+ // System.out.println("--- cmd length is "+cs.length);
+ int loc = 0;
+ float x1, x2, x3, y1, y2, y3;
+ while (loc < cs.length) {
+ int v = ((int) cs[loc++]) & 0xff;
+ if (v == 255) {
+ stack[sloc++] = ((((int) cs[loc]) & 0xff) << 24) +
+ ((((int) cs[loc + 1]) & 0xff) << 16) +
+ ((((int) cs[loc + 2]) & 0xff) << 8) +
+ ((((int) cs[loc + 3]) & 0xff));
+ loc += 4;
+// System.out.println("Pushed long "+stack[sloc-1]);
+ } else if (v >= 251) {
+ stack[sloc++] = -((v - 251) << 8) - (((int) cs[loc]) & 0xff) - 108;
+ loc++;
+// System.out.println("Pushed lo "+stack[sloc-1]);
+ } else if (v >= 247) {
+ stack[sloc++] = ((v - 247) << 8) + (((int) cs[loc]) & 0xff) + 108;
+ loc++;
+// System.out.println("Pushed hi "+stack[sloc-1]);
+ } else if (v >= 32) {
+ stack[sloc++] = v - 139;
+// System.out.println("Pushed "+stack[sloc-1]);
+ } else {
+ // System.out.println("CMD: "+v+" (stack is size "+sloc+")");
+ switch (v) {
+ case 0: // x
+ throw new RuntimeException("Bad command (" + v + ")");
+ case 1: // hstem
+ sloc = 0;
+ break;
+ case 2: // x
+ throw new RuntimeException("Bad command (" + v + ")");
+ case 3: // vstem
+ sloc = 0;
+ break;
+ case 4: // y vmoveto
+ pt.y += pop();
+ gp.moveTo(pt.x, pt.y);
+ sloc = 0;
+ break;
+ case 5: // x y rlineto
+ pt.y += pop();
+ pt.x += pop();
+ gp.lineTo(pt.x, pt.y);
+ sloc = 0;
+ break;
+ case 6: // x hlineto
+ pt.x += pop();
+ gp.lineTo(pt.x, pt.y);
+ sloc = 0;
+ break;
+ case 7: // y vlineto
+ pt.y += pop();
+ gp.lineTo(pt.x, pt.y);
+ sloc = 0;
+ break;
+ case 8: // x1 y1 x2 y2 x3 y3 rcurveto
+ y3 = pop();
+ x3 = pop();
+ y2 = pop();
+ x2 = pop();
+ y1 = pop();
+ x1 = pop();
+ gp.cubicTo(pt.x + x1, pt.y + y1,
+ pt.x + x1 + x2, pt.y + y1 + y2,
+ pt.x + x1 + x2 + x3, pt.y + y1 + y2 + y3);
+ pt.x += x1 + x2 + x3;
+ pt.y += y1 + y2 + y3;
+ sloc = 0;
+ break;
+ case 9: // closepath
+ gp.close();
+ sloc = 0;
+ break;
+ case 10: // n callsubr
+ int n = (int) pop();
+ if (subrs[n] == null) {
+ System.out.println("No subroutine #" + n);
+ } else {
+ callcount++;
+ if (callcount > 10) {
+ System.out.println("Call stack too large");
+ // throw new RuntimeException("Call stack too large");
+ } else {
+ parse(subrs[n], gp, pt, wid);
+ }
+ callcount--;
+ }
+ break;
+ case 11: // return
+ return;
+ case 12: // ext...
+ v = ((int) cs[loc++]) & 0xff;
+ if (v == 6) { // s x y b a seac
+ char a = (char) pop();
+ char b = (char) pop();
+ float y = pop();
+ float x = pop();
+ buildAccentChar(x, y, a, b, gp);
+ sloc = 0;
+ } else if (v == 7) { // x y w h sbw
+ wid.y = pop();
+ wid.x = pop();
+ pt.y = pop();
+ pt.x = pop();
+ sloc = 0;
+ } else if (v == 12) { // a b div -> a/b
+ float b = pop();
+ float a = pop();
+ stack[sloc++] = a / b;
+ } else if (v == 33) { // a b setcurrentpoint
+ pt.y = pop();
+ pt.x = pop();
+ gp.moveTo(pt.x, pt.y);
+ sloc = 0;
+ } else if (v == 0) { // dotsection
+ sloc = 0;
+ } else if (v == 1) { // vstem3
+ sloc = 0;
+ } else if (v == 2) { // hstem3
+ sloc = 0;
+ } else if (v == 16) { // n callothersubr
+ int cn = (int) pop();
+ int countargs = (int) pop();
+
+ // System.out.println("Called othersubr with index "+cn);
+
+ switch (cn) {
+ case 0:
+ // push args2 and args3 onto stack
+ psStack[psLoc++] = pop();
+ psStack[psLoc++] = pop();
+ pop();
+ break;
+ case 3:
+ // push 3 onto the postscript stack
+ psStack[psLoc++] = 3;
+ break;
+ default:
+ // push arguments onto the postscript stack
+ for (int i = 0; i > countargs; i--) {
+ psStack[psLoc++] = pop();
+ }
+ break;
+ }
+ } else if (v == 17) { // pop
+ // pop from the postscript stack onto the type1 stack
+ stack[sloc++] = psStack[psLoc - 1];
+ psLoc--;
+ } else {
+ throw new RuntimeException("Bad command (" + v + ")");
+ }
+ break;
+ case 13: // s w hsbw
+ wid.x = pop();
+ wid.y = 0;
+ pt.x = pop();
+ pt.y = 0;
+ // gp.moveTo(pt.x, pt.y);
+ sloc = 0;
+ break;
+ case 14: // endchar
+ // return;
+ break;
+ case 15: // x
+ case 16: // x
+ case 17: // x
+ case 18: // x
+ case 19: // x
+ case 20: // x
+ throw new RuntimeException("Bad command (" + v + ")");
+ case 21: // x y rmoveto
+ pt.y += pop();
+ pt.x += pop();
+ gp.moveTo(pt.x, pt.y);
+ sloc = 0;
+ break;
+ case 22: // x hmoveto
+ pt.x += pop();
+ gp.moveTo(pt.x, pt.y);
+ sloc = 0;
+ break;
+ case 23: // x
+ case 24: // x
+ case 25: // x
+ case 26: // x
+ case 27: // x
+ case 28: // x
+ case 29: // x
+ throw new RuntimeException("Bad command (" + v + ")");
+ case 30: // y1 x2 y2 x3 vhcurveto
+ x3 = pop();
+ y2 = pop();
+ x2 = pop();
+ y1 = pop();
+ x1 = y3 = 0;
+ gp.cubicTo(pt.x, pt.y + y1,
+ pt.x + x2, pt.y + y1 + y2,
+ pt.x + x2 + x3, pt.y + y1 + y2);
+ pt.x += x2 + x3;
+ pt.y += y1 + y2;
+ sloc = 0;
+ break;
+ case 31: // x1 x2 y2 y3 hvcurveto
+ y3 = pop();
+ y2 = pop();
+ x2 = pop();
+ x1 = pop();
+ y1 = x3 = 0;
+ gp.cubicTo(pt.x + x1, pt.y,
+ pt.x + x1 + x2, pt.y + y2,
+ pt.x + x1 + x2, pt.y + y2 + y3);
+ pt.x += x1 + x2;
+ pt.y += y2 + y3;
+ sloc = 0;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * build an accented character out of two pre-defined glyphs.
+ * @param x the x offset of the accent relativ to the sidebearing of the base char
+ * @param y the y offset of the accent relativ to the sidebearing of the base char
+ * @param a the index of the accent glyph
+ * @param b the index of the base glyph
+ * @param gp the GeneralPath into which the combined glyph will be
+ * written.
+ */
+ private void buildAccentChar(float x, float y, char a, char b,
+ Path gp) {
+ // get the outline of the accent
+ Path pathAorig = getOutline(a, getWidth(a, null));
+ // don't manipulate the original glyph
+ Path pathA = new Path(pathAorig);
+
+ // undo the effect of the transform applied in read
+ Matrix xformA = new Matrix();
+ if (at.invert(xformA)) {
+ // undo the effect of the transform applied in read
+ pathA.transform(xformA);
+ // Best x can not be calcualted cause we do not know the left sidebearing of the base character.
+ // Leaving x=0 gives the best results.
+ // see Chapter 6 of http://partners.adobe.com/public/developer/en/font/5015.Type1_Supp.pdf
+ // and the definition of the seac-Command in http://partners.adobe.com/public/developer/en/font/T1_SPEC.PDF
+ Matrix xformA2 = new Matrix();
+ xformA2.setTranslate(0, y);
+ pathA.transform(xformA2); //<-- goht net fürs 'ä'
+ } else {
+ Matrix xformA2 = new Matrix();
+ xformA2.setTranslate(0, y);
+ pathA.transform(xformA2); //<-- goht net fürs 'ä'
+ }
+
+ Path pathBorig = getOutline(b, getWidth(b, null));
+ // don't manipulate the original glyph
+ Path pathB = (Path) new Path(pathBorig);
+
+ Matrix xformB = new Matrix();
+ if (at.invert(xformB)) {
+ pathB.transform(xformB);
+ } else {
+ // ignore
+ }
+
+ gp.addPath(pathB);
+ gp.addPath(pathA);
+ }
+
+ /**
+ * Get the width of a given character
+ *
+ * This method is overridden to work if the width array hasn't been
+ * populated (as for one of the 14 base fonts)
+ */
+ @Override
+ public float getWidth(char code, String name) {
+ // we don't have first and last chars, so therefore no width array
+ if (getFirstChar() == -1 || getLastChar() == -1) {
+ String key = chr2name[code & 0xff];
+
+ // use a name if one is provided
+ if (name != null) {
+ key = name;
+ }
+
+ if (key != null && name2outline.containsKey(key)) {
+ if (!name2width.containsKey(key)) {
+ // glyph has not yet been parsed
+ // getting the outline will force it to get read
+ getOutline(key, 0);
+ }
+
+ FlPoint width = name2width.get(key);
+ if (width != null) {
+ return width.x / getDefaultWidth();
+ }
+ }
+
+ return 0;
+ }
+
+ // return the width that has been specified
+ return super.getWidth(code, name);
+ }
+
+ /**
+ * Decrypt a glyph stored in byte form
+ */
+ private synchronized Path parseGlyph(byte[] cs, FlPoint advance,
+ Matrix at) {
+ Path gp = new Path();
+ FlPoint curpoint = new FlPoint();
+
+ sloc = 0;
+ parse(cs, gp, curpoint, advance);
+
+ gp.transform(at);
+ return gp;
+ }
+
+ /**
+ * Get a glyph outline by name
+ *
+ * @param name the name of the desired glyph
+ * @return the glyph outline, or null if unavailable
+ */
+ protected Path getOutline(String name, float width) {
+ // make sure we have a valid name
+ if (name == null || !name2outline.containsKey(name)) {
+ name = ".notdef";
+ }
+
+ // get whatever is stored in name. Could be a GeneralPath, could be byte[]
+ Object obj = name2outline.get(name);
+
+ // if it's a byte array, it needs to be parsed
+ // otherwise, just return the path
+ if (obj instanceof Path) {
+ return (Path) obj;
+ } else {
+ byte[] cs = (byte[]) obj;
+ FlPoint advance = new FlPoint();
+
+ Path gp = parseGlyph(cs, advance, at);
+
+ if (width != 0 && advance.x != 0) {
+ // scale the glyph to fit in the width
+ PointF p = new PointF(advance.x, advance.y);
+ float[]pts = new float[]{p.x,p.y};
+ at.mapPoints(pts);
+ p.x = pts[0];
+ p.y = pts[1];
+ float scale = width / p.x;
+ Matrix xform = new Matrix();
+ xform.setScale(scale, 1.0f);
+ gp.transform(xform);
+ }
+
+ // put the parsed object in the cache
+ name2outline.put(name, gp);
+ name2width.put(name, advance);
+ return gp;
+ }
+ }
+
+ /**
+ * Get a glyph outline by character code
+ *
+ * Note this method must always return an outline
+ *
+ * @param src the character code of the desired glyph
+ * @return the glyph outline
+ */
+ protected Path getOutline(char src, float width) {
+ return getOutline(chr2name[src & 0xff], width);
+ }
+}
+
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/Type3Font.java b/PdfView/src/main/java/com/sun/pdfview/font/Type3Font.java
new file mode 100644
index 0000000..5f11a07
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/Type3Font.java
@@ -0,0 +1,189 @@
+/*
+ * $Id: Type3Font.java,v 1.3 2009/02/12 13:53:54 tomoke 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.font;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.andpdf.utils.Utils;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFPage;
+import com.sun.pdfview.PDFParser;
+
+
+/**
+ * A Type 3 Font, in which each glyph consists of a sequence of PDF
+ * commands.
+ *
+ * @author Mike Wessler
+ */
+public class Type3Font extends PDFFont {
+
+ /** resources for the character definitions */
+ HashMap rsrc;
+ /** the character processes, mapped by name */
+ Map charProcs;
+ /** bounding box for the font characters */
+ RectF bbox;
+ /** affine transform for the font characters */
+ Matrix at;
+ /** the widths */
+ float[] widths;
+ /** the start code */
+ int firstChar;
+ /** the end code */
+ int lastChar;
+
+ /**
+ * Generate a Type 3 font.
+ * @param baseFont the postscript name of this font
+ * @param fontObj a dictionary containing references to the character
+ * definitions and font information
+ * @param resources a set of resources used by the character definitions
+ * @param descriptor the descriptor for this font
+ */
+ public Type3Font(String baseFont, PDFObject fontObj,
+ HashMap resources, PDFFontDescriptor descriptor) throws IOException {
+ super(baseFont, descriptor);
+
+ rsrc = new HashMap();
+
+ if (resources != null) {
+ rsrc.putAll(resources);
+ }
+
+ // get the transform matrix
+ PDFObject matrix = fontObj.getDictRef("FontMatrix");
+ float matrixAry[] = new float[6];
+ for (int i = 0; i < 6; i++) {
+ matrixAry[i] = matrix.getAt(i).getFloatValue();
+ }
+ at = Utils.createMatrix(matrixAry);
+
+ // get the scale from the matrix
+ float scale = matrixAry[0] + matrixAry[2];
+
+ // put all the resources in a Hash
+ PDFObject rsrcObj = fontObj.getDictRef("Resources");
+ if (rsrcObj != null) {
+ rsrc.putAll(rsrcObj.getDictionary());
+ }
+
+ // get the character processes, indexed by name
+ charProcs = fontObj.getDictRef("CharProcs").getDictionary();
+
+ // get the font bounding box
+ PDFObject[] bboxdef = fontObj.getDictRef("FontBBox").getArray();
+ float[] bboxfdef = new float[4];
+ for (int i = 0; i < 4; i++) {
+ bboxfdef[i] = bboxdef[i].getFloatValue();
+ }
+ bbox = new RectF(bboxfdef[0], bboxfdef[1],
+ bboxfdef[2] - bboxfdef[0],
+ bboxfdef[3] - bboxfdef[1]);
+ if (bbox.isEmpty()) {
+ bbox = null;
+ }
+
+ // get the widths
+ PDFObject[] widthArray = fontObj.getDictRef("Widths").getArray();
+ widths = new float[widthArray.length];
+ for (int i = 0; i < widthArray.length; i++) {
+ widths[i] = widthArray[i].getFloatValue();
+ }
+
+ // get first and last chars
+ firstChar = fontObj.getDictRef("FirstChar").getIntValue();
+ lastChar = fontObj.getDictRef("LastChar").getIntValue();
+ }
+
+ /**
+ * Get the first character code
+ */
+ public int getFirstChar() {
+ return firstChar;
+ }
+
+ /**
+ * Get the last character code
+ */
+ public int getLastChar() {
+ return lastChar;
+ }
+
+ /**
+ * Get the glyph for a given character code and name
+ *
+ * The preferred method of getting the glyph should be by name. If the
+ * name is null or not valid, then the character code should be used.
+ * If the both the code and the name are invalid, the undefined glyph
+ * should be returned.
+ *
+ * Note this method must *always* return a glyph.
+ *
+ * @param src the character code of this glyph
+ * @param name the name of this glyph or null if unknown
+ * @return a glyph for this character
+ */
+ protected PDFGlyph getGlyph(char src, String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("Glyph name required for Type3 font!" +
+ "Source character: " + (int) src);
+ }
+
+ PDFObject pageObj = (PDFObject) charProcs.get(name);
+ if (pageObj == null) {
+ // glyph not found. Return an empty glyph...
+ return new PDFGlyph(src, name, new Path(), new PointF(0, 0));
+ }
+
+ try {
+ PDFPage page = new PDFPage(bbox, 0);
+ page.addXform(at);
+
+ PDFParser prc = new PDFParser(page, pageObj.getStream(), rsrc);
+ prc.go(true);
+
+ float width = widths[src - firstChar];
+
+ PointF advance = new PointF(width, 0);
+ float[]pts = {advance.x,advance.y};
+ at.mapPoints(pts);
+ advance.x = pts[0];
+ advance.y = pts[1];
+
+ return new PDFGlyph(src, name, page, advance);
+ } catch (IOException ioe) {
+ // help!
+ System.out.println("IOException in Type3 font: " + ioe);
+ ioe.printStackTrace();
+ return null;
+ }
+ }
+}
+
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/AdobeGlyphList.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/AdobeGlyphList.java
new file mode 100644
index 0000000..4e1993c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/AdobeGlyphList.java
@@ -0,0 +1,180 @@
+package com.sun.pdfview.font.ttf;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Build an object which provides access to all the Adobe glyph names, using
+ * a unicode value, and which can translate a glyph name to one or more
+ * unicode values.
+ *
+# ###################################################################################
+# Copyright (c) 1997,1998,2002,2007 Adobe Systems Incorporated
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this documentation file to use, copy, publish, distribute,
+# sublicense, and/or sell copies of the documentation, and to permit
+# others to do the same, provided that:
+# - No modification, editing or other alteration of this document is
+# allowed; and
+# - The above copyright notice and this permission notice shall be
+# included in all copies of the documentation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this documentation file, to create their own derivative works
+# from the content of this document to use, copy, publish, distribute,
+# sublicense, and/or sell the derivative works, and to permit others to do
+# the same, provided that the derived work is not represented as being a
+# copy or version of this document.
+#
+# Adobe shall not be liable to any party for any loss of revenue or profit
+# or for indirect, incidental, special, consequential, or other similar
+# damages, whether based on tort (including without limitation negligence
+# or strict liability), contract or other legal or equitable grounds even
+# if Adobe has been advised or had reason to know of the possibility of
+# such damages. The Adobe materials are provided on an "AS IS" basis.
+# Adobe specifically disclaims all express, statutory, or implied
+# warranties relating to the Adobe materials, including but not limited to
+# those concerning merchantability or fitness for a particular purpose or
+# non-infringement of any third party rights regarding the Adobe
+# materials.
+# ###################################################################################
+# Name: Adobe Glyph List
+# Table version: 2.0
+# Date: September 20, 2002
+#
+# See http://partners.adobe.com/asn/developer/typeforum/unicodegn.html
+#
+# Format: Semicolon-delimited fields:
+# (1) glyph name
+# (2) Unicode scalar value
+ *
+ * @author tomoke
+ */
+public class AdobeGlyphList {
+
+ /** provide a translation from a glyph name to the possible unicode values. */
+ static private HashMap glyphToUnicodes;
+ /** provide a translation from a unicode value to a glyph name. */
+ static private HashMap unicodeToGlyph;
+ /** the loader thread we are reading through. */
+ static Thread glyphLoaderThread = null;
+
+
+ static {
+ new AdobeGlyphList();
+ }
+
+ /**
+ *
private constructor to restrict creation to a singleton.
+ *
+ *
We initialize by creating the storage and parsing the glyphlist
+ * into the tables.
+ */
+ private AdobeGlyphList() {
+ glyphToUnicodes = new HashMap(4500);
+ unicodeToGlyph = new HashMap(4500);
+ glyphLoaderThread = new Thread(new Runnable() {
+
+ public void run() {
+ int[] codes;
+ StringTokenizer codeTokens;
+ String glyphName;
+ StringTokenizer tokens;
+ ArrayList unicodes = new ArrayList();
+
+ InputStream istr = getClass().getResourceAsStream("/com/sun/pdfview/font/ttf/resource/glyphlist.txt");
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(istr));
+ String line = "";
+ while (line != null) {
+ try {
+ unicodes.clear();
+ line = reader.readLine();
+ if (line == null) {
+ break;
+ }
+ line = line.trim();
+ if (line.length() > 0 && !line.startsWith("#")) {
+ // ignore comment lines
+ tokens = new StringTokenizer(line, ";");
+ glyphName = tokens.nextToken();
+ codeTokens = new StringTokenizer(tokens.nextToken(), " ");
+ while (codeTokens.hasMoreTokens()) {
+ unicodes.add(codeTokens.nextToken());
+ }
+ codes = new int[unicodes.size()];
+ for (int i = 0; i < unicodes.size(); i++) {
+ codes[i] = Integer.parseInt(unicodes.get(i), 16);
+ unicodeToGlyph.put(new Integer(codes[i]), glyphName);
+ }
+ glyphToUnicodes.put(glyphName, codes);
+ }
+
+ } catch (IOException ex) {
+ break;
+ }
+ }
+ }
+ }, "Adobe Glyph Loader Thread");
+ glyphLoaderThread.setDaemon(true);
+ glyphLoaderThread.setPriority(Thread.MIN_PRIORITY);
+ glyphLoaderThread.start();
+ }
+
+ /**
+ * translate a glyph name into the possible unicode values that it
+ * might represent. It is possible to have more than one unicode
+ * value for a single glyph name.
+ *
+ * @param glyphName
+ * @return int[]
+ */
+ public static int[] getUnicodeValues(String glyphName) {
+ while (glyphLoaderThread != null && glyphLoaderThread.isAlive()) {
+ synchronized (glyphToUnicodes) {
+ try {
+ glyphToUnicodes.wait(250);
+ } catch (InterruptedException ex) {
+ // ignore
+ }
+ }
+ }
+ return glyphToUnicodes.get(glyphName);
+ }
+
+ /**
+ * return a single index for a glyph, though there may be multiples.
+ *
+ * @param glyphName
+ * @return Integer
+ */
+ public static Integer getGlyphNameIndex(String glyphName) {
+ int [] unicodes = getUnicodeValues(glyphName);
+ if (unicodes == null) {
+ return null;
+ } else {
+ return new Integer(unicodes[0]);
+ }
+ }
+
+ /**
+ * translate a unicode value into a glyph name. It is possible for
+ * different unicode values to translate into the same glyph name.
+ *
+ * @param unicode
+ * @return String
+ */
+ public static String getGlyphName(int unicode) {
+ while (glyphLoaderThread != null && glyphLoaderThread.isAlive()) {
+ synchronized (glyphToUnicodes) {
+ try {
+ glyphToUnicodes.wait(250);
+ } catch (InterruptedException ex) {
+ // ignore
+ }
+ }
+ }
+ return unicodeToGlyph.get(new Integer(unicode));
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMap.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMap.java
new file mode 100644
index 0000000..dda9a8d
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMap.java
@@ -0,0 +1,184 @@
+/*
+ * $Id: CMap.java,v 1.4 2009/03/15 20:47:38 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ *
+ * @author jkaplan
+ */
+public abstract class CMap {
+
+ /**
+ * The format of this map
+ */
+ private short format;
+
+ /**
+ * The language of this map, or 0 for language-independent
+ */
+ private short language;
+
+ /** Creates a new instance of CMap
+ * Don't use this directly, use CMap.createMap()
+ */
+ protected CMap (short format, short language) {
+ this.format = format;
+ this.language = language;
+ }
+
+ /**
+ * Create a map for the given format and language
+
+ *
The Macintosh standard character to glyph mapping is supported
+ * by format 0.
+ *
+ *
Format 2 supports a mixed 8/16 bit mapping useful for Japanese,
+ * Chinese and Korean.
+ *
+ *
Format 4 is used for 16 bit mappings.
+ *
+ *
Format 6 is used for dense 16 bit mappings.
+ *
+ *
Formats 8, 10, and 12 (properly 8.0, 10.0, and 12.0) are used
+ * for mixed 16/32-bit and pure 32-bit mappings.
+ * This supports text encoded with surrogates in Unicode 2.0 and later.
+ */
+ public static CMap createMap (short format, short language) {
+ CMap outMap = null;
+
+ switch (format) {
+ case 0: // CMap format 0 - single byte codes
+ outMap = new CMapFormat0 (language);
+ break;
+ case 4: // CMap format 4 - two byte encoding
+ outMap = new CMapFormat4 (language);
+ break;
+ case 6: // CMap format 6 - 16-bit, two byte encoding
+ outMap = new CMapFormat6 (language);
+ break;
+// case 8: // CMap format 8 - Mixed 16-bit and 32-bit coverage
+// outMap = new CMapFormat_8(language);
+// break;
+// // CMap format 10 - Format 10.0 is a bit like format 6, in that it
+// // defines a trimmed array for a tight range of 32-bit character codes:
+// case 10:
+// outMap = new CMapFormat_10(language);
+// break;
+// // Format 12.0 is a bit like format 4, in that it defines
+// // segments for sparse representation in 4-byte character space.
+// case 12: // CMap format 12 -
+// outMap = new CMapFormat_12(language);
+// break;
+ default:
+ System.out.println ("Unsupport CMap format: " + format);
+ return null;
+ }
+
+ return outMap;
+ }
+
+ /**
+ * Get a map from the given data
+ *
+ * This method reads the format, data and length variables of
+ * the map.
+ */
+ public static CMap getMap (ByteBuffer data) {
+ short format = data.getShort ();
+ short lengthShort = data.getShort ();
+ int length = 0xFFFF & (int) lengthShort;
+// System.out.println (
+// "CMAP, length: " + length + ", short: " + lengthShort);
+
+ // make sure our slice of the data only contains up to the length
+ // of this table
+ data.limit (Math.min (length, data.limit ()));
+
+ short language = data.getShort ();
+
+ CMap outMap = createMap (format, language);
+ if (outMap == null) {
+ return null;
+ }
+
+ outMap.setData (data.limit (), data);
+
+ return outMap;
+ }
+
+ /**
+ * Get the format of this map
+ */
+ public short getFormat () {
+ return format;
+ }
+
+ /**
+ * Get the language of this map
+ */
+ public short getLanguage () {
+ return language;
+ }
+
+ /**
+ * Set the data for this map
+ */
+ public abstract void setData (int length, ByteBuffer data);
+
+ /**
+ * Get the data in this map as a byte buffer
+ */
+ public abstract ByteBuffer getData ();
+
+ /**
+ * Get the length of this map
+ */
+ public abstract short getLength ();
+
+ /**
+ * Map an 8 bit value to another 8 bit value
+ */
+ public abstract byte map (byte src);
+
+ /**
+ * Map a 16 bit value to another 16 but value
+ */
+ public abstract char map (char src);
+
+ /**
+ * Get the src code which maps to the given glyphID
+ */
+ public abstract char reverseMap (short glyphID);
+
+ /** Print a pretty string */
+ @Override
+ public String toString () {
+ String indent = " ";
+
+ return indent + " format: " + getFormat () + " length: " +
+ getLength () + " language: " + getLanguage () + "\n";
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat0.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat0.java
new file mode 100644
index 0000000..7c1db4c
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat0.java
@@ -0,0 +1,151 @@
+/*
+ * $Id: CMapFormat0.java,v 1.2 2007/12/20 18:33:30 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ *
+ * @author jkaplan
+ */
+public class CMapFormat0 extends CMap {
+
+ /**
+ * The glyph index array
+ */
+ private byte[] glyphIndex;
+
+ /** Creates a new instance of CMapFormat0 */
+ protected CMapFormat0(short language) {
+ super((short) 0, language);
+
+ byte[] initialIndex = new byte[256];
+ for (int i = 0; i < initialIndex.length; i++) {
+ initialIndex[i] = (byte) i;
+ }
+ setMap(initialIndex);
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public short getLength() {
+ return (short) 262;
+ }
+
+ /**
+ * Map from a byte
+ */
+ public byte map(byte src) {
+ int i = 0xff & src;
+
+ return glyphIndex[i];
+ }
+
+ /**
+ * Cannot map from short
+ */
+ public char map(char src) {
+ if (src < 0 || src > 255) {
+ // out of range
+ return (char) 0;
+ }
+
+ return (char) (map((byte) src) & 0xff);
+ }
+
+
+ /**
+ * Get the src code which maps to the given glyphID
+ */
+ public char reverseMap(short glyphID) {
+ for (int i = 0; i < glyphIndex.length; i++) {
+ if ((glyphIndex[i] & 0xff) == glyphID) {
+ return (char) i;
+ }
+ }
+
+ return (char) 0;
+ }
+
+ /**
+ * Set the entire map
+ */
+ public void setMap(byte[] glyphIndex) {
+ if (glyphIndex.length != 256) {
+ throw new IllegalArgumentException("Glyph map must be size 256!");
+ }
+
+ this.glyphIndex = glyphIndex;
+ }
+
+ /**
+ * Set a single mapping entry
+ */
+ public void setMap(byte src, byte dest) {
+ int i = 0xff & src;
+
+ glyphIndex[i] = dest;
+ }
+
+ /**
+ * Get the whole map
+ */
+ protected byte[] getMap() {
+ return glyphIndex;
+ }
+
+ /**
+ * Get the data in this map as a ByteBuffer
+ */
+ public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(262);
+
+ buf.putShort(getFormat());
+ buf.putShort(getLength());
+ buf.putShort(getLanguage());
+ buf.put(getMap());
+
+ // reset the position to the beginning of the buffer
+ buf.flip();
+
+ return buf;
+ }
+
+ /**
+ * Read the map in from a byte buffer
+ */
+ public void setData(int length, ByteBuffer data) {
+ if (length != 262) {
+ throw new IllegalArgumentException("Bad length for CMap format 0");
+ }
+
+ if (data.remaining() != 256) {
+ throw new IllegalArgumentException("Wrong amount of data for CMap format 0");
+ }
+
+ byte[] map = new byte[256];
+ data.get(map);
+
+ setMap(map);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat4.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat4.java
new file mode 100644
index 0000000..5d08810
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat4.java
@@ -0,0 +1,458 @@
+/*
+ * $Id: CMapFormat4.java,v 1.3 2009/02/12 13:53:57 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ *
+ * @author jkaplan
+ */
+public class CMapFormat4 extends CMap {
+
+ /**
+ * The segments and associated data can be a char[] or an Integer
+ */
+ public SortedMap segments;
+
+ /** Creates a new instance of CMapFormat0 */
+ protected CMapFormat4(short language) {
+ super((short) 4, language);
+
+ segments = Collections.synchronizedSortedMap(new TreeMap());
+
+ char[] map = new char[1];
+ map[0] = (char) 0;
+ addSegment((short) 0xffff, (short) 0xffff, map);
+ }
+
+ /**
+ * Add a segment with a map
+ */
+ public void addSegment(short startCode, short endCode, char[] map) {
+ if (map.length != (endCode - startCode) + 1) {
+ throw new IllegalArgumentException("Wrong number of entries in map");
+ }
+
+ Segment s = new Segment(startCode, endCode, true);
+ // make sure we remove any old entries
+ segments.remove(s);
+ segments.put(s, map);
+ }
+
+ /**
+ * Add a segment with an idDelta
+ */
+ public void addSegment(short startCode, short endCode, short idDelta) {
+ Segment s = new Segment(startCode, endCode, false);
+ // make sure we remove any old entries
+ segments.remove(s);
+ segments.put(s, new Integer(idDelta));
+ }
+
+ /**
+ * Remove a segment
+ */
+ public void removeSegment(short startCode, short endCode) {
+ Segment s = new Segment(startCode, endCode, true);
+ segments.remove(s);
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public short getLength() {
+ // start with the size of the fixed header
+ short size = 16;
+
+ // add the size of each segment header
+ size += segments.size() * 8;
+
+ // add the total number of mappings times the size of a mapping
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+
+ // see if there's a map
+ if (s.hasMap) {
+ // if there is, add its size
+ char[] map = (char[]) segments.get(s);
+ size += map.length * 2;
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ * Cannot map from a byte
+ */
+ public byte map(byte src) {
+ char c = map((char) src);
+ if (c < Byte.MIN_VALUE || c > Byte.MAX_VALUE) {
+ // out of range
+ return 0;
+ }
+
+ return (byte) c;
+ }
+
+ /**
+ * Map from char
+ */
+ public char map(char src) {
+ // find first segment with endcode > src
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+
+ if (s.endCode >= src) {
+ // are we within range?
+ if (s.startCode <= src) {
+ if (s.hasMap) {
+ // return the index of this character in
+ // the segment's map
+ char[] map = (char[]) segments.get(s);
+ return map[src - s.startCode];
+ } else {
+ // return the character code + idDelta
+ Integer idDelta = (Integer) segments.get(s);
+ return (char) (src + idDelta.intValue());
+ }
+ } else {
+ // undefined character
+ return (char) 0;
+ }
+ }
+ }
+
+ // shouldn't get here!
+ return (char) 0;
+ }
+
+ /**
+ * Get the src code which maps to the given glyphID
+ */
+ public char reverseMap(short glyphID) {
+ // look at each segment
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+
+ // see if we have a map or a delta
+ if (s.hasMap) {
+ char[] map = (char[]) segments.get(s);
+
+ // if we have a map, we have to iterate through it
+ for (int c = 0; c < map.length; c++) {
+ if (map[c] == glyphID) {
+ return (char) (s.startCode + c);
+ }
+ }
+ } else {
+ Integer idDelta = (Integer) segments.get(s);
+
+ // we can do the math to see if we're in range
+ int start = s.startCode + idDelta.intValue();
+ int end = s.endCode + idDelta.intValue();
+
+ if (glyphID >= start && glyphID <= end) {
+ // we're in the range
+ return (char) (glyphID - idDelta.intValue());
+ }
+ }
+ }
+
+ // not found!
+ return (char) 0;
+ }
+
+
+ /**
+ * Get the data in this map as a ByteBuffer
+ */
+ public void setData(int length, ByteBuffer data) {
+ // read the table size values
+ short segCount = (short) (data.getShort() / 2);
+ short searchRange = data.getShort();
+ short entrySelector = data.getShort();
+ short rangeShift = data.getShort();
+
+ // create arrays to store segment info
+ short[] endCodes = new short[segCount];
+ short[] startCodes = new short[segCount];
+ short[] idDeltas = new short[segCount];
+ short[] idRangeOffsets = new short[segCount];
+
+ // the start of the glyph array
+ int glyphArrayPos = 16 + (8 * segCount);
+
+ // read the endCodes
+ for (int i = 0; i < segCount; i++) {
+ endCodes[i] = data.getShort();
+ }
+
+ // read the pad
+ data.getShort();
+
+ // read the start codes
+ for (int i = 0; i < segCount; i++) {
+ startCodes[i] = data.getShort();
+ }
+
+ // read the idDeltas
+ for (int i = 0; i < segCount; i++) {
+ idDeltas[i] = data.getShort();
+ }
+
+ // read the id range offsets
+ for (int i = 0; i < segCount; i++) {
+ idRangeOffsets[i] = data.getShort();
+
+ // calculate the actual offset
+ if (idRangeOffsets[i] <= 0) {
+ // the easy way
+ addSegment(startCodes[i], endCodes[i], idDeltas[i]);
+ } else {
+ // find the start of the data segment
+ int offset = (data.position() - 2) + idRangeOffsets[i];
+
+ // get the number of entries in the map
+ int size = (endCodes[i] - startCodes[i]) + 1;
+
+ // allocate the actual map
+ char[] map = new char[size];
+
+ // remember our offset
+ data.mark();
+
+ // read the mappings
+ for (int c = 0; c < size; c++) {
+ data.position(offset + (c * 2));
+ map[c] = data.getChar();
+ }
+
+ // reset the position
+ data.reset();
+
+ addSegment(startCodes[i], endCodes[i], map);
+ }
+ }
+ }
+
+ /**
+ * Get the data in the map as a byte buffer
+ */
+ public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ // write the header
+ buf.putShort(getFormat());
+ buf.putShort((short) getLength());
+ buf.putShort(getLanguage());
+
+ // write the various values
+ buf.putShort((short) (getSegmentCount() * 2));
+ buf.putShort(getSearchRange());
+ buf.putShort(getEntrySelector());
+ buf.putShort(getRangeShift());
+
+ // write the endCodes
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+ buf.putShort((short) s.endCode);
+ }
+
+ // write the pad
+ buf.putShort((short) 0);
+
+ // write the startCodes
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+ buf.putShort((short) s.startCode);
+ }
+
+ // write the idDeltas for segments using deltas
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+
+ if (!s.hasMap) {
+ Integer idDelta = (Integer) segments.get(s);
+ buf.putShort(idDelta.shortValue());
+ } else {
+ buf.putShort((short) 0);
+ }
+ }
+
+ // the start of the glyph array
+ int glyphArrayOffset = 16 + (8 * getSegmentCount());
+
+ // write the idRangeOffsets and maps for segments using maps
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+
+ if (s.hasMap) {
+ // first set the offset, which is the number of bytes from the
+ // current position to the current offset
+ buf.putShort((short) (glyphArrayOffset - buf.position()));
+
+ // remember the current position
+ buf.mark();
+
+ // move the position to the offset
+ buf.position(glyphArrayOffset);
+
+ // now write the map
+ char[] map = (char[]) segments.get(s);
+ for (int c = 0; c < map.length; c++) {
+ buf.putChar(map[c]);
+ }
+
+ // reset the data pointer
+ buf.reset();
+
+ // update the offset
+ glyphArrayOffset += map.length * 2;
+ } else {
+ buf.putShort((short) 0);
+ }
+ }
+
+ // make sure we are at the end of the buffer before we flip
+ buf.position(glyphArrayOffset);
+
+ // reset the data pointer
+ buf.flip();
+
+ return buf;
+ }
+
+ /**
+ * Get the segment count
+ */
+ public short getSegmentCount() {
+ return (short) segments.size();
+ }
+
+ /**
+ * Get the search range
+ */
+ public short getSearchRange() {
+ double pow = Math.floor(Math.log(getSegmentCount()) / Math.log(2));
+ double pow2 = Math.pow(2, pow);
+
+ return (short) (2 * pow2);
+ }
+
+ /**
+ * Get the entry selector
+ */
+ public short getEntrySelector() {
+ int sr2 = getSearchRange() / 2;
+ return (short) (Math.log(sr2) / Math.log(2));
+ }
+
+ /**
+ * Get the rangeShift()
+ */
+ public short getRangeShift() {
+ return (short) ((2 * getSegmentCount()) - getSearchRange());
+ }
+
+ /** Get a pretty string */
+ @Override public String toString() {
+ StringBuffer buf = new StringBuffer();
+ String indent = " ";
+
+ buf.append(super.toString());
+ buf.append(indent + "SegmentCount : " + getSegmentCount() + "\n");
+ buf.append(indent + "SearchRange : " + getSearchRange() + "\n");
+ buf.append(indent + "EntrySelector: " + getEntrySelector() + "\n");
+ buf.append(indent + "RangeShift : " + getRangeShift() + "\n");
+
+ for (Iterator i = segments.keySet().iterator(); i.hasNext();) {
+ Segment s = (Segment) i.next();
+
+ buf.append(indent);
+ buf.append("Segment: " + Integer.toHexString(s.startCode));
+ buf.append("-" + Integer.toHexString(s.endCode) + " ");
+ buf.append("hasMap: " + s.hasMap + " ");
+
+ if (!s.hasMap) {
+ buf.append("delta: " + segments.get(s));
+ }
+
+ buf.append("\n");
+ }
+
+ return buf.toString();
+ }
+
+ class Segment implements Comparable {
+ /** the end code (highest code in this segment) */
+ int endCode;
+
+ /** the start code (lowest code in this segment) */
+ int startCode;
+
+ /** whether it is a map or a delta */
+ boolean hasMap;
+
+ /** Create a new segment */
+ public Segment(short startCode, short endCode, boolean hasMap) {
+ // convert from unsigned short
+ this.endCode = (0xffff & endCode);
+ this.startCode = (0xffff & startCode);
+
+ this.hasMap = hasMap;
+ }
+
+ /** Equals based on compareTo (only compares endCode) */
+ @Override public boolean equals(Object o) {
+ return (compareTo(o) == 0);
+ }
+
+ /** Segments sort by increasing endCode */
+ public int compareTo(Object o) {
+ if (!(o instanceof Segment)) {
+ return -1;
+ }
+
+ Segment s = (Segment) o;
+
+ // if regions overlap at all, declare the segments equal,
+ // to avoid overlap in the segment list
+ if (((s.endCode >= startCode) && (s.endCode <= endCode)) ||
+ ((s.startCode >= startCode) && (s.startCode <= endCode))) {
+ return 0;
+ } if (endCode > s.endCode) {
+ return 1;
+ } else if (endCode < s.endCode) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat6.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat6.java
new file mode 100644
index 0000000..35668f2
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CMapFormat6.java
@@ -0,0 +1,137 @@
+/*
+ * $Id: CMapFormat6.java,v 1.1 2009/02/16 00:26:24 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import java.util.*;
+
+/**
+ *
+ * @author jkaplan
+ */
+public class CMapFormat6 extends CMap {
+ /** First character code of subrange. */
+ private short firstCode;
+ /** Number of character codes in subrange. */
+ private short entryCount;
+ /** Array of glyph index values for character codes in the range. */
+ private short [] glyphIndexArray;
+ /** a reverse lookup from glyph id to index. */
+ private HashMap glyphLookup = new HashMap();
+
+ /** Creates a new instance of CMapFormat0 */
+ protected CMapFormat6(short language) {
+ super((short) 6, language);
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public short getLength() {
+ // start with the size of the fixed header
+ short size = 5 * 2;
+
+ // add the size of each segment header
+ size += entryCount * 2;
+ return size;
+ }
+
+ /**
+ * Cannot map from a byte
+ */
+ public byte map(byte src) {
+ char c = map((char) src);
+ if (c < Byte.MIN_VALUE || c > Byte.MAX_VALUE) {
+ // out of range
+ return 0;
+ }
+ return (byte) c;
+ }
+
+ /**
+ * Map from char
+ */
+ public char map(char src) {
+
+ // find first segment with endcode > src
+ if (src < firstCode || src > (firstCode + entryCount)) {
+ // Codes outside of the range are assumed to be missing and are
+ // mapped to the glyph with index 0
+ return '\000';
+ }
+ return (char) glyphIndexArray[src - firstCode];
+ }
+
+ /**
+ * Get the src code which maps to the given glyphID
+ */
+ public char reverseMap(short glyphID) {
+ Short result = glyphLookup.get(new Short(glyphID));
+ if (result == null) {
+ return '\000';
+ }
+ return (char) result.shortValue();
+ }
+
+
+ /**
+ * Get the data in this map as a ByteBuffer
+ */
+ public void setData(int length, ByteBuffer data) {
+ // read the table size values
+ firstCode = data.getShort();
+ entryCount = data.getShort();
+
+ glyphIndexArray = new short [entryCount];
+ for (int i = 0; i < glyphIndexArray.length; i++) {
+ glyphIndexArray[i] = data.getShort();
+ glyphLookup.put(new Short(glyphIndexArray[i]),
+ new Short((short) (i + firstCode)));
+ }
+ }
+
+ /**
+ * Get the data in the map as a byte buffer
+ */
+ public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ // write the header
+ buf.putShort(getFormat());
+ buf.putShort((short) getLength());
+ buf.putShort(getLanguage());
+
+ // write the various values
+ buf.putShort(firstCode);
+ buf.putShort(entryCount);
+
+ // write the endCodes
+ for (int i = 0; i < glyphIndexArray.length; i++) {
+ buf.putShort(glyphIndexArray[i]);
+ }
+ // reset the data pointer
+ buf.flip();
+
+ return buf;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/CmapTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CmapTable.java
new file mode 100644
index 0000000..cb14590
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/CmapTable.java
@@ -0,0 +1,278 @@
+/*
+ * $Id: CmapTable.java,v 1.3 2009/02/12 13:53:57 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Represents the TTF "cmap" table
+ *
+ * @author jkaplan
+ */
+public class CmapTable extends TrueTypeTable {
+
+ /** Holds value of property version. */
+ private short version;
+
+ /**
+ * Holds the CMap subtables, sorted properly
+ */
+ private SortedMap subtables;
+
+ /** Creates a new instance of CmapTable */
+ protected CmapTable() {
+ super(TrueTypeTable.CMAP_TABLE);
+
+ setVersion((short) 0x0);
+
+ subtables = Collections.synchronizedSortedMap(new TreeMap());
+ }
+
+ /**
+ * Add a CMap
+ */
+ public void addCMap(short platformID, short platformSpecificID,
+ CMap cMap) {
+ CmapSubtable key = new CmapSubtable(platformID, platformSpecificID);
+ subtables.put(key, cMap);
+ }
+
+ /**
+ * Get a CMap by platform and specific ID
+ */
+ public CMap getCMap(short platformID, short platformSpecificID) {
+ CmapSubtable key = new CmapSubtable(platformID, platformSpecificID);
+ return (CMap) subtables.get(key);
+ }
+
+ /**
+ * Get all CMaps
+ */
+ public CMap[] getCMaps() {
+ Collection c = subtables.values();
+ CMap[] maps = new CMap[c.size()];
+
+ c.toArray(maps);
+
+ return maps;
+ }
+
+ /**
+ * Remove a CMap
+ */
+ public void removeCMap(short platformID, short platformSpecificID) {
+ CmapSubtable key = new CmapSubtable(platformID, platformSpecificID);
+ subtables.remove(key);
+ }
+
+ @Override public void setData(ByteBuffer data) {
+ setVersion(data.getShort());
+
+ short numberSubtables = data.getShort();
+
+ for (int i = 0; i < numberSubtables; i++) {
+ short platformID = data.getShort();
+ short platformSpecificID = data.getShort();
+ int offset = data.getInt();
+
+ data.mark();
+
+ // get the position from the start of this buffer
+ data.position(offset);
+
+ ByteBuffer mapData = data.slice();
+
+ data.reset();
+
+ try {
+ CMap cMap = CMap.getMap(mapData);
+ if (cMap != null) {
+ addCMap(platformID, platformSpecificID, cMap);
+ }
+ } catch (Exception ex) {
+ System.out.println("Error reading map. PlatformID=" +
+ platformID + ", PlatformSpecificID=" +
+ platformSpecificID);
+ System.out.println("Reason: " + ex);
+ }
+ }
+ }
+
+ @Override public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ // write the table header
+ buf.putShort(getVersion());
+ buf.putShort((short) subtables.size());
+
+ // the current offset to write to, starts at the end of the
+ // subtables
+ int curOffset = 4 + (subtables.size() * 8);
+
+ // write the subtables
+ for (Iterator i = subtables.keySet().iterator(); i.hasNext();) {
+ CmapSubtable cms = (CmapSubtable) i.next();
+ CMap map = (CMap) subtables.get(cms);
+
+ buf.putShort(cms.platformID);
+ buf.putShort(cms.platformSpecificID);
+ buf.putInt(curOffset);
+
+ curOffset += map.getLength();
+ }
+
+ // write the tables
+ for (Iterator i = subtables.values().iterator(); i.hasNext();) {
+ CMap map = (CMap) i.next();
+ buf.put(map.getData());
+ }
+
+ // reset the position to the start of the buffer
+ buf.flip();
+
+ return buf;
+ }
+
+ /**
+ * Get the size of the table, in bytes
+ */
+ @Override public int getLength() {
+ // start with the size of the fixed data
+ int length = 4;
+
+ // add the size of the subtables
+ length += subtables.size() * 8;
+
+ // add the size of the dynamic data
+ for (Iterator i = subtables.values().iterator(); i.hasNext();) {
+ // add the size of the subtable data
+ CMap map = (CMap) i.next();
+ length += map.getLength();
+ }
+
+ return length;
+ }
+
+
+ /** Getter for property version.
+ * @return Value of property version.
+ *
+ */
+ public short getVersion() {
+ return this.version;
+ }
+
+ /** Setter for property version.
+ * @param version New value of property version.
+ *
+ */
+ public void setVersion(short version) {
+ this.version = version;
+ }
+
+ /**
+ * Get the number of tables
+ */
+ public short getNumberSubtables() {
+ return (short) subtables.size();
+ }
+
+ /** Print a pretty string */
+ @Override public String toString() {
+ StringBuffer buf = new StringBuffer();
+ String indent = " ";
+
+ buf.append(indent + "Version: " + this.getVersion() + "\n");
+ buf.append(indent + "NumMaps: " + this.getNumberSubtables() + "\n");
+
+ for (Iterator i = subtables.keySet().iterator(); i.hasNext();) {
+ CmapSubtable key = (CmapSubtable) i.next();
+
+ buf.append(indent + "Map: platformID: " + key.platformID +
+ " PlatformSpecificID: " + key.platformSpecificID + "\n");
+
+ CMap map = (CMap) subtables.get(key);
+
+ buf.append(map.toString());
+ }
+
+ return buf.toString();
+ }
+
+ class CmapSubtable implements Comparable {
+ /**
+ * The platformID for this subtable
+ */
+ short platformID;
+
+ /**
+ * The platform-specific id
+ */
+ short platformSpecificID;
+
+ /**
+ * Create a Cmap subtable
+ */
+ protected CmapSubtable(short platformID, short platformSpecificID) {
+ this.platformID = platformID;
+ this.platformSpecificID = platformSpecificID;
+ }
+
+ /**
+ * Compare two subtables
+ */
+ @Override public boolean equals(Object obj) {
+ return (compareTo(obj) == 0);
+ }
+
+ /**
+ * Sort ascending by platform ID and then specific ID
+ */
+ public int compareTo(Object obj) {
+ if (!(obj instanceof CmapSubtable)) {
+ return -1;
+ }
+
+ CmapSubtable cms = (CmapSubtable) obj;
+ if (platformID < cms.platformID) {
+ return -1;
+ } else if (platformID > cms.platformID) {
+ return 1;
+ } else {
+ if (platformSpecificID < cms.platformSpecificID) {
+ return -1;
+ } else if (platformSpecificID > cms.platformSpecificID) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/Glyf.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/Glyf.java
new file mode 100644
index 0000000..443a1ef
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/Glyf.java
@@ -0,0 +1,208 @@
+/*
+ * $Id: Glyf.java,v 1.2 2007/12/20 18:33:31 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * A single glyph in a pdf font. May be simple or compound via subclasses
+ */
+public class Glyf {
+ /** If true, the glyf is compound */
+ private boolean isCompound;
+
+ /** the number of contours */
+ private short numContours;
+
+ /** the minimum x value */
+ private short minX;
+
+ /** the minimum y value */
+ private short minY;
+
+ /** the maximum x value */
+ private short maxX;
+
+ /** the maximum y value */
+ private short maxY;
+
+ /**
+ * Creates a new instance of glyf
+ * Don't use this directly, use Glyf.getGlyf()
+ */
+ protected Glyf() {
+ }
+
+ /**
+ * Get a map from the given data
+ *
+ * This method reads the format, data and length variables of
+ * the map.
+ */
+ public static Glyf getGlyf(ByteBuffer data) {
+ short numContours = data.getShort();
+
+ Glyf g = null;
+ if (numContours == 0) {
+ // no glyph data
+ g = new Glyf();
+ } else if (numContours == -1) {
+ // compound glyf
+ g = new GlyfCompound();
+ } else if (numContours > 0) {
+ // simple glyf
+ g = new GlyfSimple();
+ } else {
+ throw new IllegalArgumentException("Unknown glyf type: " +
+ numContours);
+ }
+
+ g.setNumContours(numContours);
+ g.setMinX(data.getShort());
+ g.setMinY(data.getShort());
+ g.setMaxX(data.getShort());
+ g.setMaxY(data.getShort());
+
+ // do glyphtype-specific parsing
+ g.setData(data);
+
+ return g;
+ }
+
+ /**
+ * Set the data for this glyf. Do nothing, since a glyf with
+ * no contours has no glyf data.
+ */
+ public void setData(ByteBuffer data) {
+ return;
+ }
+
+ /**
+ * Get the data in this glyf as a byte buffer. Return the basic
+ * glyf data only, since there is no specific data. This method returns
+ * the data un-flipped, so subclasses can simply append to the allocated
+ * buffer.
+ */
+ public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ buf.putShort(getNumContours());
+ buf.putShort(getMinX());
+ buf.putShort(getMinY());
+ buf.putShort(getMaxX());
+ buf.putShort(getMaxY());
+
+ // don't flip the buffer, since it may be used by subclasses
+ return buf;
+ }
+
+ /**
+ * Get the length of this glyf. A glyf with no data has a length
+ * of 10 (2 bytes each for 5 short values)
+ */
+ public short getLength() {
+ return 10;
+ }
+
+ /**
+ * Get whether this is a simple or compound glyf
+ */
+ public boolean isCompound() {
+ return isCompound;
+ }
+
+ /**
+ * Set whether this is a simple or compound glyf
+ */
+ protected void setCompound(boolean isCompound) {
+ this.isCompound = isCompound;
+ }
+
+ /**
+ * Get the number of contours in this glyf
+ */
+ public short getNumContours() {
+ return numContours;
+ }
+
+ /**
+ * Set the number of contours in this glyf
+ */
+ protected void setNumContours(short numContours) {
+ this.numContours = numContours;
+ }
+
+ /**
+ * Get the minimum x in this glyf
+ */
+ public short getMinX() {
+ return minX;
+ }
+
+ /**
+ * Set the minimum X in this glyf
+ */
+ protected void setMinX(short minX) {
+ this.minX = minX;
+ }
+
+ /**
+ * Get the minimum y in this glyf
+ */
+ public short getMinY() {
+ return minY;
+ }
+
+ /**
+ * Set the minimum Y in this glyf
+ */
+ protected void setMinY(short minY) {
+ this.minY = minY;
+ }
+ /**
+ * Get the maximum x in this glyf
+ */
+ public short getMaxX() {
+ return maxX;
+ }
+
+ /**
+ * Set the maximum X in this glyf
+ */
+ protected void setMaxX(short maxX) {
+ this.maxX = maxX;
+ }
+
+ /**
+ * Get the maximum y in this glyf
+ */
+ public short getMaxY() {
+ return maxY;
+ }
+
+ /**
+ * Set the maximum Y in this glyf
+ */
+ protected void setMaxY(short maxY) {
+ this.maxY = maxY;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfCompound.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfCompound.java
new file mode 100644
index 0000000..1a23d22
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfCompound.java
@@ -0,0 +1,332 @@
+/*
+ * $Id: GlyfCompound.java,v 1.3 2009/02/12 13:53:57 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A single simple glyph in a pdf font.
+ */
+public class GlyfCompound extends Glyf {
+ /** flags */
+ private static final int ARG_1_AND_2_ARE_WORDS = 0x1;
+ private static final int ARGS_ARE_XY_VALUES = 0x2;
+ private static final int ROUND_XY_TO_GRID = 0x4;
+ private static final int WE_HAVE_A_SCALE = 0x8;
+ private static final int MORE_COMPONENTS = 0x20;
+ private static final int WE_HAVE_AN_X_AND_Y_SCALE = 0x40;
+ private static final int WE_HAVE_A_TWO_BY_TWO = 0x80;
+ private static final int WE_HAVE_INSTRUCTIONS = 0x100;
+ private static final int USE_MY_METRICS = 0x200;
+ private static final int OVERLAP_COMPOUND = 0x400;
+
+ /** the flags for each compound glyph */
+ private GlyfComponent[] components;
+
+ /** the instructions for the compound as a whole */
+ private byte[] instructions;
+
+ /**
+ * Creates a new instance of a simple glyf
+ */
+ protected GlyfCompound() {
+ }
+
+ /**
+ * Set the data for this glyf.
+ */
+ @Override public void setData(ByteBuffer data) {
+ // int pos = data.position();
+ // byte[] prdata = new byte[data.remaining()];
+ // data.get(prdata);
+ // HexDump.printData(prdata);
+ // data.position(pos);
+
+ // read the contour end points
+ List comps = new ArrayList();
+ GlyfComponent cur = null;
+ boolean hasInstructions = false;
+
+ do {
+ cur = new GlyfComponent();
+ cur.flags = data.getShort();
+ cur.glyphIndex = data.getShort();
+
+ // read either e/f or matching points, as shorts or bytes...
+ if (((cur.flags & ARG_1_AND_2_ARE_WORDS) != 0) &&
+ ((cur.flags & ARGS_ARE_XY_VALUES) != 0)) {
+ cur.e = data.getShort();
+ cur.f = data.getShort();
+ } else if (!((cur.flags & ARG_1_AND_2_ARE_WORDS) != 0) &&
+ ((cur.flags & ARGS_ARE_XY_VALUES) != 0)) {
+ cur.e = (float) data.get();
+ cur.f = (float) data.get();
+ } else if ( ((cur.flags & ARG_1_AND_2_ARE_WORDS) != 0) &&
+ !((cur.flags & ARGS_ARE_XY_VALUES) != 0)) {
+ cur.compoundPoint = data.getShort();
+ cur.componentPoint = data.getShort();
+ } else {
+ cur.compoundPoint = data.get();
+ cur.componentPoint = data.get();
+ }
+
+ // read the linear transform
+ if ((cur.flags & WE_HAVE_A_SCALE) != 0) {
+ cur.a = (float) data.getShort() / (float) (1 << 14);
+ cur.d = cur.a;
+ } else if ((cur.flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0) {
+ cur.a = (float) data.getShort() / (float) (1 << 14);
+ cur.d = (float) data.getShort() / (float) (1 << 14);
+ } else if ((cur.flags & WE_HAVE_A_TWO_BY_TWO) != 0) {
+ cur.a = (float) data.getShort() / (float) (1 << 14);
+ cur.b = (float) data.getShort() / (float) (1 << 14);
+ cur.c = (float) data.getShort() / (float) (1 << 14);
+ cur.d = (float) data.getShort() / (float) (1 << 14);
+ }
+
+ if ((cur.flags & WE_HAVE_INSTRUCTIONS) != 0) {
+ hasInstructions = true;
+ }
+
+ comps.add(cur);
+ } while ((cur.flags & MORE_COMPONENTS) != 0);
+
+ GlyfComponent[] componentArray = new GlyfComponent[comps.size()];
+ comps.toArray(componentArray);
+ setComponents(componentArray);
+
+ byte[] instr = null;
+ if (hasInstructions) {
+ // read the instructions
+ short numInstructions = data.getShort();
+ instr = new byte[numInstructions];
+ for (int i = 0; i < instr.length; i++) {
+ instr[i] = data.get();
+ }
+ } else {
+ instr = new byte[0];
+ }
+ setInstructions(instr);
+ }
+
+ /**
+ * Get the data in this glyf as a byte buffer. Not implemented.
+ */
+ @Override public ByteBuffer getData() {
+ ByteBuffer buf = super.getData();
+
+ // don't flip the buffer, since it may be used by subclasses
+ return buf;
+ }
+
+ /**
+ * Get the length of this glyf. Not implemented.
+ */
+ @Override public short getLength() {
+
+ // start with the length of the superclass
+ short length = super.getLength();
+ return length;
+ }
+
+ /**
+ * Get the number of components in this compound
+ */
+ public int getNumComponents() {
+ return components.length;
+ }
+
+ /**
+ * Get a given flag
+ */
+ public short getFlag(int index) {
+ return components[index].flags;
+ }
+
+ /**
+ * Get the glyf index for a given glyf
+ */
+ public short getGlyphIndex(int index) {
+ return components[index].glyphIndex;
+ }
+
+ /**
+ * Get the base affine transform. This is based on a whacy formula
+ * defined in the true type font spec.
+ */
+ public float[] getTransform(int index) {
+ GlyfComponent gc = components[index];
+
+ float m = (float) Math.max(Math.abs(gc.a), Math.abs(gc.b));
+ if (Math.abs(Math.abs(gc.a) - Math.abs(gc.c)) < (33 / 65536)) {
+ m *= 2;
+ }
+
+ float n = (float) Math.max(Math.abs(gc.c), Math.abs(gc.d));
+ if (Math.abs(Math.abs(gc.c) - Math.abs(gc.d)) < (33 / 65536)) {
+ n *= 2;
+ }
+
+ float e = m * gc.e;
+ float f = n * gc.f;
+
+ return new float[] { gc.a, gc.b, gc.c, gc.d, e, f };
+ }
+
+ /**
+ * Get the point in the compound glyph to match
+ */
+ public int getCompoundPoint(int index) {
+ return components[index].compoundPoint;
+ }
+
+ /**
+ * Get the point in the component glyph to match
+ */
+ public int getComponentPoint(int index) {
+ return components[index].componentPoint;
+ }
+
+ /**
+ * Determine whether args 1 and 2 are words or bytes
+ */
+ public boolean argsAreWords(int index) {
+ return ((getFlag(index) & ARG_1_AND_2_ARE_WORDS) != 0);
+ }
+
+ /**
+ * Determine whether args 1 and 2 are xy values or point indices
+ */
+ public boolean argsAreXYValues(int index) {
+ return ((getFlag(index) & ARGS_ARE_XY_VALUES) != 0);
+ }
+
+ /**
+ * Determine whether to round XY values to the grid
+ */
+ public boolean roundXYToGrid(int index) {
+ return ((getFlag(index) & ROUND_XY_TO_GRID) != 0);
+ }
+
+ /**
+ * Determine whether there is a simple scale
+ */
+ public boolean hasAScale(int index) {
+ return ((getFlag(index) & WE_HAVE_A_SCALE) != 0);
+ }
+
+ /**
+ * Determine whether there are more components left to read
+ */
+ protected boolean moreComponents(int index) {
+ return ((getFlag(index) & MORE_COMPONENTS) != 0);
+ }
+
+ /**
+ * Determine whether there are separate scales on X and Y
+ */
+ protected boolean hasXYScale(int index) {
+ return ((getFlag(index) & WE_HAVE_AN_X_AND_Y_SCALE) != 0);
+ }
+
+ /**
+ * Determine whether there is a 2x2 transform
+ */
+ protected boolean hasTwoByTwo(int index) {
+ return ((getFlag(index) & WE_HAVE_A_TWO_BY_TWO) != 0);
+ }
+
+ /**
+ * Determine whether there are instructions
+ */
+ protected boolean hasInstructions(int index) {
+ return ((getFlag(index) & WE_HAVE_INSTRUCTIONS) != 0);
+ }
+
+ /**
+ * Use the metrics of this component for the compound
+ */
+ public boolean useMetrics(int index) {
+ return ((getFlag(index) & USE_MY_METRICS) != 0);
+ }
+
+ /**
+ * This component overlaps the existing compound
+ */
+ public boolean overlapCompound(int index) {
+ return ((getFlag(index) & OVERLAP_COMPOUND) != 0);
+ }
+
+ /**
+ * Set the components
+ */
+ void setComponents(GlyfComponent[] components) {
+ this.components = components;
+ }
+
+ /**
+ * Get the number of instructions
+ */
+ public short getNumInstructions() {
+ return (short) instructions.length;
+ }
+
+ /**
+ * Get a given instruction
+ */
+ public byte getInstruction(int index) {
+ return instructions[index];
+ }
+
+ /**
+ * Set the instructions
+ */
+ protected void setInstructions(byte[] instructions) {
+ this.instructions = instructions;
+ }
+
+ /**
+ * The record for a single component of this compound glyph
+ */
+ class GlyfComponent {
+ /** flags */
+ short flags;
+
+ /** the index of the component glyf */
+ short glyphIndex;
+
+ /** the points to match */
+ int compoundPoint;
+ int componentPoint;
+
+ /** affine transform of this component */
+ float a = 1.0f;
+ float b = 0.0f;
+ float c = 0.0f;
+ float d = 1.0f;
+ float e = 0.0f;
+ float f = 0.0f;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfSimple.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfSimple.java
new file mode 100644
index 0000000..c99b331
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfSimple.java
@@ -0,0 +1,366 @@
+/*
+ * $Id: GlyfSimple.java,v 1.2 2007/12/20 18:33:31 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * A single simple glyph in a pdf font.
+ *
+ * @author ?
+ * @author Ferenc Hechler (ferenc@hechler.de)
+ * @author Joerg Jahnke (joergjahnke@users.sourceforge.net)
+ */
+public class GlyfSimple extends Glyf {
+
+ /** the end points of the various contours */
+ private short[] contourEndPts;
+ /** the instructions */
+ private byte[] instructions;
+ /** the flags */
+ private byte[] flags;
+ /** the x coordinates */
+ private short[] xCoords;
+ /** the y coordinates */
+ private short[] yCoords;
+
+ /**
+ * Creates a new instance of a simple glyf
+ */
+ protected GlyfSimple() {
+ }
+
+ /**
+ * Set the data for this glyf.
+ */
+ @Override
+ public void setData(final ByteBuffer data) {
+ // int pos = data.position();
+ // byte[] prdata = new byte[data.remaining()];
+ // data.get(prdata);
+ // HexDump.printData(prdata);
+ // data.position(pos);
+
+
+ // read the contour end points
+ final short[] contourEndPts_ = new short[getNumContours()];
+ for (int i = 0, to = contourEndPts_.length; i < to; ++i) {
+ contourEndPts_[i] = data.getShort();
+ }
+ setContourEndPoints(contourEndPts_);
+
+ // the number of points in the glyf is the number of the end
+ // point in the last contour
+ final int numPoints = getContourEndPoint(getNumContours() - 1) + 1;
+
+ // read the instructions
+ final short numInstructions = data.getShort();
+ final byte[] instructions_ = new byte[numInstructions];
+ data.get(instructions_);
+ setInstructions(instructions_);
+
+ // read the flags
+ final byte[] flags_ = new byte[numPoints];
+ for (int i = 0, to = flags_.length; i < to; ++i) {
+ flags_[i] = data.get();
+
+ // check for repeats
+ if ((flags_[i] & 0x8) != 0) {
+ final byte f = flags_[i];
+ final int n = (int) (data.get() & 0xff);
+ for (int c = 0; c < n; c++) {
+ flags_[++i] = f;
+ }
+ }
+ }
+ setFlags(flags_);
+
+ // read the x coordinates
+ final short[] xCoords_ = new short[numPoints];
+ for (int i = 0, to = xCoords_.length; i < to; i++) {
+ if (i > 0) {
+ xCoords_[i] = xCoords_[i - 1];
+ }
+
+ // read this value
+ if (xIsByte(i)) {
+ int val = (int) (data.get() & 0xff);
+ if (!xIsSame(i)) {
+ // the xIsSame bit controls the sign
+ val = -val;
+ }
+ xCoords_[i] += val;
+ } else if (!xIsSame(i)) {
+ xCoords_[i] += data.getShort();
+ }
+ }
+ setXCoords(xCoords_);
+
+ // read the y coordinates
+ final short[] yCoords_ = new short[numPoints];
+ for (int i = 0, to = yCoords_.length; i < to; i++) {
+ if (i > 0) {
+ yCoords_[i] = yCoords_[i - 1];
+ }
+ // read this value
+ if (yIsByte(i)) {
+ int val = (int) (data.get() & 0xff);
+ if (!yIsSame(i)) {
+ // the xIsSame bit controls the sign
+ val = -val;
+ }
+ yCoords_[i] += val;
+ } else if (!yIsSame(i)) {
+ yCoords_[i] += data.getShort();
+ }
+ }
+ setYCoords(yCoords_);
+ }
+
+ /**
+ * Get the data in this glyf as a byte buffer. Return the basic
+ * glyf data only, since there is no specific data. This method returns
+ * the data un-flipped, so subclasses can simply append to the allocated
+ * buffer.
+ */
+ @Override
+ public ByteBuffer getData() {
+ ByteBuffer buf = super.getData();
+
+ // write the contour end points
+ for (int i = 0; i < getNumContours(); i++) {
+ buf.putShort(getContourEndPoint(i));
+ }
+
+ // write the instructions
+ buf.putShort(getNumInstructions());
+ for (int i = 0; i < getNumInstructions(); i++) {
+ buf.put(getInstruction(i));
+ }
+
+ // write the flags
+ for (int i = 0; i < getNumPoints(); i++) {
+ // check for repeats
+ byte r = 0;
+ while (i > 0 && (getFlag(i) == getFlag(i - 1))) {
+ r++;
+ i++;
+ }
+ if (r > 0) {
+ buf.put(r);
+ } else {
+ buf.put(getFlag(i));
+ }
+ }
+
+ // write the x coordinates
+ for (int i = 0; i < getNumPoints(); i++) {
+ if (xIsByte(i)) {
+ buf.put((byte) getXCoord(i));
+ } else if (!xIsSame(i)) {
+ buf.putShort(getXCoord(i));
+ }
+ }
+
+ // write the y coordinates
+ for (int i = 0; i < getNumPoints(); i++) {
+ if (yIsByte(i)) {
+ buf.put((byte) getYCoord(i));
+ } else if (!yIsSame(i)) {
+ buf.putShort(getYCoord(i));
+ }
+ }
+
+ // don't flip the buffer, since it may be used by subclasses
+ return buf;
+ }
+
+ /**
+ * Get the length of this glyf.
+ */
+ @Override
+ public short getLength() {
+ // start with the length of the superclass
+ short length = super.getLength();
+
+ // add the length of the end points
+ length += getNumContours() * 2;
+
+ // add the length of the instructions
+ length += 2 + getNumInstructions();
+
+ // add the length of the flags, avoiding repeats
+ for (int i = 0; i < getNumPoints(); i++) {
+ // check for repeats
+ while (i > 0 && (getFlag(i) == getFlag(i - 1))) {
+ // empty
+ }
+ length++;
+ }
+
+ // add the length of the xCoordinates
+ for (int i = 0; i < getNumPoints(); i++) {
+ if (xIsByte(i)) {
+ length++;
+ } else if (!xIsSame(i)) {
+ length += 2;
+ }
+
+ if (yIsByte(i)) {
+ length++;
+ } else if (!yIsSame(i)) {
+ length += 2;
+ }
+ }
+
+ return length;
+ }
+
+ /**
+ * Get the end point of a given contour
+ */
+ public short getContourEndPoint(int index) {
+ return contourEndPts[index];
+ }
+
+ /**
+ * Set the number of contours in this glyf
+ */
+ protected void setContourEndPoints(short[] contourEndPts) {
+ this.contourEndPts = contourEndPts;
+ }
+
+ /**
+ * Get the number of instructions
+ */
+ public short getNumInstructions() {
+ return (short) instructions.length;
+ }
+
+ /**
+ * Get a given instruction
+ */
+ public byte getInstruction(int index) {
+ return instructions[index];
+ }
+
+ /**
+ * Set the instructions
+ */
+ protected void setInstructions(byte[] instructions) {
+ this.instructions = instructions;
+ }
+
+ /**
+ * Get the number of points in the glyf
+ */
+ public short getNumPoints() {
+ return (short) flags.length;
+ }
+
+ /**
+ * Get a given flag
+ */
+ public byte getFlag(int pointIndex) {
+ return flags[pointIndex];
+ }
+
+ /**
+ * Determine whether the given point is on the curve
+ */
+ public boolean onCurve(int pointIndex) {
+ return ((getFlag(pointIndex) & 0x1) != 0);
+ }
+
+ /**
+ * Determine whether the x value for the given point is byte or short.
+ * If true, it is a byte, if false it is a short
+ */
+ protected boolean xIsByte(int pointIndex) {
+ return ((getFlag(pointIndex) & 0x2) != 0);
+ }
+
+ /**
+ * Determine whether the x value for the given point is byte or short.
+ * If true, it is a byte, if false it is a short
+ */
+ protected boolean yIsByte(int pointIndex) {
+ return ((getFlag(pointIndex) & 0x4) != 0);
+ }
+
+ /**
+ * Determine whether this flag repeats
+ */
+ protected boolean repeat(int pointIndex) {
+ return ((getFlag(pointIndex) & 0x8) != 0);
+ }
+
+ /**
+ * Determine whether the x value for the given point is the same as
+ * the previous value.
+ */
+ protected boolean xIsSame(int pointIndex) {
+ return ((getFlag(pointIndex) & 0x10) != 0);
+ }
+
+ /**
+ * Determine whether the y value for the given point is the same as
+ * the previous value.
+ */
+ protected boolean yIsSame(int pointIndex) {
+ return ((getFlag(pointIndex) & 0x20) != 0);
+ }
+
+ /**
+ * Set the flags
+ */
+ protected void setFlags(byte[] flags) {
+ this.flags = flags;
+ }
+
+ /**
+ * Get a given x coordinate
+ */
+ public short getXCoord(int pointIndex) {
+ return xCoords[pointIndex];
+ }
+
+ /**
+ * Set the x coordinates
+ */
+ protected void setXCoords(short[] xCoords) {
+ this.xCoords = xCoords;
+ }
+
+ /**
+ * Get a given y coordinate
+ */
+ public short getYCoord(int pointIndex) {
+ return yCoords[pointIndex];
+ }
+
+ /**
+ * Set the x coordinates
+ */
+ protected void setYCoords(short[] yCoords) {
+ this.yCoords = yCoords;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfTable.java
new file mode 100644
index 0000000..28c8b4a
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/GlyfTable.java
@@ -0,0 +1,156 @@
+/*
+ * $Id: GlyfTable.java,v 1.2 2007/12/20 18:33:30 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * Model the TrueType Glyf table
+ */
+public class GlyfTable extends TrueTypeTable {
+ /**
+ * the glyph data, as either a byte buffer (unparsed) or a
+ * glyph object (parsed)
+ */
+ private Object[] glyphs;
+
+ /**
+ * The glyph location table
+ */
+ private LocaTable loca;
+
+ /** Creates a new instance of HmtxTable */
+ protected GlyfTable(TrueTypeFont ttf) {
+ super (TrueTypeTable.GLYF_TABLE);
+
+ loca = (LocaTable) ttf.getTable("loca");
+
+ MaxpTable maxp = (MaxpTable) ttf.getTable("maxp");
+ int numGlyphs = maxp.getNumGlyphs();
+
+ glyphs = new Object[numGlyphs];
+ }
+
+ /**
+ * Get the glyph at a given index, parsing it as needed
+ */
+ public Glyf getGlyph(int index) {
+ Object o = glyphs[index];
+ if (o == null) {
+ return null;
+ }
+
+ if (o instanceof ByteBuffer) {
+ Glyf g = Glyf.getGlyf((ByteBuffer) o);
+ glyphs[index] = g;
+
+ return g;
+ } else {
+ return (Glyf) o;
+ }
+ }
+
+ /** get the data in this map as a ByteBuffer */
+ public ByteBuffer getData() {
+ int size = getLength();
+
+ ByteBuffer buf = ByteBuffer.allocate(size);
+
+ // write the offsets
+ for (int i = 0; i < glyphs.length; i++) {
+ Object o = glyphs[i];
+ if (o == null) {
+ continue;
+ }
+
+ ByteBuffer glyfData = null;
+ if (o instanceof ByteBuffer) {
+ glyfData = (ByteBuffer) o;
+ } else {
+ glyfData = ((Glyf) o).getData();
+ }
+
+ glyfData.rewind();
+ buf.put(glyfData);
+ glyfData.flip();
+ }
+
+ // reset the start pointer
+ buf.flip();
+
+ return buf;
+ }
+
+ /** Initialize this structure from a ByteBuffer */
+ public void setData(ByteBuffer data) {
+ for (int i = 0; i < glyphs.length; i++) {
+ int location = loca.getOffset(i);
+ int length = loca.getSize(i);
+
+ if (length == 0) {
+ // undefined glyph
+ continue;
+ }
+
+ data.position(location);
+ ByteBuffer glyfData = data.slice();
+ glyfData.limit(length);
+
+ glyphs[i] = glyfData;
+ }
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ int length = 0;
+
+ for (int i = 0; i < glyphs.length; i++) {
+ Object o = glyphs[i];
+ if (o == null) {
+ continue;
+ }
+
+ if (o instanceof ByteBuffer) {
+ length += ((ByteBuffer) o).remaining();
+ } else {
+ length += ((Glyf) o).getLength();
+ }
+ }
+
+ return length;
+ }
+
+ /**
+ * Create a pretty String
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ String indent = " ";
+
+ buf.append(indent + "Glyf Table: (" + glyphs.length + " glyphs)\n");
+ buf.append(indent + " Glyf 0: " + getGlyph(0));
+
+ return buf.toString();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/HeadTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/HeadTable.java
new file mode 100644
index 0000000..ea16806
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/HeadTable.java
@@ -0,0 +1,472 @@
+/*
+ * $Id: HeadTable.java,v 1.3 2007/12/20 18:33:30 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import java.util.Date;
+
+/**
+ *
+ * @author jkaplan
+ */
+public class HeadTable extends TrueTypeTable {
+
+ /** Holds value of property version. */
+ private int version;
+
+ /** Holds value of property fontRevision. */
+ private int fontRevision;
+
+ /** Holds value of property checksumAdjustment. */
+ private int checksumAdjustment;
+
+ /** Holds value of property magicNumber. */
+ private int magicNumber;
+
+ /** Holds value of property flags. */
+ private short flags;
+
+ /** Holds value of property unitsPerEm. */
+ private short unitsPerEm;
+
+ /** Holds value of property created. */
+ private long created;
+
+ /** Holds value of property modified. */
+ private long modified;
+
+ /** Holds value of property xMin. */
+ private short xMin;
+
+ /** Holds value of property yMin. */
+ private short yMin;
+
+ /** Holds value of property xMax. */
+ private short xMax;
+
+ /** Holds value of property yMax. */
+ private short yMax;
+
+ /** Holds value of property macStyle. */
+ private short macStyle;
+
+ /** Holds value of property lowestRecPPem. */
+ private short lowestRecPPem;
+
+ /** Holds value of property fontDirectionHint. */
+ private short fontDirectionHint;
+
+ /** Holds value of property indexToLocFormat. */
+ private short indexToLocFormat;
+
+ /** Holds value of property glyphDataFormat. */
+ private short glyphDataFormat;
+
+ /** Creates a new instance of HeadTable
+ * Makes up reasonable(?) defaults for all values
+ */
+ protected HeadTable() {
+ super(TrueTypeTable.HEAD_TABLE);
+
+ setVersion(0x10000);
+ setFontRevision(0x10000);
+ setChecksumAdjustment(0);
+ setMagicNumber(0x5f0f3cf5);
+ setFlags((short) 0x0);
+ setUnitsPerEm((short) 64);
+ setCreated(System.currentTimeMillis());
+ setModified(System.currentTimeMillis());
+ setXMin((short) 0);
+ setXMax((short) Short.MAX_VALUE);
+ setYMin((short) 0);
+ setYMax((short) Short.MAX_VALUE);
+ setMacStyle((short) 0x0);
+ setLowestRecPPem((short) 0);
+ setFontDirectionHint((short) 0);
+ setIndexToLocFormat((short) 0);
+ setGlyphDataFormat((short) 0);
+ }
+
+ /**
+ * 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());
+ setMagicNumber(data.getInt());
+ setFlags(data.getShort());
+ setUnitsPerEm(data.getShort());
+ setCreated(data.getLong());
+ setModified(data.getLong());
+ setXMin(data.getShort());
+ setXMax(data.getShort());
+ setYMin(data.getShort());
+ setYMax(data.getShort());
+ setMacStyle(data.getShort());
+ setLowestRecPPem(data.getShort());
+ setFontDirectionHint(data.getShort());
+ setIndexToLocFormat(data.getShort());
+ setGlyphDataFormat(data.getShort());
+ }
+
+ /**
+ * Get the data we have stored
+ */
+ public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ buf.putInt(getVersion());
+ buf.putInt(getFontRevision());
+ buf.putInt(getChecksumAdjustment());
+ buf.putInt(getMagicNumber());
+ buf.putShort(getFlags());
+ buf.putShort(getUnitsPerEm());
+ buf.putLong(getCreated());
+ buf.putLong(getModified());
+ buf.putShort(getXMin());
+ buf.putShort(getXMax());
+ buf.putShort(getYMin());
+ buf.putShort(getYMax());
+ buf.putShort(getMacStyle());
+ buf.putShort(getLowestRecPPem());
+ buf.putShort(getFontDirectionHint());
+ buf.putShort(getIndexToLocFormat());
+ buf.putShort(getGlyphDataFormat());
+
+ // reset the position to the start of the buffer
+ buf.flip();
+
+ return buf;
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ return 54;
+ }
+
+ /** Getter for property version.
+ * @return Value of property version.
+ *
+ */
+ public int getVersion() {
+ return version;
+ }
+
+ /** Getter for property fontRevision.
+ * @return Value of property fontRevision.
+ *
+ */
+ public int getFontRevision() {
+ return this.fontRevision;
+ }
+
+ /** Getter for property checksumAdjustment.
+ * @return Value of property checksumAdjustment.
+ *
+ */
+ public int getChecksumAdjustment() {
+ return this.checksumAdjustment;
+ }
+
+ /** Getter for property magicNumber.
+ * @return Value of property magicNumber.
+ *
+ */
+ public int getMagicNumber() {
+ return this.magicNumber;
+ }
+
+ /** Getter for property flags.
+ * @return Value of property flags.
+ *
+ */
+ public short getFlags() {
+ return this.flags;
+ }
+
+ /** Getter for property unitsPerEm.
+ * @return Value of property unitsPerEm.
+ *
+ */
+ public short getUnitsPerEm() {
+ return this.unitsPerEm;
+ }
+
+ /** Getter for property created.
+ * @return Value of property created.
+ *
+ */
+ public long getCreated() {
+ return this.created;
+ }
+
+ /** Getter for property modified.
+ * @return Value of property modified.
+ *
+ */
+ public long getModified() {
+ return this.modified;
+ }
+
+ /** Getter for property xMin.
+ * @return Value of property xMin.
+ *
+ */
+ public short getXMin() {
+ return this.xMin;
+ }
+
+ /** Getter for property yMin.
+ * @return Value of property yMin.
+ *
+ */
+ public short getYMin() {
+ return this.yMin;
+ }
+
+ /** Getter for property xMax.
+ * @return Value of property xMax.
+ *
+ */
+ public short getXMax() {
+ return this.xMax;
+ }
+
+ /** Getter for property yMax.
+ * @return Value of property yMax.
+ *
+ */
+ public short getYMax() {
+ return this.yMax;
+ }
+
+ /** Getter for property macStyle.
+ * @return Value of property macStyle.
+ *
+ */
+ public short getMacStyle() {
+ return this.macStyle;
+ }
+
+ /** Getter for property lowestRecPPem.
+ * @return Value of property lowestRecPPem.
+ *
+ */
+ public short getLowestRecPPem() {
+ return this.lowestRecPPem;
+ }
+
+ /** Getter for property fontDirectionHint.
+ * @return Value of property fontDirectionHint.
+ *
+ */
+ public short getFontDirectionHint() {
+ return this.fontDirectionHint;
+ }
+
+ /** Getter for property indexToLocFormat.
+ * @return Value of property indexToLocFormat.
+ *
+ */
+ public short getIndexToLocFormat() {
+ return this.indexToLocFormat;
+ }
+
+ /** Getter for property glyphDataFormat.
+ * @return Value of property glyphDataFormat.
+ *
+ */
+ public short getGlyphDataFormat() {
+ return this.glyphDataFormat;
+ }
+
+ /** Setter for property XMax.
+ * @param xMax New value of property XMax.
+ *
+ */
+ public void setXMax(short xMax) {
+ this.xMax = xMax;
+ }
+
+ /** Setter for property XMin.
+ * @param xMin New value of property XMin.
+ *
+ */
+ public void setXMin(short xMin) {
+ this.xMin = xMin;
+ }
+
+ /** Setter for property YMax.
+ * @param yMax New value of property YMax.
+ *
+ */
+ public void setYMax(short yMax) {
+ this.yMax = yMax;
+ }
+
+ /** Setter for property YMin.
+ * @param yMin New value of property YMin.
+ *
+ */
+ public void setYMin(short yMin) {
+ this.yMin = yMin;
+ }
+
+ /** Setter for property checksumAdjustment.
+ * @param checksumAdjustment New value of property checksumAdjustment.
+ *
+ */
+ public void setChecksumAdjustment(int checksumAdjustment) {
+ this.checksumAdjustment = checksumAdjustment;
+ }
+
+ /** Setter for property created.
+ * @param created New value of property created.
+ *
+ */
+ public void setCreated(long created) {
+ this.created = created;
+ }
+
+ /** Setter for property flags.
+ * @param flags New value of property flags.
+ *
+ */
+ public void setFlags(short flags) {
+ this.flags = flags;
+ }
+
+ /** Setter for property fontDirectionHint.
+ * @param fontDirectionHint New value of property fontDirectionHint.
+ *
+ */
+ public void setFontDirectionHint(short fontDirectionHint) {
+ this.fontDirectionHint = fontDirectionHint;
+ }
+
+ /** Setter for property fontRevision.
+ * @param fontRevision New value of property fontRevision.
+ *
+ */
+ public void setFontRevision(int fontRevision) {
+ this.fontRevision = fontRevision;
+ }
+
+ /** Setter for property glyphDataFormat.
+ * @param glyphDataFormat New value of property glyphDataFormat.
+ *
+ */
+ public void setGlyphDataFormat(short glyphDataFormat) {
+ this.glyphDataFormat = glyphDataFormat;
+ }
+
+ /** Setter for property indexToLocFormat.
+ * @param indexToLocFormat New value of property indexToLocFormat.
+ *
+ */
+ public void setIndexToLocFormat(short indexToLocFormat) {
+ this.indexToLocFormat = indexToLocFormat;
+ }
+
+ /** Setter for property lowestRecPPem.
+ * @param lowestRecPPem New value of property lowestRecPPem.
+ *
+ */
+ public void setLowestRecPPem(short lowestRecPPem) {
+ this.lowestRecPPem = lowestRecPPem;
+ }
+
+ /** Setter for property macStyle.
+ * @param macStyle New value of property macStyle.
+ *
+ */
+ public void setMacStyle(short macStyle) {
+ this.macStyle = macStyle;
+ }
+
+ /** Setter for property magicNumber.
+ * @param magicNumber New value of property magicNumber.
+ *
+ */
+ public void setMagicNumber(int magicNumber) {
+ this.magicNumber = magicNumber;
+ }
+
+ /** Setter for property modified.
+ * @param modified New value of property modified.
+ *
+ */
+ public void setModified(long modified) {
+ this.modified = modified;
+ }
+
+ /** Setter for property unitsPerEm.
+ * @param unitsPerEm New value of property unitsPerEm.
+ *
+ */
+ public void setUnitsPerEm(short unitsPerEm) {
+ this.unitsPerEm = unitsPerEm;
+ }
+
+ /** Setter for property version.
+ * @param version New value of property version.
+ *
+ */
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ /**
+ * Create a pretty string
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ String indent = " ";
+
+ buf.append(indent + "Version : " + Integer.toHexString(getVersion()) + "\n");
+ buf.append(indent + "Revision : " + Integer.toHexString(getFontRevision()) + "\n");
+ buf.append(indent + "ChecksumAdj : " + Integer.toHexString(getChecksumAdjustment()) + "\n");
+ buf.append(indent + "MagicNumber : " + Integer.toHexString(getMagicNumber()) + "\n");
+ buf.append(indent + "Flags : " + Integer.toBinaryString(getFlags()) + "\n");
+ buf.append(indent + "UnitsPerEm : " + getUnitsPerEm() + "\n");
+ buf.append(indent + "Created : " + new Date(getCreated()) + "\n");
+ buf.append(indent + "Modified : " + new Date(getModified()) + "\n");
+ buf.append(indent + "XMin : " + getXMin() + "\n");
+ buf.append(indent + "XMax : " + getXMax() + "\n");
+ buf.append(indent + "YMin : " + getYMin() + "\n");
+ buf.append(indent + "YMax : " + getYMax() + "\n");
+ buf.append(indent + "MacStyle : " + Integer.toBinaryString(getMacStyle()) + "\n");
+ buf.append(indent + "LowestPPem : " + getLowestRecPPem() + "\n");
+ buf.append(indent + "FontDirectionHint: " + getFontDirectionHint() + "\n");
+ buf.append(indent + "IndexToLocFormat : " + getIndexToLocFormat() + "\n");
+ buf.append(indent + "GlyphDataFormat : " + getGlyphDataFormat() + "\n");
+
+ return buf.toString();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/HheaTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/HheaTable.java
new file mode 100644
index 0000000..9990004
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/HheaTable.java
@@ -0,0 +1,380 @@
+/*
+ * $Id: HheaTable.java,v 1.4 2009/01/16 01:47:59 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ *
+ * @author jkaplan
+ */
+public class HheaTable extends TrueTypeTable {
+
+ /** Holds value of property version. */
+ private int version;
+
+ /** Holds value of property ascent. */
+ private short ascent;
+
+ /** Holds value of property descent. */
+ private short descent;
+
+ /** Holds value of property lineGap. */
+ private short lineGap;
+
+ /** Holds value of property advanceWidthMax. */
+ private short advanceWidthMax;
+
+ /** Holds value of property minLeftSideBearing. */
+ private short minLeftSideBearing;
+
+ /** Holds value of property minRightSideBearing. */
+ private short minRightSideBearing;
+
+ /** Holds value of property xMaxExtent. */
+ private short xMaxExtent;
+
+ /** Holds value of property caretSlopeRise. */
+ private short caretSlopeRise;
+
+ /** Holds value of property caretSlopeRun. */
+ private short caretSlopeRun;
+
+ /** Holds value of property caretOffset. */
+ private short caretOffset;
+
+ /** Holds value of property metricDataFormat. */
+ private short metricDataFormat;
+
+ /** Holds value of property numOfLongHorMetrics. */
+ private short numOfLongHorMetrics;
+
+ /** Creates a new instance of HeadTable
+ * Makes up reasonable(?) defaults for all values
+ */
+ protected HheaTable() {
+ super(TrueTypeTable.HEAD_TABLE);
+
+ setVersion(0x10000);
+ }
+
+ /**
+ * Parse the data before it is set
+ */
+ public void setData(ByteBuffer data) {
+ if (data.remaining() != 36) {
+ throw new IllegalArgumentException("Bad Head table size");
+ }
+ setVersion(data.getInt());
+ setAscent(data.getShort());
+ setDescent(data.getShort());
+ setLineGap(data.getShort());
+ setAdvanceWidthMax(data.getShort());
+ setMinLeftSideBearing(data.getShort());
+ setMinRightSideBearing(data.getShort());
+ setXMaxExtent(data.getShort());
+ setCaretSlopeRise(data.getShort());
+ setCaretSlopeRun(data.getShort());
+ setCaretOffset(data.getShort());
+
+ // padding
+ data.getShort();
+ data.getShort();
+ data.getShort();
+ data.getShort();
+
+ setMetricDataFormat(data.getShort());
+ setNumOfLongHorMetrics(data.getShort());
+ }
+
+ /**
+ * Get the data we have stored
+ */
+ public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ buf.putInt(getVersion());
+ buf.putShort(getAscent());
+ buf.putShort(getDescent());
+ buf.putShort(getLineGap());
+ buf.putShort(getAdvanceWidthMax());
+ buf.putShort(getMinLeftSideBearing());
+ buf.putShort(getMinRightSideBearing());
+ buf.putShort(getXMaxExtent());
+ buf.putShort(getCaretSlopeRise());
+ buf.putShort(getCaretSlopeRun());
+ buf.putShort(getCaretOffset());
+
+ // padding
+ buf.putShort((short) 0);
+ buf.putShort((short) 0);
+ buf.putShort((short) 0);
+ buf.putShort((short) 0);
+
+ buf.putShort(getMetricDataFormat());
+ buf.putShort((short) getNumOfLongHorMetrics());
+
+ // reset the position to the start of the buffer
+ buf.flip();
+
+ return buf;
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ return 36;
+ }
+
+ /** Getter for property version.
+ * @return Value of property version.
+ *
+ */
+ public int getVersion() {
+ return version;
+ }
+
+ /** Setter for property version.
+ * @param version New value of property version.
+ *
+ */
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ /**
+ * Create a pretty string
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ String indent = " ";
+
+ buf.append(indent + "Version : " + Integer.toHexString(getVersion()) + "\n");
+ buf.append(indent + "Ascent : " + getAscent() + "\n");
+ buf.append(indent + "Descent : " + getDescent() + "\n");
+ buf.append(indent + "LineGap : " + getLineGap() + "\n");
+ buf.append(indent + "AdvanceWidthMax : " + getAdvanceWidthMax() + "\n");
+ buf.append(indent + "MinLSB : " + getMinLeftSideBearing() + "\n");
+ buf.append(indent + "MinRSB : " + getMinRightSideBearing() + "\n");
+ buf.append(indent + "MaxExtent : " + getXMaxExtent() + "\n");
+ buf.append(indent + "CaretSlopeRise : " + getCaretSlopeRise() + "\n");
+ buf.append(indent + "CaretSlopeRun : " + getCaretSlopeRun() + "\n");
+ buf.append(indent + "CaretOffset : " + getCaretOffset() + "\n");
+ buf.append(indent + "MetricDataFormat : " + getMetricDataFormat() + "\n");
+ buf.append(indent + "NumOfLongHorMetrics : " + getNumOfLongHorMetrics() + "\n");
+ return buf.toString();
+ }
+
+ /** Getter for property ascent.
+ * @return Value of property ascent.
+ *
+ */
+ public short getAscent() {
+ return this.ascent;
+ }
+
+ /** Setter for property ascent.
+ * @param ascent New value of property ascent.
+ *
+ */
+ public void setAscent(short ascent) {
+ this.ascent = ascent;
+ }
+
+ /** Getter for property descent.
+ * @return Value of property descent.
+ *
+ */
+ public short getDescent() {
+ return this.descent;
+ }
+
+ /** Setter for property descent.
+ * @param descent New value of property descent.
+ *
+ */
+ public void setDescent(short descent) {
+ this.descent = descent;
+ }
+
+ /** Getter for property lineGap.
+ * @return Value of property lineGap.
+ *
+ */
+ public short getLineGap() {
+ return this.lineGap;
+ }
+
+ /** Setter for property lineGap.
+ * @param lineGap New value of property lineGap.
+ *
+ */
+ public void setLineGap(short lineGap) {
+ this.lineGap = lineGap;
+ }
+
+ /** Getter for property advanceWidthMax.
+ * @return Value of property advanceWidthMax.
+ *
+ */
+ public short getAdvanceWidthMax() {
+ return this.advanceWidthMax;
+ }
+
+ /** Setter for property advanceWidthMax.
+ * @param advanceWidthMax New value of property advanceWidthMax.
+ *
+ */
+ public void setAdvanceWidthMax(short advanceWidthMax) {
+ this.advanceWidthMax = advanceWidthMax;
+ }
+
+ /** Getter for property minLeftSideBearing.
+ * @return Value of property minLeftSideBearing.
+ *
+ */
+ public short getMinLeftSideBearing() {
+ return this.minLeftSideBearing;
+ }
+
+ /** Setter for property minLeftSideBearing.
+ * @param minLeftSideBearing New value of property minLeftSideBearing.
+ *
+ */
+ public void setMinLeftSideBearing(short minLeftSideBearing) {
+ this.minLeftSideBearing = minLeftSideBearing;
+ }
+
+ /** Getter for property minRIghtSideBearing.
+ * @return Value of property minRIghtSideBearing.
+ *
+ */
+ public short getMinRightSideBearing() {
+ return this.minRightSideBearing;
+ }
+
+ /** Setter for property minRIghtSideBearing.
+ * @param minRightSideBearing New value of property minRIghtSideBearing.
+ *
+ */
+ public void setMinRightSideBearing(short minRightSideBearing) {
+ this.minRightSideBearing = minRightSideBearing;
+ }
+
+ /** Getter for property xMaxExtent.
+ * @return Value of property xMaxExtent.
+ *
+ */
+ public short getXMaxExtent() {
+ return this.xMaxExtent;
+ }
+
+ /** Setter for property xMaxExtent.
+ * @param xMaxExtent New value of property xMaxExtent.
+ *
+ */
+ public void setXMaxExtent(short xMaxExtent) {
+ this.xMaxExtent = xMaxExtent;
+ }
+
+ /** Getter for property caretSlopeRise.
+ * @return Value of property caretSlopeRise.
+ *
+ */
+ public short getCaretSlopeRise() {
+ return this.caretSlopeRise;
+ }
+
+ /** Setter for property caretSlopeRise.
+ * @param caretSlopeRise New value of property caretSlopeRise.
+ *
+ */
+ public void setCaretSlopeRise(short caretSlopeRise) {
+ this.caretSlopeRise = caretSlopeRise;
+ }
+
+ /** Getter for property caretSlopeRun.
+ * @return Value of property caretSlopeRun.
+ *
+ */
+ public short getCaretSlopeRun() {
+ return this.caretSlopeRun;
+ }
+
+ /** Setter for property caretSlopeRun.
+ * @param caretSlopeRun New value of property caretSlopeRun.
+ *
+ */
+ public void setCaretSlopeRun(short caretSlopeRun) {
+ this.caretSlopeRun = caretSlopeRun;
+ }
+
+ /** Getter for property caretOffset.
+ * @return Value of property caretOffset.
+ *
+ */
+ public short getCaretOffset() {
+ return this.caretOffset;
+ }
+
+ /** Setter for property caretOffset.
+ * @param caretOffset New value of property caretOffset.
+ *
+ */
+ public void setCaretOffset(short caretOffset) {
+ this.caretOffset = caretOffset;
+ }
+
+ /** Getter for property metricDataFormat.
+ * @return Value of property metricDataFormat.
+ *
+ */
+ public short getMetricDataFormat() {
+ return this.metricDataFormat;
+ }
+
+ /** Setter for property metricDataFormat.
+ * @param metricDataFormat New value of property metricDataFormat.
+ *
+ */
+ public void setMetricDataFormat(short metricDataFormat) {
+ this.metricDataFormat = metricDataFormat;
+ }
+
+ /** Getter for property numOfLongHorMetrics.
+ * @return Value of property numOfLongHorMetrics.
+ *
+ */
+ public int getNumOfLongHorMetrics() {
+ return this.numOfLongHorMetrics & 0xFFFF;
+ }
+
+ /** Setter for property numOfLongHorMetrics.
+ * @param numOfLongHorMetrics New value of property numOfLongHorMetrics.
+ *
+ */
+ public void setNumOfLongHorMetrics(short numOfLongHorMetrics) {
+ this.numOfLongHorMetrics = numOfLongHorMetrics;
+ }
+
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/HmtxTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/HmtxTable.java
new file mode 100644
index 0000000..4c0dd3e
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/HmtxTable.java
@@ -0,0 +1,123 @@
+/*
+ * $Id: HmtxTable.java,v 1.3 2009/01/07 14:24:13 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import java.util.Arrays;
+
+/**
+ * Model the TrueType Post table
+ *
+ * @author jkaplan
+ */
+public class HmtxTable extends TrueTypeTable {
+ /** advance widths for any glyphs that have one */
+ short advanceWidths[];
+
+ /** left side bearings for each glyph */
+ short leftSideBearings[];
+
+ /** Creates a new instance of HmtxTable */
+ protected HmtxTable(TrueTypeFont ttf) {
+ super (TrueTypeTable.HMTX_TABLE);
+
+ // the number of glyphs stored in the maxp table may be incorrect
+ // in the case of subsetted fonts produced by some pdf generators
+ MaxpTable maxp = (MaxpTable) ttf.getTable("maxp");
+ int numGlyphs = maxp.getNumGlyphs();
+
+ HheaTable hhea = (HheaTable) ttf.getTable("hhea");
+ int numOfLongHorMetrics = hhea.getNumOfLongHorMetrics();
+
+ advanceWidths = new short[numOfLongHorMetrics];
+ leftSideBearings = new short[numGlyphs];
+ }
+
+ /** get the advance of a given glyph */
+ public short getAdvance(int glyphID) {
+ if (glyphID < advanceWidths.length) {
+ return advanceWidths[glyphID];
+ } else {
+ return advanceWidths[advanceWidths.length - 1];
+ }
+ }
+
+ /** get the left side bearing of a given glyph */
+ public short getLeftSideBearing(int glyphID) {
+ return leftSideBearings[glyphID];
+ }
+
+ /** get the data in this map as a ByteBuffer */
+ public ByteBuffer getData() {
+ int size = getLength();
+
+ ByteBuffer buf = ByteBuffer.allocate(size);
+
+ // write the metrics
+ for (int i = 0; i < leftSideBearings.length; i++) {
+ if (i < advanceWidths.length) {
+ buf.putShort(advanceWidths[i]);
+ }
+
+ buf.putShort(leftSideBearings[i]);
+ }
+
+ // reset the start pointer
+ buf.flip();
+
+ return buf;
+ }
+
+ /** Initialize this structure from a ByteBuffer */
+ public void setData(ByteBuffer data) {
+ // some PDF writers subset the font but don't update the number of glyphs in the maxp table,
+ // this would appear to break the TTF spec.
+ // A better solution might be to try and override the numGlyphs in the maxp table based
+ // on the number of entries in the cmap table or by parsing the glyf table, but this
+ // appears to be the only place that gets affected by the discrepancy... so far!...
+ // so updating this allows it to work.
+ int i;
+ // only read as much data as is available
+ for (i = 0; i < leftSideBearings.length && data.hasRemaining(); i++) {
+ if (i < advanceWidths.length) {
+ advanceWidths[i] = data.getShort();
+ }
+
+ leftSideBearings[i] = data.getShort();
+ }
+ // initialise the remaining advanceWidths and leftSideBearings to 0
+ if (i < advanceWidths.length) {
+ Arrays.fill(advanceWidths, i, advanceWidths.length-1, (short) 0);
+ }
+ if (i < leftSideBearings.length) {
+ Arrays.fill(leftSideBearings, i, leftSideBearings.length-1, (short) 0);
+ }
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ return (advanceWidths.length * 2) + (leftSideBearings.length * 2);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/LocaTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/LocaTable.java
new file mode 100644
index 0000000..7a0a7a7
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/LocaTable.java
@@ -0,0 +1,116 @@
+/*
+ * $Id: LocaTable.java,v 1.3 2009/03/15 12:33:17 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * Model the TrueType Loca table
+ */
+public class LocaTable extends TrueTypeTable {
+ /** if true, the table stores glyphs in long format */
+ private boolean isLong;
+
+ /** the offsets themselves */
+ private int offsets[];
+
+ /** Creates a new instance of HmtxTable */
+ protected LocaTable(TrueTypeFont ttf) {
+ super (TrueTypeTable.LOCA_TABLE);
+
+ MaxpTable maxp = (MaxpTable) ttf.getTable("maxp");
+ int numGlyphs = maxp.getNumGlyphs();
+
+ HeadTable head = (HeadTable) ttf.getTable("head");
+ short format = head.getIndexToLocFormat();
+ isLong = (format == 1);
+
+ offsets = new int[numGlyphs + 1];
+ }
+
+ /**
+ * get the offset, in bytes, of a given glyph from the start of
+ * the glyph table
+ */
+ public int getOffset(int glyphID) {
+ return offsets[glyphID];
+ }
+
+ /**
+ * get the size, in bytes, of the given glyph
+ */
+ public int getSize(int glyphID) {
+ return offsets[glyphID + 1] - offsets[glyphID];
+ }
+
+ /**
+ * Return true if the glyphs arte in long (int) format, or
+ * false if they are in short (short) format
+ */
+ public boolean isLongFormat() {
+ return isLong;
+ }
+
+
+ /** get the data in this map as a ByteBuffer */
+ public ByteBuffer getData() {
+ int size = getLength();
+
+ ByteBuffer buf = ByteBuffer.allocate(size);
+
+ // write the offsets
+ for (int i = 0; i < offsets.length; i++) {
+ if (isLongFormat()) {
+ buf.putInt(offsets[i]);
+ } else {
+ buf.putShort((short) (offsets[i] / 2));
+ }
+ }
+
+ // reset the start pointer
+ buf.flip();
+
+ return buf;
+ }
+
+ /** Initialize this structure from a ByteBuffer */
+ public void setData(ByteBuffer data) {
+ for (int i = 0; i < offsets.length; i++) {
+ if (isLongFormat()) {
+ offsets[i] = data.getInt();
+ } else {
+ offsets[i] = 2 * ( 0xFFFF & (int) data.getShort());
+ }
+ }
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ if (isLongFormat()) {
+ return offsets.length * 4;
+ } else {
+ return offsets.length * 2;
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/MaxpTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/MaxpTable.java
new file mode 100644
index 0000000..85e1c99
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/MaxpTable.java
@@ -0,0 +1,425 @@
+/*
+ * $Id: MaxpTable.java,v 1.3 2009/01/16 01:47:59 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ *
+ * @author jkaplan
+ */
+public class MaxpTable extends TrueTypeTable {
+
+ /** Holds value of property version. */
+ private int version;
+
+ // the following are supposed to be USHORT, but will be Int to enclose the sign
+ // (http://www.microsoft.com/typography/OTSpec/maxp.htm)
+ /** Holds value of property numGlyphs. */
+ private int numGlyphs;
+
+ /** Holds value of property maxPoints. */
+ private int maxPoints;
+
+ /** Holds value of property maxContours. */
+ private int maxContours;
+
+ /** Holds value of property maxComponentPoints. */
+ private int maxComponentPoints;
+
+ /** Holds value of property maxComponentContours. */
+ private int maxComponentContours;
+
+ /** Holds value of property maxZones. */
+ private int maxZones;
+
+ /** Holds value of property maxTwilightPoints. */
+ private int maxTwilightPoints;
+
+ /** Holds value of property maxStorage. */
+ private int maxStorage;
+
+ /** Holds value of property maxFunctionDefs. */
+ private int maxFunctionDefs;
+
+ /** Holds value of property maxInstructionDefs. */
+ private int maxInstructionDefs;
+
+ /** Holds value of property maxStackElements. */
+ private int maxStackElements;
+
+ /** Holds value of property maxSizeOfInstructions. */
+ private int maxSizeOfInstructions;
+
+ /** Holds value of property maxComponentElements. */
+ private int maxComponentElements;
+
+ /** Holds value of property maxComponentDepth. */
+ private int maxComponentDepth;
+
+ /** Creates a new instance of MaxpTable */
+ protected MaxpTable() {
+ super (TrueTypeTable.MAXP_TABLE);
+
+ setVersion(0x10000);
+ setNumGlyphs(0);
+ setMaxPoints(0);
+ setMaxContours(0);
+ setMaxComponentPoints(0);
+ setMaxComponentContours(0);
+ setMaxZones(2);
+ setMaxTwilightPoints(0);
+ setMaxStorage(0);
+ setMaxFunctionDefs(0);
+ setMaxInstructionDefs(0);
+ setMaxStackElements(0);
+ setMaxSizeOfInstructions(0);
+ setMaxComponentElements(0);
+ setMaxComponentDepth(0);
+ }
+
+ /**
+ * Set the values from data
+ */
+ public void setData(ByteBuffer data) {
+ if (data.remaining() != 32) {
+ throw new IllegalArgumentException("Bad size for Maxp table");
+ }
+
+ setVersion(data.getInt());
+ setNumGlyphs(data.getShort());
+ setMaxPoints(data.getShort());
+ setMaxContours(data.getShort());
+ setMaxComponentPoints(data.getShort());
+ setMaxComponentContours(data.getShort());
+ setMaxZones(data.getShort());
+ setMaxTwilightPoints(data.getShort());
+ setMaxStorage(data.getShort());
+ setMaxFunctionDefs(data.getShort());
+ setMaxInstructionDefs(data.getShort());
+ setMaxStackElements(data.getShort());
+ setMaxSizeOfInstructions(data.getShort());
+ setMaxComponentElements(data.getShort());
+ setMaxComponentDepth(data.getShort());
+ }
+
+ /**
+ * Get a buffer from the data
+ */
+ public ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ buf.putInt(getVersion());
+ buf.putShort((short) getNumGlyphs());
+ buf.putShort((short) getMaxPoints());
+ buf.putShort((short) getMaxContours());
+ buf.putShort((short) getMaxComponentPoints());
+ buf.putShort((short) getMaxComponentContours());
+ buf.putShort((short) getMaxZones());
+ buf.putShort((short) getMaxTwilightPoints());
+ buf.putShort((short) getMaxStorage());
+ buf.putShort((short) getMaxFunctionDefs());
+ buf.putShort((short) getMaxInstructionDefs());
+ buf.putShort((short) getMaxStackElements());
+ buf.putShort((short) getMaxSizeOfInstructions());
+ buf.putShort((short) getMaxComponentElements());
+ buf.putShort((short) getMaxComponentDepth());
+
+ // reset the position to the beginning of the buffer
+ buf.flip();
+
+ return buf;
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ return 32;
+ }
+
+ /** Getter for property version.
+ * @return Value of property version.
+ *
+ */
+ public int getVersion() {
+ return this.version;
+ }
+
+ /** Setter for property version.
+ * @param version New value of property version.
+ *
+ */
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ /** Getter for property numGlyphs.
+ * @return Value of property numGlyphs.
+ *
+ */
+ public int getNumGlyphs() {
+ return this.numGlyphs & 0xFFFF;
+ }
+
+ /** Setter for property numGlyphs.
+ * @param numGlyphs New value of property numGlyphs.
+ *
+ */
+ public void setNumGlyphs(int numGlyphs) {
+ this.numGlyphs = numGlyphs;
+ }
+
+ /** Getter for property maxPoints.
+ * @return Value of property maxPoints.
+ *
+ */
+ public int getMaxPoints() {
+ return this.maxPoints & 0xFFFF;
+ }
+
+ /** Setter for property maxPoints.
+ * @param maxPoints New value of property maxPoints.
+ *
+ */
+ public void setMaxPoints(int maxPoints) {
+ this.maxPoints = maxPoints;
+ }
+
+ /** Getter for property maxContours.
+ * @return Value of property maxContours.
+ *
+ */
+ public int getMaxContours() {
+ return this.maxContours & 0xFFFF;
+ }
+
+ /** Setter for property maxContours.
+ * @param maxContours New value of property maxContours.
+ *
+ */
+ public void setMaxContours(int maxContours) {
+ this.maxContours = maxContours;
+ }
+
+ /** Getter for property maxComponentPoints.
+ * @return Value of property maxComponentPoints.
+ *
+ */
+ public int getMaxComponentPoints() {
+ return this.maxComponentPoints & 0xFFFF;
+ }
+
+ /** Setter for property maxComponentPoints.
+ * @param maxComponentPoints New value of property maxComponentPoints.
+ *
+ */
+ public void setMaxComponentPoints(int maxComponentPoints) {
+ this.maxComponentPoints = maxComponentPoints;
+ }
+
+ /** Getter for property maxComponentContours.
+ * @return Value of property maxComponentContours.
+ *
+ */
+ public int getMaxComponentContours() {
+ return this.maxComponentContours & 0xFFFF;
+ }
+
+ /** Setter for property maxComponentContours.
+ * @param maxComponentContours New value of property maxComponentContours.
+ *
+ */
+ public void setMaxComponentContours(int maxComponentContours) {
+ this.maxComponentContours = maxComponentContours;
+ }
+
+ /** Getter for property maxZones.
+ * @return Value of property maxZones.
+ *
+ */
+ public int getMaxZones() {
+ return this.maxZones & 0xFFFF;
+ }
+
+ /** Setter for property maxZones.
+ * @param maxZones New value of property maxZones.
+ *
+ */
+ public void setMaxZones(int maxZones) {
+ this.maxZones = maxZones;
+ }
+
+ /** Getter for property maxTwilightPoints.
+ * @return Value of property maxTwilightPoints.
+ *
+ */
+ public int getMaxTwilightPoints() {
+ return this.maxTwilightPoints & 0xFFFF;
+ }
+
+ /** Setter for property maxTwilightPoints.
+ * @param maxTwilightPoints New value of property maxTwilightPoints.
+ *
+ */
+ public void setMaxTwilightPoints(int maxTwilightPoints) {
+ this.maxTwilightPoints = maxTwilightPoints;
+ }
+
+ /** Getter for property maxStorage.
+ * @return Value of property maxStorage.
+ *
+ */
+ public int getMaxStorage() {
+ return this.maxStorage & 0xFFFF;
+ }
+
+ /** Setter for property maxStorage.
+ * @param maxStorage New value of property maxStorage.
+ *
+ */
+ public void setMaxStorage(int maxStorage) {
+ this.maxStorage = maxStorage;
+ }
+
+ /** Getter for property maxFunctionDefs.
+ * @return Value of property maxFunctionDefs.
+ *
+ */
+ public int getMaxFunctionDefs() {
+ return this.maxFunctionDefs & 0xFFFF;
+ }
+
+ /** Setter for property maxFunctionDefs.
+ * @param maxFunctionDefs New value of property maxFunctionDefs.
+ *
+ */
+ public void setMaxFunctionDefs(int maxFunctionDefs) {
+ this.maxFunctionDefs = maxFunctionDefs;
+ }
+
+ /** Getter for property maxInstructionDefs.
+ * @return Value of property maxInstructionDefs.
+ *
+ */
+ public int getMaxInstructionDefs() {
+ return this.maxInstructionDefs & 0xFFFF;
+ }
+
+ /** Setter for property maxInstructionDefs.
+ * @param maxInstructionDefs New value of property maxInstructionDefs.
+ *
+ */
+ public void setMaxInstructionDefs(int maxInstructionDefs) {
+ this.maxInstructionDefs = maxInstructionDefs;
+ }
+
+ /** Getter for property maxStackElements.
+ * @return Value of property maxStackElements.
+ *
+ */
+ public int getMaxStackElements() {
+ return this.maxStackElements & 0xFFFF;
+ }
+
+ /** Setter for property maxStackElements.
+ * @param maxStackElements New value of property maxStackElements.
+ *
+ */
+ public void setMaxStackElements(int maxStackElements) {
+ this.maxStackElements = maxStackElements;
+ }
+
+ /** Getter for property maxSizeOfInstructions.
+ * @return Value of property maxSizeOfInstructions.
+ *
+ */
+ public int getMaxSizeOfInstructions() {
+ return this.maxSizeOfInstructions & 0xFFFF;
+ }
+
+ /** Setter for property maxSizeOfInstructions.
+ * @param maxSizeOfInstructions New value of property maxSizeOfInstructions.
+ *
+ */
+ public void setMaxSizeOfInstructions(int maxSizeOfInstructions) {
+ this.maxSizeOfInstructions = maxSizeOfInstructions;
+ }
+
+ /** Getter for property maxComponentElements.
+ * @return Value of property maxComponentElements.
+ *
+ */
+ public int getMaxComponentElements() {
+ return this.maxComponentElements & 0xFFFF;
+ }
+
+ /** Setter for property maxComponentElements.
+ * @param maxComponentElements New value of property maxComponentElements.
+ *
+ */
+ public void setMaxComponentElements(int maxComponentElements) {
+ this.maxComponentElements = maxComponentElements;
+ }
+
+ /** Getter for property maxComponentDepth.
+ * @return Value of property maxComponentDepth.
+ *
+ */
+ public int getMaxComponentDepth() {
+ return this.maxComponentDepth & 0xFFFF;
+ }
+
+ /** Setter for property maxComponentDepth.
+ * @param maxComponentDepth New value of property maxComponentDepth.
+ *
+ */
+ public void setMaxComponentDepth(int maxComponentDepth) {
+ this.maxComponentDepth = maxComponentDepth;
+ }
+
+ /**
+ * Create a pretty String
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ String indent = " ";
+
+ buf.append(indent + "Version : " + Integer.toHexString(getVersion()) + "\n");
+ buf.append(indent + "NumGlyphs : " + getNumGlyphs() + "\n");
+ buf.append(indent + "MaxPoints : " + getMaxPoints() + "\n");
+ buf.append(indent + "MaxContours : " + getMaxContours() + "\n");
+ buf.append(indent + "MaxCompPoints : " + getMaxComponentPoints() + "\n");
+ buf.append(indent + "MaxCompContours : " + getMaxComponentContours() + "\n");
+ buf.append(indent + "MaxZones : " + getMaxZones() + "\n");
+ buf.append(indent + "MaxTwilightPoints: " + getMaxTwilightPoints() + "\n");
+ buf.append(indent + "MaxStorage : " + getMaxStorage() + "\n");
+ buf.append(indent + "MaxFuncDefs : " + getMaxFunctionDefs() + "\n");
+ buf.append(indent + "MaxInstDefs : " + getMaxInstructionDefs() + "\n");
+ buf.append(indent + "MaxStackElements : " + getMaxStackElements() + "\n");
+ buf.append(indent + "MaxSizeInst : " + getMaxSizeOfInstructions() + "\n");
+ buf.append(indent + "MaxCompElements : " + getMaxComponentElements() + "\n");
+ buf.append(indent + "MaxCompDepth : " + getMaxComponentDepth() + "\n");
+
+ return buf.toString();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/NameTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/NameTable.java
new file mode 100644
index 0000000..03fe902
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/NameTable.java
@@ -0,0 +1,423 @@
+/*
+ * $Id: NameTable.java,v 1.4 2009/02/12 13:53:57 tomoke 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.font.ttf;
+
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ *
+ * @author jon
+ */
+public class NameTable extends TrueTypeTable {
+ /**
+ * Values for platformID
+ */
+ public static final short PLATFORMID_UNICODE = 0;
+ public static final short PLATFORMID_MACINTOSH = 1;
+ public static final short PLATFORMID_MICROSOFT = 3;
+
+ /**
+ * Values for platformSpecificID if platform is Mac
+ */
+ public static final short ENCODINGID_MAC_ROMAN = 0;
+
+ /**
+ * Values for platformSpecificID if platform is Unicode
+ */
+ public static final short ENCODINGID_UNICODE_DEFAULT = 0;
+ public static final short ENCODINGID_UNICODE_V11 = 1;
+ public static final short ENCODINGID_UNICODE_V2 = 3;
+
+ /**
+ * Values for language ID if platform is Mac
+ */
+ public static final short LANGUAGEID_MAC_ENGLISH = 0;
+
+ /**
+ * Values for nameID
+ */
+ public static final short NAMEID_COPYRIGHT = 0;
+ public static final short NAMEID_FAMILY = 1;
+ public static final short NAMEID_SUBFAMILY = 2;
+ public static final short NAMEID_SUBFAMILY_UNIQUE = 3;
+ public static final short NAMEID_FULL_NAME = 4;
+ public static final short NAMEID_VERSION = 5;
+ public static final short NAMEID_POSTSCRIPT_NAME = 6;
+ public static final short NAMEID_TRADEMARK = 7;
+ /**
+ * The format of this table
+ */
+ private short format;
+
+ /**
+ * The actual name records
+ */
+ private SortedMap records;
+
+
+ /** Creates a new instance of NameTable */
+ protected NameTable() {
+ super (TrueTypeTable.NAME_TABLE);
+
+ records = Collections.synchronizedSortedMap(new TreeMap());
+ }
+
+ /**
+ * Add a record to the table
+ */
+ public void addRecord(short platformID, short platformSpecificID,
+ short languageID, short nameID,
+ String value) {
+ NameRecord rec = new NameRecord(platformID, platformSpecificID,
+ languageID, nameID);
+ records.put(rec, value);
+ }
+
+ /**
+ * Get a record from the table
+ */
+ public String getRecord(short platformID, short platformSpecificID,
+ short languageID, short nameID) {
+
+ NameRecord rec = new NameRecord(platformID, platformSpecificID,
+ languageID, nameID);
+ return (String) records.get(rec);
+ }
+
+ /**
+ * Remove a record from the table
+ */
+ public void removeRecord(short platformID, short platformSpecificID,
+ short languageID, short nameID) {
+ NameRecord rec = new NameRecord(platformID, platformSpecificID,
+ languageID, nameID);
+ records.remove(rec);
+ }
+
+ /**
+ * Determine if we have any records with a given platform ID
+ */
+ public boolean hasRecords(short platformID) {
+ for (Iterator i = records.keySet().iterator(); i.hasNext(); ) {
+ NameRecord rec = (NameRecord) i.next();
+
+ if (rec.platformID == platformID) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if we have any records with a given platform ID and
+ * platform-specific ID
+ */
+ public boolean hasRecords(short platformID, short platformSpecificID) {
+ for (Iterator i = records.keySet().iterator(); i.hasNext(); ) {
+ NameRecord rec = (NameRecord) i.next();
+
+ if (rec.platformID == platformID &&
+ rec.platformSpecificID == platformSpecificID) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Read the table from data
+ */
+ public void setData(ByteBuffer data) {
+ //read table header
+ setFormat(data.getShort());
+ int count = data.getShort();
+ int stringOffset = data.getShort();
+
+ // read the records
+ for (int i = 0; i < count; i++) {
+ short platformID = data.getShort();
+ short platformSpecificID = data.getShort();
+ short languageID = data.getShort();
+ short nameID = data.getShort();
+
+ int length = data.getShort() & 0xFFFF;
+ int offset = data.getShort() & 0xFFFF;
+
+ // read the String data
+ data.mark();
+ data.position(stringOffset + offset);
+
+ ByteBuffer stringBuf = data.slice();
+ stringBuf.limit(length);
+
+ data.reset();
+
+ // choose the character set
+ String charsetName = getCharsetName(platformID, platformSpecificID);
+ Charset charset = Charset.forName(charsetName);
+
+ // parse the data as a string
+ String value = charset.decode(stringBuf.toNIO()).toString();
+
+ // add to the mix
+ addRecord(platformID, platformSpecificID, languageID, nameID, value);
+ }
+ }
+
+ /**
+ * Get the data in this table as a buffer
+ */
+ public ByteBuffer getData() {
+ // alocate the output buffer
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ // the start of string data
+ short headerLength = (short) (6 + (12 * getCount()));
+
+ // write the header
+ buf.putShort(getFormat());
+ buf.putShort(getCount());
+ buf.putShort(headerLength);
+
+ // the offset from the start of the strings table
+ short curOffset = 0;
+
+ // add the size of each record
+ for (Iterator i = records.keySet().iterator(); i.hasNext();) {
+ NameRecord rec = (NameRecord) i.next();
+ String value = (String) records.get(rec);
+
+ // choose the charset
+ String charsetName = getCharsetName(rec.platformID,
+ rec.platformSpecificID);
+ Charset charset = Charset.forName(charsetName);
+
+ // encode
+ java.nio.ByteBuffer strNioBuf = charset.encode(value);
+ ByteBuffer strBuf = ByteBuffer.fromNIO(strNioBuf);
+ short strLen = (short) (strBuf.remaining() & 0xFFFF);
+
+ // write the IDs
+ buf.putShort(rec.platformID);
+ buf.putShort(rec.platformSpecificID);
+ buf.putShort(rec.languageID);
+ buf.putShort(rec.nameID);
+
+ // write the size and offset
+ buf.putShort(strLen);
+ buf.putShort(curOffset);
+
+ // remember or current position
+ buf.mark();
+
+ // move to the current offset and write the data
+ buf.position(headerLength + curOffset);
+ buf.put(strBuf);
+
+ // reset stuff
+ buf.reset();
+
+ // increment offset
+ curOffset += strLen;
+ }
+
+ // reset the pointer on the buffer
+ buf.position(headerLength + curOffset);
+ buf.flip();
+
+ return buf;
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ // start with the size of the fixed header plus the size of the
+ // records
+ int length = 6 + (12 * getCount());
+
+ // add the size of each record
+ for (Iterator i = records.keySet().iterator(); i.hasNext();) {
+ NameRecord rec = (NameRecord) i.next();
+ String value = (String) records.get(rec);
+
+ // choose the charset
+ String charsetName = getCharsetName(rec.platformID,
+ rec.platformSpecificID);
+ Charset charset = Charset.forName(charsetName);
+
+ // encode
+ java.nio.ByteBuffer bufNio = charset.encode(value);
+ ByteBuffer buf = ByteBuffer.fromNIO(bufNio);
+
+ // add the size of the coded buffer
+ length += buf.remaining();
+ }
+
+ return length;
+ }
+
+ /**
+ * Get the format of this table
+ */
+ public short getFormat() {
+ return format;
+ }
+
+ /**
+ * Set the format of this table
+ */
+ public void setFormat(short format) {
+ this.format = format;
+ }
+
+ /**
+ * Get the number of records in the table
+ */
+ public short getCount() {
+ return (short) records.size();
+ }
+
+ /**
+ * Get the charset name for a given platform, encoding and language
+ */
+ public static String getCharsetName(int platformID, int encodingID) {
+ String charset = "US-ASCII";
+
+ switch (platformID) {
+ case PLATFORMID_UNICODE:
+ charset = "UTF-16";
+ break;
+ case PLATFORMID_MICROSOFT:
+ charset = "UTF-16";
+ break;
+ }
+
+ return charset;
+ }
+
+ /** Get a pretty string */
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ String indent = " ";
+
+ buf.append(indent + "Format: " + getFormat() + "\n");
+ buf.append(indent + "Count : " + getCount() + "\n");
+
+ for (Iterator i = records.keySet().iterator(); i.hasNext();) {
+ NameRecord rec = (NameRecord) i.next();
+
+ buf.append(indent + " platformID: " + rec.platformID);
+ buf.append(" platformSpecificID: " + rec.platformSpecificID);
+ buf.append(" languageID: " + rec.languageID);
+ buf.append(" nameID: " + rec.nameID + "\n");
+ buf.append(indent + " " + records.get(rec) + "\n");
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * A class to hold the data associated with each record
+ */
+ class NameRecord implements Comparable {
+ /**
+ * Platform ID
+ */
+ short platformID;
+
+ /**
+ * Platform Specific ID (Encoding)
+ */
+ short platformSpecificID;
+
+ /**
+ * Language ID
+ */
+ short languageID;
+
+ /**
+ * Name ID
+ */
+ short nameID;
+
+ /**
+ * Create a new record
+ */
+ NameRecord(short platformID, short platformSpecificID,
+ short languageID, short nameID) {
+ this.platformID = platformID;
+ this.platformSpecificID = platformSpecificID;
+ this.languageID = languageID;
+ this.nameID = nameID;
+ }
+
+
+ /**
+ * Compare two records
+ */
+ public boolean equals(Object o) {
+ return (compareTo(o) == 0);
+ }
+
+ /**
+ * Compare two records
+ */
+ public int compareTo(Object obj) {
+ if (!(obj instanceof NameRecord)) {
+ return -1;
+ }
+
+ NameRecord rec = (NameRecord) obj;
+
+ if (platformID > rec.platformID) {
+ return 1;
+ } else if (platformID < rec.platformID) {
+ return -1;
+ } else if (platformSpecificID > rec.platformSpecificID) {
+ return 1;
+ } else if (platformSpecificID < rec.platformSpecificID) {
+ return -1;
+ } else if (languageID > rec.languageID) {
+ return 1;
+ } else if (languageID < rec.languageID) {
+ return -1;
+ } else if (nameID > rec.nameID) {
+ return 1;
+ } else if (nameID < rec.nameID) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/PostTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/PostTable.java
new file mode 100644
index 0000000..5856319
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/PostTable.java
@@ -0,0 +1,526 @@
+/*
+ * $Id: PostTable.java,v 1.2 2007/12/20 18:33:31 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * Model the TrueType Post table
+ *
+ * @author jkaplan
+ */
+public class PostTable extends TrueTypeTable {
+
+ /** Holds value of property format. */
+ private int format;
+
+ /** Holds value of property italicAngle. */
+ private int italicAngle;
+
+ /** Holds value of property underlinePosition. */
+ private short underlinePosition;
+
+ /** Holds value of property underlineThickness. */
+ private short underlineThickness;
+
+ /** Holds value of property isFixedPitch. */
+ private short isFixedPitch;
+
+ /** Holds value of property minMemType42. */
+ private int minMemType42;
+
+ /** Holds value of property maxMemType42. */
+ private int maxMemType42;
+
+ /** Holds value of property minMemType1. */
+ private int minMemType1;
+
+ /** Holds value of property maxMemType1. */
+ private int maxMemType1;
+
+ /** A map which character values to names and vice versa */
+ private PostMap nameMap;
+
+ /** Creates a new instance of PostTable */
+ protected PostTable() {
+ super (TrueTypeTable.POST_TABLE);
+
+ nameMap = new PostMap();
+ }
+
+ /**
+ * Map a character name to a glyphNameIndex
+ */
+ public short getGlyphNameIndex(String name) {
+ return nameMap.getCharIndex(name);
+ }
+
+ /**
+ * Map a character code to a glyphIndex name
+ */
+ public String getGlyphName(char c) {
+ return nameMap.getCharName(c);
+ }
+
+ /** get the data in this map as a ByteBuffer */
+ public ByteBuffer getData() {
+ int size = getLength();
+
+ ByteBuffer buf = ByteBuffer.allocate(size);
+
+ // write the header
+ buf.putInt(getFormat());
+ buf.putInt(getItalicAngle());
+ buf.putShort(getUnderlinePosition());
+ buf.putShort(getUnderlineThickness());
+ buf.putShort(getIsFixedPitch());
+ buf.putShort((short) 0);
+ buf.putInt(getMinMemType42());
+ buf.putInt(getMaxMemType42());
+ buf.putInt(getMinMemType1());
+ buf.putInt(getMaxMemType1());
+
+ // now write the table
+ buf.put(nameMap.getData());
+
+ // reset the start pointer
+ buf.flip();
+
+ return buf;
+ }
+
+ /** Initialize this structure from a ByteBuffer */
+ public void setData(ByteBuffer data) {
+ setFormat(data.getInt());
+ setItalicAngle(data.getInt());
+ setUnderlinePosition(data.getShort());
+ setUnderlineThickness(data.getShort());
+ setIsFixedPitch(data.getShort());
+ data.getShort();
+ setMinMemType42(data.getInt());
+ setMaxMemType42(data.getInt());
+ setMinMemType1(data.getInt());
+ setMaxMemType1(data.getInt());
+
+ // create the map, based on the type
+ switch (format) {
+ case 0x10000:
+ nameMap = new PostMapFormat0();
+ break;
+ case 0x20000:
+ nameMap = new PostMapFormat2();
+ break;
+ case 0x30000:
+ // empty post map.
+ nameMap = new PostMap();
+ break;
+ default:
+ nameMap = new PostMap();
+ System.out.println("Unknown post map type: " +
+ Integer.toHexString(format));
+ break;
+ }
+
+ // fill in the data in the map
+ nameMap.setData(data);
+ }
+
+ /**
+ * Get the length of this table
+ */
+ public int getLength() {
+ int size = 32;
+ if (nameMap != null) {
+ size += nameMap.getLength();
+ }
+
+ return size;
+ }
+
+ /** Getter for property format.
+ * @return Value of property format.
+ *
+ */
+ public int getFormat() {
+ return this.format;
+ }
+
+ /** Setter for property format.
+ * @param format New value of property format.
+ *
+ */
+ public void setFormat(int format) {
+ this.format = format;
+ }
+
+ /** Getter for property italicAngle.
+ * @return Value of property italicAngle.
+ *
+ */
+ public int getItalicAngle() {
+ return this.italicAngle;
+ }
+
+ /** Setter for property italicAngle.
+ * @param italicAngle New value of property italicAngle.
+ *
+ */
+ public void setItalicAngle(int italicAngle) {
+ this.italicAngle = italicAngle;
+ }
+
+ /** Getter for property underlinePosition.
+ * @return Value of property underlinePosition.
+ *
+ */
+ public short getUnderlinePosition() {
+ return this.underlinePosition;
+ }
+
+ /** Setter for property underlinePosition.
+ * @param underlinePosition New value of property underlinePosition.
+ *
+ */
+ public void setUnderlinePosition(short underlinePosition) {
+ this.underlinePosition = underlinePosition;
+ }
+
+ /** Getter for property underlineThickness.
+ * @return Value of property underlineThickness.
+ *
+ */
+ public short getUnderlineThickness() {
+ return this.underlineThickness;
+ }
+
+ /** Setter for property underlineThickness.
+ * @param underlineThickness New value of property underlineThickness.
+ *
+ */
+ public void setUnderlineThickness(short underlineThickness) {
+ this.underlineThickness = underlineThickness;
+ }
+
+ /** Getter for property isFixedPitch.
+ * @return Value of property isFixedPitch.
+ *
+ */
+ public short getIsFixedPitch() {
+ return this.isFixedPitch;
+ }
+
+ /** Setter for property isFixedPitch.
+ * @param isFixedPitch New value of property isFixedPitch.
+ *
+ */
+ public void setIsFixedPitch(short isFixedPitch) {
+ this.isFixedPitch = isFixedPitch;
+ }
+
+ /** Getter for property minMemType42.
+ * @return Value of property minMemType42.
+ *
+ */
+ public int getMinMemType42() {
+ return this.minMemType42;
+ }
+
+ /** Setter for property minMemType42.
+ * @param minMemType42 New value of property minMemType42.
+ *
+ */
+ public void setMinMemType42(int minMemType42) {
+ this.minMemType42 = minMemType42;
+ }
+
+ /** Getter for property maxMemType42.
+ * @return Value of property maxMemType42.
+ *
+ */
+ public int getMaxMemType42() {
+ return this.maxMemType42;
+ }
+
+ /** Setter for property maxMemType42.
+ * @param maxMemType42 New value of property maxMemType42.
+ *
+ */
+ public void setMaxMemType42(int maxMemType42) {
+ this.maxMemType42 = maxMemType42;
+ }
+
+ /** Getter for property minMemType1.
+ * @return Value of property minMemType1.
+ *
+ */
+ public int getMinMemType1() {
+ return this.minMemType1;
+ }
+
+ /** Setter for property minMemType1.
+ * @param minMemType1 New value of property minMemType1.
+ *
+ */
+ public void setMinMemType1(int minMemType1) {
+ this.minMemType1 = minMemType1;
+ }
+
+ /** Getter for property maxMemType1.
+ * @return Value of property maxMemType1.
+ *
+ */
+ public int getMaxMemType1() {
+ return this.maxMemType1;
+ }
+
+ /** Setter for property maxMemType1.
+ * @param maxMemType1 New value of property maxMemType1.
+ *
+ */
+ public void setMaxMemType1(int maxMemType1) {
+ this.maxMemType1 = maxMemType1;
+ }
+
+ /** An empty post map */
+ class PostMap {
+ /** map a name to a character index */
+ short getCharIndex(String charName) {
+ return (short) 0;
+ }
+
+ /** name a character index to a name */
+ String getCharName(char charIndex) {
+ return null;
+ }
+
+ /** get the length of the data in this map */
+ int getLength() {
+ return 0;
+ }
+
+ /** get the data in this map as a ByteBuffer */
+ ByteBuffer getData() {
+ return ByteBuffer.allocate(0);
+ }
+
+ /** set the data in this map from a ByteBuffer */
+ void setData(ByteBuffer data) {
+ // do nothing
+ return;
+ }
+ }
+
+ /** A Format 0 post map */
+ class PostMapFormat0 extends PostMap {
+ /** the glyph names in standard Macintosh ordering */
+ protected final String stdNames[] = {
+/* 0 */ ".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar",
+/* 8 */ "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma",
+/* 16 */ "hyphen", "period", "slash", "zero", "one", "two", "three", "four",
+/* 24 */ "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less",
+/* 32 */ "equal", "greater", "question", "at", "A", "B", "C", "D",
+/* 40 */ "E", "F", "G", "H", "I", "J", "K", "L",
+/* 48 */ "M", "N", "O", "P", "Q", "R", "S", "T",
+/* 56 */ "U", "V", "W", "X", "Y", "Z", "bracketleft", "ackslash",
+/* 64 */ "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d",
+/* 72 */ "e", "f", "g", "h", "i", "j", "k", "l",
+/* 80 */ "m", "n", "o", "p", "q", "r", "s", "t",
+/* 88 */ "u", "v", "w", "x", "y", "z", "braceleft", "bar",
+/* 96 */ "braceright", "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis",
+/* 104 */ "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla",
+/* 112 */ "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis",
+/* 120 */ "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave",
+/* 128 */ "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet",
+/* 136 */ "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal",
+/* 144 */ "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu",
+/* 152 */ "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega",
+/* 160 */ "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal",
+/* 168 */ "Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde",
+/* 176 */ "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright",
+/* 184 */ "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright",
+/* 192 */ "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex",
+/* 200 */ "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave",
+/* 208 */ "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
+/* 216 */ "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut",
+/* 224 */ "ogonek", "caron", "Lslash", "lslash", "Scaron", "scaron", "Zcaron", "zcaron",
+/* 232 */ "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn", "thorn", "minus",
+/* 240 */ "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf", "onequarter", "threequarters", "franc",
+/* 248 */ "Gbreve", "gbreve", "Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron",
+/* 256 */ "ccaron", "dcroat"
+ };
+
+ /** map a name to a character index */
+ short getCharIndex(String charName) {
+ for (int i = 0; i < stdNames.length; i++) {
+ if (charName.equals(stdNames[i])) {
+ return (short) i;
+ }
+ }
+
+ return (short) 0;
+ }
+
+ /** name a character index to a name */
+ String getCharName(char charIndex) {
+ return stdNames[charIndex];
+ }
+
+ /** get the length of the data in this map */
+ int getLength() {
+ return 0;
+ }
+
+ /** get the data in this map as a ByteBuffer */
+ ByteBuffer getData() {
+ return ByteBuffer.allocate(0);
+ }
+
+ /** set the data in this map from a ByteBuffer */
+ void setData(ByteBuffer data) {
+ // do nothing
+ return;
+ }
+ }
+
+ /** an extension to handle format 2 post maps */
+ class PostMapFormat2 extends PostMapFormat0 {
+ /** the glyph name index */
+ short[] glyphNameIndex;
+
+ /** the glyph names */
+ String[] glyphNames;
+
+ /** Map a character name to an index */
+ short getCharIndex(String charName) {
+ // find the index of this character name
+ short idx = -1;
+
+ // first try the local names map
+ for (int i = 0; i < glyphNames.length; i++) {
+ if (charName.equals(glyphNames[i])) {
+ // this is the value from the glyph name index
+ idx = (short) (stdNames.length + i);
+ break;
+ }
+ }
+
+ // if that doesn't work, try the standard names
+ if (idx == -1) {
+ idx = super.getCharIndex(charName);
+ }
+
+ // now get the entry in the index
+ for (int c = 0; c < glyphNameIndex.length; c++) {
+ if (glyphNameIndex[c] == idx) {
+ return (short) c;
+ }
+ }
+
+ // not found
+ return (short) 0;
+ }
+
+ /** Map an index to a character name */
+ String getCharName(char charIndex) {
+ if (charIndex >= stdNames.length) {
+ return glyphNames[charIndex - stdNames.length];
+ }
+
+ return super.getCharName(charIndex);
+ }
+
+ /** get the length of this class's data */
+ int getLength() {
+ // the size of the header plus the table of mappings
+ int size = 2 + (2 * glyphNameIndex.length);
+
+ // the size of each string -- note the extra byte for a pascal
+ // string
+ for (int i = 0; i < glyphNames.length; i++) {
+ size += glyphNames[i].length() + 1;
+ }
+
+ return size;
+ }
+
+ /** get the data in this map as a byte array */
+ ByteBuffer getData() {
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ // write the number of glyphs
+ buf.putShort((short) glyphNameIndex.length);
+
+ // write the name indices
+ for (int i = 0; i < glyphNameIndex.length; i++) {
+ buf.putShort(glyphNameIndex[i]);
+ }
+
+ // write the names as pascal strings
+ for (int i = 0; i < glyphNames.length; i++) {
+ buf.put((byte) glyphNames[i].length());
+ buf.put(glyphNames[i].getBytes());
+ }
+
+ // reset the start pointer
+ buf.flip();
+
+ return buf;
+ }
+
+ /** set the contents of this map from a ByteBuffer */
+ void setData(ByteBuffer data) {
+ short numGlyphs = data.getShort();
+ glyphNameIndex = new short[numGlyphs];
+
+ // the highest glyph index seen so far
+ int maxGlyph = 257;
+ for (int i = 0; i < numGlyphs; i++) {
+ glyphNameIndex[i] = data.getShort();
+
+ // see if this is the highest glyph
+ if (glyphNameIndex[i] > maxGlyph) {
+ maxGlyph = glyphNameIndex[i];
+ }
+ }
+
+ // subtract off the default glyphs
+ maxGlyph -= 257;
+
+ // read in any additional names
+ glyphNames = new String[maxGlyph];
+
+ // read each name from a pascal string
+ // the length is stored in the first byte, followed by
+ // the data
+ for (int i = 0; i < maxGlyph; i++) {
+ // size in the first byte
+ byte size = data.get();
+
+ // then the data
+ byte[] stringData = new byte[size];
+ data.get(stringData);
+
+ glyphNames[i] = new String(stringData);
+ }
+ }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/TrueTypeFont.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/TrueTypeFont.java
new file mode 100644
index 0000000..cb5c8a1
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/TrueTypeFont.java
@@ -0,0 +1,415 @@
+/*
+ * $Id: TrueTypeFont.java,v 1.6 2009/03/15 20:47:39 tomoke 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.font.ttf;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ *
+ * @author jkaplan
+ * @author Ferenc Hechler (ferenc@hechler.de)
+ * @author Joerg Jahnke (joergjahnke@users.sourceforge.net)
+ */
+public class TrueTypeFont {
+
+ private int type;
+ // could be a ByteBuffer or a TrueTypeTable
+ private SortedMap tables;
+
+ /** Creates a new instance of TrueTypeParser */
+ public TrueTypeFont(int type) {
+ this.type = type;
+
+ tables = Collections.synchronizedSortedMap(
+ new TreeMap());
+ }
+
+ /**
+ * Parses a TrueType font from a byte array
+ */
+ public static TrueTypeFont parseFont(byte[] orig) {
+ ByteBuffer inBuf = ByteBuffer.wrap(orig);
+ return parseFont(inBuf);
+ }
+
+ /**
+ * Parses a TrueType font from a byte buffer
+ */
+ public static TrueTypeFont parseFont(ByteBuffer inBuf) {
+ int type = inBuf.getInt();
+ short numTables = inBuf.getShort();
+ short searchRange = inBuf.getShort();
+ short entrySelector = inBuf.getShort();
+ short rangeShift = inBuf.getShort();
+
+ TrueTypeFont font = new TrueTypeFont(type);
+ parseDirectories(inBuf, numTables, font);
+
+ return font;
+ }
+
+ /**
+ * Get the type of this font
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Add a table to the font
+ *
+ * @param tagString the name of this table, as a 4 character string
+ * (i.e. cmap or head)
+ * @param data the data for this table, as a byte buffer
+ */
+ public void addTable(String tagString, ByteBuffer data) {
+ tables.put(tagString, data);
+ }
+
+ /**
+ * Add a table to the font
+ *
+ * @param tagString the name of this table, as a 4 character string
+ * (i.e. cmap or head)
+ * @param table the table
+ */
+ public void addTable(String tagString, TrueTypeTable table) {
+ tables.put(tagString, table);
+ }
+
+ /**
+ * Get a table by name. This command causes the table in question
+ * to be parsed, if it has not already been parsed.
+ *
+ * @param tagString the name of this table, as a 4 character string
+ * (i.e. cmap or head)
+ */
+ public TrueTypeTable getTable(String tagString) {
+ Object tableObj = tables.get(tagString);
+
+ TrueTypeTable table = null;
+
+ if (tableObj instanceof ByteBuffer) {
+ // the table has not yet been parsed. Parse it, and add the
+ // parsed version to the map of tables.
+ ByteBuffer data = (ByteBuffer) tableObj;
+
+ table = TrueTypeTable.createTable(this, tagString, data);
+ addTable(tagString, table);
+ } else {
+ table = (TrueTypeTable) tableObj;
+ }
+
+ return table;
+ }
+
+ /**
+ * Remove a table by name
+ *
+ * @param tagString the name of this table, as a 4 character string
+ * (i.e. cmap or head)
+ */
+ public void removeTable(String tagString) {
+ tables.remove(tagString);
+ }
+
+ /**
+ * Get the number of tables
+ */
+ public short getNumTables() {
+ return (short) tables.size();
+ }
+
+ /**
+ * Get the search range
+ */
+ public short getSearchRange() {
+ double pow2 = Math.floor(Math.log(getNumTables()) / Math.log(2));
+ double maxPower = Math.pow(2, pow2);
+
+ return (short) (16 * maxPower);
+ }
+
+ /**
+ * Get the entry selector
+ */
+ public short getEntrySelector() {
+ double pow2 = Math.floor(Math.log(getNumTables()) / Math.log(2));
+ double maxPower = Math.pow(2, pow2);
+
+ return (short) (Math.log(maxPower) / Math.log(2));
+ }
+
+ /**
+ * Get the range shift
+ */
+ public short getRangeShift() {
+ double pow2 = Math.floor(Math.log(getNumTables()) / Math.log(2));
+ double maxPower = Math.pow(2, pow2);
+
+ return (short) ((maxPower * 16) - getSearchRange());
+ }
+
+ /**
+ * Write a font given the type and an array of Table Directory Entries
+ */
+ public byte[] writeFont() {
+ // allocate a buffer to hold the font
+ ByteBuffer buf = ByteBuffer.allocate(getLength());
+
+ // write the font header
+ buf.putInt(getType());
+ buf.putShort(getNumTables());
+ buf.putShort(getSearchRange());
+ buf.putShort(getEntrySelector());
+ buf.putShort(getRangeShift());
+
+ // first offset is the end of the table directory entries
+ int curOffset = 12 + (getNumTables() * 16);
+
+ // write the tables
+ for (Iterator i = tables.keySet().iterator(); i.hasNext();) {
+ String tagString = (String) i.next();
+ int tag = TrueTypeTable.stringToTag(tagString);
+
+ ByteBuffer data = null;
+
+ Object tableObj = tables.get(tagString);
+ if (tableObj instanceof TrueTypeTable) {
+ data = ((TrueTypeTable) tableObj).getData();
+ } else {
+ data = (ByteBuffer) tableObj;
+ }
+
+ int dataLen = data.remaining();
+
+ // write the table directory entry
+ buf.putInt(tag);
+ buf.putInt(calculateChecksum(tagString, data));
+ buf.putInt(curOffset);
+ buf.putInt(dataLen);
+
+ // save the current position
+ buf.mark();
+
+ // move to the current offset and write the data
+ buf.position(curOffset);
+ buf.put(data);
+
+ // reset the data start pointer
+ data.flip();
+
+ // return to the table directory entry
+ buf.reset();
+
+ // udate the offset
+ curOffset += dataLen;
+
+ // don't forget the padding
+ while ((curOffset % 4) > 0) {
+ curOffset++;
+ }
+ }
+
+ buf.position(curOffset);
+ buf.flip();
+
+ // adjust the checksum
+ updateChecksumAdj(buf);
+
+ return buf.array();
+ }
+
+ /**
+ * Calculate the checksum for a given table
+ *
+ * @param tagString the name of the data
+ * @param data the data in the table
+ */
+ private static int calculateChecksum(final String tagString, final ByteBuffer data) {
+ int sum = 0;
+
+ data.mark();
+
+ // special adjustment for head table
+ if (tagString.equals("head")) {
+ data.putInt(8, 0);
+ }
+
+ for (int i = 0, nlongs = (data.remaining() + 3) / 4; i < nlongs; ++i) {
+ switch (data.remaining()) {
+ case 3:
+ sum += ((data.getShort() << 16) + (data.get() << 8));
+ break;
+ case 2:
+ sum += (data.getShort() << 16);
+ break;
+ case 1:
+ sum += ((data.get() & 0xff) << 24);
+ break;
+ default:
+ sum += data.getInt();
+ }
+ }
+
+ data.reset();
+
+ return sum;
+ }
+
+ /**
+ * Get directory entries from a font
+ */
+ private static void parseDirectories(ByteBuffer data, int numTables,
+ TrueTypeFont ttf) {
+ for (int i = 0; i < numTables; i++) {
+ int tag = data.getInt();
+ String tagString = TrueTypeTable.tagToString(tag);
+// System.out.println ("TTFFont.parseDirectories: " + tagString);
+ int checksum = data.getInt();
+ int offset = data.getInt();
+ int length = data.getInt();
+
+ // read the data
+// System.out.println ("TTFFont.parseDirectories: checksum: " +
+// checksum + ", offset: " + offset + ", length: " + length);
+ data.mark();
+ data.position(offset);
+
+ ByteBuffer tableData = data.slice();
+ tableData.limit(length);
+
+ int calcChecksum = calculateChecksum(tagString, tableData);
+
+ if (calcChecksum == checksum) {
+ ttf.addTable(tagString, tableData);
+ } else {
+ /* System.out.println("Mismatched checksums on table " +
+ tagString + ": " + calcChecksum + " != " +
+ checksum);*/
+
+ ttf.addTable(tagString, tableData);
+
+ }
+ data.reset();
+ }
+ }
+
+ /**
+ * Get the length of the font
+ *
+ * @return the length of the entire font, in bytes
+ */
+ private int getLength() {
+ // the size of all the table directory entries
+ int length = 12 + (getNumTables() * 16);
+
+ // for each directory entry, get the size,
+ // and don't forget the padding!
+ for (Iterator i = tables.values().iterator(); i.hasNext();) {
+ Object tableObj = i.next();
+
+ // add the length of the entry
+ if (tableObj instanceof TrueTypeTable) {
+ length += ((TrueTypeTable) tableObj).getLength();
+ } else {
+ length += ((ByteBuffer) tableObj).remaining();
+ }
+
+ // pad
+ if ((length % 4) != 0) {
+ length += (4 - (length % 4));
+ }
+ }
+
+ return length;
+ }
+
+ /**
+ * Update the checksumAdj field in the head table
+ */
+ private void updateChecksumAdj(ByteBuffer fontData) {
+ int checksum = calculateChecksum("", fontData);
+ int checksumAdj = 0xb1b0afba - checksum;
+
+ // find the head table
+ int offset = 12 + (getNumTables() * 16);
+
+ // find the head table
+ for (Iterator i = tables.keySet().iterator(); i.hasNext();) {
+ String tagString = (String) i.next();
+
+ // adjust the checksum
+ if (tagString.equals("head")) {
+ fontData.putInt(offset + 8, checksumAdj);
+ return;
+ }
+
+ // add the length of the entry
+ Object tableObj = tables.get(tagString);
+ if (tableObj instanceof TrueTypeTable) {
+ offset += ((TrueTypeTable) tableObj).getLength();
+ } else {
+ offset += ((ByteBuffer) tableObj).remaining();
+ }
+
+ // pad
+ if ((offset % 4) != 0) {
+ offset += (4 - (offset % 4));
+ }
+ }
+ }
+
+ /**
+ * Write the font to a pretty string
+ */
+ @Override
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+
+ System.out.println("Type : " + getType());
+ System.out.println("NumTables : " + getNumTables());
+ System.out.println("SearchRange : " + getSearchRange());
+ System.out.println("EntrySelector: " + getEntrySelector());
+ System.out.println("RangeShift : " + getRangeShift());
+
+ for (Iterator i = tables.entrySet().iterator(); i.hasNext();) {
+ Map.Entry e = (Map.Entry) i.next();
+
+ TrueTypeTable table = null;
+ if (e.getValue() instanceof ByteBuffer) {
+ table = getTable((String) e.getKey());
+ } else {
+ table = (TrueTypeTable) e.getValue();
+ }
+
+ System.out.println(table);
+ }
+
+ return buf.toString();
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/font/ttf/TrueTypeTable.java b/PdfView/src/main/java/com/sun/pdfview/font/ttf/TrueTypeTable.java
new file mode 100644
index 0000000..057be78
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/font/ttf/TrueTypeTable.java
@@ -0,0 +1,196 @@
+/*
+ * $Id: TrueTypeTable.java,v 1.3 2009/02/09 16:31:23 tomoke 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.font.ttf;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+/**
+ * The base class for TrueType tables. Specific tables can extend this
+ * to add more functionality
+ */
+public class TrueTypeTable {
+
+ /**
+ * Well known tables
+ */
+ public static final int CMAP_TABLE = 0x636d6170;
+ public static final int GLYF_TABLE = 0x676c7966;
+ public static final int HEAD_TABLE = 0x68656164;
+ public static final int HHEA_TABLE = 0x68686561;
+ public static final int HMTX_TABLE = 0x686d7478;
+ public static final int MAXP_TABLE = 0x6d617870;
+ public static final int NAME_TABLE = 0x6e616d65;
+ public static final int POST_TABLE = 0x706f7374;
+ public static final int LOCA_TABLE = 0x6c6f6361;
+ /**
+ * This table's tag
+ */
+ private int tag;
+ /**
+ * The data in this table, in ByteBuffer form
+ */
+ private ByteBuffer data;
+
+ /**
+ * Creates a new instance of TrueTypeTable.
+ *
+ * This method is protected. Use the getTable() methods
+ * to get new instances.
+ *
+ * @param tag the tag for this table
+ */
+ protected TrueTypeTable(int tag) {
+ this.tag = tag;
+ }
+
+ /**
+ * Get a new instance of an empty table by tag string
+ *
+ * @param ttf the font that contains this table
+ * @param tagString the tag for this table, as a 4 character string
+ * (e.g. head or cmap)
+ */
+ public static TrueTypeTable createTable(TrueTypeFont ttf,
+ String tagString) {
+ return createTable(ttf, tagString, null);
+ }
+
+ /**
+ * Get a new instance of a table with provided data
+ *
+ * @param ttf the font that contains this table
+ * @param tagString the tag for this table, as a 4 character string
+ * (e.g. head or cmap)
+ * @param data the table data
+ */
+ public static TrueTypeTable createTable(TrueTypeFont ttf,
+ String tagString, ByteBuffer data) {
+ TrueTypeTable outTable = null;
+
+ int tag = stringToTag(tagString);
+
+ switch (tag) {
+ case CMAP_TABLE: // cmap table
+ outTable = new CmapTable();
+ break;
+ case GLYF_TABLE:
+ outTable = new GlyfTable(ttf);
+ break;
+ case HEAD_TABLE: // head table
+ outTable = new HeadTable();
+ break;
+ case HHEA_TABLE: // hhea table
+ outTable = new HheaTable();
+ break;
+ case HMTX_TABLE:
+ outTable = new HmtxTable(ttf);
+ break;
+ case LOCA_TABLE:
+ outTable = new LocaTable(ttf);
+ break;
+ case MAXP_TABLE: // maxp table
+ outTable = new MaxpTable();
+ break;
+ case NAME_TABLE: // name table
+ outTable = new NameTable();
+ break;
+ case POST_TABLE: // post table
+ outTable = new PostTable();
+ break;
+ default:
+ outTable = new TrueTypeTable(tag);
+ break;
+ }
+
+ if (data != null) {
+ outTable.setData(data);
+ }
+
+ return outTable;
+ }
+
+ /**
+ * Get the table's tag
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * Get the data in the table
+ */
+ public ByteBuffer getData() {
+ return data;
+ }
+
+ /**
+ * Set the data in the table
+ */
+ public void setData(ByteBuffer data) {
+ this.data = data;
+ }
+
+ /**
+ * Get the size of the table, in bytes
+ */
+ public int getLength() {
+ return getData().remaining();
+ }
+
+ /**
+ * Get the tag as a string
+ */
+ public static String tagToString(int tag) {
+ char[] c = new char[4];
+ c[0] = (char) (0xff & (tag >> 24));
+ c[1] = (char) (0xff & (tag >> 16));
+ c[2] = (char) (0xff & (tag >> 8));
+ c[3] = (char) (0xff & (tag));
+
+ return new String(c);
+ }
+
+ /**
+ * Turn a string into a tag
+ */
+ public static int stringToTag(String tag) {
+ char[] c = tag.toCharArray();
+
+ if (c.length != 4) {
+ throw new IllegalArgumentException("Bad tag length: " + tag);
+ }
+
+ return c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3];
+ }
+
+ /**
+ * Put into a nice string
+ */
+ public String toString() {
+ String out = " " + tagToString(getTag()) + " Table. Data is: ";
+ if (getData() == null) {
+ out += "not set";
+ } else {
+ out += "set";
+ }
+ return out;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/function/FunctionType0.java b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType0.java
new file mode 100644
index 0000000..ce8d8f0
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType0.java
@@ -0,0 +1,466 @@
+/*
+ * $Id: FunctionType0.java,v 1.3 2007/12/20 18:33:35 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.function;
+
+import java.io.IOException;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * A sampled function maps input values to output values by interpolating
+ * along a line or cubic between two known values.
+ */
+public class FunctionType0 extends PDFFunction {
+ /** the valid interpolation methods */
+ protected static final int LINEAR_INTERPOLATION = 1;
+ protected static final int CUBIC_INTERPOLATION = 3;
+
+ /** the size of each input dimension, as an array of m integers */
+ private int[] size;
+
+ /** the number of bits in each sample */
+ private int bitsPerSample;
+
+ /** the interpolation type, from the list above */
+ private int order = 1;
+
+ /** the optional encoding array, tells how to map input parameters to values */
+ private float[] encode;
+
+ /** the optional decoding array, tells how to map output parameters to values */
+ private float[] decode;
+
+ /**
+ * the actual samples, converted to integers. The first index is
+ * input values (from 0 to size[m - 1] * size[m - 2] * ... * size[0]),
+ * and the second is the output dimension within the sample (from 0 to n)
+ */
+ private int[][] samples;
+
+ /** Creates a new instance of FunctionType0 */
+ protected FunctionType0() {
+ super (TYPE_0);
+ }
+
+ /** Read the function information from a PDF Object */
+ protected void parse(PDFObject obj) throws IOException {
+ // read the size array (required)
+ PDFObject sizeObj = obj.getDictRef("Size");
+ if (sizeObj == null) {
+ throw new PDFParseException("Size required for function type 0!");
+ }
+ PDFObject[] sizeAry = sizeObj.getArray();
+ int[] size = new int[sizeAry.length];
+ for (int i = 0; i < sizeAry.length; i++) {
+ size[i] = sizeAry[i].getIntValue();
+ }
+ setSize(size);
+
+ // read the # bits per sample (required)
+ PDFObject bpsObj = obj.getDictRef("BitsPerSample");
+ if (bpsObj == null) {
+ throw new PDFParseException("BitsPerSample required for function type 0!");
+ }
+ setBitsPerSample(bpsObj.getIntValue());
+
+ // read the order (optional)
+ PDFObject orderObj = obj.getDictRef("Order");
+ if (orderObj != null) {
+ setOrder(orderObj.getIntValue());
+ }
+
+ // read the encode array (optional)
+ PDFObject encodeObj = obj.getDictRef("Encode");
+ if (encodeObj != null) {
+ PDFObject[] encodeAry = encodeObj.getArray();
+ float[] encode = new float[encodeAry.length];
+ for (int i = 0; i < encodeAry.length; i++) {
+ encode[i] = encodeAry[i].getFloatValue();
+ }
+ setEncode(encode);
+ }
+
+ // read the decode array (optional)
+ PDFObject decodeObj = obj.getDictRef("Decode");
+ if (decodeObj != null) {
+ PDFObject[] decodeAry = decodeObj.getArray();
+ float[] decode = new float[decodeAry.length];
+ for (int i = 0; i < decodeAry.length; i++) {
+ decode[i] = decodeAry[i].getFloatValue();
+ }
+ setDecode(decode);
+ }
+
+ // finally, read the samples
+ setSamples(readSamples(obj.getStreamBuffer()));
+ }
+
+ /**
+ * Map from m input values to n output values.
+ * The number of inputs m must be exactly one half the size of the
+ * domain. The number of outputs should match one half the size of the
+ * range.
+ *
+ * @param inputs an array of m input values
+ * @param outputs an array of size n which will be filled
+ * with the output values, or null to return a new array
+ */
+ protected void doFunction(float[] inputs, int inputOffset,
+ float[] outputs, int outputOffset)
+ {
+ // calculate the encoded values for each input
+ float[] encoded = new float[getNumInputs()];
+ for (int i = 0; i < getNumInputs(); i++) {
+ // encode -- interpolate(x, domain<2i>, domain<2i + 1>,
+ // encode<2i>, encode<2i + 1>)
+ encoded[i] = interpolate(inputs[i + inputOffset],
+ getDomain(2 * i),
+ getDomain((2 * i) + 1),
+ getEncode(2 * i),
+ getEncode((2 * i) + 1));
+
+ // clip to size of sample table -- min(max(e, 0), size - 1)
+ encoded[i] = Math.max(encoded[i], 0);
+ encoded[i] = Math.min(encoded[i], size[i] - 1);
+ }
+
+ // do some magic
+ for (int i = 0; i < getNumOutputs(); i++) {
+ if (getOrder() == 1) {
+ outputs[i + outputOffset] = multilinearInterpolate(encoded, i);
+ } else {
+ outputs[i + outputOffset] = multicubicInterpolate(encoded, i);
+ }
+ }
+
+ // now adjust the output to be within range
+ for (int i = 0; i < outputs.length; i++) {
+ // decode -- interpolate(r, 0, 2^bps - 1,
+ // decode<2i>, decode<2i + 1>)
+ outputs[i + outputOffset] = interpolate(outputs[i + outputOffset],
+ 0,
+ (float) Math.pow(2, getBitsPerSample()) - 1,
+ getDecode(2 * i),
+ getDecode((2 * i) + 1));
+ }
+ }
+
+ /**
+ * Get the size of a given input dimension
+ *
+ * @param dimension the input dimension to get the size of
+ * @return the number of samples in the given dimension
+ */
+ protected int getSize(int dimension) {
+ return size[dimension];
+ }
+
+ /**
+ * Set the size of all input dimensions
+ */
+ protected void setSize(int[] size) {
+ this.size = size;
+ }
+
+
+ /**
+ * Get the number of bits per sample
+ */
+ protected int getBitsPerSample() {
+ return bitsPerSample;
+ }
+
+ /**
+ * Set the number of bits per sample
+ */
+ protected void setBitsPerSample(int bits) {
+ this.bitsPerSample = bits;
+ }
+
+ /**
+ * Get the interpolation type
+ */
+ protected int getOrder() {
+ return order;
+ }
+
+ /**
+ * Set the interpolation type
+ */
+ protected void setOrder(int order) {
+ this.order = order;
+ }
+
+ /**
+ * Get the encoding for a particular input parameter
+ *
+ * @param i the index into the encoding array, which has size 2 * m.
+ * the ith entry in the array has index 2i,
+ * 2i + 1
+ * @return the encoding value if the encoding array is set, or the default
+ */
+ protected float getEncode(int i) {
+ if (encode != null) {
+ return encode[i];
+ } else if ((i % 2) == 0) {
+ return 0f;
+ } else {
+ return (getSize(i / 2) - 1);
+ }
+ }
+
+ /**
+ * Set the encode array
+ */
+ protected void setEncode(float[] encode) {
+ this.encode = encode;
+ }
+
+ /**
+ * Get the decoding for a particular input parameter
+ *
+ * @param i the index into the decoding array, which has size 2 * n.
+ * the ith entry in the array has index 2i,
+ * 2i + 1
+ * @return the decoding value if the decoding array is set, or the default
+ */
+ protected float getDecode(int i) {
+ if (decode != null) {
+ return decode[i];
+ } else {
+ return getRange(i);
+ }
+ }
+
+ /**
+ * Set the decode array
+ */
+ protected void setDecode(float[] decode) {
+ this.decode = decode;
+ }
+
+ /**
+ * Get a component for a sample given m indices and output
+ * dimension.
+ *
+ * @param values an array of m values determining which sample
+ * to select
+ * @param od the output dimension (0 - n) to get the sample in
+ * @return the sample for the given values and index
+ */
+ protected int getSample(int[] values, int od) {
+ int mult = 1;
+ int index = 0;
+ for (int i = 0; i < values.length; i++) {
+ index += mult * values[i];
+ mult *= getSize(i);
+ }
+
+ return samples[index][od];
+ }
+
+ /**
+ * Set the table of samples
+ */
+ protected void setSamples(int[][] samples) {
+ this.samples = samples;
+ }
+
+ /**
+ * Read the samples from the input stream. Each sample is made up
+ * of n components, each of which has length bitsPerSample
+ * bits. The samples are arranged by dimension, then range
+ */
+ private int[][] readSamples(ByteBuffer buf) {
+ // calculate the number of samples in the table
+ int size = 1;
+ for (int i = 0; i < getNumInputs(); i++) {
+ size *= getSize(i);
+ }
+
+ // create the samples table
+ int[][] samples = new int[size][getNumOutputs()];
+
+
+ // the current location in the buffer, in bits from byteLoc
+ int bitLoc = 0;
+
+ // the current location in the buffer, in bytes
+ int byteLoc = 0;
+
+ // the current index in the samples array
+ int index = 0;
+
+ for (int i = 0; i < getNumInputs(); i++) {
+ for (int j = 0; j < getSize(i); j++) {
+ for (int k = 0; k < getNumOutputs(); k++) {
+ /** [JK FIXME one bit at a time is really inefficient */
+ int value = 0;
+
+ int toRead = getBitsPerSample();
+ byte curByte = buf.get(byteLoc);
+
+ while (toRead > 0) {
+ int nextBit = ((curByte >> (7 - bitLoc)) & 0x1);
+ value |= nextBit << (toRead - 1);
+
+ if (++bitLoc == 8) {
+ bitLoc = 0;
+ byteLoc++;
+
+ if (toRead > 1) {
+ curByte = buf.get(byteLoc);
+ }
+ }
+
+ toRead--;
+ }
+
+ samples[index][k] = value;
+ }
+
+ index++;
+ }
+ }
+
+ return samples;
+ }
+
+ /**
+ * Perform a piecewise multilinear interpolation. The provides a
+ * close approximation to the standard linear interpolation, at
+ * a far lower cost, since every element is not evaluated at every
+ * iteration. Instead, a walk of the most significant axes is performed,
+ * following the algorithm desribed at:
+ * http://osl.iu.edu/~tveldhui/papers/MAScThesis/node33.html
+ *
+ * @param encoded the encoded input values
+ * @param od the output dimension
+ */
+ private float multilinearInterpolate(float[] encoded, int od) {
+ // first calculate the distances -- the differences between
+ // each encoded value and the integer below it.
+ float[] dists = new float[encoded.length];
+
+ for (int i = 0; i < dists.length; i++) {
+ dists[i] = (float) (encoded[i] - Math.floor(encoded[i]));
+ }
+
+ // initialize the map of axes. Each bit in this map represents
+ // whether the control value in that dimension should be the integer
+ // above or below encoded[i]
+ int map = 0;
+
+ // the initial values
+ float val = getSample(encoded, map, od);
+ float prev = val;
+
+ // walk the axes
+ for (int i = 0; i < dists.length; i++) {
+ // find the largest value of dist remaining
+ int idx = 0;
+ float largest = -1;
+ for (int c = 0; c < dists.length; c++) {
+ if (dists[c] > largest) {
+ largest = dists[c];
+ idx = c;
+ }
+ }
+
+ // now find the sample with that axis set to 1
+ map |= (0x1 << idx);
+ float cur = getSample(encoded, map, od);
+
+ // calculate the value and remember it
+ val += dists[idx] * (cur - prev);
+ prev = val;
+
+ // make sure we won't find this distance again
+ dists[idx] = -1;
+ }
+
+ // voila
+ return val;
+ }
+
+ /**
+ * Perform a multicubic interpolation
+ *
+ * @param encoded the encoded input values
+ * @param od the output dimension
+ */
+ private float multicubicInterpolate(float[] encoded, int od) {
+ System.out.println("Cubic interpolation not supported!");
+ return multilinearInterpolate(encoded, od);
+ }
+
+ /**
+ * Perform a linear interpolation. Given a value x, and two points,
+ * (xmin, ymin), (xmax, ymax), where xmin <= x <= xmax, calculate a value
+ * y on the line from (xmin, ymin) to (xmax, ymax).
+ *
+ * @param x the x value of the input
+ * @param xmin the minimum x value
+ * @param ymin the minimum y value
+ * @param xmax the maximum x value
+ * @param ymax the maximum y value
+ * @return the y value interpolated from the given x
+ */
+ public static float interpolate(float x, float xmin, float xmax,
+ float ymin, float ymax)
+ {
+ float value = (ymax - ymin) / (xmax - xmin);
+ value *= x - xmin;
+ value += ymin;
+
+ return value;
+ }
+
+ /**
+ * Get a sample based on an array of encoded values and a control
+ * map. For each bit in the map, if that bit is 0 the integer below
+ * the encoded value is selected, or if the bit is 1, the interger
+ * above is selected.
+ *
+ * @param encoded the encoded values
+ * @param map the bit map of control values
+ * @param od the output dimension to read the sample for
+ */
+ private float getSample(float[] encoded, int map, int od) {
+ int[] controls = new int[encoded.length];
+
+ // fill in the controls array with appropriate ints
+ for (int i = 0; i < controls.length; i++) {
+ if ((map & (0x1 << i)) == 0) {
+ controls[i] = (int) Math.floor(encoded[i]);
+ } else {
+ controls[i] = (int) Math.ceil(encoded[i]);
+ }
+ }
+
+ // now return the actual sample
+ return getSample(controls, od);
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/function/FunctionType2.java b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType2.java
new file mode 100644
index 0000000..7dc13c8
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType2.java
@@ -0,0 +1,143 @@
+/*
+ * $Id: FunctionType2.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.function;
+
+import java.io.IOException;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * A type 2 function is an exponential interpolation function, which maps
+ * from one input value to n output values using a simple exponential
+ * formula.
+ */
+public class FunctionType2 extends PDFFunction {
+ /** the function's value at zero for the n outputs */
+ private float[] c0 = new float[] { 0f };
+
+ /** the function's value at one for the n outputs */
+ private float[] c1 = new float[] { 1f };
+
+ /** the exponent */
+ private float n;
+
+ /** Creates a new instance of FunctionType2 */
+ public FunctionType2() {
+ super(TYPE_2);
+ }
+
+ /**
+ * Read the zeros, ones and exponent
+ */
+ protected void parse(PDFObject obj) throws IOException
+ {
+ // read the exponent (required)
+ PDFObject nObj = obj.getDictRef("N");
+ if (nObj == null) {
+ throw new PDFParseException("Exponent required for function type 2!");
+ }
+ setN(nObj.getFloatValue());
+
+ // read the zeros array (optional)
+ PDFObject cZeroObj = obj.getDictRef("C0");
+ if (cZeroObj != null) {
+ PDFObject[] cZeroAry = cZeroObj.getArray();
+ float[] cZero = new float[cZeroAry.length];
+ for (int i = 0; i < cZeroAry.length; i++) {
+ cZero[i] = cZeroAry[i].getFloatValue();
+ }
+ setC0(cZero);
+ }
+
+ // read the ones array (optional)
+ PDFObject cOneObj = obj.getDictRef("C1");
+ if (cOneObj != null) {
+ PDFObject[] cOneAry = cOneObj.getArray();
+ float[] cOne = new float[cOneAry.length];
+ for (int i = 0; i < cOneAry.length; i++) {
+ cOne[i] = cOneAry[i].getFloatValue();
+ }
+ setC1(cOne);
+ }
+ }
+
+ /**
+ * Calculate the function value for the input. For each output (j),
+ * the function value is:
+ * C0(j) + x^N * (C1(j) - C0(j))
+ */
+ protected void doFunction(float[] inputs, int inputOffset,
+ float[] outputs, int outputOffset)
+ {
+ // read the input value
+ float input = inputs[inputOffset];
+
+ // calculate the output values
+ for (int i = 0; i < getNumOutputs(); i++) {
+ outputs[i + outputOffset] = getC0(i) +
+ (float) (Math.pow(input, getN()) * (getC1(i) - getC0(i)));
+ }
+ }
+
+ /**
+ * Get the exponent
+ */
+ public float getN() {
+ return n;
+ }
+
+ /**
+ * Set the exponent
+ */
+ protected void setN(float n) {
+ this.n = n;
+ }
+
+ /**
+ * Get the values at zero
+ */
+ public float getC0(int index) {
+ return c0[index];
+ }
+
+ /**
+ * Set the values at zero
+ */
+ protected void setC0(float[] c0) {
+ this.c0 = c0;
+ }
+
+ /**
+ * Get the values at one
+ */
+ public float getC1(int index) {
+ return c1[index];
+ }
+
+ /**
+ * Set the values at one
+ */
+ protected void setC1(float[] c1) {
+ this.c1 = c1;
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/function/FunctionType3.java b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType3.java
new file mode 100644
index 0000000..cbdbda1
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType3.java
@@ -0,0 +1,197 @@
+/*
+ * $Id: FunctionType3.java,v 1.1 2009/02/09 16:32:23 tomoke 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.function;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ * 3.9.3 - A stitching function define a stitching of the subdomains of
+ * several 1-input functions to produce a single new 1-input function.
+ * Since the resulting stitching function is a 1-input function, the
+ * domain is given by a two-element array, [ Domain0 Domain1 ].
+ *
+ *
+ */
+public class FunctionType3 extends PDFFunction {
+
+ /**
+ * the actual samples, converted to integers. The first index is
+ * input values (from 0 to size[m - 1] * size[m - 2] * ... * size[0]),
+ * and the second is the output dimension within the sample (from 0 to n)
+ */
+ private int[][] samples;
+
+ /** Creates a new instance of FunctionType3 */
+ protected FunctionType3() {
+ super(TYPE_3);
+ }
+
+ /**
+ *
+ *
+ * Functionsarray (Required) An array of k 1-input functions making up
+ * the stitching function. The output dimensionality of all functions
+ * must be the same, and compatible with the value of Range
+ * if Range is present.
+ *
+ * Domainarray (Required) A 2 element array where
+ * Domain0 is less than Domain1. This is read by the
+ * PDFFunction superclass.
+ *
+ * Boundsarray (Required) An array of k-1 numbers that,
+ * in combination with Domain, define the intervals to which each
+ * function from the Functions array applies. Bounds elements
+ * must be in order of increasing value, and each value must be within
+ * the domain defined by >b>Domain.
+ *
+ * Encodearray (Required) An array of 2 * k numbers that,
+ * taken in pairs, map each subset of the domain defined by
+ * and the Bounds array to the domain of the corresponding function.
+ *
+ */
+ protected void parse(PDFObject obj) throws IOException {
+ // read the Functions array (required)
+ PDFObject functionsObj = obj.getDictRef("Functions");
+ if (functionsObj == null) {
+ throw new PDFParseException("Functions required for function type 3!");
+ }
+ PDFObject[] functionsAry = functionsObj.getArray();
+ int[] size = new int[functionsAry.length];
+ for (int i = 0; i < functionsAry.length; i++) {
+ size[i] = functionsAry[i].getIntValue();
+ }
+
+ // read the Bounds array (required)
+ PDFObject boundsObj = obj.getDictRef("Bounds");
+ if (boundsObj == null) {
+ throw new PDFParseException("Bounds required for function type 3!");
+ }
+ PDFObject[] boundsAry = boundsObj.getArray();
+ int[] size1 = new int[boundsAry.length];
+ for (int i = 0; i < boundsAry.length; i++) {
+ size1[i] = boundsAry[i].getIntValue();
+ }
+
+ // read the encode array (required)
+ PDFObject encodeObj = obj.getDictRef("Encode");
+ if (encodeObj != null) {
+ throw new PDFParseException("Encode required for function type 3!");
+ }
+ PDFObject[] encodeAry = encodeObj.getArray();
+ float[] encode = new float[encodeAry.length];
+ for (int i = 0; i < encodeAry.length; i++) {
+ encode[i] = encodeAry[i].getFloatValue();
+ }
+ throw new PDFParseException("Unsupported function type 3.");
+ }
+
+ /**
+ * Map from m input values to n output values.
+ * The number of inputs m must be exactly one half the size of the
+ * domain. The number of outputs should match one half the size of the
+ * range.
+ *
+ * @param inputs an array of m input values
+ * @param outputs an array of size n which will be filled
+ * with the output values, or null to return a new array
+ */
+ protected void doFunction(float[] inputs, int inputOffset,
+ float[] outputs, int outputOffset) {
+ // calculate the encoded values for each input
+ float[] encoded = new float[getNumInputs()];
+// for (int i = 0; i < getNumInputs(); i++) {
+// // encode -- interpolate(x, domain<2i>, domain<2i + 1>,
+// // encode<2i>, encode<2i + 1>)
+// encoded[i] = interpolate(inputs[i + inputOffset],
+// getDomain(2 * i),
+// getDomain((2 * i) + 1),
+// getEncode(2 * i),
+// getEncode((2 * i) + 1));
+//
+// // clip to size of sample table -- min(max(e, 0), size - 1)
+// encoded[i] = Math.max(encoded[i], 0);
+// encoded[i] = Math.min(encoded[i], size[i] - 1);
+// }
+
+ // do some magic
+ for (int i = 0; i < getNumOutputs(); i++) {
+// if (getOrder() == 1) {
+// outputs[i + outputOffset] = multilinearInterpolate(encoded, i);
+// } else {
+// outputs[i + outputOffset] = multicubicInterpolate(encoded, i);
+// }
+ }
+
+ // now adjust the output to be within range
+// for (int i = 0; i < outputs.length; i++) {
+// // decode -- interpolate(r, 0, 2^bps - 1,
+// // decode<2i>, decode<2i + 1>)
+// outputs[i + outputOffset] = interpolate(outputs[i + outputOffset],
+// 0,
+// (float) Math.pow(2, getBitsPerSample()) - 1,
+// getDecode(2 * i),
+// getDecode((2 * i) + 1));
+// }
+ }
+}
diff --git a/PdfView/src/main/java/com/sun/pdfview/function/FunctionType4.java b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType4.java
new file mode 100644
index 0000000..4ca48f4
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/function/FunctionType4.java
@@ -0,0 +1,943 @@
+/*
+ * $Id: FunctionType4.java,v 1.3 2009/02/12 13:53:59 tomoke 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.function;
+
+import java.io.IOException;
+import java.util.*;
+
+import net.sf.andpdf.nio.ByteBuffer;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+/**
+ *
A PostScript function is represented as a stream containing code
+ * written in a small subset of the PostScript language.
+ * This reference is taken from the (3200-1:2008:7.10.5)
+ */
+public class FunctionType4 extends PDFFunction {
+
+ /** the set of all Operations we support. These operations are defined
+ * in Appendix B - Operators.*/
+ private static HashSet operationSet = null;
+ /** the list of tokens and sub-expressions. */
+ private LinkedList tokens = new LinkedList();
+ /** the stack of operations. The stack contents should all be Comparable. */
+ private LinkedList stack = new LinkedList();
+
+ /** Creates a new instance of FunctionType4 */
+ protected FunctionType4() {
+ super(TYPE_4);
+ if (operationSet == null) {
+ initOperations();
+ }
+ }
+
+ /**
+ * Initialize the operations that we can perform.
+ */
+ private void initOperations() {
+ /** these operators consider the left hand arguments as deeper in
+ * the stack than the right hand arguments, thus the right-hand is
+ * is the top of the stack and is popped first.
+ *
+ * Operation details in PostScript Language Reference Manual:
+ * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf
+ * Chapter 8 - Operator Details
+ */
+ if (operationSet == null) {
+ operationSet = new HashSet();
+ // Arithmetic Operators
+ operationSet.add(new Operation("abs") {
+
+ /**
+ * num1absnum2
+ *
+ * The type of the result is the same as the type of num1,
+ * unless num1 is the smallest (most negative) integer,
+ * in which case the result is a real number.
+ *
+ * If both operands are integers and the result is
+ * within integer range, the result is an integer;
+ * otherwise, the result is a real number.
+ *
+ * returns the angle (in degress between
+ * 0 and 360) whose tangent is num divided by den.
+ * Either num or den may be 0, but not both. The signs
+ * of num and den determine the quadrant in which the
+ * result will lie: positive num yeilds a result in the
+ * positive y plane, while a positive den yeilds a result in
+ * the positive x plane. The result is a real number.
+ *
+ * takes an integer, real, or string and produces an
+ * integer result. If the operand is an integer, cvi
+ * simply returns it. If the operand is a real number,
+ * it truncates any fractional part (that is, rounds
+ * it toward 0) and converts it to an integer.
+ * If the operand is a string, cvi invokes the equivalent
+ * of the token operator to interpret the characters
+ * of the string as a number according to the PostScript
+ * syntax rules. If that number is a real number, cvi converts
+ * it to an integer.
+ * A rangecheck error occurs if a real number is too
+ * large to convert to an integer.
+ *
+ * (convert to real) takes an integer, real, or string
+ * object and produces a real result. If the operand
+ * is an integer, cvr converts it to a real number.
+ * If the operand is a real number, cvr simply returns it.
+ * If the operand is a string, cvr invokes the equivalent
+ * of the token operator to interpret the characters of
+ * the string as a number according to the PostScript
+ * syntax rules. If that number is an integer, cvr converts
+ * it to a real number.
+ *
+ * divides num1 by num2, producing a result that is
+ * always a real number even if both operands are integers.
+ * Use idiv instead if the operands are integers and an
+ * integer result is desired.
+ *
+ * raises base to the exponent power. The operands may be
+ * either integers or real numbers. If the exponent has a
+ * fractional part, the result is meaningful only if the
+ * base is nonnegative. The result is always a real number.
+ *
+ * divides int1 by int2 and returns the integer part
+ * of the quotient, with any fractional part discarded.
+ * Both operands of idiv must be integers and the result
+ * is an integer.
+ *
+ * returns the remainder that results from
+ * dividing int1 by int2. The sign of the result
+ * is the same as the sign of the dividend int1.
+ * Both operands must be integers and the result
+ * is an integer.
+ *
+ * returns the product of num1 and num2.
+ * If both operands are integers and the result
+ * is within integer range, the result is an integer;
+ * otherwise, the result is a real number.
+ *
+ * returns the negative of num1. The type of the result
+ * is the same as the type of num1 unless num1 is the
+ * smallest (most negative) integer, in which case the
+ * result is a real number.
+ *
+ * returns the integer value nearest to num1.
+ * If num1 is equally close to its two nearest
+ * integers, round returns the greater of the two.
+ * The type of the result is the same as
+ * the type of the operand.
+ *
+ * returns the result of subtracting num2 from num1.
+ * If both operands are integers and the result is within
+ * integer range, the result is an integer; otherwise,
+ * the result is a real number.
+ *
+ * returns the logical conjunction of the operands
+ * if they are boolean. If the operands are integers,
+ * and returns the bitwise "and" of their binary
+ * representations.
+ *
+ * shifts the binary representation of int1 left by
+ * shift bits and returns the result. Bits shifted out
+ * are lost; bits shifted in are 0. If shift is negative,
+ * a right shift by –shift bits is performed.
+ * This operation produces an arithmetically correct
+ * result only for positive values of int1.
+ * Both int1 and shift must be integers.
+ *
+ * pops two objects from the operand stack and pushes\
+ * true if they are equal, or false if not.
+ * The definition of equality depends on the types of
+ * the objects being compared.
+ * Simple objects are equal if their types and values
+ * are the same. Strings are equal if their lengths and
+ * individual elements are equal. Other composite objects
+ * (arrays and dictionaries) are equal only if they share
+ * the same value. Separate values are considered unequal,
+ * even if all the components of those values are the
+ * same.
+ * This operator performs some type conversions.
+ * Integers and real numbers can be compared freely:
+ * an integer and a real number representing the same
+ * mathematical value are considered equal by eq.
+ * Strings and names can likewise be compared freely:
+ * a name defined by some sequence of characters is equal
+ * to a string whose elements are the same sequence of
+ * characters.
+ * The literal/executable and access attributes of
+ * objects are not considered in comparisons
+ * between objects.
+ *
+ * pushes a boolean object whose value is false on the
+ * operand stack. false is not an operator; it is a name in
+ * systemdict associated with the boolean value false.
+ *
+ * pops two objects from the operand stack and pushes true
+ * if the first operand is greater than or equal to the second,
+ * or false otherwise. If both operands are numbers,
+ * ge compares their mathematical values. If both operands
+ * are strings, ge compares them element by element, treating
+ * the elements as integers in the range 0 to 255, to determine
+ * whether the first string is lexically greater than or equal
+ * to the second. If the operands are of other types or one
+ * is a string and the other is a number, a typecheck
+ * error occurs.
+ *
+ * pops two objects from the operand stack and pushes true
+ * if the first operand is greater than the second, or
+ * false otherwise. If both operands are numbers, gt compares
+ * their mathematical values. If both operands are strings,
+ * gt compares them element by element, treating the elements
+ * as integers in the range 0 to 255, to determine whether
+ * the first string is lexically greater than the second.
+ * If the operands are of other types or one is a string
+ * and the other is a number, a typecheck error occurs.
+ *
+ * pops two objects from the operand stack and pushes true
+ * if the first operand is less than or equal to the second,
+ * or false otherwise. If both operands are numbers, le
+ * compares their mathematical values. If both operands are
+ * strings, le compares them element by element, treating
+ * the elements as integers in the range 0 to 255,
+ * to determine whether the first string is lexically less
+ * than or equal to the second. If the operands are of other
+ * types or one is a string and the other is a number, a
+ * typecheck error occurs.
+ *
+ * pops two objects from the operand stack and pushes true
+ * if the first operand is less than the second, or false
+ * otherwise. If both operands are numbers, lt compares
+ * their mathematical values. If both operands are strings,
+ * lt compares them element by element, treating the elements
+ * as integers in the range 0 to 255, to determine whether
+ * the first string is lexically less than the second.
+ * If the operands are of other types or one is a string
+ * and the other is a number, a typecheck error occurs.
+ *
+ * pops two objects from the operand stack and pushes false
+ * if they are equal, or true if not. What it means for objects
+ * to be equal is presented in the description of the
+ * eq operator.
+ *
+ * returns the logical negation of the operand if it is
+ * boolean. If the operand is an integer, not returns the
+ * bitwise complement (ones complement) of its binary
+ * representation.
+ *
+ * returns the logical disjunction of the operands if they
+ * are boolean. If the operands are integers, or returns
+ * the bitwise "inclusive or" of their binary representations.
+ *
+ * pushes a boolean object whose value is true on the operand
+ * stack. true is not an operator; it is a name in systemdict
+ * associated with the boolean value true.
+ *
+ * returns the logical "exclusive or" of the operands if they
+ * are boolean. If the operands are integers, xor returns the
+ * bitwise "exclusive or" of their binary representations.
+ *
+ * removes both operands from the stack, then executes proc
+ * if bool is true. The if operator pushes no results of
+ * its own on the operand stack, but proc may do so (see
+ * Section 3.5, "Execution").
+ *
+ * removes all three operands from the stack, then
+ * executes proc1 if bool is true or proc2 if bool is false.
+ * The ifelse operator pushes no results of its own on the
+ * operand stack, but the procedure it executes may do so
+ * (see Section 3.5, "Execution").
+ *
+ * Examples
+ * 4 3 lt {(TruePart)} {(FalsePart)} ifelse
+ * results in FalsePart, since 4 is not less than 3
+ *
+ * performs two entirely different functions, depending on the
+ * type of the topmost operand.
+ * In the first form, where the top element on the operand
+ * stack is a nonnegative integer n, copy pops n from the
+ * stack and duplicates the top n elements on the stack
+ * as shown above. This form of copy operates only on the
+ * objects themselves, not on the values of composite objects.
+ *
+ * In the other forms, copy copies all the elements of the
+ * first composite object into the second. The composite
+ * object operands must be of the same type, except that
+ * a packed array can be copied into an array (and only into
+ * an array—copy cannot copy into packed arrays, because
+ * they are read-only). This form of copy copies the value of
+ * a composite object. This is quite different from dup and
+ * other operators that copy only the objects themselves
+ * (see Section 3.3.1, "Simple and Composite Objects").
+ * However, copy performs only one level of copying.
+ * It does not apply recursively to elements that are
+ * themselves composite objects; instead, the values
+ * of those elements become shared. In the case of arrays or
+ * strings, the length of the second object must be at least as
+ * great as the first; copy returns the initial subarray or
+ * substring of the second operand into which the elements
+ * were copied. Any remaining elements of array2 or
+ * string2 are unaffected.
+ *
+ * duplicates the top element on the operand stack.
+ * dup copies only the object; the value of a composite
+ * object is not copied but is shared.
+ * See Section 3.3, "Data Types and Objects."
+ *
+ * errors: stackoverflow, stackunderflow
+ */
+ void eval() {
+ Object obj = popObject();
+ pushObject(obj);
+ pushObject(obj);
+ }
+ });
+ operationSet.add(new Operation("exch") {
+
+ void eval() { // any1 any2exchany2 any1 - exchange top of stack
+ Object any1 = popObject();
+ Object any2 = popObject();
+ pushObject(any2);
+ pushObject(any1);
+ }
+ });
+ operationSet.add(new Operation("index") {
+
+ void eval() { // anyn ... any0 nindexanyn ... any0 anyn
+ Object obj = stack.removeFirst();
+ stack.addFirst(obj);
+ stack.addFirst(obj);
+ }
+ });
+ operationSet.add(new Operation("pop") {
+
+ void eval() { // discard top element
+ stack.removeFirst();
+ }
+ });
+ operationSet.add(new Operation("roll") {
+
+ void eval() {
+ // anyn-1 ... any0 n jrollany(j-1)mod n ... anyn-1 ... any
+ // Roll n elements up j times
+ Object obj = stack.removeFirst();
+ stack.addFirst(obj);
+ stack.addFirst(obj);
+ }
+ });
+ }
+ }
+
+ /** Read the function information from a PDF Object */
+ protected void parse(PDFObject obj) throws IOException {
+ // read the postscript from the stream
+ readPS(obj.getStreamBuffer());
+ throw new PDFParseException("Unsupported function type 4.");
+ }
+
+ /**
+ * Map from m input values to n output values.
+ * The number of inputs m must be exactly one half the size of the
+ * domain. The number of outputs should match one half the size of the
+ * range.
+ *
+ * @param inputs an array of m input values
+ * @param outputs an array of size n which will be filled
+ * with the output values, or null to return a new array
+ */
+ protected void doFunction(float[] inputs, int inputOffset,
+ float[] outputs, int outputOffset) {
+ }
+
+ private boolean popBoolean() {
+ return false;
+ }
+
+ private void pushBoolean(boolean arg) {
+ }
+
+ private double popDouble() {
+ return 0;
+ }
+
+ private void pushDouble(double arg) {
+ }
+
+ private Expression popExpression() {
+ return null;
+ }
+
+ private void pushExpression(Expression expresson) {
+ }
+
+ private long popLong() {
+ return 0L;
+ }
+
+ private void pushLong(long arg) {
+ }
+
+ private Object popObject() {
+ return stack.removeFirst();
+ }
+
+ private void pushObject(Object obj) {
+ stack.addFirst(obj);
+ }
+
+ /**
+ *
parse the postscript operators and aguments from the stream.
+ *
+ *
Syntax is to read a set of tokens, including expressions and
+ * to queue them as they come including other expressions. Expressions are
+ * enclosed in curly brackets and constitute a reference to the
+ * expression body.
+ *
+ * @param buf the stream of postscript tokens
+ */
+ private void readPS(ByteBuffer buf) {
+ }
+
+ class Expression extends LinkedList {
+
+ public boolean equals(Object obj) {
+ if (obj instanceof Expression) {
+ // actually validate the list contents are the same expressions
+ return true;
+ }
+ return false;
+ }
+ }
+
+ abstract class Operation {
+
+ private String operatorName;
+
+ public Operation(String operatorName) {
+ if (operatorName == null) {
+ throw new RuntimeException("Cannot have a null operator name");
+ }
+ this.operatorName = operatorName;
+ }
+
+ public String getOperatorName() {
+ return operatorName;
+ }
+
+ /**
+ * evaluate the function, popping the stack as needed and pushing results.
+ */
+ abstract void eval();
+
+ /**
+ * return true if our operator is the same as the supplied one.
+ *
+ * @param obj
+ * @return
+ */
+ public boolean equals(Object obj) {
+ if (obj instanceof Operation) {
+ return ((Operation) obj).operatorName.equals(operatorName);
+ } else if (obj instanceof String) {
+ return operatorName.equals(obj);
+ }
+ return false;
+ }
+ }
+}
+
+
diff --git a/PdfView/src/main/java/com/sun/pdfview/function/PDFFunction.java b/PdfView/src/main/java/com/sun/pdfview/function/PDFFunction.java
new file mode 100644
index 0000000..65f5c88
--- /dev/null
+++ b/PdfView/src/main/java/com/sun/pdfview/function/PDFFunction.java
@@ -0,0 +1,328 @@
+/*
+ * $Id: PDFFunction.java,v 1.6 2009/03/15 12:33:17 tomoke 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.function;
+
+import com.sun.pdfview.PDFObject;
+import com.sun.pdfview.PDFParseException;
+
+import java.io.IOException;
+
+/**
+ *
PDF Functions are defined in the reference as Section 3.9.
+ *
+ *
A PDF function maps some set of m inputs into some set
+ * of n outputs. There are 4 types of functions:
+ *
Type 0: Sampled functions. (PDF 1.2)
+ * A sampled function (type 0) uses a table of sample values
+ * to define the function. Various techniques are used to
+ * interpolate values between the sample values
+ * (see Section 3.9.1, "Type 0 (Sampled) Functions").
+ *
Type 2: Exponential Interpolation. (PDF 1.3)
+ * An exponential interpolation function (type 2)
+ * defines a set of coefficients for an exponential function
+ * (see Section 3.9.2,
+ * "Type 2 (Exponential Interpolation) Functions").
+ *
Type 3: Stitching functions. (PDF 1.3)
+ * A stitching function (type 3) is a combination of
+ * other functions, partitioned across a domain
+ * (see Section 3.9.3, "Type 3 (Stitching) Functions").
+ *
Type 4: Postscript calculations. (PDF 1.3)
+ * A PostScript calculator function (type 4) uses operators
+ * from the PostScript language to describe an arithmetic
+ * expression (see Section 3.9.4,
+ * "Type 4 (PostScript Calculator) Functions").
+ *
+ *
+ *
+ *
+ * The function interface contains a single method, calculate which
+ * takes an array of m floats an interprets them into an array of
+ * n floats.
+ *
+ * PDFFunctions do not have accessible constructors. Instead, use the
+ * static getFunction() method to read a functions from a PDF Object.
+ *
+ */
+public abstract class PDFFunction {
+
+ /** Sampled function */
+ public static final int TYPE_0 = 0;
+
+ /** Exponential interpolation function */
+ public static final int TYPE_2 = 2;
+
+ /** Stitching function. */
+ public static final int TYPE_3 = 3;
+
+ /** PostScript calculator function. */
+ public static final int TYPE_4 = 4;
+
+ /** the type of this function from the list of known types */
+ private int type;
+
+ /** the input domain of this function, an array of 2 * m floats */
+ private float[] domain;
+
+ /** the output range of this functions, and array of 2 * n floats.
+ * required for type 0 and 4 functions
+ */
+ private float[] range;
+
+ /** Creates a new instance of PDFFunction */
+ protected PDFFunction (int type) {
+ this.type = type;
+ }
+
+ /**
+ * Get a PDFFunction from a PDFObject
+ */
+ public static PDFFunction getFunction (PDFObject obj)
+ throws IOException {
+ PDFFunction function;
+ int type;
+ float[] domain = null;
+ float[] range = null;
+
+ // read the function type (required)
+ PDFObject typeObj = obj.getDictRef ("FunctionType");
+ if (typeObj == null) {
+ throw new PDFParseException (
+ "No FunctionType specified in function!");
+ }
+ type = typeObj.getIntValue ();
+
+ // read the function's domain (required)
+ PDFObject domainObj = obj.getDictRef ("Domain");
+ if (domainObj == null) {
+ throw new PDFParseException ("No Domain specified in function!");
+ }
+
+ PDFObject[] domainAry = domainObj.getArray ();
+ domain = new float[domainAry.length];
+ for (int i = 0; i < domainAry.length; i++) {
+ domain[i] = domainAry[i].getFloatValue ();
+ }
+
+ // read the function's range (optional)
+ PDFObject rangeObj = obj.getDictRef ("Range");
+ if (rangeObj != null) {
+ PDFObject[] rangeAry = rangeObj.getArray ();
+ range = new float[rangeAry.length];
+ for (int i = 0; i < rangeAry.length; i++) {
+ range[i] = rangeAry[i].getFloatValue ();
+ }
+ }
+
+ // now create the acual function object
+ switch (type) {
+ case TYPE_0:
+ if (rangeObj == null) {
+ throw new PDFParseException (
+ "No Range specified in Type 0 Function!");
+ }
+ function = new FunctionType0 ();
+ break;
+ case TYPE_2:
+ function = new FunctionType2 ();
+ break;
+ case TYPE_3:
+ function = new FunctionType3 ();
+ break;
+ case TYPE_4:
+ if (rangeObj == null) {
+ throw new PDFParseException (
+ "No Range specified in Type 4 Function!");
+ }
+ function = new FunctionType4 ();
+ break;
+ default:
+ throw new PDFParseException (
+ "Unsupported function type: " + type);
+ }
+
+ // fill in the domain and optionally the range
+ function.setDomain (domain);
+ if (range != null) {
+ function.setRange (range);
+ }
+
+ // now initialize the function
+ function.parse (obj);
+
+ return function;
+ }
+
+ /**
+ * Get the type of this function
+ *
+ * @return one of the types of function (0-4)
+ */
+ public int getType () {
+ return type;
+ }
+
+ /**
+ * Get the number of inputs, m, required by this function
+ *
+ * @return the number of input values expected by this function
+ */
+ public int getNumInputs () {
+ return (domain.length / 2);
+ }
+
+ /**
+ * Get the number of outputs, n, returned by this function
+ *
+ * @return the number of output values this function will return
+ */
+ public int getNumOutputs () {
+ if (range == null) {
+ return 0;
+ }
+ return (range.length / 2);
+ }
+
+ /**
+ * Get a component of the domain of this function
+ *
+ * @param i the index into the domain array, which has size 2 * m.
+ * the ith entry in the array has index 2i,
+ * 2i + 1
+ * @return the ith entry in the domain array
+ */
+ protected float getDomain (int i) {
+ return domain[i];
+ }
+
+ /**
+ * Set the domain of this function
+ */
+ protected void setDomain (float[] domain) {
+ this.domain = domain;
+ }
+
+ /**
+ * Get a component of the range of this function
+ *
+ * @param i the index into the range array, which has size 2 * n.
+ * the ith entry in the array has index 2i,
+ * 2i + 1
+ * @return the ith entry in the range array
+ */
+ protected float getRange (int i) {
+ if (range == null) {
+ if ((i % 2) == 0) {
+ return Float.MIN_VALUE;
+ } else {
+ return Float.MAX_VALUE;
+ }
+ }
+ return range[i];
+ }
+
+ /**
+ * Set the range of this function
+ */
+ protected void setRange (float[] range) {
+ this.range = range;
+ }
+
+ /**
+ * Map from m input values to n output values.
+ * The number of inputs m must be exactly one half the size of the
+ * domain. The number of outputs should match one half the size of the
+ * range.
+ *
+ * @param inputs an array of >= m input values
+ * @return the array of n output values
+ */
+ public float[] calculate (float[] inputs) {
+ float[] outputs = new float[getNumOutputs ()];
+ calculate (inputs, 0, outputs, 0);
+ return outputs;
+ }
+
+ /**
+ * Map from m input values to n output values.
+ * The number of inputs m must be exactly one half the size of the
+ * domain. The number of outputs should match one half the size of the
+ * range.
+ *
+ * @param inputs an array of >= m input values
+ * @param inputOffset the offset into the input array to read from
+ * @param outputs an array of size >= n which will be filled
+ * with the output values
+ * @param outputOffset the offset into the output array to write to
+ * @return the array of n output values
+ */
+ public float[] calculate (float[] inputs, int inputOffset,
+ float[] outputs, int outputOffset) {
+ // check the inputs
+ if (inputs.length - inputOffset < getNumInputs ()) {
+ throw new IllegalArgumentException (
+ "Wrong number of inputs to function!");
+ }
+
+ // check the outputs
+ if (range != null && outputs.length - outputOffset < getNumOutputs ()) {
+ throw new IllegalArgumentException (
+ "Wrong number of outputs for function!");
+ }
+
+ // clip the inputs to domain
+ for (int i = 0; i < inputs.length; i++) {
+ // clip to the domain -- min(max(x, domain<2i>), domain<2i+1>)
+ inputs[i] = Math.max (inputs[i], getDomain (2 * i));
+ inputs[i] = Math.min (inputs[i], getDomain ((2 * i) + 1));
+ }
+
+ // do the actual calculation
+ doFunction (inputs, inputOffset, outputs, outputOffset);
+
+ // clip the outputs to range
+ for (int i = 0; range != null && i < outputs.length; i++) {
+ // clip to range -- min(max(r, range<2i>), range<2i + 1>)
+ outputs[i] = Math.max (outputs[i], getRange (2 * i));
+ outputs[i] = Math.min (outputs[i], getRange ((2 * i) + 1));
+ }
+
+ return outputs;
+ }
+
+ /**
+ * Subclasses must implement this method to perform the actual function
+ * on the given set of data. Note that the inputs are guaranteed to be
+ * clipped to the domain, while the outputs will be automatically clipped
+ * to the range after being returned from this function.
+ *
+ * @param inputs guaranteed to be at least as big as
+ * getNumInputs() and all values within range
+ * @param inputOffset the offset into the inputs array to read from
+ * @param outputs guaranteed to be at least as big as
+ * getNumOutputs(), but not yet clipped to domain
+ * @param outputOffset the offset into the output array to write to
+ */
+ protected abstract void doFunction (float[] inputs, int inputOffset,
+ float[] outputs, int outputOffset);
+
+ /** Read the function information from a PDF Object */
+ protected abstract void parse (PDFObject obj) throws IOException;
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/crypto/Cipher.java b/PdfView/src/main/java/net/sf/andpdf/crypto/Cipher.java
new file mode 100644
index 0000000..a4432d3
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/crypto/Cipher.java
@@ -0,0 +1,38 @@
+package net.sf.andpdf.crypto;
+
+import java.nio.ByteBuffer;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public abstract class Cipher {
+
+ public static final int ENCRYPT_MODE = javax.crypto.Cipher.ENCRYPT_MODE;
+ public static final int DECRYPT_MODE = javax.crypto.Cipher.DECRYPT_MODE;
+
+ public static Cipher getInstance(String cipher) throws NoSuchAlgorithmException, NoSuchPaddingException {
+ if (cipher.equals("RC4"))
+ return new RC4Cipher();
+ return new CryptoCipher(cipher);
+ }
+
+ public abstract void init(int mode, Key key);
+ public abstract void init(int mode, SecretKey key);
+ public abstract void init(int mode, SecretKeySpec keySpec);
+ public abstract void init(int mode, SecretKeySpec keySpec, IvParameterSpec Iv);
+
+ public abstract void doFinal(ByteBuffer nio, ByteBuffer decryptedBuf) throws IllegalBlockSizeException, ShortBufferException, BadPaddingException;
+ public abstract byte[] doFinal(byte[] input) throws IllegalBlockSizeException, BadPaddingException;
+ public abstract void doFinal(byte[] src, int from, int length, byte[] dest) throws IllegalBlockSizeException, ShortBufferException, BadPaddingException;
+
+
+
+
+}
\ No newline at end of file
diff --git a/PdfView/src/main/java/net/sf/andpdf/crypto/CryptoCipher.java b/PdfView/src/main/java/net/sf/andpdf/crypto/CryptoCipher.java
new file mode 100644
index 0000000..6da1887
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/crypto/CryptoCipher.java
@@ -0,0 +1,59 @@
+package net.sf.andpdf.crypto;
+
+import java.nio.ByteBuffer;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class CryptoCipher extends Cipher {
+
+ javax.crypto.Cipher cipher;
+
+ public CryptoCipher(String ciphername) throws NoSuchAlgorithmException, NoSuchPaddingException {
+ this.cipher = javax.crypto.Cipher.getInstance(ciphername);
+
+ }
+
+ @Override
+ public void doFinal(ByteBuffer nio, ByteBuffer decryptedBuf) throws IllegalBlockSizeException, ShortBufferException, BadPaddingException {
+ cipher.doFinal(nio, decryptedBuf);
+ }
+
+ @Override
+ public byte[] doFinal(byte[] input) throws IllegalBlockSizeException, BadPaddingException {
+ return cipher.doFinal(input);
+ }
+
+ @Override
+ public void doFinal(byte[] src, int from, int length, byte[] dest) throws IllegalBlockSizeException, ShortBufferException, BadPaddingException {
+ cipher.doFinal(src, from, length, dest);
+ }
+
+ @Override
+ public void init(int mode, Key key) {
+ init(mode, key);
+ }
+
+ @Override
+ public void init(int mode, SecretKey key) {
+ init(mode, key);
+ }
+
+ @Override
+ public void init(int mode, SecretKeySpec keySpec) {
+ init(mode, keySpec);
+ }
+
+ @Override
+ public void init(int mode, SecretKeySpec keySpec, IvParameterSpec Iv) {
+ init(mode, keySpec, Iv);
+ }
+
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/crypto/RC4Cipher.java b/PdfView/src/main/java/net/sf/andpdf/crypto/RC4Cipher.java
new file mode 100644
index 0000000..e4e19d6
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/crypto/RC4Cipher.java
@@ -0,0 +1,58 @@
+package net.sf.andpdf.crypto;
+
+import java.nio.ByteBuffer;
+import java.security.Key;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.crypto.engines.RC4Engine;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+public class RC4Cipher extends Cipher {
+
+ private RC4Engine rc4;
+
+ @Override
+ public void doFinal(ByteBuffer nio, ByteBuffer decryptedBuf) {
+ while (nio.hasRemaining())
+ decryptedBuf.put(rc4.returnByte(nio.get()));
+ }
+
+ @Override
+ public byte[] doFinal(byte[] input) {
+ byte[] result = new byte[input.length];
+ rc4.processBytes(input, 0, input.length, result, 0);
+ return result;
+ }
+
+ @Override
+ public void doFinal(byte[] src, int from, int length, byte[] dest) {
+ rc4.processBytes(src, from, length, dest, 0);
+ }
+
+ @Override
+ public void init(int mode, Key key) {
+ rc4 = new RC4Engine();
+ rc4.init(mode == Cipher.ENCRYPT_MODE, new KeyParameter(key.getEncoded()));
+ }
+
+ @Override
+ public void init(int mode, SecretKey key) {
+ rc4 = new RC4Engine();
+ rc4.init(mode == Cipher.ENCRYPT_MODE, new KeyParameter(key.getEncoded()));
+ }
+
+ @Override
+ public void init(int mode, SecretKeySpec keySpec) {
+ rc4 = new RC4Engine();
+ rc4.init(mode == Cipher.ENCRYPT_MODE, new KeyParameter(keySpec.getEncoded()));
+ }
+
+ @Override
+ public void init(int mode, SecretKeySpec keySpec, IvParameterSpec Iv) {
+ throw new RuntimeException("not yet supported");
+ }
+
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/nio/ArrayBackedByteBuffer.java b/PdfView/src/main/java/net/sf/andpdf/nio/ArrayBackedByteBuffer.java
new file mode 100644
index 0000000..716f952
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/nio/ArrayBackedByteBuffer.java
@@ -0,0 +1,357 @@
+package net.sf.andpdf.nio;
+
+/**
+ * A byte-array backed ByteBuffer
+ *
+ * @author Ferenc Hechler (ferenc@hechler.de)
+ * @author Joerg Jahnke (joergjahnke@users.sourceforge.net)
+ */
+public final class ArrayBackedByteBuffer extends ByteBuffer {
+
+ /**
+ * the byte array backing this buffer
+ */
+ private final byte[] buf;
+ /**
+ * offset to the first byte to use
+ */
+ private int ofs;
+ /**
+ * current buffer position
+ */
+ private int pos;
+ /**
+ * buffer size
+ */
+ private int siz;
+ /**
+ * a marker inside the buffer
+ */
+ private int mrk;
+
+ /**
+ * Create a new ByteBuffer from an existing array
+ *
+ * @param buf the array backing this buffer
+ */
+ public ArrayBackedByteBuffer(final byte[] buf) {
+ this(buf, 0, 0, (buf == null ? 0 : buf.length));
+ }
+
+ /**
+ * Create a new ByteBuffer from an existing array
+ *
+ * @param buf the array backing this buffer
+ * @param ofs offset to the first byte to use
+ * @param pos current buffer position
+ * @param siz total buffer size
+ */
+ public ArrayBackedByteBuffer(final byte[] buf, final int ofs, final int pos, final int siz) {
+ this.buf = buf;
+ this.ofs = ofs;
+ this.pos = pos;
+ this.siz = siz;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#position()
+ */
+ public int position() {
+ return pos - ofs;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#position(int)
+ */
+ public void position(final int position) {
+ // TODO: check range 0..length-1
+ pos = position + ofs;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#get()
+ */
+ public byte get() {
+ // TODO: check range
+ return buf[pos++];
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#remaining()
+ */
+ public int remaining() {
+ return siz - pos;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#get(int)
+ */
+ public byte get(final int position) {
+ return buf[position + ofs];
+ }
+
+ public static ByteBuffer allocate(final int size) {
+ return new ArrayBackedByteBuffer(new byte[size]);
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#slice()
+ */
+ public ByteBuffer slice() {
+ return new ArrayBackedByteBuffer(buf, pos, pos, siz);
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#limit(int)
+ */
+ public void limit(final int length) {
+ // TODO: check range 0..buf.length
+ siz = ofs + length;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#get(byte[])
+ */
+ public void get(final byte[] outBuf) {
+ System.arraycopy(buf, pos, outBuf, 0, outBuf.length);
+ pos += outBuf.length;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#rewind()
+ */
+ public void rewind() {
+ pos = ofs;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#limit()
+ */
+ public int limit() {
+ return siz - ofs;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#hasArray()
+ */
+ public boolean hasArray() {
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#arrayOffset()
+ */
+ public int arrayOffset() {
+ return ofs;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#array()
+ */
+ public byte[] array() {
+ return buf;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#flip()
+ */
+ public void flip() {
+ siz = pos;
+ pos = ofs;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#duplicate()
+ */
+ public ByteBuffer duplicate() {
+ return new ArrayBackedByteBuffer(buf, ofs, pos, siz);
+ }
+
+ public static ByteBuffer wrap(final byte[] bytes) {
+ return new ArrayBackedByteBuffer(bytes);
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#getChar(int)
+ */
+ public char getChar(final int position) {
+ // TODO: check current byteorder, assume BIG_ENDIAN
+ int result = get(position) & 0xff;
+ result = (result << 8) + (get(position + 1) & 0xff);
+ return (char) result;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#getInt()
+ */
+ public int getInt() {
+ // TODO: check current byteorder, assume BIG_ENDIAN
+ final int pos_ = this.pos;
+ final byte[] buf_ = this.buf;
+ final int result = ((buf_[pos_] & 0xff) << 24) + ((buf_[pos_ + 1] & 0xff) << 16) + ((buf_[pos_ + 2] & 0xff) << 8) + (buf_[pos_ + 3] & 0xff);
+
+ this.pos += 4;
+
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#getLong()
+ */
+ public long getLong() {
+ // TODO: check current byteorder, assume BIG_ENDIAN
+ long result = get() & 0xff;
+ result = (result << 8) + (get() & 0xff);
+ result = (result << 8) + (get() & 0xff);
+ result = (result << 8) + (get() & 0xff);
+ result = (result << 8) + (get() & 0xff);
+ result = (result << 8) + (get() & 0xff);
+ result = (result << 8) + (get() & 0xff);
+ result = (result << 8) + (get() & 0xff);
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#getChar()
+ */
+ public char getChar() {
+ // TODO: check current byteorder, assume BIG_ENDIAN
+ int result = get() & 0xff;
+ result = (result << 8) + (get() & 0xff);
+ return (char) result;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#getShort()
+ */
+ public short getShort() {
+ final int result = ((buf[pos] & 0xff) << 8) + (buf[pos + 1] & 0xff);
+
+ pos += 2;
+
+ return (short)result;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#put(int, byte)
+ */
+ public void put(final int index, final byte b) {
+ buf[index + ofs] = b;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#put(byte)
+ */
+ public void put(final byte b) {
+ buf[pos++] = b;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#putInt(int)
+ */
+ public void putInt(final int i) {
+ put((byte) ((i >> 24) & 0xff));
+ put((byte) ((i >> 16) & 0xff));
+ put((byte) ((i >> 8) & 0xff));
+ put((byte) (i & 0xff));
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#putShort(short)
+ */
+ public void putShort(final short s) {
+ put((byte) ((s >> 8) & 0xff));
+ put((byte) (s & 0xff));
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#mark()
+ */
+ public void mark() {
+ mrk = pos;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#put(net.sf.andpdf.pdfviewer.ByteBuffer)
+ */
+ public void put(final ByteBuffer data) {
+ int len = data.remaining();
+ System.arraycopy(data.array(), data.position() + data.arrayOffset(), buf, pos, len);
+ pos += len;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#reset()
+ */
+ public void reset() {
+ // TODO: check for mark set
+ pos = mrk;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#putInt(int, int)
+ */
+ public void putInt(final int index, final int value) {
+ put(index, (byte) ((value >> 24) & 0xff));
+ put(index + 1, (byte) ((value >> 16) & 0xff));
+ put(index + 2, (byte) ((value >> 8) & 0xff));
+ put(index + 3, (byte) (value & 0xff));
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#putLong(long)
+ */
+ public void putLong(final long value) {
+ put((byte) ((value >> 56) & 0xff));
+ put((byte) ((value >> 48) & 0xff));
+ put((byte) ((value >> 40) & 0xff));
+ put((byte) ((value >> 32) & 0xff));
+ put((byte) ((value >> 24) & 0xff));
+ put((byte) ((value >> 16) & 0xff));
+ put((byte) ((value >> 8) & 0xff));
+ put((byte) (value & 0xff));
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#putChar(char)
+ */
+ public void putChar(final char value) {
+ put((byte) ((value >> 8) & 0xff));
+ put((byte) (value & 0xff));
+ }
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#put(byte[])
+ */
+
+ public void put(final byte[] data) {
+ int len = data.length;
+ System.arraycopy(data, 0, buf, pos, len);
+ pos += len;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#get(byte[], int, int)
+ */
+ public void get(final byte[] outBuf, final int outOffset, final int length) {
+ System.arraycopy(buf, pos, outBuf, outOffset, length);
+ pos += length;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#toNIO()
+ */
+ public java.nio.ByteBuffer toNIO() {
+ return java.nio.ByteBuffer.wrap(buf, pos, siz - pos);
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.andpdf.pdfviewer.ByteBuffer#hasRemaining()
+ */
+ public boolean hasRemaining() {
+ return pos < siz;
+ }
+
+ public static ByteBuffer fromNIO(final java.nio.ByteBuffer nioBuf) {
+ return new ArrayBackedByteBuffer(nioBuf.array(), nioBuf.arrayOffset(), nioBuf.arrayOffset() + nioBuf.position(), nioBuf.arrayOffset() + nioBuf.position() + nioBuf.remaining());
+ }
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/nio/ByteBuffer.java b/PdfView/src/main/java/net/sf/andpdf/nio/ByteBuffer.java
new file mode 100644
index 0000000..da1b8a9
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/nio/ByteBuffer.java
@@ -0,0 +1,102 @@
+package net.sf.andpdf.nio;
+
+import java.nio.MappedByteBuffer;
+
+public abstract class ByteBuffer {
+
+ public abstract int position();
+
+ public abstract void position(int position);
+
+ public abstract byte get();
+
+ public abstract int remaining();
+
+ public abstract byte get(int position);
+
+ public abstract ByteBuffer slice();
+
+ public abstract void limit(int length);
+
+ public abstract void get(byte[] outBuf);
+
+ public abstract void rewind();
+
+ public abstract int limit();
+
+ public abstract boolean hasArray();
+
+ public abstract int arrayOffset();
+
+ public abstract byte[] array();
+
+ public abstract void flip();
+
+ public abstract ByteBuffer duplicate();
+
+ public abstract char getChar(int position);
+
+ public abstract int getInt();
+
+ public abstract long getLong();
+
+ public abstract char getChar();
+
+ public abstract short getShort();
+
+ public abstract void put(int index, byte b);
+
+ public abstract void put(byte b);
+
+ public abstract void putInt(int i);
+
+ public abstract void putShort(short s);
+
+ public abstract void mark();
+
+ public abstract void put(ByteBuffer data);
+
+ public abstract void reset();
+
+ public abstract void putInt(int index, int value);
+
+ public abstract void putLong(long value);
+
+ public abstract void putChar(char value);
+
+ public abstract void put(byte[] data);
+
+ public abstract void get(byte[] outBuf, int outOffset, int length);
+
+ public abstract java.nio.ByteBuffer toNIO();
+
+ public abstract boolean hasRemaining();
+
+
+ public static boolean sUseNIO = true;
+ public static ByteBuffer NEW(MappedByteBuffer map) {
+ return new NioByteBuffer(map);
+ }
+ public static ByteBuffer NEW(byte[] buf) {
+ return new ArrayBackedByteBuffer(buf);
+ }
+ public static ByteBuffer wrap(byte[] decode) {
+ if (sUseNIO)
+ return NioByteBuffer.wrap(decode);
+ else
+ return ArrayBackedByteBuffer.wrap(decode);
+ }
+ public static ByteBuffer fromNIO(java.nio.ByteBuffer byteBuf) {
+// if (USE_NIO)
+ return NioByteBuffer.fromNIO(byteBuf);
+// else
+// return OwnByteBuffer.fromNIO(byteBuf);
+ }
+ public static ByteBuffer allocate(int i) {
+ if (sUseNIO)
+ return NioByteBuffer.allocate(i);
+ else
+ return ArrayBackedByteBuffer.allocate(i);
+ }
+
+}
\ No newline at end of file
diff --git a/PdfView/src/main/java/net/sf/andpdf/nio/NioByteBuffer.java b/PdfView/src/main/java/net/sf/andpdf/nio/NioByteBuffer.java
new file mode 100644
index 0000000..fb21459
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/nio/NioByteBuffer.java
@@ -0,0 +1,322 @@
+package net.sf.andpdf.nio;
+
+import java.nio.Buffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A wrapper for the java.nio.ByteBuffer class
+ *
+ * @author Ferenc Hechler (ferenc@hechler.de)
+ * @author Joerg Jahnke (joergjahnke@users.sourceforge.net)
+ */
+public final class NioByteBuffer extends ByteBuffer {
+
+ /**
+ * the underlying buffer
+ */
+ private java.nio.ByteBuffer nioBuf;
+
+ public byte[] array() {
+ return nioBuf.array();
+ }
+
+ public int arrayOffset() {
+ return nioBuf.arrayOffset();
+ }
+
+ public CharBuffer asCharBuffer() {
+ return nioBuf.asCharBuffer();
+ }
+
+ public DoubleBuffer asDoubleBuffer() {
+ return nioBuf.asDoubleBuffer();
+ }
+
+ public FloatBuffer asFloatBuffer() {
+ return nioBuf.asFloatBuffer();
+ }
+
+ public IntBuffer asIntBuffer() {
+ return nioBuf.asIntBuffer();
+ }
+
+ public LongBuffer asLongBuffer() {
+ return nioBuf.asLongBuffer();
+ }
+
+ public java.nio.ByteBuffer asReadOnlyBuffer() {
+ return nioBuf.asReadOnlyBuffer();
+ }
+
+ public ShortBuffer asShortBuffer() {
+ return nioBuf.asShortBuffer();
+ }
+
+ public int capacity() {
+ return nioBuf.capacity();
+ }
+
+ public Buffer clear() {
+ return nioBuf.clear();
+ }
+
+ public java.nio.ByteBuffer compact() {
+ return nioBuf.compact();
+ }
+
+ public int compareTo(final java.nio.ByteBuffer otherBuffer) {
+ return nioBuf.compareTo(otherBuffer);
+ }
+
+ public NioByteBuffer duplicate() {
+ return new NioByteBuffer(nioBuf.duplicate());
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ return nioBuf.equals(other);
+ }
+
+ public void flip() {
+ nioBuf.flip();
+ }
+
+ public byte get() {
+ return nioBuf.get();
+ }
+
+ public void get(final byte[] dest, final int off, final int len) {
+ nioBuf.get(dest, off, len);
+ }
+
+ public void get(final byte[] dest) {
+ nioBuf.get(dest);
+ }
+
+ public byte get(final int index) {
+ return nioBuf.get(index);
+ }
+
+ public char getChar() {
+ return nioBuf.getChar();
+ }
+
+ public char getChar(final int index) {
+ return nioBuf.getChar(index);
+ }
+
+ public double getDouble() {
+ return nioBuf.getDouble();
+ }
+
+ public double getDouble(final int index) {
+ return nioBuf.getDouble(index);
+ }
+
+ public float getFloat() {
+ return nioBuf.getFloat();
+ }
+
+ public float getFloat(final int index) {
+ return nioBuf.getFloat(index);
+ }
+
+ public int getInt() {
+ return nioBuf.getInt();
+ }
+
+ public int getInt(final int index) {
+ return nioBuf.getInt(index);
+ }
+
+ public long getLong() {
+ return nioBuf.getLong();
+ }
+
+ public long getLong(final int index) {
+ return nioBuf.getLong(index);
+ }
+
+ public short getShort() {
+ return nioBuf.getShort();
+ }
+
+ public short getShort(final int index) {
+ return nioBuf.getShort(index);
+ }
+
+ public boolean hasArray() {
+ return nioBuf.hasArray();
+ }
+
+ @Override
+ public int hashCode() {
+ return nioBuf.hashCode();
+ }
+
+ public boolean hasRemaining() {
+ return nioBuf.hasRemaining();
+ }
+
+ public boolean isDirect() {
+ return nioBuf.isDirect();
+ }
+
+ public boolean isReadOnly() {
+ return nioBuf.isReadOnly();
+ }
+
+ public int limit() {
+ return nioBuf.limit();
+ }
+
+ public void limit(final int newLimit) {
+ nioBuf.limit(newLimit);
+ }
+
+ public void mark() {
+ nioBuf.mark();
+ }
+
+ public ByteOrder order() {
+ return nioBuf.order();
+ }
+
+ public java.nio.ByteBuffer order(final ByteOrder byteOrder) {
+ return nioBuf.order(byteOrder);
+ }
+
+ public int position() {
+ return nioBuf.position();
+ }
+
+ public void position(final int newPosition) {
+ nioBuf.position(newPosition);
+ }
+
+ public void put(final byte b) {
+ nioBuf.put(b);
+ }
+
+ public NioByteBuffer put(final byte[] src, final int off, final int len) {
+ nioBuf.put(src, off, len);
+ return this;
+ }
+
+ public void put(final byte[] src) {
+ nioBuf.put(src);
+ }
+
+ public ByteBuffer put(final java.nio.ByteBuffer src) {
+ nioBuf.put(src);
+ return this;
+ }
+
+ public void put(final ByteBuffer src) {
+ nioBuf.put(src.toNIO());
+ }
+
+ public void put(final int index, final byte b) {
+ nioBuf.put(index, b);
+ }
+
+ public void putChar(final char value) {
+ nioBuf.putChar(value);
+ }
+
+ public NioByteBuffer putChar(final int index, final char value) {
+ nioBuf.putChar(index, value);
+ return this;
+ }
+
+ public NioByteBuffer putDouble(final double value) {
+ nioBuf.putDouble(value);
+ return this;
+ }
+
+ public NioByteBuffer putDouble(final int index, final double value) {
+ nioBuf.putDouble(index, value);
+ return this;
+ }
+
+ public NioByteBuffer putFloat(final float value) {
+ nioBuf.putFloat(value);
+ return this;
+ }
+
+ public NioByteBuffer putFloat(final int index, final float value) {
+ nioBuf.putFloat(index, value);
+ return this;
+ }
+
+ public void putInt(final int index, final int value) {
+ nioBuf.putInt(index, value);
+ }
+
+ public void putInt(final int value) {
+ nioBuf.putInt(value);
+ }
+
+ public void putLong(final int index, final long value) {
+ nioBuf.putLong(index, value);
+ }
+
+ public void putLong(final long value) {
+ nioBuf.putLong(value);
+ }
+
+ public void putShort(final int index, final short value) {
+ nioBuf.putShort(index, value);
+ }
+
+ public void putShort(final short value) {
+ nioBuf.putShort(value);
+ }
+
+ public int remaining() {
+ return nioBuf.remaining();
+ }
+
+ public void reset() {
+ nioBuf.reset();
+ }
+
+ public void rewind() {
+ nioBuf.rewind();
+ }
+
+ public NioByteBuffer slice() {
+ return new NioByteBuffer(nioBuf.slice());
+ }
+
+ @Override
+ public String toString() {
+ return nioBuf.toString();
+ }
+
+ public NioByteBuffer(final java.nio.ByteBuffer nioBuf) {
+ this.nioBuf = nioBuf;
+ }
+
+ public java.nio.ByteBuffer toNIO() {
+ return nioBuf;
+ }
+
+ public static NioByteBuffer fromNIO(final java.nio.ByteBuffer nioBuf) {
+ return new NioByteBuffer(nioBuf);
+ }
+
+ public static NioByteBuffer allocate(final int i) {
+ return new NioByteBuffer(java.nio.ByteBuffer.allocate(i));
+ }
+
+ public static NioByteBuffer wrap(final byte[] bytes) {
+ return new NioByteBuffer(java.nio.ByteBuffer.wrap(bytes));
+ }
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/pdfviewer/PdfViewerActivity.java b/PdfView/src/main/java/net/sf/andpdf/pdfviewer/PdfViewerActivity.java
new file mode 100644
index 0000000..951c217
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/pdfviewer/PdfViewerActivity.java
@@ -0,0 +1,871 @@
+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.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+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 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.pdfviewer.gui.PdfView;
+import net.sf.andpdf.refs.HardReference;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+
+/**
+ * U:\Android\android-sdk-windows-1.5_r1\tools\adb push u:\Android\simple_T.pdf /data/test.pdf
+ *
+ * @author ferenc.hechler
+ */
+public abstract 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_PDFFILENAME = "net.sf.andpdf.extra.PDFFILENAME";
+ 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 String EXTRA_KEEPCACHES = "net.sf.andpdf.extra.KEEPCACHES";
+
+ public static final boolean DEFAULTSHOWIMAGES = true;
+ public static final boolean DEFAULTANTIALIAS = true;
+ public static final boolean DEFAULTUSEFONTSUBSTITUTION = false;
+ public static final boolean DEFAULTKEEPCACHES = 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 String pdffilename;
+ private PDFFile mPdfFile;
+ private int mPage;
+ private float mZoom;
+ private File mTmpFile;
+ private ProgressDialog progress;
+
+ /*private View navigationPanel;
+ private Handler closeNavigationHandler;
+ private Thread closeNavigationThread;*/
+
+ private PDFPage mPdfPage;
+
+ private Thread backgroundThread;
+ private Handler uiHandler;
+
+ @Override public Object onRetainNonConfigurationInstance() {
+ // return a reference to the current instance
+ Log.e(TAG, "onRetainNonConfigurationInstance");
+ return this;
+ }
+
+ /**
+ * 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;
+ mTmpFile = inst.mTmpFile;
+ mZoom = inst.mZoom;
+ pdffilename = inst.pdffilename;
+ backgroundThread = inst.backgroundThread;
+ // mGraphView.invalidate();
+ }
+ return true;
+ }
+
+ public abstract String getFileName();
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ pdffilename = getFileName();
+ Log.i(TAG, "onCreate");
+ uiHandler = new Handler();
+ restoreInstance();
+ if (mOldGraphView != null) {
+ mGraphView = new GraphView(this);
+ mGraphView.mBi = mOldGraphView.mBi;
+ mOldGraphView = null;
+ mGraphView.mImageView.setImageBitmap(mGraphView.mBi);
+ // mGraphView.updateTexts();
+ setContentView(mGraphView);
+ } else {
+ mGraphView = new GraphView(this);
+ Intent intent = getIntent();
+ Log.i(TAG, "" + intent);
+
+ boolean showImages =
+ getIntent().getBooleanExtra(PdfViewerActivity.EXTRA_SHOWIMAGES, PdfViewerActivity.DEFAULTSHOWIMAGES);
+ PDFImage.sShowImages = showImages;
+ boolean antiAlias =
+ getIntent().getBooleanExtra(PdfViewerActivity.EXTRA_ANTIALIAS, PdfViewerActivity.DEFAULTANTIALIAS);
+ PDFPaint.s_doAntiAlias = antiAlias;
+ boolean useFontSubstitution = getIntent().getBooleanExtra(PdfViewerActivity.EXTRA_USEFONTSUBSTITUTION,
+ PdfViewerActivity.DEFAULTUSEFONTSUBSTITUTION);
+ PDFFont.sUseFontSubstitution = useFontSubstitution;
+ boolean keepCaches =
+ getIntent().getBooleanExtra(PdfViewerActivity.EXTRA_KEEPCACHES, PdfViewerActivity.DEFAULTKEEPCACHES);
+ HardReference.sKeepCaches = true;
+
+ if (intent != null && pdffilename == null) {
+ if ("android.intent.action.VIEW".equals(intent.getAction())) {
+ pdffilename = storeUriContentToFile(intent.getData());
+ } else {
+ pdffilename = getIntent().getStringExtra(PdfViewerActivity.EXTRA_PDFFILENAME);
+ }
+ }
+
+ if (pdffilename == null) pdffilename = "no file selected";
+
+ mPage = STARTPAGE;
+ mZoom = STARTZOOM;
+
+ setContent(null);
+ }
+ }
+
+ private void setContent(String password) {
+ try {
+ parsePDF(pdffilename, password);
+ pdfView.setmPdfFile(mPdfFile);
+ 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();
+ }
+ });
+ }
+ }
+
+ private synchronized void startRenderThread(final int page, final float zoom) {
+ if (backgroundThread != null) return;
+ // mGraphView.showText("reading page " + page + ", zoom:" + zoom);
+ //progress = ProgressDialog.show(PdfViewerActivity.this, "Loading", "Loading PDF Page");
+ 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() {
+ // Log.i(TAG, "updateImageStatus: " + (System.currentTimeMillis()&0xffff));
+ if (backgroundThread == null) {
+ // mGraphView.updateUi();
+
+ /*if (progress != null)
+ progress.dismiss();*/
+ return;
+ }
+ // mGraphView.updateUi();
+ mGraphView.postDelayed(new Runnable() {
+ public void run() {
+ updateImageStatus();
+
+ /*if (progress != null)
+ progress.dismiss();*/
+ }
+ }, 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);
+
+ //progress = ProgressDialog.show(PdfViewerActivity.this, "Rendering", "Rendering PDF Page");
+ 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);
+
+ //progress = ProgressDialog.show(PdfViewerActivity.this, "Rendering", "Rendering PDF Page");
+ 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;
+ }
+
+ //TODO
+ PdfView pdfView;
+
+ private class GraphView extends FullScrollView {
+ //private String mText;
+ //private long fileMillis;
+ //private long pageParseMillis;
+ //private long pageRenderMillis;
+ private Bitmap mBi;
+ //private String mLine1;
+ //private String mLine2;
+ //private String mLine3;
+ private ImageView mImageView;
+ //private TextView mLine1View;
+ //private TextView mLine2View;
+ //private TextView mLine3View;
+ private Button mBtPage;
+ private Button mBtPage2;
+
+ ImageButton bZoomOut;
+ ImageButton bZoomIn;
+
+ public GraphView(Context context) {
+ super(context);
+
+ //setContentView(R.layout.graphics_view);
+ // layout params
+ LinearLayout.LayoutParams lpWrap1 =
+ new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ LinearLayout.LayoutParams lpWrap10 =
+ new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+
+ LinearLayout.LayoutParams matchLp =
+ new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ // vertical layout
+ 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);
+ }
+ //TODO
+ pdfView = new PdfView(PdfViewerActivity.this);
+
+ addNavButtons(vl);
+ // remember page button for updates
+ mBtPage2 = mBtPage;
+
+ mImageView = new ImageView(context);
+ setPageBitmap(null);
+ updateImage();
+ mImageView.setLayoutParams(lpWrap1);
+ // mImageView.setPadding(5, 5, 5, 5);
+ vl.addView(mImageView);
+ vl.addView(pdfView);
+ //addView(pdfView);
+ pdfView.setLayoutParams(matchLp);
+
+ /*mImageView = (ImageView) findViewById(R.id.pdf_image);
+ if (mImageView == null) {
+ Log.i(TAG, "mImageView is null!!!!!!");
+ }
+ setPageBitmap(null);
+ updateImage();*/
+
+ /*
+ navigationPanel = new ViewStub(PdfViewerActivity.this, R.layout.navigation_overlay);
+ final ImageButton previous = (ImageButton)navigationPanel.findViewById(R.id.navigation_previous);
+ previous.setBackgroundDrawable(null);
+ previous.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ prevPage();
+ }
+ });
+
+ final ImageButton next = (ImageButton)navigationPanel.findViewById(R.id.navigation_next);
+ next.setBackgroundDrawable(null);
+ previous.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ nextPage();
+ }
+ });
+
+ //stub.setLayoutParams(Layou)
+ vl.addView(navigationPanel);
+
+ vl.setOnTouchListener(new OnTouchListener() {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (navigationPanel.getVisibility() != View.VISIBLE) {
+ navigationPanel.startAnimation(AnimationUtils.loadAnimation(PdfViewerActivity.this,
+ R.anim.slide_in));
+ navigationPanel.setVisibility(View.VISIBLE);
+ }
+
+ return false;
+ }
+ });
+ */
+
+ //addNavButtons(vl);
+
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ setBackgroundColor(Color.LTGRAY);
+ setHorizontalScrollBarEnabled(true);
+ setHorizontalFadingEdgeEnabled(true);
+ setVerticalScrollBarEnabled(true);
+ setVerticalFadingEdgeEnabled(true);
+ addView(vl);
+ }
+
+ private void addNavButtons(ViewGroup vg) {
+
+ // addSpace(vg, 6, 6);
+
+ LinearLayout.LayoutParams lpChild1 =
+ new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ LinearLayout.LayoutParams lpWrap10 =
+ new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+
+ 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.setText("-");
+ //bZoomOut.setWidth(40);
+ 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.setText("+");
+ //bZoomIn.setWidth(40);
+ bZoomIn.setImageResource(getZoomInImageResource());
+ bZoomIn.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ zoomIn();
+ }
+ });
+ hl.addView(bZoomIn);
+
+ // addSpace(hl, 6, 6);
+
+ // 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);
+ String maxPage = ((mPdfFile == null) ? "0" : Integer.toString(mPdfFile.getNumPages()));
+ mBtPage.setText(mPage + "/" + maxPage);
+ 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.setText(">");
+ //bNext.setWidth(40);
+ bNext.setImageResource(getNextPageImageResource());
+ bNext.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ nextPage();
+ }
+ });
+ hl.addView(bNext);
+
+ // addSpace(hl, 20, 20);
+
+ // exit button
+ /*
+ Button bExit=new Button(context);
+ bExit.setLayoutParams(lpChild1);
+ bExit.setText("Back");
+ bExit.setWidth(60);
+ bExit.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ hl.addView(bExit);*/
+
+ 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("");
+ // tvSpacer.setWidth(width);
+ // tvSpacer.setHeight(height);
+ vg.addView(tvSpacer);
+ }
+
+ // private void updateUi() {
+ // uiHandler.post(new Runnable() {
+ // public void run() {
+ // updateTexts();
+ // }
+ // });
+ // }
+
+ private void updateImage() {
+ uiHandler.post(new Runnable() {
+ public void run() {
+ mImageView.setImageBitmap(mBi);
+
+ /*if (progress != null)
+ progress.dismiss();*/
+ }
+ });
+ }
+
+ private void setPageBitmap(Bitmap bi) {
+ if (bi != null) {
+ mBi = bi;
+ } else {
+ /*
+ mBi = Bitmap.createBitmap(100, 100, Config.RGB_565);
+ Canvas can = new Canvas(mBi);
+ can.drawColor(Color.RED);
+
+ Paint paint = new Paint();
+ paint.setColor(Color.BLUE);
+ can.drawCircle(50, 50, 50, paint);
+
+ paint.setStrokeWidth(0);
+ paint.setColor(Color.BLACK);
+ can.drawText("Bitmap", 10, 50, paint);
+ */
+ }
+ }
+
+ // 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 {
+ pdfView.showPage(page, zoom);
+ //long startTime = System.currentTimeMillis();
+ //long middleTime = startTime;
+ try {
+
+ // 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);
+ }
+ //int num = mPdfPage.getPageNumber();
+ //int maxNum = mPdfFile.getNumPages();
+ float width = mPdfPage.getWidth();
+ float height = mPdfPage.getHeight();
+ //String pageInfo= new File(pdffilename).getName() + " - " + num +"/"+maxNum+ ": " + width + "x" + height;
+ //mGraphView.showText(pageInfo);
+ //Log.i(TAG, pageInfo);
+ RectF clip = null;
+ //middleTime = System.currentTimeMillis();
+ 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);
+ // mGraphView.showText("Exception: " + e.getMessage());
+ }
+ //long stopTime = System.currentTimeMillis();
+ //mGraphView.pageParseMillis = middleTime-startTime;
+ //mGraphView.pageRenderMillis = stopTime-middleTime;
+ }
+
+ private void parsePDF(String filename, String password) throws PDFAuthenticationFailureException {
+ //long startTime = System.currentTimeMillis();
+ try {
+ File f = new File(filename);
+ long len = f.length();
+ if (len == 0) {
+ // mGraphView.showText("file '" + filename + "' not found");
+ } else {
+ // mGraphView.showText("file '" + filename + "' has " + len + " bytes");
+ openFile(f, password);
+ }
+ } catch (PDFAuthenticationFailureException e) {
+ throw e;
+ } catch (Throwable e) {
+ e.printStackTrace();
+ // mGraphView.showText("Exception: " + e.getMessage());
+ }
+ //long stopTime = System.currentTimeMillis();
+ //mGraphView.fileMillis = stopTime-startTime;
+ }
+
+ /**
+ *
Open a specific pdf file. Creates a DocumentInfo from the file,
+ * and opens that.
+ *
+ *
Note: Mapping the file locks the file until the PDFFile
+ * is closed.
+ *
+ * @param file the file to open
+ * @throws IOException
+ */
+ public void openFile(File file, String password) throws IOException {
+ // first open the file for random access
+ RandomAccessFile raf = new RandomAccessFile(file, "r");
+
+ // extract a file channel
+ FileChannel channel = raf.getChannel();
+
+ // now memory-map a byte-buffer
+ ByteBuffer bb = ByteBuffer.NEW(channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()));
+ // create a PDFFile from the data
+ if (password == null) {
+ mPdfFile = new PDFFile(bb);
+ } else {
+ mPdfFile = new PDFFile(bb, new PDFPassword(password));
+ }
+ // mGraphView.showText("Anzahl Seiten:" + mPdfFile.getNumPages());
+ }
+
+
+ /*private byte[] readBytes(File srcFile) throws IOException {
+ long fileLength = srcFile.length();
+ int len = (int)fileLength;
+ byte[] result = new byte[len];
+ FileInputStream fis = new FileInputStream(srcFile);
+ int pos = 0;
+ int cnt = fis.read(result, pos, len-pos);
+ while (cnt > 0) {
+ pos += cnt;
+ cnt = fis.read(result, pos, len-pos);
+ }
+ return result;
+ }*/
+
+ private String storeUriContentToFile(Uri uri) {
+ String result = null;
+ try {
+ if (mTmpFile == null) {
+ File root = Environment.getExternalStorageDirectory();
+ if (root == null) throw new Exception("external storage dir not found");
+ mTmpFile = new File(root, "AndroidPdfViewer/AndroidPdfViewer_temp.pdf");
+ mTmpFile.getParentFile().mkdirs();
+ mTmpFile.delete();
+ } else {
+ mTmpFile.delete();
+ }
+ InputStream is = getContentResolver().openInputStream(uri);
+ OutputStream os = new FileOutputStream(mTmpFile);
+ byte[] buf = new byte[1024];
+ int cnt = is.read(buf);
+ while (cnt > 0) {
+ os.write(buf, 0, cnt);
+ cnt = is.read(buf);
+ }
+ os.close();
+ is.close();
+ result = mTmpFile.getCanonicalPath();
+ mTmpFile.deleteOnExit();
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return result;
+ }
+
+ @Override protected void onDestroy() {
+ super.onDestroy();
+ if (mTmpFile != null) {
+ mTmpFile.delete();
+ mTmpFile = null;
+ }
+ }
+
+ /*private void postHideNavigation() {
+ // Start a time to hide the panel after 3 seconds
+ closeNavigationHandler.removeCallbacks(closeNavigationThread);
+ closeNavigationHandler.postDelayed(closeNavigationThread, 3000);
+ }*/
+
+ public abstract int getPreviousPageImageResource(); // R.drawable.left_arrow
+
+ public abstract int getNextPageImageResource(); // R.drawable.right_arrow
+
+ public abstract int getZoomInImageResource(); // R.drawable.zoom_int
+
+ public abstract int getZoomOutImageResource(); // R.drawable.zoom_out
+
+ public abstract int getPdfPasswordLayoutResource(); // R.layout.pdf_file_password
+
+ public abstract int getPdfPageNumberResource(); // R.layout.dialog_pagenumber
+
+ public abstract int getPdfPasswordEditField(); // R.id.etPassword
+
+ public abstract int getPdfPasswordOkButton(); // R.id.btOK
+
+ public abstract int getPdfPasswordExitButton(); // R.id.btExit
+
+ public abstract int getPdfPageNumberEditField(); // R.id.pagenum_edit
+}
\ No newline at end of file
diff --git a/PdfView/src/main/java/net/sf/andpdf/pdfviewer/gui/FullScrollView.java b/PdfView/src/main/java/net/sf/andpdf/pdfviewer/gui/FullScrollView.java
new file mode 100644
index 0000000..d0c69d2
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/pdfviewer/gui/FullScrollView.java
@@ -0,0 +1,1732 @@
+/*
+ * Copyright (C) 2008-2009 pjv (and others, see About dialog)
+ *
+ * This file is part of Android's Fortune.
+ *
+ * Android's Fortune is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Android's Fortune 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Android's Fortune. If not, see .
+ *
+ *
+ *
+ * Based on android.widget.ScrollView (original license below).
+ */
+
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.pdfviewer.gui;
+// package net.lp.androidsfortune.utils;
+
+import java.util.List;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.Scroller;
+import android.widget.TextView;
+
+/**
+ * Layout container for a view hierarchy that can be scrolled by the user,
+ * allowing it to be larger than the physical display. A ScrollView
+ * is a {@link FrameLayout}, meaning you should place one child in it
+ * containing the entire contents to scroll; this child may itself be a layout
+ * manager with a complex hierarchy of objects. A child that is often used
+ * is a {@link LinearLayout} in a vertical orientation, presenting a vertical
+ * array of top-level items that the user can scroll through.
+ *
+ *
You should never use a ScrollView with a {@link ListView}, since
+ * ListView takes care of its own scrolling. Most importantly, doing this
+ * defeats all of the important optimizations in ListView for dealing with
+ * large lists, since it effectively forces the ListView to display its entire
+ * list of items to fill up the infinite container supplied by ScrollView.
+ *
+ *
The {@link TextView} class also
+ * takes care of its own scrolling, so does not require a ScrollView, but
+ * using the two together is possible to achieve the effect of a text view
+ * within a larger container.
+ *
+ *
FullScrollView supports vertical and horizontal scrolling. FullScrollView is based on ScrollView and extended according to symmetry so horizontal scrolling would be possible. Contains many bugs and documentation has not been updated, but kinda works when containing one TextView as a child. pjv 2009-01-24
+ */
+public class FullScrollView extends FrameLayout {
+ static final String TAG = "FullScrollView";
+ static final boolean localLOGV = false || Config.LOGV;
+
+ private static final int ANIMATED_SCROLL_GAP = 250;
+
+ /**
+ * When arrow scrolling, ListView will never scroll more than this factor
+ * times the height of the list.
+ */
+ private static final float MAX_SCROLL_FACTOR = 0.5f;
+
+
+
+
+ /**
+ * The offset, in pixels, by which the content of this view is scrolled
+ * horizontally.
+ * {@hide}
+ */
+ protected int mScrollX;
+ /**
+ * The offset, in pixels, by which the content of this view is scrolled
+ * vertically.
+ * {@hide}
+ */
+ protected int mScrollY;
+
+
+
+
+ private long mLastScroll;
+
+ private final Rect mTempRect = new Rect();
+ private Scroller mScroller;
+
+ /**
+ * Flag to indicate that we are moving focus ourselves. This is so the
+ * code that watches for focus changes initiated outside this ScrollView
+ * knows that it does not have to do anything.
+ */
+ private boolean mScrollViewMovedFocus;
+
+ /**
+ * Position of the last motion event.
+ */
+ private float mLastMotionX;
+ private float mLastMotionY;
+
+ /**
+ * True when the layout has changed but the traversal has not come through yet.
+ * Ideally the view hierarchy would keep track of this for us.
+ */
+ private boolean mIsLayoutDirty = true;
+
+ /**
+ * The child to give focus to in the event that a child has requested focus while the
+ * layout is dirty. This prevents the scroll from being wrong if the child has not been
+ * laid out before requesting focus.
+ */
+ private View mChildToScrollTo = null;
+
+ /**
+ * True if the user is currently dragging this ScrollView around. This is
+ * not the same as 'is being flinged', which can be checked by
+ * mScroller.isFinished() (flinging begins when the user lifts his finger).
+ */
+ private boolean mIsBeingDraggedX = false;
+ private boolean mIsBeingDraggedY = false;
+
+ /**
+ * Determines speed during touch scrolling
+ */
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * When set to true, the scroll view measure its child to make it fill the currently
+ * visible area.
+ */
+ private boolean mFillViewportX;
+ private boolean mFillViewportY;
+
+ /**
+ * Whether arrow scrolling is animated.
+ */
+ private boolean mSmoothScrollingEnabled = true;
+
+ public FullScrollView(Context context) {
+ this(context, null);
+ }
+
+ public FullScrollView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.scrollViewStyle);
+ }
+
+ public FullScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ initScrollView();
+ //TODO
+ //TypedArray a =
+ // context.obtainStyledAttributes(attrs, net.lp.androidsfortune.R.attr.ScrollView, defStyle, 0);
+
+ //setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
+
+ //a.recycle();
+ }
+
+ @Override
+ protected float getLeftFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+
+ final int width = getHorizontalFadingEdgeLength();
+ if (mScrollX < width) {
+ return mScrollX / (float) width;
+ }
+
+ return 1.0f;
+ }
+
+ @Override
+ protected float getRightFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+
+ final int width = getHorizontalFadingEdgeLength();
+ final int right = getChildAt(0).getRight();
+ final int span = right - mScrollX - getWidth();
+ if (span < width) {
+ return span / (float) width;
+ }
+
+ return 1.0f;
+ }
+ @Override
+ protected float getTopFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+
+ final int length = getVerticalFadingEdgeLength();
+ if (mScrollY < length) {
+ return mScrollY / (float) length;
+ }
+
+ return 1.0f;
+ }
+
+ @Override
+ protected float getBottomFadingEdgeStrength() {
+ if (getChildCount() == 0) {
+ return 0.0f;
+ }
+
+ final int length = getVerticalFadingEdgeLength();
+ final int bottom = getChildAt(0).getBottom();
+ final int span = bottom - mScrollY - getHeight();
+ if (span < length) {
+ return span / (float) length;
+ }
+
+ return 1.0f;
+ }
+
+ /**
+ * @return The maximum amount this scroll view will scroll in response to
+ * an arrow event.
+ */
+ public int getMaxScrollAmountY() {
+ return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop()));
+ }
+ /**
+ * @return The maximum amount this scroll view will scroll in response to
+ * an arrow event.
+ */
+ public int getMaxScrollAmountX() {
+ return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft()));
+ }
+
+
+ private void initScrollView() {
+ mScroller = new Scroller(getContext());
+ setFocusable(true);
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+ setWillNotDraw(false);
+ }
+ @Override
+ public void addView(View child) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("ScrollView can host only one direct child");
+ }
+
+ super.addView(child);
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("ScrollView can host only one direct child");
+ }
+
+ super.addView(child, index);
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("ScrollView can host only one direct child");
+ }
+
+ super.addView(child, params);
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (getChildCount() > 0) {
+ throw new IllegalStateException("ScrollView can host only one direct child");
+ }
+
+ super.addView(child, index, params);
+ }
+
+ /**
+ * @return Returns true this ScrollView can be scrolled
+ */
+ private boolean canScrollX() {
+ View child = getChildAt(0);
+ if (child != null) {
+ int childWidth = child.getWidth();
+ return getWidth() < childWidth + getPaddingLeft() + getPaddingRight();
+ }
+ return false;
+ }
+
+ /**
+ * @return Returns true this ScrollView can be scrolled
+ */
+ private boolean canScrollY() {
+ View child = getChildAt(0);
+ if (child != null) {
+ int childHeight = child.getHeight();
+ return getHeight() < childHeight + getPaddingTop() + getPaddingBottom();
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether this ScrollView's content is stretched to fill the viewport.
+ *
+ * @return True if the content fills the viewport, false otherwise.
+ */
+ public boolean isFillViewportX() {
+ return mFillViewportX;
+ }
+
+ /**
+ * Indicates whether this ScrollView's content is stretched to fill the viewport.
+ *
+ * @return True if the content fills the viewport, false otherwise.
+ */
+ public boolean isFillViewportY() {
+ return mFillViewportY;
+ }
+
+ /**
+ * Indicates this ScrollView whether it should stretch its content height to fill
+ * the viewport or not.
+ *
+ * @param fillViewport True to stretch the content's height to the viewport's
+ * boundaries, false otherwise.
+ */
+ public void setFillViewportX(boolean fillViewportX) {
+ if (fillViewportX != mFillViewportX) {
+ mFillViewportX = fillViewportX;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Indicates this ScrollView whether it should stretch its content height to fill
+ * the viewport or not.
+ *
+ * @param fillViewport True to stretch the content's height to the viewport's
+ * boundaries, false otherwise.
+ */
+ public void setFillViewportY(boolean fillViewportY) {
+ if (fillViewportY != mFillViewportY) {
+ mFillViewportY = fillViewportY;
+ requestLayout();
+ }
+ }
+
+ /**
+ * @return Whether arrow scrolling will animate its transition.
+ */
+ public boolean isSmoothScrollingEnabled() {
+ return mSmoothScrollingEnabled;
+ }
+
+ /**
+ * Set whether arrow scrolling will animate its transition.
+ * @param smoothScrollingEnabled whether arrow scrolling will animate its transition
+ */
+ public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
+ mSmoothScrollingEnabled = smoothScrollingEnabled;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (!mFillViewportX&&!mFillViewportY) {
+ return;
+ }
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ if (heightMode == MeasureSpec.UNSPECIFIED && widthMode == MeasureSpec.UNSPECIFIED) {
+ return;
+ }
+
+ final View child = getChildAt(0);
+ int width = getMeasuredWidth();
+ int height = getMeasuredHeight();
+ if (child.getMeasuredWidth() < width && child.getMeasuredHeight() < height && heightMode != MeasureSpec.UNSPECIFIED && widthMode != MeasureSpec.UNSPECIFIED && mFillViewportX&&mFillViewportY) {
+ //final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ width -= getPaddingLeft();
+ width -= getPaddingRight();
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+ height -= getPaddingTop();
+ height -= getPaddingBottom();
+ int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }else if (child.getMeasuredHeight() < height && heightMode != MeasureSpec.UNSPECIFIED && mFillViewportY) {
+ final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft()
+ + getPaddingRight(), lp.width);
+ height -= getPaddingTop();
+ height -= getPaddingBottom();
+ int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }else if (child.getMeasuredWidth() < width && widthMode != MeasureSpec.UNSPECIFIED && mFillViewportX) {
+ final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
+ + getPaddingBottom(), lp.height);
+ width -= getPaddingLeft();
+ width -= getPaddingRight();
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // Let the focused view and/or our descendants get the key first
+ boolean handled = super.dispatchKeyEvent(event);
+ if (handled) {
+ return true;
+ }
+ return executeKeyEvent(event);
+ }
+
+ /**
+ * You can call this function yourself to have the scroll view perform
+ * scrolling from a key event, just as if the event had been dispatched to
+ * it by the view hierarchy.
+ *
+ * @param event The key event to execute.
+ * @return Return true if the event was handled, else false.
+ */
+ public boolean executeKeyEvent(KeyEvent event) {
+ mTempRect.setEmpty();
+
+ if (!canScrollX()||!canScrollY()) {
+ if (isFocused()) {
+ View currentFocused = findFocus();
+ if (currentFocused == this) currentFocused = null;
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this,
+ currentFocused, View.FOCUS_DOWN);
+ return nextFocused != null
+ && nextFocused != this
+ && nextFocused.requestFocus(View.FOCUS_DOWN);
+ }
+ return false;
+ }
+
+ boolean handled = false;
+ if(canScrollY()){
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_UP);
+ } else {
+ handled = fullScroll(View.FOCUS_UP);
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_DOWN);
+ } else {
+ handled = fullScroll(View.FOCUS_DOWN);
+ }
+ break;
+ case KeyEvent.KEYCODE_SPACE:
+ pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
+ break;
+ }
+ }
+ }
+ if(canScrollX()){
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_LEFT);
+ } else {
+ handled = fullScroll(View.FOCUS_LEFT);
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (!event.isAltPressed()) {
+ handled = arrowScroll(View.FOCUS_RIGHT);
+ } else {
+ handled = fullScroll(View.FOCUS_RIGHT);
+ }
+ break;
+ }
+ }
+ }
+
+ return handled;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onMotionEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+ /*
+ * Shortcut the most recurring case: the user is in the dragging
+ * state and he is moving his finger. We want to intercept this
+ * motion.
+ */
+ final int action = ev.getAction();
+ if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDraggedX||mIsBeingDraggedY)) {
+ return true;
+ }
+
+ if (!canScrollX()) {
+ mIsBeingDraggedX = false;
+ }
+ if (!canScrollY()) {
+ mIsBeingDraggedY = false;
+ }
+
+ if (!canScrollY()&&!canScrollX()) {
+ return false;
+ }
+
+ final float x = ev.getX();
+ final float y = ev.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+
+ /*
+ * Locally do absolute value. mLastMotionY is set to the x value
+ * of the down event.
+ */
+ final int xDiff = (int) Math.abs(x - mLastMotionX);
+ if (xDiff > ViewConfiguration.getTouchSlop()) {
+ mIsBeingDraggedX = true;
+ }
+
+ /*
+ * Locally do absolute value. mLastMotionY is set to the y value
+ * of the down event.
+ */
+ final int yDiff = (int) Math.abs(y - mLastMotionY);
+ if (yDiff > ViewConfiguration.getTouchSlop()) {
+ mIsBeingDraggedY = true;
+ }
+ break;
+
+ case MotionEvent.ACTION_DOWN:
+ /* Remember location of down touch */
+ mLastMotionX = x;
+ mLastMotionY = y;
+
+ /*
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged.
+ */
+ mIsBeingDraggedX = !mScroller.isFinished();
+ mIsBeingDraggedY = !mScroller.isFinished();
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ /* Release the drag */
+ mIsBeingDraggedX = false;
+ mIsBeingDraggedY = false;
+ break;
+ }
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return (mIsBeingDraggedX||mIsBeingDraggedY);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+ // Don't handle edge touches immediately -- they may actually belong to one of our
+ // descendants.
+ return false;
+ }
+
+ if (!canScrollX()&&!canScrollY()) {
+ return false;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ final int action = ev.getAction();
+ final float x = ev.getX();
+ final float y = ev.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ /*
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+
+ // Remember where the motion event started
+ mLastMotionX = x;
+ mLastMotionY = y;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Scroll to follow the motion event
+ final int deltaX = (int) (mLastMotionX - x);
+ mLastMotionX = x;
+ final int deltaY = (int) (mLastMotionY - y);
+ mLastMotionY = y;
+
+ if(localLOGV) Log.v(TAG, "onTouchEvent ActionMove deltaX="+deltaX+" deltaY="+deltaY);
+
+ if (deltaX < 0) {
+ if (mScrollX >= 0) {//FIX Changed, don't know why, pjv.
+ scrollBy(deltaX, 0);
+ }
+ } else if (deltaX > 0) {
+ final int rightEdge = getWidth() - getPaddingRight();
+ final int availableToScroll = getChildAt(0).getRight() - mScrollX - rightEdge;
+ if (availableToScroll > 0) {
+ scrollBy(Math.min(availableToScroll, deltaX), 0);
+ }
+ }
+
+ if (deltaY < 0) {
+ if (mScrollY >= 0) {//FIX Changed, don't know why, pjv.
+ scrollBy(0, deltaY);
+ }
+ } else if (deltaY > 0) {
+ final int bottomEdge = getHeight() - getPaddingBottom();
+ final int availableToScroll = getChildAt(0).getBottom() - mScrollY - bottomEdge;
+ if (availableToScroll > 0) {
+ scrollBy(0, Math.min(availableToScroll, deltaY));
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000);
+ int initialVelocityX = (int) velocityTracker.getXVelocity();
+ int initialVelocityY = (int) velocityTracker.getYVelocity();
+
+ if ((Math.abs(initialVelocityX) > ViewConfiguration.getMinimumFlingVelocity()) &&
+ (getChildCount() > 0)) {
+ flingX(-initialVelocityX);
+ }
+
+ if ((Math.abs(initialVelocityY) > ViewConfiguration.getMinimumFlingVelocity()) &&
+ (getChildCount() > 0)) {
+ flingY(-initialVelocityY);
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Finds the next focusable component that fits in this View's bounds
+ * (excluding fading edges) pretending that this View's left is located at
+ * the parameter left.
+ *
+ *
+ * @param leftFocus look for a candidate is the one at the left of the bounds
+ * if leftFocus is true, or at the bottom of the bounds if leftFocus is
+ * false
+ * @param left the left offset of the bounds in which a focusable must be
+ * found (the fading edge is assumed to start at this position)
+ * @param preferredFocusable the View that has highest priority and will be
+ * returned if it is within my bounds (null is valid)
+ * @return the next focusable component in the bounds or null if none can be
+ * found
+ */
+ private View findFocusableViewInMyBoundsX(final boolean leftFocus,
+ final int left, View preferredFocusable) {
+ /*
+ * The fading edge's transparent side should be considered for focus
+ * since it's mostly visible, so we divide the actual fading edge length
+ * by 2.
+ */
+ final int fadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
+ final int leftWithoutFadingEdge = left + fadingEdgeLength;
+ final int rightWithoutFadingEdge = left + getWidth() - fadingEdgeLength;
+
+ if ((preferredFocusable != null)
+ && (preferredFocusable.getLeft() < rightWithoutFadingEdge)
+ && (preferredFocusable.getRight() > leftWithoutFadingEdge)) {
+ return preferredFocusable;
+ }
+
+ return findFocusableViewInBoundsX(leftFocus, leftWithoutFadingEdge,
+ rightWithoutFadingEdge);
+ }
+ /**
+ *
+ * Finds the next focusable component that fits in this View's bounds
+ * (excluding fading edges) pretending that this View's top is located at
+ * the parameter top.
+ *
+ *
+ * @param topFocus look for a candidate is the one at the top of the bounds
+ * if topFocus is true, or at the bottom of the bounds if topFocus is
+ * false
+ * @param top the top offset of the bounds in which a focusable must be
+ * found (the fading edge is assumed to start at this position)
+ * @param preferredFocusable the View that has highest priority and will be
+ * returned if it is within my bounds (null is valid)
+ * @return the next focusable component in the bounds or null if none can be
+ * found
+ */
+ private View findFocusableViewInMyBoundsY(final boolean topFocus,
+ final int top, View preferredFocusable) {
+ /*
+ * The fading edge's transparent side should be considered for focus
+ * since it's mostly visible, so we divide the actual fading edge length
+ * by 2.
+ */
+ final int fadingEdgeLength = getVerticalFadingEdgeLength() / 2;
+ final int topWithoutFadingEdge = top + fadingEdgeLength;
+ final int bottomWithoutFadingEdge = top + getHeight() - fadingEdgeLength;
+
+ if ((preferredFocusable != null)
+ && (preferredFocusable.getTop() < bottomWithoutFadingEdge)
+ && (preferredFocusable.getBottom() > topWithoutFadingEdge)) {
+ return preferredFocusable;
+ }
+
+ return findFocusableViewInBoundsY(topFocus, topWithoutFadingEdge,
+ bottomWithoutFadingEdge);
+ }
+
+ /**
+ *
+ * Finds the next focusable component that fits in the specified bounds.
+ *
+ *
+ * @param leftFocus look for a candidate is the one at the left of the bounds
+ * if leftFocus is true, or at the right of the bounds if leftFocus is
+ * false
+ * @param left the left offset of the bounds in which a focusable must be
+ * found
+ * @param right the right offset of the bounds in which a focusable must
+ * be found
+ * @return the next focusable component in the bounds or null if none can
+ * be found
+ */
+ private View findFocusableViewInBoundsX(boolean leftFocus, int left, int right) {
+
+ List focusables = getFocusables(View.FOCUS_FORWARD);
+ View focusCandidate = null;
+
+ /*
+ * A fully contained focusable is one where its left is right of the bound's
+ * left, and its right is left of the bound's right. A partially
+ * contained focusable is one where some part of it is within the
+ * bounds, but it also has some part that is not within bounds. A fully contained
+ * focusable is preferred to a partially contained focusable.
+ */
+ boolean foundFullyContainedFocusable = false;
+
+ int count = focusables.size();
+ for (int i = 0; i < count; i++) {
+ View view = focusables.get(i);
+ int viewLeft = view.getLeft();
+ int viewRight = view.getRight();
+
+ if (left < viewRight && viewLeft < right) {
+ /*
+ * the focusable is in the target area, it is a candidate for
+ * focusing
+ */
+
+ final boolean viewIsFullyContained = (left < viewLeft) &&
+ (viewRight < right);
+
+ if (focusCandidate == null) {
+ /* No candidate, take this one */
+ focusCandidate = view;
+ foundFullyContainedFocusable = viewIsFullyContained;
+ } else {
+ final boolean viewIsCloserToBoundary =
+ (leftFocus && viewLeft < focusCandidate.getLeft()) ||
+ (!leftFocus && viewRight > focusCandidate
+ .getRight());
+
+ if (foundFullyContainedFocusable) {
+ if (viewIsFullyContained && viewIsCloserToBoundary) {
+ /*
+ * We're dealing with only fully contained views, so
+ * it has to be closer to the boundary to beat our
+ * candidate
+ */
+ focusCandidate = view;
+ }
+ } else {
+ if (viewIsFullyContained) {
+ /* Any fully contained view beats a partially contained view */
+ focusCandidate = view;
+ foundFullyContainedFocusable = true;
+ } else if (viewIsCloserToBoundary) {
+ /*
+ * Partially contained view beats another partially
+ * contained view if it's closer
+ */
+ focusCandidate = view;
+ }
+ }
+ }
+ }
+ }
+
+ return focusCandidate;
+ }
+
+ /**
+ *
+ * Finds the next focusable component that fits in the specified bounds.
+ *
+ *
+ * @param topFocus look for a candidate is the one at the top of the bounds
+ * if topFocus is true, or at the bottom of the bounds if topFocus is
+ * false
+ * @param top the top offset of the bounds in which a focusable must be
+ * found
+ * @param bottom the bottom offset of the bounds in which a focusable must
+ * be found
+ * @return the next focusable component in the bounds or null if none can
+ * be found
+ */
+ private View findFocusableViewInBoundsY(boolean topFocus, int top, int bottom) {
+
+ List focusables = getFocusables(View.FOCUS_FORWARD);
+ View focusCandidate = null;
+
+ /*
+ * A fully contained focusable is one where its top is below the bound's
+ * top, and its bottom is above the bound's bottom. A partially
+ * contained focusable is one where some part of it is within the
+ * bounds, but it also has some part that is not within bounds. A fully contained
+ * focusable is preferred to a partially contained focusable.
+ */
+ boolean foundFullyContainedFocusable = false;
+
+ int count = focusables.size();
+ for (int i = 0; i < count; i++) {
+ View view = focusables.get(i);
+ int viewTop = view.getTop();
+ int viewBottom = view.getBottom();
+
+ if (top < viewBottom && viewTop < bottom) {
+ /*
+ * the focusable is in the target area, it is a candidate for
+ * focusing
+ */
+
+ final boolean viewIsFullyContained = (top < viewTop) &&
+ (viewBottom < bottom);
+
+ if (focusCandidate == null) {
+ /* No candidate, take this one */
+ focusCandidate = view;
+ foundFullyContainedFocusable = viewIsFullyContained;
+ } else {
+ final boolean viewIsCloserToBoundary =
+ (topFocus && viewTop < focusCandidate.getTop()) ||
+ (!topFocus && viewBottom > focusCandidate
+ .getBottom());
+
+ if (foundFullyContainedFocusable) {
+ if (viewIsFullyContained && viewIsCloserToBoundary) {
+ /*
+ * We're dealing with only fully contained views, so
+ * it has to be closer to the boundary to beat our
+ * candidate
+ */
+ focusCandidate = view;
+ }
+ } else {
+ if (viewIsFullyContained) {
+ /* Any fully contained view beats a partially contained view */
+ focusCandidate = view;
+ foundFullyContainedFocusable = true;
+ } else if (viewIsCloserToBoundary) {
+ /*
+ * Partially contained view beats another partially
+ * contained view if it's closer
+ */
+ focusCandidate = view;
+ }
+ }
+ }
+ }
+ }
+
+ return focusCandidate;
+ }
+
+ /**
+ *
Handles scrolling in response to a "page up/down" shortcut press. This
+ * method will scroll the view by one page up or down and give the focus
+ * to the topmost/bottommost component in the new visible area. If no
+ * component is a good candidate for focus, this scrollview reclaims the
+ * focus.
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+ * to go one page up or
+ * {@link android.view.View#FOCUS_DOWN} to go one page down
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ public boolean pageScroll(int direction) {
+ boolean down = direction == View.FOCUS_DOWN;
+ int height = getHeight();
+
+ if (down) {
+ mTempRect.top = getScrollY() + height;
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ if (mTempRect.top + height > view.getBottom()) {
+ mTempRect.top = view.getBottom() - height;
+ }
+ }
+ } else {
+ mTempRect.top = getScrollY() - height;
+ if (mTempRect.top < 0) {
+ mTempRect.top = 0;
+ }
+ }
+ mTempRect.bottom = mTempRect.top + height;
+
+ return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
+ }
+
+ /**
+ *
Handles scrolling in response to a "home/end" shortcut press. This
+ * method will scroll the view to the top or bottom and give the focus
+ * to the topmost/bottommost component in the new visible area. If no
+ * component is a good candidate for focus, this scrollview reclaims the
+ * focus.
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+ * to go the top of the view or
+ * {@link android.view.View#FOCUS_DOWN} to go the bottom
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ public boolean fullScroll(int direction) {
+ boolean down = direction == View.FOCUS_DOWN;
+ int height = getHeight();
+
+ mTempRect.top = 0;
+ mTempRect.bottom = height;
+
+ if (down) {
+ int count = getChildCount();
+ if (count > 0) {
+ View view = getChildAt(count - 1);
+ mTempRect.bottom = view.getBottom();
+ mTempRect.top = mTempRect.bottom - height;
+ }
+ }
+
+ return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
+ }
+
+ /**
+ *
Scrolls the view to make the area defined by top and
+ * bottom visible. This method attempts to give the focus
+ * to a component visible in this area. If no component can be focused in
+ * the new visible area, the focus is reclaimed by this scrollview.
+ *
+ * @param direction the scroll direction: {@link android.view.View#FOCUS_UP}
+ * to go upward
+ * {@link android.view.View#FOCUS_DOWN} to downward
+ * @param top the top offset of the new area to be made visible
+ * @param bottom the bottom offset of the new area to be made visible
+ * @return true if the key event is consumed by this method, false otherwise
+ */
+ private boolean scrollAndFocus(int direction, int top, int bottom) {
+ boolean handled = true;
+
+ int height = getHeight();
+ int containerTop = getScrollY();
+ int containerBottom = containerTop + height;
+ boolean up = direction == View.FOCUS_UP;
+
+ View newFocused = findFocusableViewInBoundsY(up, top, bottom);//TODO
+ if (newFocused == null) {
+ newFocused = this;
+ }
+
+ if (top >= containerTop && bottom <= containerBottom) {
+ handled = false;
+ } else {
+ int delta = up ? (top - containerTop) : (bottom - containerBottom);
+ doScrollY(delta);
+ }
+
+ if (newFocused != findFocus() && newFocused.requestFocus(direction)) {
+ mScrollViewMovedFocus = true;
+ mScrollViewMovedFocus = false;
+ }
+
+ return handled;
+ }
+
+ /**
+ * Handle scrolling in response to an up or down or left or right arrow click.
+ *
+ * @param direction The direction corresponding to the arrow key that was
+ * pressed
+ * @return True if we consumed the event, false otherwise
+ */
+ public boolean arrowScroll(int direction) {
+
+ View currentFocused = findFocus();
+ if (currentFocused == this) currentFocused = null;
+
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
+
+ final int maxJumpX = getMaxScrollAmountX();
+ final int maxJumpY = getMaxScrollAmountY();
+
+ if (nextFocused != null && isWithinDeltaOfScreenX(nextFocused, maxJumpX) && isWithinDeltaOfScreenY(nextFocused, maxJumpY)) {//TODO
+ nextFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(nextFocused, mTempRect);
+ int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreenX(mTempRect);
+ doScrollX(scrollDeltaX);
+ int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreenY(mTempRect);
+ doScrollY(scrollDeltaY);
+ nextFocused.requestFocus(direction);
+ } else {
+ // no new focus
+ int scrollDeltaX = maxJumpX;
+ int scrollDeltaY = maxJumpY;
+
+ if (direction == View.FOCUS_UP && getScrollX() < scrollDeltaX) {
+ scrollDeltaX = getScrollX();
+ } else if (direction == View.FOCUS_DOWN) {
+
+ int daRight = getChildAt(getChildCount() - 1).getRight();
+
+ int screenRight = getScrollX() + getWidth();
+
+ if (daRight - screenRight < maxJumpX) {
+ scrollDeltaX = daRight - screenRight;
+ }
+ }
+ if (direction == View.FOCUS_UP && getScrollY() < scrollDeltaY) {
+ scrollDeltaY = getScrollY();
+ } else if (direction == View.FOCUS_DOWN) {
+
+ int daBottom = getChildAt(getChildCount() - 1).getBottom();
+
+ int screenBottom = getScrollY() + getHeight();
+
+ if (daBottom - screenBottom < maxJumpY) {
+ scrollDeltaY = daBottom - screenBottom;
+ }
+ }
+ if (scrollDeltaX == 0 && scrollDeltaY == 0) {
+ return false;
+ }else if (scrollDeltaX == 0) {
+ doScrollY(direction == View.FOCUS_DOWN ? scrollDeltaY : -scrollDeltaY);
+ }else if (scrollDeltaY == 0) {
+ doScrollX(direction == View.FOCUS_DOWN ? scrollDeltaX : -scrollDeltaX);
+ }
+ }
+
+ if (currentFocused != null && currentFocused.isFocused()
+ && isOffScreen(currentFocused)) {
+ // previously focused item still has focus and is off screen, give
+ // it up (take it back to ourselves)
+ // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are
+ // sure to
+ // get it)
+ final int descendantFocusability = getDescendantFocusability(); // save
+ setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ requestFocus();
+ setDescendantFocusability(descendantFocusability); // restore
+ }
+ return true;
+ }
+
+ /**
+ * @return whether the descendant of this scroll view is scrolled off
+ * screen.
+ */
+ private boolean isOffScreen(View descendant) {
+ return !isWithinDeltaOfScreenX(descendant, 0)&&!isWithinDeltaOfScreenY(descendant, 0);
+ }
+
+ /**
+ * @return whether the descendant of this scroll view is within delta
+ * pixels of being on the screen.
+ */
+ private boolean isWithinDeltaOfScreenX(View descendant, int delta) {
+ descendant.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(descendant, mTempRect);
+
+ return (mTempRect.right + delta) >= getScrollX()
+ && (mTempRect.left - delta) <= (getScrollX() + getWidth());
+ }
+
+ /**
+ * @return whether the descendant of this scroll view is within delta
+ * pixels of being on the screen.
+ */
+ private boolean isWithinDeltaOfScreenY(View descendant, int delta) {
+ descendant.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(descendant, mTempRect);
+
+ return (mTempRect.bottom + delta) >= getScrollY()
+ && (mTempRect.top - delta) <= (getScrollY() + getHeight());
+ }
+
+
+ /**
+ * Smooth scroll by a X delta
+ *
+ * @param delta the number of pixels to scroll by on the Y axis
+ */
+ private void doScrollX(int delta) {
+ if (delta != 0) {
+ if (mSmoothScrollingEnabled) {
+ smoothScrollBy(delta, 0);
+ } else {
+ scrollBy(delta, 0);
+ }
+ }
+ }
+
+ /**
+ * Smooth scroll by a Y delta
+ *
+ * @param delta the number of pixels to scroll by on the X axis
+ */
+ private void doScrollY(int delta) {
+ if (delta != 0) {
+ if (mSmoothScrollingEnabled) {
+ smoothScrollBy(0, delta);
+ } else {
+ scrollBy(0, delta);
+ }
+ }
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param dx the number of pixels to scroll by on the X axis
+ * @param dy the number of pixels to scroll by on the Y axis
+ */
+ public final void smoothScrollBy(int dx, int dy) {
+ long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
+ if (duration > ANIMATED_SCROLL_GAP) {
+ if (localLOGV) Log.v(TAG, "Smooth scroll: mScrollX=" + mScrollX
+ + " dx=" + dx + "mScrollY=" + mScrollY
+ + " dy=" + dy);
+ mScroller.startScroll(mScrollX, mScrollY, dx, dy);
+ invalidate();
+ } else {
+ if (!mScroller.isFinished()) {
+ mScroller.abortAnimation();
+ }
+ if (localLOGV) Log.v(TAG, "Immediate scroll: mScrollX=" + mScrollX
+ + " dx=" + dx + "mScrollY=" + mScrollY
+ + " dy=" + dy);
+ scrollBy(dx, dy);
+ }
+ mLastScroll = AnimationUtils.currentAnimationTimeMillis();
+ }
+
+ /**
+ * Like {@link #scrollTo}, but scroll smoothly instead of immediately.
+ *
+ * @param x the position where to scroll on the X axis
+ * @param y the position where to scroll on the Y axis
+ */
+ public final void smoothScrollTo(int x, int y) {
+ smoothScrollBy(x - mScrollX, y - mScrollY);
+ }
+
+ /**
+ *
The scroll range of a scroll view is the overall width of all of its
+ * children.
The scroll range of a scroll view is the overall height of all of its
+ * children.
+ */
+ @Override
+ protected int computeVerticalScrollRange() {
+ int count = getChildCount();
+ return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
+ }
+
+
+ @Override
+ protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
+ int childWidthMeasureSpec;
+ int childHeightMeasureSpec;
+
+ childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
+ int parentHeightMeasureSpec, int heightUsed) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);
+ final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ // This is called at drawing time by ViewGroup. We don't want to
+ // re-show the scrollbars at this point, which scrollTo will do,
+ // so we replicate most of scrollTo here.
+ //
+ // It's a little odd to call onScrollChanged from inside the drawing.
+ //
+ // It is, except when you remember that computeScroll() is used to
+ // animate scrolling. So unless we want to defer the onScrollChanged()
+ // until the end of the animated scrolling, we don't really have a
+ // choice here.
+ //
+ // I agree. The alternative, which I think would be worse, is to post
+ // something and tell the subclasses later. This is bad because there
+ // will be a window where mScrollX/Y is different from what the app
+ // thinks it is.
+ //
+ int oldX = mScrollX;
+ int oldY = mScrollY;
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ mScrollX = clamp(x, this.getWidth(), child.getWidth());
+ mScrollY = clamp(y, this.getHeight(), child.getHeight());
+ if (localLOGV) Log.v(TAG, "mScrollX=" + mScrollX + " x=" + x
+ + " width=" + this.getWidth()
+ + " child width=" + child.getWidth() + " mScrollY=" + mScrollY + " y=" + y
+ + " height=" + this.getHeight()
+ + " child height=" + child.getHeight());
+ } else {
+ mScrollX = x;
+ mScrollY = y;
+ }
+ if (oldX != mScrollX || oldY != mScrollY) {
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+ }
+
+ // Keep on drawing until the animation has finished.
+ postInvalidate();
+ }
+ }
+
+ /**
+ * Scrolls the view to the given child.
+ *
+ * @param child the View to scroll to
+ */
+ private void scrollToChild(View child) {
+ child.getDrawingRect(mTempRect);
+
+ /* Offset from child's local coordinates to ScrollView coordinates */
+ offsetDescendantRectToMyCoords(child, mTempRect);
+
+ int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreenX(mTempRect);
+ int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreenY(mTempRect);
+
+ if (scrollDeltaY != 0 || scrollDeltaX != 0) {
+ scrollBy(scrollDeltaX, scrollDeltaY);
+ }
+ }
+
+ /**
+ * If rect is off screen, scroll just enough to get it (or at least the
+ * first screen size chunk of it) on screen.
+ *
+ * @param rect The rectangle.
+ * @param immediate True to scroll immediately without animation
+ * @return true if scrolling was performed
+ */
+ private boolean scrollToChildRect(Rect rect, boolean immediate) {
+ final int deltaX = computeScrollDeltaToGetChildRectOnScreenX(rect);
+ final int deltaY = computeScrollDeltaToGetChildRectOnScreenY(rect);
+ final boolean scroll = deltaX != 0 || deltaY != 0;
+ if (scroll) {
+ if (immediate) {
+ scrollBy(deltaX, deltaY);
+ } else {
+ smoothScrollBy(deltaX, deltaY);
+ }
+ }
+ return scroll;
+ }
+
+ /**
+ * Compute the amount to scroll in the X direction in order to get
+ * a rectangle completely on the screen (or, if taller than the screen,
+ * at least the first screen size chunk of it).
+ *
+ * @param rect The rect.
+ * @return The scroll delta.
+ */
+ protected int computeScrollDeltaToGetChildRectOnScreenX(Rect rect) {
+
+ int width = getWidth();
+ int screenLeft = getScrollX();
+ int screenRight = screenLeft + width;
+
+ int fadingEdge = getHorizontalFadingEdgeLength();
+
+ // leave room for left fading edge as long as rect isn't at very left
+ if (rect.left > 0) {
+ screenLeft += fadingEdge;
+ }
+
+ // leave room for right fading edge as long as rect isn't at very right
+ if (rect.right < getChildAt(0).getWidth()) {
+ screenRight -= fadingEdge;
+ }
+
+ int scrollXDelta = 0;
+
+ if (localLOGV) Log.v(TAG, "child=" + rect.toString()
+ + " screenLeft=" + screenLeft + " screenRight=" + screenRight
+ + " width=" + width);
+ if (rect.right > screenRight && rect.left > screenLeft) {
+ // need to move right to get it in view: move right just enough so
+ // that the entire rectangle is in view (or at least the first
+ // screen size chunk).
+
+ if (rect.width() > width) {
+ // just enough to get screen size chunk on
+ scrollXDelta += (rect.left - screenLeft);
+ } else {
+ // get entire rect at right of screen
+ scrollXDelta += (rect.right - screenRight);
+ }
+
+ // make sure we aren't scrolling beyond the end of our content
+ int right = getChildAt(getChildCount() - 1).getRight();
+ int distanceToRight = right - screenRight;
+ if (localLOGV) Log.v(TAG, "scrollXDelta=" + scrollXDelta
+ + " distanceToRight=" + distanceToRight);
+ scrollXDelta = Math.min(scrollXDelta, distanceToRight);
+
+ } else if (rect.left < screenLeft && rect.right < screenRight) {
+ // need to move left to get it in view: move left just enough so that
+ // entire rectangle is in view (or at least the first screen
+ // size chunk of it).
+
+ if (rect.width() > width) {
+ // screen size chunk
+ scrollXDelta -= (screenRight - rect.right);
+ } else {
+ // entire rect at left
+ scrollXDelta -= (screenLeft - rect.left);
+ }
+
+ // make sure we aren't scrolling any further than the left our content
+ scrollXDelta = Math.max(scrollXDelta, -getScrollX());
+ }
+ return scrollXDelta;
+ }
+ /**
+ * Compute the amount to scroll in the Y direction in order to get
+ * a rectangle completely on the screen (or, if taller than the screen,
+ * at least the first screen size chunk of it).
+ *
+ * @param rect The rect.
+ * @return The scroll delta.
+ */
+ protected int computeScrollDeltaToGetChildRectOnScreenY(Rect rect) {
+
+ int height = getHeight();
+ int screenTop = getScrollY();
+ int screenBottom = screenTop + height;
+
+ int fadingEdge = getVerticalFadingEdgeLength();
+
+ // leave room for top fading edge as long as rect isn't at very top
+ if (rect.top > 0) {
+ screenTop += fadingEdge;
+ }
+
+ // leave room for bottom fading edge as long as rect isn't at very bottom
+ if (rect.bottom < getChildAt(0).getHeight()) {
+ screenBottom -= fadingEdge;
+ }
+
+ int scrollYDelta = 0;
+
+ if (localLOGV) Log.v(TAG, "child=" + rect.toString()
+ + " screenTop=" + screenTop + " screenBottom=" + screenBottom
+ + " height=" + height);
+ if (rect.bottom > screenBottom && rect.top > screenTop) {
+ // need to move down to get it in view: move down just enough so
+ // that the entire rectangle is in view (or at least the first
+ // screen size chunk).
+
+ if (rect.height() > height) {
+ // just enough to get screen size chunk on
+ scrollYDelta += (rect.top - screenTop);
+ } else {
+ // get entire rect at bottom of screen
+ scrollYDelta += (rect.bottom - screenBottom);
+ }
+
+ // make sure we aren't scrolling beyond the end of our content
+ int bottom = getChildAt(getChildCount() - 1).getBottom();
+ int distanceToBottom = bottom - screenBottom;
+ if (localLOGV) Log.v(TAG, "scrollYDelta=" + scrollYDelta
+ + " distanceToBottom=" + distanceToBottom);
+ scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
+
+ } else if (rect.top < screenTop && rect.bottom < screenBottom) {
+ // need to move up to get it in view: move up just enough so that
+ // entire rectangle is in view (or at least the first screen
+ // size chunk of it).
+
+ if (rect.height() > height) {
+ // screen size chunk
+ scrollYDelta -= (screenBottom - rect.bottom);
+ } else {
+ // entire rect at top
+ scrollYDelta -= (screenTop - rect.top);
+ }
+
+ // make sure we aren't scrolling any further than the top our content
+ scrollYDelta = Math.max(scrollYDelta, -getScrollY());
+ }
+ return scrollYDelta;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (!mScrollViewMovedFocus) {
+ if (!mIsLayoutDirty) {
+ scrollToChild(focused);
+ } else {
+ // The child may not be laid out yet, we can't compute the scroll yet
+ mChildToScrollTo = focused;
+ }
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+
+ /**
+ * When looking for focus in children of a scroll view, need to be a little
+ * more careful not to give focus to something that is scrolled off screen.
+ *
+ * This is more expensive than the default {@link android.view.ViewGroup}
+ * implementation, otherwise this behavior might have been made the default.
+ */
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+
+ // convert from forward / backward notation to up / down / left / right
+ // (ugh).
+ if (direction == View.FOCUS_FORWARD) {
+ direction = View.FOCUS_DOWN;
+ } else if (direction == View.FOCUS_BACKWARD) {
+ direction = View.FOCUS_UP;
+ }
+
+ final View nextFocus = previouslyFocusedRect == null ?
+ FocusFinder.getInstance().findNextFocus(this, null, direction) :
+ FocusFinder.getInstance().findNextFocusFromRect(this,
+ previouslyFocusedRect, direction);
+
+ if (nextFocus == null) {
+ return false;
+ }
+
+ if (isOffScreen(nextFocus)) {
+ return false;
+ }
+
+ return nextFocus.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+ boolean immediate) {
+ // offset into coordinate space of this scroll view
+ rectangle.offset(child.getLeft() - child.getScrollX(),
+ child.getTop() - child.getScrollY());
+
+ return scrollToChildRect(rectangle, immediate);
+ }
+
+ @Override
+ public void requestLayout() {
+ mIsLayoutDirty = true;
+ super.requestLayout();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ mIsLayoutDirty = false;
+ // Give a child focus if it needs it
+ if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
+ scrollToChild(mChildToScrollTo);
+ }
+ mChildToScrollTo = null;
+
+ // Calling this with the present values causes it to re-clam them
+ scrollTo(mScrollX, mScrollY);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ View currentFocused = findFocus();
+ if (null == currentFocused || this == currentFocused)
+ return;
+
+ final int maxJumpX = getRight() - getLeft();
+ final int maxJumpY = getBottom() - getTop();
+
+ if (isWithinDeltaOfScreenX(currentFocused, maxJumpX)) {
+ currentFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+ int scrollDeltaX = computeScrollDeltaToGetChildRectOnScreenX(mTempRect);
+ doScrollX(scrollDeltaX);
+ }
+ if (isWithinDeltaOfScreenY(currentFocused, maxJumpY)) {
+ currentFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+ int scrollDeltaY = computeScrollDeltaToGetChildRectOnScreenY(mTempRect);
+ doScrollY(scrollDeltaY);
+ }
+ }
+
+ /**
+ * Return true if child is an descendant of parent, (or equal to the parent).
+ */
+ private boolean isViewDescendantOf(View child, View parent) {
+ if (child == parent) {
+ return true;
+ }
+
+ final ViewParent theParent = child.getParent();
+ return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
+ }
+
+ /**
+ * Fling the scroll view
+ *
+ * @param velocityX The initial velocity in the X direction. Positive
+ * numbers mean that the finger/cursor is moving right the screen,
+ * which means we want to scroll towards the left.
+ */
+ public void flingX(int velocityX) {
+ int width = getWidth();
+ int right = getChildAt(getChildCount() - 1).getRight();
+
+ mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, 0, 0, right - width);
+
+ final boolean movingRight = velocityX > 0;
+
+ View newFocused =
+ findFocusableViewInMyBoundsX(movingRight, mScroller.getFinalX(), findFocus());
+ if (newFocused == null) {
+ newFocused = this;
+ }
+
+ if (newFocused != findFocus()
+ && newFocused.requestFocus(movingRight ? View.FOCUS_DOWN : View.FOCUS_UP)) {
+ mScrollViewMovedFocus = true;
+ mScrollViewMovedFocus = false;
+ }
+
+ invalidate();
+ }
+
+ /**
+ * Fling the scroll view
+ *
+ * @param velocityY The initial velocity in the Y direction. Positive
+ * numbers mean that the finger/cursor is moving down the screen,
+ * which means we want to scroll towards the top.
+ */
+ public void flingY(int velocityY) {
+ int height = getHeight();
+ int bottom = getChildAt(getChildCount() - 1).getBottom();
+
+ mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height);
+
+ final boolean movingDown = velocityY > 0;
+
+ View newFocused =
+ findFocusableViewInMyBoundsY(movingDown, mScroller.getFinalY(), findFocus());
+ if (newFocused == null) {
+ newFocused = this;
+ }
+
+ if (newFocused != findFocus()
+ && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) {
+ mScrollViewMovedFocus = true;
+ mScrollViewMovedFocus = false;
+ }
+
+ invalidate();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
This version also clamps the scrolling to the bounds of our child.
+ */
+ public void scrollTo(int x, int y) {
+ // we rely on the fact the View.scrollBy calls scrollTo.
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ x = clamp(x, this.getWidth(), child.getWidth());
+ y = clamp(y, this.getHeight(), child.getHeight());
+ if (x != mScrollX || y != mScrollY) {
+ super.scrollTo(x, y);
+ mScrollX=x;//FIX Changed, pjv.
+ mScrollY=y;//FIX Changed, pjv.
+ }
+ }
+ if(localLOGV) Log.v(TAG, "scrollTo x="+x+" y="+y);
+ }
+
+ private int clamp(int n, int my, int child) {
+ if (my >= child || n < 0) {
+ /* my >= child is this case:
+ * |--------------- me ---------------|
+ * |------ child ------|
+ * or
+ * |--------------- me ---------------|
+ * |------ child ------|
+ * or
+ * |--------------- me ---------------|
+ * |------ child ------|
+ *
+ * n < 0 is this case:
+ * |------ me ------|
+ * |-------- child --------|
+ * |-- mScrollX --|
+ */
+ return 0;
+ }
+ if ((my+n) > child) {
+ /* this case:
+ * |------ me ------|
+ * |------ child ------|
+ * |-- mScrollX --|
+ */
+ return child-my;
+ }
+ return n;
+ }
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/pdfviewer/gui/PdfView.java b/PdfView/src/main/java/net/sf/andpdf/pdfviewer/gui/PdfView.java
new file mode 100644
index 0000000..9df31eb
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/pdfviewer/gui/PdfView.java
@@ -0,0 +1,293 @@
+package net.sf.andpdf.pdfviewer.gui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.Toast;
+import com.polites.android.Direction;
+import com.polites.android.GestureImageView;
+import com.polites.android.GestureImageViewTouchListener;
+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 java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import net.sf.andpdf.nio.ByteBuffer;
+import net.sf.andpdf.refs.HardReference;
+
+public class PdfView extends FullScrollView {
+
+ 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 Bitmap mBi;
+ public GestureImageView mImageView;
+ private Handler uiHandler;
+ private PDFFile mPdfFile;
+ private PDFPage mPdfPage;
+ private Thread backgroundThread;
+ private int mPage;
+ private float mZoom;
+
+ private boolean drawing = false;
+
+ public PdfView(Context context) {
+ this(context, null);
+ }
+
+ public PdfView(Context context, AttributeSet attrs) {
+ this(context, attrs, android.R.attr.scrollViewStyle);
+ }
+
+ public PdfView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ PDFImage.sShowImages = true;
+ PDFPaint.s_doAntiAlias = true;
+ HardReference.sKeepCaches = true;
+ uiHandler = new Handler();
+ LayoutParams matchLp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ mImageView = new GestureImageView(context);
+ mImageView.setLayoutParams(matchLp);
+ addView(mImageView);
+ setLayoutParams(matchLp);
+ setBackgroundColor(Color.LTGRAY);
+ setHorizontalScrollBarEnabled(true);
+ setHorizontalFadingEdgeEnabled(true);
+ setVerticalScrollBarEnabled(true);
+ setVerticalFadingEdgeEnabled(true);
+ mZoom = STARTZOOM;
+ mImageView.setOnClickListener(new OnClickListener() {
+ @Override public void onClick(View v) {
+ nextPage();
+ }
+ });
+ mImageView.setOnReachBoundListener(new GestureImageViewTouchListener.OnReachBoundListener() {
+ @Override public void onReach(View view, int direction) {
+ if (drawing) {
+ return;
+ }
+ switch (direction) {
+ case Direction.LEFT:
+ prevPage();
+ break;
+ case Direction.RIGHT:
+ nextPage();
+ break;
+ case Direction.TOP:
+ break;
+ case Direction.BOTTOM:
+ break;
+ }
+ }
+ });
+ }
+
+ public PDFFile getmPdfFile() {
+ return mPdfFile;
+ }
+
+ public void setmPdfFile(PDFFile mPdfFile) {
+ this.mPdfFile = mPdfFile;
+ }
+
+ private int getDeviceWidth() {
+ DisplayMetrics metric = new DisplayMetrics();
+ WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(metric);
+ return metric.widthPixels; // å±å¹•宽度(åƒç´ )
+ }
+
+ private int getDeviceHeight() {
+ DisplayMetrics metric = new DisplayMetrics();
+ WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(metric);
+ return metric.heightPixels; // å±å¹•高度(åƒç´ )
+ }
+
+ public void showPage(int page, float zoom) throws Exception {
+ try {
+ // free memory from previous page
+ 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();
+ if (getLayoutParams().height == ViewGroup.LayoutParams.MATCH_PARENT) {
+ height *= getDeviceWidth() / width;
+ }
+ if (getLayoutParams().width == LayoutParams.MATCH_PARENT) {
+ width = getDeviceWidth();
+ }
+ RectF clip = null;
+ final Bitmap bi = mPdfPage.getImage((int) (width * zoom), (int) (height * zoom), clip, true, true);
+ setPageBitmap(bi);
+ updateImage();
+ } catch (Throwable e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+
+ private void updateImage() {
+ uiHandler.post(new Runnable() {
+ public void run() {
+ mImageView.reset();
+ mImageView.setImageBitmap(mBi);
+ }
+ });
+ }
+
+ private void setPageBitmap(Bitmap bi) {
+ if (bi != null) {
+ mBi = bi;
+ }
+ }
+
+ private void zoomIn() {
+ if (mPdfFile != null) {
+ if (mZoom < MAX_ZOOM) {
+ mZoom *= ZOOM_INCREMENT;
+ if (mZoom > MAX_ZOOM) mZoom = MAX_ZOOM;
+ startRenderThread(mPage, mZoom);
+ }
+ }
+ }
+
+ private void zoomOut() {
+ if (mPdfFile != null) {
+ if (mZoom > MIN_ZOOM) {
+ mZoom /= ZOOM_INCREMENT;
+ if (mZoom < MIN_ZOOM) mZoom = MIN_ZOOM;
+ startRenderThread(mPage, mZoom);
+ }
+ }
+ }
+
+ private void nextPage() {
+ if (mPdfFile != null) {
+ if (mPage < mPdfFile.getNumPages()) {
+ mPage += 1;
+ startRenderThread(mPage, mZoom);
+ }
+ }
+ }
+
+ private void prevPage() {
+ if (mPdfFile != null) {
+ if (mPage > 1) {
+ mPage -= 1;
+ startRenderThread(mPage, mZoom);
+ }
+ }
+ }
+
+ private void gotoPage() {
+ if (mPdfFile != null) {
+ // showDialog(DIALOG_PAGENUM);
+ }
+ }
+
+ public synchronized void startRenderThread(final int page, final float zoom) {
+ if (backgroundThread != null) return;
+ backgroundThread = new Thread(new Runnable() {
+ public void run() {
+ drawing = true;
+ try {
+ if (mPdfFile != null) {
+ showPage(page, zoom);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ drawing = false;
+ backgroundThread = null;
+ }
+ });
+ updateImageStatus();
+ backgroundThread.start();
+ }
+
+ private void updateImageStatus() {
+ if (backgroundThread == null) {
+ return;
+ }
+ postDelayed(new Runnable() {
+ public void run() {
+ updateImageStatus();
+ }
+ }, 1000);
+ }
+
+ public void parsePDF(File f, String password) throws PDFAuthenticationFailureException {
+ try {
+ long len = f.length();
+ if (len == 0) {
+ toastMessage("file '" + f.getName() + "' not found");
+ } else {
+ toastMessage("file '" + f.getName() + "' has " + len + " bytes");
+ openFile(f, password);
+ }
+ } catch (PDFAuthenticationFailureException e) {
+ throw e;
+ } catch (Throwable e) {
+ e.printStackTrace();
+ toastMessage("Exception: " + e.getMessage());
+ }
+ }
+
+ public void parsePDF(String filename, String password) throws PDFAuthenticationFailureException {
+ try {
+ File f = new File(filename);
+ long len = f.length();
+ if (len == 0) {
+ toastMessage("file '" + filename + "' not found");
+ } else {
+ toastMessage("file '" + filename + "' has " + len + " bytes");
+ openFile(f, password);
+ }
+ } catch (PDFAuthenticationFailureException e) {
+ throw e;
+ } catch (Throwable e) {
+ e.printStackTrace();
+ toastMessage("Exception: " + e.getMessage());
+ }
+ }
+
+ public void openFile(File file, String password) throws IOException {
+ // first open the file for random access
+ RandomAccessFile raf = new RandomAccessFile(file, "r");
+ // extract a file channel
+ FileChannel channel = raf.getChannel();
+ // now memory-map a byte-buffer
+ ByteBuffer bb = ByteBuffer.NEW(channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()));
+ // create a PDFFile from the data
+ if (password == null) {
+ mPdfFile = new PDFFile(bb);
+ } else {
+ mPdfFile = new PDFFile(bb, new PDFPassword(password));
+ }
+ toastMessage("Anzahl Seiten:" + mPdfFile.getNumPages());
+ }
+
+ public void toastMessage(String msg) {
+ Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/refs/HardReference.java b/PdfView/src/main/java/net/sf/andpdf/refs/HardReference.java
new file mode 100644
index 0000000..b673a56
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/refs/HardReference.java
@@ -0,0 +1,37 @@
+package net.sf.andpdf.refs;
+
+import java.util.ArrayList;
+
+public class HardReference {
+
+ public static boolean sKeepCaches = false;
+
+ private static ArrayList cleanupList = new ArrayList();
+ public static void cleanup() {
+ ArrayList oldList = cleanupList;
+ cleanupList = new ArrayList();
+ for (HardReference hr:oldList) {
+ hr.clean();
+ }
+ oldList.clear();
+ }
+
+ private T ref;
+
+
+ public HardReference(T o) {
+ ref = o;
+ cleanupList.add(this);
+ }
+
+ public T get() {
+ return ref;
+ }
+
+ public void clean() {
+ ref = null;
+ }
+
+
+
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/refs/SoftReference.java b/PdfView/src/main/java/net/sf/andpdf/refs/SoftReference.java
new file mode 100644
index 0000000..7cf10b7
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/refs/SoftReference.java
@@ -0,0 +1,24 @@
+package net.sf.andpdf.refs;
+
+import java.util.ArrayList;
+
+public class SoftReference {
+
+ java.lang.ref.SoftReference softRef;
+ HardReference hardRef;
+
+ public SoftReference(T o) {
+ if (HardReference.sKeepCaches)
+ hardRef = new HardReference(o);
+ else
+ softRef = new java.lang.ref.SoftReference(o);
+ }
+
+ public T get() {
+ if (HardReference.sKeepCaches)
+ return hardRef.get();
+ else
+ return softRef.get();
+ }
+
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/refs/WeakReference.java b/PdfView/src/main/java/net/sf/andpdf/refs/WeakReference.java
new file mode 100644
index 0000000..20cd8ef
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/refs/WeakReference.java
@@ -0,0 +1,22 @@
+package net.sf.andpdf.refs;
+
+public class WeakReference {
+
+ java.lang.ref.WeakReference weakRef;
+ HardReference hardRef;
+
+ public WeakReference(T o) {
+ if (HardReference.sKeepCaches)
+ hardRef = new HardReference(o);
+ else
+ weakRef = new java.lang.ref.WeakReference(o);
+ }
+
+ public T get() {
+ if (HardReference.sKeepCaches)
+ return hardRef.get();
+ else
+ return weakRef.get();
+ }
+
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/utils/BiCa.java b/PdfView/src/main/java/net/sf/andpdf/utils/BiCa.java
new file mode 100644
index 0000000..3dfb9fd
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/utils/BiCa.java
@@ -0,0 +1,37 @@
+package net.sf.andpdf.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+
+/**
+ * AWT: BufferedImage creates and stores Graphics2D object for reuse
+ * Use BiCa to simulate this behaviour in PDFRenderer imageRef
+ * @author ferenc.hechler
+ */
+public class BiCa {
+
+ private Bitmap bi;
+ private Canvas ca;
+
+ public BiCa(Bitmap bi, Canvas ca) {
+ this.bi = bi;
+ this.ca = ca;
+ }
+ /**
+ * get stored Bitmap
+ * @return
+ */
+ public Bitmap getBi() {
+ return bi;
+ }
+ /**
+ * create new Canvas or reuse already created
+ * @return
+ */
+ public Canvas createCa() {
+ if (ca == null)
+ ca = new Canvas(bi);
+ return ca;
+ }
+
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/utils/FileUtils.java b/PdfView/src/main/java/net/sf/andpdf/utils/FileUtils.java
new file mode 100644
index 0000000..862404c
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/utils/FileUtils.java
@@ -0,0 +1,43 @@
+package net.sf.andpdf.utils;
+
+import android.content.Context;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class FileUtils {
+
+ private FileUtils() {
+ // Prevents instantiation
+ }
+
+ public static File fileFromAsset(Context context, String assetName) throws IOException {
+ File outFile = new File(context.getCacheDir(), assetName + "-pdfview.pdf");
+ copy(context.getAssets().open(assetName), outFile);
+ return outFile;
+ }
+
+ public static void copy(InputStream inputStream, File output) throws IOException {
+ OutputStream outputStream = null;
+ try {
+ outputStream = new FileOutputStream(output);
+ int read = 0;
+ byte[] bytes = new byte[1024];
+ while ((read = inputStream.read(bytes)) != -1) {
+ outputStream.write(bytes, 0, read);
+ }
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } finally {
+ if (outputStream != null) {
+ outputStream.close();
+ }
+ }
+ }
+ }
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/utils/MathUtils.java b/PdfView/src/main/java/net/sf/andpdf/utils/MathUtils.java
new file mode 100755
index 0000000..9eb9e3c
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/utils/MathUtils.java
@@ -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);
+ }
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/utils/Utils.java b/PdfView/src/main/java/net/sf/andpdf/utils/Utils.java
new file mode 100644
index 0000000..8d7d379
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/utils/Utils.java
@@ -0,0 +1,39 @@
+package net.sf.andpdf.utils;
+
+import android.graphics.Matrix;
+
+public class Utils {
+
+ /**
+ * MSCALE_X MSKEW_X MTRANS_X
+ * MSKEW_Y MSCALE_Y MTRANS_Y
+ * MPERSP_0 MPERSP_1 MPERSP_2
+ *
+ * @param mat
+ * @param m00 MSCALE_X
+ * @param m01 MSKEW_X
+ * @param m10 MSKEW_Y
+ * @param m11 MSCALE_Y
+ * @param m02 MTRANS_X
+ * @param m12 MTRANS_Y
+ */
+ public static void setMatValues(Matrix mat, float m00, float m01, float m10, float m11, float m02, float m12) {
+ float[] fs = {m00, m10, m02, m01, m11, m12, 0,0,1};
+// float[] fs = {m00, m01, m02, m10, m11, m12, 0,0,1};
+ mat.setValues(fs);
+ }
+
+ public static void setMatValues(Matrix mat, float[] m6) {
+ setMatValues(mat, m6[0], m6[1], m6[2], m6[3], m6[4], m6[5]);
+ }
+
+ public static Matrix createMatrix(float m00, float m01, float m10, float m11, float m02, float m12) {
+ Matrix result = new Matrix();
+ setMatValues(result, m00,m01,m10,m11,m02,m12);
+ return result;
+ }
+ public static Matrix createMatrix(float[] m6) {
+ return createMatrix(m6[0], m6[1], m6[2], m6[3], m6[4], m6[5]);
+ }
+
+}
diff --git a/PdfView/src/main/java/net/sf/andpdf/utils/VectorF.java b/PdfView/src/main/java/net/sf/andpdf/utils/VectorF.java
new file mode 100644
index 0000000..b0669af
--- /dev/null
+++ b/PdfView/src/main/java/net/sf/andpdf/utils/VectorF.java
@@ -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;
+ }
+
+
+}
diff --git a/PdfView/src/main/java/org/bouncycastle/crypto/CipherParameters.java b/PdfView/src/main/java/org/bouncycastle/crypto/CipherParameters.java
new file mode 100644
index 0000000..5be8730
--- /dev/null
+++ b/PdfView/src/main/java/org/bouncycastle/crypto/CipherParameters.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.crypto;
+
+/**
+ * all parameter classes implement this.
+ */
+public interface CipherParameters
+{
+}
diff --git a/PdfView/src/main/java/org/bouncycastle/crypto/DataLengthException.java b/PdfView/src/main/java/org/bouncycastle/crypto/DataLengthException.java
new file mode 100644
index 0000000..fbf047c
--- /dev/null
+++ b/PdfView/src/main/java/org/bouncycastle/crypto/DataLengthException.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.crypto;
+
+/**
+ * this exception is thrown if a buffer that is meant to have output
+ * copied into it turns out to be too short, or if we've been given
+ * insufficient input. In general this exception will get thrown rather
+ * than an ArrayOutOfBounds exception.
+ */
+public class DataLengthException
+ extends RuntimeCryptoException
+{
+ /**
+ * base constructor.
+ */
+ public DataLengthException()
+ {
+ }
+
+ /**
+ * create a DataLengthException with the given message.
+ *
+ * @param message the message to be carried with the exception.
+ */
+ public DataLengthException(
+ String message)
+ {
+ super(message);
+ }
+}
diff --git a/PdfView/src/main/java/org/bouncycastle/crypto/RuntimeCryptoException.java b/PdfView/src/main/java/org/bouncycastle/crypto/RuntimeCryptoException.java
new file mode 100644
index 0000000..c157202
--- /dev/null
+++ b/PdfView/src/main/java/org/bouncycastle/crypto/RuntimeCryptoException.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.crypto;
+
+/**
+ * the foundation class for the exceptions thrown by the crypto packages.
+ */
+public class RuntimeCryptoException
+ extends RuntimeException
+{
+ /**
+ * base constructor.
+ */
+ public RuntimeCryptoException()
+ {
+ }
+
+ /**
+ * create a RuntimeCryptoException with the given message.
+ *
+ * @param message the message to be carried with the exception.
+ */
+ public RuntimeCryptoException(
+ String message)
+ {
+ super(message);
+ }
+}
diff --git a/PdfView/src/main/java/org/bouncycastle/crypto/StreamCipher.java b/PdfView/src/main/java/org/bouncycastle/crypto/StreamCipher.java
new file mode 100644
index 0000000..2a55d4f
--- /dev/null
+++ b/PdfView/src/main/java/org/bouncycastle/crypto/StreamCipher.java
@@ -0,0 +1,53 @@
+package org.bouncycastle.crypto;
+
+/**
+ * the interface stream ciphers conform to.
+ */
+public interface StreamCipher
+{
+ /**
+ * Initialise the cipher.
+ *
+ * @param forEncryption if true the cipher is initialised for
+ * encryption, if false for decryption.
+ * @param params the key and other data required by the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(boolean forEncryption, CipherParameters params)
+ throws IllegalArgumentException;
+
+ /**
+ * Return the name of the algorithm the cipher implements.
+ *
+ * @return the name of the algorithm the cipher implements.
+ */
+ public String getAlgorithmName();
+
+ /**
+ * encrypt/decrypt a single byte returning the result.
+ *
+ * @param in the byte to be processed.
+ * @return the result of processing the input byte.
+ */
+ public byte returnByte(byte in);
+
+ /**
+ * process a block of bytes from in putting the result into out.
+ *
+ * @param in the input byte array.
+ * @param inOff the offset into the in array where the data to be processed starts.
+ * @param len the number of bytes to be processed.
+ * @param out the output buffer the processed bytes go into.
+ * @param outOff the offset into the output byte array the processed data starts at.
+ * @exception DataLengthException if the output buffer is too small.
+ */
+ public void processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+ throws DataLengthException;
+
+ /**
+ * reset the cipher. This leaves it in the same state
+ * it was at after the last init (if there was one).
+ */
+ public void reset();
+}
diff --git a/PdfView/src/main/java/org/bouncycastle/crypto/engines/RC4Engine.java b/PdfView/src/main/java/org/bouncycastle/crypto/engines/RC4Engine.java
new file mode 100644
index 0000000..e7a9cdd
--- /dev/null
+++ b/PdfView/src/main/java/org/bouncycastle/crypto/engines/RC4Engine.java
@@ -0,0 +1,143 @@
+package org.bouncycastle.crypto.engines;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+public class RC4Engine implements StreamCipher
+{
+ private final static int STATE_LENGTH = 256;
+
+ /*
+ * variables to hold the state of the RC4 engine
+ * during encryption and decryption
+ */
+
+ private byte[] engineState = null;
+ private int x = 0;
+ private int y = 0;
+ private byte[] workingKey = null;
+
+ /**
+ * initialise a RC4 cipher.
+ *
+ * @param forEncryption whether or not we are for encryption.
+ * @param params the parameters required to set up the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(
+ boolean forEncryption,
+ CipherParameters params
+ )
+ {
+ if (params instanceof KeyParameter)
+ {
+ /*
+ * RC4 encryption and decryption is completely
+ * symmetrical, so the 'forEncryption' is
+ * irrelevant.
+ */
+ workingKey = ((KeyParameter)params).getKey();
+ setKey(workingKey);
+
+ return;
+ }
+
+ throw new IllegalArgumentException("invalid parameter passed to RC4 init - " + params.getClass().getName());
+ }
+
+ public String getAlgorithmName()
+ {
+ return "RC4";
+ }
+
+ public byte returnByte(byte in)
+ {
+ x = (x + 1) & 0xff;
+ y = (engineState[x] + y) & 0xff;
+
+ // swap
+ byte tmp = engineState[x];
+ engineState[x] = engineState[y];
+ engineState[y] = tmp;
+
+ // xor
+ return (byte)(in ^ engineState[(engineState[x] + engineState[y]) & 0xff]);
+ }
+
+ public void processBytes(
+ byte[] in,
+ int inOff,
+ int len,
+ byte[] out,
+ int outOff)
+ {
+ if ((inOff + len) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + len) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ for (int i = 0; i < len ; i++)
+ {
+ x = (x + 1) & 0xff;
+ y = (engineState[x] + y) & 0xff;
+
+ // swap
+ byte tmp = engineState[x];
+ engineState[x] = engineState[y];
+ engineState[y] = tmp;
+
+ // xor
+ out[i+outOff] = (byte)(in[i + inOff]
+ ^ engineState[(engineState[x] + engineState[y]) & 0xff]);
+ }
+ }
+
+ public void reset()
+ {
+ setKey(workingKey);
+ }
+
+ // Private implementation
+
+ private void setKey(byte[] keyBytes)
+ {
+ workingKey = keyBytes;
+
+ // System.out.println("the key length is ; "+ workingKey.length);
+
+ x = 0;
+ y = 0;
+
+ if (engineState == null)
+ {
+ engineState = new byte[STATE_LENGTH];
+ }
+
+ // reset the state of the engine
+ for (int i=0; i < STATE_LENGTH; i++)
+ {
+ engineState[i] = (byte)i;
+ }
+
+ int i1 = 0;
+ int i2 = 0;
+
+ for (int i=0; i < STATE_LENGTH; i++)
+ {
+ i2 = ((keyBytes[i1] & 0xff) + engineState[i] + i2) & 0xff;
+ // do the byte-swap inline
+ byte tmp = engineState[i];
+ engineState[i] = engineState[i2];
+ engineState[i2] = tmp;
+ i1 = (i1+1) % keyBytes.length;
+ }
+ }
+}
diff --git a/PdfView/src/main/java/org/bouncycastle/crypto/params/KeyParameter.java b/PdfView/src/main/java/org/bouncycastle/crypto/params/KeyParameter.java
new file mode 100644
index 0000000..5c4fe0e
--- /dev/null
+++ b/PdfView/src/main/java/org/bouncycastle/crypto/params/KeyParameter.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.crypto.params;
+
+import org.bouncycastle.crypto.CipherParameters;
+
+public class KeyParameter
+ implements CipherParameters
+{
+ private byte[] key;
+
+ public KeyParameter(
+ byte[] key)
+ {
+ this(key, 0, key.length);
+ }
+
+ public KeyParameter(
+ byte[] key,
+ int keyOff,
+ int keyLen)
+ {
+ this.key = new byte[keyLen];
+
+ System.arraycopy(key, keyOff, this.key, 0, keyLen);
+ }
+
+ public byte[] getKey()
+ {
+ return key;
+ }
+}
diff --git a/PdfView/src/main/res/anim/slide_in.xml b/PdfView/src/main/res/anim/slide_in.xml
new file mode 100644
index 0000000..32b65c8
--- /dev/null
+++ b/PdfView/src/main/res/anim/slide_in.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/PdfView/src/main/res/anim/slide_out.xml b/PdfView/src/main/res/anim/slide_out.xml
new file mode 100644
index 0000000..f74390c
--- /dev/null
+++ b/PdfView/src/main/res/anim/slide_out.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/PdfView/src/main/res/drawable-hdpi/icon.png b/PdfView/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
Binary files /dev/null and b/PdfView/src/main/res/drawable-hdpi/icon.png differ
diff --git a/PdfView/src/main/res/drawable-ldpi/icon.png b/PdfView/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
Binary files /dev/null and b/PdfView/src/main/res/drawable-ldpi/icon.png differ
diff --git a/PdfView/src/main/res/drawable-mdpi/icon.png b/PdfView/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
Binary files /dev/null and b/PdfView/src/main/res/drawable-mdpi/icon.png differ
diff --git a/PdfView/src/main/res/drawable/back01.png b/PdfView/src/main/res/drawable/back01.png
new file mode 100644
index 0000000..65c583e
Binary files /dev/null and b/PdfView/src/main/res/drawable/back01.png differ
diff --git a/PdfView/src/main/res/drawable/back02.png b/PdfView/src/main/res/drawable/back02.png
new file mode 100644
index 0000000..7427467
Binary files /dev/null and b/PdfView/src/main/res/drawable/back02.png differ
diff --git a/PdfView/src/main/res/drawable/doc.png b/PdfView/src/main/res/drawable/doc.png
new file mode 100644
index 0000000..e6faab9
Binary files /dev/null and b/PdfView/src/main/res/drawable/doc.png differ
diff --git a/PdfView/src/main/res/drawable/folder.png b/PdfView/src/main/res/drawable/folder.png
new file mode 100644
index 0000000..6b5ba9d
Binary files /dev/null and b/PdfView/src/main/res/drawable/folder.png differ
diff --git a/PdfView/src/main/res/drawable/icon.png b/PdfView/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..d6ceb0f
Binary files /dev/null and b/PdfView/src/main/res/drawable/icon.png differ
diff --git a/PdfView/src/main/res/drawable/left_arrow.png b/PdfView/src/main/res/drawable/left_arrow.png
new file mode 100644
index 0000000..5a479e5
Binary files /dev/null and b/PdfView/src/main/res/drawable/left_arrow.png differ
diff --git a/PdfView/src/main/res/drawable/pdf.png b/PdfView/src/main/res/drawable/pdf.png
new file mode 100644
index 0000000..94d8dc5
Binary files /dev/null and b/PdfView/src/main/res/drawable/pdf.png differ
diff --git a/PdfView/src/main/res/drawable/right_arrow.png b/PdfView/src/main/res/drawable/right_arrow.png
new file mode 100644
index 0000000..863b785
Binary files /dev/null and b/PdfView/src/main/res/drawable/right_arrow.png differ
diff --git a/PdfView/src/main/res/drawable/zoom_in.png b/PdfView/src/main/res/drawable/zoom_in.png
new file mode 100644
index 0000000..b7adbaf
Binary files /dev/null and b/PdfView/src/main/res/drawable/zoom_in.png differ
diff --git a/PdfView/src/main/res/drawable/zoom_out.png b/PdfView/src/main/res/drawable/zoom_out.png
new file mode 100644
index 0000000..c98b9d5
Binary files /dev/null and b/PdfView/src/main/res/drawable/zoom_out.png differ
diff --git a/PdfView/src/main/res/layout/dialog_pagenumber.xml b/PdfView/src/main/res/layout/dialog_pagenumber.xml
new file mode 100644
index 0000000..40f2dbf
--- /dev/null
+++ b/PdfView/src/main/res/layout/dialog_pagenumber.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/PdfView/src/main/res/layout/graphics_view.xml b/PdfView/src/main/res/layout/graphics_view.xml
new file mode 100644
index 0000000..c12cd06
--- /dev/null
+++ b/PdfView/src/main/res/layout/graphics_view.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/PdfView/src/main/res/layout/main.xml b/PdfView/src/main/res/layout/main.xml
new file mode 100644
index 0000000..3a5f117
--- /dev/null
+++ b/PdfView/src/main/res/layout/main.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/PdfView/src/main/res/layout/navigation_overlay.xml b/PdfView/src/main/res/layout/navigation_overlay.xml
new file mode 100644
index 0000000..371853a
--- /dev/null
+++ b/PdfView/src/main/res/layout/navigation_overlay.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PdfView/src/main/res/layout/pdf_file_password.xml b/PdfView/src/main/res/layout/pdf_file_password.xml
new file mode 100644
index 0000000..178cd3b
--- /dev/null
+++ b/PdfView/src/main/res/layout/pdf_file_password.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PdfView/src/main/res/layout/scroll_layout.xml b/PdfView/src/main/res/layout/scroll_layout.xml
new file mode 100644
index 0000000..4f87461
--- /dev/null
+++ b/PdfView/src/main/res/layout/scroll_layout.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PdfView/src/main/res/values/color.xml b/PdfView/src/main/res/values/color.xml
new file mode 100644
index 0000000..7e3907f
--- /dev/null
+++ b/PdfView/src/main/res/values/color.xml
@@ -0,0 +1,7 @@
+
+
+ #FFFFFFFF
+ #000000
+ #000000FF
+ #9370DB
+
diff --git a/PdfView/src/main/res/values/strings.xml b/PdfView/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d26d562
--- /dev/null
+++ b/PdfView/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Hello World, PdfViewerActivity!
+ PdfViewer
+
diff --git a/PdfView/src/main/resources/com/sun/pdfview/.cvsignore b/PdfView/src/main/resources/com/sun/pdfview/.cvsignore
new file mode 100644
index 0000000..6e8821b
--- /dev/null
+++ b/PdfView/src/main/resources/com/sun/pdfview/.cvsignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/PdfView/src/main/resources/com/sun/pdfview/decode/CCITTCodes b/PdfView/src/main/resources/com/sun/pdfview/decode/CCITTCodes
new file mode 100644
index 0000000..f6671c0
--- /dev/null
+++ b/PdfView/src/main/resources/com/sun/pdfview/decode/CCITTCodes
@@ -0,0 +1,236 @@
+
+# $Id: CCITTCodes,v 1.2 2007/12/20 18:33:33 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
+
+
+# WHITE CODES
+00110101 0
+000111 1
+0111 2
+1000 3
+1011 4
+1100 5
+1110 6
+1111 7
+10011 8
+10100 9
+00111 10
+01000 11
+001000 12
+000011 13
+110100 14
+110101 15
+101010 16
+101011 17
+0100111 18
+0001100 19
+0001000 20
+0010111 21
+0000011 22
+0000100 23
+0101000 24
+0101011 25
+0010011 26
+0100100 27
+0011000 28
+00000010 29
+00000011 30
+00011010 31
+00011011 32
+00010010 33
+00010011 34
+00010100 35
+00010101 36
+00010110 37
+00010111 38
+00101000 39
+00101001 40
+00101010 41
+00101011 42
+00101100 43
+00101101 44
+00000100 45
+00000101 46
+00001010 47
+00001011 48
+01010010 49
+01010011 50
+01010100 51
+01010101 52
+00100100 53
+00100101 54
+01011000 55
+01011001 56
+01011010 57
+01011011 58
+01001010 59
+01001011 60
+00110010 61
+00110011 62
+00110100 63
+11011 64
+10010 128
+010111 192
+0110111 256
+00110110 320
+00110111 384
+01100100 448
+01100101 512
+01101000 576
+01100111 640
+011001100 704
+011001101 768
+011010010 832
+011010011 896
+011010100 960
+011010101 1024
+011010110 1088
+011010111 1152
+011011000 1216
+011011001 1280
+011011010 1344
+011011011 1408
+010011000 1472
+010011001 1536
+010011010 1600
+011000 1664
+010011011 1728
+00000001000 1792
+00000001100 1856
+00000001101 1920
+000000010010 1984
+000000010011 2048
+000000010100 2112
+000000010101 2176
+000000010110 2240
+000000010111 2304
+000000011100 2368
+000000011101 2432
+000000011110 2496
+000000011111 2560
+000000001111 -2
+0000000000 -1
+
+# BLACK CODES
+0000110111 0
+010 1
+11 2
+10 3
+011 4
+0011 5
+0010 6
+00011 7
+000101 8
+000100 9
+0000100 10
+0000101 11
+0000111 12
+00000100 13
+00000111 14
+000011000 15
+0000010111 16
+0000011000 17
+0000001000 18
+00001100111 19
+00001101000 20
+00001101100 21
+00000110111 22
+00000101000 23
+00000010111 24
+00000011000 25
+000011001010 26
+000011001011 27
+000011001100 28
+000011001101 29
+000001101000 30
+000001101001 31
+000001101010 32
+000001101011 33
+000011010010 34
+000011010011 35
+000011010100 36
+000011010101 37
+000011010110 38
+000011010111 39
+000001101100 40
+000001101101 41
+000011011010 42
+000011011011 43
+000001010100 44
+000001010101 45
+000001010110 46
+000001010111 47
+000001100100 48
+000001100101 49
+000001010010 50
+000001010011 51
+000000100100 52
+000000110111 53
+000000111000 54
+000000100111 55
+000000101000 56
+000001011000 57
+000001011001 58
+000000101011 59
+000000101100 60
+000001011010 61
+000001100110 62
+000001100111 63
+0000001111 64
+000011001000 128
+000011001001 192
+000001011011 256
+000000110011 320
+000000110100 384
+000000110101 448
+0000001101100 512
+0000001101101 576
+0000001001010 640
+0000001001011 704
+0000001001100 768
+0000001001101 832
+0000001110010 896
+0000001110011 960
+0000001110100 1024
+0000001110101 1088
+0000001110110 1152
+0000001110111 1216
+0000001010010 1280
+0000001010011 1344
+0000001010100 1408
+0000001010101 1472
+0000001011010 1536
+0000001011011 1600
+0000001100100 1664
+0000001100101 1728
+00000001000 1792
+00000001100 1856
+00000001101 1920
+000000010010 1984
+000000010011 2048
+000000010100 2112
+000000010101 2176
+000000010110 2240
+000000010111 2304
+000000011100 2368
+000000011101 2432
+000000011110 2496
+000000011111 2560
+000000001111 -2
+00000000000 -1
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/BaseFonts.properties b/PdfView/src/main/resources/com/sun/pdfview/font/res/BaseFonts.properties
new file mode 100644
index 0000000..3f94a60
--- /dev/null
+++ b/PdfView/src/main/resources/com/sun/pdfview/font/res/BaseFonts.properties
@@ -0,0 +1,57 @@
+# Sample ResourceBundle properties file
+
+Courier.file=n022003l.pfb
+
+Courier.length=96263
+
+Courier-Bold.file=n022004l.pfb
+
+Courier-Bold.length=120373
+
+Courier-BoldOblique.file=n022024l.pfb
+
+Courier-BoldOblique.length=114228
+
+Courier-Oblique.file=n022023l.pfb
+
+Courier-Oblique.length=101133
+
+Helvetica.file=n019003l.pfb
+
+Helvetica.length=68590
+
+Helvetica-Bold.file=n019004l.pfb
+
+Helvetica-Bold.length=72400
+
+Helvetica-BoldOblique.file=n019024l.pfb
+
+Helvetica-BoldOblique.length=73879
+
+Helvetica-Oblique.file=n019023l.pfb
+
+Helvetica-Oblique.length=71719
+
+Times-Roman.file=n021003l.pfb
+
+Times-Roman.length=113206
+
+Times-Bold.file=n021004l.pfb
+
+Times-Bold.length=108822
+
+Times-BoldItalic.file=n021024l.pfb
+
+Times-BoldItalic.length=96211
+
+Times-Italic.file=n021023l.pfb
+
+Times-Italic.length=108217
+
+Symbol.file=s050000l.pfb
+
+Symbol.length=32213
+
+ZapfDingbats.file=d050000l.pfb
+
+ZapfDingbats.length=45955
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/d050000l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/d050000l.pfb
new file mode 100644
index 0000000..4a3c386
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/d050000l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n019003l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019003l.pfb
new file mode 100644
index 0000000..82b6ad8
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019003l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n019004l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019004l.pfb
new file mode 100644
index 0000000..94a3020
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019004l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n019023l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019023l.pfb
new file mode 100644
index 0000000..b124a58
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019023l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n019024l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019024l.pfb
new file mode 100644
index 0000000..1a9d6f5
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n019024l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n021003l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021003l.pfb
new file mode 100644
index 0000000..08261c6
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021003l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n021004l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021004l.pfb
new file mode 100644
index 0000000..2b59a67
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021004l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n021023l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021023l.pfb
new file mode 100644
index 0000000..8e4db51
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021023l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n021024l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021024l.pfb
new file mode 100644
index 0000000..67bc587
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n021024l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n022003l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022003l.pfb
new file mode 100644
index 0000000..ecd871a
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022003l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n022004l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022004l.pfb
new file mode 100644
index 0000000..da2d22c
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022004l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n022023l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022023l.pfb
new file mode 100644
index 0000000..b0edaf2
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022023l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/n022024l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022024l.pfb
new file mode 100644
index 0000000..f32adb4
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/n022024l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/res/s050000l.pfb b/PdfView/src/main/resources/com/sun/pdfview/font/res/s050000l.pfb
new file mode 100644
index 0000000..3c16723
Binary files /dev/null and b/PdfView/src/main/resources/com/sun/pdfview/font/res/s050000l.pfb differ
diff --git a/PdfView/src/main/resources/com/sun/pdfview/font/ttf/resource/glyphlist.txt b/PdfView/src/main/resources/com/sun/pdfview/font/ttf/resource/glyphlist.txt
new file mode 100644
index 0000000..d91b136
--- /dev/null
+++ b/PdfView/src/main/resources/com/sun/pdfview/font/ttf/resource/glyphlist.txt
@@ -0,0 +1,4323 @@
+# ###################################################################################
+# Copyright (c) 1997,1998,2002,2007 Adobe Systems Incorporated
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this documentation file to use, copy, publish, distribute,
+# sublicense, and/or sell copies of the documentation, and to permit
+# others to do the same, provided that:
+# - No modification, editing or other alteration of this document is
+# allowed; and
+# - The above copyright notice and this permission notice shall be
+# included in all copies of the documentation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this documentation file, to create their own derivative works
+# from the content of this document to use, copy, publish, distribute,
+# sublicense, and/or sell the derivative works, and to permit others to do
+# the same, provided that the derived work is not represented as being a
+# copy or version of this document.
+#
+# Adobe shall not be liable to any party for any loss of revenue or profit
+# or for indirect, incidental, special, consequential, or other similar
+# damages, whether based on tort (including without limitation negligence
+# or strict liability), contract or other legal or equitable grounds even
+# if Adobe has been advised or had reason to know of the possibility of
+# such damages.Ê The Adobe materials are provided on an "AS IS" basis.Ê
+# Adobe specifically disclaims all express, statutory, or implied
+# warranties relating to the Adobe materials, including but not limited to
+# those concerning merchantability or fitness for a particular purpose or
+# non-infringement of any third party rights regarding the Adobe
+# materials.
+# ###################################################################################
+# Name: Adobe Glyph List
+# Table version: 2.0
+# Date: September 20, 2002
+#
+# See http://partners.adobe.com/asn/developer/typeforum/unicodegn.html
+#
+# Format: Semicolon-delimited fields:
+# (1) glyph name
+# (2) Unicode scalar value
+A;0041
+AE;00C6
+AEacute;01FC
+AEmacron;01E2
+AEsmall;F7E6
+Aacute;00C1
+Aacutesmall;F7E1
+Abreve;0102
+Abreveacute;1EAE
+Abrevecyrillic;04D0
+Abrevedotbelow;1EB6
+Abrevegrave;1EB0
+Abrevehookabove;1EB2
+Abrevetilde;1EB4
+Acaron;01CD
+Acircle;24B6
+Acircumflex;00C2
+Acircumflexacute;1EA4
+Acircumflexdotbelow;1EAC
+Acircumflexgrave;1EA6
+Acircumflexhookabove;1EA8
+Acircumflexsmall;F7E2
+Acircumflextilde;1EAA
+Acute;F6C9
+Acutesmall;F7B4
+Acyrillic;0410
+Adblgrave;0200
+Adieresis;00C4
+Adieresiscyrillic;04D2
+Adieresismacron;01DE
+Adieresissmall;F7E4
+Adotbelow;1EA0
+Adotmacron;01E0
+Agrave;00C0
+Agravesmall;F7E0
+Ahookabove;1EA2
+Aiecyrillic;04D4
+Ainvertedbreve;0202
+Alpha;0391
+Alphatonos;0386
+Amacron;0100
+Amonospace;FF21
+Aogonek;0104
+Aring;00C5
+Aringacute;01FA
+Aringbelow;1E00
+Aringsmall;F7E5
+Asmall;F761
+Atilde;00C3
+Atildesmall;F7E3
+Aybarmenian;0531
+B;0042
+Bcircle;24B7
+Bdotaccent;1E02
+Bdotbelow;1E04
+Becyrillic;0411
+Benarmenian;0532
+Beta;0392
+Bhook;0181
+Blinebelow;1E06
+Bmonospace;FF22
+Brevesmall;F6F4
+Bsmall;F762
+Btopbar;0182
+C;0043
+Caarmenian;053E
+Cacute;0106
+Caron;F6CA
+Caronsmall;F6F5
+Ccaron;010C
+Ccedilla;00C7
+Ccedillaacute;1E08
+Ccedillasmall;F7E7
+Ccircle;24B8
+Ccircumflex;0108
+Cdot;010A
+Cdotaccent;010A
+Cedillasmall;F7B8
+Chaarmenian;0549
+Cheabkhasiancyrillic;04BC
+Checyrillic;0427
+Chedescenderabkhasiancyrillic;04BE
+Chedescendercyrillic;04B6
+Chedieresiscyrillic;04F4
+Cheharmenian;0543
+Chekhakassiancyrillic;04CB
+Cheverticalstrokecyrillic;04B8
+Chi;03A7
+Chook;0187
+Circumflexsmall;F6F6
+Cmonospace;FF23
+Coarmenian;0551
+Csmall;F763
+D;0044
+DZ;01F1
+DZcaron;01C4
+Daarmenian;0534
+Dafrican;0189
+Dcaron;010E
+Dcedilla;1E10
+Dcircle;24B9
+Dcircumflexbelow;1E12
+Dcroat;0110
+Ddotaccent;1E0A
+Ddotbelow;1E0C
+Decyrillic;0414
+Deicoptic;03EE
+Delta;2206
+Deltagreek;0394
+Dhook;018A
+Dieresis;F6CB
+DieresisAcute;F6CC
+DieresisGrave;F6CD
+Dieresissmall;F7A8
+Digammagreek;03DC
+Djecyrillic;0402
+Dlinebelow;1E0E
+Dmonospace;FF24
+Dotaccentsmall;F6F7
+Dslash;0110
+Dsmall;F764
+Dtopbar;018B
+Dz;01F2
+Dzcaron;01C5
+Dzeabkhasiancyrillic;04E0
+Dzecyrillic;0405
+Dzhecyrillic;040F
+E;0045
+Eacute;00C9
+Eacutesmall;F7E9
+Ebreve;0114
+Ecaron;011A
+Ecedillabreve;1E1C
+Echarmenian;0535
+Ecircle;24BA
+Ecircumflex;00CA
+Ecircumflexacute;1EBE
+Ecircumflexbelow;1E18
+Ecircumflexdotbelow;1EC6
+Ecircumflexgrave;1EC0
+Ecircumflexhookabove;1EC2
+Ecircumflexsmall;F7EA
+Ecircumflextilde;1EC4
+Ecyrillic;0404
+Edblgrave;0204
+Edieresis;00CB
+Edieresissmall;F7EB
+Edot;0116
+Edotaccent;0116
+Edotbelow;1EB8
+Efcyrillic;0424
+Egrave;00C8
+Egravesmall;F7E8
+Eharmenian;0537
+Ehookabove;1EBA
+Eightroman;2167
+Einvertedbreve;0206
+Eiotifiedcyrillic;0464
+Elcyrillic;041B
+Elevenroman;216A
+Emacron;0112
+Emacronacute;1E16
+Emacrongrave;1E14
+Emcyrillic;041C
+Emonospace;FF25
+Encyrillic;041D
+Endescendercyrillic;04A2
+Eng;014A
+Enghecyrillic;04A4
+Enhookcyrillic;04C7
+Eogonek;0118
+Eopen;0190
+Epsilon;0395
+Epsilontonos;0388
+Ercyrillic;0420
+Ereversed;018E
+Ereversedcyrillic;042D
+Escyrillic;0421
+Esdescendercyrillic;04AA
+Esh;01A9
+Esmall;F765
+Eta;0397
+Etarmenian;0538
+Etatonos;0389
+Eth;00D0
+Ethsmall;F7F0
+Etilde;1EBC
+Etildebelow;1E1A
+Euro;20AC
+Ezh;01B7
+Ezhcaron;01EE
+Ezhreversed;01B8
+F;0046
+Fcircle;24BB
+Fdotaccent;1E1E
+Feharmenian;0556
+Feicoptic;03E4
+Fhook;0191
+Fitacyrillic;0472
+Fiveroman;2164
+Fmonospace;FF26
+Fourroman;2163
+Fsmall;F766
+G;0047
+GBsquare;3387
+Gacute;01F4
+Gamma;0393
+Gammaafrican;0194
+Gangiacoptic;03EA
+Gbreve;011E
+Gcaron;01E6
+Gcedilla;0122
+Gcircle;24BC
+Gcircumflex;011C
+Gcommaaccent;0122
+Gdot;0120
+Gdotaccent;0120
+Gecyrillic;0413
+Ghadarmenian;0542
+Ghemiddlehookcyrillic;0494
+Ghestrokecyrillic;0492
+Gheupturncyrillic;0490
+Ghook;0193
+Gimarmenian;0533
+Gjecyrillic;0403
+Gmacron;1E20
+Gmonospace;FF27
+Grave;F6CE
+Gravesmall;F760
+Gsmall;F767
+Gsmallhook;029B
+Gstroke;01E4
+H;0048
+H18533;25CF
+H18543;25AA
+H18551;25AB
+H22073;25A1
+HPsquare;33CB
+Haabkhasiancyrillic;04A8
+Hadescendercyrillic;04B2
+Hardsigncyrillic;042A
+Hbar;0126
+Hbrevebelow;1E2A
+Hcedilla;1E28
+Hcircle;24BD
+Hcircumflex;0124
+Hdieresis;1E26
+Hdotaccent;1E22
+Hdotbelow;1E24
+Hmonospace;FF28
+Hoarmenian;0540
+Horicoptic;03E8
+Hsmall;F768
+Hungarumlaut;F6CF
+Hungarumlautsmall;F6F8
+Hzsquare;3390
+I;0049
+IAcyrillic;042F
+IJ;0132
+IUcyrillic;042E
+Iacute;00CD
+Iacutesmall;F7ED
+Ibreve;012C
+Icaron;01CF
+Icircle;24BE
+Icircumflex;00CE
+Icircumflexsmall;F7EE
+Icyrillic;0406
+Idblgrave;0208
+Idieresis;00CF
+Idieresisacute;1E2E
+Idieresiscyrillic;04E4
+Idieresissmall;F7EF
+Idot;0130
+Idotaccent;0130
+Idotbelow;1ECA
+Iebrevecyrillic;04D6
+Iecyrillic;0415
+Ifraktur;2111
+Igrave;00CC
+Igravesmall;F7EC
+Ihookabove;1EC8
+Iicyrillic;0418
+Iinvertedbreve;020A
+Iishortcyrillic;0419
+Imacron;012A
+Imacroncyrillic;04E2
+Imonospace;FF29
+Iniarmenian;053B
+Iocyrillic;0401
+Iogonek;012E
+Iota;0399
+Iotaafrican;0196
+Iotadieresis;03AA
+Iotatonos;038A
+Ismall;F769
+Istroke;0197
+Itilde;0128
+Itildebelow;1E2C
+Izhitsacyrillic;0474
+Izhitsadblgravecyrillic;0476
+J;004A
+Jaarmenian;0541
+Jcircle;24BF
+Jcircumflex;0134
+Jecyrillic;0408
+Jheharmenian;054B
+Jmonospace;FF2A
+Jsmall;F76A
+K;004B
+KBsquare;3385
+KKsquare;33CD
+Kabashkircyrillic;04A0
+Kacute;1E30
+Kacyrillic;041A
+Kadescendercyrillic;049A
+Kahookcyrillic;04C3
+Kappa;039A
+Kastrokecyrillic;049E
+Kaverticalstrokecyrillic;049C
+Kcaron;01E8
+Kcedilla;0136
+Kcircle;24C0
+Kcommaaccent;0136
+Kdotbelow;1E32
+Keharmenian;0554
+Kenarmenian;053F
+Khacyrillic;0425
+Kheicoptic;03E6
+Khook;0198
+Kjecyrillic;040C
+Klinebelow;1E34
+Kmonospace;FF2B
+Koppacyrillic;0480
+Koppagreek;03DE
+Ksicyrillic;046E
+Ksmall;F76B
+L;004C
+LJ;01C7
+LL;F6BF
+Lacute;0139
+Lambda;039B
+Lcaron;013D
+Lcedilla;013B
+Lcircle;24C1
+Lcircumflexbelow;1E3C
+Lcommaaccent;013B
+Ldot;013F
+Ldotaccent;013F
+Ldotbelow;1E36
+Ldotbelowmacron;1E38
+Liwnarmenian;053C
+Lj;01C8
+Ljecyrillic;0409
+Llinebelow;1E3A
+Lmonospace;FF2C
+Lslash;0141
+Lslashsmall;F6F9
+Lsmall;F76C
+M;004D
+MBsquare;3386
+Macron;F6D0
+Macronsmall;F7AF
+Macute;1E3E
+Mcircle;24C2
+Mdotaccent;1E40
+Mdotbelow;1E42
+Menarmenian;0544
+Mmonospace;FF2D
+Msmall;F76D
+Mturned;019C
+Mu;039C
+N;004E
+NJ;01CA
+Nacute;0143
+Ncaron;0147
+Ncedilla;0145
+Ncircle;24C3
+Ncircumflexbelow;1E4A
+Ncommaaccent;0145
+Ndotaccent;1E44
+Ndotbelow;1E46
+Nhookleft;019D
+Nineroman;2168
+Nj;01CB
+Njecyrillic;040A
+Nlinebelow;1E48
+Nmonospace;FF2E
+Nowarmenian;0546
+Nsmall;F76E
+Ntilde;00D1
+Ntildesmall;F7F1
+Nu;039D
+O;004F
+OE;0152
+OEsmall;F6FA
+Oacute;00D3
+Oacutesmall;F7F3
+Obarredcyrillic;04E8
+Obarreddieresiscyrillic;04EA
+Obreve;014E
+Ocaron;01D1
+Ocenteredtilde;019F
+Ocircle;24C4
+Ocircumflex;00D4
+Ocircumflexacute;1ED0
+Ocircumflexdotbelow;1ED8
+Ocircumflexgrave;1ED2
+Ocircumflexhookabove;1ED4
+Ocircumflexsmall;F7F4
+Ocircumflextilde;1ED6
+Ocyrillic;041E
+Odblacute;0150
+Odblgrave;020C
+Odieresis;00D6
+Odieresiscyrillic;04E6
+Odieresissmall;F7F6
+Odotbelow;1ECC
+Ogoneksmall;F6FB
+Ograve;00D2
+Ogravesmall;F7F2
+Oharmenian;0555
+Ohm;2126
+Ohookabove;1ECE
+Ohorn;01A0
+Ohornacute;1EDA
+Ohorndotbelow;1EE2
+Ohorngrave;1EDC
+Ohornhookabove;1EDE
+Ohorntilde;1EE0
+Ohungarumlaut;0150
+Oi;01A2
+Oinvertedbreve;020E
+Omacron;014C
+Omacronacute;1E52
+Omacrongrave;1E50
+Omega;2126
+Omegacyrillic;0460
+Omegagreek;03A9
+Omegaroundcyrillic;047A
+Omegatitlocyrillic;047C
+Omegatonos;038F
+Omicron;039F
+Omicrontonos;038C
+Omonospace;FF2F
+Oneroman;2160
+Oogonek;01EA
+Oogonekmacron;01EC
+Oopen;0186
+Oslash;00D8
+Oslashacute;01FE
+Oslashsmall;F7F8
+Osmall;F76F
+Ostrokeacute;01FE
+Otcyrillic;047E
+Otilde;00D5
+Otildeacute;1E4C
+Otildedieresis;1E4E
+Otildesmall;F7F5
+P;0050
+Pacute;1E54
+Pcircle;24C5
+Pdotaccent;1E56
+Pecyrillic;041F
+Peharmenian;054A
+Pemiddlehookcyrillic;04A6
+Phi;03A6
+Phook;01A4
+Pi;03A0
+Piwrarmenian;0553
+Pmonospace;FF30
+Psi;03A8
+Psicyrillic;0470
+Psmall;F770
+Q;0051
+Qcircle;24C6
+Qmonospace;FF31
+Qsmall;F771
+R;0052
+Raarmenian;054C
+Racute;0154
+Rcaron;0158
+Rcedilla;0156
+Rcircle;24C7
+Rcommaaccent;0156
+Rdblgrave;0210
+Rdotaccent;1E58
+Rdotbelow;1E5A
+Rdotbelowmacron;1E5C
+Reharmenian;0550
+Rfraktur;211C
+Rho;03A1
+Ringsmall;F6FC
+Rinvertedbreve;0212
+Rlinebelow;1E5E
+Rmonospace;FF32
+Rsmall;F772
+Rsmallinverted;0281
+Rsmallinvertedsuperior;02B6
+S;0053
+SF010000;250C
+SF020000;2514
+SF030000;2510
+SF040000;2518
+SF050000;253C
+SF060000;252C
+SF070000;2534
+SF080000;251C
+SF090000;2524
+SF100000;2500
+SF110000;2502
+SF190000;2561
+SF200000;2562
+SF210000;2556
+SF220000;2555
+SF230000;2563
+SF240000;2551
+SF250000;2557
+SF260000;255D
+SF270000;255C
+SF280000;255B
+SF360000;255E
+SF370000;255F
+SF380000;255A
+SF390000;2554
+SF400000;2569
+SF410000;2566
+SF420000;2560
+SF430000;2550
+SF440000;256C
+SF450000;2567
+SF460000;2568
+SF470000;2564
+SF480000;2565
+SF490000;2559
+SF500000;2558
+SF510000;2552
+SF520000;2553
+SF530000;256B
+SF540000;256A
+Sacute;015A
+Sacutedotaccent;1E64
+Sampigreek;03E0
+Scaron;0160
+Scarondotaccent;1E66
+Scaronsmall;F6FD
+Scedilla;015E
+Schwa;018F
+Schwacyrillic;04D8
+Schwadieresiscyrillic;04DA
+Scircle;24C8
+Scircumflex;015C
+Scommaaccent;0218
+Sdotaccent;1E60
+Sdotbelow;1E62
+Sdotbelowdotaccent;1E68
+Seharmenian;054D
+Sevenroman;2166
+Shaarmenian;0547
+Shacyrillic;0428
+Shchacyrillic;0429
+Sheicoptic;03E2
+Shhacyrillic;04BA
+Shimacoptic;03EC
+Sigma;03A3
+Sixroman;2165
+Smonospace;FF33
+Softsigncyrillic;042C
+Ssmall;F773
+Stigmagreek;03DA
+T;0054
+Tau;03A4
+Tbar;0166
+Tcaron;0164
+Tcedilla;0162
+Tcircle;24C9
+Tcircumflexbelow;1E70
+Tcommaaccent;0162
+Tdotaccent;1E6A
+Tdotbelow;1E6C
+Tecyrillic;0422
+Tedescendercyrillic;04AC
+Tenroman;2169
+Tetsecyrillic;04B4
+Theta;0398
+Thook;01AC
+Thorn;00DE
+Thornsmall;F7FE
+Threeroman;2162
+Tildesmall;F6FE
+Tiwnarmenian;054F
+Tlinebelow;1E6E
+Tmonospace;FF34
+Toarmenian;0539
+Tonefive;01BC
+Tonesix;0184
+Tonetwo;01A7
+Tretroflexhook;01AE
+Tsecyrillic;0426
+Tshecyrillic;040B
+Tsmall;F774
+Twelveroman;216B
+Tworoman;2161
+U;0055
+Uacute;00DA
+Uacutesmall;F7FA
+Ubreve;016C
+Ucaron;01D3
+Ucircle;24CA
+Ucircumflex;00DB
+Ucircumflexbelow;1E76
+Ucircumflexsmall;F7FB
+Ucyrillic;0423
+Udblacute;0170
+Udblgrave;0214
+Udieresis;00DC
+Udieresisacute;01D7
+Udieresisbelow;1E72
+Udieresiscaron;01D9
+Udieresiscyrillic;04F0
+Udieresisgrave;01DB
+Udieresismacron;01D5
+Udieresissmall;F7FC
+Udotbelow;1EE4
+Ugrave;00D9
+Ugravesmall;F7F9
+Uhookabove;1EE6
+Uhorn;01AF
+Uhornacute;1EE8
+Uhorndotbelow;1EF0
+Uhorngrave;1EEA
+Uhornhookabove;1EEC
+Uhorntilde;1EEE
+Uhungarumlaut;0170
+Uhungarumlautcyrillic;04F2
+Uinvertedbreve;0216
+Ukcyrillic;0478
+Umacron;016A
+Umacroncyrillic;04EE
+Umacrondieresis;1E7A
+Umonospace;FF35
+Uogonek;0172
+Upsilon;03A5
+Upsilon1;03D2
+Upsilonacutehooksymbolgreek;03D3
+Upsilonafrican;01B1
+Upsilondieresis;03AB
+Upsilondieresishooksymbolgreek;03D4
+Upsilonhooksymbol;03D2
+Upsilontonos;038E
+Uring;016E
+Ushortcyrillic;040E
+Usmall;F775
+Ustraightcyrillic;04AE
+Ustraightstrokecyrillic;04B0
+Utilde;0168
+Utildeacute;1E78
+Utildebelow;1E74
+V;0056
+Vcircle;24CB
+Vdotbelow;1E7E
+Vecyrillic;0412
+Vewarmenian;054E
+Vhook;01B2
+Vmonospace;FF36
+Voarmenian;0548
+Vsmall;F776
+Vtilde;1E7C
+W;0057
+Wacute;1E82
+Wcircle;24CC
+Wcircumflex;0174
+Wdieresis;1E84
+Wdotaccent;1E86
+Wdotbelow;1E88
+Wgrave;1E80
+Wmonospace;FF37
+Wsmall;F777
+X;0058
+Xcircle;24CD
+Xdieresis;1E8C
+Xdotaccent;1E8A
+Xeharmenian;053D
+Xi;039E
+Xmonospace;FF38
+Xsmall;F778
+Y;0059
+Yacute;00DD
+Yacutesmall;F7FD
+Yatcyrillic;0462
+Ycircle;24CE
+Ycircumflex;0176
+Ydieresis;0178
+Ydieresissmall;F7FF
+Ydotaccent;1E8E
+Ydotbelow;1EF4
+Yericyrillic;042B
+Yerudieresiscyrillic;04F8
+Ygrave;1EF2
+Yhook;01B3
+Yhookabove;1EF6
+Yiarmenian;0545
+Yicyrillic;0407
+Yiwnarmenian;0552
+Ymonospace;FF39
+Ysmall;F779
+Ytilde;1EF8
+Yusbigcyrillic;046A
+Yusbigiotifiedcyrillic;046C
+Yuslittlecyrillic;0466
+Yuslittleiotifiedcyrillic;0468
+Z;005A
+Zaarmenian;0536
+Zacute;0179
+Zcaron;017D
+Zcaronsmall;F6FF
+Zcircle;24CF
+Zcircumflex;1E90
+Zdot;017B
+Zdotaccent;017B
+Zdotbelow;1E92
+Zecyrillic;0417
+Zedescendercyrillic;0498
+Zedieresiscyrillic;04DE
+Zeta;0396
+Zhearmenian;053A
+Zhebrevecyrillic;04C1
+Zhecyrillic;0416
+Zhedescendercyrillic;0496
+Zhedieresiscyrillic;04DC
+Zlinebelow;1E94
+Zmonospace;FF3A
+Zsmall;F77A
+Zstroke;01B5
+a;0061
+aabengali;0986
+aacute;00E1
+aadeva;0906
+aagujarati;0A86
+aagurmukhi;0A06
+aamatragurmukhi;0A3E
+aarusquare;3303
+aavowelsignbengali;09BE
+aavowelsigndeva;093E
+aavowelsigngujarati;0ABE
+abbreviationmarkarmenian;055F
+abbreviationsigndeva;0970
+abengali;0985
+abopomofo;311A
+abreve;0103
+abreveacute;1EAF
+abrevecyrillic;04D1
+abrevedotbelow;1EB7
+abrevegrave;1EB1
+abrevehookabove;1EB3
+abrevetilde;1EB5
+acaron;01CE
+acircle;24D0
+acircumflex;00E2
+acircumflexacute;1EA5
+acircumflexdotbelow;1EAD
+acircumflexgrave;1EA7
+acircumflexhookabove;1EA9
+acircumflextilde;1EAB
+acute;00B4
+acutebelowcmb;0317
+acutecmb;0301
+acutecomb;0301
+acutedeva;0954
+acutelowmod;02CF
+acutetonecmb;0341
+acyrillic;0430
+adblgrave;0201
+addakgurmukhi;0A71
+adeva;0905
+adieresis;00E4
+adieresiscyrillic;04D3
+adieresismacron;01DF
+adotbelow;1EA1
+adotmacron;01E1
+ae;00E6
+aeacute;01FD
+aekorean;3150
+aemacron;01E3
+afii00208;2015
+afii08941;20A4
+afii10017;0410
+afii10018;0411
+afii10019;0412
+afii10020;0413
+afii10021;0414
+afii10022;0415
+afii10023;0401
+afii10024;0416
+afii10025;0417
+afii10026;0418
+afii10027;0419
+afii10028;041A
+afii10029;041B
+afii10030;041C
+afii10031;041D
+afii10032;041E
+afii10033;041F
+afii10034;0420
+afii10035;0421
+afii10036;0422
+afii10037;0423
+afii10038;0424
+afii10039;0425
+afii10040;0426
+afii10041;0427
+afii10042;0428
+afii10043;0429
+afii10044;042A
+afii10045;042B
+afii10046;042C
+afii10047;042D
+afii10048;042E
+afii10049;042F
+afii10050;0490
+afii10051;0402
+afii10052;0403
+afii10053;0404
+afii10054;0405
+afii10055;0406
+afii10056;0407
+afii10057;0408
+afii10058;0409
+afii10059;040A
+afii10060;040B
+afii10061;040C
+afii10062;040E
+afii10063;F6C4
+afii10064;F6C5
+afii10065;0430
+afii10066;0431
+afii10067;0432
+afii10068;0433
+afii10069;0434
+afii10070;0435
+afii10071;0451
+afii10072;0436
+afii10073;0437
+afii10074;0438
+afii10075;0439
+afii10076;043A
+afii10077;043B
+afii10078;043C
+afii10079;043D
+afii10080;043E
+afii10081;043F
+afii10082;0440
+afii10083;0441
+afii10084;0442
+afii10085;0443
+afii10086;0444
+afii10087;0445
+afii10088;0446
+afii10089;0447
+afii10090;0448
+afii10091;0449
+afii10092;044A
+afii10093;044B
+afii10094;044C
+afii10095;044D
+afii10096;044E
+afii10097;044F
+afii10098;0491
+afii10099;0452
+afii10100;0453
+afii10101;0454
+afii10102;0455
+afii10103;0456
+afii10104;0457
+afii10105;0458
+afii10106;0459
+afii10107;045A
+afii10108;045B
+afii10109;045C
+afii10110;045E
+afii10145;040F
+afii10146;0462
+afii10147;0472
+afii10148;0474
+afii10192;F6C6
+afii10193;045F
+afii10194;0463
+afii10195;0473
+afii10196;0475
+afii10831;F6C7
+afii10832;F6C8
+afii10846;04D9
+afii299;200E
+afii300;200F
+afii301;200D
+afii57381;066A
+afii57388;060C
+afii57392;0660
+afii57393;0661
+afii57394;0662
+afii57395;0663
+afii57396;0664
+afii57397;0665
+afii57398;0666
+afii57399;0667
+afii57400;0668
+afii57401;0669
+afii57403;061B
+afii57407;061F
+afii57409;0621
+afii57410;0622
+afii57411;0623
+afii57412;0624
+afii57413;0625
+afii57414;0626
+afii57415;0627
+afii57416;0628
+afii57417;0629
+afii57418;062A
+afii57419;062B
+afii57420;062C
+afii57421;062D
+afii57422;062E
+afii57423;062F
+afii57424;0630
+afii57425;0631
+afii57426;0632
+afii57427;0633
+afii57428;0634
+afii57429;0635
+afii57430;0636
+afii57431;0637
+afii57432;0638
+afii57433;0639
+afii57434;063A
+afii57440;0640
+afii57441;0641
+afii57442;0642
+afii57443;0643
+afii57444;0644
+afii57445;0645
+afii57446;0646
+afii57448;0648
+afii57449;0649
+afii57450;064A
+afii57451;064B
+afii57452;064C
+afii57453;064D
+afii57454;064E
+afii57455;064F
+afii57456;0650
+afii57457;0651
+afii57458;0652
+afii57470;0647
+afii57505;06A4
+afii57506;067E
+afii57507;0686
+afii57508;0698
+afii57509;06AF
+afii57511;0679
+afii57512;0688
+afii57513;0691
+afii57514;06BA
+afii57519;06D2
+afii57534;06D5
+afii57636;20AA
+afii57645;05BE
+afii57658;05C3
+afii57664;05D0
+afii57665;05D1
+afii57666;05D2
+afii57667;05D3
+afii57668;05D4
+afii57669;05D5
+afii57670;05D6
+afii57671;05D7
+afii57672;05D8
+afii57673;05D9
+afii57674;05DA
+afii57675;05DB
+afii57676;05DC
+afii57677;05DD
+afii57678;05DE
+afii57679;05DF
+afii57680;05E0
+afii57681;05E1
+afii57682;05E2
+afii57683;05E3
+afii57684;05E4
+afii57685;05E5
+afii57686;05E6
+afii57687;05E7
+afii57688;05E8
+afii57689;05E9
+afii57690;05EA
+afii57694;FB2A
+afii57695;FB2B
+afii57700;FB4B
+afii57705;FB1F
+afii57716;05F0
+afii57717;05F1
+afii57718;05F2
+afii57723;FB35
+afii57793;05B4
+afii57794;05B5
+afii57795;05B6
+afii57796;05BB
+afii57797;05B8
+afii57798;05B7
+afii57799;05B0
+afii57800;05B2
+afii57801;05B1
+afii57802;05B3
+afii57803;05C2
+afii57804;05C1
+afii57806;05B9
+afii57807;05BC
+afii57839;05BD
+afii57841;05BF
+afii57842;05C0
+afii57929;02BC
+afii61248;2105
+afii61289;2113
+afii61352;2116
+afii61573;202C
+afii61574;202D
+afii61575;202E
+afii61664;200C
+afii63167;066D
+afii64937;02BD
+agrave;00E0
+agujarati;0A85
+agurmukhi;0A05
+ahiragana;3042
+ahookabove;1EA3
+aibengali;0990
+aibopomofo;311E
+aideva;0910
+aiecyrillic;04D5
+aigujarati;0A90
+aigurmukhi;0A10
+aimatragurmukhi;0A48
+ainarabic;0639
+ainfinalarabic;FECA
+aininitialarabic;FECB
+ainmedialarabic;FECC
+ainvertedbreve;0203
+aivowelsignbengali;09C8
+aivowelsigndeva;0948
+aivowelsigngujarati;0AC8
+akatakana;30A2
+akatakanahalfwidth;FF71
+akorean;314F
+alef;05D0
+alefarabic;0627
+alefdageshhebrew;FB30
+aleffinalarabic;FE8E
+alefhamzaabovearabic;0623
+alefhamzaabovefinalarabic;FE84
+alefhamzabelowarabic;0625
+alefhamzabelowfinalarabic;FE88
+alefhebrew;05D0
+aleflamedhebrew;FB4F
+alefmaddaabovearabic;0622
+alefmaddaabovefinalarabic;FE82
+alefmaksuraarabic;0649
+alefmaksurafinalarabic;FEF0
+alefmaksurainitialarabic;FEF3
+alefmaksuramedialarabic;FEF4
+alefpatahhebrew;FB2E
+alefqamatshebrew;FB2F
+aleph;2135
+allequal;224C
+alpha;03B1
+alphatonos;03AC
+amacron;0101
+amonospace;FF41
+ampersand;0026
+ampersandmonospace;FF06
+ampersandsmall;F726
+amsquare;33C2
+anbopomofo;3122
+angbopomofo;3124
+angkhankhuthai;0E5A
+angle;2220
+anglebracketleft;3008
+anglebracketleftvertical;FE3F
+anglebracketright;3009
+anglebracketrightvertical;FE40
+angleleft;2329
+angleright;232A
+angstrom;212B
+anoteleia;0387
+anudattadeva;0952
+anusvarabengali;0982
+anusvaradeva;0902
+anusvaragujarati;0A82
+aogonek;0105
+apaatosquare;3300
+aparen;249C
+apostrophearmenian;055A
+apostrophemod;02BC
+apple;F8FF
+approaches;2250
+approxequal;2248
+approxequalorimage;2252
+approximatelyequal;2245
+araeaekorean;318E
+araeakorean;318D
+arc;2312
+arighthalfring;1E9A
+aring;00E5
+aringacute;01FB
+aringbelow;1E01
+arrowboth;2194
+arrowdashdown;21E3
+arrowdashleft;21E0
+arrowdashright;21E2
+arrowdashup;21E1
+arrowdblboth;21D4
+arrowdbldown;21D3
+arrowdblleft;21D0
+arrowdblright;21D2
+arrowdblup;21D1
+arrowdown;2193
+arrowdownleft;2199
+arrowdownright;2198
+arrowdownwhite;21E9
+arrowheaddownmod;02C5
+arrowheadleftmod;02C2
+arrowheadrightmod;02C3
+arrowheadupmod;02C4
+arrowhorizex;F8E7
+arrowleft;2190
+arrowleftdbl;21D0
+arrowleftdblstroke;21CD
+arrowleftoverright;21C6
+arrowleftwhite;21E6
+arrowright;2192
+arrowrightdblstroke;21CF
+arrowrightheavy;279E
+arrowrightoverleft;21C4
+arrowrightwhite;21E8
+arrowtableft;21E4
+arrowtabright;21E5
+arrowup;2191
+arrowupdn;2195
+arrowupdnbse;21A8
+arrowupdownbase;21A8
+arrowupleft;2196
+arrowupleftofdown;21C5
+arrowupright;2197
+arrowupwhite;21E7
+arrowvertex;F8E6
+asciicircum;005E
+asciicircummonospace;FF3E
+asciitilde;007E
+asciitildemonospace;FF5E
+ascript;0251
+ascriptturned;0252
+asmallhiragana;3041
+asmallkatakana;30A1
+asmallkatakanahalfwidth;FF67
+asterisk;002A
+asteriskaltonearabic;066D
+asteriskarabic;066D
+asteriskmath;2217
+asteriskmonospace;FF0A
+asterisksmall;FE61
+asterism;2042
+asuperior;F6E9
+asymptoticallyequal;2243
+at;0040
+atilde;00E3
+atmonospace;FF20
+atsmall;FE6B
+aturned;0250
+aubengali;0994
+aubopomofo;3120
+audeva;0914
+augujarati;0A94
+augurmukhi;0A14
+aulengthmarkbengali;09D7
+aumatragurmukhi;0A4C
+auvowelsignbengali;09CC
+auvowelsigndeva;094C
+auvowelsigngujarati;0ACC
+avagrahadeva;093D
+aybarmenian;0561
+ayin;05E2
+ayinaltonehebrew;FB20
+ayinhebrew;05E2
+b;0062
+babengali;09AC
+backslash;005C
+backslashmonospace;FF3C
+badeva;092C
+bagujarati;0AAC
+bagurmukhi;0A2C
+bahiragana;3070
+bahtthai;0E3F
+bakatakana;30D0
+bar;007C
+barmonospace;FF5C
+bbopomofo;3105
+bcircle;24D1
+bdotaccent;1E03
+bdotbelow;1E05
+beamedsixteenthnotes;266C
+because;2235
+becyrillic;0431
+beharabic;0628
+behfinalarabic;FE90
+behinitialarabic;FE91
+behiragana;3079
+behmedialarabic;FE92
+behmeeminitialarabic;FC9F
+behmeemisolatedarabic;FC08
+behnoonfinalarabic;FC6D
+bekatakana;30D9
+benarmenian;0562
+bet;05D1
+beta;03B2
+betasymbolgreek;03D0
+betdagesh;FB31
+betdageshhebrew;FB31
+bethebrew;05D1
+betrafehebrew;FB4C
+bhabengali;09AD
+bhadeva;092D
+bhagujarati;0AAD
+bhagurmukhi;0A2D
+bhook;0253
+bihiragana;3073
+bikatakana;30D3
+bilabialclick;0298
+bindigurmukhi;0A02
+birusquare;3331
+blackcircle;25CF
+blackdiamond;25C6
+blackdownpointingtriangle;25BC
+blackleftpointingpointer;25C4
+blackleftpointingtriangle;25C0
+blacklenticularbracketleft;3010
+blacklenticularbracketleftvertical;FE3B
+blacklenticularbracketright;3011
+blacklenticularbracketrightvertical;FE3C
+blacklowerlefttriangle;25E3
+blacklowerrighttriangle;25E2
+blackrectangle;25AC
+blackrightpointingpointer;25BA
+blackrightpointingtriangle;25B6
+blacksmallsquare;25AA
+blacksmilingface;263B
+blacksquare;25A0
+blackstar;2605
+blackupperlefttriangle;25E4
+blackupperrighttriangle;25E5
+blackuppointingsmalltriangle;25B4
+blackuppointingtriangle;25B2
+blank;2423
+blinebelow;1E07
+block;2588
+bmonospace;FF42
+bobaimaithai;0E1A
+bohiragana;307C
+bokatakana;30DC
+bparen;249D
+bqsquare;33C3
+braceex;F8F4
+braceleft;007B
+braceleftbt;F8F3
+braceleftmid;F8F2
+braceleftmonospace;FF5B
+braceleftsmall;FE5B
+bracelefttp;F8F1
+braceleftvertical;FE37
+braceright;007D
+bracerightbt;F8FE
+bracerightmid;F8FD
+bracerightmonospace;FF5D
+bracerightsmall;FE5C
+bracerighttp;F8FC
+bracerightvertical;FE38
+bracketleft;005B
+bracketleftbt;F8F0
+bracketleftex;F8EF
+bracketleftmonospace;FF3B
+bracketlefttp;F8EE
+bracketright;005D
+bracketrightbt;F8FB
+bracketrightex;F8FA
+bracketrightmonospace;FF3D
+bracketrighttp;F8F9
+breve;02D8
+brevebelowcmb;032E
+brevecmb;0306
+breveinvertedbelowcmb;032F
+breveinvertedcmb;0311
+breveinverteddoublecmb;0361
+bridgebelowcmb;032A
+bridgeinvertedbelowcmb;033A
+brokenbar;00A6
+bstroke;0180
+bsuperior;F6EA
+btopbar;0183
+buhiragana;3076
+bukatakana;30D6
+bullet;2022
+bulletinverse;25D8
+bulletoperator;2219
+bullseye;25CE
+c;0063
+caarmenian;056E
+cabengali;099A
+cacute;0107
+cadeva;091A
+cagujarati;0A9A
+cagurmukhi;0A1A
+calsquare;3388
+candrabindubengali;0981
+candrabinducmb;0310
+candrabindudeva;0901
+candrabindugujarati;0A81
+capslock;21EA
+careof;2105
+caron;02C7
+caronbelowcmb;032C
+caroncmb;030C
+carriagereturn;21B5
+cbopomofo;3118
+ccaron;010D
+ccedilla;00E7
+ccedillaacute;1E09
+ccircle;24D2
+ccircumflex;0109
+ccurl;0255
+cdot;010B
+cdotaccent;010B
+cdsquare;33C5
+cedilla;00B8
+cedillacmb;0327
+cent;00A2
+centigrade;2103
+centinferior;F6DF
+centmonospace;FFE0
+centoldstyle;F7A2
+centsuperior;F6E0
+chaarmenian;0579
+chabengali;099B
+chadeva;091B
+chagujarati;0A9B
+chagurmukhi;0A1B
+chbopomofo;3114
+cheabkhasiancyrillic;04BD
+checkmark;2713
+checyrillic;0447
+chedescenderabkhasiancyrillic;04BF
+chedescendercyrillic;04B7
+chedieresiscyrillic;04F5
+cheharmenian;0573
+chekhakassiancyrillic;04CC
+cheverticalstrokecyrillic;04B9
+chi;03C7
+chieuchacirclekorean;3277
+chieuchaparenkorean;3217
+chieuchcirclekorean;3269
+chieuchkorean;314A
+chieuchparenkorean;3209
+chochangthai;0E0A
+chochanthai;0E08
+chochingthai;0E09
+chochoethai;0E0C
+chook;0188
+cieucacirclekorean;3276
+cieucaparenkorean;3216
+cieuccirclekorean;3268
+cieuckorean;3148
+cieucparenkorean;3208
+cieucuparenkorean;321C
+circle;25CB
+circlemultiply;2297
+circleot;2299
+circleplus;2295
+circlepostalmark;3036
+circlewithlefthalfblack;25D0
+circlewithrighthalfblack;25D1
+circumflex;02C6
+circumflexbelowcmb;032D
+circumflexcmb;0302
+clear;2327
+clickalveolar;01C2
+clickdental;01C0
+clicklateral;01C1
+clickretroflex;01C3
+club;2663
+clubsuitblack;2663
+clubsuitwhite;2667
+cmcubedsquare;33A4
+cmonospace;FF43
+cmsquaredsquare;33A0
+coarmenian;0581
+colon;003A
+colonmonetary;20A1
+colonmonospace;FF1A
+colonsign;20A1
+colonsmall;FE55
+colontriangularhalfmod;02D1
+colontriangularmod;02D0
+comma;002C
+commaabovecmb;0313
+commaaboverightcmb;0315
+commaaccent;F6C3
+commaarabic;060C
+commaarmenian;055D
+commainferior;F6E1
+commamonospace;FF0C
+commareversedabovecmb;0314
+commareversedmod;02BD
+commasmall;FE50
+commasuperior;F6E2
+commaturnedabovecmb;0312
+commaturnedmod;02BB
+compass;263C
+congruent;2245
+contourintegral;222E
+control;2303
+controlACK;0006
+controlBEL;0007
+controlBS;0008
+controlCAN;0018
+controlCR;000D
+controlDC1;0011
+controlDC2;0012
+controlDC3;0013
+controlDC4;0014
+controlDEL;007F
+controlDLE;0010
+controlEM;0019
+controlENQ;0005
+controlEOT;0004
+controlESC;001B
+controlETB;0017
+controlETX;0003
+controlFF;000C
+controlFS;001C
+controlGS;001D
+controlHT;0009
+controlLF;000A
+controlNAK;0015
+controlRS;001E
+controlSI;000F
+controlSO;000E
+controlSOT;0002
+controlSTX;0001
+controlSUB;001A
+controlSYN;0016
+controlUS;001F
+controlVT;000B
+copyright;00A9
+copyrightsans;F8E9
+copyrightserif;F6D9
+cornerbracketleft;300C
+cornerbracketlefthalfwidth;FF62
+cornerbracketleftvertical;FE41
+cornerbracketright;300D
+cornerbracketrighthalfwidth;FF63
+cornerbracketrightvertical;FE42
+corporationsquare;337F
+cosquare;33C7
+coverkgsquare;33C6
+cparen;249E
+cruzeiro;20A2
+cstretched;0297
+curlyand;22CF
+curlyor;22CE
+currency;00A4
+cyrBreve;F6D1
+cyrFlex;F6D2
+cyrbreve;F6D4
+cyrflex;F6D5
+d;0064
+daarmenian;0564
+dabengali;09A6
+dadarabic;0636
+dadeva;0926
+dadfinalarabic;FEBE
+dadinitialarabic;FEBF
+dadmedialarabic;FEC0
+dagesh;05BC
+dageshhebrew;05BC
+dagger;2020
+daggerdbl;2021
+dagujarati;0AA6
+dagurmukhi;0A26
+dahiragana;3060
+dakatakana;30C0
+dalarabic;062F
+dalet;05D3
+daletdagesh;FB33
+daletdageshhebrew;FB33
+dalethatafpatah;05D3 05B2
+dalethatafpatahhebrew;05D3 05B2
+dalethatafsegol;05D3 05B1
+dalethatafsegolhebrew;05D3 05B1
+dalethebrew;05D3
+dalethiriq;05D3 05B4
+dalethiriqhebrew;05D3 05B4
+daletholam;05D3 05B9
+daletholamhebrew;05D3 05B9
+daletpatah;05D3 05B7
+daletpatahhebrew;05D3 05B7
+daletqamats;05D3 05B8
+daletqamatshebrew;05D3 05B8
+daletqubuts;05D3 05BB
+daletqubutshebrew;05D3 05BB
+daletsegol;05D3 05B6
+daletsegolhebrew;05D3 05B6
+daletsheva;05D3 05B0
+daletshevahebrew;05D3 05B0
+dalettsere;05D3 05B5
+dalettserehebrew;05D3 05B5
+dalfinalarabic;FEAA
+dammaarabic;064F
+dammalowarabic;064F
+dammatanaltonearabic;064C
+dammatanarabic;064C
+danda;0964
+dargahebrew;05A7
+dargalefthebrew;05A7
+dasiapneumatacyrilliccmb;0485
+dblGrave;F6D3
+dblanglebracketleft;300A
+dblanglebracketleftvertical;FE3D
+dblanglebracketright;300B
+dblanglebracketrightvertical;FE3E
+dblarchinvertedbelowcmb;032B
+dblarrowleft;21D4
+dblarrowright;21D2
+dbldanda;0965
+dblgrave;F6D6
+dblgravecmb;030F
+dblintegral;222C
+dbllowline;2017
+dbllowlinecmb;0333
+dbloverlinecmb;033F
+dblprimemod;02BA
+dblverticalbar;2016
+dblverticallineabovecmb;030E
+dbopomofo;3109
+dbsquare;33C8
+dcaron;010F
+dcedilla;1E11
+dcircle;24D3
+dcircumflexbelow;1E13
+dcroat;0111
+ddabengali;09A1
+ddadeva;0921
+ddagujarati;0AA1
+ddagurmukhi;0A21
+ddalarabic;0688
+ddalfinalarabic;FB89
+dddhadeva;095C
+ddhabengali;09A2
+ddhadeva;0922
+ddhagujarati;0AA2
+ddhagurmukhi;0A22
+ddotaccent;1E0B
+ddotbelow;1E0D
+decimalseparatorarabic;066B
+decimalseparatorpersian;066B
+decyrillic;0434
+degree;00B0
+dehihebrew;05AD
+dehiragana;3067
+deicoptic;03EF
+dekatakana;30C7
+deleteleft;232B
+deleteright;2326
+delta;03B4
+deltaturned;018D
+denominatorminusonenumeratorbengali;09F8
+dezh;02A4
+dhabengali;09A7
+dhadeva;0927
+dhagujarati;0AA7
+dhagurmukhi;0A27
+dhook;0257
+dialytikatonos;0385
+dialytikatonoscmb;0344
+diamond;2666
+diamondsuitwhite;2662
+dieresis;00A8
+dieresisacute;F6D7
+dieresisbelowcmb;0324
+dieresiscmb;0308
+dieresisgrave;F6D8
+dieresistonos;0385
+dihiragana;3062
+dikatakana;30C2
+dittomark;3003
+divide;00F7
+divides;2223
+divisionslash;2215
+djecyrillic;0452
+dkshade;2593
+dlinebelow;1E0F
+dlsquare;3397
+dmacron;0111
+dmonospace;FF44
+dnblock;2584
+dochadathai;0E0E
+dodekthai;0E14
+dohiragana;3069
+dokatakana;30C9
+dollar;0024
+dollarinferior;F6E3
+dollarmonospace;FF04
+dollaroldstyle;F724
+dollarsmall;FE69
+dollarsuperior;F6E4
+dong;20AB
+dorusquare;3326
+dotaccent;02D9
+dotaccentcmb;0307
+dotbelowcmb;0323
+dotbelowcomb;0323
+dotkatakana;30FB
+dotlessi;0131
+dotlessj;F6BE
+dotlessjstrokehook;0284
+dotmath;22C5
+dottedcircle;25CC
+doubleyodpatah;FB1F
+doubleyodpatahhebrew;FB1F
+downtackbelowcmb;031E
+downtackmod;02D5
+dparen;249F
+dsuperior;F6EB
+dtail;0256
+dtopbar;018C
+duhiragana;3065
+dukatakana;30C5
+dz;01F3
+dzaltone;02A3
+dzcaron;01C6
+dzcurl;02A5
+dzeabkhasiancyrillic;04E1
+dzecyrillic;0455
+dzhecyrillic;045F
+e;0065
+eacute;00E9
+earth;2641
+ebengali;098F
+ebopomofo;311C
+ebreve;0115
+ecandradeva;090D
+ecandragujarati;0A8D
+ecandravowelsigndeva;0945
+ecandravowelsigngujarati;0AC5
+ecaron;011B
+ecedillabreve;1E1D
+echarmenian;0565
+echyiwnarmenian;0587
+ecircle;24D4
+ecircumflex;00EA
+ecircumflexacute;1EBF
+ecircumflexbelow;1E19
+ecircumflexdotbelow;1EC7
+ecircumflexgrave;1EC1
+ecircumflexhookabove;1EC3
+ecircumflextilde;1EC5
+ecyrillic;0454
+edblgrave;0205
+edeva;090F
+edieresis;00EB
+edot;0117
+edotaccent;0117
+edotbelow;1EB9
+eegurmukhi;0A0F
+eematragurmukhi;0A47
+efcyrillic;0444
+egrave;00E8
+egujarati;0A8F
+eharmenian;0567
+ehbopomofo;311D
+ehiragana;3048
+ehookabove;1EBB
+eibopomofo;311F
+eight;0038
+eightarabic;0668
+eightbengali;09EE
+eightcircle;2467
+eightcircleinversesansserif;2791
+eightdeva;096E
+eighteencircle;2471
+eighteenparen;2485
+eighteenperiod;2499
+eightgujarati;0AEE
+eightgurmukhi;0A6E
+eighthackarabic;0668
+eighthangzhou;3028
+eighthnotebeamed;266B
+eightideographicparen;3227
+eightinferior;2088
+eightmonospace;FF18
+eightoldstyle;F738
+eightparen;247B
+eightperiod;248F
+eightpersian;06F8
+eightroman;2177
+eightsuperior;2078
+eightthai;0E58
+einvertedbreve;0207
+eiotifiedcyrillic;0465
+ekatakana;30A8
+ekatakanahalfwidth;FF74
+ekonkargurmukhi;0A74
+ekorean;3154
+elcyrillic;043B
+element;2208
+elevencircle;246A
+elevenparen;247E
+elevenperiod;2492
+elevenroman;217A
+ellipsis;2026
+ellipsisvertical;22EE
+emacron;0113
+emacronacute;1E17
+emacrongrave;1E15
+emcyrillic;043C
+emdash;2014
+emdashvertical;FE31
+emonospace;FF45
+emphasismarkarmenian;055B
+emptyset;2205
+enbopomofo;3123
+encyrillic;043D
+endash;2013
+endashvertical;FE32
+endescendercyrillic;04A3
+eng;014B
+engbopomofo;3125
+enghecyrillic;04A5
+enhookcyrillic;04C8
+enspace;2002
+eogonek;0119
+eokorean;3153
+eopen;025B
+eopenclosed;029A
+eopenreversed;025C
+eopenreversedclosed;025E
+eopenreversedhook;025D
+eparen;24A0
+epsilon;03B5
+epsilontonos;03AD
+equal;003D
+equalmonospace;FF1D
+equalsmall;FE66
+equalsuperior;207C
+equivalence;2261
+erbopomofo;3126
+ercyrillic;0440
+ereversed;0258
+ereversedcyrillic;044D
+escyrillic;0441
+esdescendercyrillic;04AB
+esh;0283
+eshcurl;0286
+eshortdeva;090E
+eshortvowelsigndeva;0946
+eshreversedloop;01AA
+eshsquatreversed;0285
+esmallhiragana;3047
+esmallkatakana;30A7
+esmallkatakanahalfwidth;FF6A
+estimated;212E
+esuperior;F6EC
+eta;03B7
+etarmenian;0568
+etatonos;03AE
+eth;00F0
+etilde;1EBD
+etildebelow;1E1B
+etnahtafoukhhebrew;0591
+etnahtafoukhlefthebrew;0591
+etnahtahebrew;0591
+etnahtalefthebrew;0591
+eturned;01DD
+eukorean;3161
+euro;20AC
+evowelsignbengali;09C7
+evowelsigndeva;0947
+evowelsigngujarati;0AC7
+exclam;0021
+exclamarmenian;055C
+exclamdbl;203C
+exclamdown;00A1
+exclamdownsmall;F7A1
+exclammonospace;FF01
+exclamsmall;F721
+existential;2203
+ezh;0292
+ezhcaron;01EF
+ezhcurl;0293
+ezhreversed;01B9
+ezhtail;01BA
+f;0066
+fadeva;095E
+fagurmukhi;0A5E
+fahrenheit;2109
+fathaarabic;064E
+fathalowarabic;064E
+fathatanarabic;064B
+fbopomofo;3108
+fcircle;24D5
+fdotaccent;1E1F
+feharabic;0641
+feharmenian;0586
+fehfinalarabic;FED2
+fehinitialarabic;FED3
+fehmedialarabic;FED4
+feicoptic;03E5
+female;2640
+ff;FB00
+ffi;FB03
+ffl;FB04
+fi;FB01
+fifteencircle;246E
+fifteenparen;2482
+fifteenperiod;2496
+figuredash;2012
+filledbox;25A0
+filledrect;25AC
+finalkaf;05DA
+finalkafdagesh;FB3A
+finalkafdageshhebrew;FB3A
+finalkafhebrew;05DA
+finalkafqamats;05DA 05B8
+finalkafqamatshebrew;05DA 05B8
+finalkafsheva;05DA 05B0
+finalkafshevahebrew;05DA 05B0
+finalmem;05DD
+finalmemhebrew;05DD
+finalnun;05DF
+finalnunhebrew;05DF
+finalpe;05E3
+finalpehebrew;05E3
+finaltsadi;05E5
+finaltsadihebrew;05E5
+firsttonechinese;02C9
+fisheye;25C9
+fitacyrillic;0473
+five;0035
+fivearabic;0665
+fivebengali;09EB
+fivecircle;2464
+fivecircleinversesansserif;278E
+fivedeva;096B
+fiveeighths;215D
+fivegujarati;0AEB
+fivegurmukhi;0A6B
+fivehackarabic;0665
+fivehangzhou;3025
+fiveideographicparen;3224
+fiveinferior;2085
+fivemonospace;FF15
+fiveoldstyle;F735
+fiveparen;2478
+fiveperiod;248C
+fivepersian;06F5
+fiveroman;2174
+fivesuperior;2075
+fivethai;0E55
+fl;FB02
+florin;0192
+fmonospace;FF46
+fmsquare;3399
+fofanthai;0E1F
+fofathai;0E1D
+fongmanthai;0E4F
+forall;2200
+four;0034
+fourarabic;0664
+fourbengali;09EA
+fourcircle;2463
+fourcircleinversesansserif;278D
+fourdeva;096A
+fourgujarati;0AEA
+fourgurmukhi;0A6A
+fourhackarabic;0664
+fourhangzhou;3024
+fourideographicparen;3223
+fourinferior;2084
+fourmonospace;FF14
+fournumeratorbengali;09F7
+fouroldstyle;F734
+fourparen;2477
+fourperiod;248B
+fourpersian;06F4
+fourroman;2173
+foursuperior;2074
+fourteencircle;246D
+fourteenparen;2481
+fourteenperiod;2495
+fourthai;0E54
+fourthtonechinese;02CB
+fparen;24A1
+fraction;2044
+franc;20A3
+g;0067
+gabengali;0997
+gacute;01F5
+gadeva;0917
+gafarabic;06AF
+gaffinalarabic;FB93
+gafinitialarabic;FB94
+gafmedialarabic;FB95
+gagujarati;0A97
+gagurmukhi;0A17
+gahiragana;304C
+gakatakana;30AC
+gamma;03B3
+gammalatinsmall;0263
+gammasuperior;02E0
+gangiacoptic;03EB
+gbopomofo;310D
+gbreve;011F
+gcaron;01E7
+gcedilla;0123
+gcircle;24D6
+gcircumflex;011D
+gcommaaccent;0123
+gdot;0121
+gdotaccent;0121
+gecyrillic;0433
+gehiragana;3052
+gekatakana;30B2
+geometricallyequal;2251
+gereshaccenthebrew;059C
+gereshhebrew;05F3
+gereshmuqdamhebrew;059D
+germandbls;00DF
+gershayimaccenthebrew;059E
+gershayimhebrew;05F4
+getamark;3013
+ghabengali;0998
+ghadarmenian;0572
+ghadeva;0918
+ghagujarati;0A98
+ghagurmukhi;0A18
+ghainarabic;063A
+ghainfinalarabic;FECE
+ghaininitialarabic;FECF
+ghainmedialarabic;FED0
+ghemiddlehookcyrillic;0495
+ghestrokecyrillic;0493
+gheupturncyrillic;0491
+ghhadeva;095A
+ghhagurmukhi;0A5A
+ghook;0260
+ghzsquare;3393
+gihiragana;304E
+gikatakana;30AE
+gimarmenian;0563
+gimel;05D2
+gimeldagesh;FB32
+gimeldageshhebrew;FB32
+gimelhebrew;05D2
+gjecyrillic;0453
+glottalinvertedstroke;01BE
+glottalstop;0294
+glottalstopinverted;0296
+glottalstopmod;02C0
+glottalstopreversed;0295
+glottalstopreversedmod;02C1
+glottalstopreversedsuperior;02E4
+glottalstopstroke;02A1
+glottalstopstrokereversed;02A2
+gmacron;1E21
+gmonospace;FF47
+gohiragana;3054
+gokatakana;30B4
+gparen;24A2
+gpasquare;33AC
+gradient;2207
+grave;0060
+gravebelowcmb;0316
+gravecmb;0300
+gravecomb;0300
+gravedeva;0953
+gravelowmod;02CE
+gravemonospace;FF40
+gravetonecmb;0340
+greater;003E
+greaterequal;2265
+greaterequalorless;22DB
+greatermonospace;FF1E
+greaterorequivalent;2273
+greaterorless;2277
+greateroverequal;2267
+greatersmall;FE65
+gscript;0261
+gstroke;01E5
+guhiragana;3050
+guillemotleft;00AB
+guillemotright;00BB
+guilsinglleft;2039
+guilsinglright;203A
+gukatakana;30B0
+guramusquare;3318
+gysquare;33C9
+h;0068
+haabkhasiancyrillic;04A9
+haaltonearabic;06C1
+habengali;09B9
+hadescendercyrillic;04B3
+hadeva;0939
+hagujarati;0AB9
+hagurmukhi;0A39
+haharabic;062D
+hahfinalarabic;FEA2
+hahinitialarabic;FEA3
+hahiragana;306F
+hahmedialarabic;FEA4
+haitusquare;332A
+hakatakana;30CF
+hakatakanahalfwidth;FF8A
+halantgurmukhi;0A4D
+hamzaarabic;0621
+hamzadammaarabic;0621 064F
+hamzadammatanarabic;0621 064C
+hamzafathaarabic;0621 064E
+hamzafathatanarabic;0621 064B
+hamzalowarabic;0621
+hamzalowkasraarabic;0621 0650
+hamzalowkasratanarabic;0621 064D
+hamzasukunarabic;0621 0652
+hangulfiller;3164
+hardsigncyrillic;044A
+harpoonleftbarbup;21BC
+harpoonrightbarbup;21C0
+hasquare;33CA
+hatafpatah;05B2
+hatafpatah16;05B2
+hatafpatah23;05B2
+hatafpatah2f;05B2
+hatafpatahhebrew;05B2
+hatafpatahnarrowhebrew;05B2
+hatafpatahquarterhebrew;05B2
+hatafpatahwidehebrew;05B2
+hatafqamats;05B3
+hatafqamats1b;05B3
+hatafqamats28;05B3
+hatafqamats34;05B3
+hatafqamatshebrew;05B3
+hatafqamatsnarrowhebrew;05B3
+hatafqamatsquarterhebrew;05B3
+hatafqamatswidehebrew;05B3
+hatafsegol;05B1
+hatafsegol17;05B1
+hatafsegol24;05B1
+hatafsegol30;05B1
+hatafsegolhebrew;05B1
+hatafsegolnarrowhebrew;05B1
+hatafsegolquarterhebrew;05B1
+hatafsegolwidehebrew;05B1
+hbar;0127
+hbopomofo;310F
+hbrevebelow;1E2B
+hcedilla;1E29
+hcircle;24D7
+hcircumflex;0125
+hdieresis;1E27
+hdotaccent;1E23
+hdotbelow;1E25
+he;05D4
+heart;2665
+heartsuitblack;2665
+heartsuitwhite;2661
+hedagesh;FB34
+hedageshhebrew;FB34
+hehaltonearabic;06C1
+heharabic;0647
+hehebrew;05D4
+hehfinalaltonearabic;FBA7
+hehfinalalttwoarabic;FEEA
+hehfinalarabic;FEEA
+hehhamzaabovefinalarabic;FBA5
+hehhamzaaboveisolatedarabic;FBA4
+hehinitialaltonearabic;FBA8
+hehinitialarabic;FEEB
+hehiragana;3078
+hehmedialaltonearabic;FBA9
+hehmedialarabic;FEEC
+heiseierasquare;337B
+hekatakana;30D8
+hekatakanahalfwidth;FF8D
+hekutaarusquare;3336
+henghook;0267
+herutusquare;3339
+het;05D7
+hethebrew;05D7
+hhook;0266
+hhooksuperior;02B1
+hieuhacirclekorean;327B
+hieuhaparenkorean;321B
+hieuhcirclekorean;326D
+hieuhkorean;314E
+hieuhparenkorean;320D
+hihiragana;3072
+hikatakana;30D2
+hikatakanahalfwidth;FF8B
+hiriq;05B4
+hiriq14;05B4
+hiriq21;05B4
+hiriq2d;05B4
+hiriqhebrew;05B4
+hiriqnarrowhebrew;05B4
+hiriqquarterhebrew;05B4
+hiriqwidehebrew;05B4
+hlinebelow;1E96
+hmonospace;FF48
+hoarmenian;0570
+hohipthai;0E2B
+hohiragana;307B
+hokatakana;30DB
+hokatakanahalfwidth;FF8E
+holam;05B9
+holam19;05B9
+holam26;05B9
+holam32;05B9
+holamhebrew;05B9
+holamnarrowhebrew;05B9
+holamquarterhebrew;05B9
+holamwidehebrew;05B9
+honokhukthai;0E2E
+hookabovecomb;0309
+hookcmb;0309
+hookpalatalizedbelowcmb;0321
+hookretroflexbelowcmb;0322
+hoonsquare;3342
+horicoptic;03E9
+horizontalbar;2015
+horncmb;031B
+hotsprings;2668
+house;2302
+hparen;24A3
+hsuperior;02B0
+hturned;0265
+huhiragana;3075
+huiitosquare;3333
+hukatakana;30D5
+hukatakanahalfwidth;FF8C
+hungarumlaut;02DD
+hungarumlautcmb;030B
+hv;0195
+hyphen;002D
+hypheninferior;F6E5
+hyphenmonospace;FF0D
+hyphensmall;FE63
+hyphensuperior;F6E6
+hyphentwo;2010
+i;0069
+iacute;00ED
+iacyrillic;044F
+ibengali;0987
+ibopomofo;3127
+ibreve;012D
+icaron;01D0
+icircle;24D8
+icircumflex;00EE
+icyrillic;0456
+idblgrave;0209
+ideographearthcircle;328F
+ideographfirecircle;328B
+ideographicallianceparen;323F
+ideographiccallparen;323A
+ideographiccentrecircle;32A5
+ideographicclose;3006
+ideographiccomma;3001
+ideographiccommaleft;FF64
+ideographiccongratulationparen;3237
+ideographiccorrectcircle;32A3
+ideographicearthparen;322F
+ideographicenterpriseparen;323D
+ideographicexcellentcircle;329D
+ideographicfestivalparen;3240
+ideographicfinancialcircle;3296
+ideographicfinancialparen;3236
+ideographicfireparen;322B
+ideographichaveparen;3232
+ideographichighcircle;32A4
+ideographiciterationmark;3005
+ideographiclaborcircle;3298
+ideographiclaborparen;3238
+ideographicleftcircle;32A7
+ideographiclowcircle;32A6
+ideographicmedicinecircle;32A9
+ideographicmetalparen;322E
+ideographicmoonparen;322A
+ideographicnameparen;3234
+ideographicperiod;3002
+ideographicprintcircle;329E
+ideographicreachparen;3243
+ideographicrepresentparen;3239
+ideographicresourceparen;323E
+ideographicrightcircle;32A8
+ideographicsecretcircle;3299
+ideographicselfparen;3242
+ideographicsocietyparen;3233
+ideographicspace;3000
+ideographicspecialparen;3235
+ideographicstockparen;3231
+ideographicstudyparen;323B
+ideographicsunparen;3230
+ideographicsuperviseparen;323C
+ideographicwaterparen;322C
+ideographicwoodparen;322D
+ideographiczero;3007
+ideographmetalcircle;328E
+ideographmooncircle;328A
+ideographnamecircle;3294
+ideographsuncircle;3290
+ideographwatercircle;328C
+ideographwoodcircle;328D
+ideva;0907
+idieresis;00EF
+idieresisacute;1E2F
+idieresiscyrillic;04E5
+idotbelow;1ECB
+iebrevecyrillic;04D7
+iecyrillic;0435
+ieungacirclekorean;3275
+ieungaparenkorean;3215
+ieungcirclekorean;3267
+ieungkorean;3147
+ieungparenkorean;3207
+igrave;00EC
+igujarati;0A87
+igurmukhi;0A07
+ihiragana;3044
+ihookabove;1EC9
+iibengali;0988
+iicyrillic;0438
+iideva;0908
+iigujarati;0A88
+iigurmukhi;0A08
+iimatragurmukhi;0A40
+iinvertedbreve;020B
+iishortcyrillic;0439
+iivowelsignbengali;09C0
+iivowelsigndeva;0940
+iivowelsigngujarati;0AC0
+ij;0133
+ikatakana;30A4
+ikatakanahalfwidth;FF72
+ikorean;3163
+ilde;02DC
+iluyhebrew;05AC
+imacron;012B
+imacroncyrillic;04E3
+imageorapproximatelyequal;2253
+imatragurmukhi;0A3F
+imonospace;FF49
+increment;2206
+infinity;221E
+iniarmenian;056B
+integral;222B
+integralbottom;2321
+integralbt;2321
+integralex;F8F5
+integraltop;2320
+integraltp;2320
+intersection;2229
+intisquare;3305
+invbullet;25D8
+invcircle;25D9
+invsmileface;263B
+iocyrillic;0451
+iogonek;012F
+iota;03B9
+iotadieresis;03CA
+iotadieresistonos;0390
+iotalatin;0269
+iotatonos;03AF
+iparen;24A4
+irigurmukhi;0A72
+ismallhiragana;3043
+ismallkatakana;30A3
+ismallkatakanahalfwidth;FF68
+issharbengali;09FA
+istroke;0268
+isuperior;F6ED
+iterationhiragana;309D
+iterationkatakana;30FD
+itilde;0129
+itildebelow;1E2D
+iubopomofo;3129
+iucyrillic;044E
+ivowelsignbengali;09BF
+ivowelsigndeva;093F
+ivowelsigngujarati;0ABF
+izhitsacyrillic;0475
+izhitsadblgravecyrillic;0477
+j;006A
+jaarmenian;0571
+jabengali;099C
+jadeva;091C
+jagujarati;0A9C
+jagurmukhi;0A1C
+jbopomofo;3110
+jcaron;01F0
+jcircle;24D9
+jcircumflex;0135
+jcrossedtail;029D
+jdotlessstroke;025F
+jecyrillic;0458
+jeemarabic;062C
+jeemfinalarabic;FE9E
+jeeminitialarabic;FE9F
+jeemmedialarabic;FEA0
+jeharabic;0698
+jehfinalarabic;FB8B
+jhabengali;099D
+jhadeva;091D
+jhagujarati;0A9D
+jhagurmukhi;0A1D
+jheharmenian;057B
+jis;3004
+jmonospace;FF4A
+jparen;24A5
+jsuperior;02B2
+k;006B
+kabashkircyrillic;04A1
+kabengali;0995
+kacute;1E31
+kacyrillic;043A
+kadescendercyrillic;049B
+kadeva;0915
+kaf;05DB
+kafarabic;0643
+kafdagesh;FB3B
+kafdageshhebrew;FB3B
+kaffinalarabic;FEDA
+kafhebrew;05DB
+kafinitialarabic;FEDB
+kafmedialarabic;FEDC
+kafrafehebrew;FB4D
+kagujarati;0A95
+kagurmukhi;0A15
+kahiragana;304B
+kahookcyrillic;04C4
+kakatakana;30AB
+kakatakanahalfwidth;FF76
+kappa;03BA
+kappasymbolgreek;03F0
+kapyeounmieumkorean;3171
+kapyeounphieuphkorean;3184
+kapyeounpieupkorean;3178
+kapyeounssangpieupkorean;3179
+karoriisquare;330D
+kashidaautoarabic;0640
+kashidaautonosidebearingarabic;0640
+kasmallkatakana;30F5
+kasquare;3384
+kasraarabic;0650
+kasratanarabic;064D
+kastrokecyrillic;049F
+katahiraprolongmarkhalfwidth;FF70
+kaverticalstrokecyrillic;049D
+kbopomofo;310E
+kcalsquare;3389
+kcaron;01E9
+kcedilla;0137
+kcircle;24DA
+kcommaaccent;0137
+kdotbelow;1E33
+keharmenian;0584
+kehiragana;3051
+kekatakana;30B1
+kekatakanahalfwidth;FF79
+kenarmenian;056F
+kesmallkatakana;30F6
+kgreenlandic;0138
+khabengali;0996
+khacyrillic;0445
+khadeva;0916
+khagujarati;0A96
+khagurmukhi;0A16
+khaharabic;062E
+khahfinalarabic;FEA6
+khahinitialarabic;FEA7
+khahmedialarabic;FEA8
+kheicoptic;03E7
+khhadeva;0959
+khhagurmukhi;0A59
+khieukhacirclekorean;3278
+khieukhaparenkorean;3218
+khieukhcirclekorean;326A
+khieukhkorean;314B
+khieukhparenkorean;320A
+khokhaithai;0E02
+khokhonthai;0E05
+khokhuatthai;0E03
+khokhwaithai;0E04
+khomutthai;0E5B
+khook;0199
+khorakhangthai;0E06
+khzsquare;3391
+kihiragana;304D
+kikatakana;30AD
+kikatakanahalfwidth;FF77
+kiroguramusquare;3315
+kiromeetorusquare;3316
+kirosquare;3314
+kiyeokacirclekorean;326E
+kiyeokaparenkorean;320E
+kiyeokcirclekorean;3260
+kiyeokkorean;3131
+kiyeokparenkorean;3200
+kiyeoksioskorean;3133
+kjecyrillic;045C
+klinebelow;1E35
+klsquare;3398
+kmcubedsquare;33A6
+kmonospace;FF4B
+kmsquaredsquare;33A2
+kohiragana;3053
+kohmsquare;33C0
+kokaithai;0E01
+kokatakana;30B3
+kokatakanahalfwidth;FF7A
+kooposquare;331E
+koppacyrillic;0481
+koreanstandardsymbol;327F
+koroniscmb;0343
+kparen;24A6
+kpasquare;33AA
+ksicyrillic;046F
+ktsquare;33CF
+kturned;029E
+kuhiragana;304F
+kukatakana;30AF
+kukatakanahalfwidth;FF78
+kvsquare;33B8
+kwsquare;33BE
+l;006C
+labengali;09B2
+lacute;013A
+ladeva;0932
+lagujarati;0AB2
+lagurmukhi;0A32
+lakkhangyaothai;0E45
+lamaleffinalarabic;FEFC
+lamalefhamzaabovefinalarabic;FEF8
+lamalefhamzaaboveisolatedarabic;FEF7
+lamalefhamzabelowfinalarabic;FEFA
+lamalefhamzabelowisolatedarabic;FEF9
+lamalefisolatedarabic;FEFB
+lamalefmaddaabovefinalarabic;FEF6
+lamalefmaddaaboveisolatedarabic;FEF5
+lamarabic;0644
+lambda;03BB
+lambdastroke;019B
+lamed;05DC
+lameddagesh;FB3C
+lameddageshhebrew;FB3C
+lamedhebrew;05DC
+lamedholam;05DC 05B9
+lamedholamdagesh;05DC 05B9 05BC
+lamedholamdageshhebrew;05DC 05B9 05BC
+lamedholamhebrew;05DC 05B9
+lamfinalarabic;FEDE
+lamhahinitialarabic;FCCA
+laminitialarabic;FEDF
+lamjeeminitialarabic;FCC9
+lamkhahinitialarabic;FCCB
+lamlamhehisolatedarabic;FDF2
+lammedialarabic;FEE0
+lammeemhahinitialarabic;FD88
+lammeeminitialarabic;FCCC
+lammeemjeeminitialarabic;FEDF FEE4 FEA0
+lammeemkhahinitialarabic;FEDF FEE4 FEA8
+largecircle;25EF
+lbar;019A
+lbelt;026C
+lbopomofo;310C
+lcaron;013E
+lcedilla;013C
+lcircle;24DB
+lcircumflexbelow;1E3D
+lcommaaccent;013C
+ldot;0140
+ldotaccent;0140
+ldotbelow;1E37
+ldotbelowmacron;1E39
+leftangleabovecmb;031A
+lefttackbelowcmb;0318
+less;003C
+lessequal;2264
+lessequalorgreater;22DA
+lessmonospace;FF1C
+lessorequivalent;2272
+lessorgreater;2276
+lessoverequal;2266
+lesssmall;FE64
+lezh;026E
+lfblock;258C
+lhookretroflex;026D
+lira;20A4
+liwnarmenian;056C
+lj;01C9
+ljecyrillic;0459
+ll;F6C0
+lladeva;0933
+llagujarati;0AB3
+llinebelow;1E3B
+llladeva;0934
+llvocalicbengali;09E1
+llvocalicdeva;0961
+llvocalicvowelsignbengali;09E3
+llvocalicvowelsigndeva;0963
+lmiddletilde;026B
+lmonospace;FF4C
+lmsquare;33D0
+lochulathai;0E2C
+logicaland;2227
+logicalnot;00AC
+logicalnotreversed;2310
+logicalor;2228
+lolingthai;0E25
+longs;017F
+lowlinecenterline;FE4E
+lowlinecmb;0332
+lowlinedashed;FE4D
+lozenge;25CA
+lparen;24A7
+lslash;0142
+lsquare;2113
+lsuperior;F6EE
+ltshade;2591
+luthai;0E26
+lvocalicbengali;098C
+lvocalicdeva;090C
+lvocalicvowelsignbengali;09E2
+lvocalicvowelsigndeva;0962
+lxsquare;33D3
+m;006D
+mabengali;09AE
+macron;00AF
+macronbelowcmb;0331
+macroncmb;0304
+macronlowmod;02CD
+macronmonospace;FFE3
+macute;1E3F
+madeva;092E
+magujarati;0AAE
+magurmukhi;0A2E
+mahapakhhebrew;05A4
+mahapakhlefthebrew;05A4
+mahiragana;307E
+maichattawalowleftthai;F895
+maichattawalowrightthai;F894
+maichattawathai;0E4B
+maichattawaupperleftthai;F893
+maieklowleftthai;F88C
+maieklowrightthai;F88B
+maiekthai;0E48
+maiekupperleftthai;F88A
+maihanakatleftthai;F884
+maihanakatthai;0E31
+maitaikhuleftthai;F889
+maitaikhuthai;0E47
+maitholowleftthai;F88F
+maitholowrightthai;F88E
+maithothai;0E49
+maithoupperleftthai;F88D
+maitrilowleftthai;F892
+maitrilowrightthai;F891
+maitrithai;0E4A
+maitriupperleftthai;F890
+maiyamokthai;0E46
+makatakana;30DE
+makatakanahalfwidth;FF8F
+male;2642
+mansyonsquare;3347
+maqafhebrew;05BE
+mars;2642
+masoracirclehebrew;05AF
+masquare;3383
+mbopomofo;3107
+mbsquare;33D4
+mcircle;24DC
+mcubedsquare;33A5
+mdotaccent;1E41
+mdotbelow;1E43
+meemarabic;0645
+meemfinalarabic;FEE2
+meeminitialarabic;FEE3
+meemmedialarabic;FEE4
+meemmeeminitialarabic;FCD1
+meemmeemisolatedarabic;FC48
+meetorusquare;334D
+mehiragana;3081
+meizierasquare;337E
+mekatakana;30E1
+mekatakanahalfwidth;FF92
+mem;05DE
+memdagesh;FB3E
+memdageshhebrew;FB3E
+memhebrew;05DE
+menarmenian;0574
+merkhahebrew;05A5
+merkhakefulahebrew;05A6
+merkhakefulalefthebrew;05A6
+merkhalefthebrew;05A5
+mhook;0271
+mhzsquare;3392
+middledotkatakanahalfwidth;FF65
+middot;00B7
+mieumacirclekorean;3272
+mieumaparenkorean;3212
+mieumcirclekorean;3264
+mieumkorean;3141
+mieumpansioskorean;3170
+mieumparenkorean;3204
+mieumpieupkorean;316E
+mieumsioskorean;316F
+mihiragana;307F
+mikatakana;30DF
+mikatakanahalfwidth;FF90
+minus;2212
+minusbelowcmb;0320
+minuscircle;2296
+minusmod;02D7
+minusplus;2213
+minute;2032
+miribaarusquare;334A
+mirisquare;3349
+mlonglegturned;0270
+mlsquare;3396
+mmcubedsquare;33A3
+mmonospace;FF4D
+mmsquaredsquare;339F
+mohiragana;3082
+mohmsquare;33C1
+mokatakana;30E2
+mokatakanahalfwidth;FF93
+molsquare;33D6
+momathai;0E21
+moverssquare;33A7
+moverssquaredsquare;33A8
+mparen;24A8
+mpasquare;33AB
+mssquare;33B3
+msuperior;F6EF
+mturned;026F
+mu;00B5
+mu1;00B5
+muasquare;3382
+muchgreater;226B
+muchless;226A
+mufsquare;338C
+mugreek;03BC
+mugsquare;338D
+muhiragana;3080
+mukatakana;30E0
+mukatakanahalfwidth;FF91
+mulsquare;3395
+multiply;00D7
+mumsquare;339B
+munahhebrew;05A3
+munahlefthebrew;05A3
+musicalnote;266A
+musicalnotedbl;266B
+musicflatsign;266D
+musicsharpsign;266F
+mussquare;33B2
+muvsquare;33B6
+muwsquare;33BC
+mvmegasquare;33B9
+mvsquare;33B7
+mwmegasquare;33BF
+mwsquare;33BD
+n;006E
+nabengali;09A8
+nabla;2207
+nacute;0144
+nadeva;0928
+nagujarati;0AA8
+nagurmukhi;0A28
+nahiragana;306A
+nakatakana;30CA
+nakatakanahalfwidth;FF85
+napostrophe;0149
+nasquare;3381
+nbopomofo;310B
+nbspace;00A0
+ncaron;0148
+ncedilla;0146
+ncircle;24DD
+ncircumflexbelow;1E4B
+ncommaaccent;0146
+ndotaccent;1E45
+ndotbelow;1E47
+nehiragana;306D
+nekatakana;30CD
+nekatakanahalfwidth;FF88
+newsheqelsign;20AA
+nfsquare;338B
+ngabengali;0999
+ngadeva;0919
+ngagujarati;0A99
+ngagurmukhi;0A19
+ngonguthai;0E07
+nhiragana;3093
+nhookleft;0272
+nhookretroflex;0273
+nieunacirclekorean;326F
+nieunaparenkorean;320F
+nieuncieuckorean;3135
+nieuncirclekorean;3261
+nieunhieuhkorean;3136
+nieunkorean;3134
+nieunpansioskorean;3168
+nieunparenkorean;3201
+nieunsioskorean;3167
+nieuntikeutkorean;3166
+nihiragana;306B
+nikatakana;30CB
+nikatakanahalfwidth;FF86
+nikhahitleftthai;F899
+nikhahitthai;0E4D
+nine;0039
+ninearabic;0669
+ninebengali;09EF
+ninecircle;2468
+ninecircleinversesansserif;2792
+ninedeva;096F
+ninegujarati;0AEF
+ninegurmukhi;0A6F
+ninehackarabic;0669
+ninehangzhou;3029
+nineideographicparen;3228
+nineinferior;2089
+ninemonospace;FF19
+nineoldstyle;F739
+nineparen;247C
+nineperiod;2490
+ninepersian;06F9
+nineroman;2178
+ninesuperior;2079
+nineteencircle;2472
+nineteenparen;2486
+nineteenperiod;249A
+ninethai;0E59
+nj;01CC
+njecyrillic;045A
+nkatakana;30F3
+nkatakanahalfwidth;FF9D
+nlegrightlong;019E
+nlinebelow;1E49
+nmonospace;FF4E
+nmsquare;339A
+nnabengali;09A3
+nnadeva;0923
+nnagujarati;0AA3
+nnagurmukhi;0A23
+nnnadeva;0929
+nohiragana;306E
+nokatakana;30CE
+nokatakanahalfwidth;FF89
+nonbreakingspace;00A0
+nonenthai;0E13
+nonuthai;0E19
+noonarabic;0646
+noonfinalarabic;FEE6
+noonghunnaarabic;06BA
+noonghunnafinalarabic;FB9F
+noonhehinitialarabic;FEE7 FEEC
+nooninitialarabic;FEE7
+noonjeeminitialarabic;FCD2
+noonjeemisolatedarabic;FC4B
+noonmedialarabic;FEE8
+noonmeeminitialarabic;FCD5
+noonmeemisolatedarabic;FC4E
+noonnoonfinalarabic;FC8D
+notcontains;220C
+notelement;2209
+notelementof;2209
+notequal;2260
+notgreater;226F
+notgreaternorequal;2271
+notgreaternorless;2279
+notidentical;2262
+notless;226E
+notlessnorequal;2270
+notparallel;2226
+notprecedes;2280
+notsubset;2284
+notsucceeds;2281
+notsuperset;2285
+nowarmenian;0576
+nparen;24A9
+nssquare;33B1
+nsuperior;207F
+ntilde;00F1
+nu;03BD
+nuhiragana;306C
+nukatakana;30CC
+nukatakanahalfwidth;FF87
+nuktabengali;09BC
+nuktadeva;093C
+nuktagujarati;0ABC
+nuktagurmukhi;0A3C
+numbersign;0023
+numbersignmonospace;FF03
+numbersignsmall;FE5F
+numeralsigngreek;0374
+numeralsignlowergreek;0375
+numero;2116
+nun;05E0
+nundagesh;FB40
+nundageshhebrew;FB40
+nunhebrew;05E0
+nvsquare;33B5
+nwsquare;33BB
+nyabengali;099E
+nyadeva;091E
+nyagujarati;0A9E
+nyagurmukhi;0A1E
+o;006F
+oacute;00F3
+oangthai;0E2D
+obarred;0275
+obarredcyrillic;04E9
+obarreddieresiscyrillic;04EB
+obengali;0993
+obopomofo;311B
+obreve;014F
+ocandradeva;0911
+ocandragujarati;0A91
+ocandravowelsigndeva;0949
+ocandravowelsigngujarati;0AC9
+ocaron;01D2
+ocircle;24DE
+ocircumflex;00F4
+ocircumflexacute;1ED1
+ocircumflexdotbelow;1ED9
+ocircumflexgrave;1ED3
+ocircumflexhookabove;1ED5
+ocircumflextilde;1ED7
+ocyrillic;043E
+odblacute;0151
+odblgrave;020D
+odeva;0913
+odieresis;00F6
+odieresiscyrillic;04E7
+odotbelow;1ECD
+oe;0153
+oekorean;315A
+ogonek;02DB
+ogonekcmb;0328
+ograve;00F2
+ogujarati;0A93
+oharmenian;0585
+ohiragana;304A
+ohookabove;1ECF
+ohorn;01A1
+ohornacute;1EDB
+ohorndotbelow;1EE3
+ohorngrave;1EDD
+ohornhookabove;1EDF
+ohorntilde;1EE1
+ohungarumlaut;0151
+oi;01A3
+oinvertedbreve;020F
+okatakana;30AA
+okatakanahalfwidth;FF75
+okorean;3157
+olehebrew;05AB
+omacron;014D
+omacronacute;1E53
+omacrongrave;1E51
+omdeva;0950
+omega;03C9
+omega1;03D6
+omegacyrillic;0461
+omegalatinclosed;0277
+omegaroundcyrillic;047B
+omegatitlocyrillic;047D
+omegatonos;03CE
+omgujarati;0AD0
+omicron;03BF
+omicrontonos;03CC
+omonospace;FF4F
+one;0031
+onearabic;0661
+onebengali;09E7
+onecircle;2460
+onecircleinversesansserif;278A
+onedeva;0967
+onedotenleader;2024
+oneeighth;215B
+onefitted;F6DC
+onegujarati;0AE7
+onegurmukhi;0A67
+onehackarabic;0661
+onehalf;00BD
+onehangzhou;3021
+oneideographicparen;3220
+oneinferior;2081
+onemonospace;FF11
+onenumeratorbengali;09F4
+oneoldstyle;F731
+oneparen;2474
+oneperiod;2488
+onepersian;06F1
+onequarter;00BC
+oneroman;2170
+onesuperior;00B9
+onethai;0E51
+onethird;2153
+oogonek;01EB
+oogonekmacron;01ED
+oogurmukhi;0A13
+oomatragurmukhi;0A4B
+oopen;0254
+oparen;24AA
+openbullet;25E6
+option;2325
+ordfeminine;00AA
+ordmasculine;00BA
+orthogonal;221F
+oshortdeva;0912
+oshortvowelsigndeva;094A
+oslash;00F8
+oslashacute;01FF
+osmallhiragana;3049
+osmallkatakana;30A9
+osmallkatakanahalfwidth;FF6B
+ostrokeacute;01FF
+osuperior;F6F0
+otcyrillic;047F
+otilde;00F5
+otildeacute;1E4D
+otildedieresis;1E4F
+oubopomofo;3121
+overline;203E
+overlinecenterline;FE4A
+overlinecmb;0305
+overlinedashed;FE49
+overlinedblwavy;FE4C
+overlinewavy;FE4B
+overscore;00AF
+ovowelsignbengali;09CB
+ovowelsigndeva;094B
+ovowelsigngujarati;0ACB
+p;0070
+paampssquare;3380
+paasentosquare;332B
+pabengali;09AA
+pacute;1E55
+padeva;092A
+pagedown;21DF
+pageup;21DE
+pagujarati;0AAA
+pagurmukhi;0A2A
+pahiragana;3071
+paiyannoithai;0E2F
+pakatakana;30D1
+palatalizationcyrilliccmb;0484
+palochkacyrillic;04C0
+pansioskorean;317F
+paragraph;00B6
+parallel;2225
+parenleft;0028
+parenleftaltonearabic;FD3E
+parenleftbt;F8ED
+parenleftex;F8EC
+parenleftinferior;208D
+parenleftmonospace;FF08
+parenleftsmall;FE59
+parenleftsuperior;207D
+parenlefttp;F8EB
+parenleftvertical;FE35
+parenright;0029
+parenrightaltonearabic;FD3F
+parenrightbt;F8F8
+parenrightex;F8F7
+parenrightinferior;208E
+parenrightmonospace;FF09
+parenrightsmall;FE5A
+parenrightsuperior;207E
+parenrighttp;F8F6
+parenrightvertical;FE36
+partialdiff;2202
+paseqhebrew;05C0
+pashtahebrew;0599
+pasquare;33A9
+patah;05B7
+patah11;05B7
+patah1d;05B7
+patah2a;05B7
+patahhebrew;05B7
+patahnarrowhebrew;05B7
+patahquarterhebrew;05B7
+patahwidehebrew;05B7
+pazerhebrew;05A1
+pbopomofo;3106
+pcircle;24DF
+pdotaccent;1E57
+pe;05E4
+pecyrillic;043F
+pedagesh;FB44
+pedageshhebrew;FB44
+peezisquare;333B
+pefinaldageshhebrew;FB43
+peharabic;067E
+peharmenian;057A
+pehebrew;05E4
+pehfinalarabic;FB57
+pehinitialarabic;FB58
+pehiragana;307A
+pehmedialarabic;FB59
+pekatakana;30DA
+pemiddlehookcyrillic;04A7
+perafehebrew;FB4E
+percent;0025
+percentarabic;066A
+percentmonospace;FF05
+percentsmall;FE6A
+period;002E
+periodarmenian;0589
+periodcentered;00B7
+periodhalfwidth;FF61
+periodinferior;F6E7
+periodmonospace;FF0E
+periodsmall;FE52
+periodsuperior;F6E8
+perispomenigreekcmb;0342
+perpendicular;22A5
+perthousand;2030
+peseta;20A7
+pfsquare;338A
+phabengali;09AB
+phadeva;092B
+phagujarati;0AAB
+phagurmukhi;0A2B
+phi;03C6
+phi1;03D5
+phieuphacirclekorean;327A
+phieuphaparenkorean;321A
+phieuphcirclekorean;326C
+phieuphkorean;314D
+phieuphparenkorean;320C
+philatin;0278
+phinthuthai;0E3A
+phisymbolgreek;03D5
+phook;01A5
+phophanthai;0E1E
+phophungthai;0E1C
+phosamphaothai;0E20
+pi;03C0
+pieupacirclekorean;3273
+pieupaparenkorean;3213
+pieupcieuckorean;3176
+pieupcirclekorean;3265
+pieupkiyeokkorean;3172
+pieupkorean;3142
+pieupparenkorean;3205
+pieupsioskiyeokkorean;3174
+pieupsioskorean;3144
+pieupsiostikeutkorean;3175
+pieupthieuthkorean;3177
+pieuptikeutkorean;3173
+pihiragana;3074
+pikatakana;30D4
+pisymbolgreek;03D6
+piwrarmenian;0583
+plus;002B
+plusbelowcmb;031F
+pluscircle;2295
+plusminus;00B1
+plusmod;02D6
+plusmonospace;FF0B
+plussmall;FE62
+plussuperior;207A
+pmonospace;FF50
+pmsquare;33D8
+pohiragana;307D
+pointingindexdownwhite;261F
+pointingindexleftwhite;261C
+pointingindexrightwhite;261E
+pointingindexupwhite;261D
+pokatakana;30DD
+poplathai;0E1B
+postalmark;3012
+postalmarkface;3020
+pparen;24AB
+precedes;227A
+prescription;211E
+primemod;02B9
+primereversed;2035
+product;220F
+projective;2305
+prolongedkana;30FC
+propellor;2318
+propersubset;2282
+propersuperset;2283
+proportion;2237
+proportional;221D
+psi;03C8
+psicyrillic;0471
+psilipneumatacyrilliccmb;0486
+pssquare;33B0
+puhiragana;3077
+pukatakana;30D7
+pvsquare;33B4
+pwsquare;33BA
+q;0071
+qadeva;0958
+qadmahebrew;05A8
+qafarabic;0642
+qaffinalarabic;FED6
+qafinitialarabic;FED7
+qafmedialarabic;FED8
+qamats;05B8
+qamats10;05B8
+qamats1a;05B8
+qamats1c;05B8
+qamats27;05B8
+qamats29;05B8
+qamats33;05B8
+qamatsde;05B8
+qamatshebrew;05B8
+qamatsnarrowhebrew;05B8
+qamatsqatanhebrew;05B8
+qamatsqatannarrowhebrew;05B8
+qamatsqatanquarterhebrew;05B8
+qamatsqatanwidehebrew;05B8
+qamatsquarterhebrew;05B8
+qamatswidehebrew;05B8
+qarneyparahebrew;059F
+qbopomofo;3111
+qcircle;24E0
+qhook;02A0
+qmonospace;FF51
+qof;05E7
+qofdagesh;FB47
+qofdageshhebrew;FB47
+qofhatafpatah;05E7 05B2
+qofhatafpatahhebrew;05E7 05B2
+qofhatafsegol;05E7 05B1
+qofhatafsegolhebrew;05E7 05B1
+qofhebrew;05E7
+qofhiriq;05E7 05B4
+qofhiriqhebrew;05E7 05B4
+qofholam;05E7 05B9
+qofholamhebrew;05E7 05B9
+qofpatah;05E7 05B7
+qofpatahhebrew;05E7 05B7
+qofqamats;05E7 05B8
+qofqamatshebrew;05E7 05B8
+qofqubuts;05E7 05BB
+qofqubutshebrew;05E7 05BB
+qofsegol;05E7 05B6
+qofsegolhebrew;05E7 05B6
+qofsheva;05E7 05B0
+qofshevahebrew;05E7 05B0
+qoftsere;05E7 05B5
+qoftserehebrew;05E7 05B5
+qparen;24AC
+quarternote;2669
+qubuts;05BB
+qubuts18;05BB
+qubuts25;05BB
+qubuts31;05BB
+qubutshebrew;05BB
+qubutsnarrowhebrew;05BB
+qubutsquarterhebrew;05BB
+qubutswidehebrew;05BB
+question;003F
+questionarabic;061F
+questionarmenian;055E
+questiondown;00BF
+questiondownsmall;F7BF
+questiongreek;037E
+questionmonospace;FF1F
+questionsmall;F73F
+quotedbl;0022
+quotedblbase;201E
+quotedblleft;201C
+quotedblmonospace;FF02
+quotedblprime;301E
+quotedblprimereversed;301D
+quotedblright;201D
+quoteleft;2018
+quoteleftreversed;201B
+quotereversed;201B
+quoteright;2019
+quoterightn;0149
+quotesinglbase;201A
+quotesingle;0027
+quotesinglemonospace;FF07
+r;0072
+raarmenian;057C
+rabengali;09B0
+racute;0155
+radeva;0930
+radical;221A
+radicalex;F8E5
+radoverssquare;33AE
+radoverssquaredsquare;33AF
+radsquare;33AD
+rafe;05BF
+rafehebrew;05BF
+ragujarati;0AB0
+ragurmukhi;0A30
+rahiragana;3089
+rakatakana;30E9
+rakatakanahalfwidth;FF97
+ralowerdiagonalbengali;09F1
+ramiddlediagonalbengali;09F0
+ramshorn;0264
+ratio;2236
+rbopomofo;3116
+rcaron;0159
+rcedilla;0157
+rcircle;24E1
+rcommaaccent;0157
+rdblgrave;0211
+rdotaccent;1E59
+rdotbelow;1E5B
+rdotbelowmacron;1E5D
+referencemark;203B
+reflexsubset;2286
+reflexsuperset;2287
+registered;00AE
+registersans;F8E8
+registerserif;F6DA
+reharabic;0631
+reharmenian;0580
+rehfinalarabic;FEAE
+rehiragana;308C
+rehyehaleflamarabic;0631 FEF3 FE8E 0644
+rekatakana;30EC
+rekatakanahalfwidth;FF9A
+resh;05E8
+reshdageshhebrew;FB48
+reshhatafpatah;05E8 05B2
+reshhatafpatahhebrew;05E8 05B2
+reshhatafsegol;05E8 05B1
+reshhatafsegolhebrew;05E8 05B1
+reshhebrew;05E8
+reshhiriq;05E8 05B4
+reshhiriqhebrew;05E8 05B4
+reshholam;05E8 05B9
+reshholamhebrew;05E8 05B9
+reshpatah;05E8 05B7
+reshpatahhebrew;05E8 05B7
+reshqamats;05E8 05B8
+reshqamatshebrew;05E8 05B8
+reshqubuts;05E8 05BB
+reshqubutshebrew;05E8 05BB
+reshsegol;05E8 05B6
+reshsegolhebrew;05E8 05B6
+reshsheva;05E8 05B0
+reshshevahebrew;05E8 05B0
+reshtsere;05E8 05B5
+reshtserehebrew;05E8 05B5
+reversedtilde;223D
+reviahebrew;0597
+reviamugrashhebrew;0597
+revlogicalnot;2310
+rfishhook;027E
+rfishhookreversed;027F
+rhabengali;09DD
+rhadeva;095D
+rho;03C1
+rhook;027D
+rhookturned;027B
+rhookturnedsuperior;02B5
+rhosymbolgreek;03F1
+rhotichookmod;02DE
+rieulacirclekorean;3271
+rieulaparenkorean;3211
+rieulcirclekorean;3263
+rieulhieuhkorean;3140
+rieulkiyeokkorean;313A
+rieulkiyeoksioskorean;3169
+rieulkorean;3139
+rieulmieumkorean;313B
+rieulpansioskorean;316C
+rieulparenkorean;3203
+rieulphieuphkorean;313F
+rieulpieupkorean;313C
+rieulpieupsioskorean;316B
+rieulsioskorean;313D
+rieulthieuthkorean;313E
+rieultikeutkorean;316A
+rieulyeorinhieuhkorean;316D
+rightangle;221F
+righttackbelowcmb;0319
+righttriangle;22BF
+rihiragana;308A
+rikatakana;30EA
+rikatakanahalfwidth;FF98
+ring;02DA
+ringbelowcmb;0325
+ringcmb;030A
+ringhalfleft;02BF
+ringhalfleftarmenian;0559
+ringhalfleftbelowcmb;031C
+ringhalfleftcentered;02D3
+ringhalfright;02BE
+ringhalfrightbelowcmb;0339
+ringhalfrightcentered;02D2
+rinvertedbreve;0213
+rittorusquare;3351
+rlinebelow;1E5F
+rlongleg;027C
+rlonglegturned;027A
+rmonospace;FF52
+rohiragana;308D
+rokatakana;30ED
+rokatakanahalfwidth;FF9B
+roruathai;0E23
+rparen;24AD
+rrabengali;09DC
+rradeva;0931
+rragurmukhi;0A5C
+rreharabic;0691
+rrehfinalarabic;FB8D
+rrvocalicbengali;09E0
+rrvocalicdeva;0960
+rrvocalicgujarati;0AE0
+rrvocalicvowelsignbengali;09C4
+rrvocalicvowelsigndeva;0944
+rrvocalicvowelsigngujarati;0AC4
+rsuperior;F6F1
+rtblock;2590
+rturned;0279
+rturnedsuperior;02B4
+ruhiragana;308B
+rukatakana;30EB
+rukatakanahalfwidth;FF99
+rupeemarkbengali;09F2
+rupeesignbengali;09F3
+rupiah;F6DD
+ruthai;0E24
+rvocalicbengali;098B
+rvocalicdeva;090B
+rvocalicgujarati;0A8B
+rvocalicvowelsignbengali;09C3
+rvocalicvowelsigndeva;0943
+rvocalicvowelsigngujarati;0AC3
+s;0073
+sabengali;09B8
+sacute;015B
+sacutedotaccent;1E65
+sadarabic;0635
+sadeva;0938
+sadfinalarabic;FEBA
+sadinitialarabic;FEBB
+sadmedialarabic;FEBC
+sagujarati;0AB8
+sagurmukhi;0A38
+sahiragana;3055
+sakatakana;30B5
+sakatakanahalfwidth;FF7B
+sallallahoualayhewasallamarabic;FDFA
+samekh;05E1
+samekhdagesh;FB41
+samekhdageshhebrew;FB41
+samekhhebrew;05E1
+saraaathai;0E32
+saraaethai;0E41
+saraaimaimalaithai;0E44
+saraaimaimuanthai;0E43
+saraamthai;0E33
+saraathai;0E30
+saraethai;0E40
+saraiileftthai;F886
+saraiithai;0E35
+saraileftthai;F885
+saraithai;0E34
+saraothai;0E42
+saraueeleftthai;F888
+saraueethai;0E37
+saraueleftthai;F887
+sarauethai;0E36
+sarauthai;0E38
+sarauuthai;0E39
+sbopomofo;3119
+scaron;0161
+scarondotaccent;1E67
+scedilla;015F
+schwa;0259
+schwacyrillic;04D9
+schwadieresiscyrillic;04DB
+schwahook;025A
+scircle;24E2
+scircumflex;015D
+scommaaccent;0219
+sdotaccent;1E61
+sdotbelow;1E63
+sdotbelowdotaccent;1E69
+seagullbelowcmb;033C
+second;2033
+secondtonechinese;02CA
+section;00A7
+seenarabic;0633
+seenfinalarabic;FEB2
+seeninitialarabic;FEB3
+seenmedialarabic;FEB4
+segol;05B6
+segol13;05B6
+segol1f;05B6
+segol2c;05B6
+segolhebrew;05B6
+segolnarrowhebrew;05B6
+segolquarterhebrew;05B6
+segoltahebrew;0592
+segolwidehebrew;05B6
+seharmenian;057D
+sehiragana;305B
+sekatakana;30BB
+sekatakanahalfwidth;FF7E
+semicolon;003B
+semicolonarabic;061B
+semicolonmonospace;FF1B
+semicolonsmall;FE54
+semivoicedmarkkana;309C
+semivoicedmarkkanahalfwidth;FF9F
+sentisquare;3322
+sentosquare;3323
+seven;0037
+sevenarabic;0667
+sevenbengali;09ED
+sevencircle;2466
+sevencircleinversesansserif;2790
+sevendeva;096D
+seveneighths;215E
+sevengujarati;0AED
+sevengurmukhi;0A6D
+sevenhackarabic;0667
+sevenhangzhou;3027
+sevenideographicparen;3226
+seveninferior;2087
+sevenmonospace;FF17
+sevenoldstyle;F737
+sevenparen;247A
+sevenperiod;248E
+sevenpersian;06F7
+sevenroman;2176
+sevensuperior;2077
+seventeencircle;2470
+seventeenparen;2484
+seventeenperiod;2498
+seventhai;0E57
+sfthyphen;00AD
+shaarmenian;0577
+shabengali;09B6
+shacyrillic;0448
+shaddaarabic;0651
+shaddadammaarabic;FC61
+shaddadammatanarabic;FC5E
+shaddafathaarabic;FC60
+shaddafathatanarabic;0651 064B
+shaddakasraarabic;FC62
+shaddakasratanarabic;FC5F
+shade;2592
+shadedark;2593
+shadelight;2591
+shademedium;2592
+shadeva;0936
+shagujarati;0AB6
+shagurmukhi;0A36
+shalshelethebrew;0593
+shbopomofo;3115
+shchacyrillic;0449
+sheenarabic;0634
+sheenfinalarabic;FEB6
+sheeninitialarabic;FEB7
+sheenmedialarabic;FEB8
+sheicoptic;03E3
+sheqel;20AA
+sheqelhebrew;20AA
+sheva;05B0
+sheva115;05B0
+sheva15;05B0
+sheva22;05B0
+sheva2e;05B0
+shevahebrew;05B0
+shevanarrowhebrew;05B0
+shevaquarterhebrew;05B0
+shevawidehebrew;05B0
+shhacyrillic;04BB
+shimacoptic;03ED
+shin;05E9
+shindagesh;FB49
+shindageshhebrew;FB49
+shindageshshindot;FB2C
+shindageshshindothebrew;FB2C
+shindageshsindot;FB2D
+shindageshsindothebrew;FB2D
+shindothebrew;05C1
+shinhebrew;05E9
+shinshindot;FB2A
+shinshindothebrew;FB2A
+shinsindot;FB2B
+shinsindothebrew;FB2B
+shook;0282
+sigma;03C3
+sigma1;03C2
+sigmafinal;03C2
+sigmalunatesymbolgreek;03F2
+sihiragana;3057
+sikatakana;30B7
+sikatakanahalfwidth;FF7C
+siluqhebrew;05BD
+siluqlefthebrew;05BD
+similar;223C
+sindothebrew;05C2
+siosacirclekorean;3274
+siosaparenkorean;3214
+sioscieuckorean;317E
+sioscirclekorean;3266
+sioskiyeokkorean;317A
+sioskorean;3145
+siosnieunkorean;317B
+siosparenkorean;3206
+siospieupkorean;317D
+siostikeutkorean;317C
+six;0036
+sixarabic;0666
+sixbengali;09EC
+sixcircle;2465
+sixcircleinversesansserif;278F
+sixdeva;096C
+sixgujarati;0AEC
+sixgurmukhi;0A6C
+sixhackarabic;0666
+sixhangzhou;3026
+sixideographicparen;3225
+sixinferior;2086
+sixmonospace;FF16
+sixoldstyle;F736
+sixparen;2479
+sixperiod;248D
+sixpersian;06F6
+sixroman;2175
+sixsuperior;2076
+sixteencircle;246F
+sixteencurrencydenominatorbengali;09F9
+sixteenparen;2483
+sixteenperiod;2497
+sixthai;0E56
+slash;002F
+slashmonospace;FF0F
+slong;017F
+slongdotaccent;1E9B
+smileface;263A
+smonospace;FF53
+sofpasuqhebrew;05C3
+softhyphen;00AD
+softsigncyrillic;044C
+sohiragana;305D
+sokatakana;30BD
+sokatakanahalfwidth;FF7F
+soliduslongoverlaycmb;0338
+solidusshortoverlaycmb;0337
+sorusithai;0E29
+sosalathai;0E28
+sosothai;0E0B
+sosuathai;0E2A
+space;0020
+spacehackarabic;0020
+spade;2660
+spadesuitblack;2660
+spadesuitwhite;2664
+sparen;24AE
+squarebelowcmb;033B
+squarecc;33C4
+squarecm;339D
+squarediagonalcrosshatchfill;25A9
+squarehorizontalfill;25A4
+squarekg;338F
+squarekm;339E
+squarekmcapital;33CE
+squareln;33D1
+squarelog;33D2
+squaremg;338E
+squaremil;33D5
+squaremm;339C
+squaremsquared;33A1
+squareorthogonalcrosshatchfill;25A6
+squareupperlefttolowerrightfill;25A7
+squareupperrighttolowerleftfill;25A8
+squareverticalfill;25A5
+squarewhitewithsmallblack;25A3
+srsquare;33DB
+ssabengali;09B7
+ssadeva;0937
+ssagujarati;0AB7
+ssangcieuckorean;3149
+ssanghieuhkorean;3185
+ssangieungkorean;3180
+ssangkiyeokkorean;3132
+ssangnieunkorean;3165
+ssangpieupkorean;3143
+ssangsioskorean;3146
+ssangtikeutkorean;3138
+ssuperior;F6F2
+sterling;00A3
+sterlingmonospace;FFE1
+strokelongoverlaycmb;0336
+strokeshortoverlaycmb;0335
+subset;2282
+subsetnotequal;228A
+subsetorequal;2286
+succeeds;227B
+suchthat;220B
+suhiragana;3059
+sukatakana;30B9
+sukatakanahalfwidth;FF7D
+sukunarabic;0652
+summation;2211
+sun;263C
+superset;2283
+supersetnotequal;228B
+supersetorequal;2287
+svsquare;33DC
+syouwaerasquare;337C
+t;0074
+tabengali;09A4
+tackdown;22A4
+tackleft;22A3
+tadeva;0924
+tagujarati;0AA4
+tagurmukhi;0A24
+taharabic;0637
+tahfinalarabic;FEC2
+tahinitialarabic;FEC3
+tahiragana;305F
+tahmedialarabic;FEC4
+taisyouerasquare;337D
+takatakana;30BF
+takatakanahalfwidth;FF80
+tatweelarabic;0640
+tau;03C4
+tav;05EA
+tavdages;FB4A
+tavdagesh;FB4A
+tavdageshhebrew;FB4A
+tavhebrew;05EA
+tbar;0167
+tbopomofo;310A
+tcaron;0165
+tccurl;02A8
+tcedilla;0163
+tcheharabic;0686
+tchehfinalarabic;FB7B
+tchehinitialarabic;FB7C
+tchehmedialarabic;FB7D
+tchehmeeminitialarabic;FB7C FEE4
+tcircle;24E3
+tcircumflexbelow;1E71
+tcommaaccent;0163
+tdieresis;1E97
+tdotaccent;1E6B
+tdotbelow;1E6D
+tecyrillic;0442
+tedescendercyrillic;04AD
+teharabic;062A
+tehfinalarabic;FE96
+tehhahinitialarabic;FCA2
+tehhahisolatedarabic;FC0C
+tehinitialarabic;FE97
+tehiragana;3066
+tehjeeminitialarabic;FCA1
+tehjeemisolatedarabic;FC0B
+tehmarbutaarabic;0629
+tehmarbutafinalarabic;FE94
+tehmedialarabic;FE98
+tehmeeminitialarabic;FCA4
+tehmeemisolatedarabic;FC0E
+tehnoonfinalarabic;FC73
+tekatakana;30C6
+tekatakanahalfwidth;FF83
+telephone;2121
+telephoneblack;260E
+telishagedolahebrew;05A0
+telishaqetanahebrew;05A9
+tencircle;2469
+tenideographicparen;3229
+tenparen;247D
+tenperiod;2491
+tenroman;2179
+tesh;02A7
+tet;05D8
+tetdagesh;FB38
+tetdageshhebrew;FB38
+tethebrew;05D8
+tetsecyrillic;04B5
+tevirhebrew;059B
+tevirlefthebrew;059B
+thabengali;09A5
+thadeva;0925
+thagujarati;0AA5
+thagurmukhi;0A25
+thalarabic;0630
+thalfinalarabic;FEAC
+thanthakhatlowleftthai;F898
+thanthakhatlowrightthai;F897
+thanthakhatthai;0E4C
+thanthakhatupperleftthai;F896
+theharabic;062B
+thehfinalarabic;FE9A
+thehinitialarabic;FE9B
+thehmedialarabic;FE9C
+thereexists;2203
+therefore;2234
+theta;03B8
+theta1;03D1
+thetasymbolgreek;03D1
+thieuthacirclekorean;3279
+thieuthaparenkorean;3219
+thieuthcirclekorean;326B
+thieuthkorean;314C
+thieuthparenkorean;320B
+thirteencircle;246C
+thirteenparen;2480
+thirteenperiod;2494
+thonangmonthothai;0E11
+thook;01AD
+thophuthaothai;0E12
+thorn;00FE
+thothahanthai;0E17
+thothanthai;0E10
+thothongthai;0E18
+thothungthai;0E16
+thousandcyrillic;0482
+thousandsseparatorarabic;066C
+thousandsseparatorpersian;066C
+three;0033
+threearabic;0663
+threebengali;09E9
+threecircle;2462
+threecircleinversesansserif;278C
+threedeva;0969
+threeeighths;215C
+threegujarati;0AE9
+threegurmukhi;0A69
+threehackarabic;0663
+threehangzhou;3023
+threeideographicparen;3222
+threeinferior;2083
+threemonospace;FF13
+threenumeratorbengali;09F6
+threeoldstyle;F733
+threeparen;2476
+threeperiod;248A
+threepersian;06F3
+threequarters;00BE
+threequartersemdash;F6DE
+threeroman;2172
+threesuperior;00B3
+threethai;0E53
+thzsquare;3394
+tihiragana;3061
+tikatakana;30C1
+tikatakanahalfwidth;FF81
+tikeutacirclekorean;3270
+tikeutaparenkorean;3210
+tikeutcirclekorean;3262
+tikeutkorean;3137
+tikeutparenkorean;3202
+tilde;02DC
+tildebelowcmb;0330
+tildecmb;0303
+tildecomb;0303
+tildedoublecmb;0360
+tildeoperator;223C
+tildeoverlaycmb;0334
+tildeverticalcmb;033E
+timescircle;2297
+tipehahebrew;0596
+tipehalefthebrew;0596
+tippigurmukhi;0A70
+titlocyrilliccmb;0483
+tiwnarmenian;057F
+tlinebelow;1E6F
+tmonospace;FF54
+toarmenian;0569
+tohiragana;3068
+tokatakana;30C8
+tokatakanahalfwidth;FF84
+tonebarextrahighmod;02E5
+tonebarextralowmod;02E9
+tonebarhighmod;02E6
+tonebarlowmod;02E8
+tonebarmidmod;02E7
+tonefive;01BD
+tonesix;0185
+tonetwo;01A8
+tonos;0384
+tonsquare;3327
+topatakthai;0E0F
+tortoiseshellbracketleft;3014
+tortoiseshellbracketleftsmall;FE5D
+tortoiseshellbracketleftvertical;FE39
+tortoiseshellbracketright;3015
+tortoiseshellbracketrightsmall;FE5E
+tortoiseshellbracketrightvertical;FE3A
+totaothai;0E15
+tpalatalhook;01AB
+tparen;24AF
+trademark;2122
+trademarksans;F8EA
+trademarkserif;F6DB
+tretroflexhook;0288
+triagdn;25BC
+triaglf;25C4
+triagrt;25BA
+triagup;25B2
+ts;02A6
+tsadi;05E6
+tsadidagesh;FB46
+tsadidageshhebrew;FB46
+tsadihebrew;05E6
+tsecyrillic;0446
+tsere;05B5
+tsere12;05B5
+tsere1e;05B5
+tsere2b;05B5
+tserehebrew;05B5
+tserenarrowhebrew;05B5
+tserequarterhebrew;05B5
+tserewidehebrew;05B5
+tshecyrillic;045B
+tsuperior;F6F3
+ttabengali;099F
+ttadeva;091F
+ttagujarati;0A9F
+ttagurmukhi;0A1F
+tteharabic;0679
+ttehfinalarabic;FB67
+ttehinitialarabic;FB68
+ttehmedialarabic;FB69
+tthabengali;09A0
+tthadeva;0920
+tthagujarati;0AA0
+tthagurmukhi;0A20
+tturned;0287
+tuhiragana;3064
+tukatakana;30C4
+tukatakanahalfwidth;FF82
+tusmallhiragana;3063
+tusmallkatakana;30C3
+tusmallkatakanahalfwidth;FF6F
+twelvecircle;246B
+twelveparen;247F
+twelveperiod;2493
+twelveroman;217B
+twentycircle;2473
+twentyhangzhou;5344
+twentyparen;2487
+twentyperiod;249B
+two;0032
+twoarabic;0662
+twobengali;09E8
+twocircle;2461
+twocircleinversesansserif;278B
+twodeva;0968
+twodotenleader;2025
+twodotleader;2025
+twodotleadervertical;FE30
+twogujarati;0AE8
+twogurmukhi;0A68
+twohackarabic;0662
+twohangzhou;3022
+twoideographicparen;3221
+twoinferior;2082
+twomonospace;FF12
+twonumeratorbengali;09F5
+twooldstyle;F732
+twoparen;2475
+twoperiod;2489
+twopersian;06F2
+tworoman;2171
+twostroke;01BB
+twosuperior;00B2
+twothai;0E52
+twothirds;2154
+u;0075
+uacute;00FA
+ubar;0289
+ubengali;0989
+ubopomofo;3128
+ubreve;016D
+ucaron;01D4
+ucircle;24E4
+ucircumflex;00FB
+ucircumflexbelow;1E77
+ucyrillic;0443
+udattadeva;0951
+udblacute;0171
+udblgrave;0215
+udeva;0909
+udieresis;00FC
+udieresisacute;01D8
+udieresisbelow;1E73
+udieresiscaron;01DA
+udieresiscyrillic;04F1
+udieresisgrave;01DC
+udieresismacron;01D6
+udotbelow;1EE5
+ugrave;00F9
+ugujarati;0A89
+ugurmukhi;0A09
+uhiragana;3046
+uhookabove;1EE7
+uhorn;01B0
+uhornacute;1EE9
+uhorndotbelow;1EF1
+uhorngrave;1EEB
+uhornhookabove;1EED
+uhorntilde;1EEF
+uhungarumlaut;0171
+uhungarumlautcyrillic;04F3
+uinvertedbreve;0217
+ukatakana;30A6
+ukatakanahalfwidth;FF73
+ukcyrillic;0479
+ukorean;315C
+umacron;016B
+umacroncyrillic;04EF
+umacrondieresis;1E7B
+umatragurmukhi;0A41
+umonospace;FF55
+underscore;005F
+underscoredbl;2017
+underscoremonospace;FF3F
+underscorevertical;FE33
+underscorewavy;FE4F
+union;222A
+universal;2200
+uogonek;0173
+uparen;24B0
+upblock;2580
+upperdothebrew;05C4
+upsilon;03C5
+upsilondieresis;03CB
+upsilondieresistonos;03B0
+upsilonlatin;028A
+upsilontonos;03CD
+uptackbelowcmb;031D
+uptackmod;02D4
+uragurmukhi;0A73
+uring;016F
+ushortcyrillic;045E
+usmallhiragana;3045
+usmallkatakana;30A5
+usmallkatakanahalfwidth;FF69
+ustraightcyrillic;04AF
+ustraightstrokecyrillic;04B1
+utilde;0169
+utildeacute;1E79
+utildebelow;1E75
+uubengali;098A
+uudeva;090A
+uugujarati;0A8A
+uugurmukhi;0A0A
+uumatragurmukhi;0A42
+uuvowelsignbengali;09C2
+uuvowelsigndeva;0942
+uuvowelsigngujarati;0AC2
+uvowelsignbengali;09C1
+uvowelsigndeva;0941
+uvowelsigngujarati;0AC1
+v;0076
+vadeva;0935
+vagujarati;0AB5
+vagurmukhi;0A35
+vakatakana;30F7
+vav;05D5
+vavdagesh;FB35
+vavdagesh65;FB35
+vavdageshhebrew;FB35
+vavhebrew;05D5
+vavholam;FB4B
+vavholamhebrew;FB4B
+vavvavhebrew;05F0
+vavyodhebrew;05F1
+vcircle;24E5
+vdotbelow;1E7F
+vecyrillic;0432
+veharabic;06A4
+vehfinalarabic;FB6B
+vehinitialarabic;FB6C
+vehmedialarabic;FB6D
+vekatakana;30F9
+venus;2640
+verticalbar;007C
+verticallineabovecmb;030D
+verticallinebelowcmb;0329
+verticallinelowmod;02CC
+verticallinemod;02C8
+vewarmenian;057E
+vhook;028B
+vikatakana;30F8
+viramabengali;09CD
+viramadeva;094D
+viramagujarati;0ACD
+visargabengali;0983
+visargadeva;0903
+visargagujarati;0A83
+vmonospace;FF56
+voarmenian;0578
+voicediterationhiragana;309E
+voicediterationkatakana;30FE
+voicedmarkkana;309B
+voicedmarkkanahalfwidth;FF9E
+vokatakana;30FA
+vparen;24B1
+vtilde;1E7D
+vturned;028C
+vuhiragana;3094
+vukatakana;30F4
+w;0077
+wacute;1E83
+waekorean;3159
+wahiragana;308F
+wakatakana;30EF
+wakatakanahalfwidth;FF9C
+wakorean;3158
+wasmallhiragana;308E
+wasmallkatakana;30EE
+wattosquare;3357
+wavedash;301C
+wavyunderscorevertical;FE34
+wawarabic;0648
+wawfinalarabic;FEEE
+wawhamzaabovearabic;0624
+wawhamzaabovefinalarabic;FE86
+wbsquare;33DD
+wcircle;24E6
+wcircumflex;0175
+wdieresis;1E85
+wdotaccent;1E87
+wdotbelow;1E89
+wehiragana;3091
+weierstrass;2118
+wekatakana;30F1
+wekorean;315E
+weokorean;315D
+wgrave;1E81
+whitebullet;25E6
+whitecircle;25CB
+whitecircleinverse;25D9
+whitecornerbracketleft;300E
+whitecornerbracketleftvertical;FE43
+whitecornerbracketright;300F
+whitecornerbracketrightvertical;FE44
+whitediamond;25C7
+whitediamondcontainingblacksmalldiamond;25C8
+whitedownpointingsmalltriangle;25BF
+whitedownpointingtriangle;25BD
+whiteleftpointingsmalltriangle;25C3
+whiteleftpointingtriangle;25C1
+whitelenticularbracketleft;3016
+whitelenticularbracketright;3017
+whiterightpointingsmalltriangle;25B9
+whiterightpointingtriangle;25B7
+whitesmallsquare;25AB
+whitesmilingface;263A
+whitesquare;25A1
+whitestar;2606
+whitetelephone;260F
+whitetortoiseshellbracketleft;3018
+whitetortoiseshellbracketright;3019
+whiteuppointingsmalltriangle;25B5
+whiteuppointingtriangle;25B3
+wihiragana;3090
+wikatakana;30F0
+wikorean;315F
+wmonospace;FF57
+wohiragana;3092
+wokatakana;30F2
+wokatakanahalfwidth;FF66
+won;20A9
+wonmonospace;FFE6
+wowaenthai;0E27
+wparen;24B2
+wring;1E98
+wsuperior;02B7
+wturned;028D
+wynn;01BF
+x;0078
+xabovecmb;033D
+xbopomofo;3112
+xcircle;24E7
+xdieresis;1E8D
+xdotaccent;1E8B
+xeharmenian;056D
+xi;03BE
+xmonospace;FF58
+xparen;24B3
+xsuperior;02E3
+y;0079
+yaadosquare;334E
+yabengali;09AF
+yacute;00FD
+yadeva;092F
+yaekorean;3152
+yagujarati;0AAF
+yagurmukhi;0A2F
+yahiragana;3084
+yakatakana;30E4
+yakatakanahalfwidth;FF94
+yakorean;3151
+yamakkanthai;0E4E
+yasmallhiragana;3083
+yasmallkatakana;30E3
+yasmallkatakanahalfwidth;FF6C
+yatcyrillic;0463
+ycircle;24E8
+ycircumflex;0177
+ydieresis;00FF
+ydotaccent;1E8F
+ydotbelow;1EF5
+yeharabic;064A
+yehbarreearabic;06D2
+yehbarreefinalarabic;FBAF
+yehfinalarabic;FEF2
+yehhamzaabovearabic;0626
+yehhamzaabovefinalarabic;FE8A
+yehhamzaaboveinitialarabic;FE8B
+yehhamzaabovemedialarabic;FE8C
+yehinitialarabic;FEF3
+yehmedialarabic;FEF4
+yehmeeminitialarabic;FCDD
+yehmeemisolatedarabic;FC58
+yehnoonfinalarabic;FC94
+yehthreedotsbelowarabic;06D1
+yekorean;3156
+yen;00A5
+yenmonospace;FFE5
+yeokorean;3155
+yeorinhieuhkorean;3186
+yerahbenyomohebrew;05AA
+yerahbenyomolefthebrew;05AA
+yericyrillic;044B
+yerudieresiscyrillic;04F9
+yesieungkorean;3181
+yesieungpansioskorean;3183
+yesieungsioskorean;3182
+yetivhebrew;059A
+ygrave;1EF3
+yhook;01B4
+yhookabove;1EF7
+yiarmenian;0575
+yicyrillic;0457
+yikorean;3162
+yinyang;262F
+yiwnarmenian;0582
+ymonospace;FF59
+yod;05D9
+yoddagesh;FB39
+yoddageshhebrew;FB39
+yodhebrew;05D9
+yodyodhebrew;05F2
+yodyodpatahhebrew;FB1F
+yohiragana;3088
+yoikorean;3189
+yokatakana;30E8
+yokatakanahalfwidth;FF96
+yokorean;315B
+yosmallhiragana;3087
+yosmallkatakana;30E7
+yosmallkatakanahalfwidth;FF6E
+yotgreek;03F3
+yoyaekorean;3188
+yoyakorean;3187
+yoyakthai;0E22
+yoyingthai;0E0D
+yparen;24B4
+ypogegrammeni;037A
+ypogegrammenigreekcmb;0345
+yr;01A6
+yring;1E99
+ysuperior;02B8
+ytilde;1EF9
+yturned;028E
+yuhiragana;3086
+yuikorean;318C
+yukatakana;30E6
+yukatakanahalfwidth;FF95
+yukorean;3160
+yusbigcyrillic;046B
+yusbigiotifiedcyrillic;046D
+yuslittlecyrillic;0467
+yuslittleiotifiedcyrillic;0469
+yusmallhiragana;3085
+yusmallkatakana;30E5
+yusmallkatakanahalfwidth;FF6D
+yuyekorean;318B
+yuyeokorean;318A
+yyabengali;09DF
+yyadeva;095F
+z;007A
+zaarmenian;0566
+zacute;017A
+zadeva;095B
+zagurmukhi;0A5B
+zaharabic;0638
+zahfinalarabic;FEC6
+zahinitialarabic;FEC7
+zahiragana;3056
+zahmedialarabic;FEC8
+zainarabic;0632
+zainfinalarabic;FEB0
+zakatakana;30B6
+zaqefgadolhebrew;0595
+zaqefqatanhebrew;0594
+zarqahebrew;0598
+zayin;05D6
+zayindagesh;FB36
+zayindageshhebrew;FB36
+zayinhebrew;05D6
+zbopomofo;3117
+zcaron;017E
+zcircle;24E9
+zcircumflex;1E91
+zcurl;0291
+zdot;017C
+zdotaccent;017C
+zdotbelow;1E93
+zecyrillic;0437
+zedescendercyrillic;0499
+zedieresiscyrillic;04DF
+zehiragana;305C
+zekatakana;30BC
+zero;0030
+zeroarabic;0660
+zerobengali;09E6
+zerodeva;0966
+zerogujarati;0AE6
+zerogurmukhi;0A66
+zerohackarabic;0660
+zeroinferior;2080
+zeromonospace;FF10
+zerooldstyle;F730
+zeropersian;06F0
+zerosuperior;2070
+zerothai;0E50
+zerowidthjoiner;FEFF
+zerowidthnonjoiner;200C
+zerowidthspace;200B
+zeta;03B6
+zhbopomofo;3113
+zhearmenian;056A
+zhebrevecyrillic;04C2
+zhecyrillic;0436
+zhedescendercyrillic;0497
+zhedieresiscyrillic;04DD
+zihiragana;3058
+zikatakana;30B8
+zinorhebrew;05AE
+zlinebelow;1E95
+zmonospace;FF5A
+zohiragana;305E
+zokatakana;30BE
+zparen;24B5
+zretroflexhook;0290
+zstroke;01B6
+zuhiragana;305A
+zukatakana;30BA
+#--end
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..739ceb5
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,15 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.0.0'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/gen/net/sf/andpdf/pdfviewer/BuildConfig.java b/gen/net/sf/andpdf/pdfviewer/BuildConfig.java
new file mode 100644
index 0000000..6b20ef7
--- /dev/null
+++ b/gen/net/sf/andpdf/pdfviewer/BuildConfig.java
@@ -0,0 +1,8 @@
+/*___Generated_by_IDEA___*/
+
+package net.sf.andpdf.pdfviewer;
+
+/* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */
+public final class BuildConfig {
+ public final static boolean DEBUG = Boolean.parseBoolean(null);
+}
\ No newline at end of file
diff --git a/gen/net/sf/andpdf/pdfviewer/Manifest.java b/gen/net/sf/andpdf/pdfviewer/Manifest.java
new file mode 100644
index 0000000..093b478
--- /dev/null
+++ b/gen/net/sf/andpdf/pdfviewer/Manifest.java
@@ -0,0 +1,7 @@
+/*___Generated_by_IDEA___*/
+
+package net.sf.andpdf.pdfviewer;
+
+/* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */
+public final class Manifest {
+}
\ No newline at end of file
diff --git a/gen/net/sf/andpdf/pdfviewer/R.java b/gen/net/sf/andpdf/pdfviewer/R.java
new file mode 100644
index 0000000..4caecf1
--- /dev/null
+++ b/gen/net/sf/andpdf/pdfviewer/R.java
@@ -0,0 +1,7 @@
+/*___Generated_by_IDEA___*/
+
+package net.sf.andpdf.pdfviewer;
+
+/* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */
+public final class R {
+}
\ No newline at end of file
diff --git a/gestureimageview/build.gradle b/gestureimageview/build.gradle
new file mode 100644
index 0000000..ba19968
--- /dev/null
+++ b/gestureimageview/build.gradle
@@ -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 {}
\ No newline at end of file
diff --git a/gestureimageview/gestureimageview.iml b/gestureimageview/gestureimageview.iml
new file mode 100644
index 0000000..748fd66
--- /dev/null
+++ b/gestureimageview/gestureimageview.iml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestureimageview/src/main/AndroidManifest.xml b/gestureimageview/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ceae95b
--- /dev/null
+++ b/gestureimageview/src/main/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestureimageview/src/main/java/com/polites/android/Animation.java b/gestureimageview/src/main/java/com/polites/android/Animation.java
new file mode 100644
index 0000000..6e3d3bc
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/Animation.java
@@ -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);
+
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/Animator.java b/gestureimageview/src/main/java/com/polites/android/Animator.java
new file mode 100644
index 0000000..fbcb7d2
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/Animator.java
@@ -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;
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/Direction.java b/gestureimageview/src/main/java/com/polites/android/Direction.java
new file mode 100644
index 0000000..83a605b
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/Direction.java
@@ -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;
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/FlingAnimation.java b/gestureimageview/src/main/java/com/polites/android/FlingAnimation.java
new file mode 100644
index 0000000..66cdca5
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/FlingAnimation.java
@@ -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;
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/FlingAnimationListener.java b/gestureimageview/src/main/java/com/polites/android/FlingAnimationListener.java
new file mode 100644
index 0000000..b1b38e3
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/FlingAnimationListener.java
@@ -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();
+
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/FlingListener.java b/gestureimageview/src/main/java/com/polites/android/FlingListener.java
new file mode 100644
index 0000000..1b9b3dc
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/FlingListener.java
@@ -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;
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/GestureImageView.java b/gestureimageview/src/main/java/com/polites/android/GestureImageView.java
new file mode 100644
index 0000000..dd4d511
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/GestureImageView.java
@@ -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;
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/GestureImageViewListener.java b/gestureimageview/src/main/java/com/polites/android/GestureImageViewListener.java
new file mode 100644
index 0000000..ad5df10
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/GestureImageViewListener.java
@@ -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);
+
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/GestureImageViewTouchListener.java b/gestureimageview/src/main/java/com/polites/android/GestureImageViewTouchListener.java
new file mode 100644
index 0000000..7a10f07
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/GestureImageViewTouchListener.java
@@ -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;
+ }
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/MathUtils.java b/gestureimageview/src/main/java/com/polites/android/MathUtils.java
new file mode 100644
index 0000000..1f98a5f
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/MathUtils.java
@@ -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);
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/MoveAnimation.java b/gestureimageview/src/main/java/com/polites/android/MoveAnimation.java
new file mode 100644
index 0000000..ccb8812
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/MoveAnimation.java
@@ -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;
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/MoveAnimationListener.java b/gestureimageview/src/main/java/com/polites/android/MoveAnimationListener.java
new file mode 100644
index 0000000..cb570b2
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/MoveAnimationListener.java
@@ -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);
+
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/VectorF.java b/gestureimageview/src/main/java/com/polites/android/VectorF.java
new file mode 100644
index 0000000..e86d2bd
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/VectorF.java
@@ -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;
+ }
+
+
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/ZoomAnimation.java b/gestureimageview/src/main/java/com/polites/android/ZoomAnimation.java
new file mode 100644
index 0000000..5cceeb0
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/ZoomAnimation.java
@@ -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;
+ }
+}
diff --git a/gestureimageview/src/main/java/com/polites/android/ZoomAnimationListener.java b/gestureimageview/src/main/java/com/polites/android/ZoomAnimationListener.java
new file mode 100644
index 0000000..4d50898
--- /dev/null
+++ b/gestureimageview/src/main/java/com/polites/android/ZoomAnimationListener.java
@@ -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();
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..05ef575
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0ee6bab
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Apr 19 14:29:07 CST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/import-summary.txt b/import-summary.txt
new file mode 100644
index 0000000..a66c6fd
--- /dev/null
+++ b/import-summary.txt
@@ -0,0 +1,40 @@
+ECLIPSE ANDROID PROJECT IMPORT SUMMARY
+======================================
+
+Ignored Files:
+--------------
+The following files were *not* copied into the new Gradle project; you
+should evaluate whether these are still needed in your project and if
+so manually move them:
+
+* .DS_Store
+* .gitignore
+* build.xml
+* gesture-imageview.iml
+* proguard.cfg
+
+Moved Files:
+------------
+Android Gradle projects use a different directory structure than ADT
+Eclipse projects. Here's how the projects were restructured:
+
+* AndroidManifest.xml => gestureimageview/src/main/AndroidManifest.xml
+* src/ => gestureimageview/src/main/java/
+* src/.DS_Store => gestureimageview/src/main/resources/.DS_Store
+* src/com/.DS_Store => gestureimageview/src/main/resources/com/.DS_Store
+* src/com/polites/.DS_Store => gestureimageview/src/main/resources/com/polites/.DS_Store
+
+Next Steps:
+-----------
+You can now build the project. The Gradle project needs network
+connectivity to download dependencies.
+
+Bugs:
+-----
+If for some reason your project does not build, and you determine that
+it is due to a bug or limitation of the Eclipse to Gradle importer,
+please file a bug at http://b.android.com with category
+Component-Tools.
+
+(This import summary is for your information only, and can be deleted
+after import once you are satisfied with the results.)
diff --git a/local.properties b/local.properties
new file mode 100644
index 0000000..3515972
--- /dev/null
+++ b/local.properties
@@ -0,0 +1,12 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Tue Feb 23 18:08:03 CST 2016
+ndk.dir=/Users/winney/Documents/android-sdk-macosx/ndk-bundle
+sdk.dir=/Users/winney/Documents/android-sdk-macosx
diff --git a/pdfviewsample/.gitignore b/pdfviewsample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/pdfviewsample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/pdfviewsample/build.gradle b/pdfviewsample/build.gradle
new file mode 100644
index 0000000..c5111b1
--- /dev/null
+++ b/pdfviewsample/build.gradle
@@ -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')
+}
diff --git a/pdfviewsample/pdfviewsample.iml b/pdfviewsample/pdfviewsample.iml
new file mode 100644
index 0000000..6ebb85b
--- /dev/null
+++ b/pdfviewsample/pdfviewsample.iml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pdfviewsample/proguard-rules.pro b/pdfviewsample/proguard-rules.pro
new file mode 100644
index 0000000..8471c22
--- /dev/null
+++ b/pdfviewsample/proguard-rules.pro
@@ -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 *;
+#}
diff --git a/pdfviewsample/src/androidTest/java/com/wyx/pdfviewsample/ApplicationTest.java b/pdfviewsample/src/androidTest/java/com/wyx/pdfviewsample/ApplicationTest.java
new file mode 100644
index 0000000..ba92db9
--- /dev/null
+++ b/pdfviewsample/src/androidTest/java/com/wyx/pdfviewsample/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.wyx.pdfviewsample;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/pdfviewsample/src/main/AndroidManifest.xml b/pdfviewsample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b855ada
--- /dev/null
+++ b/pdfviewsample/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pdfviewsample/src/main/assets/about.pdf b/pdfviewsample/src/main/assets/about.pdf
new file mode 100755
index 0000000..af992a0
Binary files /dev/null and b/pdfviewsample/src/main/assets/about.pdf differ
diff --git a/pdfviewsample/src/main/assets/sample.pdf b/pdfviewsample/src/main/assets/sample.pdf
new file mode 100755
index 0000000..376f277
Binary files /dev/null and b/pdfviewsample/src/main/assets/sample.pdf differ
diff --git a/pdfviewsample/src/main/java/com/wyx/pdfviewsample/L.java b/pdfviewsample/src/main/java/com/wyx/pdfviewsample/L.java
new file mode 100644
index 0000000..62a1242
--- /dev/null
+++ b/pdfviewsample/src/main/java/com/wyx/pdfviewsample/L.java
@@ -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);
+ }
+ }
+}
diff --git a/pdfviewsample/src/main/java/com/wyx/pdfviewsample/Main2Activity.java b/pdfviewsample/src/main/java/com/wyx/pdfviewsample/Main2Activity.java
new file mode 100644
index 0000000..21d2de6
--- /dev/null
+++ b/pdfviewsample/src/main/java/com/wyx/pdfviewsample/Main2Activity.java
@@ -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);
+ }
+}
diff --git a/pdfviewsample/src/main/java/com/wyx/pdfviewsample/MainActivity.java b/pdfviewsample/src/main/java/com/wyx/pdfviewsample/MainActivity.java
new file mode 100644
index 0000000..ebd60b5
--- /dev/null
+++ b/pdfviewsample/src/main/java/com/wyx/pdfviewsample/MainActivity.java
@@ -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;
+ }
+}
+
diff --git a/pdfviewsample/src/main/res/layout/activity_main.xml b/pdfviewsample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..4ae4c8c
--- /dev/null
+++ b/pdfviewsample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/pdfviewsample/src/main/res/layout/activity_main2.xml b/pdfviewsample/src/main/res/layout/activity_main2.xml
new file mode 100644
index 0000000..45a1003
--- /dev/null
+++ b/pdfviewsample/src/main/res/layout/activity_main2.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/pdfviewsample/src/main/res/mipmap-hdpi/ic_launcher.png b/pdfviewsample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/pdfviewsample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/pdfviewsample/src/main/res/mipmap-mdpi/ic_launcher.png b/pdfviewsample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/pdfviewsample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/pdfviewsample/src/main/res/mipmap-xhdpi/ic_launcher.png b/pdfviewsample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/pdfviewsample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/pdfviewsample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pdfviewsample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/pdfviewsample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/pdfviewsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pdfviewsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/pdfviewsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/pdfviewsample/src/main/res/values-w820dp/dimens.xml b/pdfviewsample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/pdfviewsample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/pdfviewsample/src/main/res/values/colors.xml b/pdfviewsample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..5a077b3
--- /dev/null
+++ b/pdfviewsample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/pdfviewsample/src/main/res/values/dimens.xml b/pdfviewsample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/pdfviewsample/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/pdfviewsample/src/main/res/values/strings.xml b/pdfviewsample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e21481d
--- /dev/null
+++ b/pdfviewsample/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ PdfViewSample
+
diff --git a/pdfviewsample/src/main/res/values/styles.xml b/pdfviewsample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..705be27
--- /dev/null
+++ b/pdfviewsample/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/pdfviewsample/src/test/java/com/wyx/pdfviewsample/ExampleUnitTest.java b/pdfviewsample/src/test/java/com/wyx/pdfviewsample/ExampleUnitTest.java
new file mode 100644
index 0000000..3754320
--- /dev/null
+++ b/pdfviewsample/src/test/java/com/wyx/pdfviewsample/ExampleUnitTest.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..8f90eb5
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':PdfView', ':pdfviewsample'
+include ':gestureimageview'