/*
* This code is part of Project Waterloo from King's College London
*
*
* Copyright King's College London 2013-
*
* @author Malcolm Lidierth, King's College London [Contact]
*
* Project Waterloo is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Project Waterloo 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
*/
package kcl.waterloo.deploy.svg;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Iterator;
import java.util.zip.GZIPOutputStream;
import kcl.waterloo.common.deploy.AbstractDeployableGraphics2D;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.w3c.dom.Element;
/**
*
* WSVGGraphics2D - a Graphics2D subclass that generates SVG.
*
*
* WSVGGraphics2D is simply a wrapper class for Apache Batik SVGGraphics2D
* instances. Apache Batik does all the real work. WSVGGraphics2D justs adds a
* few groups to the DOM to facilitate interaction with the charts using
* JavaScript, e.g. via d3d.js - and adds HTML file writing support together
* with support for translating the SVG to HTML5 canvas commands using
* canvg.js.
*
*
* For info about canvg.js and known issues see the README.txt file in package
* {@code kcl.waterloo.deploy.canvg}
*
*
* Usage:
*
*
* WSVGGraphics2D instances are used in AWT/Swing component paint methods in the
* same way as any other Graphics2D class object. When painting is complete, a
* Processing script can be accessed by calling the {@code getCode()} method on
* the instance which returns the SVG as a String. Alternatively, call one of
* the {@code write(...)} methods to save the code to file. These methods
* optionally support the creation of HTML files, together with CSS style
* sheets, to display the generated script in an HTML5 browser canvas via
* canvg.js [see https://code.google.com/p/canvg/].
*
*
* The methods are not thread-safe. All painting should be done on the
* EDT.
*
*
* To create a PDEGraphics2D instance, pass the constructor an instance of a
* standard Graphics2D object
*
*
* PDEGraphics2D g= new PDEGraphics2D(Graphics2D g)
*
*
* Write methods:
*
* The {@code write} methods create files following a convention that is
* intended to mimic those used in PDEGraphics2D.
*
*
* The convention is as follows
*
* If you write a file named e.g. "script_name.svg", the file will be created in
* a folder named "script_name". This folder will be created if it does not
* exist. Thus a file specified as "/Users/ML/Documents/script_name", would
* create "/Users/ML/Documents/script_name/script_name.svg" - the .svg extension
* will be added if not specified.
*
*
* If the the paint methods called with the PDEGraphics2D instance include calls
* to the {@code drawImage} methods, the required images will be added to a
* "/script_name/data/" folder as PNG images. Gradient paints, including
* MultipleGradientPaints, are also supported through drawing images and require
* a /data folder.
*
*
* The {@code write} methods also support the creation of HTML files. If the
* {@code htmlFlag} on input is {@code true}, the following files can be
* created:
*
* script_name.html - a simple file that shows only the
* graphics
*
* index.html - a styled file that includes titles etc
*
* WSVGGraphics.css - styling sheet for index.html
*
* index.xml - an xml file describing the contents of the /data
* folder
*
* htppd.py - a Python 2.7 script that displays index.html in
* the system default brpwser using a local server. Use this if the default
* browser's security settings prevent JavaScript accessing the local file
* system directly.
*
* See the {@code write{)} methods for full details of the options available.
*
*
*
* WSVGGraphics2D has been tested with the following browsers
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* Adding code and comments to the scripts:
* Several methods allow the generated script to be modified on-the-fly when
* users have access to the paint methods of the components being painted. These
* methods are defined in AbstractDeployableGraphics2D, the WSVGGraphics
* superclass.
*
* - public void setPaintObject(Object referenceObject)
* Adds an SVG group to DOM and adds subsequent elements to that group.
* - public void addComment(String comment)
* Adds a comment to the script at the current writing location. Format markers
* will be added automatically.
*
*
*
* Titles etc
*
* - public void setTitle(String title)
* Gives the script a title. This will added as the name of the displayed Window
* or displayed in HTML files. It will also be used to set the title tag in
* HTML.
* - public void setDescription(String description)
* The description will be displayed with index.html
* - public void setKeyWords(ArrayList
keyWords)
* Adds the specified strings as keywords to HTML documents.
* - public void addKeyWord(String word)
* Adds the keyword to the current list for HTML files.
*
*
*
* WSVGGraphics can only render graphics normally rendered through
* Java2D graphics calls. When painting using a proprietary, platform-specific,
* Java look-and-feel, some features may be painted using native graphics calls
* and will not appear in the generated scripts. For example, JFrames will not
* be decorated with close/maximize buttons etc.
*
*
* In addition, some components may not render fully perhaps because the
* componentUIs are cloning the wrapped Graphics2D instance instead of the
* WSVGGraphics instance or maybe generating a new instance with a call to
* Component.getGraphics(). This seems to affect mainly the Mac Aqua L&F. Some
* fixes/workarounds are included for specific L&Fs and AWT/Swing components
* where the cause of the problem has been identified.
*
* @version 1.0.0 @author Malcolm Lidierth, King's College London
* [Contact]
*
*/
public class WSVGGraphics2D extends AbstractDeployableGraphics2D {
String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
private Element lastAddedElement;
private String title = "Title";
private String description = "Description:";
public WSVGGraphics2D(SVGGraphics2D g) {
super(g);
}
@Override
public void append(Component c, int elapsedTime) {
}
@Override
public void append(Component c) {
}
@Override
public void setPaintObject(Object referenceObject) {
Element element = ((SVGGraphics2D) parentGraphics).getDOMFactory().createElementNS(svgNS, "g");
element.setAttribute("id", String.format("id%x_%x", (int) (System.currentTimeMillis() - startTime), referenceObject.hashCode()));
element.setAttribute("class", String.format("%s", referenceObject.getClass().toString()));
Element desc = ((SVGGraphics2D) parentGraphics).getDOMFactory().createElementNS(svgNS, "desc");
desc.setTextContent("wgraphic");
element.appendChild(desc);
if (lastAddedElement == null) {
((SVGGraphics2D) parentGraphics).getDOMTreeManager().appendGroup(element, null);
lastAddedElement = element;
} else {
lastAddedElement.appendChild(element);
((SVGGraphics2D) parentGraphics).getDOMTreeManager().appendGroup(element, null);
lastAddedElement = element;
}
}
@Override
public void addComment(String comment) {
((SVGGraphics2D) parentGraphics).getDOMFactory().createComment(comment);
}
@Override
public void prependCode(String code) {
}
@Override
public void addCode(String code) {
}
@Override
public void appendCode(String code) {
}
@Override
public boolean copyImage(Image img, int dx, int dy, int sx, int sy, int width, int height, Color bgcolor, ImageObserver observer) {
return true;
}
@Override
public Object clone() {
WSVGGraphics2D g = new WSVGGraphics2D((SVGGraphics2D) parentGraphics.create());
g.startTime = this.startTime;
g.lastAddedElement = this.lastAddedElement;
return g;
}
@Override
public Graphics create() {
return (Graphics) clone();
}
@Override
public Graphics create(int x, int y, int width, int height) {
WSVGGraphics2D g = new WSVGGraphics2D((SVGGraphics2D) parentGraphics.create(x, y, width, height));
g.startTime = this.startTime;
g.lastAddedElement = this.lastAddedElement;
return g;
}
public static void deploy(File f, String svgCode, Dimension dim, boolean inline, String jsLoc, String cssLoc, boolean httpdFlag) throws IOException {
deploy(f, svgCode, dim, inline, jsLoc, cssLoc, httpdFlag, null, "Title", "Description");
}
/**
*
* @param f SVG File to write to.
* @param svgCode SVG code as String
* @param dim the Dimensions of the component being painted
* @param inline if true, SVG code will be inlined in the HTML file
* @param jsLoc Location of the canvg.js file
* @param cssLoc Location of the CSS file
* @param httpdFlag If true, generated a Python script
* @throws IOException
*/
public static void deploy(File f, String svgCode, Dimension dim, boolean inline, String jsLoc, String cssLoc, boolean httpdFlag, ArrayList keyWords, String title, String description) throws IOException {
if (!f.getName().endsWith(".svg") && !f.getName().endsWith(".svg.gz")) {
throw new IOException("Not an svg file");
}
// Create a folder to parent the generated files
File folder;
folder = new File(f.getPath().replace(".svg", ""));
if (!folder.isDirectory()) {
folder.mkdir();
}
// If required, create the JavaScript files
if (jsLoc.equals("canvg-min.js") || jsLoc.equals("./canvg-min.js")) {
InputStream js = AbstractDeployableGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/common/deploy/canvg/canvg-min.js");
FileOutputStream output = new FileOutputStream(
folder.getPath()
.concat(File.separator)
.concat("canvg-min.js"));
while (js.available() > 0) {
byte[] buffer = new byte[js.available()];
int n = js.read(buffer, 0, js.available());
output.write(buffer, 0, n);
}
output.close();
js.close();
js = AbstractDeployableGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/common/deploy/canvg/rgbcolor-min.js");
output = new FileOutputStream(
folder.getPath()
.concat(File.separator)
.concat("rgbcolor-min.js"));
while (js.available() > 0) {
byte[] buffer = new byte[js.available()];
int n = js.read(buffer, 0, js.available());
output.write(buffer, 0, n);
}
output.close();
js.close();
InputStream lic = AbstractDeployableGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/common/deploy/canvg/MIT-LICENSE.txt");
FileOutputStream output2 = new FileOutputStream(
folder.getPath()
.concat(File.separator)
.concat("CANVG-LICENSE.TXT"));
while (lic.available() > 0) {
byte[] buffer = new byte[lic.available()];
int n = lic.read(buffer, 0, lic.available());
output2.write(buffer, 0, n);
}
output2.close();
lic.close();
} else {
}
Formatter cssFormatter = new Formatter(new StringBuilder(512));
if (cssLoc.equals("WSVGGraphics2D.css") || cssLoc.equals("./WSVGGraphics2D.css")) {
InputStream css = WSVGGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/deploy/svg/WSVGGraphics2D.css");
FileOutputStream cssFile = new FileOutputStream(
folder.getPath()
.concat(File.separator)
.concat("WSVGGraphics2D.css"));
OutputStreamWriter cssStream = new OutputStreamWriter(cssFile, "UTF-8");
while (css.available() > 0) {
byte[] buffer = new byte[css.available()];
int n = css.read(buffer, 0, css.available());
String s = new String(buffer);
s = s.replaceAll("1111111111", Integer.valueOf(dim.width).toString());
s = s.replaceAll("2222222222", Integer.valueOf(dim.height).toString());
cssFile.write(s.getBytes("UTF-8"));
}
css.close();
cssFile.close();
cssStream.close();
} else if (cssLoc.equals("index.html") || cssLoc.equals("./index.html")) {
InputStream css = WSVGGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/deploy/svg/WSVGGraphics2D.css");
while (css.available() > 0) {
byte[] buffer = new byte[css.available()];
int n = css.read(buffer, 0, css.available());
cssFormatter.format("%s", new String(buffer, 0, n));
}
}
if (httpdFlag) {
InputStream py = WSVGGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/deploy/svg/httpd.py");
FileOutputStream output = new FileOutputStream(
folder.getPath()
.concat(File.separator)
.concat("httpd.py"));
while (py.available() > 0) {
byte[] buffer = new byte[py.available()];
int n = py.read(buffer, 0, py.available());
output.write(buffer, 0, n);
}
output.close();
}
Formatter keyWordString = new Formatter(new StringBuilder(512));
keyWordString.format(" it = keyWords.iterator(); it.hasNext();) {
String word = it.next();
if (it.hasNext()) {
keyWordString.format("%s, ", word);
} else {
keyWordString.format("%s", word);
}
}
}
keyWordString.format("\">%n");
InputStream index;
/*
User-specified HTML template to use instead of the one embedded in the
jar file.
*/
String userHTML = "";
if (!userHTML.isEmpty() && new File(userHTML).isFile()) {
index = new FileInputStream(new File(userHTML));
} else {
index = WSVGGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/deploy/svg/index.html");
}
FileOutputStream indexFile = new FileOutputStream(
folder.getPath()
.concat(File.separator)
.concat("index.html"));
OutputStreamWriter indexStream = new OutputStreamWriter(indexFile, "UTF-8");
while (index.available() > 0) {
byte[] buffer = new byte[index.available()];
int n = index.read(buffer, 0, index.available());
String s = new String(buffer);
if (s.contains("")) {
if (inline) {
// Inline the svg code
int i = svgCode.indexOf("
Ads help maintain this website.