8264838: IGV: enhance graph export functionality

Co-authored-by: Roberto Castañeda Lozano <rcastanedalo@openjdk.org>
Co-authored-by: Nils Eliasson <neliasso@openjdk.org>
Reviewed-by: chagedorn, thartmann
This commit is contained in:
Nils Eliasson 2021-11-29 08:19:24 +00:00
parent 0c7a4b8aa8
commit aed53eea5e
12 changed files with 94 additions and 279 deletions

View File

@ -0,0 +1 @@
15

View File

@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Oracle nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>IdealGraphVisualizer-parent</artifactId>
<groupId>com.sun.hotspot.igv</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.sun.hotspot.igv</groupId>
<artifactId>BatikSVGProxy</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>BatikSVGProxy</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.netbeans.utilities</groupId>
<artifactId>nbm-maven-plugin</artifactId>
<version>${nbmmvnplugin.version}</version>
<extensions>true</extensions>
<configuration>
<publicPackages>
<publicPackage>com.sun.hotspot.igv.svg</publicPackage>
</publicPackages>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${mvncompilerplugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${mvnjarplugin.version}</version>
<configuration>
<!-- to have the jar plugin pickup the nbm generated manifest -->
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,114 +0,0 @@
/*
* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
package com.sun.hotspot.igv.svg;
import java.awt.Graphics2D;
import java.io.Writer;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.w3c.dom.DOMImplementation;
/**
* Utility class
* @author Thomas Wuerthinger
*/
public class BatikSVG {
private BatikSVG() {
}
private static Constructor SVGGraphics2DConstructor;
private static Method streamMethod;
private static Method createDefaultMethod;
private static Method getDOMImplementationMethod;
private static Method setEmbeddedFontsOnMethod;
private static Class<?> classSVGGraphics2D;
/**
* Creates a graphics object that allows to be exported to SVG data using the {@link #printToStream(Graphics2D, Writer, boolean) printToStream} method.
* @return the newly created Graphics2D object or null if the library does not exist
*/
public static Graphics2D createGraphicsObject() {
try {
if (SVGGraphics2DConstructor == null) {
String batikJar = System.getenv().get("IGV_BATIK_JAR");
if (batikJar == null) {
return null;
}
// Load batik in it's own class loader since some it's support jars interfere with the JDK
URL url = new File(batikJar).toURI().toURL();
ClassLoader cl = new URLClassLoader(new URL[] { url });
Class<?> classGenericDOMImplementation = cl.loadClass("org.apache.batik.dom.GenericDOMImplementation");
Class<?> classSVGGeneratorContext = cl.loadClass("org.apache.batik.svggen.SVGGeneratorContext");
classSVGGraphics2D = cl.loadClass("org.apache.batik.svggen.SVGGraphics2D");
getDOMImplementationMethod = classGenericDOMImplementation.getDeclaredMethod("getDOMImplementation", new Class[0]);
createDefaultMethod = classSVGGeneratorContext.getDeclaredMethod("createDefault", new Class[]{org.w3c.dom.Document.class});
setEmbeddedFontsOnMethod = classSVGGeneratorContext.getDeclaredMethod("setEmbeddedFontsOn", new Class[]{boolean.class});
streamMethod = classSVGGraphics2D.getDeclaredMethod("stream", Writer.class, boolean.class);
SVGGraphics2DConstructor = classSVGGraphics2D.getConstructor(classSVGGeneratorContext, boolean.class);
}
DOMImplementation dom = (DOMImplementation) getDOMImplementationMethod.invoke(null);
org.w3c.dom.Document document = dom.createDocument("http://www.w3.org/2000/svg", "svg", null);
Object ctx = createDefaultMethod.invoke(null, document);
setEmbeddedFontsOnMethod.invoke(ctx, true);
Graphics2D svgGenerator = (Graphics2D) SVGGraphics2DConstructor.newInstance(ctx, true);
return svgGenerator;
} catch (ClassNotFoundException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
} catch (IllegalAccessException e) {
return null;
} catch (InvocationTargetException e) {
return null;
} catch (InstantiationException e) {
return null;
} catch (MalformedURLException e) {
return null;
}
}
/**
* Serializes a graphics object to a stream in SVG format.
* @param svgGenerator the graphics object. Only graphics objects created by the {@link #createGraphicsObject() createGraphicsObject} method are valid.
* @param stream the stream to which the data is written
* @param useCSS whether to use CSS styles in the SVG output
*/
public static void printToStream(Graphics2D svgGenerator, Writer stream, boolean useCSS) {
assert classSVGGraphics2D != null;
assert classSVGGraphics2D.isInstance(svgGenerator);
try {
streamMethod.invoke(svgGenerator, stream, useCSS);
} catch (IllegalAccessException e) {
assert false;
} catch (InvocationTargetException e) {
assert false;
}
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/**
* This package is used to proxy the SVG export functionality of the BatikSVG library. Reflection is used such that the
* library is optional and need not be present at build time.
*/
package com.sun.hotspot.igv.svg;

View File

@ -1,5 +0,0 @@
Manifest-Version: 1.0
OpenIDE-Module: com.sun.hotspot.igv.svg
OpenIDE-Module-Localizing-Bundle: com/sun/hotspot/igv/svg/Bundle.properties
OpenIDE-Module-Specification-Version: 1.0

View File

@ -81,11 +81,6 @@
<artifactId>SelectionCoordinator</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sun.hotspot.igv</groupId>
<artifactId>BatikSVGProxy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sun.hotspot.igv</groupId>
<artifactId>Settings</artifactId>
@ -151,6 +146,21 @@
<artifactId>org-netbeans-api-visual</artifactId>
<version>${netbeans.version}</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-dom</artifactId>
<version>${batik.version}</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
<version>${batik.version}</version>
</dependency>
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>${openpdf.version}</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -27,6 +27,7 @@ package com.sun.hotspot.igv.view;
import com.sun.hotspot.igv.graph.Figure;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.Collection;
import java.util.List;
import javax.swing.JComponent;
@ -44,7 +45,7 @@ interface DiagramViewer {
PANNING,
}
public void paint(Graphics2D svgGenerator);
public void paint(Graphics2D generator);
public Lookup getLookup();
@ -70,4 +71,6 @@ interface DiagramViewer {
public void setInteractionMode(InteractionMode mode);
public Rectangle getBounds();
}

View File

@ -36,7 +36,6 @@ import com.sun.hotspot.igv.filter.FilterChainProvider;
import com.sun.hotspot.igv.graph.Diagram;
import com.sun.hotspot.igv.graph.Figure;
import com.sun.hotspot.igv.graph.services.DiagramProvider;
import com.sun.hotspot.igv.svg.BatikSVG;
import com.sun.hotspot.igv.util.LookupHistory;
import com.sun.hotspot.igv.util.RangeSlider;
import com.sun.hotspot.igv.view.actions.*;
@ -48,10 +47,21 @@ import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.*;
import javax.swing.*;
import javax.swing.border.Border;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGeneratorContext;
import org.apache.batik.svggen.SVGGraphics2D;
import com.lowagie.text.Document;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfGraphics2D;
import org.w3c.dom.DOMImplementation;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.actions.RedoAction;
@ -103,30 +113,14 @@ public final class EditorTopComponent extends TopComponent implements PropertyCh
@Override
public void export(File f) {
Graphics2D svgGenerator = BatikSVG.createGraphicsObject();
if (svgGenerator == null) {
NotifyDescriptor message = new NotifyDescriptor.Message("For export to SVG files the Batik SVG Toolkit must be intalled.", NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notifyLater(message);
String lcFileName = f.getName().toLowerCase();
if (lcFileName.endsWith(".pdf")) {
exportToPDF(scene, f);
} else if (lcFileName.endsWith(".svg")) {
exportToSVG(scene, f);
} else {
scene.paint(svgGenerator);
FileOutputStream os = null;
try {
os = new FileOutputStream(f);
Writer out = new OutputStreamWriter(os, UTF_8);
BatikSVG.printToStream(svgGenerator, out, true);
} catch (FileNotFoundException e) {
NotifyDescriptor message = new NotifyDescriptor.Message("For export to SVG files the Batik SVG Toolkit must be intalled.", NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notifyLater(message);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
}
}
}
NotifyDescriptor message = new NotifyDescriptor.Message("Unknown image file extension: expected either '.pdf' or '.svg'", NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notifyLater(message);
}
}
};
@ -639,5 +633,47 @@ public final class EditorTopComponent extends TopComponent implements PropertyCh
@Override
protected Object writeReplace() throws ObjectStreamException {
throw new NotSerializableException();
}
}
private static void exportToPDF(DiagramViewer scene, File f) {
int width = scene.getBounds().width;
int height = scene.getBounds().height;
com.lowagie.text.Document document = new Document(new Rectangle(width, height));
PdfWriter writer = null;
try {
writer = PdfWriter.getInstance(document, new FileOutputStream(f));
writer.setCloseStream(true);
document.open();
PdfContentByte contentByte = writer.getDirectContent();
PdfTemplate template = contentByte.createTemplate(width, height);
PdfGraphics2D pdfGenerator = new PdfGraphics2D(contentByte, width, height);
scene.paint(pdfGenerator);
pdfGenerator.dispose();
contentByte.addTemplate(template, 0, 0);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (document.isOpen()) {
document.close();
}
if (writer != null) {
writer.close();
}
}
}
private static void exportToSVG(DiagramViewer scene, File f) {
DOMImplementation dom = GenericDOMImplementation.getDOMImplementation();
org.w3c.dom.Document document = dom.createDocument("http://www.w3.org/2000/svg", "svg", null);
SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(document);
ctx.setEmbeddedFontsOn(true);
SVGGraphics2D svgGenerator = new SVGGraphics2D(ctx, true);
scene.paint(svgGenerator);
try (FileOutputStream os = new FileOutputStream(f)) {
Writer out = new OutputStreamWriter(os, StandardCharsets.UTF_8);
svgGenerator.stream(out, true);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -45,7 +45,7 @@ public final class ExportAction extends CallableSystemAction implements LookupLi
private final Lookup.Result<ExportCookie> result;
public ExportAction() {
putValue(Action.SHORT_DESCRIPTION, "Export current graph as SVG file");
putValue(Action.SHORT_DESCRIPTION, "Export current graph as image file");
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_MASK));
lookup = Utilities.actionsGlobalContext();
result = lookup.lookup(new Lookup.Template<>(ExportCookie.class));
@ -66,12 +66,15 @@ public final class ExportAction extends CallableSystemAction implements LookupLi
@Override
public boolean accept(File f) {
return true;
String lcFileName = f.getName().toLowerCase();
return lcFileName.endsWith(".pdf") ||
lcFileName.endsWith(".svg") ||
f.isDirectory();
}
@Override
public String getDescription() {
return "SVG files (*.svg)";
return "Image files (*.pdf, *.svg)";
}
});
fc.setCurrentDirectory(new File(Settings.get().get(Settings.DIRECTORY, Settings.DIRECTORY_DEFAULT)));
@ -80,7 +83,7 @@ public final class ExportAction extends CallableSystemAction implements LookupLi
if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
if (!file.getName().contains(".")) {
file = new File(file.getAbsolutePath() + ".svg");
file = new File(file.getAbsolutePath() + ".pdf");
}
File dir = file;

View File

@ -142,11 +142,6 @@
<artifactId>Graal</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>BatikSVGProxy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>View</artifactId>

View File

@ -86,16 +86,17 @@
<module>ServerCompiler</module>
<module>FilterWindow</module>
<module>Graal</module>
<module>BatikSVGProxy</module>
<module>View</module>
</modules>
<properties>
<netbeans.version>RELEASE123</netbeans.version>
<swinglayouts.version>1.0.2</swinglayouts.version>
<nbmmvnplugin.version>4.3</nbmmvnplugin.version>
<nbmmvnplugin.version>4.6</nbmmvnplugin.version>
<mvncompilerplugin.version>3.8.1</mvncompilerplugin.version>
<mvnjarplugin.version>3.1.2</mvnjarplugin.version>
<mvnjarplugin.version>3.2.0</mvnjarplugin.version>
<junit.version>4.13.2</junit.version>
<batik.version>1.14</batik.version>
<openpdf.version>1.3.26</openpdf.version>
<brandingToken>idealgraphvisualizer</brandingToken>
</properties>
</project>