--- /dev/null
+package org.simantics.diagram.export;\r
+\r
+import java.awt.Graphics2D;\r
+import java.awt.RenderingHints;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.List;\r
+\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.OperationCanceledException;\r
+import org.osgi.service.prefs.Preferences;\r
+import org.simantics.databoard.Accessors;\r
+import org.simantics.databoard.accessor.RecordAccessor;\r
+import org.simantics.databoard.accessor.UnionAccessor;\r
+import org.simantics.databoard.accessor.error.AccessorConstructionException;\r
+import org.simantics.databoard.accessor.error.AccessorException;\r
+import org.simantics.databoard.accessor.reference.ChildReference;\r
+import org.simantics.databoard.accessor.reference.LabelReference;\r
+import org.simantics.databoard.binding.mutable.Variant;\r
+import org.simantics.databoard.type.RecordType;\r
+import org.simantics.databoard.type.UnionType;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.common.ResourceArray;\r
+import org.simantics.db.common.primitiverequest.SingleObject;\r
+import org.simantics.db.common.request.Queries;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.request.PossibleModel;\r
+import org.simantics.db.layer0.util.SessionGarbageCollection;\r
+import org.simantics.diagram.elements.DiagramNodeUtil;\r
+import org.simantics.diagram.query.DiagramRequests;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.export.core.ExportContext;\r
+import org.simantics.export.core.error.ExportException;\r
+import org.simantics.export.core.intf.ExportClass;\r
+import org.simantics.export.core.manager.Content;\r
+import org.simantics.export.core.pdf.ExportPdfWriter;\r
+import org.simantics.export.core.pdf.ExportPdfWriter.Page;\r
+import org.simantics.export.core.util.ExportQueries;\r
+import org.simantics.export.core.util.ExporterUtils;\r
+import org.simantics.g2d.canvas.Hints;\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.canvas.impl.CanvasContext;\r
+import org.simantics.g2d.diagram.DiagramHints;\r
+import org.simantics.g2d.diagram.DiagramUtils;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.participant.TransformUtil;\r
+import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.modeling.ModelingResources;\r
+import org.simantics.modeling.template2d.ontology.Template2dResource;\r
+import org.simantics.scenegraph.g2d.G2DPDFRenderingHints;\r
+import org.simantics.scenegraph.g2d.G2DRenderingHints;\r
+import org.simantics.scenegraph.utils.QualityHints;\r
+import org.simantics.simulation.ontology.SimulationResource;\r
+import org.simantics.structural.stubs.StructuralResource2;\r
+import org.simantics.structural2.StructuralVariables;\r
+import org.simantics.utils.datastructures.MapList;\r
+import org.simantics.utils.page.MarginUtils;\r
+import org.simantics.utils.page.MarginUtils.Margins;\r
+import org.simantics.utils.page.PageDesc;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+import org.simantics.utils.threads.WorkerThread;\r
+\r
+import com.kitfox.svg.SVGCache;\r
+\r
+public class ExportDiagramPdf implements ExportClass {\r
+\r
+ public static LabelReference P_DIAGRAM_OPTIONS = new LabelReference("Diagram Options"); \r
+ public static String S_CONTENT_FIT = "Content Fit";\r
+ public static ChildReference P_CONTENT_FIT = ChildReference.parsePath("Diagram Options/"+S_CONTENT_FIT);\r
+ public static String S_PAGE_SIZE = "Page Size";\r
+ public static ChildReference P_PAGE_SIZE = ChildReference.parsePath("Diagram Options/"+S_PAGE_SIZE);\r
+ \r
+ // Diagram export options\r
+ RecordType deo, options;\r
+ UnionType contentUt, pageUt;\r
+ \r
+ public ExportDiagramPdf() { \r
+ options = new RecordType();\r
+ deo = new RecordType();\r
+ contentUt = UnionType.newEnum("Use the diagram specific borders", "Fit page by its contents");\r
+ pageUt = UnionType.newEnum("Use the page size from this wizard", "Use diagram specific page sizes");\r
+ deo.addComponent(S_CONTENT_FIT, contentUt);\r
+ deo.addComponent(S_PAGE_SIZE, pageUt);\r
+ options.addComponent(P_DIAGRAM_OPTIONS.label, deo); \r
+ }\r
+ \r
+ @Override\r
+ public RecordType options(ExportContext context, Collection<String> content) throws ExportException {\r
+ return options;\r
+ }\r
+\r
+ @Override\r
+ public void fillDefaultPrefs(ExportContext ctx, Variant options) throws ExportException {\r
+ try {\r
+ RecordAccessor ra = Accessors.getAccessor(options);\r
+ ExporterUtils.setUnionValue(ra, P_CONTENT_FIT, 0);\r
+ ExporterUtils.setUnionValue(ra, P_PAGE_SIZE, 1); \r
+ } catch (AccessorConstructionException e) {\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void export(List<Content> contents, \r
+ Object handle,\r
+ ExportContext ctx, \r
+ Variant options,\r
+ IProgressMonitor monitor, \r
+ MapList<Content, Content> attachmentMap\r
+ ) throws ExportException {\r
+\r
+ final ExportPdfWriter writer = (ExportPdfWriter) handle;\r
+ \r
+ WorkerThread workerThread = new WorkerThread("Diagram PDF Painter");\r
+ workerThread.start();\r
+\r
+ Page page = null;\r
+ try {\r
+ ModelingResources MOD = ModelingResources.getInstance( ctx.session );\r
+ SimulationResource SIMU = SimulationResource.getInstance( ctx.session ); \r
+ StructuralResource2 STR = StructuralResource2.getInstance( ctx.session ); \r
+ DiagramResource DIA = DiagramResource.getInstance( ctx.session );\r
+ final Template2dResource TMPL = Template2dResource.getInstance( ctx.session ); \r
+ Layer0 L0 = Layer0.getInstance( ctx.session );\r
+ \r
+ for ( Content content : contents ) {\r
+ if (monitor.isCanceled())\r
+ throw new OperationCanceledException();\r
+\r
+ // Diagram's composite resource\r
+ Resource resource = ctx.session.syncRequest( ExportQueries.toResource(content.url) );\r
+ boolean isComposite = ctx.session.syncRequest( Queries.isInstanceOf(resource, STR.Composite) );\r
+ if ( !isComposite ) {\r
+ resource = ctx.session.syncRequest( Queries.possibleObjectWithType(resource, L0.ConsistsOf, STR.Composite) );\r
+ isComposite = ctx.session.syncRequest( Queries.isInstanceOf(resource, STR.Composite) );\r
+ }\r
+ if ( !isComposite ) {\r
+ throw new ExportException(content.url+" doesnt contain a diagram.");\r
+ }\r
+ final Resource composite = resource;\r
+ final Resource diagram = ctx.session.syncRequest( new SingleObject( composite, MOD.CompositeToDiagram ) );\r
+ final Resource drawingTemplate = ctx.session.syncRequest( Queries.possibleObject( diagram, TMPL.HasDrawingTemplate ) );\r
+ final Resource activeProfile = ctx.session.syncRequest( Queries.possibleObject( diagram, DIA.HasActiveProfile ) );\r
+ final Collection<Resource> activeProfileEntries = activeProfile != null ? ctx.session.syncRequest( Queries.objects(activeProfile, SIMU.IsActive) ) : Collections.<Resource>emptyList();\r
+ final Resource model = ctx.session.syncRequest( new PossibleModel( composite ) ); \r
+ if ( model==null ) throw new ExportException("Cannot export diagram. Model was not found."); \r
+ final String diagramName = content.label;\r
+ \r
+ ResourceArray compositePath = StructuralVariables.getCompositeArray(ctx.session, composite);\r
+ ResourceArray variablePath = compositePath.removeFromBeginning(1);\r
+ final String modelRVI = StructuralVariables.getRVI(ctx.session, variablePath);\r
+ \r
+ // PageDesc, Read page desc from the graph\r
+ int tag = getPageFitArea(options);\r
+ PageDesc diagramPageDesc = ctx.session.syncRequest( DiagramRequests.getPageDesc(diagram, writer.defaultPageDesc));\r
+ PageDesc wizardPageDesc = writer.defaultPageDesc;\r
+ PageDesc marginaaliViiva = diagramPageDesc;\r
+\r
+ // Close previous page before starting a new one.\r
+ if (page != null) {\r
+ page.close();\r
+ page = null;\r
+ }\r
+\r
+ if ( tag == 0 ) {\r
+ // top,left margin translation is applied to G2D on createGraphics(..) \r
+ page = writer.createPage( wizardPageDesc ); \r
+ marginaaliViiva = diagramPageDesc;\r
+ } else {\r
+ marginaaliViiva = diagramPageDesc;\r
+ page = writer.createPage( diagramPageDesc );\r
+ }\r
+ final PageDesc _marginaaliViiva = marginaaliViiva;\r
+ final PageDesc _diagramPageDesc = diagramPageDesc;\r
+ final Page _page = page;\r
+ final boolean fitDiagramContentsToPageMargins = getContentFitArea(options) == 1;\r
+ \r
+ final CanvasContext cctx = new CanvasContext( workerThread );\r
+ final Exception[] errors = new Exception[1];\r
+ final ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(cctx, model, diagram, modelRVI);\r
+ \r
+ ThreadUtils.syncExec(workerThread, new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ try { \r
+ cctx.getDefaultHintContext().setHint(Hints.KEY_PAGE_DESC, _marginaaliViiva);\r
+ \r
+ String bottomLabel = diagramName;\r
+ if ( drawingTemplate != null && activeProfileEntries.contains(TMPL.DrawingTemplate) ) bottomLabel = null;\r
+ \r
+ paint(cctx, writer, _page, fitDiagramContentsToPageMargins, bottomLabel, _diagramPageDesc);\r
+// } catch (DatabaseException e) {\r
+// errors[0] = e;\r
+// } catch (InterruptedException e) {\r
+// errors[0] = e;\r
+ } finally {\r
+ provider.dispose();\r
+ cctx.dispose();\r
+ }\r
+ }\r
+ });\r
+ if ( errors[0] != null ) {\r
+ throw new ExportException( errors[0] );\r
+ }\r
+ \r
+ \r
+ // Add page specific attachments\r
+ if ( attachmentMap!=null ) {\r
+ List<Content> ats = attachmentMap.getValues(content);\r
+ if ( ats != null ) for ( Content at : ats ) page.addAttachment(at);\r
+ }\r
+ }\r
+\r
+ // These still have to be done to keep memory consumption down\r
+ // and avoid OOMs.\r
+ SVGCache.getSVGUniverse().clearUnreferenced();\r
+ SessionGarbageCollection.gc(null, writer.ctx.session, true, null);\r
+ System.gc();\r
+\r
+ } catch (DatabaseException e) {\r
+ throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
+ } catch (InterruptedException e) {\r
+ throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );\r
+ } finally {\r
+ if ( workerThread!=null ) workerThread.stopDispatchingEvents(true);\r
+ if ( page!=null ) page.close();\r
+ }\r
+ \r
+ }\r
+ \r
+\r
+ public void paint(ICanvasContext canvasContext, ExportPdfWriter writer, Page page, boolean fitDiagramContentsToPageMargins, String diagramName, PageDesc diagramPageDesc) {\r
+ // Specify rendering template size in points.\r
+ Graphics2D g2 = page.createGraphics(false);\r
+ try {\r
+ QualityHints.HIGH_QUALITY_HINTS.setQuality(g2);\r
+ g2.setRenderingHint(G2DPDFRenderingHints.KEY_EXPORT_PDF_WRITER, writer);\r
+ g2.setRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER, writer.pdfCopy);\r
+ g2.setRenderingHint(G2DPDFRenderingHints.KEY_PDF_BYTECONTENT, writer.cb);\r
+ g2.setRenderingHint(G2DPDFRenderingHints.KEY_PDF_FONTMAPPER, writer.fontMapper); \r
+ g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);\r
+\r
+ // Page size in mm\r
+ double pw = page.pageDesc.getWidth();\r
+ double ph = page.pageDesc.getHeight();\r
+ // Drawable area size in mm\r
+ double w = page.getWidth();\r
+ double h = page.getHeight();\r
+\r
+ // Get margins in mm\r
+ if (fitDiagramContentsToPageMargins) {\r
+ Rectangle2D controlArea = new Rectangle2D.Double(0, 0, w, h);\r
+ IDiagram diagram = canvasContext.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);\r
+ final Rectangle2D diagramRect = DiagramUtils.getContentRect(diagram);\r
+ if (diagramRect != null) {\r
+ canvasContext.getSingleItem(TransformUtil.class).fitArea(controlArea, diagramRect, MarginUtils.NO_MARGINS);\r
+ }\r
+ }\r
+ \r
+ /// Margins\r
+ // Margin differences\r
+ Margins diagramMargins = diagramPageDesc.getMargins();\r
+ Margins wizardMargins = page.pageDesc.getMargins();\r
+\r
+ g2.translate( wizardMargins.left.diagramAbsolute, wizardMargins.top.diagramAbsolute);\r
+\r
+ double diagramPageWidth = diagramPageDesc.getOrientedWidth();\r
+ double diagramPageHeight = diagramPageDesc.getOrientedHeight();\r
+ \r
+ double wizardPageWidth = page.pageDesc.getOrientedWidth();\r
+ double wizardPageHeight = page.pageDesc.getOrientedHeight();\r
+ \r
+ double diagramContentWidth = diagramPageDesc.getOrientedWidth() - diagramMargins.left.diagramAbsolute - diagramMargins.right.diagramAbsolute;\r
+ double diagramContentHeight = diagramPageDesc.getOrientedHeight() - diagramMargins.top.diagramAbsolute - diagramMargins.bottom.diagramAbsolute;\r
+ \r
+ double wizardContentWidth = page.pageDesc.getOrientedWidth() - wizardMargins.left.diagramAbsolute - wizardMargins.right.diagramAbsolute;\r
+ double wizardContentHeight = page.pageDesc.getOrientedHeight() - wizardMargins.top.diagramAbsolute - wizardMargins.bottom.diagramAbsolute;\r
+\r
+ if ( diagramContentWidth!=wizardContentWidth || diagramContentHeight!=wizardContentHeight ) {\r
+ double r1 = wizardContentWidth / diagramContentWidth;\r
+ double r2 = wizardContentHeight / diagramContentHeight;\r
+ double r = Math.min(r1, r2);\r
+ if ( r1 < r2 ) {\r
+ g2.translate(0, wizardContentHeight/2);\r
+ } else {\r
+ g2.translate( wizardContentWidth/2, 0);\r
+ }\r
+ g2.scale(r, r);\r
+ if ( r1 < r2 ) {\r
+ g2.translate(0, -diagramContentHeight/ 2);\r
+ } else {\r
+ g2.translate(-diagramContentWidth/ 2, 0);\r
+ }\r
+ }\r
+ g2.translate( -diagramMargins.left.diagramAbsolute, -diagramMargins.top.diagramAbsolute);\r
+ g2.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, new Rectangle2D.Double(0, 0, w, h));\r
+\r
+ if (canvasContext.isLocked())\r
+ throw new IllegalStateException("cannot render PDF, canvas context is locked: " + canvasContext);\r
+\r
+ canvasContext.getSceneGraph().render(g2);\r
+\r
+// // Write diagram name\r
+// if ( diagramName != null ) {\r
+// g2.setColor(Color.black);\r
+// java.awt.Font arial = new java.awt.Font("Arial", java.awt.Font.ITALIC, 3);\r
+// g2.setFont(arial);\r
+// FontMetrics metrics = g2.getFontMetrics();\r
+// int width = metrics.stringWidth(diagramName);\r
+//// System.out.println(diagramName + ", w: " + w + ", width: " + width);\r
+//// System.out.println(diagramName + ", wizardPageHeight: " + wizardPageHeight + ", wizardMargins: " + wizardMargins);\r
+//// System.out.println(PageDesc.toPoints(1));\r
+// float x = (float) ((w - width) / 2);\r
+// float y = (float) (wizardPageHeight - wizardMargins.bottom.diagramAbsolute - PageDesc.toPoints(1));\r
+// g2.drawString(diagramName, x, y);\r
+//// System.out.println(diagramName + ", X: " + x + ", Y: " + y);\r
+// }\r
+ \r
+ } finally {\r
+ g2.dispose();\r
+ }\r
+ }\r
+ \r
+\r
+ @Override\r
+ public void savePref(Variant options, Preferences contentScopeNode, Preferences workbenchScopeNode) throws ExportException {\r
+ int tag = getContentFitArea(options);\r
+ if ( tag>=0 ) contentScopeNode.putInt(S_CONTENT_FIT, tag);\r
+ \r
+ tag = getPageFitArea(options);\r
+ if ( tag>=0 ) contentScopeNode.putInt(S_PAGE_SIZE, tag); \r
+ }\r
+\r
+ @Override\r
+ public void loadPref(Variant options, Preferences contentScopeNode, Preferences workbenchScopeNode) throws ExportException {\r
+ try {\r
+ RecordAccessor ra = Accessors.getAccessor(options);\r
+ \r
+ int tag = contentScopeNode.getInt(S_CONTENT_FIT, -1);\r
+ if ( tag>=0 ) ExporterUtils.setUnionValue(ra, P_CONTENT_FIT, tag);\r
+ \r
+ tag = contentScopeNode.getInt(S_PAGE_SIZE, -1);\r
+ if ( tag>=0 ) ExporterUtils.setUnionValue(ra, P_PAGE_SIZE, tag);\r
+ \r
+ } catch (AccessorConstructionException e) {\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public List<String> validate(String contentUri, ExportContext context, Variant options) {\r
+ return Collections.emptyList();\r
+ }\r
+\r
+ public static int getContentFitArea(Variant options) {\r
+ try {\r
+ RecordAccessor ra = Accessors.getAccessor(options);\r
+ UnionAccessor ua = ra.getComponent(P_CONTENT_FIT);\r
+ return ua.getTag();\r
+ } catch (AccessorConstructionException e) {\r
+ return -1;\r
+ } catch (AccessorException e) {\r
+ return -1;\r
+ }\r
+ }\r
+\r
+ public static int getPageFitArea(Variant options) {\r
+ try {\r
+ RecordAccessor ra = Accessors.getAccessor(options);\r
+ UnionAccessor ua = ra.getComponent(P_PAGE_SIZE);\r
+ return ua.getTag();\r
+ } catch (AccessorConstructionException e) {\r
+ return -1;\r
+ } catch (AccessorException e) {\r
+ return -1;\r
+ }\r
+ }\r
+ \r
+}\r