summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Svetoslav Ganov <svetoslavganov@google.com> 2013-06-21 20:09:50 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2013-06-21 20:09:51 +0000
commitb06accf34f53956a5ccd44cecc3604cc2d166cd7 (patch)
treeb5b34ae664e9c2d6cf5835522f8ea4ff6d1c5911
parent142a29f45f478fe7e6323dd84d99014c1c5c473f (diff)
parentff4adde5737be08d3e2d03fbe588c591d27d4a74 (diff)
Merge "Generate PDF from Canvas."
-rw-r--r--api/current.txt34
-rw-r--r--core/java/android/print/pdf/PdfDocument.java443
-rw-r--r--core/jni/Android.mk2
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android/print/android_print_pdf_PdfDocument.cpp87
-rw-r--r--graphics/java/android/graphics/Canvas.java52
6 files changed, 607 insertions, 13 deletions
diff --git a/api/current.txt b/api/current.txt
index a896ecb8025a..481521e2498e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18385,6 +18385,40 @@ package android.preference {
}
+package android.print.pdf {
+
+ public final class PdfDocument {
+ method public void close();
+ method protected final void finalize() throws java.lang.Throwable;
+ method public void finishPage(android.print.pdf.PdfDocument.Page);
+ method public java.util.List<android.print.pdf.PdfDocument.PageInfo> getPages();
+ method public static android.print.pdf.PdfDocument open();
+ method public android.print.pdf.PdfDocument.Page startPage(android.print.pdf.PdfDocument.PageInfo);
+ method public void writeTo(java.io.OutputStream);
+ }
+
+ public static final class PdfDocument.Page {
+ method public android.graphics.Canvas getCanvas();
+ method public android.print.pdf.PdfDocument.PageInfo getInfo();
+ }
+
+ public static final class PdfDocument.PageInfo {
+ method public android.graphics.Rect getContentSize();
+ method public int getDesity();
+ method public android.graphics.Matrix getInitialTransform();
+ method public int getPageNumber();
+ method public android.graphics.Rect getPageSize();
+ }
+
+ public static final class PdfDocument.PageInfo.Builder {
+ ctor public PdfDocument.PageInfo.Builder(android.graphics.Rect, int, int);
+ method public android.print.pdf.PdfDocument.PageInfo create();
+ method public android.print.pdf.PdfDocument.PageInfo.Builder setContentSize(android.graphics.Rect);
+ method public android.print.pdf.PdfDocument.PageInfo.Builder setInitialTransform(android.graphics.Matrix);
+ }
+
+}
+
package android.provider {
public final class AlarmClock {
diff --git a/core/java/android/print/pdf/PdfDocument.java b/core/java/android/print/pdf/PdfDocument.java
new file mode 100644
index 000000000000..7fb170e212e9
--- /dev/null
+++ b/core/java/android/print/pdf/PdfDocument.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2013 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 android.print.pdf;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+
+import dalvik.system.CloseGuard;
+
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>
+ * This class enables generating a PDF document from native Android content. You
+ * open a new document and then for every page you want to add you start a page,
+ * write content to the page, and finish the page. After you are done with all
+ * pages, you write the document to an output stream and close the document.
+ * After a document is closed you should not use it anymore.
+ * </p>
+ * <p>
+ * A typical use of the APIs looks like this:
+ * </p>
+ * <pre>
+ * // open a new document
+ * PdfDocument document = PdfDocument.open();
+ *
+ * // crate a page description
+ * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1, 300).create();
+ *
+ * // start a page
+ * Page page = document.startPage(pageInfo);
+ *
+ * // draw something on the page
+ * View content = getContentView();
+ * content.draw(page.getCanvas());
+ *
+ * // finish the page
+ * document.finishPage(page);
+ * . . .
+ * add more pages
+ * . . .
+ * // write the document content
+ * document.writeTo(getOutputStream());
+ *
+ * //close the document
+ * document.close();
+ * </pre>
+ */
+public final class PdfDocument {
+
+ private final byte[] mChunk = new byte[4096];
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private final List<PageInfo> mPages = new ArrayList<PageInfo>();
+
+ private int mNativeDocument;
+
+ private Page mCurrentPage;
+
+ /**
+ * Opens a new document.
+ * <p>
+ * <strong>Note:</strong> You must close the document after you are
+ * done by calling {@link #close()}
+ * </p>
+ *
+ * @return The document.
+ *
+ * @see #close()
+ */
+ public static PdfDocument open() {
+ return new PdfDocument();
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ private PdfDocument() {
+ mNativeDocument = nativeCreateDocument();
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Starts a page using the provided {@link PageInfo}. After the page
+ * is created you can draw arbitrary content on the page's canvas which
+ * you can get by calling {@link Page#getCanvas()}. After you are done
+ * drawing the content you should finish the page by calling
+ * {@link #finishPage(Page). After the page is finished you should
+ * no longer access the page or its canvas.
+ * <p>
+ * <strong>Note:</strong> Do not call this method after {@link #close()}.
+ * </p>
+ *
+ * @param pageInfo The page info.
+ * @return A blank page.
+ *
+ * @see #finishPage(Page)
+ */
+ public Page startPage(PageInfo pageInfo) {
+ throwIfClosed();
+ if (pageInfo == null) {
+ throw new IllegalArgumentException("page cannot be null!");
+ }
+ if (mCurrentPage != null) {
+ throw new IllegalStateException("Previous page not finished!");
+ }
+ Canvas canvas = new PdfCanvas(nativeCreatePage(pageInfo.mPageSize,
+ pageInfo.mContentSize, pageInfo.mInitialTransform.native_instance),
+ pageInfo.mDensity);
+ mCurrentPage = new Page(canvas, pageInfo);
+ return mCurrentPage;
+ }
+
+ /**
+ * Finishes a started page. You should always finish the last started page.
+ * <p>
+ * <strong>Note:</strong> Do not call this method after {@link #close()}.
+ * </p>
+ *
+ * @param page The page.
+ *
+ * @see #startPage(PageInfo)
+ */
+ public void finishPage(Page page) {
+ throwIfClosed();
+ if (page == null) {
+ throw new IllegalArgumentException("page cannot be null");
+ }
+ if (page != mCurrentPage) {
+ throw new IllegalStateException("invalid page");
+ }
+ mPages.add(page.getInfo());
+ mCurrentPage = null;
+ nativeAppendPage(mNativeDocument, page.mCanvas.mNativeCanvas);
+ page.finish();
+ }
+
+ /**
+ * Writes the document to an output stream.
+ * <p>
+ * <strong>Note:</strong> Do not call this method after {@link #close()}.
+ * </p>
+ *
+ * @param out The output stream.
+ */
+ public void writeTo(OutputStream out) {
+ throwIfClosed();
+ if (out == null) {
+ throw new IllegalArgumentException("out cannot be null!");
+ }
+ nativeWriteTo(mNativeDocument, out, mChunk);
+ }
+
+ /**
+ * Gets the pages of the document.
+ *
+ * @return The pages.
+ */
+ public List<PageInfo> getPages() {
+ return Collections.unmodifiableList(mPages);
+ }
+
+ /**
+ * Closes this document. This method should be called after you
+ * are done working with the document. After this call the document
+ * is considered closed and none of its methods should be called.
+ */
+ public void close() {
+ dispose();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void dispose() {
+ if (mNativeDocument != 0) {
+ nativeFinalize(mNativeDocument);
+ mCloseGuard.close();
+ mNativeDocument = 0;
+ }
+ }
+
+ /**
+ * Throws an exception if the document is already closed.
+ */
+ private void throwIfClosed() {
+ if (mNativeDocument == 0) {
+ throw new IllegalStateException("document is closed!");
+ }
+ }
+
+ private native int nativeCreateDocument();
+
+ private native void nativeFinalize(int document);
+
+ private native void nativeAppendPage(int document, int page);
+
+ private native void nativeWriteTo(int document, OutputStream out, byte[] chunk);
+
+ private static native int nativeCreatePage(Rect pageSize,
+ Rect contentSize, int nativeMatrix);
+
+
+ private final class PdfCanvas extends Canvas {
+
+ public PdfCanvas(int nativeCanvas, int density) {
+ super(nativeCanvas);
+ super.setDensity(density);
+ }
+
+ @Override
+ public void setBitmap(Bitmap bitmap) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDensity(int density) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setScreenDensity(int density) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * This class represents meta-data that describes a PDF {@link Page}.
+ */
+ public static final class PageInfo {
+ private Rect mPageSize;
+ private Rect mContentSize;
+ private Matrix mInitialTransform;
+ private int mPageNumber;
+ private int mDensity;
+
+ /**
+ * Creates a new instance.
+ */
+ private PageInfo() {
+ /* do nothing */
+ }
+
+ /**
+ * Gets the page size in pixels.
+ *
+ * @return The page size.
+ */
+ public Rect getPageSize() {
+ return mPageSize;
+ }
+
+ /**
+ * Get the content size in pixels.
+ *
+ * @return The content size.
+ */
+ public Rect getContentSize() {
+ return mContentSize;
+ }
+
+ /**
+ * Gets the initial transform which is applied to the page. This may be
+ * useful to move the origin to account for a margin, apply scale, or
+ * apply a rotation.
+ *
+ * @return The initial transform.
+ */
+ public Matrix getInitialTransform() {
+ return mInitialTransform;
+ }
+
+ /**
+ * Gets the page number.
+ *
+ * @return The page number.
+ */
+ public int getPageNumber() {
+ return mPageNumber;
+ }
+
+ /**
+ * Gets the density of the page in DPI.
+ *
+ * @return The density.
+ */
+ public int getDesity() {
+ return mDensity;
+ }
+
+ /**
+ * Builder for creating a {@link PageInfo}.
+ */
+ public static final class Builder {
+ private final PageInfo mPageInfo = new PageInfo();
+
+ /**
+ * Creates a new builder with the mandatory page info attributes.
+ *
+ * @param pageSize The page size in pixels.
+ * @param pageNumber The page number.
+ * @param density The page density in DPI.
+ */
+ public Builder(Rect pageSize, int pageNumber, int density) {
+ if (pageSize.width() == 0 || pageSize.height() == 0) {
+ throw new IllegalArgumentException("page width and height" +
+ " must be greater than zero!");
+ }
+ if (pageNumber < 0) {
+ throw new IllegalArgumentException("pageNumber cannot be less than zero!");
+ }
+ if (density <= 0) {
+ throw new IllegalArgumentException("density must be greater than zero!");
+ }
+ mPageInfo.mPageSize = pageSize;
+ mPageInfo.mPageNumber = pageNumber;
+ mPageInfo.mDensity = density;
+ }
+
+ /**
+ * Sets the content size in pixels.
+ *
+ * @param contentSize The content size.
+ */
+ public Builder setContentSize(Rect contentSize) {
+ Rect pageSize = mPageInfo.mPageSize;
+ if (contentSize != null && (pageSize.left > contentSize.left
+ || pageSize.top > contentSize.top
+ || pageSize.right < contentSize.right
+ || pageSize.bottom < contentSize.bottom)) {
+ throw new IllegalArgumentException("contentSize does not fit the pageSize!");
+ }
+ mPageInfo.mContentSize = contentSize;
+ return this;
+ }
+
+ /**
+ * Sets the initial transform which is applied to the page. This may be
+ * useful to move the origin to account for a margin, apply scale, or
+ * apply a rotation.
+ *
+ * @param transform The initial transform.
+ */
+ public Builder setInitialTransform(Matrix transform) {
+ mPageInfo.mInitialTransform = transform;
+ return this;
+ }
+
+ /**
+ * Creates a new {@link PageInfo}.
+ *
+ * @return The new instance.
+ */
+ public PageInfo create() {
+ if (mPageInfo.mContentSize == null) {
+ mPageInfo.mContentSize = mPageInfo.mPageSize;
+ }
+ if (mPageInfo.mInitialTransform == null) {
+ mPageInfo.mInitialTransform = new Matrix();
+ }
+ return mPageInfo;
+ }
+ }
+ }
+
+ /**
+ * This class represents a PDF document page. It has associated
+ * a canvas on which you can draw content and is acquired by a
+ * call to {@link #getCanvas()}. It also has associated a
+ * {@link PageInfo} instance that describes its attributes.
+ */
+ public static final class Page {
+ private final PageInfo mPageInfo;
+ private Canvas mCanvas;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param canvas The canvas of the page.
+ * @param pageInfo The info with meta-data.
+ */
+ private Page(Canvas canvas, PageInfo pageInfo) {
+ mCanvas = canvas;
+ mPageInfo = pageInfo;
+ }
+
+ /**
+ * Gets the {@link Canvas} of the page.
+ *
+ * @return The canvas if the page is not finished, null otherwise.
+ *
+ * @see PdfDocument#finishPage(Page)
+ */
+ public Canvas getCanvas() {
+ return mCanvas;
+ }
+
+ /**
+ * Gets the {@link PageInfo} with meta-data for the page.
+ *
+ * @return The page info.
+ *
+ * @see PdfDocument#finishPage(Page)
+ */
+ public PageInfo getInfo() {
+ return mPageInfo;
+ }
+
+ private void finish() {
+ if (mCanvas != null) {
+ mCanvas.release();
+ mCanvas = null;
+ }
+ }
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 5e1090f5a834..faaf58845294 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -140,6 +140,7 @@ LOCAL_SRC_FILES:= \
android_util_FileObserver.cpp \
android/opengl/poly_clip.cpp.arm \
android/opengl/util.cpp.arm \
+ android/print/android_print_pdf_PdfDocument.cpp \
android_server_NetworkManagementSocketTagger.cpp \
android_server_Watchdog.cpp \
android_ddm_DdmHandleNativeHeap.cpp \
@@ -164,6 +165,7 @@ LOCAL_C_INCLUDES += \
$(call include-path-for, libhardware_legacy)/hardware_legacy \
$(TOP)/frameworks/av/include \
external/skia/src/core \
+ external/skia/src/pdf \
external/skia/src/images \
external/skia/include/utils \
external/sqlite/dist \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 0158bf5fdb2b..144cc84e21aa 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -144,6 +144,7 @@ extern int register_android_os_FileObserver(JNIEnv *env);
extern int register_android_os_FileUtils(JNIEnv *env);
extern int register_android_os_UEventObserver(JNIEnv* env);
extern int register_android_os_MemoryFile(JNIEnv* env);
+extern int register_android_print_pdf_PdfDocument(JNIEnv* env);
extern int register_android_net_LocalSocketImpl(JNIEnv* env);
extern int register_android_net_NetworkUtils(JNIEnv* env);
extern int register_android_net_TrafficStats(JNIEnv* env);
@@ -1182,6 +1183,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_SELinux),
REG_JNI(register_android_os_Trace),
REG_JNI(register_android_os_UEventObserver),
+ REG_JNI(register_android_print_pdf_PdfDocument),
REG_JNI(register_android_net_LocalSocketImpl),
REG_JNI(register_android_net_NetworkUtils),
REG_JNI(register_android_net_TrafficStats),
diff --git a/core/jni/android/print/android_print_pdf_PdfDocument.cpp b/core/jni/android/print/android_print_pdf_PdfDocument.cpp
new file mode 100644
index 000000000000..745fe8aa0b79
--- /dev/null
+++ b/core/jni/android/print/android_print_pdf_PdfDocument.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkCanvas.h"
+#include "SkPDFDevice.h"
+#include "SkPDFDocument.h"
+#include "SkRect.h"
+#include "SkSize.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+
+namespace android {
+
+static jint nativeCreateDocument(JNIEnv* env, jobject clazz) {
+ return reinterpret_cast<jint>(new SkPDFDocument());
+}
+
+static void nativeFinalize(JNIEnv* env, jobject thiz, jint documentPtr) {
+ delete reinterpret_cast<SkPDFDocument*>(documentPtr);
+}
+
+static jint nativeCreatePage(JNIEnv* env, jobject thiz,
+ jobject pageSize, jobject contentSize, jint initialTransformation) {
+ SkIRect skPageSizeRect;
+ GraphicsJNI::jrect_to_irect(env, pageSize, &skPageSizeRect);
+ SkISize skPageSize = SkISize::Make(skPageSizeRect.width(),
+ skPageSizeRect.height());
+
+ SkIRect skContentRect;
+ GraphicsJNI::jrect_to_irect(env, contentSize, &skContentRect);
+ SkISize skContentSize = SkISize::Make(skContentRect.width(),
+ skContentRect.height());
+
+ SkMatrix* transformation = reinterpret_cast<SkMatrix*>(initialTransformation);
+ SkPDFDevice* skPdfDevice = new SkPDFDevice(skPageSize, skContentSize, *transformation);
+
+ return reinterpret_cast<jint>(new SkCanvas(skPdfDevice));
+}
+
+static void nativeAppendPage(JNIEnv* env, jobject thiz, jint documentPtr, jint pagePtr) {
+ SkCanvas* page = reinterpret_cast<SkCanvas*>(pagePtr);
+ SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr);
+ SkPDFDevice* device = static_cast<SkPDFDevice*>(page->getDevice());
+ document->appendPage(device);
+}
+
+static void nativeWriteTo(JNIEnv* env, jobject clazz, jint documentPtr,
+ jobject out, jbyteArray chunk) {
+ SkWStream* skWStream = CreateJavaOutputStreamAdaptor(env, out, chunk);
+ SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr);
+ document->emitPDF(skWStream);
+ delete skWStream;
+}
+
+static JNINativeMethod gPdfDocument_Methods[] = {
+ {"nativeCreateDocument", "()I", (void*) nativeCreateDocument},
+ {"nativeFinalize", "(I)V", (void*) nativeFinalize},
+ {"nativeCreatePage", "(Landroid/graphics/Rect;Landroid/graphics/Rect;I)I",
+ (void*) nativeCreatePage},
+ {"nativeAppendPage", "(II)V", (void*) nativeAppendPage},
+ {"nativeWriteTo", "(ILjava/io/OutputStream;[B)V", (void*) nativeWriteTo}
+};
+
+int register_android_print_pdf_PdfDocument(JNIEnv* env) {
+ int result = android::AndroidRuntime::registerNativeMethods(
+ env, "android/print/pdf/PdfDocument", gPdfDocument_Methods,
+ NELEM(gPdfDocument_Methods));
+ return result;
+}
+
+};
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index a1c87cb64e9f..bd871c4746d4 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -37,12 +37,14 @@ import javax.microedition.khronos.opengles.GL;
* Canvas and Drawables</a> developer guide.</p></div>
*/
public class Canvas {
+
// assigned in constructors or setBitmap, freed in finalizer
- int mNativeCanvas;
-
+ /** @hide */
+ public int mNativeCanvas;
+
// may be null
private Bitmap mBitmap;
-
+
// optional field set by the caller
private DrawFilter mDrawFilter;
@@ -59,7 +61,6 @@ public class Canvas {
protected int mScreenDensity = Bitmap.DENSITY_NONE;
// Used by native code
- @SuppressWarnings({"UnusedDeclaration"})
private int mSurfaceFormat;
/**
@@ -79,10 +80,9 @@ public class Canvas {
private static final int MAXMIMUM_BITMAP_SIZE = 32766;
// This field is used to finalize the native Canvas properly
- @SuppressWarnings({"UnusedDeclaration"})
private final CanvasFinalizer mFinalizer;
- private static class CanvasFinalizer {
+ private static final class CanvasFinalizer {
private int mNativeCanvas;
public CanvasFinalizer(int nativeCanvas) {
@@ -92,13 +92,18 @@ public class Canvas {
@Override
protected void finalize() throws Throwable {
try {
- if (mNativeCanvas != 0) {
- finalizer(mNativeCanvas);
- }
+ dispose();
} finally {
super.finalize();
}
}
+
+ public void dispose() {
+ if (mNativeCanvas != 0) {
+ finalizer(mNativeCanvas);
+ mNativeCanvas = 0;
+ }
+ }
}
/**
@@ -132,13 +137,14 @@ public class Canvas {
mBitmap = bitmap;
mDensity = bitmap.mDensity;
}
-
- Canvas(int nativeCanvas) {
+
+ /** @hide */
+ public Canvas(int nativeCanvas) {
if (nativeCanvas == 0) {
throw new IllegalStateException();
}
mNativeCanvas = nativeCanvas;
- mFinalizer = new CanvasFinalizer(nativeCanvas);
+ mFinalizer = new CanvasFinalizer(mNativeCanvas);
mDensity = Bitmap.getDefaultDensity();
}
@@ -155,7 +161,18 @@ public class Canvas {
}
finalizer(oldCanvas);
}
-
+
+ /**
+ * Gets the native canvas pointer.
+ *
+ * @return The native pointer.
+ *
+ * @hide
+ */
+ public int getNativeCanvas() {
+ return mNativeCanvas;
+ }
+
/**
* Returns null.
*
@@ -1660,6 +1677,15 @@ public class Canvas {
}
/**
+ * Releases the resources associated with this canvas.
+ *
+ * @hide
+ */
+ public void release() {
+ mFinalizer.dispose();
+ }
+
+ /**
* Free up as much memory as possible from private caches (e.g. fonts, images)
*
* @hide