| /* |
| * 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; |
| } |
| } |
| } |
| } |