blob: cfeb97543d956ae30c8ed1ce3b696723ccfaed00 [file] [log] [blame]
Svetoslav Ganovff4adde52013-06-10 08:47:27 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.print.pdf;
18
19import android.graphics.Bitmap;
20import android.graphics.Canvas;
21import android.graphics.Matrix;
22import android.graphics.Rect;
23
24import dalvik.system.CloseGuard;
25
26import java.io.OutputStream;
27import java.util.ArrayList;
28import java.util.Collections;
29import java.util.List;
30
31/**
32 * <p>
33 * This class enables generating a PDF document from native Android content. You
34 * open a new document and then for every page you want to add you start a page,
35 * write content to the page, and finish the page. After you are done with all
36 * pages, you write the document to an output stream and close the document.
37 * After a document is closed you should not use it anymore.
38 * </p>
39 * <p>
40 * A typical use of the APIs looks like this:
41 * </p>
42 * <pre>
43 * // open a new document
44 * PdfDocument document = PdfDocument.open();
45 *
46 * // crate a page description
47 * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1, 300).create();
48 *
49 * // start a page
50 * Page page = document.startPage(pageInfo);
51 *
52 * // draw something on the page
53 * View content = getContentView();
54 * content.draw(page.getCanvas());
55 *
56 * // finish the page
57 * document.finishPage(page);
58 * . . .
Svetoslav62836082013-07-17 14:52:35 -070059 * // add more pages
Svetoslav Ganovff4adde52013-06-10 08:47:27 -070060 * . . .
61 * // write the document content
62 * document.writeTo(getOutputStream());
63 *
64 * //close the document
65 * document.close();
66 * </pre>
67 */
68public final class PdfDocument {
69
70 private final byte[] mChunk = new byte[4096];
71
72 private final CloseGuard mCloseGuard = CloseGuard.get();
73
74 private final List<PageInfo> mPages = new ArrayList<PageInfo>();
75
76 private int mNativeDocument;
77
78 private Page mCurrentPage;
79
80 /**
81 * Opens a new document.
82 * <p>
83 * <strong>Note:</strong> You must close the document after you are
84 * done by calling {@link #close()}
85 * </p>
86 *
87 * @return The document.
88 *
89 * @see #close()
90 */
91 public static PdfDocument open() {
92 return new PdfDocument();
93 }
94
95 /**
96 * Creates a new instance.
97 */
98 private PdfDocument() {
99 mNativeDocument = nativeCreateDocument();
100 mCloseGuard.open("close");
101 }
102
103 /**
104 * Starts a page using the provided {@link PageInfo}. After the page
105 * is created you can draw arbitrary content on the page's canvas which
106 * you can get by calling {@link Page#getCanvas()}. After you are done
107 * drawing the content you should finish the page by calling
Svetoslavfd906512013-06-24 09:04:48 -0700108 * {@link #finishPage(Page)}. After the page is finished you should
Svetoslav Ganovff4adde52013-06-10 08:47:27 -0700109 * no longer access the page or its canvas.
110 * <p>
111 * <strong>Note:</strong> Do not call this method after {@link #close()}.
112 * </p>
113 *
114 * @param pageInfo The page info.
115 * @return A blank page.
116 *
117 * @see #finishPage(Page)
118 */
119 public Page startPage(PageInfo pageInfo) {
120 throwIfClosed();
121 if (pageInfo == null) {
122 throw new IllegalArgumentException("page cannot be null!");
123 }
124 if (mCurrentPage != null) {
125 throw new IllegalStateException("Previous page not finished!");
126 }
127 Canvas canvas = new PdfCanvas(nativeCreatePage(pageInfo.mPageSize,
128 pageInfo.mContentSize, pageInfo.mInitialTransform.native_instance),
129 pageInfo.mDensity);
130 mCurrentPage = new Page(canvas, pageInfo);
131 return mCurrentPage;
132 }
133
134 /**
135 * Finishes a started page. You should always finish the last started page.
136 * <p>
137 * <strong>Note:</strong> Do not call this method after {@link #close()}.
138 * </p>
139 *
140 * @param page The page.
141 *
142 * @see #startPage(PageInfo)
143 */
144 public void finishPage(Page page) {
145 throwIfClosed();
146 if (page == null) {
147 throw new IllegalArgumentException("page cannot be null");
148 }
149 if (page != mCurrentPage) {
150 throw new IllegalStateException("invalid page");
151 }
152 mPages.add(page.getInfo());
153 mCurrentPage = null;
154 nativeAppendPage(mNativeDocument, page.mCanvas.mNativeCanvas);
155 page.finish();
156 }
157
158 /**
159 * Writes the document to an output stream.
160 * <p>
161 * <strong>Note:</strong> Do not call this method after {@link #close()}.
162 * </p>
163 *
164 * @param out The output stream.
165 */
166 public void writeTo(OutputStream out) {
167 throwIfClosed();
168 if (out == null) {
169 throw new IllegalArgumentException("out cannot be null!");
170 }
171 nativeWriteTo(mNativeDocument, out, mChunk);
172 }
173
174 /**
175 * Gets the pages of the document.
176 *
177 * @return The pages.
178 */
179 public List<PageInfo> getPages() {
180 return Collections.unmodifiableList(mPages);
181 }
182
183 /**
184 * Closes this document. This method should be called after you
185 * are done working with the document. After this call the document
186 * is considered closed and none of its methods should be called.
187 */
188 public void close() {
189 dispose();
190 }
191
192 @Override
193 protected void finalize() throws Throwable {
194 try {
195 mCloseGuard.warnIfOpen();
196 dispose();
197 } finally {
198 super.finalize();
199 }
200 }
201
202 private void dispose() {
203 if (mNativeDocument != 0) {
204 nativeFinalize(mNativeDocument);
205 mCloseGuard.close();
206 mNativeDocument = 0;
207 }
208 }
209
210 /**
211 * Throws an exception if the document is already closed.
212 */
213 private void throwIfClosed() {
214 if (mNativeDocument == 0) {
215 throw new IllegalStateException("document is closed!");
216 }
217 }
218
219 private native int nativeCreateDocument();
220
221 private native void nativeFinalize(int document);
222
223 private native void nativeAppendPage(int document, int page);
224
225 private native void nativeWriteTo(int document, OutputStream out, byte[] chunk);
226
227 private static native int nativeCreatePage(Rect pageSize,
228 Rect contentSize, int nativeMatrix);
229
230
231 private final class PdfCanvas extends Canvas {
232
233 public PdfCanvas(int nativeCanvas, int density) {
234 super(nativeCanvas);
235 super.setDensity(density);
236 }
237
238 @Override
239 public void setBitmap(Bitmap bitmap) {
240 throw new UnsupportedOperationException();
241 }
242
243 @Override
244 public void setDensity(int density) {
245 throw new UnsupportedOperationException();
246 }
247
248 @Override
249 public void setScreenDensity(int density) {
250 throw new UnsupportedOperationException();
251 }
252 }
253
254 /**
255 * This class represents meta-data that describes a PDF {@link Page}.
256 */
257 public static final class PageInfo {
258 private Rect mPageSize;
259 private Rect mContentSize;
260 private Matrix mInitialTransform;
261 private int mPageNumber;
262 private int mDensity;
263
264 /**
265 * Creates a new instance.
266 */
267 private PageInfo() {
268 /* do nothing */
269 }
270
271 /**
272 * Gets the page size in pixels.
273 *
274 * @return The page size.
275 */
276 public Rect getPageSize() {
277 return mPageSize;
278 }
279
280 /**
281 * Get the content size in pixels.
282 *
283 * @return The content size.
284 */
285 public Rect getContentSize() {
286 return mContentSize;
287 }
288
289 /**
290 * Gets the initial transform which is applied to the page. This may be
291 * useful to move the origin to account for a margin, apply scale, or
292 * apply a rotation.
293 *
294 * @return The initial transform.
295 */
296 public Matrix getInitialTransform() {
297 return mInitialTransform;
298 }
299
300 /**
301 * Gets the page number.
302 *
303 * @return The page number.
304 */
305 public int getPageNumber() {
306 return mPageNumber;
307 }
308
309 /**
310 * Gets the density of the page in DPI.
311 *
312 * @return The density.
313 */
314 public int getDesity() {
315 return mDensity;
316 }
317
318 /**
319 * Builder for creating a {@link PageInfo}.
320 */
321 public static final class Builder {
322 private final PageInfo mPageInfo = new PageInfo();
323
324 /**
325 * Creates a new builder with the mandatory page info attributes.
326 *
327 * @param pageSize The page size in pixels.
328 * @param pageNumber The page number.
329 * @param density The page density in DPI.
330 */
331 public Builder(Rect pageSize, int pageNumber, int density) {
332 if (pageSize.width() == 0 || pageSize.height() == 0) {
333 throw new IllegalArgumentException("page width and height" +
334 " must be greater than zero!");
335 }
336 if (pageNumber < 0) {
337 throw new IllegalArgumentException("pageNumber cannot be less than zero!");
338 }
339 if (density <= 0) {
340 throw new IllegalArgumentException("density must be greater than zero!");
341 }
342 mPageInfo.mPageSize = pageSize;
343 mPageInfo.mPageNumber = pageNumber;
344 mPageInfo.mDensity = density;
345 }
346
347 /**
348 * Sets the content size in pixels.
349 *
350 * @param contentSize The content size.
351 */
352 public Builder setContentSize(Rect contentSize) {
353 Rect pageSize = mPageInfo.mPageSize;
354 if (contentSize != null && (pageSize.left > contentSize.left
355 || pageSize.top > contentSize.top
356 || pageSize.right < contentSize.right
357 || pageSize.bottom < contentSize.bottom)) {
358 throw new IllegalArgumentException("contentSize does not fit the pageSize!");
359 }
360 mPageInfo.mContentSize = contentSize;
361 return this;
362 }
363
364 /**
365 * Sets the initial transform which is applied to the page. This may be
366 * useful to move the origin to account for a margin, apply scale, or
367 * apply a rotation.
368 *
369 * @param transform The initial transform.
370 */
371 public Builder setInitialTransform(Matrix transform) {
372 mPageInfo.mInitialTransform = transform;
373 return this;
374 }
375
376 /**
377 * Creates a new {@link PageInfo}.
378 *
379 * @return The new instance.
380 */
381 public PageInfo create() {
382 if (mPageInfo.mContentSize == null) {
383 mPageInfo.mContentSize = mPageInfo.mPageSize;
384 }
385 if (mPageInfo.mInitialTransform == null) {
386 mPageInfo.mInitialTransform = new Matrix();
387 }
388 return mPageInfo;
389 }
390 }
391 }
392
393 /**
394 * This class represents a PDF document page. It has associated
395 * a canvas on which you can draw content and is acquired by a
396 * call to {@link #getCanvas()}. It also has associated a
397 * {@link PageInfo} instance that describes its attributes.
398 */
399 public static final class Page {
400 private final PageInfo mPageInfo;
401 private Canvas mCanvas;
402
403 /**
404 * Creates a new instance.
405 *
406 * @param canvas The canvas of the page.
407 * @param pageInfo The info with meta-data.
408 */
409 private Page(Canvas canvas, PageInfo pageInfo) {
410 mCanvas = canvas;
411 mPageInfo = pageInfo;
412 }
413
414 /**
415 * Gets the {@link Canvas} of the page.
416 *
417 * @return The canvas if the page is not finished, null otherwise.
418 *
419 * @see PdfDocument#finishPage(Page)
420 */
421 public Canvas getCanvas() {
422 return mCanvas;
423 }
424
425 /**
426 * Gets the {@link PageInfo} with meta-data for the page.
427 *
428 * @return The page info.
429 *
430 * @see PdfDocument#finishPage(Page)
431 */
432 public PageInfo getInfo() {
433 return mPageInfo;
434 }
435
436 private void finish() {
437 if (mCanvas != null) {
438 mCanvas.release();
439 mCanvas = null;
440 }
441 }
442 }
443}