From: Tuukka Lehtonen Date: Fri, 10 Mar 2017 16:27:03 +0000 (+0200) Subject: Fonts are now embedded in diagram, wiki, etc PDF exports. X-Git-Tag: v1.28.0~64 X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=9d4a145fef9bcec16e3d1f1477894cba6429b9c4;p=simantics%2Fplatform.git Fonts are now embedded in diagram, wiki, etc PDF exports. A lot of general cleanup/refactoring for the old "Diagrams to PDF" export wizard backend implementation. Consolidated some page numbering code into org.simantics.export.core.pdf.PageNumbering. Also consolidated iText FontMapper initialization into org.simantics.export.core.pdf.FontMapping. It turned out that the key to getting iText to embed fonts for text rendered into PDFs was that the FontMapper used needs to have all system fonts registered into it. Also fixed PDF export page number positioning in the more generic PDF export wizard. refs #7084 Change-Id: Ife4f96889659834a780566152f63e9e1ccd4979f --- diff --git a/bundles/org.simantics.document/src/org/simantics/document/DocumentUtils.java b/bundles/org.simantics.document/src/org/simantics/document/DocumentUtils.java index e539df2c3..16a81f6f3 100644 --- a/bundles/org.simantics.document/src/org/simantics/document/DocumentUtils.java +++ b/bundles/org.simantics.document/src/org/simantics/document/DocumentUtils.java @@ -28,7 +28,6 @@ import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.ResourceRead; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.request.WriteResultRequest; -import org.simantics.db.common.utils.Logger; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; @@ -194,22 +193,15 @@ public class DocumentUtils { } } - public void print(RequestProcessor processor, Resource res, String wiki, String css, DocumentSettings settings, final PdfWriter writer, final Document document) throws DocumentException { - try { - Exportable exp = processor.syncRequest(new UniqueRead() { - - @Override - public Exportable perform(ReadGraph graph) throws DatabaseException { - return new Exportable(graph, res, wiki, css, settings, true); - } - - }); - exp.export(document, writer); - } catch (DocumentException e) { - Logger.defaultLogError(e); - } catch (DatabaseException e) { - Logger.defaultLogError(e); - } + public int print(RequestProcessor processor, Resource res, String wiki, String css, DocumentSettings settings, final PdfWriter writer, final Document document) + throws DocumentException, DatabaseException { + Exportable exp = processor.syncRequest(new UniqueRead() { + @Override + public Exportable perform(ReadGraph graph) throws DatabaseException { + return new Exportable(graph, res, wiki, css, settings, true); + } + }); + return exp.export(document, writer); } public static String indexRootPath(ReadGraph graph, Variable selection) throws DatabaseException { diff --git a/bundles/org.simantics.document/src/org/simantics/document/Exportable.java b/bundles/org.simantics.document/src/org/simantics/document/Exportable.java index a1053dd83..a6dd790de 100644 --- a/bundles/org.simantics.document/src/org/simantics/document/Exportable.java +++ b/bundles/org.simantics.document/src/org/simantics/document/Exportable.java @@ -105,7 +105,7 @@ public class Exportable implements IExportable { } @Override - public void export(Document document, PdfWriter writer) throws DocumentException { + public int export(Document document, PdfWriter writer) throws DocumentException { File temp = Simantics.getTempfile("wikiPdfExport", "pdf"); try { @@ -113,6 +113,7 @@ public class Exportable implements IExportable { temp.getParentFile().mkdirs(); PhantomJSDriver.print(html, settings, temp); + int result = 0; PdfContentByte cb = writer.getDirectContent(); PdfReader reader = new PdfReader(new BufferedInputStream(new FileInputStream(temp))); for (int i = 1; i <= reader.getNumberOfPages(); i++) { @@ -123,8 +124,10 @@ public class Exportable implements IExportable { //add the page to the destination pdf float pts = Utilities.millimetersToPoints(10); cb.addTemplate(page, pts, pts); + ++result; } + return result; } catch (IOException e) { throw new DocumentException(e); diff --git a/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfFormat.java b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfFormat.java index 9711fbccf..9e80cd16f 100644 --- a/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfFormat.java +++ b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfFormat.java @@ -46,7 +46,6 @@ import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.FontFactory; import com.lowagie.text.Rectangle; -import com.lowagie.text.pdf.DefaultFontMapper; import com.lowagie.text.pdf.PdfBoolean; import com.lowagie.text.pdf.PdfCopy; import com.lowagie.text.pdf.PdfName; @@ -160,7 +159,7 @@ public class ExportPdfFormat implements FormatClass { writer.fos = new FileOutputStream(writer.outputFile); writer.pdfCopy = new PdfCopy(writer.document, writer.fos); writer.pdfCopy.setPdfVersion(PdfWriter.PDF_VERSION_1_7); - writer.fontMapper = new DefaultFontMapper(); + writer.fontMapper = FontMapping.defaultFontMapper(); writer.options = options; writer.ctx = ctx; diff --git a/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java index 204763b68..97488908e 100644 --- a/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java +++ b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/ExportPdfWriter.java @@ -317,10 +317,10 @@ public class ExportPdfWriter { try { int n = reader.getNumberOfPages(); - for (int i = 0; i < n; ) { - Rectangle pageSize = reader.getPageSizeWithRotation(n); + for (int i = 1; i <= n; ++i) { + Rectangle pageSize = reader.getPageSizeWithRotation(i); - PdfImportedPage imp = pdfCopy.getImportedPage(reader, ++i); + PdfImportedPage imp = pdfCopy.getImportedPage(reader, i); PdfCopy.PageStamp ps = pdfCopy.createPageStamp(imp); PdfContentByte over = ps.getOverContent(); diff --git a/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/FontMapping.java b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/FontMapping.java new file mode 100644 index 000000000..9c68b16d4 --- /dev/null +++ b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/FontMapping.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.export.core.pdf; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.lowagie.text.FontFactory; +import com.lowagie.text.pdf.DefaultFontMapper; +import com.lowagie.text.pdf.FontMapper; + +/** + * A utility class for constructing properly initialize iText {@link FontMapper} + * instances. + * + * @author Tuukka Lehtonen + * @since 1.28.0 + */ +public class FontMapping { + + private static final AtomicBoolean fontFactoryInitialized = new AtomicBoolean(); + + public static FontMapper defaultFontMapper() { + // NOTE: recreation is intentional because users might add fonts between exports. + // At some point, perhaps we could listen to file system changes in these locations + // and only then recreate the mapper. + return createDefaultFontMapper(); + } + + public static FontMapper createDefaultFontMapper() { + initializeFontFactory(); + DefaultFontMapper mapper = new DefaultFontMapper(); + insertDirectories(mapper); + return mapper; + } + + private static void insertDirectories(DefaultFontMapper mapper) { + switch (OSType.calculate()) { + case APPLE: + mapper.insertDirectory("/Library/Fonts"); + mapper.insertDirectory("/System/Library/Fonts"); + + case LINUX: + case SUN: + mapper.insertDirectory("/usr/share/X11/fonts"); + mapper.insertDirectory("/usr/X/lib/X11/fonts"); + mapper.insertDirectory("/usr/openwin/lib/X11/fonts"); + mapper.insertDirectory("/usr/share/fonts"); + mapper.insertDirectory("/usr/X11R6/lib/X11/fonts"); + break; + + case WINDOWS: + String windir = System.getenv("WINDIR"); + if (windir != null && !windir.isEmpty()) { + mapper.insertDirectory(windir + "\\Fonts"); + } + break; + + default: + break; + } + } + + private static void initializeFontFactory() { + if (!fontFactoryInitialized.compareAndSet(false, true)) + return; + + switch (OSType.calculate()) { + case APPLE: + FontFactory.registerDirectory("/Library/Fonts"); + FontFactory.registerDirectory("/System/Library/Fonts"); + + case LINUX: + case SUN: + FontFactory.registerDirectory("/usr/share/X11/fonts", true); + FontFactory.registerDirectory("/usr/X/lib/X11/fonts", true); + FontFactory.registerDirectory("/usr/openwin/lib/X11/fonts", true); + FontFactory.registerDirectory("/usr/share/fonts", true); + FontFactory.registerDirectory("/usr/X11R6/lib/X11/fonts", true); + break; + + case WINDOWS: + String windir = System.getenv("WINDIR"); + if (windir != null && !windir.isEmpty()) { + FontFactory.registerDirectory(windir + "\\Fonts"); + } + break; + + default: + break; + } + } + + static enum OSType { + APPLE, LINUX, SUN, WINDOWS, UNKNOWN; + + public static OSType calculate() { + String osName = System.getProperty("os.name"); + assert osName != null; + osName = osName.toLowerCase(); + if (osName.startsWith("mac os x")) + return APPLE; + if (osName.startsWith("windows")) + return WINDOWS; + if (osName.startsWith("linux")) + return LINUX; + if (osName.startsWith("sun")) + return SUN; + return UNKNOWN; + } + } + +} diff --git a/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/PageNumbering.java b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/PageNumbering.java new file mode 100644 index 000000000..edf4e72f6 --- /dev/null +++ b/bundles/org.simantics.export.core/src/org/simantics/export/core/pdf/PageNumbering.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.export.core.pdf; + +import java.awt.geom.Point2D; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; + +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Element; +import com.lowagie.text.Font; +import com.lowagie.text.Phrase; +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.BadPdfFormatException; +import com.lowagie.text.pdf.ColumnText; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfCopy; +import com.lowagie.text.pdf.PdfImportedPage; +import com.lowagie.text.pdf.PdfReader; +import com.lowagie.text.pdf.PdfWriter; + +/** + * PDF page numbering related post-processing utilities using iText. + * + * @author Tuukka Lehtonen + * @since 1.28.0 + */ +public class PageNumbering { + + public static enum NumberingFormat { + PAGE_SLASH_TOTAL_PAGES + } + + public static enum Position { + BOTTOM_LEFT, + BOTTOM_RIGHT, + TOP_LEFT, + TOP_RIGHT, + } + + private static int pageNumberAlignment(Position positioning) { + switch (positioning) { + case BOTTOM_LEFT: + case TOP_LEFT: return Element.ALIGN_LEFT; + + case BOTTOM_RIGHT: + case TOP_RIGHT: + default: return Element.ALIGN_RIGHT; + } + } + + private static Point2D.Float pageNumberPosition(Position positioning, Rectangle pageSize, Font font) { + switch (positioning) { + case TOP_LEFT: + return new Point2D.Float(12, pageSize.getHeight() - 12 - font.getCalculatedSize()*0.8f); + case TOP_RIGHT: + return new Point2D.Float(pageSize.getWidth() - 12, pageSize.getHeight() - 12 - font.getCalculatedSize()*0.8f); + case BOTTOM_LEFT: + return new Point2D.Float(12, 12); + case BOTTOM_RIGHT: + default: + return new Point2D.Float(pageSize.getWidth() - 12, 12); + } + } + + public static String formatPageNumber(NumberingFormat format, int pageNumber, int totalPages) { + switch (format) { + case PAGE_SLASH_TOTAL_PAGES: return String.format("%d / %d", pageNumber, totalPages); + default: + throw new UnsupportedOperationException("Unsupported numbering format: " + format); + } + } + + public static void addPageNumber( + PdfCopy pdfCopy, + PdfReader sourceReader, + int sourcePageNumber, + int currentPageNumber, + int totalPages, + Font font, + Position positioning, + NumberingFormat format) + throws IOException, BadPdfFormatException + { + Rectangle pageSize = sourceReader.getPageSizeWithRotation(sourcePageNumber); + PdfImportedPage imp = pdfCopy.getImportedPage(sourceReader, sourcePageNumber); + PdfCopy.PageStamp ps = pdfCopy.createPageStamp(imp); + PdfContentByte over = ps.getOverContent(); + String text = formatPageNumber(format, currentPageNumber, totalPages); + Point2D.Float pos = pageNumberPosition(positioning, pageSize, font); + int alignment = pageNumberAlignment(positioning); + ColumnText.showTextAligned(over, alignment, new Phrase(text, font), pos.x, pos.y, 0); + ps.alterContents(); + pdfCopy.addPage(imp); + } + + public static void addPageNumbers( + IProgressMonitor monitor, + Path inputFile, + Path outputFile, + Position positioning, + NumberingFormat format, + Font pageNumberFont) + throws IOException, DocumentException + { + PdfReader reader = null; + try { + reader = new PdfReader(inputFile.toString()); + int totalPages = reader.getNumberOfPages(); + int currentPage = 1; + + SubMonitor mon = SubMonitor.convert(monitor, totalPages); + + try (OutputStream fos = Files.newOutputStream(outputFile)) { + Document document = new Document(); + PdfCopy pdfCopy = new PdfCopy(document, fos); + pdfCopy.setPdfVersion(PdfWriter.PDF_VERSION_1_7); + document.open(); + try { + for (int i = 1; i <= totalPages; ++i) { + mon.subTask(i + "/" + totalPages); + PageNumbering.addPageNumber(pdfCopy, reader, i, + currentPage++, totalPages, + pageNumberFont, + positioning, + format); + } + } finally { + if (document != null && document.isOpen()) + document.close(); + if (pdfCopy != null) + pdfCopy.close(); + } + } + } finally { + if (reader != null) + reader.close(); + } + + } + + public static void addPageNumbers( + IProgressMonitor monitor, + Path inputFile, + Path outputFile, + Position positioning, + NumberingFormat format) + throws IOException, DocumentException + { + addPageNumbers(monitor, inputFile, outputFile, positioning, format, new Font(Font.HELVETICA, 8)); + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/DiagramPrinter.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/DiagramPrinter.java index e3101dce2..7f0559348 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/DiagramPrinter.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/DiagramPrinter.java @@ -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 flattenedNodes, - ISessionContext sessionContext) - throws PdfException { - Collection flattened = flattenedNodes; + 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(); + } - 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 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"); - 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 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 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() { - @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); - } - } - } + { + 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(); @@ -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 diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java index ba389f77d..f0c9cddb3 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java @@ -260,9 +260,7 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { - // Print pdf - DiagramPrinter.printToPdf(monitor, exportPlan, exportPlan.exportLocation.toString(), exportPlan.selectedNodes, exportPlan.sessionContext); - + DiagramPrinter.printToPdf(monitor, exportPlan, exportPlan.exportLocation.toString(), exportPlan.selectedNodes); } catch (PdfException e) { throw new InvocationTargetException(e); } finally { diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java index 88312f67f..a41715f3b 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java @@ -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,6 +8,7 @@ * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation + * Semantum Oy - (#7084) page numbering *******************************************************************************/ package org.simantics.modeling.ui.pdf; @@ -20,6 +21,7 @@ import java.util.Set; import org.simantics.db.common.NamedResource; import org.simantics.db.management.ISessionContext; +import org.simantics.export.core.pdf.PageNumbering; import org.simantics.modeling.requests.CollectionResult; import org.simantics.modeling.requests.Node; import org.simantics.project.IProject; @@ -63,8 +65,26 @@ public class PDFExportPlan { * true to attach Wiki page. */ public boolean attachWiki = false; - - + + /** + * Whether or not to add page numbers to the exported PDF. Default value is + * {@value #addPageNumbers}. + * + * @since 1.28.0 + */ + public boolean addPageNumbers = true; + + /** + * This is ignored if {@link #addPageNumbers} is false. + */ + public PageNumbering.Position pageNumberPosition = PageNumbering.Position.BOTTOM_RIGHT; + + /** + * This is ignored if {@link #addPageNumbers} is false. + */ + public PageNumbering.NumberingFormat pageNumberFormat = PageNumbering.NumberingFormat.PAGE_SLASH_TOTAL_PAGES; + + public PDFExportPlan(ISessionContext sessionContext, Collection recentLocations) { this.sessionContext = sessionContext; this.recentLocations = recentLocations; diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFPainter.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFPainter.java index 85cf4bc08..1adcf25c2 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFPainter.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFPainter.java @@ -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,15 @@ * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation + * Semantum Oy - (#7084) refactoring *******************************************************************************/ package org.simantics.modeling.ui.pdf; import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicReference; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; +import org.simantics.db.Session; import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.utils.NameUtils; @@ -23,7 +24,7 @@ import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ValidationException; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; -import org.simantics.db.management.ISessionContext; +import org.simantics.db.request.Read; import org.simantics.diagram.elements.DiagramNodeUtil; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.g2d.canvas.Hints; @@ -31,7 +32,6 @@ import org.simantics.g2d.canvas.impl.CanvasContext; import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider; import org.simantics.modeling.requests.Node; import org.simantics.structural.stubs.StructuralResource2; -import org.simantics.utils.DataContainer; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.page.PageDesc; import org.simantics.utils.threads.IThreadWorkQueue; @@ -48,9 +48,8 @@ import com.lowagie.text.pdf.PdfWriter; */ public class PDFPainter { - public static boolean render( + public static void render( final IThreadWorkQueue thread, - final ISessionContext sessionContext, PDFExportPlan exportModel, final Node node, final PdfWriter writer, @@ -59,105 +58,101 @@ public class PDFPainter { final PageDesc pageDesc, final boolean fitDiagramContentsToPageMargins, long timeout) - throws InterruptedException, DatabaseException + throws InterruptedException, DatabaseException { - final DataContainer result = new DataContainer(false); - final DataContainer exception = new DataContainer(); + DatabaseException[] exception = { null }; + ICanvasSceneGraphProvider[] sgProvider = { null }; - final CanvasContext ctx = new CanvasContext(thread); - final AtomicReference sgProvider = new AtomicReference(); + CanvasContext ctx = new CanvasContext(thread); try { final Semaphore done = new Semaphore(0); // IMPORTANT: Load diagram in a different thread than the canvas context thread! - ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() { - @Override - public void run() { - try { - Pair modelAndRVI = sessionContext.getSession().syncRequest(new UniqueRead>() { - @Override - public Pair perform(ReadGraph graph) throws DatabaseException { - return new Pair( resolveModel(graph, node), resolveRVI(graph, node) ); - } - }); - - final Boolean isSymbol = sessionContext.getSession().syncRequest(new UniqueRead() { - @Override - public Boolean perform(ReadGraph graph) throws DatabaseException { - StructuralResource2 STR = StructuralResource2.getInstance(graph); - DiagramResource DIA = DiagramResource.getInstance(graph); - Resource possibleSymbol = graph.getPossibleObject(node.getDiagramResource(), STR.Defines); - return possibleSymbol != null && graph.isInstanceOf(possibleSymbol, DIA.ElementClass); - } - }); - - ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(ctx, modelAndRVI.first, node.getDiagramResource(), modelAndRVI.second, 5000); - sgProvider.set( provider ); - ctx.getDefaultHintContext().setHint(Hints.KEY_PAGE_DESC, pageDesc); - -// StringBuilder b = new StringBuilder(); -// NodeUtil.printTreeNodes(ctx.getCanvasNode(), b); -// System.err.println(b.toString()); - - ThreadUtils.asyncExec(thread, new Runnable() { - @Override - public void run() { - try { - PDFBuilder chassis = new PDFBuilder(writer, mapper, pageSize, pageDesc, fitDiagramContentsToPageMargins || isSymbol); - - chassis.paint(ctx, true); - } finally { - done.release(); - } - } - }); - } catch (DatabaseException e) { - done.release(); - exception.set(e); - } catch (Throwable e) { - done.release(); - exception.set(new DatabaseException(e)); - } finally { - done.release(); - } + ThreadUtils.getBlockingWorkExecutor().execute(() -> { + try { + Session s = exportModel.sessionContext.getSession(); + + Pair modelAndRVI = s.syncRequest( modelAndRVI(node) ); + Boolean isSymbol = s.syncRequest( isSymbol(node) ); + + ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider( + ctx, + modelAndRVI.first, + node.getDiagramResource(), + modelAndRVI.second, + 5000); + sgProvider[0] = provider; + ctx.getDefaultHintContext().setHint(Hints.KEY_PAGE_DESC, pageDesc); + +// System.err.println(NodeUtil.printTreeNodes(ctx.getCanvasNode(), new StringBuilder()).toString()); + + ThreadUtils.asyncExec(thread, () -> { + try { + PDFBuilder chassis = new PDFBuilder(writer, mapper, pageSize, pageDesc, fitDiagramContentsToPageMargins || isSymbol); + chassis.paint(ctx, true); + } catch (Throwable e) { + exception[0] = new DatabaseException(e); + } finally { + done.release(); + } + }); + } catch (DatabaseException e) { + done.release(); + exception[0] = e; + } catch (Throwable e) { + done.release(); + exception[0] = new DatabaseException(e); + } finally { + done.release(); } }); done.acquire(2); - if (exception.get() != null) - throw exception.get(); - return result.get(); + if (exception[0] != null) + throw exception[0]; } finally { - if (sgProvider.get() != null) - sgProvider.get().dispose(); + if (sgProvider[0] != null) + sgProvider[0].dispose(); ctx.dispose(); } } + private static Read> modelAndRVI(Node node) { + return new UniqueRead>() { + @Override + public Pair perform(ReadGraph graph) throws DatabaseException { + return Pair.make( resolveModel(graph, node), resolveRVI(graph, node) ); + } + }; + } + + private static Read isSymbol(Node node) { + return new UniqueRead() { + @Override + public Boolean perform(ReadGraph graph) throws DatabaseException { + StructuralResource2 STR = StructuralResource2.getInstance(graph); + DiagramResource DIA = DiagramResource.getInstance(graph); + Resource possibleSymbol = graph.getPossibleObject(node.getDiagramResource(), STR.Defines); + return possibleSymbol != null && graph.isInstanceOf(possibleSymbol, DIA.ElementClass); + } + }; + } + private static Resource resolveModel(ReadGraph graph, Node node) throws DatabaseException { Resource composite = node.getDefiningResources().head(); Resource model = graph.syncRequest(new PossibleIndexRoot(composite)); -// Resource model = StructuralVariables.getModel(graph, composite); if (model == null) throw new ValidationException("no model found for composite " + NameUtils.getSafeName(graph, composite)); return model; } -// private static String resolveModelURI(ReadGraph graph, final Node node) throws DatabaseException { -// return graph.getURI(resolveModel(graph, node)); -// } - private static String resolveRVI(ReadGraph graph, final Node node) throws DatabaseException { - String RVI = node.getRVI(); - if(RVI != null) return RVI; + String RVI = node.getRVI(); + if (RVI != null) return RVI; Resource composite = node.getDefiningResources().head(); Variable var = Variables.getVariable(graph, composite); org.simantics.db.layer0.variable.RVI rvi = var.getPossibleRVI(graph); - if(rvi == null) return null; - return rvi.toString(); -// final ResourceArray compositePath = StructuralVariables.getCompositeArray(graph, composite); -// final ResourceArray variablePath = compositePath.removeFromBeginning(1); -// return StructuralVariables.getRVI(graph, variablePath); + return rvi != null ? rvi.toString() : null; } -} +} \ No newline at end of file diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java index 80b9a8a52..c3bb21441 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java @@ -336,11 +336,12 @@ public final class NodeUtil { return result; } - public static final void printTreeNodes(INode node, StringBuilder builder) { + public static final StringBuilder printTreeNodes(INode node, StringBuilder builder) { printTreeNodes(node, 0, builder); + return builder; } - public static final void printTreeNodes(INode node, int indent, StringBuilder builder) { + public static final StringBuilder printTreeNodes(INode node, int indent, StringBuilder builder) { for (int i = 0; i < indent; i++) builder.append(" "); builder.append(node.toString() + "\n"); @@ -351,6 +352,7 @@ public final class NodeUtil { printTreeNodes(n, indent+2, builder); } } + return builder; } public static final Set collectNodes(INode node, Class clazz) { diff --git a/bundles/org.simantics.wiki.ui/src/org/simantics/wiki/ui/editor/IExportable.java b/bundles/org.simantics.wiki.ui/src/org/simantics/wiki/ui/editor/IExportable.java index eba35a571..492fd1928 100644 --- a/bundles/org.simantics.wiki.ui/src/org/simantics/wiki/ui/editor/IExportable.java +++ b/bundles/org.simantics.wiki.ui/src/org/simantics/wiki/ui/editor/IExportable.java @@ -16,5 +16,11 @@ import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.PdfWriter; public interface IExportable { - public void export(Document doc, PdfWriter writer) throws DocumentException; + /** + * @param doc + * @param writer + * @return the amount of PDF pages exported + * @throws DocumentException + */ + public int export(Document doc, PdfWriter writer) throws DocumentException; }