]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/DiagramPrinter.java
Improved usability of shared library export wizard
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / pdf / DiagramPrinter.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2017 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *     Semantum Oy - (#7084) refactoring, page numbering support
12  *******************************************************************************/
13 package org.simantics.modeling.ui.pdf;
14
15 import java.io.FileNotFoundException;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.nio.file.Path;
19 import java.nio.file.Paths;
20 import java.security.Security;
21 import java.util.Collection;
22
23 import org.eclipse.core.runtime.IProduct;
24 import org.eclipse.core.runtime.IProgressMonitor;
25 import org.eclipse.core.runtime.OperationCanceledException;
26 import org.eclipse.core.runtime.Platform;
27 import org.eclipse.core.runtime.SubMonitor;
28 import org.simantics.db.RequestProcessor;
29 import org.simantics.db.Resource;
30 import org.simantics.db.Session;
31 import org.simantics.db.exception.DatabaseException;
32 import org.simantics.db.layer0.util.SessionGarbageCollection;
33 import org.simantics.document.DocumentSettings;
34 import org.simantics.document.DocumentUtils;
35 import org.simantics.export.core.pdf.FontMapping;
36 import org.simantics.export.core.pdf.PageNumbering;
37 import org.simantics.export.core.pdf.ServiceBasedPdfExportPageEvent;
38 import org.simantics.modeling.requests.CollectionRequest;
39 import org.simantics.modeling.requests.CollectionResult;
40 import org.simantics.modeling.requests.Node;
41 import org.simantics.modeling.ui.preferences.DiagramPreferenceUtil;
42 import org.simantics.ui.jobs.SessionGarbageCollectorJob;
43 import org.simantics.utils.page.PageDesc;
44 import org.simantics.utils.page.PageOrientation;
45 import org.simantics.utils.threads.WorkerThread;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import com.kitfox.svg.SVGCache;
50 import com.lowagie.text.Document;
51 import com.lowagie.text.DocumentException;
52 import com.lowagie.text.ExceptionConverter;
53 import com.lowagie.text.PageSize;
54 import com.lowagie.text.Rectangle;
55 import com.lowagie.text.pdf.FontMapper;
56 import com.lowagie.text.pdf.PdfBoolean;
57 import com.lowagie.text.pdf.PdfContentByte;
58 import com.lowagie.text.pdf.PdfName;
59 import com.lowagie.text.pdf.PdfTemplate;
60 import com.lowagie.text.pdf.PdfWriter;
61
62 /**
63  * @author Tuukka Lehtonen
64  */
65 public class DiagramPrinter {
66
67     private static final Logger LOGGER = LoggerFactory.getLogger(DiagramPrinter.class);
68
69     public static CollectionResult browse(IProgressMonitor monitor, RequestProcessor processor, Resource[] input) throws DatabaseException {
70         final CollectionResult result = processor.syncRequest(new CollectionRequest(monitor, DiagramPreferenceUtil.getDefaultPreferences().getCompletePageDesc(), input));
71         return result;
72     }
73
74     /**
75      * @param monitor the progress monitor to use for reporting progress to the
76      *        user. It is the caller's responsibility to call done() on the
77      *        given monitor. Accepts <code>null</code>, indicating that no
78      *        progress should be reported and that the operation cannot be
79      *        cancelled.
80      * 
81      * @param exportPath
82      * @param flattenedNodes
83      * @param sessionContext
84      * @throws DocumentException
85      * @throws FileNotFoundException
86      */
87     public static void printToPdf(
88             IProgressMonitor monitor, 
89             PDFExportPlan exportPlan, 
90             String exportPath, 
91             Collection<Node> flattenedNodes) 
92                 throws PdfException
93     {
94         if (!exportPlan.addPageNumbers) {
95             printToPdfWithoutPageNumbers(monitor, exportPlan, exportPath, flattenedNodes);
96         } else {
97             SubMonitor mon = SubMonitor.convert(monitor, "Export to PDF", flattenedNodes.size() * 3);
98
99             Path tempOutput = Paths.get(exportPath + ".tmp");
100             Path finalOutput = Paths.get(exportPath);
101
102             int exportedPages = printToPdfWithoutPageNumbers(
103                     mon.newChild(flattenedNodes.size() * 2, SubMonitor.SUPPRESS_NONE),
104                     exportPlan,
105                     tempOutput.toString(),
106                     flattenedNodes);
107
108             if (mon.isCanceled()) {
109                 tempOutput.toFile().delete();
110                 throw new OperationCanceledException();
111             }
112
113             try {
114                 mon.setWorkRemaining(exportedPages);
115                 mon.setTaskName("Numbering output pages");
116                 mon.subTask("");
117                 PageNumbering.addPageNumbers(
118                         mon.newChild(flattenedNodes.size()),
119                         tempOutput, finalOutput,
120                         exportPlan.pageNumberPosition,
121                         exportPlan.pageNumberFormat);
122             } catch (IOException | DocumentException | ExceptionConverter e) {
123                 throw new PdfException(e);
124             } finally {
125                 tempOutput.toFile().delete();
126             }
127         }
128     }
129
130     /**
131      * @param monitor the progress monitor to use for reporting progress to the
132      *        user. It is the caller's responsibility to call done() on the
133      *        given monitor. Accepts <code>null</code>, indicating that no
134      *        progress should be reported and that the operation cannot be
135      *        cancelled.
136      * 
137      * @param exportPath
138      * @param flattenedNodes
139      * @return number of pages printed
140      * @throws PdfException
141      * @since 1.28.0
142      */
143     public static int printToPdfWithoutPageNumbers(
144             IProgressMonitor monitor, 
145             PDFExportPlan exportPlan, 
146             String exportPath, 
147             Collection<Node> flattenedNodes) 
148                     throws PdfException
149     {
150         SubMonitor progress = SubMonitor.convert(monitor, "Export to PDF", flattenedNodes.size() * 2);
151
152         WorkerThread workerThread = new WorkerThread("Diagram PDF Painter");
153         workerThread.start();
154
155         PdfWriter writer = null;
156         Document document = null;
157         int exportedPages = 0;
158
159         try {
160             progress.subTask("Loading system fonts");
161             FontMapper mapper = FontMapping.defaultFontMapper();
162
163             SessionGarbageCollectorJob.getInstance().setEnabled(false);
164
165             boolean first = true;
166             int i = 0;
167             for (Node d : flattenedNodes) {
168                 ++i;
169
170                 //System.out.println("PAGE DESC: " + d.getPageDesc());
171                 //System.out.println("PAGE SIZE: " + pageSize);
172
173                 Rectangle pageSize = toPageSize(d.getPageDesc());
174                 if (writer == null) {
175                     document = new Document(pageSize);
176                     writer = PdfWriter.getInstance(document, new FileOutputStream(exportPath));
177                     writer.setPdfVersion(PdfWriter.PDF_VERSION_1_7);
178                     writer.setPageEvent(new ServiceBasedPdfExportPageEvent());
179                     if ( exportPlan.attachTG ) {
180                         writer.addViewerPreference(PdfName.USEATTACHMENTS, PdfBoolean.PDFTRUE);
181                     }
182    
183                     String creator = getCreator();
184                     document.addCreator(creator);
185
186                     /*
187                     File keystoreFile = new File("c:\\0009278.p12");
188                     String password = "ka7GfzI9Oq";
189
190                     try {
191                         KeyStore ks = KeyStore.getInstance("pkcs12");
192                         ks.load(new FileInputStream(keystoreFile), password.toCharArray());
193                         List<String> aliases = Collections.list(ks.aliases());
194                         String alias = aliases.get(0);
195                         PrivateKey key = (PrivateKey)ks.getKey(alias, password.toCharArray());
196                         Certificate[] chain = ks.getCertificateChain(alias);
197                         int permission = PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING|PdfWriter.ALLOW_COPY|PdfWriter.ALLOW_ASSEMBLY;
198
199                         PdfEncryption crypto = new PdfEncryption();
200                         //for (Certificate c : chain) crypto.addRecipient(c, permission);
201                         //crypto.addRecipient(chain[2], permission);
202                         crypto.setCryptoMode(PdfWriter.ENCRYPTION_AES_128, 0);
203                         crypto.setupByEncryptionKey(key.getEncoded(), key.getEncoded().length*8);
204                         crypto.getEncryptionDictionary();
205
206                     } catch (Exception e) {
207                         e.printStackTrace();
208                     }*/
209
210                     /*
211                     writer.setEncryption(
212                             new Certificate[] {}, 
213                             new int[] {PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING}, 
214                             PdfWriter.STANDARD_ENCRYPTION_128);
215                             */
216                     //writer.setEncryption(PdfWriter.STANDARD_ENCRYPTION_128, "", "password", PdfWriter.ALLOW_FILL_IN|PdfWriter.ALLOW_PRINTING|PdfWriter.ALLOW_COPY|PdfWriter.ALLOW_ASSEMBLY);
217
218 //                    PdfName companyName = new PdfName("SMTC");
219 //                    PdfDeveloperExtension ext = new PdfDeveloperExtension(companyName, PdfWriter.PDF_VERSION_1_7, 3);
220 //                    writer.addDeveloperExtension( ext );
221
222                     document.open();
223                 }
224
225                 if (!first) {
226                     document.setPageSize(pageSize);
227                     document.newPage();
228                 }
229
230                 /*
231                 /// ATTACHMENTS - TG ///
232                 byte[] attachment = null;
233                 if ( exportPlan.attachTG && !d.getDefiningResources().isEmpty() ) 
234                 try {
235                     PdfDictionary fileParameter = new PdfDictionary();
236
237                     {
238                         final Resource composite = d.getDefiningResources().iterator().next();
239                         final Session session = exportPlan.sessionContext.getSession();
240
241                         SimanticsClipboard clipboard = session.syncRequest(new Read<SimanticsClipboard>() {
242                             @Override
243                             public SimanticsClipboard perform(ReadGraph graph) throws DatabaseException {
244                                 CopyHandler ch = graph.adapt(composite, CopyHandler.class);
245                                 SimanticsClipboardImpl clipboard = new SimanticsClipboardImpl();
246                                 ch.copyToClipboard(graph, clipboard);
247                                 return clipboard;
248                             }
249                         });
250                         for (Set<Representation> object : clipboard.getContents()) {
251                             TransferableGraph1 tg = ClipboardUtils.accept(object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH);
252                             String filename = d.getName()+".diagram";
253                             try {
254                                 byte[] data = DataContainers.writeFile(
255                                     new DataContainer("aprosDiagram", 1, new Variant(TransferableGraph1.BINDING, tg))
256                                 );
257                                 PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(
258                                         writer, 
259                                         "/Diagram", filename, data, true, "application/simantics/diagram", 
260                                         fileParameter);
261                                 writer.addFileAttachment(d.getName()+".diagram", fs);
262                             } catch ( NullPointerException npe ) {
263                                 throw new PdfException("Experiment must be activated to export attachments"+npe.getMessage(), npe);
264                             }
265                         }
266                     }
267
268                 } catch (DatabaseException e) {
269                     e.printStackTrace();
270                 } catch (IOException e) {
271                     e.printStackTrace();
272                 }
273                 */
274                 //////////////////////////
275
276                 String diagramName = formDiagramName(d, true);
277                 String subTask = "Page (" + i + "/" + flattenedNodes.size() + "): " + diagramName;
278
279                 Resource diagram = d.getDiagramResource();
280                 if (diagram == null) {
281                     // No diagram, skip page.
282                     subTask += " skipped, no diagram.";
283                     LOGGER.info(subTask);
284                     continue;
285                 }
286
287                 LOGGER.info(subTask);
288                 progress.subTask(subTask);
289
290                 try {
291                     PDFPainter.render(workerThread, exportPlan, d, writer, mapper,
292                             pageSize, d.getPageDesc(), exportPlan.fitContentToPageMargins, 10000);
293                     ++exportedPages;
294                 } catch (DatabaseException | InterruptedException e) {
295                     LOGGER.error("PDF rendering failed.", e);
296                 }
297
298                 /// ATTACHMENTS - Write WIKI ///
299                 if ( exportPlan.attachWiki && !d.getDefiningResources().isEmpty() ) {
300                     int w = (int) pageSize.getWidth();
301                     int h = (int) pageSize.getHeight();
302                     PdfContentByte cb = writer.getDirectContent();
303                     Session session = exportPlan.sessionContext.getSession();
304                     Resource composite = d.getDefiningResources().iterator().next();
305                     DocumentUtils du = new DocumentUtils();
306                     StringBuilder wiki = new StringBuilder();
307                     StringBuilder css = new StringBuilder();
308                     du.getDocumentWikiTextRecursive(session, composite, wiki, css);
309                     DocumentSettings settings = du.getDocumentSettings(session, composite);
310                     PdfTemplate tp_ = cb.createTemplate(w, h);
311                     if ( wiki.length()>0 ) {
312                         String wikiText = wiki.toString();
313                         String cssText = css.toString();
314                         try {
315                             exportedPages += du.print(session, composite, wikiText, cssText, settings, tp_.getPdfWriter(), document);
316                         } catch (DatabaseException | DocumentException e) {
317                             LOGGER.error("Wiki documentation to PDF rendering failed.", e);
318                         }
319                     }
320                     cb.addTemplate(tp_, 0, 0);
321                 }
322                 //////////////////////////
323
324                 progress.worked(1);
325                 first = false;
326
327                 if (progress.isCanceled())
328                     throw new OperationCanceledException();
329
330                 LOGGER.trace("GC");
331                 SVGCache.getSVGUniverse().clearUnreferenced();
332                 SessionGarbageCollection.gc(null, exportPlan.sessionContext.getSession(), true, null);
333                 System.gc();
334                 LOGGER.trace("GC finished");
335                 progress.worked(1);
336             }
337
338             return exportedPages;
339         } catch (DatabaseException | FileNotFoundException | DocumentException | ExceptionConverter e) {
340             throw new PdfException(e);
341         } finally {
342             workerThread.stopDispatchingEvents(true);
343             LOGGER.trace("closing document");
344             try {
345                 if ( document != null ) document.close();
346                 if ( writer != null ) writer.close();
347                 LOGGER.trace("document closed");
348             } catch (RuntimeException e) {
349                 LOGGER.error("Error closing PDF document writer", e);
350             }
351             SessionGarbageCollectorJob.getInstance().setEnabled(true).scheduleAfterQuietTime();
352         }
353     }
354
355     public static Rectangle toPageSize(PageDesc pageDesc) {
356         String arg = PageDesc.toPoints(pageDesc.getWidth()) + " " + PageDesc.toPoints(pageDesc.getHeight());
357         Rectangle r = PageSize.getRectangle(arg);
358
359         if (PageOrientation.Landscape == pageDesc.getOrientation())
360             r = r.rotate();
361
362         // Disable inherent borders from the PDF writer.
363         r.setBorder(0);
364
365         return r;
366     }
367
368     public static String formDiagramName(Node node, boolean parents) {
369         Node d = node;
370         String ret = d.getName();
371         if (parents) {
372             while (d.getParent() != null) {
373                 d = d.getParent();
374                 ret = d.getName() + " / " + ret;
375             }
376         }
377 //        String[] pg = node.getPartOfGroups();
378 //        if (pg.length > 0)
379 //            ret += " [" + EString.implode(pg, " / ") + " / " + node.getName() + "]";
380         return ret;
381     }
382
383     public static String getCreator() {
384         String creator = null;
385         IProduct product = Platform.getProduct();
386         if (product != null) {
387             creator = product.getDescription();
388             if (creator == null) {
389                 creator = product.getName();
390             }
391         }
392         if (creator == null) {
393             creator = "Simantics";
394         }
395         return creator;
396     }
397
398     static {
399         Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
400     }
401
402 }