diff options
| author | 2013-06-21 20:09:50 +0000 | |
|---|---|---|
| committer | 2013-06-21 20:09:51 +0000 | |
| commit | b06accf34f53956a5ccd44cecc3604cc2d166cd7 (patch) | |
| tree | b5b34ae664e9c2d6cf5835522f8ea4ff6d1c5911 | |
| parent | 142a29f45f478fe7e6323dd84d99014c1c5c473f (diff) | |
| parent | ff4adde5737be08d3e2d03fbe588c591d27d4a74 (diff) | |
Merge "Generate PDF from Canvas."
| -rw-r--r-- | api/current.txt | 34 | ||||
| -rw-r--r-- | core/java/android/print/pdf/PdfDocument.java | 443 | ||||
| -rw-r--r-- | core/jni/Android.mk | 2 | ||||
| -rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
| -rw-r--r-- | core/jni/android/print/android_print_pdf_PdfDocument.cpp | 87 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Canvas.java | 52 |
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 |