CloseGuard instances are allocated in constructors and usually assigned to final fields. This implies they're non-null in finalizers except in the case where the constructor throws. We add a null check to make sure we can continue cleaning up other state in the finalizer (if applicable). Also, this change decouples closeguard warnings in constructors from other state based logic. This because the logic there is usually duplicated with the call to close(). NOTE: This change is not a "complete" fix. Many of these finalizers are broken in the case where <init> throws. The only objective of this change is to make such errors more obvious. Note that some of these classes don't have CTS tests. Test: make, CtsMediaTestCases. Bug: 35609098 Change-Id: I24d9e0215f80e44914dba8ab99b6312fd6ed1fc0
460 lines
15 KiB
Java
460 lines
15 KiB
Java
/*
|
|
* 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.graphics.pdf;
|
|
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Rect;
|
|
|
|
import dalvik.system.CloseGuard;
|
|
|
|
import java.io.IOException;
|
|
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
|
|
* create 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. Note that pages are
|
|
* created one by one, i.e. you can have only a single page to which you are
|
|
* writing at any given time. This class is not thread safe.
|
|
* </p>
|
|
* <p>
|
|
* A typical use of the APIs looks like this:
|
|
* </p>
|
|
* <pre>
|
|
* // create a new document
|
|
* PdfDocument document = new PdfDocument();
|
|
*
|
|
* // crate a page description
|
|
* PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1).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 class PdfDocument {
|
|
|
|
// TODO: We need a constructor that will take an OutputStream to
|
|
// support online data serialization as opposed to the current
|
|
// on demand one. The current approach is fine until Skia starts
|
|
// to support online PDF generation at which point we need to
|
|
// handle this.
|
|
|
|
private final byte[] mChunk = new byte[4096];
|
|
|
|
private final CloseGuard mCloseGuard = CloseGuard.get();
|
|
|
|
private final List<PageInfo> mPages = new ArrayList<PageInfo>();
|
|
|
|
private long mNativeDocument;
|
|
|
|
private Page mCurrentPage;
|
|
|
|
/**
|
|
* Creates a new instance.
|
|
*/
|
|
public 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()}.
|
|
* Also do not call this method if the last page returned by this method
|
|
* is not finished by calling {@link #finishPage(Page)}.
|
|
* </p>
|
|
*
|
|
* @param pageInfo The page info. Cannot be null.
|
|
* @return A blank page.
|
|
*
|
|
* @see #finishPage(Page)
|
|
*/
|
|
public Page startPage(PageInfo pageInfo) {
|
|
throwIfClosed();
|
|
throwIfCurrentPageNotFinished();
|
|
if (pageInfo == null) {
|
|
throw new IllegalArgumentException("page cannot be null");
|
|
}
|
|
Canvas canvas = new PdfCanvas(nativeStartPage(mNativeDocument, pageInfo.mPageWidth,
|
|
pageInfo.mPageHeight, pageInfo.mContentRect.left, pageInfo.mContentRect.top,
|
|
pageInfo.mContentRect.right, pageInfo.mContentRect.bottom));
|
|
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()}.
|
|
* You should not finish the same page more than once.
|
|
* </p>
|
|
*
|
|
* @param page The page. Cannot be null.
|
|
*
|
|
* @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");
|
|
}
|
|
if (page.isFinished()) {
|
|
throw new IllegalStateException("page already finished");
|
|
}
|
|
mPages.add(page.getInfo());
|
|
mCurrentPage = null;
|
|
nativeFinishPage(mNativeDocument);
|
|
page.finish();
|
|
}
|
|
|
|
/**
|
|
* Writes the document to an output stream. You can call this method
|
|
* multiple times.
|
|
* <p>
|
|
* <strong>Note:</strong> Do not call this method after {@link #close()}.
|
|
* Also do not call this method if a page returned by {@link #startPage(
|
|
* PageInfo)} is not finished by calling {@link #finishPage(Page)}.
|
|
* </p>
|
|
*
|
|
* @param out The output stream. Cannot be null.
|
|
*
|
|
* @throws IOException If an error occurs while writing.
|
|
*/
|
|
public void writeTo(OutputStream out) throws IOException {
|
|
throwIfClosed();
|
|
throwIfCurrentPageNotFinished();
|
|
if (out == null) {
|
|
throw new IllegalArgumentException("out cannot be null!");
|
|
}
|
|
nativeWriteTo(mNativeDocument, out, mChunk);
|
|
}
|
|
|
|
/**
|
|
* Gets the pages of the document.
|
|
*
|
|
* @return The pages or an empty list.
|
|
*/
|
|
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.
|
|
* <p>
|
|
* <strong>Note:</strong> Do not call this method if the page
|
|
* returned by {@link #startPage(PageInfo)} is not finished by
|
|
* calling {@link #finishPage(Page)}.
|
|
* </p>
|
|
*/
|
|
public void close() {
|
|
throwIfCurrentPageNotFinished();
|
|
dispose();
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
if (mCloseGuard != null) {
|
|
mCloseGuard.warnIfOpen();
|
|
}
|
|
|
|
dispose();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
private void dispose() {
|
|
if (mNativeDocument != 0) {
|
|
nativeClose(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!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Throws an exception if the last started page is not finished.
|
|
*/
|
|
private void throwIfCurrentPageNotFinished() {
|
|
if (mCurrentPage != null) {
|
|
throw new IllegalStateException("Current page not finished!");
|
|
}
|
|
}
|
|
|
|
private native long nativeCreateDocument();
|
|
|
|
private native void nativeClose(long nativeDocument);
|
|
|
|
private native void nativeFinishPage(long nativeDocument);
|
|
|
|
private native void nativeWriteTo(long nativeDocument, OutputStream out, byte[] chunk);
|
|
|
|
private static native long nativeStartPage(long nativeDocument, int pageWidth, int pageHeight,
|
|
int contentLeft, int contentTop, int contentRight, int contentBottom);
|
|
|
|
private final class PdfCanvas extends Canvas {
|
|
|
|
public PdfCanvas(long nativeCanvas) {
|
|
super(nativeCanvas);
|
|
}
|
|
|
|
@Override
|
|
public void setBitmap(Bitmap bitmap) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class represents meta-data that describes a PDF {@link Page}.
|
|
*/
|
|
public static final class PageInfo {
|
|
private int mPageWidth;
|
|
private int mPageHeight;
|
|
private Rect mContentRect;
|
|
private int mPageNumber;
|
|
|
|
/**
|
|
* Creates a new instance.
|
|
*/
|
|
private PageInfo() {
|
|
/* do nothing */
|
|
}
|
|
|
|
/**
|
|
* Gets the page width in PostScript points (1/72th of an inch).
|
|
*
|
|
* @return The page width.
|
|
*/
|
|
public int getPageWidth() {
|
|
return mPageWidth;
|
|
}
|
|
|
|
/**
|
|
* Gets the page height in PostScript points (1/72th of an inch).
|
|
*
|
|
* @return The page height.
|
|
*/
|
|
public int getPageHeight() {
|
|
return mPageHeight;
|
|
}
|
|
|
|
/**
|
|
* Get the content rectangle in PostScript points (1/72th of an inch).
|
|
* This is the area that contains the page content and is relative to
|
|
* the page top left.
|
|
*
|
|
* @return The content rectangle.
|
|
*/
|
|
public Rect getContentRect() {
|
|
return mContentRect;
|
|
}
|
|
|
|
/**
|
|
* Gets the page number.
|
|
*
|
|
* @return The page number.
|
|
*/
|
|
public int getPageNumber() {
|
|
return mPageNumber;
|
|
}
|
|
|
|
/**
|
|
* 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 pageWidth The page width in PostScript (1/72th of an inch).
|
|
* @param pageHeight The page height in PostScript (1/72th of an inch).
|
|
* @param pageNumber The page number.
|
|
*/
|
|
public Builder(int pageWidth, int pageHeight, int pageNumber) {
|
|
if (pageWidth <= 0) {
|
|
throw new IllegalArgumentException("page width must be positive");
|
|
}
|
|
if (pageHeight <= 0) {
|
|
throw new IllegalArgumentException("page width must be positive");
|
|
}
|
|
if (pageNumber < 0) {
|
|
throw new IllegalArgumentException("pageNumber must be non negative");
|
|
}
|
|
mPageInfo.mPageWidth = pageWidth;
|
|
mPageInfo.mPageHeight = pageHeight;
|
|
mPageInfo.mPageNumber = pageNumber;
|
|
}
|
|
|
|
/**
|
|
* Sets the content rectangle in PostScript point (1/72th of an inch).
|
|
* This is the area that contains the page content and is relative to
|
|
* the page top left.
|
|
*
|
|
* @param contentRect The content rectangle. Must fit in the page.
|
|
*/
|
|
public Builder setContentRect(Rect contentRect) {
|
|
if (contentRect != null && (contentRect.left < 0
|
|
|| contentRect.top < 0
|
|
|| contentRect.right > mPageInfo.mPageWidth
|
|
|| contentRect.bottom > mPageInfo.mPageHeight)) {
|
|
throw new IllegalArgumentException("contentRect does not fit the page");
|
|
}
|
|
mPageInfo.mContentRect = contentRect;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a new {@link PageInfo}.
|
|
*
|
|
* @return The new instance.
|
|
*/
|
|
public PageInfo create() {
|
|
if (mPageInfo.mContentRect == null) {
|
|
mPageInfo.mContentRect = new Rect(0, 0,
|
|
mPageInfo.mPageWidth, mPageInfo.mPageHeight);
|
|
}
|
|
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. Also
|
|
* a page has
|
|
*/
|
|
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.
|
|
*
|
|
* <p>
|
|
* <strong>Note: </strong> There are some draw operations that are not yet
|
|
* supported by the canvas returned by this method. More specifically:
|
|
* <ul>
|
|
* <li>Inverse path clipping performed via {@link Canvas#clipPath(android.graphics.Path,
|
|
* android.graphics.Region.Op) Canvas.clipPath(android.graphics.Path,
|
|
* android.graphics.Region.Op)} for {@link
|
|
* android.graphics.Region.Op#REVERSE_DIFFERENCE
|
|
* Region.Op#REVERSE_DIFFERENCE} operations.</li>
|
|
* <li>{@link Canvas#drawVertices(android.graphics.Canvas.VertexMode, int,
|
|
* float[], int, float[], int, int[], int, short[], int, int,
|
|
* android.graphics.Paint) Canvas.drawVertices(
|
|
* android.graphics.Canvas.VertexMode, int, float[], int, float[],
|
|
* int, int[], int, short[], int, int, android.graphics.Paint)}</li>
|
|
* <li>Color filters set via {@link Paint#setColorFilter(
|
|
* android.graphics.ColorFilter)}</li>
|
|
* <li>Mask filters set via {@link Paint#setMaskFilter(
|
|
* android.graphics.MaskFilter)}</li>
|
|
* <li>Some XFER modes such as
|
|
* {@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC},
|
|
* {@link android.graphics.PorterDuff.Mode#DST_ATOP PorterDuff.DST_ATOP},
|
|
* {@link android.graphics.PorterDuff.Mode#XOR PorterDuff.XOR},
|
|
* {@link android.graphics.PorterDuff.Mode#ADD PorterDuff.ADD}</li>
|
|
* </ul>
|
|
*
|
|
* @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;
|
|
}
|
|
|
|
boolean isFinished() {
|
|
return mCanvas == null;
|
|
}
|
|
|
|
private void finish() {
|
|
if (mCanvas != null) {
|
|
mCanvas.release();
|
|
mCanvas = null;
|
|
}
|
|
}
|
|
}
|
|
}
|