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