]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/DiagramPrinter.java
Fonts are now embedded in diagram, wiki, etc PDF exports.
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / pdf / DiagramPrinter.java
index e3101dce2e31ea8d2e200d9f96f56c692e6e463f..7f0559348d5b67d227512b37c433b5b407936041 100644 (file)
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * Copyright (c) 2007, 2017 Association for Decentralized Information Management
  * in Industry THTH ry.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -8,14 +8,17 @@
  *
  * Contributors:
  *     VTT Technical Research Centre of Finland - initial API and implementation
+ *     Semantum Oy - (#7084) refactoring, page numbering support
  *******************************************************************************/
 package org.simantics.modeling.ui.pdf;
 
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.Security;
 import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.core.runtime.IProduct;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -27,9 +30,10 @@ import org.simantics.db.Resource;
 import org.simantics.db.Session;
 import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.layer0.util.SessionGarbageCollection;
-import org.simantics.db.management.ISessionContext;
 import org.simantics.document.DocumentSettings;
 import org.simantics.document.DocumentUtils;
+import org.simantics.export.core.pdf.FontMapping;
+import org.simantics.export.core.pdf.PageNumbering;
 import org.simantics.export.core.pdf.ServiceBasedPdfExportPageEvent;
 import org.simantics.modeling.requests.CollectionRequest;
 import org.simantics.modeling.requests.CollectionResult;
@@ -39,29 +43,34 @@ import org.simantics.ui.jobs.SessionGarbageCollectorJob;
 import org.simantics.utils.page.PageDesc;
 import org.simantics.utils.page.PageOrientation;
 import org.simantics.utils.threads.WorkerThread;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.kitfox.svg.SVGCache;
 import com.lowagie.text.Document;
 import com.lowagie.text.DocumentException;
-import com.lowagie.text.FontFactory;
+import com.lowagie.text.ExceptionConverter;
 import com.lowagie.text.PageSize;
 import com.lowagie.text.Rectangle;
-import com.lowagie.text.pdf.DefaultFontMapper;
+import com.lowagie.text.pdf.FontMapper;
 import com.lowagie.text.pdf.PdfBoolean;
 import com.lowagie.text.pdf.PdfContentByte;
 import com.lowagie.text.pdf.PdfName;
 import com.lowagie.text.pdf.PdfTemplate;
 import com.lowagie.text.pdf.PdfWriter;
 
+/**
+ * @author Tuukka Lehtonen
+ */
 public class DiagramPrinter {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(DiagramPrinter.class);
+
     public static CollectionResult browse(IProgressMonitor monitor, RequestProcessor processor, Resource[] input) throws DatabaseException {
         final CollectionResult result = processor.syncRequest(new CollectionRequest(monitor, DiagramPreferenceUtil.getDefaultPreferences().getCompletePageDesc(), input));
         return result;
     }
 
-    private static final AtomicBoolean fontFactoryInitialized = new AtomicBoolean();
-
     /**
      * @param monitor the progress monitor to use for reporting progress to the
      *        user. It is the caller's responsibility to call done() on the
@@ -76,38 +85,88 @@ public class DiagramPrinter {
      * @throws FileNotFoundException
      */
     public static void printToPdf(
-               IProgressMonitor monitor, 
-               PDFExportPlan exportPlan, 
-               String exportPath, 
-               Collection<Node> flattenedNodes,
-            ISessionContext sessionContext) 
-    throws PdfException {
-        Collection<Node> flattened = flattenedNodes;
+            IProgressMonitor monitor, 
+            PDFExportPlan exportPlan, 
+            String exportPath, 
+            Collection<Node> flattenedNodes) 
+                throws PdfException
+    {
+        if (!exportPlan.addPageNumbers) {
+            printToPdfWithoutPageNumbers(monitor, exportPlan, exportPath, flattenedNodes);
+        } else {
+            SubMonitor mon = SubMonitor.convert(monitor, "Export to PDF", flattenedNodes.size() * 3);
+
+            Path tempOutput = Paths.get(exportPath + ".tmp");
+            Path finalOutput = Paths.get(exportPath);
+
+            int exportedPages = printToPdfWithoutPageNumbers(
+                    mon.newChild(flattenedNodes.size() * 2, SubMonitor.SUPPRESS_NONE),
+                    exportPlan,
+                    tempOutput.toString(),
+                    flattenedNodes);
+
+            if (mon.isCanceled()) {
+                tempOutput.toFile().delete();
+                throw new OperationCanceledException();
+            }
 
-        SubMonitor progress = SubMonitor.convert(monitor, "Export to PDF", flattened.size() * 2);
+            try {
+                mon.setWorkRemaining(exportedPages);
+                mon.setTaskName("Numbering output pages");
+                mon.subTask("");
+                PageNumbering.addPageNumbers(
+                        mon.newChild(flattenedNodes.size()),
+                        tempOutput, finalOutput,
+                        exportPlan.pageNumberPosition,
+                        exportPlan.pageNumberFormat);
+            } catch (IOException | DocumentException | ExceptionConverter e) {
+                throw new PdfException(e);
+            } finally {
+                tempOutput.toFile().delete();
+            }
+        }
+    }
+
+    /**
+     * @param monitor the progress monitor to use for reporting progress to the
+     *        user. It is the caller's responsibility to call done() on the
+     *        given monitor. Accepts <code>null</code>, indicating that no
+     *        progress should be reported and that the operation cannot be
+     *        cancelled.
+     * 
+     * @param exportPath
+     * @param flattenedNodes
+     * @return number of pages printed
+     * @throws PdfException
+     * @since 1.28.0
+     */
+    public static int printToPdfWithoutPageNumbers(
+            IProgressMonitor monitor, 
+            PDFExportPlan exportPlan, 
+            String exportPath, 
+            Collection<Node> flattenedNodes) 
+                    throws PdfException
+    {
+        SubMonitor progress = SubMonitor.convert(monitor, "Export to PDF", flattenedNodes.size() * 2);
 
         WorkerThread workerThread = new WorkerThread("Diagram PDF Painter");
         workerThread.start();
 
         PdfWriter writer = null;
         Document document = null;
+        int exportedPages = 0;
 
         try {
             progress.subTask("Loading system fonts");
-            DefaultFontMapper mapper = new DefaultFontMapper();
-            if (fontFactoryInitialized.compareAndSet(false, true)) {
-                // Only register directories once.
-                FontFactory.registerDirectories();
-            }
+            FontMapper mapper = FontMapping.defaultFontMapper();
 
             SessionGarbageCollectorJob.getInstance().setEnabled(false);
 
-
-            boolean first = true;           
+            boolean first = true;
             int i = 0;
-            for (Node d : flattened) {
+            for (Node d : flattenedNodes) {
                 ++i;
-                
+
                 //System.out.println("PAGE DESC: " + d.getPageDesc());
                 //System.out.println("PAGE SIZE: " + pageSize);
 
@@ -115,98 +174,96 @@ public class DiagramPrinter {
                 if (writer == null) {
                     document = new Document(pageSize);
                     writer = PdfWriter.getInstance(document, new FileOutputStream(exportPath));
-                   writer.setPdfVersion(PdfWriter.PDF_VERSION_1_7);
-                   writer.setPageEvent(new ServiceBasedPdfExportPageEvent());
-                   if ( exportPlan.attachTG ) {
-                       writer.addViewerPreference(PdfName.USEATTACHMENTS, PdfBoolean.PDFTRUE);
-                   }
+                    writer.setPdfVersion(PdfWriter.PDF_VERSION_1_7);
+                    writer.setPageEvent(new ServiceBasedPdfExportPageEvent());
+                    if ( exportPlan.attachTG ) {
+                        writer.addViewerPreference(PdfName.USEATTACHMENTS, PdfBoolean.PDFTRUE);
+                    }
    
-                   String creator = getCreator();
-                   document.addCreator(creator);
-
-                   /*
-                       File keystoreFile = new File("c:\\0009278.p12");
-                       String password = "ka7GfzI9Oq";
-
-                       try {
-                               KeyStore ks = KeyStore.getInstance("pkcs12");
-                               ks.load(new FileInputStream(keystoreFile), password.toCharArray());
-                               List<String> aliases = Collections.list(ks.aliases());
-                               String alias = aliases.get(0);
-                               PrivateKey key = (PrivateKey)ks.getKey(alias, password.toCharArray());
-                               Certificate[] chain = ks.getCertificateChain(alias);
-                               int permission = PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING|PdfWriter.ALLOW_COPY|PdfWriter.ALLOW_ASSEMBLY;
-                               
-                               PdfEncryption crypto = new PdfEncryption();
-                               //for (Certificate c : chain) crypto.addRecipient(c, permission);
-                               //crypto.addRecipient(chain[2], permission);
-                       crypto.setCryptoMode(PdfWriter.ENCRYPTION_AES_128, 0);
-                               crypto.setupByEncryptionKey(key.getEncoded(), key.getEncoded().length*8);
-                       crypto.getEncryptionDictionary();
-                       
-                               
-                       } catch (Exception e) {
-                               e.printStackTrace();
-                       }*/
-                   
-                   /*
-                   writer.setEncryption(
-                               new Certificate[] {}, 
-                               new int[] {PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING}, 
-                               PdfWriter.STANDARD_ENCRYPTION_128);
-                               */
-                   //writer.setEncryption(PdfWriter.STANDARD_ENCRYPTION_128, "", "password", PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING|PdfWriter.ALLOW_COPY|PdfWriter.ALLOW_ASSEMBLY);
-                       
-                   
-//                     PdfName companyName = new PdfName("SMTC");          
-//                 PdfDeveloperExtension ext = new PdfDeveloperExtension(companyName, PdfWriter.PDF_VERSION_1_7, 3);
-//                 writer.addDeveloperExtension( ext );
-                   
+                    String creator = getCreator();
+                    document.addCreator(creator);
+
+                    /*
+                    File keystoreFile = new File("c:\\0009278.p12");
+                    String password = "ka7GfzI9Oq";
+
+                    try {
+                        KeyStore ks = KeyStore.getInstance("pkcs12");
+                        ks.load(new FileInputStream(keystoreFile), password.toCharArray());
+                        List<String> aliases = Collections.list(ks.aliases());
+                        String alias = aliases.get(0);
+                        PrivateKey key = (PrivateKey)ks.getKey(alias, password.toCharArray());
+                        Certificate[] chain = ks.getCertificateChain(alias);
+                        int permission = PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING|PdfWriter.ALLOW_COPY|PdfWriter.ALLOW_ASSEMBLY;
+
+                        PdfEncryption crypto = new PdfEncryption();
+                        //for (Certificate c : chain) crypto.addRecipient(c, permission);
+                        //crypto.addRecipient(chain[2], permission);
+                        crypto.setCryptoMode(PdfWriter.ENCRYPTION_AES_128, 0);
+                        crypto.setupByEncryptionKey(key.getEncoded(), key.getEncoded().length*8);
+                        crypto.getEncryptionDictionary();
+
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }*/
+
+                    /*
+                    writer.setEncryption(
+                            new Certificate[] {}, 
+                            new int[] {PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING}, 
+                            PdfWriter.STANDARD_ENCRYPTION_128);
+                            */
+                    //writer.setEncryption(PdfWriter.STANDARD_ENCRYPTION_128, "", "password", PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING|PdfWriter.ALLOW_COPY|PdfWriter.ALLOW_ASSEMBLY);
+
+//                    PdfName companyName = new PdfName("SMTC");
+//                    PdfDeveloperExtension ext = new PdfDeveloperExtension(companyName, PdfWriter.PDF_VERSION_1_7, 3);
+//                    writer.addDeveloperExtension( ext );
+
                     document.open();
                 }
 
                 if (!first) {
-                document.setPageSize(pageSize);
-                document.newPage();
+                    document.setPageSize(pageSize);
+                    document.newPage();
                 }
 
                 /*
                 /// ATTACHMENTS - TG ///
                 byte[] attachment = null;
-                       if ( exportPlan.attachTG && !d.getDefiningResources().isEmpty() ) 
+                if ( exportPlan.attachTG && !d.getDefiningResources().isEmpty() ) 
                 try {
                     PdfDictionary fileParameter = new PdfDictionary();
 
-                       {
-                               final Resource composite = d.getDefiningResources().iterator().next();                                          
-                               final Session session = exportPlan.sessionContext.getSession();
-                               
-                           SimanticsClipboard clipboard = session.syncRequest(new Read<SimanticsClipboard>() {
-                               @Override
-                               public SimanticsClipboard perform(ReadGraph graph) throws DatabaseException {
-                                   CopyHandler ch = graph.adapt(composite, CopyHandler.class);
-                                   SimanticsClipboardImpl clipboard = new SimanticsClipboardImpl();
-                                   ch.copyToClipboard(graph, clipboard);
-                                   return clipboard;
-                               }
-                           });
-                           for (Set<Representation> object : clipboard.getContents()) {
-                               TransferableGraph1 tg = ClipboardUtils.accept(object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH);
-                               String filename = d.getName()+".diagram";
-                               try {
-                                       byte[] data = DataContainers.writeFile(                                 
-                                               new DataContainer("aprosDiagram", 1, new Variant(TransferableGraph1.BINDING, tg))
-                                       );
-                                           PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(
-                                                       writer, 
-                                                       "/Diagram", filename, data, true, "application/simantics/diagram", 
-                                                       fileParameter);
-                                           writer.addFileAttachment(d.getName()+".diagram", fs);
-                               } catch ( NullPointerException npe ) {
-                                       throw new PdfException("Experiment must be activated to export attachments"+npe.getMessage(), npe);
-                               }
-                           }
-                       }
+                    {
+                        final Resource composite = d.getDefiningResources().iterator().next();
+                        final Session session = exportPlan.sessionContext.getSession();
+
+                        SimanticsClipboard clipboard = session.syncRequest(new Read<SimanticsClipboard>() {
+                            @Override
+                            public SimanticsClipboard perform(ReadGraph graph) throws DatabaseException {
+                                CopyHandler ch = graph.adapt(composite, CopyHandler.class);
+                                SimanticsClipboardImpl clipboard = new SimanticsClipboardImpl();
+                                ch.copyToClipboard(graph, clipboard);
+                                return clipboard;
+                            }
+                        });
+                        for (Set<Representation> object : clipboard.getContents()) {
+                            TransferableGraph1 tg = ClipboardUtils.accept(object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH);
+                            String filename = d.getName()+".diagram";
+                            try {
+                                byte[] data = DataContainers.writeFile(
+                                    new DataContainer("aprosDiagram", 1, new Variant(TransferableGraph1.BINDING, tg))
+                                );
+                                PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(
+                                        writer, 
+                                        "/Diagram", filename, data, true, "application/simantics/diagram", 
+                                        fileParameter);
+                                writer.addFileAttachment(d.getName()+".diagram", fs);
+                            } catch ( NullPointerException npe ) {
+                                throw new PdfException("Experiment must be activated to export attachments"+npe.getMessage(), npe);
+                            }
+                        }
+                    }
 
                 } catch (DatabaseException e) {
                     e.printStackTrace();
@@ -215,66 +272,50 @@ public class DiagramPrinter {
                 }
                 */
                 //////////////////////////
-                
+
                 String diagramName = formDiagramName(d, true);
-                String subTask = "Page (" + i + "/" + flattened.size() + "): " + diagramName;
+                String subTask = "Page (" + i + "/" + flattenedNodes.size() + "): " + diagramName;
 
                 Resource diagram = d.getDiagramResource();
                 if (diagram == null) {
                     // No diagram, skip page.
                     subTask += " skipped, no diagram.";
-                    System.out.println(subTask);
+                    LOGGER.info(subTask);
                     continue;
                 }
 
-                System.out.println(subTask);
+                LOGGER.info(subTask);
                 progress.subTask(subTask);
 
                 try {
-                    PDFPainter.render(workerThread, sessionContext, exportPlan, d, writer, mapper,
+                    PDFPainter.render(workerThread, exportPlan, d, writer, mapper,
                             pageSize, d.getPageDesc(), exportPlan.fitContentToPageMargins, 10000);
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                } catch (DatabaseException e) {
-                    e.printStackTrace();
+                    ++exportedPages;
+                } catch (DatabaseException | InterruptedException e) {
+                    LOGGER.error("PDF rendering failed.", e);
                 }
 
-                // Paint diagram path/name on the page
-                // TODO: remove this hard coded diagram name printing and
-                // replace it with a page templates that is loaded along with
-                // the rest of the diagram
-
-                int w = (int) pageSize.getWidth();
-                int h = (int) pageSize.getHeight();
-
-                // Write Page Number
-                PdfContentByte cb = writer.getDirectContent();
-
-//                PdfTemplate tp = cb.createTemplate(w, h);
-//                Graphics2D g2d = tp.createGraphics(w, h, mapper);
-//                g2d.setColor(Color.black);
-//                java.awt.Font thisFont = new java.awt.Font("Arial", java.awt.Font.ITALIC, 10);
-//                g2d.setFont(thisFont);
-//                FontMetrics metrics = g2d.getFontMetrics();
-//                int width = metrics.stringWidth(diagramName);
-//                g2d.drawString(diagramName, (w - width) / 2, document.getPageSize().getHeight() - PageDesc.toPoints(5));
-//                g2d.dispose();
-//                cb.addTemplate(tp, 0, 0);
-
                 /// ATTACHMENTS - Write WIKI ///
                 if ( exportPlan.attachWiki && !d.getDefiningResources().isEmpty() ) {
-                    final Session session = exportPlan.sessionContext.getSession();
+                    int w = (int) pageSize.getWidth();
+                    int h = (int) pageSize.getHeight();
+                    PdfContentByte cb = writer.getDirectContent();
+                    Session session = exportPlan.sessionContext.getSession();
                     Resource composite = d.getDefiningResources().iterator().next();
                     DocumentUtils du = new DocumentUtils();
                     StringBuilder wiki = new StringBuilder();
                     StringBuilder css = new StringBuilder();
                     du.getDocumentWikiTextRecursive(session, composite, wiki, css);
-                       DocumentSettings settings = du.getDocumentSettings(session, composite);
+                    DocumentSettings settings = du.getDocumentSettings(session, composite);
                     PdfTemplate tp_ = cb.createTemplate(w, h);
                     if ( wiki.length()>0 ) {
                         String wikiText = wiki.toString();
                         String cssText = css.toString();
-                        du.print(session, composite, wikiText, cssText, settings, tp_.getPdfWriter(), document);
+                        try {
+                            exportedPages += du.print(session, composite, wikiText, cssText, settings, tp_.getPdfWriter(), document);
+                        } catch (DatabaseException | DocumentException e) {
+                            LOGGER.error("Wiki documentation to PDF rendering failed.", e);
+                        }
                     }
                     cb.addTemplate(tp_, 0, 0);
                 }
@@ -286,29 +327,27 @@ public class DiagramPrinter {
                 if (progress.isCanceled())
                     throw new OperationCanceledException();
 
-                System.out.println("GC");
+                LOGGER.trace("GC");
                 SVGCache.getSVGUniverse().clearUnreferenced();
-                SessionGarbageCollection.gc(null, sessionContext.getSession(), true, null);
+                SessionGarbageCollection.gc(null, exportPlan.sessionContext.getSession(), true, null);
                 System.gc();
-                System.out.println("GC finished");
+                LOGGER.trace("GC finished");
                 progress.worked(1);
             }
-               } catch (DatabaseException e) {
-                       throw new PdfException(e);
-               } catch (FileNotFoundException e) {
-                       throw new PdfException(e);
-               } catch (DocumentException e) {
-                       throw new PdfException(e);
-               } finally {
+
+            return exportedPages;
+        } catch (DatabaseException | FileNotFoundException | DocumentException | ExceptionConverter e) {
+            throw new PdfException(e);
+        } finally {
             workerThread.stopDispatchingEvents(true);
-            System.out.println("closing document");
+            LOGGER.trace("closing document");
             try {
-               if ( document!=null ) document.close();
-                if ( writer!=null ) writer.close();
-            } catch(RuntimeException e) {
-               e.printStackTrace();
+                if ( document != null ) document.close();
+                if ( writer != null ) writer.close();
+                LOGGER.trace("document closed");
+            } catch (RuntimeException e) {
+                LOGGER.error("Error closing PDF document writer", e);
             }
-            System.out.println("document closed");
             SessionGarbageCollectorJob.getInstance().setEnabled(true).scheduleAfterQuietTime();
         }
     }
@@ -341,23 +380,23 @@ public class DiagramPrinter {
         return ret;
     }
 
-       public static String getCreator() {
-               String creator = null;
-               IProduct product = Platform.getProduct();
-               if (product != null) {
-                       creator = product.getDescription();
-                       if (creator == null) {
-                               creator = product.getName();
-                       }
-               }
-               if (creator == null) {
-                       creator = "Simantics";
-               }
-               return creator;
-       }
+    public static String getCreator() {
+        String creator = null;
+        IProduct product = Platform.getProduct();
+        if (product != null) {
+            creator = product.getDescription();
+            if (creator == null) {
+                creator = product.getName();
+            }
+        }
+        if (creator == null) {
+            creator = "Simantics";
+        }
+        return creator;
+    }
 
     static {
-               Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
     }
 
 }
\ No newline at end of file