EMF export: --without-emf can be used to disable this feature.
[scilab.git] / scilab / modules / graphic_export / src / java / org / scilab / modules / graphic_export / Export.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2012 - Scilab Enterprises - Calixte Denizet
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  *
11  */
12
13 package org.scilab.modules.graphic_export;
14
15 import java.awt.Dimension;
16 import java.awt.Font;
17 import java.awt.Graphics2D;
18 import java.awt.Shape;
19 import java.awt.geom.AffineTransform;
20 import java.awt.geom.Ellipse2D;
21 import java.awt.geom.Path2D;
22 import java.awt.geom.PathIterator;
23 import java.awt.image.BufferedImage;
24 import java.io.BufferedOutputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.File;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.io.OutputStreamWriter;
32 import java.io.Writer;
33 import java.lang.reflect.Constructor;
34 import java.lang.reflect.InvocationTargetException;
35 import java.lang.reflect.Type;
36 import java.nio.charset.Charset;
37 import java.nio.charset.CharsetEncoder;
38 import java.text.AttributedCharacterIterator;
39 import java.util.HashMap;
40 import java.util.Map;
41 import java.util.WeakHashMap;
42
43 import org.apache.batik.dom.GenericDOMImplementation;
44 import org.apache.batik.svggen.SVGGeneratorContext;
45 import org.apache.batik.svggen.SVGGraphics2D;
46 import org.apache.fop.Version;
47 import org.apache.fop.svg.PDFDocumentGraphics2D;
48 import org.apache.xmlgraphics.java2d.GraphicContext;
49 import org.apache.xmlgraphics.java2d.ps.AbstractPSDocumentGraphics2D;
50 import org.apache.xmlgraphics.java2d.ps.EPSDocumentGraphics2D;
51 import org.apache.xmlgraphics.java2d.ps.PSDocumentGraphics2D;
52 import org.apache.xmlgraphics.ps.DSCConstants;
53 import org.apache.xmlgraphics.ps.PSGenerator;
54 import org.scilab.forge.scirenderer.Canvas;
55 import org.scilab.forge.scirenderer.implementation.g2d.G2DCanvas;
56 import org.scilab.forge.scirenderer.implementation.g2d.G2DCanvasFactory;
57 import org.scilab.forge.scirenderer.implementation.jogl.JoGLCanvas;
58 import org.scilab.forge.scirenderer.implementation.jogl.JoGLCanvasFactory;
59 import org.scilab.modules.commons.ScilabCommonsUtils;
60 import org.scilab.modules.graphic_export.convertToPPM.PPMEncoder;
61 import org.scilab.modules.graphic_objects.figure.Figure;
62 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
63 import org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties;
64 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
65 import org.w3c.dom.DOMImplementation;
66 import org.w3c.dom.Document;
67
68 /**
69  * Main class to export
70  * Dependancies are put in inner classes to avoid the deps loading.
71  *
72  * @author Calixte DENIZET
73  */
74 public class Export {
75
76     public static final int SUCCESS = 0;
77     public static final int IOEXCEPTION_ERROR = 1;
78     public static final int INVALID_FILE = 2;
79     public static final int MEMORY_ERROR = 3;
80     public static final int UNKNOWN_ERROR = 4;
81     public static final int FILENOTFOUND_ERROR = 5;
82
83     private static final float DEFAULT_JPEG_COMPRESSION = 0.95f;
84
85     private static final String CLASSPATH_PDF_PS_EPS_EXPORT_NAME = "pdf_ps_eps_graphic_export";
86     private static final String CLASSPATH_SVG_EXPORT_NAME = "svg_graphic_export";
87     private static final String CLASSPATH_EMF_EXPORT_NAME = "emf_graphic_export";
88
89     private static final Map<DrawerVisitor, Exporter> visitorsToExp = new WeakHashMap<DrawerVisitor, Exporter>();
90
91     private static final Map<String, Integer> extToType = new HashMap<String, Integer>();
92     static {
93         extToType.put("bmp", 1);
94         extToType.put("gif", 2);
95         extToType.put("jpeg", 3);
96         extToType.put("jpg", 3);
97         extToType.put("png", 4);
98         extToType.put("ppm", 5);
99         extToType.put("eps", 6);
100         extToType.put("pdf", 7);
101         extToType.put("svg", 8);
102         extToType.put("ps", 9);
103         extToType.put("pos", 9);
104         extToType.put("emf", 10);
105     }
106
107     private static boolean emfLoaded;
108     private static boolean svgLoaded;
109     private static boolean pdfLoaded;
110
111     public enum TYPE { PNG, JPEG, GIF, BMP, PPM, SVG, PS, EPS, PDF, EMF }
112     private static final TYPE[] types = new TYPE[] {TYPE.PNG, TYPE.BMP, TYPE.GIF, TYPE.JPEG, TYPE.PNG, TYPE.PPM, TYPE.EPS, TYPE.PDF, TYPE.SVG, TYPE.PS, TYPE.EMF};
113
114     /**
115      * @param type the image type
116      * @return true if bitmap image format
117      */
118     public static boolean isBitmapFormat(TYPE type) {
119         return type == TYPE.PNG || type == TYPE.JPEG || type == TYPE.GIF || type == TYPE.BMP || type == TYPE.PPM;
120     }
121
122     public static int getType(String ext) {
123         Integer type = extToType.get(ext.toLowerCase());
124         if (type == null) {
125             return -1;
126         }
127
128         return type;
129     }
130
131     /**
132      * Export in drawing in a Graphics2D
133      * @param uid the figure uid
134      * @param type the export type
135      * @param fileName the file name
136      * @param params the export paramaters
137      * @return the export status
138      */
139     public static int export(int uid, int type, String fileName, ExportParams params, boolean headless) {
140         // Check that the fileName contains an extension
141         int dotPosition = fileName.lastIndexOf('.'); // position of the dot
142         boolean extensionFound = false;
143         if (dotPosition > 0 && dotPosition <= fileName.length() - 2) {
144             extensionFound = true;
145         }
146
147         String extendedFilename = fileName;
148         if (!extensionFound) { // Add default extension if no one found
149             String[] extensions = {"png", "bmp", "gif", "jpeg", "png", "ppm", "eps", "pdf", "svg", "ps", "emf"};
150             extendedFilename = fileName + "." + extensions[type];
151         }
152
153         DrawerVisitor visitor = DrawerVisitor.getVisitor(uid);
154         if (visitor != null) {
155             Canvas canvas = visitor.getCanvas();
156             if (canvas instanceof JoGLCanvas && isBitmapFormat(types[type])) {
157                 try {
158                     return exportBitmap(uid, type, extendedFilename, true, params);
159                 } catch (OutOfMemoryError e) {
160                     return MEMORY_ERROR;
161                 } catch (Throwable e) {
162                     return UNKNOWN_ERROR;
163                 }
164             }
165         }
166
167         return exportVectorial(uid, type, extendedFilename, params, headless);
168     }
169
170     /**
171      * Export in drawing in a Graphics2D
172      * @param uid the figure uid
173      * @param type the export type
174      * @param fileName the file name
175      * @param params the export paramaters
176      * @return the export status
177      */
178     public static int exportVectorial(int uid, int type, String fileName, ExportParams params, boolean headless) {
179         if (fileName == null) {
180             return INVALID_FILE;
181         }
182
183         File f = new File(fileName);
184         int ret = Utils.checkWritePermission(f);
185         if (ret != SUCCESS) {
186             return ret;
187         }
188
189         try {
190             return exportVectorial(uid, types[type], f, params, headless);
191         } catch (IOException e) {
192             if (e instanceof FileNotFoundException) {
193                 return FILENOTFOUND_ERROR;
194             }
195             return IOEXCEPTION_ERROR;
196         }
197     }
198
199     /**
200      * Export in drawing in a Graphics2D
201      * @param uid the figure uid
202      * @param type the export type
203      * @param file the file where to export
204      * @param params the export paramaters
205      */
206     public static int exportVectorial(int uid, TYPE type, File file, ExportParams params, boolean headless) throws IOException {
207         Figure figure = (Figure) GraphicController.getController().getObjectFromId(uid);
208
209         if (!headless) {
210             Exporter exporter = getExporter(type);
211             Integer[] dims = figure.getAxesSize();
212             int width = dims[0];
213             int height = dims[1];
214
215             Graphics2D g2d = exporter.getGraphics2D(width, height, file, params);
216             if (g2d == null) {
217                 return FILENOTFOUND_ERROR;
218             }
219
220             params.setParamsOnGraphics(g2d);
221
222             Canvas canvas = G2DCanvasFactory.createCanvas(g2d, width, height);
223             DrawerVisitor oldVisitor = DrawerVisitor.getVisitor(uid);
224             DrawerVisitor visitor = new DrawerVisitor(null, canvas, figure) {
225                 @Override
226                 public void updateObject(Integer id, int property) {
227                     // Don't update during the export
228                 }
229             };
230
231             try {
232                 canvas.setMainDrawer(visitor);
233                 canvas.redraw();
234                 exporter.write();
235             } catch (OutOfMemoryError e) {
236                 return MEMORY_ERROR;
237             } catch (IOException e) {
238                 throw e;
239             } catch (Throwable e) {
240                 return UNKNOWN_ERROR;
241             } finally {
242                 GraphicController.getController().unregister(visitor);
243                 DrawerVisitor.changeVisitor(figure, oldVisitor);
244                 exporter.dispose();
245                 exporter = null;
246                 visitorsToExp.remove(visitor);
247                 canvas.destroy();
248             }
249         } else {
250             DrawerVisitor visitor = DrawerVisitor.getVisitor(uid);
251             if (visitor.getCanvas() instanceof G2DCanvas) {
252                 G2DCanvas canvas = (G2DCanvas) visitor.getCanvas();
253                 canvas.enableDraw();
254                 Exporter exporter = null;
255                 try {
256                     canvas.redraw();
257                     exporter = visitorsToExp.get(visitor);
258                     if (exporter != null) {
259                         exporter.file = file;
260                         exporter.write();
261                     }
262                 } catch (OutOfMemoryError e) {
263                     return MEMORY_ERROR;
264                 } catch (IOException e) {
265                     throw e;
266                 } catch (Throwable e) {
267                     return UNKNOWN_ERROR;
268                 } finally {
269                     if (exporter != null) {
270                         exporter.dispose();
271                         exporter = null;
272                         visitorsToExp.remove(visitor);
273                     }
274                     DrawerVisitor.changeVisitor(figure, null);
275                     GraphicController.getController().unregister(visitor);
276                     canvas.destroy();
277                 }
278             }
279         }
280
281         return SUCCESS;
282     }
283
284     /**
285      * Export in getting a buffered image from JoGL
286      * @param uid the figure uid
287      * @param type the export type
288      * @param fileName the file name
289      * @param fromScreen if true, then use the screen view
290      * @param params the export paramaters
291      * @return the export status
292      */
293     public static int exportBitmap(int uid, int type, String fileName, boolean fromScreen, ExportParams params) {
294         if (fileName == null) {
295             return INVALID_FILE;
296         }
297
298         File f = new File(fileName);
299         int ret = Utils.checkWritePermission(f);
300         if (ret != SUCCESS) {
301             return ret;
302         }
303
304         try {
305             exportBitmap(uid, types[type], f, fromScreen, params);
306         } catch (IOException e) {
307             if (e instanceof FileNotFoundException) {
308                 return FILENOTFOUND_ERROR;
309             }
310             return IOEXCEPTION_ERROR;
311         }
312
313         return SUCCESS;
314     }
315
316     /**
317      * Export in getting a buffered image from JoGL
318      * @param uid the figure uid
319      * @param type the export type
320      * @param file the file where to export
321      * @param fromScreen if true, then use the screen view
322      * @param params the export paramaters
323      */
324     public static void exportBitmap(int uid, TYPE type, File file, boolean fromScreen, ExportParams params) throws IOException {
325         if (isBitmapFormat(type)) {
326             JoGLCanvas joglCanvas = null;
327             if (fromScreen) {
328                 DrawerVisitor visitor = DrawerVisitor.getVisitor(uid);
329                 Canvas canvas = visitor.getCanvas();
330                 if (canvas instanceof JoGLCanvas) {
331                     joglCanvas = (JoGLCanvas) canvas;
332                 }
333             } else {
334                 Figure figure = (Figure) GraphicController.getController().getObjectFromId(uid);
335                 Integer[] dims = figure.getAxesSize();
336                 DrawerVisitor oldVisitor = DrawerVisitor.getVisitor(uid);
337                 joglCanvas = (JoGLCanvas) JoGLCanvasFactory.createCanvas(dims[0], dims[1]);
338                 DrawerVisitor visitor = new DrawerVisitor(null, joglCanvas, figure) {
339                     @Override
340                     public void updateObject(Integer id, int property) {
341                         // Don't update during the export
342                     }
343
344                     @Override
345                     public void deleteObject(Integer id) {
346                         // Don't delete during the export
347                     }
348                 };
349                 joglCanvas.setMainDrawer(visitor);
350                 joglCanvas.redraw();
351                 GraphicController.getController().unregister(visitor);
352                 DrawerVisitor.changeVisitor(figure, oldVisitor);
353             }
354
355             if (joglCanvas != null) {
356                 BufferedImage image = joglCanvas.getImage();
357                 //joglCanvas.destroy();
358                 PNGExporter exporter = (PNGExporter) getExporter(type);
359                 exporter.setImage(file, image, params);
360                 exporter.write();
361                 exporter.dispose();
362             }
363         }
364     }
365
366     /**
367      * Export in drawing in a Graphics2D
368      * @param uid the figure uid
369      * @param type the export type
370      * @param file the file where to export
371      * @param params the export paramaters
372      */
373     public static void setVisitor(int uid, int type, final ExportParams params) {
374         final Exporter exporter = getExporter(types[type]);
375         Figure figure = (Figure) GraphicController.getController().getObjectFromId(uid);
376         final Integer[] dims = figure.getAxesSize();
377         int width = dims[0];
378         int height = dims[1];
379
380         final Graphics2D g2d = exporter.getGraphics2D(width, height, null, params);
381         params.setParamsOnGraphics(g2d);
382
383         final G2DCanvas canvas = G2DCanvasFactory.createCanvas(g2d, width, height);
384         canvas.disableDraw();
385         DrawerVisitor visitor = new DrawerVisitor(null, canvas, figure) {
386             @Override
387             public void deleteObject(Integer id) {
388                 // Don't delete during the export
389             }
390
391             @Override
392             public void updateObject(Integer id, int property) {
393                 if (needUpdate(id, property)) {
394                     axesDrawer.update(id, property);
395                     if (property == GraphicObjectProperties.__GO_AXES_SIZE__) {
396                         Integer[] size = getFigure().getAxesSize();
397                         if (size[0] != dims[0] || size[1] != dims[1]) {
398                             Graphics2D newg2d = exporter.getGraphics2D(size[0], size[1], null, params);
399                             params.setParamsOnGraphics(newg2d);
400                             canvas.setGraphics(newg2d, size[0], size[1]);
401                             dims[0] = size[0];
402                             dims[1] = size[1];
403
404                             g2d.dispose();
405                         }
406                     } else if (property == GraphicObjectProperties.__GO_ANTIALIASING__) {
407                         canvas.setAntiAliasingLevel(getFigure().getAntialiasing());
408                     }
409                 }
410             }
411         };
412         visitor.setDrawingTools(canvas.getDrawingTools());
413         canvas.setMainDrawer(visitor);
414         visitorsToExp.put(visitor, exporter);
415     }
416
417     /**
418      * Get an exporter from a type
419      * @param type the exporter type.
420      * @return the corresponding exporter/
421      */
422     private static Exporter getExporter(TYPE type) {
423         switch (type) {
424             case PNG :
425                 return new PNGExporter();
426             case GIF :
427                 return new GIFExporter();
428             case JPEG :
429                 return new JPEGExporter();
430             case BMP :
431                 return new BMPExporter();
432             case PPM :
433                 return new PPMExporter();
434             case SVG :
435                 if (!svgLoaded) {
436                     ScilabCommonsUtils.loadOnUse(CLASSPATH_SVG_EXPORT_NAME);
437                     svgLoaded = true;
438                 }
439                 return new SVGExporter();
440             case PDF :
441                 loadPDF();
442                 return new PDFExporter();
443             case PS :
444                 loadPDF();
445                 return new PSExporter();
446             case EPS :
447                 loadPDF();
448                 return new EPSExporter();
449             case EMF :
450                 if (!emfLoaded) {
451                     ScilabCommonsUtils.loadOnUse(CLASSPATH_EMF_EXPORT_NAME);
452                     emfLoaded = true;
453                 }
454                 return new EMFExporter();
455             default :
456                 break;
457         }
458
459         return null;
460     }
461
462     /**
463      * Load the PDF/PS/EPS dependencies
464      */
465     private static final void loadPDF() {
466         if (!pdfLoaded) {
467             ScilabCommonsUtils.loadOnUse(CLASSPATH_PDF_PS_EPS_EXPORT_NAME);
468             pdfLoaded = true;
469         }
470     }
471
472     /**
473      * Interface to export
474      */
475     private static abstract class Exporter {
476
477         protected File file;
478
479         /**
480          * @param width graphics width
481          * @param height graphisc height
482          * @param file the file
483          * @param params the export parameters
484          */
485         abstract Graphics2D getGraphics2D(int width, int height, File file, ExportParams params);
486
487         /**
488          * Write the file
489          */
490         abstract void write() throws IOException;
491
492         abstract void dispose();
493     }
494
495     /**
496      * PNG Exporter
497      */
498     private static class PNGExporter extends Exporter {
499
500         protected BufferedImage image;
501         protected Graphics2D g2d;
502         protected ExportParams params;
503
504         public PNGExporter() { }
505
506         public void setImage(File file, BufferedImage image, ExportParams params) {
507             this.file = file;
508             this.image = image;
509             this.params = params;
510         }
511
512         @Override
513         public Graphics2D getGraphics2D(int width, int height, File file, ExportParams params) {
514             this.file = file;
515             this.params = params;
516             image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
517             g2d = image.createGraphics();
518
519             return g2d;
520         }
521
522         @Override
523         public void write() throws IOException {
524             ExportBitmap.writeFile(image, "png", file);
525         }
526
527         @Override
528         public void dispose() {
529             if (g2d != null) {
530                 g2d.dispose();
531             }
532         }
533     }
534
535     /**
536      * GIF Exporter
537      */
538     private static class GIFExporter extends PNGExporter {
539
540         public GIFExporter() { }
541
542         @Override
543         public void write() throws IOException {
544             ExportBitmap.writeFile(image, "gif", file);
545         }
546     }
547
548     /**
549      * BMP Exporter
550      */
551     private static class BMPExporter extends PNGExporter {
552
553         public BMPExporter() { }
554
555         @Override
556         public Graphics2D getGraphics2D(int width, int height, File file, ExportParams params) {
557             this.file = file;
558             this.params = params;
559             image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
560             g2d = image.createGraphics();
561
562             return g2d;
563         }
564
565         @Override
566         public void write() throws IOException {
567             ExportBitmap.writeFile(image, "bmp", file);
568         }
569     }
570
571     /**
572      * JPEG Exporter
573      */
574     private static class JPEGExporter extends BMPExporter {
575
576         public JPEGExporter() { }
577
578         @Override
579         public void write() throws IOException {
580             if (params.compressionQuality == -1) {
581                 ExportBitmap.writeJPEG(image, DEFAULT_JPEG_COMPRESSION, file);
582             } else {
583                 ExportBitmap.writeJPEG(image, params.compressionQuality, file);
584             }
585         }
586     }
587
588     /**
589      * PPM Exporter
590      */
591     private static class PPMExporter extends BMPExporter {
592
593         public PPMExporter() { }
594
595         @Override
596         public void write() throws IOException {
597             OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
598             PPMEncoder encoder = new PPMEncoder(image, out);
599             int[] pixels = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
600             encoder.encodeStart(image.getWidth(), image.getHeight());
601             encoder.encodePixels(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
602             out.flush();
603             out.close();
604         }
605     }
606
607     /**
608      * SVG Exporter
609      */
610     private static class SVGExporter extends Exporter {
611
612         private SVGGraphics2D g2d;
613         private ExportParams params;
614
615         public SVGExporter() { }
616
617         @Override
618         public Graphics2D getGraphics2D(int width, int height, File file, ExportParams params) {
619             this.file = file;
620             this.params = params;
621             DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
622             Document document = domImpl.createDocument("http://www.w3.org/2000/svg", "svg", null);
623             final SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
624             ctx.setComment("Generated by Scilab with Batik SVG Generator");
625             // TODO: better handle of LaTeX fonts (should remove the 'true' below and include the font in the SVG)
626             // same thing for PDF & co...
627             ctx.setEmbeddedFontsOn(true);
628             g2d = new SVGGraphics2D(ctx, false) {
629
630                 @Override
631                 public void drawString(String s, float x, float y) {
632                     textAsShapes = getFont().getFontName().startsWith("jlm");
633                     super.drawString(s, x, y);
634                 }
635
636                 @Override
637                 public void drawString(AttributedCharacterIterator ati, float x, float y) {
638                     textAsShapes = getFont().getFontName().startsWith("jlm");
639                     super.drawString(ati, x, y);
640                 }
641             };
642             if (params.orientation == ExportParams.LANDSCAPE) {
643                 g2d.setSVGCanvasSize(new Dimension(height, width));
644                 AffineTransform transf = AffineTransform.getRotateInstance(Math.PI / 2);
645                 transf.preConcatenate(AffineTransform.getTranslateInstance(height, 0));
646                 g2d.setTransform(transf);
647             } else {
648                 g2d.setSVGCanvasSize(new Dimension(width, height));
649             }
650             return g2d;
651         }
652
653         @Override
654         public void write() throws IOException {
655             boolean useCSS = true;
656             OutputStream svgs = new BufferedOutputStream(new FileOutputStream(file));
657             Writer out = new OutputStreamWriter(svgs, "UTF-8");
658             g2d.stream(out, useCSS);
659             svgs.flush();
660             svgs.close();
661         }
662
663         @Override
664         public void dispose() {
665             if (g2d != null) {
666                 g2d.dispose();
667             }
668         }
669     }
670
671     /**
672      * PDF Exporter
673      */
674     private static class PDFExporter extends Exporter {
675
676         private OutputStream out;
677         private PDFDocumentGraphics2D g2d;
678         private ExportParams params;
679         private ByteArrayOutputStream buffer;
680
681         public PDFExporter() { }
682
683         @Override
684         public Graphics2D getGraphics2D(int width, int height, File file, ExportParams params) {
685             this.file = file;
686             this.params = params;
687             try {
688                 if (file == null) {
689                     buffer = new ByteArrayOutputStream();
690                     out = new BufferedOutputStream(buffer);
691                 } else {
692                     out = new BufferedOutputStream(new FileOutputStream(file));
693                 }
694                 g2d = new PDFDocumentGraphics2D(true);
695                 g2d.setupDefaultFontInfo();
696                 g2d.getPDFDocument().getInfo().setProducer("Generated by Scilab with Apache FOP Version " + Version.getVersion());
697                 g2d.setGraphicContext(new GraphicContext());
698                 if (params.orientation == ExportParams.LANDSCAPE) {
699                     g2d.setupDocument(out, height, width);
700                     g2d.setSVGDimension(height, width);
701                     double s = PDFDocumentGraphics2D.NORMAL_PDF_RESOLUTION / g2d.getDeviceDPI();
702                     AffineTransform transf = AffineTransform.getRotateInstance(Math.PI / 2);
703                     transf.preConcatenate(AffineTransform.getTranslateInstance(height / s, 0));
704                     g2d.setTransform(transf);
705                 } else {
706                     g2d.setupDocument(out, width, height);
707                     g2d.setSVGDimension(width, height);
708                 }
709             } catch (IOException e) { }
710
711             return g2d;
712         }
713
714         @Override
715         public void write() throws IOException {
716             if (g2d != null) {
717                 g2d.finish();
718             }
719             if (buffer != null && file != null) {
720                 FileOutputStream fos = new FileOutputStream(file);
721                 buffer.writeTo(fos);
722                 buffer.close();
723                 fos.flush();
724                 fos.close();
725             }
726             if (out != null) {
727                 out.close();
728             }
729         }
730
731         @Override
732         public void dispose() {
733             g2d.dispose();
734         }
735     }
736
737     /**
738      * PS Exporter
739      */
740     private static class PSExporter extends Exporter {
741
742         protected OutputStream out;
743         protected AbstractPSDocumentGraphics2D g2d;
744         protected ExportParams params;
745         protected ByteArrayOutputStream buffer;
746
747         public PSExporter() { }
748
749         @Override
750         public Graphics2D getGraphics2D(int width, int height, File file, final ExportParams params) {
751             this.file = file;
752             this.params = params;
753             try {
754                 if (file == null) {
755                     buffer = new ByteArrayOutputStream();
756                     out = new BufferedOutputStream(buffer);
757                 } else {
758                     out = new BufferedOutputStream(new FileOutputStream(file));
759                 }
760                 g2d = new PSDocumentGraphics2D(true, out, width, height) {
761                     @Override
762                     protected void writePageHeader() throws IOException {
763                         super.writePageHeader();
764                         if (params.orientation == ExportParams.LANDSCAPE) {
765                             gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Landscape");
766                         } else {
767                             gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Portrait");
768                         }
769                         gen.writeln("/ReEncode { /MyEncoding exch def exch findfont dup length dict begin {def} forall /Encoding MyEncoding def currentdict end definefont } def");
770                         gen.writeln("/Helvetica /HelveticaLatin1 ISOLatin1Encoding ReEncode");
771                         gen.writeln("/Times /TimesLatin1 ISOLatin1Encoding ReEncode");
772                         gen.writeln("/DP {/Points exch def N Points 0 get Points 1 get M 2 2 Points length 1 sub {/i exch def Points i get Points i 1 add get L}for} def");
773                     }
774
775                     @Override
776                     public void drawString(String s, float x, float y) {
777                         if (s != null && !s.isEmpty()) {
778                             CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder();
779                             if (encoder.canEncode(s)) {
780                                 Font font = getFont();
781                                 boolean sserif = font.getName().equals("SansSerif");
782                                 boolean serif = font.getName().equals("Serif");
783                                 if (sserif || serif) {
784                                     try {
785                                         preparePainting();
786                                         establishColor(getColor());
787                                         gen.writeln((sserif ? "/HelveticaLatin1" : "/TimesLatin1") + " " + gen.formatDouble(getFont().getSize()) + " F");
788
789                                         gen.saveGraphicsState();
790                                         Shape imclip = getClip();
791                                         writeClip(imclip);
792
793                                         AffineTransform trans = getTransform();
794                                         boolean newTransform = gen.getCurrentState().checkTransform(trans) && !trans.isIdentity();
795
796                                         if (newTransform) {
797                                             gen.concatMatrix(trans);
798                                         }
799
800                                         gen.writeln(gen.formatDouble(x)
801                                                     + " " + gen.formatDouble(y)
802                                                     + " M 1 -1 scale");
803
804                                         StringBuffer buf = new StringBuffer("(");
805                                         for (int i = 0; i < s.length(); i++) {
806                                             PSGenerator.escapeChar(s.charAt(i), buf);
807                                         }
808                                         buf.append(") t");
809
810                                         gen.writeln(buf.toString());
811
812                                         gen.restoreGraphicsState();
813                                     } catch (IOException e) {
814                                         System.err.println(e);
815                                     }
816
817                                     return;
818                                 }
819                             }
820
821                             super.drawString(s, x, y);
822                         }
823                     }
824
825                     @Override
826                     public boolean shouldBeClipped(Shape clip, Shape s) {
827                         if (clip == null || s == null) {
828                             return false;
829                         }
830
831                         return clip.getBounds2D().intersects(s.getBounds2D());
832                     }
833
834                     @Override
835                     public int processShape(Shape s) throws IOException {
836                         if (s instanceof Ellipse2D.Double) {
837                             Ellipse2D.Double ell = (Ellipse2D.Double) s;
838                             if (ell.height == ell.width) {
839                                 gen.writeln(gen.formatDouble(ell.x + ell.width / 2)
840                                             + " " + gen.formatDouble(ell.y + ell.height / 2)
841                                             + " " + gen.formatDouble(ell.width / 2)
842                                             + " " + gen.formatDouble(0d)
843                                             + " " + gen.formatDouble(360d)
844                                             + " arc cp");
845
846                                 return PathIterator.WIND_NON_ZERO;
847                             }
848                         } else if (s instanceof Path2D) {
849                             StringBuilder buffer = new StringBuilder();
850                             double[] coords = new double[2];
851                             PathIterator it = ((Path2D) s).getPathIterator(new AffineTransform());
852                             if (!it.isDone()) {
853                                 int type = it.currentSegment(coords);
854                                 if (type == PathIterator.SEG_MOVETO) {
855                                     buffer.append("[").append(gen.formatDouble(coords[0])).append(" ").append(gen.formatDouble(coords[1]));
856                                     it.next();
857                                 } else {
858                                     return super.processShape(s);
859                                 }
860                             } else {
861                                 return super.processShape(s);
862                             }
863
864                             for (; !it.isDone(); it.next()) {
865                                 int type = it.currentSegment(coords);
866                                 if (type == PathIterator.SEG_LINETO) {
867                                     buffer.append(" ").append(gen.formatDouble(coords[0])).append(" ").append(gen.formatDouble(coords[1]));
868                                 } else {
869                                     return super.processShape(s);
870                                 }
871                             }
872                             buffer.append("] DP");
873                             gen.writeln(buffer.toString());
874                             return PathIterator.WIND_NON_ZERO;
875                         }
876
877                         return super.processShape(s);
878                     }
879                 };
880                 g2d.setGraphicContext(new GraphicContext());
881             } catch (IOException e) { }
882
883             return g2d;
884         }
885
886         @Override
887         public void write() throws IOException {
888             if (g2d != null) {
889                 g2d.finish();
890             }
891             if (buffer != null && file != null) {
892                 FileOutputStream fos = new FileOutputStream(file);
893                 buffer.writeTo(fos);
894                 buffer.close();
895                 fos.flush();
896                 fos.close();
897             }
898             if (out != null) {
899                 out.close();
900             }
901         }
902
903         @Override
904         public void dispose() {
905             if (g2d != null) {
906                 g2d.dispose();
907             }
908         }
909     }
910
911     /**
912      * EPS Exporter
913      */
914     private static class EPSExporter extends PSExporter {
915
916         public EPSExporter() { }
917
918         @Override
919         public Graphics2D getGraphics2D(int width, int height, File file, final ExportParams params) {
920             this.file = file;
921             this.params = params;
922             try {
923                 if (file == null) {
924                     buffer = new ByteArrayOutputStream();
925                     out = new BufferedOutputStream(buffer);
926                 } else {
927                     out = new BufferedOutputStream(new FileOutputStream(file));
928                 }
929                 g2d = new EPSDocumentGraphics2D(true) {
930                     @Override
931                     protected void writePageHeader() throws IOException {
932                         super.writePageHeader();
933                         if (params.orientation == ExportParams.LANDSCAPE) {
934                             gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Landscape");
935                         } else {
936                             gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Portrait");
937                         }
938                         gen.writeln("/ReEncode { /MyEncoding exch def exch findfont dup length dict begin {def} forall /Encoding MyEncoding def currentdict end definefont } def");
939                         gen.writeln("/Helvetica /HelveticaLatin1 ISOLatin1Encoding ReEncode");
940                         gen.writeln("/Times /TimesLatin1 ISOLatin1Encoding ReEncode");
941
942                         // DP macro is used to draw an array as a polyline
943                         gen.writeln("/DP {/Points exch def Points 0 get Points 1 get M 2 2 Points length 1 sub {/i exch def Points i get Points i 1 add get L}for} def");
944                     }
945
946                     @Override
947                     public boolean shouldBeClipped(Shape clip, Shape s) {
948                         if (clip == null || s == null) {
949                             return false;
950                         }
951
952                         return clip.getBounds2D().intersects(s.getBounds2D());
953                     }
954
955                     @Override
956                     public void drawString(String s, float x, float y) {
957                         if (s != null && !s.isEmpty()) {
958                             CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder();
959                             if (encoder.canEncode(s)) {
960                                 Font font = getFont();
961                                 boolean sserif = font.getName().equals("SansSerif");
962                                 boolean serif = font.getName().equals("Serif");
963                                 if (sserif || serif) {
964                                     try {
965                                         preparePainting();
966                                         establishColor(getColor());
967                                         gen.writeln((sserif ? "/HelveticaLatin1" : "/TimesLatin1") + " " + gen.formatDouble(getFont().getSize()) + " F");
968
969                                         gen.saveGraphicsState();
970                                         Shape imclip = getClip();
971                                         writeClip(imclip);
972
973                                         AffineTransform trans = getTransform();
974                                         boolean newTransform = gen.getCurrentState().checkTransform(trans) && !trans.isIdentity();
975
976                                         if (newTransform) {
977                                             gen.concatMatrix(trans);
978                                         }
979
980                                         gen.writeln(gen.formatDouble(x)
981                                                     + " " + gen.formatDouble(y)
982                                                     + " M 1 -1 scale");
983
984                                         StringBuffer buf = new StringBuffer("(");
985                                         for (int i = 0; i < s.length(); i++) {
986                                             PSGenerator.escapeChar(s.charAt(i), buf);
987                                         }
988                                         buf.append(") t");
989
990                                         gen.writeln(buf.toString());
991
992                                         gen.restoreGraphicsState();
993                                     } catch (IOException e) {
994                                         System.err.println(e);
995                                     }
996
997                                     return;
998                                 }
999                             }
1000
1001                             super.drawString(s, x, y);
1002                         }
1003                     }
1004
1005                     @Override
1006                     public int processShape(Shape s) throws IOException {
1007                         if (s instanceof Ellipse2D.Double) {
1008                             Ellipse2D.Double ell = (Ellipse2D.Double) s;
1009                             if (ell.height == ell.width) {
1010                                 gen.writeln(gen.formatDouble(ell.x + ell.width / 2)
1011                                             + " " + gen.formatDouble(ell.y + ell.height / 2)
1012                                             + " " + gen.formatDouble(ell.width / 2)
1013                                             + " " + gen.formatDouble(0d)
1014                                             + " " + gen.formatDouble(360d)
1015                                             + " arc cp");
1016
1017                                 return PathIterator.WIND_NON_ZERO;
1018                             }
1019                         } else if (s instanceof Path2D) {
1020                             StringBuilder buffer = new StringBuilder();
1021                             double[] coords = new double[2];
1022                             PathIterator it = ((Path2D) s).getPathIterator(new AffineTransform());
1023                             if (!it.isDone()) {
1024                                 int type = it.currentSegment(coords);
1025                                 if (type == PathIterator.SEG_MOVETO) {
1026                                     buffer.append("[").append(gen.formatDouble(coords[0])).append(" ").append(gen.formatDouble(coords[1]));
1027                                     it.next();
1028                                 } else {
1029                                     return super.processShape(s);
1030                                 }
1031                             } else {
1032                                 return super.processShape(s);
1033                             }
1034
1035                             for (; !it.isDone(); it.next()) {
1036                                 int type = it.currentSegment(coords);
1037                                 if (type == PathIterator.SEG_LINETO) {
1038                                     buffer.append(" ").append(gen.formatDouble(coords[0])).append(" ").append(gen.formatDouble(coords[1]));
1039                                 } else {
1040                                     return super.processShape(s);
1041                                 }
1042                             }
1043                             buffer.append("] DP");
1044                             gen.writeln(buffer.toString());
1045                             return PathIterator.WIND_NON_ZERO;
1046                         }
1047
1048                         return super.processShape(s);
1049                     }
1050
1051                 };
1052                 g2d.setupDocument(out, width, height);
1053                 g2d.setGraphicContext(new GraphicContext());
1054             } catch (IOException e) { }
1055
1056             return g2d;
1057         }
1058     }
1059
1060     /**
1061      * EMF Exporter
1062      */
1063     private static class EMFExporter extends Exporter {
1064
1065         private OutputStream out;
1066         private Class<Graphics2D> g2dClass;
1067         private Constructor<Graphics2D> g2dCtor;
1068         private Graphics2D g2d;
1069         private ByteArrayOutputStream buffer;
1070
1071         public EMFExporter() {
1072             final Class<Graphics2D> g2dClass;
1073             try {
1074                 g2dClass = (Class<Graphics2D>) Class.forName("org.freehep.graphicsio.emf.EMFGraphics2D");
1075             } catch (ClassNotFoundException e) {
1076                 throw new RuntimeException("This Scilab build does not provide EMF support");
1077             }
1078
1079             final Constructor[] ctors = g2dClass.getDeclaredConstructors();
1080             Constructor ctor = null;
1081             for (int i = 0; i < ctors.length; i++) {
1082                 ctor = ctors[i];
1083                 final Type[] args = ctor.getGenericParameterTypes();
1084                 if (args.length != 2) {
1085                     continue;
1086                 }
1087                 if (args[0] != OutputStream.class) {
1088                     continue;
1089                 }
1090                 if (args[1] != Dimension.class) {
1091                     continue;
1092                 }
1093
1094                 g2dCtor = ctor;
1095                 break;
1096             }
1097         }
1098
1099         @Override
1100         public Graphics2D getGraphics2D(int width, int height, File file, final ExportParams params) {
1101             this.file = file;
1102             try {
1103                 if (file == null) {
1104                     buffer = new ByteArrayOutputStream();
1105                     out = new BufferedOutputStream(buffer);
1106                 } else {
1107                     out = new BufferedOutputStream(new FileOutputStream(file));
1108                 }
1109                 if (params.orientation == ExportParams.LANDSCAPE) {
1110                     g2d = g2dCtor.newInstance(out, new Dimension(height, width));
1111                     g2dClass.getMethod("startExport").invoke(g2d);
1112                     AffineTransform transf = AffineTransform.getRotateInstance(Math.PI / 2);
1113                     transf.preConcatenate(AffineTransform.getTranslateInstance(height, 0));
1114                     g2d.setTransform(transf);
1115                 } else {
1116                     g2d = g2dCtor.newInstance(out, new Dimension(width, height));
1117                     g2dClass.getMethod("startExport").invoke(g2d);
1118                 }
1119             } catch (IOException e) {
1120             } catch (IllegalAccessException e) {
1121             } catch (IllegalArgumentException e) {
1122             } catch (InvocationTargetException e) {
1123             } catch (NoSuchMethodException e) {
1124             } catch (SecurityException e) {
1125             } catch (InstantiationException e) {
1126             }
1127
1128             return g2d;
1129         }
1130
1131         @Override
1132         public void write() throws IOException {
1133             if (g2d != null) {
1134                 try {
1135                     g2dClass.getMethod("endExport").invoke(g2d);
1136                     g2dClass.getMethod("closeStream").invoke(g2d);
1137                 } catch (IllegalAccessException e) {
1138                 } catch (IllegalArgumentException e) {
1139                 } catch (InvocationTargetException e) {
1140                 } catch (NoSuchMethodException e) {
1141                 } catch (SecurityException e) {
1142                 }
1143             }
1144             if (buffer != null && file != null) {
1145                 FileOutputStream fos = new FileOutputStream(file);
1146                 buffer.writeTo(fos);
1147                 buffer.close();
1148                 fos.flush();
1149                 fos.close();
1150             }
1151             if (out != null) {
1152                 out.close();
1153             }
1154         }
1155
1156         @Override
1157         public void dispose() {
1158             if (g2d != null) {
1159                 g2d.dispose();
1160             }
1161         }
1162     }
1163 }