/*******************************************************************************
* 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
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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 org.eclipse.core.runtime.IProduct;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubMonitor;
import org.simantics.db.RequestProcessor;
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.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;
import org.simantics.modeling.requests.Node;
import org.simantics.modeling.ui.preferences.DiagramPreferenceUtil;
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.ExceptionConverter;
import com.lowagie.text.PageSize;
import com.lowagie.text.Rectangle;
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;
}
/**
* @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 null
, indicating that no
* progress should be reported and that the operation cannot be
* cancelled.
*
* @param exportPath
* @param flattenedNodes
* @param sessionContext
* @throws DocumentException
* @throws FileNotFoundException
*/
public static void printToPdf(
IProgressMonitor monitor,
PDFExportPlan exportPlan,
String exportPath,
Collection 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();
}
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 null
, 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 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");
FontMapper mapper = FontMapping.defaultFontMapper();
SessionGarbageCollectorJob.getInstance().setEnabled(false);
boolean first = true;
int i = 0;
for (Node d : flattenedNodes) {
++i;
//System.out.println("PAGE DESC: " + d.getPageDesc());
//System.out.println("PAGE SIZE: " + pageSize);
Rectangle pageSize = toPageSize(d.getPageDesc());
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);
}
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 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();
}
/*
/// ATTACHMENTS - TG ///
byte[] attachment = null;
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() {
@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 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();
} catch (IOException e) {
e.printStackTrace();
}
*/
//////////////////////////
String diagramName = formDiagramName(d, true);
String subTask = "Page (" + i + "/" + flattenedNodes.size() + "): " + diagramName;
Resource diagram = d.getDiagramResource();
if (diagram == null) {
// No diagram, skip page.
subTask += " skipped, no diagram.";
LOGGER.info(subTask);
continue;
}
LOGGER.info(subTask);
progress.subTask(subTask);
try {
PDFPainter.render(workerThread, exportPlan, d, writer, mapper,
pageSize, d.getPageDesc(), exportPlan.fitContentToPageMargins, 10000);
++exportedPages;
} catch (DatabaseException | InterruptedException e) {
LOGGER.error("PDF rendering failed.", e);
}
/// ATTACHMENTS - Write WIKI ///
if ( exportPlan.attachWiki && !d.getDefiningResources().isEmpty() ) {
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);
PdfTemplate tp_ = cb.createTemplate(w, h);
if ( wiki.length()>0 ) {
String wikiText = wiki.toString();
String cssText = css.toString();
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);
}
//////////////////////////
progress.worked(1);
first = false;
if (progress.isCanceled())
throw new OperationCanceledException();
LOGGER.trace("GC");
SVGCache.getSVGUniverse().clearUnreferenced();
SessionGarbageCollection.gc(null, exportPlan.sessionContext.getSession(), true, null);
System.gc();
LOGGER.trace("GC finished");
progress.worked(1);
}
return exportedPages;
} catch (DatabaseException | FileNotFoundException | DocumentException | ExceptionConverter e) {
throw new PdfException(e);
} finally {
workerThread.stopDispatchingEvents(true);
LOGGER.trace("closing document");
try {
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);
}
SessionGarbageCollectorJob.getInstance().setEnabled(true).scheduleAfterQuietTime();
}
}
public static Rectangle toPageSize(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;
}
public static String formDiagramName(Node node, boolean parents) {
Node d = node;
String ret = d.getName();
if (parents) {
while (d.getParent() != null) {
d = d.getParent();
ret = d.getName() + " / " + ret;
}
}
// String[] pg = node.getPartOfGroups();
// if (pg.length > 0)
// ret += " [" + EString.implode(pg, " / ") + " / " + node.getName() + "]";
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;
}
static {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
}