Merge branch 'master' of https://source.jalview.org/git/jalviewjs.git
[jalviewjs.git] / src / javajs / export / PDFCreator.java
1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2009-06-30 18:58:33 -0500 (Tue, 30 Jun 2009) $
4  * $Revision: 11158 $
5  *
6  * Copyright (C) 2002-2005  The Jmol Development Team
7  *
8  * Contact: jmol-developers@lists.sf.net
9  *
10  *  This library is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU Lesser General Public
12  *  License as published by the Free Software Foundation; either
13  *  version 2.1 of the License, or (at your option) any later version.
14  *
15  *  This library is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  Lesser General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Lesser General Public
21  *  License along with this library; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 package javajs.export;
25
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.util.Hashtable;
29 import java.util.Map;
30 import java.util.Map.Entry;
31
32 import javajs.util.Lst;
33 import javajs.util.SB;
34
35
36
37 public class PDFCreator {
38  
39   private OutputStream os;
40   private Lst<PDFObject> indirectObjects;
41   private PDFObject root;
42   private PDFObject graphics; 
43 //  private PDFObject pageResources;
44 //  private PDFObject graphicsResources;
45
46   private int pt;
47   private int xrefPt;
48   private int count;
49
50   private int height;
51   private int width;
52   
53   private Map<String, PDFObject>fonts;
54
55   public PDFCreator() {
56    // for Class.forName  
57   }
58
59   public void setOutputStream(OutputStream os) {
60     this.os = os;
61   }
62
63   public void newDocument(int paperWidth, int paperHeight, boolean isLandscape) {
64     width = (isLandscape ? paperHeight : paperWidth);
65     height = (isLandscape ? paperWidth : paperHeight);
66     System.out.println("Creating PDF with width=" + width + " and height=" + height);
67     fonts = new Hashtable<String, PDFObject>();
68     indirectObjects = new Lst<PDFObject>();
69     //graphicsResources = newObject(null);
70     //pageResources = newObject(null); // will set this to compressed stream later
71     root = newObject("Catalog");
72     PDFObject pages = newObject("Pages");
73     PDFObject page = newObject("Page");
74     PDFObject pageContents = newObject(null);
75     graphics = newObject("XObject");
76     
77     root.addDef("Pages", pages.getRef());
78     pages.addDef("Count", "1");
79     pages.addDef("Kids", "[ " + page.getRef() +" ]");
80     page.addDef("Parent", pages.getRef());
81     page.addDef("MediaBox", "[ 0 0 " + paperWidth + " " + paperHeight + " ]");
82     if (isLandscape)
83       page.addDef("Rotate", "90");
84
85     pageContents.addDef("Length", "?");
86     pageContents.append((isLandscape ? "q 0 1 1 0 0 0 " : "q 1 0 0 -1 0 "+(paperHeight))+" cm /" + graphics.getID() + " Do Q");
87     page.addDef("Contents", pageContents.getRef());   
88     addProcSet(page);
89     addProcSet(graphics);
90     // will add fonts as well as they are needed
91     graphics.addDef("Subtype", "/Form");
92     graphics.addDef("FormType", "1");
93     graphics.addDef("BBox", "[0 0 " + width + " " + height + "]");
94     graphics.addDef("Matrix", "[1 0 0 1 0 0]");
95     graphics.addDef("Length", "?");
96     page.addResource("XObject", graphics.getID(), graphics.getRef());   
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
98     clip(0, 0, width, height);
99   }   
100
101   private void addProcSet(PDFObject o) {
102     o.addResource(null, "ProcSet", "[/PDF /Text /ImageB /ImageC /ImageI]");
103   }
104
105   private void clip(int x1, int y1, int x2, int y2) {
106     moveto(x1, y1);
107     lineto(x2, y1);
108     lineto(x2, y2);
109     lineto(x1, y2);
110     g("h W n");
111   }
112
113   public void moveto(int x, int y) {
114     g(x + " " + y  + " m");
115   }
116
117   public void lineto(int x, int y) {
118     g(x + " " + y  + " l");
119   }
120
121   private PDFObject newObject(String type) {
122     PDFObject o = new PDFObject(++count);
123     if (type != null)
124       o.addDef("Type", "/" + type);
125     indirectObjects.addLast(o);
126     return o;
127   }
128
129   public void addInfo(Map<String, String> data) {
130     Hashtable<String, Object> info = new Hashtable<String, Object>();
131     for (Entry<String, String> e: data.entrySet()) {
132       String value = "(" + e.getValue().replace(')','_').replace('(','_')+ ")";
133       info.put(e.getKey(), value);      
134     }
135     root.addDef("Info", info);
136   }
137
138   private PDFObject addFontResource(String fname) {
139     PDFObject f = newObject("Font");
140     fonts.put(fname, f);
141     f.addDef("BaseFont", fname);
142     f.addDef("Encoding", "/WinAnsiEncoding");
143     f.addDef("Subtype", "/Type1");
144     graphics.addResource("Font", f.getID(), f.getRef());
145     return f;
146   }
147
148   private Map<Object, PDFObject> images;
149   
150   public void addImageResource(Object newImage, int width, int height, int[] buffer, boolean isRGB) {
151     PDFObject imageObj = newObject("XObject");
152     if (images == null)
153       images = new Hashtable<Object, PDFObject>();
154     images.put(newImage, imageObj);   
155     imageObj.addDef("Subtype", "/Image");
156     imageObj.addDef("Length", "?");
157     imageObj.addDef("ColorSpace", isRGB ? "/DeviceRGB" : "/DeviceGray");
158     imageObj.addDef("BitsPerComponent", "8");
159     imageObj.addDef("Width", "" + width);
160     imageObj.addDef("Height", "" + height);
161     graphics.addResource("XObject", imageObj.getID(), imageObj.getRef());
162     int n = buffer.length;
163     byte[] stream = new byte[n * (isRGB ? 3 : 1)];
164     if (isRGB) {
165       for (int i = 0, pt = 0; i < n; i++) {
166         stream[pt++] = (byte) ((buffer[i] >> 16) & 0xFF);
167         stream[pt++] = (byte) ((buffer[i] >> 8) & 0xFF);
168         stream[pt++] = (byte) (buffer[i] & 0xFF);
169       }
170     } else {
171       for (int i = 0; i < n; i++)
172         stream[i] = (byte) buffer[i];
173     }
174     imageObj.setStream(stream);
175     graphics.addResource("XObject", imageObj.getID(), imageObj.getRef());
176   }
177
178   public void g(String cmd) {
179     graphics.append(cmd).appendC('\n');
180   }
181
182   private void output(String s) throws IOException {
183    byte[] b = s.getBytes();
184    os.write(b, 0, b.length);
185    pt += b.length;
186   }
187
188   public void closeDocument() throws IOException {
189     g("Q Q");
190     outputHeader();
191     writeObjects();
192     writeXRefTable();
193     writeTrailer();
194     os.flush();
195     os.close();
196   }
197
198   private void outputHeader() throws IOException {
199     output("%PDF-1.3\n%");
200     byte[] b = new byte[] {-1, -1, -1, -1};
201     os.write(b, 0, b.length);
202     pt += 4;
203     output("\n");
204   }
205
206   private void writeTrailer() throws IOException {
207     PDFObject trailer = new PDFObject(-2);
208     output("trailer");
209     trailer.addDef("Size", "" + indirectObjects.size());
210     trailer.addDef("Root", root.getRef());
211     trailer.output(os);
212     output("startxref\n");
213     output("" + xrefPt + "\n");
214     output("%%EOF\n");
215   }
216
217   /**
218    * Write Font objects first.
219    * 
220    * @throws IOException
221    */
222   private void writeObjects() throws IOException {
223     int nObj = indirectObjects.size();
224     for (int i = 0; i < nObj; i++) {
225       PDFObject o = indirectObjects.get(i);
226       if (!o.isFont())
227         continue;
228       o.pt = pt;
229       pt += o.output(os);
230     }
231     for (int i = 0; i < nObj; i++) {
232       PDFObject o = indirectObjects.get(i);
233       if (o.isFont())
234         continue;
235       o.pt = pt;
236       pt += o.output(os);
237     }
238   }
239
240   private void writeXRefTable() throws IOException {
241     xrefPt = pt;
242     int nObj = indirectObjects.size();
243     SB sb = new SB();
244     // note trailing space, needed because \n is just one character
245     sb.append("xref\n0 " + (nObj + 1) 
246         + "\n0000000000 65535 f\r\n");
247     for (int i = 0; i < nObj; i++) {
248       PDFObject o = indirectObjects.get(i);
249       String s = "0000000000" + o.pt;
250       sb.append(s.substring(s.length() - 10));
251       sb.append(" 00000 n\r\n");
252     }
253     output(sb.toString());
254   }
255
256   public boolean canDoLineTo() {
257     return true;
258   }
259
260   public void fill() {
261     g("f");   
262   }
263
264   public void stroke() {
265     g("S");   
266   }
267
268   public void doCircle(int x, int y, int r, boolean doFill) {
269     double d = r*4*(Math.sqrt(2)-1)/3;
270     double dx = x;
271     double dy = y;
272     g((dx + r) + " " + dy + " m");
273     g((dx + r) + " " + (dy + d) + " " + (dx + d) + " " + (dy + r) + " " + (dx) + " " + (dy + r) + " "  + " c");
274     g((dx - d) + " " + (dy + r) + " " + (dx - r) + " " + (dy + d) + " " + (dx - r) + " " + (dy) + " c");
275     g((dx - r) + " " + (dy - d) + " " + (dx - d) + " " + (dy - r) + " " + (dx) + " " + (dy - r) + " c");
276     g((dx + d) + " " + (dy - r) + " " + (dx + r) + " " + (dy - d) + " " + (dx + r) + " " + (dy) + " c");
277     g(doFill ? "f" : "s");
278   }
279
280   public void doPolygon(int[] axPoints, int[] ayPoints, int nPoints, boolean doFill) {
281     moveto(axPoints[0], ayPoints[0]);
282     for (int i = 1; i < nPoints; i++)
283       lineto(axPoints[i], ayPoints[i]);
284     g(doFill ? "f" : "s");
285   }
286
287   public void doRect(int x, int y, int width, int height, boolean doFill) {
288     g(x + " " + y + " " + width + " " + height + " re " + (doFill ? "f" : "s"));
289   }
290
291   public void drawImage(Object image, int destX0, int destY0,
292       int destX1, int destY1, int srcX0, int srcY0, int srcX1, int srcY1) {
293     PDFObject imageObj = images.get(image);
294     if (imageObj == null)
295       return;
296     g("q");
297     clip(destX0, destY0, destX1, destY1);
298     double iw = Double.parseDouble((String) imageObj.getDef("Width"));
299     double ih = Double.parseDouble((String) imageObj.getDef("Height"));   
300     double dw = (destX1 - destX0 + 1);
301     double dh  = (destY1 - destY0 + 1);
302     double sw = (srcX1 - srcX0 + 1);
303     double sh = (srcY1 - srcY0 + 1);
304     double scaleX = dw / sw;
305     double scaleY = dh / sh;
306     double transX = destX0 - srcX0 * scaleX;
307     double transY = destY0 + (ih - srcY0) * scaleY;
308     g(scaleX*iw + " 0 0 " + -scaleY*ih + " " + transX + " " + transY + " cm");
309     g("/" + imageObj.getID() + " Do");
310     g("Q");
311   }
312
313   public void drawStringRotated(String s, int x, int y, int angle) {
314     g("q " + getRotation(angle) + " " + x + " " + y
315         + " cm BT(" + s + ")Tj ET Q");
316   }
317
318   public String getRotation(int angle) {    
319     float cos = 0, sin = 0;
320     switch (angle) {
321     case 0:
322       cos = 1;
323       break;
324     case 90:
325       sin = 1;
326       break;
327     case -90:
328       sin = -1;
329       break;
330     case 180:
331       cos = -1;
332       break;
333     default:
334       float a = (float) (angle / 180.0 * Math.PI);
335       cos = (float) Math.cos(a);
336       sin = (float) Math.sin(a);
337       if (Math.abs(cos) < 0.0001)
338         cos = 0;
339       if (Math.abs(sin) < 0.0001)
340         sin = 0;
341     }
342     return  cos + " " + sin + " " + sin + " " + -cos;
343   }
344
345   public void setColor(float[] rgb, boolean isFill) {
346     g(rgb[0] + " " + rgb[1] + " " + rgb[2] + (isFill ? " rg" : " RG"));
347   }
348
349   public void setFont(String fname, float size) {
350     PDFObject f = fonts.get(fname);
351     if (f == null)
352       f = addFontResource(fname);
353     g("/" + f.getID() + " " + size + " Tf");
354   }
355
356   public void setLineWidth(float width) {
357     g(width + " w");    
358   }
359
360   public void translateScale(float x, float y, float scale) {
361     g(scale + " 0 0 " + scale + " " + x + " " + y + " cm");
362   }
363
364 }