/* $RCSfile$ * $Author: hansonr $ * $Date: 2009-06-30 18:58:33 -0500 (Tue, 30 Jun 2009) $ * $Revision: 11158 $ * * Copyright (C) 2002-2005 The Jmol Development Team * * Contact: jmol-developers@lists.sf.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package javajs.export; import java.io.IOException; import java.io.OutputStream; import java.util.Hashtable; import java.util.Map; import java.util.Map.Entry; import javajs.util.Lst; import javajs.util.SB; public class PDFCreator { private OutputStream os; private Lst indirectObjects; private PDFObject root; private PDFObject graphics; // private PDFObject pageResources; // private PDFObject graphicsResources; private int pt; private int xrefPt; private int count; private int height; private int width; private Mapfonts; public PDFCreator() { // for Class.forName } public void setOutputStream(OutputStream os) { this.os = os; } public void newDocument(int paperWidth, int paperHeight, boolean isLandscape) { width = (isLandscape ? paperHeight : paperWidth); height = (isLandscape ? paperWidth : paperHeight); System.out.println("Creating PDF with width=" + width + " and height=" + height); fonts = new Hashtable(); indirectObjects = new Lst(); //graphicsResources = newObject(null); //pageResources = newObject(null); // will set this to compressed stream later root = newObject("Catalog"); PDFObject pages = newObject("Pages"); PDFObject page = newObject("Page"); PDFObject pageContents = newObject(null); graphics = newObject("XObject"); root.addDef("Pages", pages.getRef()); pages.addDef("Count", "1"); pages.addDef("Kids", "[ " + page.getRef() +" ]"); page.addDef("Parent", pages.getRef()); page.addDef("MediaBox", "[ 0 0 " + paperWidth + " " + paperHeight + " ]"); if (isLandscape) page.addDef("Rotate", "90"); pageContents.addDef("Length", "?"); pageContents.append((isLandscape ? "q 0 1 1 0 0 0 " : "q 1 0 0 -1 0 "+(paperHeight))+" cm /" + graphics.getID() + " Do Q"); page.addDef("Contents", pageContents.getRef()); addProcSet(page); addProcSet(graphics); // will add fonts as well as they are needed graphics.addDef("Subtype", "/Form"); graphics.addDef("FormType", "1"); graphics.addDef("BBox", "[0 0 " + width + " " + height + "]"); graphics.addDef("Matrix", "[1 0 0 1 0 0]"); graphics.addDef("Length", "?"); page.addResource("XObject", graphics.getID(), graphics.getRef()); 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 clip(0, 0, width, height); } private void addProcSet(PDFObject o) { o.addResource(null, "ProcSet", "[/PDF /Text /ImageB /ImageC /ImageI]"); } private void clip(int x1, int y1, int x2, int y2) { moveto(x1, y1); lineto(x2, y1); lineto(x2, y2); lineto(x1, y2); g("h W n"); } public void moveto(int x, int y) { g(x + " " + y + " m"); } public void lineto(int x, int y) { g(x + " " + y + " l"); } private PDFObject newObject(String type) { PDFObject o = new PDFObject(++count); if (type != null) o.addDef("Type", "/" + type); indirectObjects.addLast(o); return o; } public void addInfo(Map data) { Hashtable info = new Hashtable(); for (Entry e: data.entrySet()) { String value = "(" + e.getValue().replace(')','_').replace('(','_')+ ")"; info.put(e.getKey(), value); } root.addDef("Info", info); } private PDFObject addFontResource(String fname) { PDFObject f = newObject("Font"); fonts.put(fname, f); f.addDef("BaseFont", fname); f.addDef("Encoding", "/WinAnsiEncoding"); f.addDef("Subtype", "/Type1"); graphics.addResource("Font", f.getID(), f.getRef()); return f; } private Map images; public void addImageResource(Object newImage, int width, int height, int[] buffer, boolean isRGB) { PDFObject imageObj = newObject("XObject"); if (images == null) images = new Hashtable(); images.put(newImage, imageObj); imageObj.addDef("Subtype", "/Image"); imageObj.addDef("Length", "?"); imageObj.addDef("ColorSpace", isRGB ? "/DeviceRGB" : "/DeviceGray"); imageObj.addDef("BitsPerComponent", "8"); imageObj.addDef("Width", "" + width); imageObj.addDef("Height", "" + height); graphics.addResource("XObject", imageObj.getID(), imageObj.getRef()); int n = buffer.length; byte[] stream = new byte[n * (isRGB ? 3 : 1)]; if (isRGB) { for (int i = 0, pt = 0; i < n; i++) { stream[pt++] = (byte) ((buffer[i] >> 16) & 0xFF); stream[pt++] = (byte) ((buffer[i] >> 8) & 0xFF); stream[pt++] = (byte) (buffer[i] & 0xFF); } } else { for (int i = 0; i < n; i++) stream[i] = (byte) buffer[i]; } imageObj.setStream(stream); graphics.addResource("XObject", imageObj.getID(), imageObj.getRef()); } public void g(String cmd) { graphics.append(cmd).appendC('\n'); } private void output(String s) throws IOException { byte[] b = s.getBytes(); os.write(b, 0, b.length); pt += b.length; } public void closeDocument() throws IOException { g("Q Q"); outputHeader(); writeObjects(); writeXRefTable(); writeTrailer(); os.flush(); os.close(); } private void outputHeader() throws IOException { output("%PDF-1.3\n%"); byte[] b = new byte[] {-1, -1, -1, -1}; os.write(b, 0, b.length); pt += 4; output("\n"); } private void writeTrailer() throws IOException { PDFObject trailer = new PDFObject(-2); output("trailer"); trailer.addDef("Size", "" + indirectObjects.size()); trailer.addDef("Root", root.getRef()); trailer.output(os); output("startxref\n"); output("" + xrefPt + "\n"); output("%%EOF\n"); } /** * Write Font objects first. * * @throws IOException */ private void writeObjects() throws IOException { int nObj = indirectObjects.size(); for (int i = 0; i < nObj; i++) { PDFObject o = indirectObjects.get(i); if (!o.isFont()) continue; o.pt = pt; pt += o.output(os); } for (int i = 0; i < nObj; i++) { PDFObject o = indirectObjects.get(i); if (o.isFont()) continue; o.pt = pt; pt += o.output(os); } } private void writeXRefTable() throws IOException { xrefPt = pt; int nObj = indirectObjects.size(); SB sb = new SB(); // note trailing space, needed because \n is just one character sb.append("xref\n0 " + (nObj + 1) + "\n0000000000 65535 f\r\n"); for (int i = 0; i < nObj; i++) { PDFObject o = indirectObjects.get(i); String s = "0000000000" + o.pt; sb.append(s.substring(s.length() - 10)); sb.append(" 00000 n\r\n"); } output(sb.toString()); } public boolean canDoLineTo() { return true; } public void fill() { g("f"); } public void stroke() { g("S"); } public void doCircle(int x, int y, int r, boolean doFill) { double d = r*4*(Math.sqrt(2)-1)/3; double dx = x; double dy = y; g((dx + r) + " " + dy + " m"); g((dx + r) + " " + (dy + d) + " " + (dx + d) + " " + (dy + r) + " " + (dx) + " " + (dy + r) + " " + " c"); g((dx - d) + " " + (dy + r) + " " + (dx - r) + " " + (dy + d) + " " + (dx - r) + " " + (dy) + " c"); g((dx - r) + " " + (dy - d) + " " + (dx - d) + " " + (dy - r) + " " + (dx) + " " + (dy - r) + " c"); g((dx + d) + " " + (dy - r) + " " + (dx + r) + " " + (dy - d) + " " + (dx + r) + " " + (dy) + " c"); g(doFill ? "f" : "s"); } public void doPolygon(int[] axPoints, int[] ayPoints, int nPoints, boolean doFill) { moveto(axPoints[0], ayPoints[0]); for (int i = 1; i < nPoints; i++) lineto(axPoints[i], ayPoints[i]); g(doFill ? "f" : "s"); } public void doRect(int x, int y, int width, int height, boolean doFill) { g(x + " " + y + " " + width + " " + height + " re " + (doFill ? "f" : "s")); } public void drawImage(Object image, int destX0, int destY0, int destX1, int destY1, int srcX0, int srcY0, int srcX1, int srcY1) { PDFObject imageObj = images.get(image); if (imageObj == null) return; g("q"); clip(destX0, destY0, destX1, destY1); double iw = Double.parseDouble((String) imageObj.getDef("Width")); double ih = Double.parseDouble((String) imageObj.getDef("Height")); double dw = (destX1 - destX0 + 1); double dh = (destY1 - destY0 + 1); double sw = (srcX1 - srcX0 + 1); double sh = (srcY1 - srcY0 + 1); double scaleX = dw / sw; double scaleY = dh / sh; double transX = destX0 - srcX0 * scaleX; double transY = destY0 + (ih - srcY0) * scaleY; g(scaleX*iw + " 0 0 " + -scaleY*ih + " " + transX + " " + transY + " cm"); g("/" + imageObj.getID() + " Do"); g("Q"); } public void drawStringRotated(String s, int x, int y, int angle) { g("q " + getRotation(angle) + " " + x + " " + y + " cm BT(" + s + ")Tj ET Q"); } public String getRotation(int angle) { float cos = 0, sin = 0; switch (angle) { case 0: cos = 1; break; case 90: sin = 1; break; case -90: sin = -1; break; case 180: cos = -1; break; default: float a = (float) (angle / 180.0 * Math.PI); cos = (float) Math.cos(a); sin = (float) Math.sin(a); if (Math.abs(cos) < 0.0001) cos = 0; if (Math.abs(sin) < 0.0001) sin = 0; } return cos + " " + sin + " " + sin + " " + -cos; } public void setColor(float[] rgb, boolean isFill) { g(rgb[0] + " " + rgb[1] + " " + rgb[2] + (isFill ? " rg" : " RG")); } public void setFont(String fname, float size) { PDFObject f = fonts.get(fname); if (f == null) f = addFontResource(fname); g("/" + f.getID() + " " + size + " Tf"); } public void setLineWidth(float width) { g(width + " w"); } public void translateScale(float x, float y, float scale) { g(scale + " 0 0 " + scale + " " + x + " " + y + " cm"); } }