]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.export.core / src / org / simantics / export / core / pdf / ExportPdfWriter.java
diff --git a/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java
new file mode 100644 (file)
index 0000000..7a490e6
--- /dev/null
@@ -0,0 +1,546 @@
+package org.simantics.export.core.pdf;\r
+\r
+import java.awt.Graphics2D;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.security.KeyStore;\r
+import java.security.KeyStoreException;\r
+import java.security.NoSuchAlgorithmException;\r
+import java.security.PrivateKey;\r
+import java.security.UnrecoverableKeyException;\r
+import java.security.cert.Certificate;\r
+import java.security.cert.CertificateException;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import org.simantics.Simantics;\r
+import org.simantics.databoard.binding.mutable.Variant;\r
+import org.simantics.export.core.ExportContext;\r
+import org.simantics.export.core.error.ExportException;\r
+import org.simantics.export.core.intf.Format;\r
+import org.simantics.export.core.manager.Content;\r
+import org.simantics.utils.page.MarginUtils.Margins;\r
+import org.simantics.utils.page.PageDesc;\r
+import org.simantics.utils.page.PageOrientation;\r
+\r
+import com.lowagie.text.Document;\r
+import com.lowagie.text.DocumentException;\r
+import com.lowagie.text.Element;\r
+import com.lowagie.text.ExceptionConverter;\r
+import com.lowagie.text.Font;\r
+import com.lowagie.text.PageSize;\r
+import com.lowagie.text.Phrase;\r
+import com.lowagie.text.Rectangle;\r
+import com.lowagie.text.pdf.AcroFields;\r
+import com.lowagie.text.pdf.BadPdfFormatException;\r
+import com.lowagie.text.pdf.ColumnText;\r
+import com.lowagie.text.pdf.FontMapper;\r
+import com.lowagie.text.pdf.PdfContentByte;\r
+import com.lowagie.text.pdf.PdfCopy;\r
+import com.lowagie.text.pdf.PdfDictionary;\r
+import com.lowagie.text.pdf.PdfFileSpecification;\r
+import com.lowagie.text.pdf.PdfImportedPage;\r
+import com.lowagie.text.pdf.PdfReader;\r
+import com.lowagie.text.pdf.PdfSignatureAppearance;\r
+import com.lowagie.text.pdf.PdfStamper;\r
+import com.lowagie.text.pdf.PdfTemplate;\r
+import com.lowagie.text.pdf.PdfWriter;\r
+\r
+/**\r
+ * A PDF writer object.  \r
+ *\r
+ * @author toni.kalajainen@semantum.fi\r
+ */\r
+public class ExportPdfWriter {\r
+\r
+       /** PDF Document */\r
+       public Document document;\r
+\r
+       /** PDF Output stream */\r
+       public PdfCopy pdfCopy; \r
+       \r
+       /** Open output stream */\r
+       public FileOutputStream fos;\r
+\r
+       /** The direct content byte of the document */\r
+       public PdfContentByte cb;\r
+       \r
+       /** Contains Pdf Templates, e.g. symbols. Resource Uri -> Template mapping */\r
+       public Map<String, PdfTemplate> templates = new HashMap<String, PdfTemplate>();\r
+       \r
+       /** Pages */\r
+       public List<Page> pages = new ArrayList<Page>(); \r
+       \r
+       /** Suggested Page Desc */\r
+       public PageDesc defaultPageDesc;\r
+       \r
+       /** PageDesc as PDF rectangle */\r
+       public Rectangle defaultRectangle;\r
+       \r
+       /** Initialized FontMapper */\r
+       public FontMapper fontMapper;\r
+       \r
+       /** The output file */\r
+       public File outputFile;\r
+       \r
+       /** All options */\r
+       public Variant options;\r
+       \r
+       /** Export Context */\r
+       public ExportContext ctx;\r
+       \r
+       /** The margins the user selected. */\r
+       public Margins margins;\r
+       \r
+       /** Compression Level */\r
+       public int compressionLevel;\r
+\r
+       /**\r
+        * Create new page.\r
+        * \r
+        * @param (Optional) page description. If null is used, the default size is used.\r
+        * @return Page for writing\r
+        * @throws ExportException\r
+        */\r
+       public Page createPage( PageDesc pd ) throws ExportException {\r
+                               \r
+               Rectangle rect; \r
+               if ( pd == null || pd.isInfinite() ) {\r
+                       pd = defaultPageDesc;\r
+                       rect = defaultRectangle;\r
+               } else {\r
+                       rect = toRectangle( pd );\r
+               }\r
+               \r
+               Page page = new Page( pd, rect, pages.size() );                         \r
+               pages.add(page);                \r
+               return page;\r
+       }\r
+       \r
+       /**\r
+        * Create a new template.\r
+        * \r
+        * Note, template is not visible on the document until you add it with \r
+        *   cb.addTemplate( template.tp, 0, 0);\r
+        *  \r
+        * @param name (Optional) Template identifier \r
+        * @param pd (Optional) template size description. If null is used, the default size is used.\r
+        * @return Template handle\r
+        * @throws ExportException\r
+        */\r
+       public Template createTemplate( String name, PageDesc pd ) throws ExportException {\r
+               Rectangle rect; \r
+               if ( pd == null || pd.isInfinite() ) {\r
+                       pd = defaultPageDesc;\r
+                       rect = defaultRectangle;\r
+               } else {\r
+                       rect = toRectangle( pd );\r
+               }\r
+               \r
+        int w = (int) pd.getWidth();\r
+        int h = (int) pd.getHeight();\r
+        PdfTemplate tp = cb.createTemplate(w, h);        \r
+               Template canvas = new Template( name, pd, rect, tp );\r
+               canvas.name = name;\r
+        if ( name!=null ) templates.put(name, tp);             \r
+               return canvas;\r
+       }       \r
+       \r
+       /**\r
+        * Sign the file with a private+public key pair (PPK).\r
+        * The file must be closed already. \r
+        * \r
+        * @param keystoreFile the keystore file\r
+        * @param keystorePassword (optional) password \r
+        * @param privateKeyPassword (optional) password\r
+        * @param signLocation (optional) sign locaiton, e.g. "Helsinki"\r
+        * @param signReason (optional) e.g. "approved"\r
+        * @throws ExportException \r
+        */\r
+       public void sign( File keystoreFile, String keystorePassword, String privateKeyPassword, String signLocation, String signReason) throws ExportException {\r
+               // Add Bouncycastle, if found. If not, try anyway.\r
+               /*\r
+        if (providerAdded.compareAndSet(false, true)) {\r
+               try {\r
+                       String className = "org.bouncycastle.jce.provider.BouncyCastleProvider";\r
+                       Class<?> clazz = Class.forName(className);\r
+                       Provider provide = (Provider) clazz.newInstance();\r
+                       Security.addProvider( provide );\r
+               } catch (SecurityException se) {\r
+                       se.printStackTrace();\r
+               } catch (NullPointerException npe) {\r
+                       npe.printStackTrace();\r
+               } catch (ClassNotFoundException e) {\r
+                       e.printStackTrace();\r
+                       } catch (InstantiationException e) {\r
+                               e.printStackTrace();\r
+                       } catch (IllegalAccessException e) {\r
+                               e.printStackTrace();\r
+                       }\r
+        }*/\r
+\r
+        // Sign\r
+               FileInputStream  ksfis = null;\r
+               FileInputStream  fis = null;\r
+               FileOutputStream fos = null;\r
+               File signedFile = null;\r
+               try {\r
+                       KeyStore ks = KeyStore.getInstance("pkcs12");\r
+                       signedFile = new File( outputFile.getCanonicalPath()+".signed" );\r
+                       if (signedFile.exists()) signedFile.delete();\r
+                       ksfis = new FileInputStream(keystoreFile);\r
+                       fis = new FileInputStream(outputFile);\r
+                       fos = new FileOutputStream( signedFile );\r
+                       ks.load(ksfis, keystorePassword != null ? keystorePassword.toCharArray() : null);\r
+                                       \r
+                       List<String> aliases = Collections.list(ks.aliases());\r
+                       String alias = aliases.get(0);\r
+                       PrivateKey key = (PrivateKey)ks.getKey(alias, privateKeyPassword != null ? privateKeyPassword.toCharArray() : null);\r
+                       Certificate[] chain = ks.getCertificateChain(alias);\r
+\r
+                       PdfReader reader = new PdfReader( fis );\r
+                       PdfStamper stp = PdfStamper.createSignature(reader, fos, '\0');\r
+                       PdfSignatureAppearance sap = stp.getSignatureAppearance();\r
+\r
+                       /// Signature\r
+                       String fieldName = "sign"; //signReason==null?"sign":URIUtil.encodeFilename( signReason );\r
+               AcroFields af = stp.getAcroFields();\r
+               AcroFields.Item item = af.getFieldItem(fieldName);\r
+                       if (signReason!=null) sap.setReason( signReason );\r
+                       if (signLocation!=null) sap.setLocation( signLocation );\r
+                       sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED);\r
+                       sap.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);\r
+                       sap.setRender(PdfSignatureAppearance.SignatureRenderNameAndDescription);\r
+                       \r
+                       // Make field the signature\r
+                       //sap.setVisibleSignature(fieldName);\r
+                       \r
+                       // Visible signature\r
+               //AcroFields af = stp.getAcroFields();\r
+               //AcroFields.Item item = af.getFieldItem(fieldName);\r
+                       //sap.setVisibleSignature(new Rectangle(0, 0, 100, 10), 0, fieldName);\r
+                                       \r
+                       // comment next line to have an invisible signature\r
+                       //sap.setVisibleSignature(new Rectangle(682, 130, 822, 145), 1, "approved_by");\r
+                                       \r
+                       //stp.getAcroFields().setField(fieldName, "someValue");\r
+                                       \r
+                       stp.close();    \r
+                       reader.close();\r
+                       fis.close();\r
+               } catch (DocumentException e) {\r
+                       throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
+               } catch (UnrecoverableKeyException e) {\r
+                       throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
+               } catch (NoSuchAlgorithmException e) {\r
+                       throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
+               } catch (CertificateException e) {\r
+                       throw new ExportException( e.getClass().getName()+": "+e.getMessage(),e  );\r
+               } catch (IOException e) {\r
+                       throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
+               } catch (KeyStoreException e) {\r
+                       throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
+               } finally {\r
+                       if ( ksfis != null ) try { ksfis.close(); } catch (IOException e) {}\r
+                       if ( fis != null ) try { fis.close(); } catch (IOException e) {}\r
+                       if ( fos != null ) try { fos.close(); } catch (IOException e) {}\r
+                                       \r
+                       if ( signedFile!=null && signedFile.exists() && outputFile.exists() ) {\r
+                               outputFile.delete();\r
+                               signedFile.renameTo( outputFile );\r
+                       }\r
+                       \r
+               }\r
+       }\r
+       \r
+       public void addAttachment(Content content) throws ExportException {\r
+               try {\r
+                       if ( content.tmpFile == null ) throw new ExportException("Could not export "+content.filename+", null file.");\r
+                       if ( !content.tmpFile.exists() ) throw new ExportException("Could not export "+content.filename+", file not found.");\r
+       \r
+                       Format format = ctx.eep.getFormat( content.formatId );\r
+                       //byte[] data = StreamUtil.readFully( content.tmpFile );\r
+                   PdfDictionary fileParameter = new PdfDictionary();      \r
+           PdfFileSpecification spec = PdfFileSpecification.fileEmbedded(\r
+                       pdfCopy,\r
+                       content.tmpFile.getAbsolutePath(),\r
+                       content.filename, \r
+                       null, \r
+                       true, \r
+                       "application/simantics/"+format.id(), \r
+                       fileParameter);\r
+           \r
+           pdfCopy.addFileAttachment( content.filename, spec );            \r
+           \r
+                       \r
+               } catch (IOException e) {\r
+                       throw new ExportException( e.getClass().getName()+": "+e.getMessage() );\r
+               }\r
+       }\r
+       \r
+       public void close() throws ExportException {\r
+               // Flush & close\r
+               try {\r
+                       if ( pages.isEmpty() ) {\r
+                               Page page = createPage(null);                           \r
+                               Graphics2D g2d = page.createGraphics(true);\r
+                               try {\r
+                                       g2d.drawString("This page is intentionally left blank.", 100, 100);\r
+                               } finally {\r
+                                       g2d.dispose();\r
+                               }\r
+                       }\r
+                       \r
+                       for ( Page page : pages ) page.close();\r
+                       \r
+                       Font f = new Font(Font.HELVETICA, 8);\r
+\r
+                       int totalPages = 0;\r
+                       int currentPage = 1;\r
+                       \r
+                       for ( Page page : pages ) {\r
+                               PdfReader reader = new PdfReader( page.tmpFile.getAbsolutePath() );\r
+                               try {\r
+                               totalPages += reader.getNumberOfPages();\r
+                               } finally {\r
+                                       reader.close();\r
+                               }\r
+                       }\r
+                       \r
+                       for ( Page page : pages ) {\r
+                               PdfReader reader = new PdfReader( page.tmpFile.getAbsolutePath() );\r
+                               try {\r
+                               int n = reader.getNumberOfPages();\r
+\r
+                               for (int i = 0; i < n; ) {\r
+                                   Rectangle pageSize = reader.getPageSizeWithRotation(n);\r
+\r
+                                   PdfImportedPage imp = pdfCopy.getImportedPage(reader, ++i);\r
+                                   PdfCopy.PageStamp ps = pdfCopy.createPageStamp(imp);\r
+\r
+                                   PdfContentByte over = ps.getOverContent();\r
+\r
+                                   ColumnText.showTextAligned(over, Element.ALIGN_RIGHT,\r
+                                               new Phrase(\r
+                                                               String.format("%d / %d", currentPage++, totalPages), f),\r
+                                                               pageSize.getWidth()-12, 12, 0);\r
+                                   ps.alterContents();\r
+                                   pdfCopy.addPage(imp);\r
+                                   \r
+                               }\r
+                               } finally {\r
+                                       reader.close();\r
+                               }\r
+               }                                               \r
+               } catch (IOException e) {\r
+                       throw new ExportException( e );\r
+               } catch (ExceptionConverter e) {\r
+                       throw new ExportException( e );\r
+               } catch (BadPdfFormatException e) {\r
+                       throw new ExportException( e );\r
+               } catch (Exception e) {\r
+                       throw new ExportException( e );\r
+               } finally {\r
+                       for ( Page page : pages ) {\r
+                               if ( page.tmpFile != null ) { page.tmpFile.delete(); page.tmpFile = null; }                             \r
+                       }\r
+                       pages.clear();\r
+                       \r
+                       if ( document != null ) { document.close(); document = null; }\r
+                       if ( pdfCopy != null ) { pdfCopy.close(); pdfCopy = null; }\r
+                       if ( fos != null ) { try {\r
+                               fos.close();\r
+                       } catch (IOException e) {\r
+                               throw new ExportException(e);\r
+                       } fos = null; }\r
+               }\r
+       }\r
+       \r
+       public class Page {\r
+\r
+               /** PDF Output stream */\r
+               public PdfWriter pdfWriter;\r
+               \r
+               /** PDF Document */\r
+               public Document document;\r
+               \r
+               /** Open output stream */\r
+               public FileOutputStream fos;\r
+\r
+               /** The direct content byte of the document */\r
+               public PdfContentByte cb;\r
+               \r
+               /** Tmp-file where the page is written to */\r
+               public File tmpFile;\r
+               \r
+               /** Suggested Page Desc */\r
+               public PageDesc pageDesc;\r
+               \r
+               /** PageDesc as PDF rectangle */\r
+               public Rectangle rectangle;\r
+               \r
+               /** Page number */\r
+               public int pageNumber;\r
+               \r
+               Page(PageDesc pageDesc, Rectangle rect, int pageNumber) throws ExportException {\r
+                       try {\r
+                               this.pageDesc = pageDesc;\r
+                               this.rectangle = rect;\r
+                               this.pageNumber = pageNumber;\r
+                               this.tmpFile = Simantics.getTempfile("export.core", "pdf");\r
+                               this.fos = new FileOutputStream( tmpFile, false );                      \r
+                               this.document = new Document(rectangle);                        \r
+                               this.document.setPageSize( rect ); // redundant?\r
+                               this.pdfWriter = PdfWriter.getInstance(document, fos);\r
+                               this.pdfWriter.setPdfVersion(PdfWriter.PDF_VERSION_1_7);\r
+                               this.pdfWriter.setCompressionLevel( compressionLevel );\r
+                               this.document.open();\r
+                               this.cb = this.pdfWriter.getDirectContent();\r
+                               if (!this.document.newPage()) throw new ExportException("Failed to create new page.");\r
+                       } catch (IOException e) {\r
+                               throw new ExportException( e );\r
+                       } catch (DocumentException e) {\r
+                               throw new ExportException( e );\r
+                       }\r
+               }\r
+               \r
+               /**\r
+                * Create a graphics 2d Context that uses millimeters.\r
+                * \r
+                * @param applyMargins top left position of margins is applied\r
+                * @return graphics 2d context\r
+                */\r
+               public Graphics2D createGraphics(boolean applyMargins) {\r
+            float w = rectangle.getWidth();\r
+            float h = rectangle.getHeight();\r
+                       double pw = pageDesc.getOrientedWidth();\r
+                       double ph = pageDesc.getOrientedHeight();\r
+                       Graphics2D g2d = cb.createGraphics(w, h, fontMapper);\r
+                       \r
+                       if ( applyMargins ) {\r
+                               Margins m = pageDesc.getMargins();\r
+\r
+                   double mw = pw - m.left.diagramAbsolute - m.right.diagramAbsolute;\r
+                   double mh = ph - m.top.diagramAbsolute - m.bottom.diagramAbsolute;\r
+                   double sx = m.left.diagramAbsolute;\r
+                   double sy = m.top.diagramAbsolute;\r
+                   \r
+                   // Convert to points\r
+                   mw = PageDesc.toPoints( mw );\r
+                   mh = PageDesc.toPoints( mh );\r
+                   sx = PageDesc.toPoints( sx );\r
+                   sy = PageDesc.toPoints( sy );\r
+                   \r
+                               g2d.translate(sx, sy);\r
+                       }\r
+                       \r
+                       g2d.scale(w/pw, h/ph);          \r
+                       return g2d;\r
+               }\r
+               \r
+               /**\r
+                * Of area inside the margins, return the width of the page in millimeters. \r
+                * \r
+                * @return width (mm)\r
+                */\r
+               public double getWidth() {\r
+                       Margins m = pageDesc.getMargins();\r
+                       return pageDesc.getOrientedWidth() - m.left.diagramAbsolute - m.right.diagramAbsolute; \r
+               }\r
+               \r
+               public double getHeight() {\r
+                       Margins m = pageDesc.getMargins();\r
+                       return pageDesc.getOrientedHeight() - m.top.diagramAbsolute - m.bottom.diagramAbsolute; \r
+               }\r
+               \r
+               /**\r
+                * Add attachment to this page\r
+                * @param content\r
+                * @throws ExportException\r
+                */\r
+               public void addAttachment(Content content) throws ExportException {\r
+                       /*\r
+                       try {\r
+                               if ( content.tmpFile == null ) throw new ExportException("Could not export "+content.filename+", null file.");\r
+                               if ( !content.tmpFile.exists() ) throw new ExportException("Could not export "+content.filename+", file not found.");\r
+               \r
+                               Format format = ctx.eep.getFormat( content.formatId );\r
+                               //byte[] data = StreamUtil.readFully( content.tmpFile );\r
+                           PdfDictionary fileParameter = new PdfDictionary();      \r
+                   PdfFileSpecification spec = PdfFileSpecification.fileEmbedded(\r
+                               pdfWriter,\r
+                               content.tmpFile.getAbsolutePath(),\r
+                               content.filename, \r
+                               null, \r
+                               true, \r
+                               "application/simantics/"+format.id(), \r
+                               fileParameter);\r
+                   \r
+                   pdfWriter.addFileAttachment( content.filename, spec );                                              \r
+                       } catch (IOException e) {\r
+                               throw new ExportException( e.getClass().getName()+": "+e.getMessage() );\r
+                       }*/\r
+                       ExportPdfWriter.this.addAttachment(content);\r
+               }\r
+               \r
+               public void close() throws ExportException {\r
+                       try {\r
+                               if ( document != null ) { document.close(); document = null; }\r
+                               if ( pdfWriter != null ) { pdfWriter.close(); pdfWriter = null; }\r
+                               if ( fos != null ) { fos.close(); fos = null; }\r
+                               if ( cb != null ) { cb = null; }\r
+                       } catch (IOException e) {\r
+                               throw new ExportException(e);\r
+                       }\r
+               }\r
+               \r
+       }\r
+       \r
+       public class Template {\r
+\r
+               /** Suggested Page Desc */\r
+               public PageDesc pageDesc;\r
+               \r
+               /** PageDesc as PDF rectangle */\r
+               public Rectangle rectangle;\r
+               \r
+               /** Template name */\r
+               public String name;\r
+               \r
+               /** PdfTemplate */\r
+               public PdfTemplate tp;\r
+\r
+               Template(String name, PageDesc pd, Rectangle rect, PdfTemplate tp) {\r
+                       this.pageDesc = pd;\r
+                       this.rectangle = rect;\r
+                       this.name = name;\r
+                       this.tp = tp;\r
+               }\r
+               \r
+               public Graphics2D createGraphics() {                    \r
+            double w = pageDesc.getWidth();\r
+            double h = pageDesc.getHeight();\r
+                       return tp.createGraphics((float) w, (float) h, fontMapper);\r
+               }\r
+               \r
+       }\r
+       \r
+    public static Rectangle toRectangle(PageDesc pageDesc) {\r
+        String arg = PageDesc.toPoints(pageDesc.getWidth()) + " " + PageDesc.toPoints(pageDesc.getHeight());\r
+        Rectangle r = PageSize.getRectangle(arg);\r
+\r
+        if (PageOrientation.Landscape == pageDesc.getOrientation())\r
+            r = r.rotate();\r
+\r
+        // Disable inherent borders from the PDF writer.\r
+        r.setBorder(0);\r
+\r
+        return r;\r
+    }\r
+       \r
+}\r