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