3 * $Date: 2009-06-30 18:58:33 -0500 (Tue, 30 Jun 2009) $
\r
6 * Copyright (C) 2002-2005 The Jmol Development Team
\r
8 * Contact: jmol-developers@lists.sf.net
\r
10 * This library is free software; you can redistribute it and/or
\r
11 * modify it under the terms of the GNU Lesser General Public
\r
12 * License as published by the Free Software Foundation; either
\r
13 * version 2.1 of the License, or (at your option) any later version.
\r
15 * This library is distributed in the hope that it will be useful,
\r
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
18 * Lesser General Public License for more details.
\r
20 * You should have received a copy of the GNU Lesser General Public
\r
21 * License along with this library; if not, write to the Free Software
\r
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
\r
24 package javajs.export;
\r
26 import java.io.IOException;
\r
27 import java.io.OutputStream;
\r
28 import java.util.Hashtable;
\r
29 import java.util.Map;
\r
30 import java.util.Map.Entry;
\r
32 import javajs.util.Lst;
\r
33 import javajs.util.SB;
\r
37 public class PDFCreator {
\r
39 private OutputStream os;
\r
40 private Lst<PDFObject> indirectObjects;
\r
41 private PDFObject root;
\r
42 private PDFObject graphics;
\r
43 // private PDFObject pageResources;
\r
44 // private PDFObject graphicsResources;
\r
53 private Map<String, PDFObject>fonts;
\r
55 public PDFCreator() {
\r
56 // for Class.forName
\r
59 public void setOutputStream(OutputStream os) {
\r
63 public void newDocument(int paperWidth, int paperHeight, boolean isLandscape) {
\r
64 width = (isLandscape ? paperHeight : paperWidth);
\r
65 height = (isLandscape ? paperWidth : paperHeight);
\r
66 System.out.println("Creating PDF with width=" + width + " and height=" + height);
\r
67 fonts = new Hashtable<String, PDFObject>();
\r
68 indirectObjects = new Lst<PDFObject>();
\r
69 //graphicsResources = newObject(null);
\r
70 //pageResources = newObject(null); // will set this to compressed stream later
\r
71 root = newObject("Catalog");
\r
72 PDFObject pages = newObject("Pages");
\r
73 PDFObject page = newObject("Page");
\r
74 PDFObject pageContents = newObject(null);
\r
75 graphics = newObject("XObject");
\r
77 root.addDef("Pages", pages.getRef());
\r
78 pages.addDef("Count", "1");
\r
79 pages.addDef("Kids", "[ " + page.getRef() +" ]");
\r
80 page.addDef("Parent", pages.getRef());
\r
81 page.addDef("MediaBox", "[ 0 0 " + paperWidth + " " + paperHeight + " ]");
\r
83 page.addDef("Rotate", "90");
\r
85 pageContents.addDef("Length", "?");
\r
86 pageContents.append((isLandscape ? "q 0 1 1 0 0 0 " : "q 1 0 0 -1 0 "+(paperHeight))+" cm /" + graphics.getID() + " Do Q");
\r
87 page.addDef("Contents", pageContents.getRef());
\r
89 addProcSet(graphics);
\r
90 // will add fonts as well as they are needed
\r
91 graphics.addDef("Subtype", "/Form");
\r
92 graphics.addDef("FormType", "1");
\r
93 graphics.addDef("BBox", "[0 0 " + width + " " + height + "]");
\r
94 graphics.addDef("Matrix", "[1 0 0 1 0 0]");
\r
95 graphics.addDef("Length", "?");
\r
96 page.addResource("XObject", graphics.getID(), graphics.getRef());
\r
97 g("q 1 w 1 J 1 j 10 M []0 d q "); // line width 1, line cap circle, line join circle, miter limit 10, solid
\r
98 clip(0, 0, width, height);
\r
101 private void addProcSet(PDFObject o) {
\r
102 o.addResource(null, "ProcSet", "[/PDF /Text /ImageB /ImageC /ImageI]");
\r
105 private void clip(int x1, int y1, int x2, int y2) {
\r
113 public void moveto(int x, int y) {
\r
114 g(x + " " + y + " m");
\r
117 public void lineto(int x, int y) {
\r
118 g(x + " " + y + " l");
\r
121 private PDFObject newObject(String type) {
\r
122 PDFObject o = new PDFObject(++count);
\r
124 o.addDef("Type", "/" + type);
\r
125 indirectObjects.addLast(o);
\r
129 public void addInfo(Map<String, String> data) {
\r
130 Hashtable<String, Object> info = new Hashtable<String, Object>();
\r
131 for (Entry<String, String> e: data.entrySet()) {
\r
132 String value = "(" + e.getValue().replace(')','_').replace('(','_')+ ")";
\r
133 info.put(e.getKey(), value);
\r
135 root.addDef("Info", info);
\r
138 private PDFObject addFontResource(String fname) {
\r
139 PDFObject f = newObject("Font");
\r
140 fonts.put(fname, f);
\r
141 f.addDef("BaseFont", fname);
\r
142 f.addDef("Encoding", "/WinAnsiEncoding");
\r
143 f.addDef("Subtype", "/Type1");
\r
144 graphics.addResource("Font", f.getID(), f.getRef());
\r
148 private Map<Object, PDFObject> images;
\r
150 public void addImageResource(Object newImage, int width, int height, int[] buffer, boolean isRGB) {
\r
151 PDFObject imageObj = newObject("XObject");
\r
152 if (images == null)
\r
153 images = new Hashtable<Object, PDFObject>();
\r
154 images.put(newImage, imageObj);
\r
155 imageObj.addDef("Subtype", "/Image");
\r
156 imageObj.addDef("Length", "?");
\r
157 imageObj.addDef("ColorSpace", isRGB ? "/DeviceRGB" : "/DeviceGray");
\r
158 imageObj.addDef("BitsPerComponent", "8");
\r
159 imageObj.addDef("Width", "" + width);
\r
160 imageObj.addDef("Height", "" + height);
\r
161 graphics.addResource("XObject", imageObj.getID(), imageObj.getRef());
\r
162 int n = buffer.length;
\r
163 byte[] stream = new byte[n * (isRGB ? 3 : 1)];
\r
165 for (int i = 0, pt = 0; i < n; i++) {
\r
166 stream[pt++] = (byte) ((buffer[i] >> 16) & 0xFF);
\r
167 stream[pt++] = (byte) ((buffer[i] >> 8) & 0xFF);
\r
168 stream[pt++] = (byte) (buffer[i] & 0xFF);
\r
171 for (int i = 0; i < n; i++)
\r
172 stream[i] = (byte) buffer[i];
\r
174 imageObj.setStream(stream);
\r
175 graphics.addResource("XObject", imageObj.getID(), imageObj.getRef());
\r
178 public void g(String cmd) {
\r
179 graphics.append(cmd).appendC('\n');
\r
182 private void output(String s) throws IOException {
\r
183 byte[] b = s.getBytes();
\r
184 os.write(b, 0, b.length);
\r
188 public void closeDocument() throws IOException {
\r
198 private void outputHeader() throws IOException {
\r
199 output("%PDF-1.3\n%");
\r
200 byte[] b = new byte[] {-1, -1, -1, -1};
\r
201 os.write(b, 0, b.length);
\r
206 private void writeTrailer() throws IOException {
\r
207 PDFObject trailer = new PDFObject(-2);
\r
209 trailer.addDef("Size", "" + indirectObjects.size());
\r
210 trailer.addDef("Root", root.getRef());
\r
211 trailer.output(os);
\r
212 output("startxref\n");
\r
213 output("" + xrefPt + "\n");
\r
218 * Write Font objects first.
\r
220 * @throws IOException
\r
222 private void writeObjects() throws IOException {
\r
223 int nObj = indirectObjects.size();
\r
224 for (int i = 0; i < nObj; i++) {
\r
225 PDFObject o = indirectObjects.get(i);
\r
229 pt += o.output(os);
\r
231 for (int i = 0; i < nObj; i++) {
\r
232 PDFObject o = indirectObjects.get(i);
\r
236 pt += o.output(os);
\r
240 private void writeXRefTable() throws IOException {
\r
242 int nObj = indirectObjects.size();
\r
244 // note trailing space, needed because \n is just one character
\r
245 sb.append("xref\n0 " + (nObj + 1)
\r
246 + "\n0000000000 65535 f\r\n");
\r
247 for (int i = 0; i < nObj; i++) {
\r
248 PDFObject o = indirectObjects.get(i);
\r
249 String s = "0000000000" + o.pt;
\r
250 sb.append(s.substring(s.length() - 10));
\r
251 sb.append(" 00000 n\r\n");
\r
253 output(sb.toString());
\r
256 public boolean canDoLineTo() {
\r
260 public void fill() {
\r
264 public void stroke() {
\r
268 public void doCircle(int x, int y, int r, boolean doFill) {
\r
269 double d = r*4*(Math.sqrt(2)-1)/3;
\r
272 g((dx + r) + " " + dy + " m");
\r
273 g((dx + r) + " " + (dy + d) + " " + (dx + d) + " " + (dy + r) + " " + (dx) + " " + (dy + r) + " " + " c");
\r
274 g((dx - d) + " " + (dy + r) + " " + (dx - r) + " " + (dy + d) + " " + (dx - r) + " " + (dy) + " c");
\r
275 g((dx - r) + " " + (dy - d) + " " + (dx - d) + " " + (dy - r) + " " + (dx) + " " + (dy - r) + " c");
\r
276 g((dx + d) + " " + (dy - r) + " " + (dx + r) + " " + (dy - d) + " " + (dx + r) + " " + (dy) + " c");
\r
277 g(doFill ? "f" : "s");
\r
280 public void doPolygon(int[] axPoints, int[] ayPoints, int nPoints, boolean doFill) {
\r
281 moveto(axPoints[0], ayPoints[0]);
\r
282 for (int i = 1; i < nPoints; i++)
\r
283 lineto(axPoints[i], ayPoints[i]);
\r
284 g(doFill ? "f" : "s");
\r
287 public void doRect(int x, int y, int width, int height, boolean doFill) {
\r
288 g(x + " " + y + " " + width + " " + height + " re " + (doFill ? "f" : "s"));
\r
291 public void drawImage(Object image, int destX0, int destY0,
\r
292 int destX1, int destY1, int srcX0, int srcY0, int srcX1, int srcY1) {
\r
293 PDFObject imageObj = images.get(image);
\r
294 if (imageObj == null)
\r
297 clip(destX0, destY0, destX1, destY1);
\r
298 double iw = Double.parseDouble((String) imageObj.getDef("Width"));
\r
299 double ih = Double.parseDouble((String) imageObj.getDef("Height"));
\r
300 double dw = (destX1 - destX0 + 1);
\r
301 double dh = (destY1 - destY0 + 1);
\r
302 double sw = (srcX1 - srcX0 + 1);
\r
303 double sh = (srcY1 - srcY0 + 1);
\r
304 double scaleX = dw / sw;
\r
305 double scaleY = dh / sh;
\r
306 double transX = destX0 - srcX0 * scaleX;
\r
307 double transY = destY0 + (ih - srcY0) * scaleY;
\r
308 g(scaleX*iw + " 0 0 " + -scaleY*ih + " " + transX + " " + transY + " cm");
\r
309 g("/" + imageObj.getID() + " Do");
\r
313 public void drawStringRotated(String s, int x, int y, int angle) {
\r
314 g("q " + getRotation(angle) + " " + x + " " + y
\r
315 + " cm BT(" + s + ")Tj ET Q");
\r
318 public String getRotation(int angle) {
\r
319 float cos = 0, sin = 0;
\r
334 float a = (float) (angle / 180.0 * Math.PI);
\r
335 cos = (float) Math.cos(a);
\r
336 sin = (float) Math.sin(a);
\r
337 if (Math.abs(cos) < 0.0001)
\r
339 if (Math.abs(sin) < 0.0001)
\r
342 return cos + " " + sin + " " + sin + " " + -cos;
\r
345 public void setColor(float[] rgb, boolean isFill) {
\r
346 g(rgb[0] + " " + rgb[1] + " " + rgb[2] + (isFill ? " rg" : " RG"));
\r
349 public void setFont(String fname, float size) {
\r
350 PDFObject f = fonts.get(fname);
\r
352 f = addFontResource(fname);
\r
353 g("/" + f.getID() + " " + size + " Tf");
\r
356 public void setLineWidth(float width) {
\r
360 public void translateScale(float x, float y, float scale) {
\r
361 g(scale + " 0 0 " + scale + " " + x + " " + y + " cm");
\r