JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / javajs / export / PDFCreator.java
1 /* $RCSfile$\r
2  * $Author: hansonr $\r
3  * $Date: 2009-06-30 18:58:33 -0500 (Tue, 30 Jun 2009) $\r
4  * $Revision: 11158 $\r
5  *\r
6  * Copyright (C) 2002-2005  The Jmol Development Team\r
7  *\r
8  * Contact: jmol-developers@lists.sf.net\r
9  *\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
14  *\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
19  *\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
23  */\r
24 package javajs.export;\r
25 \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
31 \r
32 import javajs.util.Lst;\r
33 import javajs.util.SB;\r
34 \r
35 \r
36 \r
37 public class PDFCreator {\r
38  \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
45 \r
46   private int pt;\r
47   private int xrefPt;\r
48   private int count;\r
49 \r
50   private int height;\r
51   private int width;\r
52   \r
53   private Map<String, PDFObject>fonts;\r
54 \r
55   public PDFCreator() {\r
56    // for Class.forName  \r
57   }\r
58 \r
59   public void setOutputStream(OutputStream os) {\r
60     this.os = os;\r
61   }\r
62 \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
76     \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
82     if (isLandscape)\r
83       page.addDef("Rotate", "90");\r
84 \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
88     addProcSet(page);\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
99   }   \r
100 \r
101   private void addProcSet(PDFObject o) {\r
102     o.addResource(null, "ProcSet", "[/PDF /Text /ImageB /ImageC /ImageI]");\r
103   }\r
104 \r
105   private void clip(int x1, int y1, int x2, int y2) {\r
106     moveto(x1, y1);\r
107     lineto(x2, y1);\r
108     lineto(x2, y2);\r
109     lineto(x1, y2);\r
110     g("h W n");\r
111   }\r
112 \r
113   public void moveto(int x, int y) {\r
114     g(x + " " + y  + " m");\r
115   }\r
116 \r
117   public void lineto(int x, int y) {\r
118     g(x + " " + y  + " l");\r
119   }\r
120 \r
121   private PDFObject newObject(String type) {\r
122     PDFObject o = new PDFObject(++count);\r
123     if (type != null)\r
124       o.addDef("Type", "/" + type);\r
125     indirectObjects.addLast(o);\r
126     return o;\r
127   }\r
128 \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
134     }\r
135     root.addDef("Info", info);\r
136   }\r
137 \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
145     return f;\r
146   }\r
147 \r
148   private Map<Object, PDFObject> images;\r
149   \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
164     if (isRGB) {\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
169       }\r
170     } else {\r
171       for (int i = 0; i < n; i++)\r
172         stream[i] = (byte) buffer[i];\r
173     }\r
174     imageObj.setStream(stream);\r
175     graphics.addResource("XObject", imageObj.getID(), imageObj.getRef());\r
176   }\r
177 \r
178   public void g(String cmd) {\r
179     graphics.append(cmd).appendC('\n');\r
180   }\r
181 \r
182   private void output(String s) throws IOException {\r
183    byte[] b = s.getBytes();\r
184    os.write(b, 0, b.length);\r
185    pt += b.length;\r
186   }\r
187 \r
188   public void closeDocument() throws IOException {\r
189     g("Q Q");\r
190     outputHeader();\r
191     writeObjects();\r
192     writeXRefTable();\r
193     writeTrailer();\r
194     os.flush();\r
195     os.close();\r
196   }\r
197 \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
202     pt += 4;\r
203     output("\n");\r
204   }\r
205 \r
206   private void writeTrailer() throws IOException {\r
207     PDFObject trailer = new PDFObject(-2);\r
208     output("trailer");\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
214     output("%%EOF\n");\r
215   }\r
216 \r
217   /**\r
218    * Write Font objects first.\r
219    * \r
220    * @throws IOException\r
221    */\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
226       if (!o.isFont())\r
227         continue;\r
228       o.pt = pt;\r
229       pt += o.output(os);\r
230     }\r
231     for (int i = 0; i < nObj; i++) {\r
232       PDFObject o = indirectObjects.get(i);\r
233       if (o.isFont())\r
234         continue;\r
235       o.pt = pt;\r
236       pt += o.output(os);\r
237     }\r
238   }\r
239 \r
240   private void writeXRefTable() throws IOException {\r
241     xrefPt = pt;\r
242     int nObj = indirectObjects.size();\r
243     SB sb = new SB();\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
252     }\r
253     output(sb.toString());\r
254   }\r
255 \r
256   public boolean canDoLineTo() {\r
257     return true;\r
258   }\r
259 \r
260   public void fill() {\r
261     g("f");   \r
262   }\r
263 \r
264   public void stroke() {\r
265     g("S");   \r
266   }\r
267 \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
270     double dx = x;\r
271     double dy = y;\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
278   }\r
279 \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
285   }\r
286 \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
289   }\r
290 \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
295       return;\r
296     g("q");\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
310     g("Q");\r
311   }\r
312 \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
316   }\r
317 \r
318   public String getRotation(int angle) {    \r
319     float cos = 0, sin = 0;\r
320     switch (angle) {\r
321     case 0:\r
322       cos = 1;\r
323       break;\r
324     case 90:\r
325       sin = 1;\r
326       break;\r
327     case -90:\r
328       sin = -1;\r
329       break;\r
330     case 180:\r
331       cos = -1;\r
332       break;\r
333     default:\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
338         cos = 0;\r
339       if (Math.abs(sin) < 0.0001)\r
340         sin = 0;\r
341     }\r
342     return  cos + " " + sin + " " + sin + " " + -cos;\r
343   }\r
344 \r
345   public void setColor(float[] rgb, boolean isFill) {\r
346     g(rgb[0] + " " + rgb[1] + " " + rgb[2] + (isFill ? " rg" : " RG"));\r
347   }\r
348 \r
349   public void setFont(String fname, float size) {\r
350     PDFObject f = fonts.get(fname);\r
351     if (f == null)\r
352       f = addFontResource(fname);\r
353     g("/" + f.getID() + " " + size + " Tf");\r
354   }\r
355 \r
356   public void setLineWidth(float width) {\r
357     g(width + " w");    \r
358   }\r
359 \r
360   public void translateScale(float x, float y, float scale) {\r
361     g(scale + " 0 0 " + scale + " " + x + " " + y + " cm");\r
362   }\r
363 \r
364 }\r