Merge "Generate PDF from Canvas."

This commit is contained in:
Svetoslav Ganov
2013-06-21 20:09:50 +00:00
committed by Android (Google) Code Review
6 changed files with 607 additions and 13 deletions

View File

@@ -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 {

View File

@@ -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;
}
}
}
}

View File

@@ -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 \

View File

@@ -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),

View File

@@ -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;
}
};

View File

@@ -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.
*
@@ -1659,6 +1676,15 @@ public class Canvas {
restore();
}
/**
* 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)
*