]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/export/ExportDiagramPdf.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / export / ExportDiagramPdf.java
1 package org.simantics.diagram.export;
2
3 import java.awt.Graphics2D;
4 import java.awt.RenderingHints;
5 import java.awt.geom.Rectangle2D;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.List;
9
10 import org.eclipse.core.runtime.IProgressMonitor;
11 import org.eclipse.core.runtime.OperationCanceledException;
12 import org.osgi.service.prefs.Preferences;
13 import org.simantics.databoard.Accessors;
14 import org.simantics.databoard.accessor.RecordAccessor;
15 import org.simantics.databoard.accessor.UnionAccessor;
16 import org.simantics.databoard.accessor.error.AccessorConstructionException;
17 import org.simantics.databoard.accessor.error.AccessorException;
18 import org.simantics.databoard.accessor.reference.ChildReference;
19 import org.simantics.databoard.accessor.reference.LabelReference;
20 import org.simantics.databoard.binding.mutable.Variant;
21 import org.simantics.databoard.type.RecordType;
22 import org.simantics.databoard.type.UnionType;
23 import org.simantics.db.Resource;
24 import org.simantics.db.common.ResourceArray;
25 import org.simantics.db.common.primitiverequest.SingleObject;
26 import org.simantics.db.common.request.Queries;
27 import org.simantics.db.exception.DatabaseException;
28 import org.simantics.db.layer0.request.PossibleModel;
29 import org.simantics.db.layer0.util.SessionGarbageCollection;
30 import org.simantics.diagram.elements.DiagramNodeUtil;
31 import org.simantics.diagram.query.DiagramRequests;
32 import org.simantics.diagram.stubs.DiagramResource;
33 import org.simantics.export.core.ExportContext;
34 import org.simantics.export.core.error.ExportException;
35 import org.simantics.export.core.intf.ExportClass;
36 import org.simantics.export.core.manager.Content;
37 import org.simantics.export.core.pdf.ExportPdfWriter;
38 import org.simantics.export.core.pdf.ExportPdfWriter.Page;
39 import org.simantics.export.core.util.ExportQueries;
40 import org.simantics.export.core.util.ExporterUtils;
41 import org.simantics.g2d.canvas.Hints;
42 import org.simantics.g2d.canvas.ICanvasContext;
43 import org.simantics.g2d.canvas.impl.CanvasContext;
44 import org.simantics.g2d.diagram.DiagramHints;
45 import org.simantics.g2d.diagram.DiagramUtils;
46 import org.simantics.g2d.diagram.IDiagram;
47 import org.simantics.g2d.participant.TransformUtil;
48 import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider;
49 import org.simantics.layer0.Layer0;
50 import org.simantics.modeling.ModelingResources;
51 import org.simantics.modeling.template2d.ontology.Template2dResource;
52 import org.simantics.scenegraph.g2d.G2DPDFRenderingHints;
53 import org.simantics.scenegraph.g2d.G2DRenderingHints;
54 import org.simantics.scenegraph.utils.QualityHints;
55 import org.simantics.simulation.ontology.SimulationResource;
56 import org.simantics.structural.stubs.StructuralResource2;
57 import org.simantics.structural2.StructuralVariables;
58 import org.simantics.utils.datastructures.MapList;
59 import org.simantics.utils.page.MarginUtils;
60 import org.simantics.utils.page.MarginUtils.Margins;
61 import org.simantics.utils.page.PageDesc;
62 import org.simantics.utils.threads.ThreadUtils;
63 import org.simantics.utils.threads.WorkerThread;
64
65 import com.kitfox.svg.SVGCache;
66
67 public class ExportDiagramPdf implements ExportClass {
68
69         public static LabelReference P_DIAGRAM_OPTIONS = new LabelReference("Diagram Options"); 
70         public static String S_CONTENT_FIT = "Content Fit";
71         public static ChildReference P_CONTENT_FIT = ChildReference.parsePath("Diagram Options/"+S_CONTENT_FIT);
72         public static String S_PAGE_SIZE = "Page Size";
73         public static ChildReference P_PAGE_SIZE = ChildReference.parsePath("Diagram Options/"+S_PAGE_SIZE);
74         
75         // Diagram export options
76         RecordType deo, options;
77         UnionType contentUt, pageUt;
78         
79         public ExportDiagramPdf() {             
80         options = new RecordType();
81             deo = new RecordType();
82                 contentUt = UnionType.newEnum("Use the diagram specific borders", "Fit page by its contents");
83                 pageUt = UnionType.newEnum("Use the page size from this wizard", "Use diagram specific page sizes");
84         deo.addComponent(S_CONTENT_FIT, contentUt);
85         deo.addComponent(S_PAGE_SIZE, pageUt);
86         options.addComponent(P_DIAGRAM_OPTIONS.label, deo);        
87         }
88         
89         @Override
90         public RecordType options(ExportContext context, Collection<String> content) throws ExportException {
91                 return options;
92         }
93
94         @Override
95         public void fillDefaultPrefs(ExportContext ctx, Variant options) throws ExportException {
96                 try {
97                         RecordAccessor ra = Accessors.getAccessor(options);
98                         ExporterUtils.setUnionValue(ra, P_CONTENT_FIT, 0);
99                         ExporterUtils.setUnionValue(ra, P_PAGE_SIZE, 1);                        
100                 } catch (AccessorConstructionException e) {
101                 }
102         }
103
104         @Override
105         public void export(List<Content> contents, 
106                         Object handle,
107                         ExportContext ctx, 
108                         Variant options,
109                         IProgressMonitor monitor, 
110                         MapList<Content, Content> attachmentMap
111                         ) throws ExportException {
112
113                 final ExportPdfWriter writer = (ExportPdfWriter) handle;
114                 
115         WorkerThread workerThread = new WorkerThread("Diagram PDF Painter");
116         workerThread.start();
117
118                 Page page = null;
119                 try {
120                         ModelingResources MOD = ModelingResources.getInstance( ctx.session );
121                         SimulationResource SIMU = SimulationResource.getInstance( ctx.session ); 
122                         StructuralResource2 STR = StructuralResource2.getInstance( ctx.session ); 
123                         DiagramResource DIA = DiagramResource.getInstance( ctx.session );
124                         final Template2dResource TMPL = Template2dResource.getInstance( ctx.session ); 
125                         Layer0 L0 = Layer0.getInstance( ctx.session );
126                         
127                         for ( Content content : contents ) {
128                                 if (monitor.isCanceled())
129                                         throw new OperationCanceledException();
130
131                                 // Diagram's composite resource
132                                 Resource resource = ctx.session.syncRequest( ExportQueries.toResource(content.url) );
133                                 boolean isComposite = ctx.session.syncRequest( Queries.isInstanceOf(resource, STR.Composite) );
134                                 if ( !isComposite ) {
135                                         resource = ctx.session.syncRequest( Queries.possibleObjectWithType(resource, L0.ConsistsOf, STR.Composite) );
136                                         isComposite = ctx.session.syncRequest( Queries.isInstanceOf(resource, STR.Composite) );
137                                 }
138                                 if ( !isComposite ) {
139                                         throw new ExportException(content.url+" doesnt contain a diagram.");
140                                 }
141                                 final Resource composite = resource;
142                                 final Resource diagram = ctx.session.syncRequest( new SingleObject( composite, MOD.CompositeToDiagram ) );
143                                 final Resource drawingTemplate = ctx.session.syncRequest( Queries.possibleObject( diagram, TMPL.HasDrawingTemplate ) );
144                                 final Resource activeProfile = ctx.session.syncRequest( Queries.possibleObject( diagram, DIA.HasActiveProfile ) );
145                                 final Collection<Resource> activeProfileEntries = activeProfile != null ? ctx.session.syncRequest( Queries.objects(activeProfile, SIMU.IsActive) ) : Collections.<Resource>emptyList();
146                                 final Resource model = ctx.session.syncRequest( new PossibleModel( composite ) );                               
147                                 if ( model==null ) throw new ExportException("Cannot export diagram. Model was not found.");                            
148                                 final String diagramName = content.label;
149                                 
150                         ResourceArray compositePath = StructuralVariables.getCompositeArray(ctx.session, composite);
151                         ResourceArray variablePath = compositePath.removeFromBeginning(1);
152                         final String modelRVI = StructuralVariables.getRVI(ctx.session, variablePath);
153                                 
154                         // PageDesc, Read page desc from the graph
155                                 int tag = getPageFitArea(options);
156                                 PageDesc diagramPageDesc = ctx.session.syncRequest( DiagramRequests.getPageDesc(diagram, writer.defaultPageDesc));
157                                 PageDesc wizardPageDesc = writer.defaultPageDesc;
158                                 PageDesc marginaaliViiva = diagramPageDesc;
159
160                                 // Close previous page before starting a new one.
161                                 if (page != null) {
162                                         page.close();
163                                         page = null;
164                                 }
165
166                                 if ( tag == 0 ) {
167                                         // top,left margin translation is applied to G2D on createGraphics(..) 
168                                         page = writer.createPage( wizardPageDesc ); 
169                                         marginaaliViiva = diagramPageDesc;
170                                 } else {
171                                         marginaaliViiva = diagramPageDesc;
172                                         page = writer.createPage( diagramPageDesc );
173                                 }
174                                 final PageDesc _marginaaliViiva = marginaaliViiva;
175                                 final PageDesc _diagramPageDesc = diagramPageDesc;
176                                 final Page _page = page;
177                                 final boolean fitDiagramContentsToPageMargins = getContentFitArea(options) == 1;
178                                 
179                         final CanvasContext cctx = new CanvasContext( workerThread );
180                         final Exception[] errors = new Exception[1];
181                 final ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(cctx, model, diagram, modelRVI);
182                 
183                 ThreadUtils.syncExec(workerThread, new Runnable() {
184                     @Override
185                     public void run() {
186                                 try {
187                                         cctx.getDefaultHintContext().setHint(Hints.KEY_PAGE_DESC, _marginaaliViiva);
188                             if (!fitDiagramContentsToPageMargins) {
189                                 // Prevent PDF printing from drawing page borders if the
190                                 // print area is fitted directly to the page size.
191                                 // This avoids unwanted black half-visible edges.
192                                 cctx.getDefaultHintContext().setHint(Hints.KEY_DISPLAY_PAGE, false);
193                             }
194                                         
195                                         String bottomLabel = diagramName;
196                                         if ( drawingTemplate != null && activeProfileEntries.contains(TMPL.DrawingTemplate) ) bottomLabel = null;
197                                         
198                                 paint(cctx, writer, _page, fitDiagramContentsToPageMargins, bottomLabel, _diagramPageDesc);
199 //                        } catch (DatabaseException e) {
200 //                              errors[0] = e;
201 //                                              } catch (InterruptedException e) {
202 //                              errors[0] = e;
203                                                 } finally {
204                                 provider.dispose();
205                                 cctx.dispose();
206                         }
207                     }
208                 });
209                         if ( errors[0] != null ) {
210                                 throw new ExportException( errors[0] );
211                         }
212                         
213                         
214                         // Add page specific attachments
215                                 if ( attachmentMap!=null ) {
216                                         List<Content> ats = attachmentMap.getValues(content);
217                                         if ( ats != null ) for ( Content at : ats ) page.addAttachment(at);
218                                 }
219                         }
220
221                         // These still have to be done to keep memory consumption down
222                         // and avoid OOMs.
223                         SVGCache.getSVGUniverse().clearUnreferenced();
224                         SessionGarbageCollection.gc(null, writer.ctx.session, true, null);
225                         System.gc();
226
227                 } catch (DatabaseException e) {
228                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );
229                 } catch (InterruptedException e) {
230                         throw new ExportException( e.getClass().getName()+": "+e.getMessage(), e );
231                 } finally {
232                         if ( workerThread!=null ) workerThread.stopDispatchingEvents(true);
233                         if ( page!=null ) page.close();
234                 }
235                 
236         }
237         
238
239     public void paint(ICanvasContext canvasContext, ExportPdfWriter writer, Page page, boolean fitDiagramContentsToPageMargins, String diagramName, PageDesc diagramPageDesc) {
240         // Specify rendering template size in points.
241         Graphics2D g2 = page.createGraphics(false);
242         try {
243             QualityHints.HIGH_QUALITY_HINTS.setQuality(g2);
244                 g2.setRenderingHint(G2DPDFRenderingHints.KEY_EXPORT_PDF_WRITER, writer);
245                 g2.setRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER, writer.pdfCopy);
246                 g2.setRenderingHint(G2DPDFRenderingHints.KEY_PDF_BYTECONTENT, writer.cb);
247                 g2.setRenderingHint(G2DPDFRenderingHints.KEY_PDF_FONTMAPPER, writer.fontMapper);        
248             g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
249
250                 // Page size in mm
251                 double pw = page.pageDesc.getWidth();
252                 double ph = page.pageDesc.getHeight();
253                 // Drawable area size in mm
254                 double w = page.getWidth();
255                 double h = page.getHeight();
256
257             // Get margins in mm
258             if (fitDiagramContentsToPageMargins) {
259                 Rectangle2D controlArea = new Rectangle2D.Double(0, 0, w, h);
260                 IDiagram diagram = canvasContext.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
261                 final Rectangle2D diagramRect = DiagramUtils.getContentRect(diagram);
262                 if (diagramRect != null) {
263                     canvasContext.getSingleItem(TransformUtil.class).fitArea(controlArea, diagramRect, MarginUtils.NO_MARGINS);
264                 }
265             }
266             
267             /// Margins
268             // Margin differences
269             Margins diagramMargins = diagramPageDesc.getMargins();
270             Margins wizardMargins = page.pageDesc.getMargins();
271
272             g2.translate( wizardMargins.left.diagramAbsolute, wizardMargins.top.diagramAbsolute);
273
274             double diagramPageWidth  = diagramPageDesc.getOrientedWidth();
275             double diagramPageHeight = diagramPageDesc.getOrientedHeight();
276             
277             double wizardPageWidth = page.pageDesc.getOrientedWidth();
278             double wizardPageHeight = page.pageDesc.getOrientedHeight();
279             
280             double diagramContentWidth  = diagramPageDesc.getOrientedWidth() - diagramMargins.left.diagramAbsolute - diagramMargins.right.diagramAbsolute;
281             double diagramContentHeight = diagramPageDesc.getOrientedHeight() - diagramMargins.top.diagramAbsolute - diagramMargins.bottom.diagramAbsolute;
282             
283             double wizardContentWidth = page.pageDesc.getOrientedWidth() - wizardMargins.left.diagramAbsolute - wizardMargins.right.diagramAbsolute;
284             double wizardContentHeight = page.pageDesc.getOrientedHeight() - wizardMargins.top.diagramAbsolute - wizardMargins.bottom.diagramAbsolute;
285
286             if ( diagramContentWidth!=wizardContentWidth || diagramContentHeight!=wizardContentHeight ) {
287                 double r1 = wizardContentWidth / diagramContentWidth;
288                 double r2 = wizardContentHeight / diagramContentHeight;
289                 double r = Math.min(r1, r2);
290                 if ( r1 < r2 ) {
291                         g2.translate(0, wizardContentHeight/2);
292                 } else {
293                         g2.translate( wizardContentWidth/2, 0);
294                 }
295                 g2.scale(r, r);
296                 if ( r1 < r2 ) {
297                         g2.translate(0, -diagramContentHeight/ 2);
298                 } else {
299                         g2.translate(-diagramContentWidth/ 2, 0);
300                 }
301             }
302             g2.translate( -diagramMargins.left.diagramAbsolute, -diagramMargins.top.diagramAbsolute);
303             g2.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, new Rectangle2D.Double(0, 0, w, h));
304
305             if (canvasContext.isLocked())
306                 throw new IllegalStateException("cannot render PDF, canvas context is locked: " + canvasContext);
307
308             canvasContext.getSceneGraph().render(g2);
309
310 //            // Write diagram name
311 //            if ( diagramName != null ) {
312 //                  g2.setColor(Color.black);
313 //                  java.awt.Font arial = new java.awt.Font("Arial", java.awt.Font.ITALIC, 3);
314 //                  g2.setFont(arial);
315 //                  FontMetrics metrics = g2.getFontMetrics();
316 //                  int width = metrics.stringWidth(diagramName);
317 ////                System.out.println(diagramName + ", w: " + w + ", width: " + width);
318 ////                System.out.println(diagramName + ", wizardPageHeight: " + wizardPageHeight + ", wizardMargins: " + wizardMargins);
319 ////                System.out.println(PageDesc.toPoints(1));
320 //                  float x = (float) ((w - width) / 2);
321 //                  float y = (float) (wizardPageHeight - wizardMargins.bottom.diagramAbsolute - PageDesc.toPoints(1));
322 //                  g2.drawString(diagramName, x, y);
323 ////                System.out.println(diagramName + ", X: " + x + ", Y: " + y);
324 //            }
325             
326         } finally {
327             g2.dispose();
328         }
329     }
330         
331
332         @Override
333         public void savePref(Variant options, Preferences contentScopeNode, Preferences workbenchScopeNode) throws ExportException {
334                 int tag = getContentFitArea(options);
335                 if ( tag>=0 ) contentScopeNode.putInt(S_CONTENT_FIT, tag);
336                 
337                 tag = getPageFitArea(options);
338                 if ( tag>=0 ) contentScopeNode.putInt(S_PAGE_SIZE, tag);                
339         }
340
341         @Override
342         public void loadPref(Variant options, Preferences contentScopeNode, Preferences workbenchScopeNode) throws ExportException {
343                 try {
344                         RecordAccessor ra = Accessors.getAccessor(options);
345                         
346                         int tag = contentScopeNode.getInt(S_CONTENT_FIT, -1);
347                         if ( tag>=0 ) ExporterUtils.setUnionValue(ra, P_CONTENT_FIT, tag);
348                         
349                         tag = contentScopeNode.getInt(S_PAGE_SIZE, -1);
350                         if ( tag>=0 ) ExporterUtils.setUnionValue(ra, P_PAGE_SIZE, tag);
351                         
352                 } catch (AccessorConstructionException e) {
353                 }
354         }
355
356         @Override
357         public List<String> validate(String contentUri, ExportContext context, Variant options) {
358                 return Collections.emptyList();
359         }
360
361         public static int getContentFitArea(Variant options) {
362                 try {
363                         RecordAccessor ra = Accessors.getAccessor(options);
364                         UnionAccessor ua = ra.getComponent(P_CONTENT_FIT);
365                         return ua.getTag();
366                 } catch (AccessorConstructionException e) {
367                         return -1;
368                 } catch (AccessorException e) {
369                         return -1;
370                 }
371         }
372
373         public static int getPageFitArea(Variant options) {
374                 try {
375                         RecordAccessor ra = Accessors.getAccessor(options);
376                         UnionAccessor ua = ra.getComponent(P_PAGE_SIZE);
377                         return ua.getTag();
378                 } catch (AccessorConstructionException e) {
379                         return -1;
380                 } catch (AccessorException e) {
381                         return -1;
382                 }
383         }
384         
385 }