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