]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java
7a490e642dd5ea601faa9324e783e7b4a41fb19f
[simantics/platform.git] / bundles / org.simantics.export.core / src / org / simantics / export / core / pdf / ExportPdfWriter.java
1 package org.simantics.export.core.pdf;\r
2 \r
3 import java.awt.Graphics2D;\r
4 import java.io.File;\r
5 import java.io.FileInputStream;\r
6 import java.io.FileOutputStream;\r
7 import java.io.IOException;\r
8 import java.security.KeyStore;\r
9 import java.security.KeyStoreException;\r
10 import java.security.NoSuchAlgorithmException;\r
11 import java.security.PrivateKey;\r
12 import java.security.UnrecoverableKeyException;\r
13 import java.security.cert.Certificate;\r
14 import java.security.cert.CertificateException;\r
15 import java.util.ArrayList;\r
16 import java.util.Collections;\r
17 import java.util.HashMap;\r
18 import java.util.List;\r
19 import java.util.Map;\r
20 \r
21 import org.simantics.Simantics;\r
22 import org.simantics.databoard.binding.mutable.Variant;\r
23 import org.simantics.export.core.ExportContext;\r
24 import org.simantics.export.core.error.ExportException;\r
25 import org.simantics.export.core.intf.Format;\r
26 import org.simantics.export.core.manager.Content;\r
27 import org.simantics.utils.page.MarginUtils.Margins;\r
28 import org.simantics.utils.page.PageDesc;\r
29 import org.simantics.utils.page.PageOrientation;\r
30 \r
31 import com.lowagie.text.Document;\r
32 import com.lowagie.text.DocumentException;\r
33 import com.lowagie.text.Element;\r
34 import com.lowagie.text.ExceptionConverter;\r
35 import com.lowagie.text.Font;\r
36 import com.lowagie.text.PageSize;\r
37 import com.lowagie.text.Phrase;\r
38 import com.lowagie.text.Rectangle;\r
39 import com.lowagie.text.pdf.AcroFields;\r
40 import com.lowagie.text.pdf.BadPdfFormatException;\r
41 import com.lowagie.text.pdf.ColumnText;\r
42 import com.lowagie.text.pdf.FontMapper;\r
43 import com.lowagie.text.pdf.PdfContentByte;\r
44 import com.lowagie.text.pdf.PdfCopy;\r
45 import com.lowagie.text.pdf.PdfDictionary;\r
46 import com.lowagie.text.pdf.PdfFileSpecification;\r
47 import com.lowagie.text.pdf.PdfImportedPage;\r
48 import com.lowagie.text.pdf.PdfReader;\r
49 import com.lowagie.text.pdf.PdfSignatureAppearance;\r
50 import com.lowagie.text.pdf.PdfStamper;\r
51 import com.lowagie.text.pdf.PdfTemplate;\r
52 import com.lowagie.text.pdf.PdfWriter;\r
53 \r
54 /**\r
55  * A PDF writer object.  \r
56  *\r
57  * @author toni.kalajainen@semantum.fi\r
58  */\r
59 public class ExportPdfWriter {\r
60 \r
61         /** PDF Document */\r
62         public Document document;\r
63 \r
64         /** PDF Output stream */\r
65         public PdfCopy pdfCopy; \r
66         \r
67         /** Open output stream */\r
68         public FileOutputStream fos;\r
69 \r
70         /** The direct content byte of the document */\r
71         public PdfContentByte cb;\r
72         \r
73         /** Contains Pdf Templates, e.g. symbols. Resource Uri -> Template mapping */\r
74         public Map<String, PdfTemplate> templates = new HashMap<String, PdfTemplate>();\r
75         \r
76         /** Pages */\r
77         public List<Page> pages = new ArrayList<Page>(); \r
78         \r
79         /** Suggested Page Desc */\r
80         public PageDesc defaultPageDesc;\r
81         \r
82         /** PageDesc as PDF rectangle */\r
83         public Rectangle defaultRectangle;\r
84         \r
85         /** Initialized FontMapper */\r
86         public FontMapper fontMapper;\r
87         \r
88         /** The output file */\r
89         public File outputFile;\r
90         \r
91         /** All options */\r
92         public Variant options;\r
93         \r
94         /** Export Context */\r
95         public ExportContext ctx;\r
96         \r
97         /** The margins the user selected. */\r
98         public Margins margins;\r
99         \r
100         /** Compression Level */\r
101         public int compressionLevel;\r
102 \r
103         /**\r
104          * Create new page.\r
105          * \r
106          * @param (Optional) page description. If null is used, the default size is used.\r
107          * @return Page for writing\r
108          * @throws ExportException\r
109          */\r
110         public Page createPage( PageDesc pd ) throws ExportException {\r
111                                 \r
112                 Rectangle rect; \r
113                 if ( pd == null || pd.isInfinite() ) {\r
114                         pd = defaultPageDesc;\r
115                         rect = defaultRectangle;\r
116                 } else {\r
117                         rect = toRectangle( pd );\r
118                 }\r
119                 \r
120                 Page page = new Page( pd, rect, pages.size() );                         \r
121                 pages.add(page);                \r
122                 return page;\r
123         }\r
124         \r
125         /**\r
126          * Create a new template.\r
127          * \r
128          * Note, template is not visible on the document until you add it with \r
129          *   cb.addTemplate( template.tp, 0, 0);\r
130          *  \r
131          * @param name (Optional) Template identifier \r
132          * @param pd (Optional) template size description. If null is used, the default size is used.\r
133          * @return Template handle\r
134          * @throws ExportException\r
135          */\r
136         public Template createTemplate( String name, PageDesc pd ) throws ExportException {\r
137                 Rectangle rect; \r
138                 if ( pd == null || pd.isInfinite() ) {\r
139                         pd = defaultPageDesc;\r
140                         rect = defaultRectangle;\r
141                 } else {\r
142                         rect = toRectangle( pd );\r
143                 }\r
144                 \r
145         int w = (int) pd.getWidth();\r
146         int h = (int) pd.getHeight();\r
147         PdfTemplate tp = cb.createTemplate(w, h);        \r
148                 Template canvas = new Template( name, pd, rect, tp );\r
149                 canvas.name = name;\r
150         if ( name!=null ) templates.put(name, tp);              \r
151                 return canvas;\r
152         }       \r
153         \r
154         /**\r
155          * Sign the file with a private+public key pair (PPK).\r
156          * The file must be closed already. \r
157          * \r
158          * @param keystoreFile the keystore file\r
159          * @param keystorePassword (optional) password \r
160          * @param privateKeyPassword (optional) password\r
161          * @param signLocation (optional) sign locaiton, e.g. "Helsinki"\r
162          * @param signReason (optional) e.g. "approved"\r
163          * @throws ExportException \r
164          */\r
165         public void sign( File keystoreFile, String keystorePassword, String privateKeyPassword, String signLocation, String signReason) throws ExportException {\r
166                 // Add Bouncycastle, if found. If not, try anyway.\r
167                 /*\r
168         if (providerAdded.compareAndSet(false, true)) {\r
169                 try {\r
170                         String className = "org.bouncycastle.jce.provider.BouncyCastleProvider";\r
171                         Class<?> clazz = Class.forName(className);\r
172                         Provider provide = (Provider) clazz.newInstance();\r
173                         Security.addProvider( provide );\r
174                 } catch (SecurityException se) {\r
175                         se.printStackTrace();\r
176                 } catch (NullPointerException npe) {\r
177                         npe.printStackTrace();\r
178                 } catch (ClassNotFoundException e) {\r
179                         e.printStackTrace();\r
180                         } catch (InstantiationException e) {\r
181                                 e.printStackTrace();\r
182                         } catch (IllegalAccessException e) {\r
183                                 e.printStackTrace();\r
184                         }\r
185         }*/\r
186 \r
187         // Sign\r
188                 FileInputStream  ksfis = null;\r
189                 FileInputStream  fis = null;\r
190                 FileOutputStream fos = null;\r
191                 File signedFile = null;\r
192                 try {\r
193                         KeyStore ks = KeyStore.getInstance("pkcs12");\r
194                         signedFile = new File( outputFile.getCanonicalPath()+".signed" );\r
195                         if (signedFile.exists()) signedFile.delete();\r
196                         ksfis = new FileInputStream(keystoreFile);\r
197                         fis = new FileInputStream(outputFile);\r
198                         fos = new FileOutputStream( signedFile );\r
199                         ks.load(ksfis, keystorePassword != null ? keystorePassword.toCharArray() : null);\r
200                                         \r
201                         List<String> aliases = Collections.list(ks.aliases());\r
202                         String alias = aliases.get(0);\r
203                         PrivateKey key = (PrivateKey)ks.getKey(alias, privateKeyPassword != null ? privateKeyPassword.toCharArray() : null);\r
204                         Certificate[] chain = ks.getCertificateChain(alias);\r
205 \r
206                         PdfReader reader = new PdfReader( fis );\r
207                         PdfStamper stp = PdfStamper.createSignature(reader, fos, '\0');\r
208                         PdfSignatureAppearance sap = stp.getSignatureAppearance();\r
209 \r
210                         /// Signature\r
211                         String fieldName = "sign"; //signReason==null?"sign":URIUtil.encodeFilename( signReason );\r
212                 AcroFields af = stp.getAcroFields();\r
213                 AcroFields.Item item = af.getFieldItem(fieldName);\r
214                         if (signReason!=null) sap.setReason( signReason );\r
215                         if (signLocation!=null) sap.setLocation( signLocation );\r
216                         sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED);\r
217                         sap.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);\r
218                         sap.setRender(PdfSignatureAppearance.SignatureRenderNameAndDescription);\r
219                         \r
220                         // Make field the signature\r
221                         //sap.setVisibleSignature(fieldName);\r
222                         \r
223                         // Visible signature\r
224                 //AcroFields af = stp.getAcroFields();\r
225                 //AcroFields.Item item = af.getFieldItem(fieldName);\r
226                         //sap.setVisibleSignature(new Rectangle(0, 0, 100, 10), 0, fieldName);\r
227                                         \r
228                         // comment next line to have an invisible signature\r
229                         //sap.setVisibleSignature(new Rectangle(682, 130, 822, 145), 1, "approved_by");\r
230                                         \r
231                         //stp.getAcroFields().setField(fieldName, "someValue");\r
232                                         \r
233                         stp.close();    \r
234                         reader.close();\r
235                         fis.close();\r
236                 } catch (DocumentException e) {\r
237                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
238                 } catch (UnrecoverableKeyException e) {\r
239                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
240                 } catch (NoSuchAlgorithmException e) {\r
241                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
242                 } catch (CertificateException e) {\r
243                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(),e  );\r
244                 } catch (IOException e) {\r
245                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
246                 } catch (KeyStoreException e) {\r
247                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
248                 } finally {\r
249                         if ( ksfis != null ) try { ksfis.close(); } catch (IOException e) {}\r
250                         if ( fis != null ) try { fis.close(); } catch (IOException e) {}\r
251                         if ( fos != null ) try { fos.close(); } catch (IOException e) {}\r
252                                         \r
253                         if ( signedFile!=null && signedFile.exists() && outputFile.exists() ) {\r
254                                 outputFile.delete();\r
255                                 signedFile.renameTo( outputFile );\r
256                         }\r
257                         \r
258                 }\r
259         }\r
260         \r
261         public void addAttachment(Content content) throws ExportException {\r
262                 try {\r
263                         if ( content.tmpFile == null ) throw new ExportException("Could not export "+content.filename+", null file.");\r
264                         if ( !content.tmpFile.exists() ) throw new ExportException("Could not export "+content.filename+", file not found.");\r
265         \r
266                         Format format = ctx.eep.getFormat( content.formatId );\r
267                         //byte[] data = StreamUtil.readFully( content.tmpFile );\r
268                     PdfDictionary fileParameter = new PdfDictionary();      \r
269             PdfFileSpecification spec = PdfFileSpecification.fileEmbedded(\r
270                         pdfCopy,\r
271                         content.tmpFile.getAbsolutePath(),\r
272                         content.filename, \r
273                         null, \r
274                         true, \r
275                         "application/simantics/"+format.id(), \r
276                         fileParameter);\r
277             \r
278             pdfCopy.addFileAttachment( content.filename, spec );            \r
279             \r
280                         \r
281                 } catch (IOException e) {\r
282                         throw new ExportException( e.getClass().getName()+": "+e.getMessage() );\r
283                 }\r
284         }\r
285         \r
286         public void close() throws ExportException {\r
287                 // Flush & close\r
288                 try {\r
289                         if ( pages.isEmpty() ) {\r
290                                 Page page = createPage(null);                           \r
291                                 Graphics2D g2d = page.createGraphics(true);\r
292                                 try {\r
293                                         g2d.drawString("This page is intentionally left blank.", 100, 100);\r
294                                 } finally {\r
295                                         g2d.dispose();\r
296                                 }\r
297                         }\r
298                         \r
299                         for ( Page page : pages ) page.close();\r
300                         \r
301                         Font f = new Font(Font.HELVETICA, 8);\r
302 \r
303                         int totalPages = 0;\r
304                         int currentPage = 1;\r
305                         \r
306                         for ( Page page : pages ) {\r
307                                 PdfReader reader = new PdfReader( page.tmpFile.getAbsolutePath() );\r
308                                 try {\r
309                                 totalPages += reader.getNumberOfPages();\r
310                                 } finally {\r
311                                         reader.close();\r
312                                 }\r
313                         }\r
314                         \r
315                         for ( Page page : pages ) {\r
316                                 PdfReader reader = new PdfReader( page.tmpFile.getAbsolutePath() );\r
317                                 try {\r
318                                 int n = reader.getNumberOfPages();\r
319 \r
320                                 for (int i = 0; i < n; ) {\r
321                                     Rectangle pageSize = reader.getPageSizeWithRotation(n);\r
322 \r
323                                     PdfImportedPage imp = pdfCopy.getImportedPage(reader, ++i);\r
324                                     PdfCopy.PageStamp ps = pdfCopy.createPageStamp(imp);\r
325 \r
326                                     PdfContentByte over = ps.getOverContent();\r
327 \r
328                                     ColumnText.showTextAligned(over, Element.ALIGN_RIGHT,\r
329                                                 new Phrase(\r
330                                                                 String.format("%d / %d", currentPage++, totalPages), f),\r
331                                                                 pageSize.getWidth()-12, 12, 0);\r
332                                     ps.alterContents();\r
333                                     pdfCopy.addPage(imp);\r
334                                     \r
335                                 }\r
336                                 } finally {\r
337                                         reader.close();\r
338                                 }\r
339                 }                                               \r
340                 } catch (IOException e) {\r
341                         throw new ExportException( e );\r
342                 } catch (ExceptionConverter e) {\r
343                         throw new ExportException( e );\r
344                 } catch (BadPdfFormatException e) {\r
345                         throw new ExportException( e );\r
346                 } catch (Exception e) {\r
347                         throw new ExportException( e );\r
348                 } finally {\r
349                         for ( Page page : pages ) {\r
350                                 if ( page.tmpFile != null ) { page.tmpFile.delete(); page.tmpFile = null; }                             \r
351                         }\r
352                         pages.clear();\r
353                         \r
354                         if ( document != null ) { document.close(); document = null; }\r
355                         if ( pdfCopy != null ) { pdfCopy.close(); pdfCopy = null; }\r
356                         if ( fos != null ) { try {\r
357                                 fos.close();\r
358                         } catch (IOException e) {\r
359                                 throw new ExportException(e);\r
360                         } fos = null; }\r
361                 }\r
362         }\r
363         \r
364         public class Page {\r
365 \r
366                 /** PDF Output stream */\r
367                 public PdfWriter pdfWriter;\r
368                 \r
369                 /** PDF Document */\r
370                 public Document document;\r
371                 \r
372                 /** Open output stream */\r
373                 public FileOutputStream fos;\r
374 \r
375                 /** The direct content byte of the document */\r
376                 public PdfContentByte cb;\r
377                 \r
378                 /** Tmp-file where the page is written to */\r
379                 public File tmpFile;\r
380                 \r
381                 /** Suggested Page Desc */\r
382                 public PageDesc pageDesc;\r
383                 \r
384                 /** PageDesc as PDF rectangle */\r
385                 public Rectangle rectangle;\r
386                 \r
387                 /** Page number */\r
388                 public int pageNumber;\r
389                 \r
390                 Page(PageDesc pageDesc, Rectangle rect, int pageNumber) throws ExportException {\r
391                         try {\r
392                                 this.pageDesc = pageDesc;\r
393                                 this.rectangle = rect;\r
394                                 this.pageNumber = pageNumber;\r
395                                 this.tmpFile = Simantics.getTempfile("export.core", "pdf");\r
396                                 this.fos = new FileOutputStream( tmpFile, false );                      \r
397                                 this.document = new Document(rectangle);                        \r
398                                 this.document.setPageSize( rect ); // redundant?\r
399                                 this.pdfWriter = PdfWriter.getInstance(document, fos);\r
400                                 this.pdfWriter.setPdfVersion(PdfWriter.PDF_VERSION_1_7);\r
401                                 this.pdfWriter.setCompressionLevel( compressionLevel );\r
402                                 this.document.open();\r
403                                 this.cb = this.pdfWriter.getDirectContent();\r
404                                 if (!this.document.newPage()) throw new ExportException("Failed to create new page.");\r
405                         } catch (IOException e) {\r
406                                 throw new ExportException( e );\r
407                         } catch (DocumentException e) {\r
408                                 throw new ExportException( e );\r
409                         }\r
410                 }\r
411                 \r
412                 /**\r
413                  * Create a graphics 2d Context that uses millimeters.\r
414                  * \r
415                  * @param applyMargins top left position of margins is applied\r
416                  * @return graphics 2d context\r
417                  */\r
418                 public Graphics2D createGraphics(boolean applyMargins) {\r
419             float w = rectangle.getWidth();\r
420             float h = rectangle.getHeight();\r
421                         double pw = pageDesc.getOrientedWidth();\r
422                         double ph = pageDesc.getOrientedHeight();\r
423                         Graphics2D g2d = cb.createGraphics(w, h, fontMapper);\r
424                         \r
425                         if ( applyMargins ) {\r
426                                 Margins m = pageDesc.getMargins();\r
427 \r
428                     double mw = pw - m.left.diagramAbsolute - m.right.diagramAbsolute;\r
429                     double mh = ph - m.top.diagramAbsolute - m.bottom.diagramAbsolute;\r
430                     double sx = m.left.diagramAbsolute;\r
431                     double sy = m.top.diagramAbsolute;\r
432                     \r
433                     // Convert to points\r
434                     mw = PageDesc.toPoints( mw );\r
435                     mh = PageDesc.toPoints( mh );\r
436                     sx = PageDesc.toPoints( sx );\r
437                     sy = PageDesc.toPoints( sy );\r
438                     \r
439                                 g2d.translate(sx, sy);\r
440                         }\r
441                         \r
442                         g2d.scale(w/pw, h/ph);          \r
443                         return g2d;\r
444                 }\r
445                 \r
446                 /**\r
447                  * Of area inside the margins, return the width of the page in millimeters. \r
448                  * \r
449                  * @return width (mm)\r
450                  */\r
451                 public double getWidth() {\r
452                         Margins m = pageDesc.getMargins();\r
453                         return pageDesc.getOrientedWidth() - m.left.diagramAbsolute - m.right.diagramAbsolute; \r
454                 }\r
455                 \r
456                 public double getHeight() {\r
457                         Margins m = pageDesc.getMargins();\r
458                         return pageDesc.getOrientedHeight() - m.top.diagramAbsolute - m.bottom.diagramAbsolute; \r
459                 }\r
460                 \r
461                 /**\r
462                  * Add attachment to this page\r
463                  * @param content\r
464                  * @throws ExportException\r
465                  */\r
466                 public void addAttachment(Content content) throws ExportException {\r
467                         /*\r
468                         try {\r
469                                 if ( content.tmpFile == null ) throw new ExportException("Could not export "+content.filename+", null file.");\r
470                                 if ( !content.tmpFile.exists() ) throw new ExportException("Could not export "+content.filename+", file not found.");\r
471                 \r
472                                 Format format = ctx.eep.getFormat( content.formatId );\r
473                                 //byte[] data = StreamUtil.readFully( content.tmpFile );\r
474                             PdfDictionary fileParameter = new PdfDictionary();      \r
475                     PdfFileSpecification spec = PdfFileSpecification.fileEmbedded(\r
476                                 pdfWriter,\r
477                                 content.tmpFile.getAbsolutePath(),\r
478                                 content.filename, \r
479                                 null, \r
480                                 true, \r
481                                 "application/simantics/"+format.id(), \r
482                                 fileParameter);\r
483                     \r
484                     pdfWriter.addFileAttachment( content.filename, spec );                                              \r
485                         } catch (IOException e) {\r
486                                 throw new ExportException( e.getClass().getName()+": "+e.getMessage() );\r
487                         }*/\r
488                         ExportPdfWriter.this.addAttachment(content);\r
489                 }\r
490                 \r
491                 public void close() throws ExportException {\r
492                         try {\r
493                                 if ( document != null ) { document.close(); document = null; }\r
494                                 if ( pdfWriter != null ) { pdfWriter.close(); pdfWriter = null; }\r
495                                 if ( fos != null ) { fos.close(); fos = null; }\r
496                                 if ( cb != null ) { cb = null; }\r
497                         } catch (IOException e) {\r
498                                 throw new ExportException(e);\r
499                         }\r
500                 }\r
501                 \r
502         }\r
503         \r
504         public class Template {\r
505 \r
506                 /** Suggested Page Desc */\r
507                 public PageDesc pageDesc;\r
508                 \r
509                 /** PageDesc as PDF rectangle */\r
510                 public Rectangle rectangle;\r
511                 \r
512                 /** Template name */\r
513                 public String name;\r
514                 \r
515                 /** PdfTemplate */\r
516                 public PdfTemplate tp;\r
517 \r
518                 Template(String name, PageDesc pd, Rectangle rect, PdfTemplate tp) {\r
519                         this.pageDesc = pd;\r
520                         this.rectangle = rect;\r
521                         this.name = name;\r
522                         this.tp = tp;\r
523                 }\r
524                 \r
525                 public Graphics2D createGraphics() {                    \r
526             double w = pageDesc.getWidth();\r
527             double h = pageDesc.getHeight();\r
528                         return tp.createGraphics((float) w, (float) h, fontMapper);\r
529                 }\r
530                 \r
531         }\r
532         \r
533     public static Rectangle toRectangle(PageDesc pageDesc) {\r
534         String arg = PageDesc.toPoints(pageDesc.getWidth()) + " " + PageDesc.toPoints(pageDesc.getHeight());\r
535         Rectangle r = PageSize.getRectangle(arg);\r
536 \r
537         if (PageOrientation.Landscape == pageDesc.getOrientation())\r
538             r = r.rotate();\r
539 \r
540         // Disable inherent borders from the PDF writer.\r
541         r.setBorder(0);\r
542 \r
543         return r;\r
544     }\r
545         \r
546 }\r