/*
* 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.pde;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.MultipleGradientPaint;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Transparency;
import java.awt.Window;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.VolatileImage;
import java.awt.image.renderable.RenderableImage;
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.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.Date;
import java.util.Formatter;
import java.util.Iterator;
import java.util.LinkedHashMap;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.UIManager;
import kcl.waterloo.common.deploy.AbstractDeployableGraphics2D;
import kcl.waterloo.logging.CommonLogger;
/**
*
* PDEGraphics2D - a Graphics2D subclass that generates a script using the
* Processing graphics language.
*
*
* The generated script may be displayed using the open-source, multi-platform
* Processing
* application
* and displayed in web pages using the processing.js javascript when graphics will be rendered
* to an HTML5 canvas element.
*
*
* Usage:
*
*
* PDEGraphics2D 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 script 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.
*
*
* 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, together with the size of the canvas to
* paint:
*
*
* PDEGraphics2D g= new PDEGraphics2D(Graphics2D g, int w, int h)
* or
* PDEGraphics2D g= new PDEGraphics2D(Graphics2D g, Dimension d)
*
*
* Alternatively, pass the component to paint as the input to one of the static
* paint methods:
*
*
* PDEGraphics2D g=PDEGraphics2D.paint(Component c);
* PDEGraphics2D g=PDEGraphics2D.paint(JComponent c);
* PDEGraphics2D g=PDEGraphics2D.paint(Window c);
* PDEGraphics2D g=PDEGraphics2D.paint(JApplet c);
*
*
* Creating animations:
*
* The PDEGraphics2D {@code append()} methods can be used to append paints to
* PDEGraphics2D instance to create animations using Processing. The
* {@code append()} method should be called after the first use of the instance
* to paint a component.
*
*
* Write methods:
*
* The {@code write} methods create files following a convention that is
* intended to facilitate the use of the generated scripts with both the Processing application
* and processing.js
* javascript
*
*
* The convention is as follows
*
* If you write a file named e.g. "script_name.pde", 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.pde" - the .pde 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
*
* PDEGraphics2D.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.
*
*
*
* PDEGraphics2D 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 PDEGraphics
* superclass.
*
* - public void setPaintObject(Object referenceObject)
* Places a comment in file. Custom-subclasses could override this to do
* something more complex.
* - public void addComment(String comment)
* Adds a comment to the script at the current writing location. Format markers
* will be added automatically.
* - public void addCode(String comment)
* Inserts the specified code at the current write location
* - public void prependCode(String code)
* Prepends the specified code to the Processing script before
* {@code void setup() {...}}
* - public void appendCode(String code)
* Adds the code to the end of the generated script.
*
*
*
* 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.
*
*
*
* PDEGraphics 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
* PDEGraphics2D 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.
*
*
* Revisions:
*
* 14.05.2013 corrected drawString(AttributedCharacterIterator iterator, float
* x, float y).
*
* 14.05.2013 corrected handling of special cases from processShape(...).
*
* 15.05.2013 improved support for quadTo: now built from flattened
* pathiterator: heavy-handed perhaps but works with any control points and
* produces relatively compact output (2-3 lines of processing code per
* SEG_QUADTO).
*
* 15.05.2013 Add support for rotated text.
*
* 18.05.2013 Improved write options, CSS options etc.
*
* 27.05.2013 Minor improvements: colors, sizing of images.
*
* @version 1.0.0c @author Malcolm Lidierth, King's College London
* [Contact]
*
*/
public class PDEGraphics2D extends AbstractDeployableGraphics2D implements Cloneable {
/**
* When true, text will be rendered as shapes rather than by using
* Processing's text mechanisms.
*
* Note that:
*
* - setting this true hugely increases file sizes
-
*
- text will be drawn in outline - not filled - as filling is:
-
*
* - complex
* - requires features not presently implemented in Processing.js
*
*
*/
private boolean textAsShapes = false;
/**
* Stroke used when rendering text as shapes.
*
* This static variable applies across instances of the PDEGraphics2D class
* in a JVM session.
*/
private static final BasicStroke glyphStroke = new BasicStroke(0.5f);
/**
* The current stroke weight
*/
private float strokeLineWidth = -1f;
/**
* The current stroke end cap
*/
private int strokeEndCap = -1;
/**
* The current stroke join mode
*/
private int strokeJoin = -1;
/**
* List of custom shapes
*/
private ArrayList customShapes = new ArrayList();
private LinkedHashMap code = new LinkedHashMap();
/**
* Dimensions of the drawn component
*/
private Dimension dimension;
/**
* Map of images to be drawn
*/
private LinkedHashMap images = new LinkedHashMap();
private LinkedHashMap imageDim = new LinkedHashMap();
private Font baseFont = null;
private LinkedHashMap fontList = new LinkedHashMap();
String prependedCode = "";
String appendedCode = "";
/**
* For HTML etc
*/
private ArrayList keyWords = new ArrayList();
private String title = "Title";
private String description = "Description:";
/**
* User-specified HTML template to use instead of the one embedded in the
* jar file.
*/
private static String userHTML = "";
/**
* L&F specific workarounds/fixes
*/
/**
* Constants
*/
/**
* MacOS Aqua L&F
*/
public final static int aquaJButton = 1;
public final static int aquaFixes = aquaJButton;
/**
* L&F workarounds for this instance.
*/
private int lfFixes = UIManager.getLookAndFeel().getName().contains("Mac") ? aquaFixes : 0;
/**
* Set true to generate comments in the code
*/
private boolean verbose = true;
/**
* The last color specfication written to file. This is static as painting
* is done on the EDT so a spec can be shared.
*/
private static String colorString = null;
private static final CommonLogger logger = new CommonLogger(PDEGraphics2D.class);
/**
* Private constructor for internal use.
*
* @param g Graphics2D object for the java component to be drawn.
*/
private PDEGraphics2D(Graphics2D g) {
super(g);
}
/**
* Constructs an initialised PDEGraphics2D object of specified width and
* height.
*
* @param g Graphics2D object for the java component to be drawn.
* @param w the width of the component
* @param h the height of the component
*/
public PDEGraphics2D(Graphics2D g, int w, int h) {
this(g, new Dimension(w, h));
}
/**
* Constructs an initialised PDEGraphics2D object of specified width and
* height.
*
* @param g Graphics2D object for the java component to be drawn.
* @param d Dimension of the component
*/
public PDEGraphics2D(Graphics2D g, Dimension d) {
super(g);
pictures.add(0, new Formatter(new StringBuilder(1024)));
initialise(d);
}
/**
* Internal use.
*
* @param d a Dimension instance
*/
private void initialise(Dimension d) {
if (parentGraphics != null && getFont() != null) {
baseFont = getFont();
fontList.put(getFont().getName(), getFont());
}
dimension = d;
}
/**
* Convenience static method for painting an AWT Component and its contents.
* Returns a PDEGraphics instance that is ready to write to file.
*
* @param c the component to paint
* @return a ready-to-write PDEGraphics instance.
*/
public static PDEGraphics2D paint(final Component c) {
final PDEGraphics2D g = new PDEGraphics2D((Graphics2D) c.getGraphics(), c.getSize());
c.paint(g);
return g;
}
/**
* Convenience static method for painting an Swing JComponent and its
* contents. Returns a PDEGraphics instance that is ready to write to file.
*
* @param c the component to paint
* @return a ready-to-write PDEGraphics instance.
*/
public static PDEGraphics2D paint(final JComponent c) {
final PDEGraphics2D g = new PDEGraphics2D((Graphics2D) c.getGraphics(), c.getSize());
c.paint(g);
return g;
}
/**
* Convenience static method for painting an AWT Window and its contents.
* Returns a PDEGraphics instance that is ready to write to file.
*
* @param c the component to paint
* @return a ready-to-write PDEGraphics instance.
*/
public static PDEGraphics2D paint(final Window c) {
final PDEGraphics2D g = new PDEGraphics2D((Graphics2D) c.getGraphics(), c.getSize());
c.paint(g);
return g;
}
/**
* Convenience static method for painting an Swing JApplet and its contents.
* Returns a PDEGraphics instance that is ready to write to file.
*
* @param c the component to paint
* @return a ready-to-write PDEGraphics instance.
*/
public static PDEGraphics2D paint(final JApplet c) {
final PDEGraphics2D g = new PDEGraphics2D((Graphics2D) c.getGraphics(), c.getSize());
c.paint(g);
return g;
}
/**
* Returns an integer value, the bits of which indicate the use of
* fixes/workarounds for the look and feel in use.
*
* @return the lfFixes
*/
public int getLFFixes() {
return lfFixes;
}
/**
* Returns a shallow copy the the keywords.
*
* @return an ArrayList
*/
public ArrayList getKeyWords() {
return new ArrayList(keyWords);
}
/**
* Sets the keyWords list.
*
* @param keyWords the keyWords to set
*/
public void setKeyWords(ArrayList keyWords) {
this.keyWords = keyWords;
}
public void addKeyWord(String word) {
keyWords.add(word);
}
/**
* Returns the title.
*
* @return the title
*/
public String getTitle() {
return title;
}
/**
* Creates a title. This will be display in index.html and used as the frame
* title by Processing.app.
*
* @param title the title to set
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Returns the description.
*
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Creates a description. This will be display in index.html.
*
* @param description the description to set
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Return the textAsShapes setting.
*
* If true, text will be rendered as glyphs. If false, the default
* Processing font will be used.
*
* @return the textAsShapes
*/
public boolean isTextAsShapes() {
return textAsShapes;
}
/**
* Sets the textAsShapes setting. If true, text will be rendered as glyphs.
* If false, the default Processing font will be used.
*
* @param textAsShapes the textAsShapes to set
*/
public void setTextAsShapes(boolean textAsShapes) {
this.textAsShapes = textAsShapes;
}
@Override
public Graphics create() {
return (Graphics) clone();
}
@Override
public Graphics create(int x, int y, int width, int height) {
PDEGraphics2D g = new PDEGraphics2D((Graphics2D) parentGraphics.create(x, y, width, height));
g.customShapes = customShapes;
g.pictures = pictures;
g.setTextAsShapes(isTextAsShapes());
g.images = images;
g.imageDim = imageDim;
g.dimension = dimension;
g.lfFixes = lfFixes;
g.fontList = fontList;
g.code = code;
g.strokeEndCap = strokeEndCap;
g.strokeJoin = strokeJoin;
g.strokeLineWidth = strokeLineWidth;
g.parentGraphics.setColor(getColor());
return g;
}
/**
* Appends a paint of the specified component to this instance. The
* component should have the same dimensions as the graphics for this
* instance.
*
* The appended graphics will be painted, when deployed to the target, at
* the time specified (+ the actionDelay constant value).
*
* @param c the component to paint
* @param elapsedTime the time to paint this component in the deployed code.
*/
@Override
public void append(Component c, int elapsedTime) {
pictures.add(new Formatter(new StringBuilder(1024)));
latency.add(elapsedTime);
c.paint(this);
}
/**
* Appends a paint of the specified component to this instance. The
* component should have the same dimensions as the graphics for this
* instance.
*
* This is equivalent to {@code append(c, elapsedTime)} where elapsedTime is
* the interval in milliseconds between the instantiation of this instance
* and this call to {@code append}.
*
* @param c the component to paint
*/
@Override
public void append(Component c) {
append(c, (int) (System.currentTimeMillis() - startTime));
}
/**
* Writes the generated code to file.
*
* Equivalent to:
*
* write(fileName, true, "./processing.js", "./PDEGraphics2D.css", true);
*
* @param fileName - String describing the file to write to
*
* The remaining arguments are relevant only when htmlFlag==true and
* will be ignored otherwise.
*
*
* Notes:
*
*
A file named "FILENAME.pde" will always be added to a folder name
* "FILENAME": the folder will be created if required following the usual
* Processing.app convention. If that folder already exists, any existing
* files will be overwritten
*
*
Additional data such as images will be written to a "/data"
* sub-folder.
*
*
* @throws IOException
*/
@Override
public void write(String fileName) throws IOException {
write(fileName, true, "./processing.js", "./PDEGraphics2D.css", true);
}
/**
* Writes the generated code to file.
*
* Equivalent to:
*
* write(fileName, htmlFlag, "./processing.js", "./PDEGraphics2D.css",
* true);
*
* @param fileName - String describing the file to write to
* @param htmlFlag - if true, html files to view the script will also be
* created.
*
* The remaining arguments are relevant only when htmlFlag==true and
* will be ignored otherwise.
*
*
* Notes:
*
*
A file named "FILENAME.pde" will always be added to a folder name
* "FILENAME": the folder will be created if required following the usual
* Processing.app convention. If that folder already exists, any existing
* files will be overwritten
*
*
Additional data such as images will be written to a "/data"
* sub-folder.
*
*
* @throws IOException
*/
public void write(String fileName, boolean htmlFlag) throws IOException {
write(fileName, htmlFlag, "./processing.js", "./PDEGraphics2D.css", true);
}
/**
* Writes the generated code to file.
*
* Equivalent to:
*
* write(fileName,htmlFlag, jsLoc, "./PDEGraphics2D.css", true);
*
* @param fileName - String describing the file to write to
* @param htmlFlag - if true, html files to view the script will also be
* created.
*
* The remaining arguments are relevant only when htmlFlag==true and
* will be ignored otherwise.
* @param jsLoc - String specifying the location of the processing.js file.
* If this is "processing.js" or "./processing.js", a copy of the JavaScript
* file will be created in the folder alongside the html files. Otherwise,
* the string should point to the location of a processing JavaScript file
* relative to the HTML file folder OR
* describe a URL.
*
* Notes:
*
*
* Notes:
*
*
A file named "FILENAME.pde" will always be added to a folder name
* "FILENAME": the folder will be created if required following the usual
* Processing.app convention. If that folder already exists, any existing
* files will be overwritten
*
*
Additional data such as images will be written to a "/data"
* sub-folder.
*
*
* @throws IOException
*/
public void write(String fileName, boolean htmlFlag, String jsLoc) throws IOException {
write(fileName, htmlFlag, jsLoc, "./PDEGraphics2D.css", true);
}
/**
* Writes the generated code to file.
*
* @param fileName - String describing the file to write to
* @param htmlFlag - if true, html files to view the script will also be
* created.
*
* The remaining arguments are relevant only when htmlFlag==true and
* will be ignored otherwise.
* @param jsLoc - String specifying the location of the processing.js file.
* If this is "processing.js" or "./processing.js", a copy of the JavaScript
* file will be created in the folder alongside the html files. Otherwise,
* the string should point to the location of a processing JavaScript file
* relative to the HTML file folder OR
* describe a URL.
* @param cssLoc - String specifying the location of the CSS styling. If
* this is "PDEGraphics2D.css" or "./PDEGraphics2D.css", a copy of the
* default CSS settings will be created in the folder alongside the html
* files. The the string may also point to the location of a suitable CSS
* file relative to the HTML file folder OR
* describe a URL. Alternatively, if cssLoc is "index.html" or
* "./index.html" the default CSS settings will be embedded in the heml
* file.
* @param httpdFlag If true, a Python 2.7 script ("httpd.py") will be
* created alongside the html files which will set up a local server and
* display index.html in the system browser. This can be useful if system or
* security settings prevent access to the local file system. The port to
* use will be obtained by the script from the OS.
*
*
* Notes:
*
*
A file named "FILENAME.pde" will always be added to a folder name
* "FILENAME": the folder will be created if required following the usual
* Processing.app convention. If that folder already exists, any existing
* files will be overwritten
*
*
Additional data such as images will be written to a "/data"
* sub-folder.
*
*
* @throws IOException
*/
public void write(String fileName, boolean htmlFlag, String jsLoc, String cssLoc, boolean httpdFlag) throws IOException {
if (!fileName.endsWith(".pde")) {
fileName = fileName.concat(".pde");
}
File f = new File(fileName);
String path = f.toURI().getPath().replace(f.getName(), "");
String pdeFile = path.concat(f.getName().replace(".pde", File.separator));
pdeFile = pdeFile.concat(f.getName());
write(new File(pdeFile), htmlFlag, jsLoc, cssLoc, httpdFlag);
}
/**
*
* Writes the generated code to file.
*
* @param f - the {@code File} to write to
* @param htmlFlag - if true, html files to view the script will also be
* created.
*
* The remaining arguments are relevant only when htmlFlag==true and
* will be ignored otherwise.
* @param jsLoc - String specifying the location of the processing.js file.
* If this is "processing.js" or "./processing.js", a copy of the JavaScript
* file will be created in the folder alongside the html files. Otherwise,
* the string should point to the location of a processing JavaScript file
* relative to the HTML file folder OR
* describe a URL.
* @param cssLoc - String specifying the location of the CSS styling. If
* this is "PDEGraphics2D.css" or "./PDEGraphics2D.css", a copy of the
* default CSS settings will be created in the folder alongside the html
* files. The the string may also point to the location of a suitable CSS
* file relative to the HTML file folder OR
* describe a URL. Alternatively, if cssLoc is "index.html" or
* "./index.html" the default CSS settings will be embedded in the heml
* file.
* @param httpdFlag If true, a Python 2.7 script ("httpd.py") will be
* created alongside the html files which will set up a local server and
* display index.html in the system browser. This can be useful if system or
* security settings prevent access to the local file system. The port to
* use will be obtained by the script from the OS.
*
*
* Notes:
*
*
A file named "FILENAME.pde" will always be added to a folder name
* "FILENAME": the folder will be created if required following the usual
* Processing.app convention. If that folder already exists, any existing
* files will be overwritten
*
*
Additional data such as images will be written to a "/data"
* sub-folder.
*
*
* @throws IOException
*/
public void write(File f, boolean htmlFlag, String jsLoc, String cssLoc, boolean httpdFlag) throws IOException {
if (!f.getName().endsWith(".pde")) {
throw new IOException("Not a pde file");
}
File folder;
if (f.getParentFile() != null && f.getParentFile().toString().endsWith(f.getName().replace(".pde", ""))) {
folder = new File(f.getParentFile().toString());
} else {
folder = new File(f.getPath().replace(f.getName(), ""));
}
if (!folder.isDirectory()) {
folder.mkdir();
}
String f2 = folder.getPath().concat(File.separator).concat(f.getName());
if (!f2.endsWith(".pde")) {
f2 = f2.concat(".pde");
}
super.write(f2);
if (images.size() > 0) {
InputStream image_lic = PDEGraphics2D.class.getClassLoader().getResourceAsStream("kcl/waterloo/deploy/NOTICE.TXT");
FileOutputStream output3 = new FileOutputStream(
folder.getPath()
.concat(File.separator)
.concat("NOTICE.TXT"));
try {
while (image_lic.available() > 0) {
byte[] buffer = new byte[image_lic.available()];
int n = image_lic.read(buffer, 0, image_lic.available());
output3.write(buffer, 0, n);
}
} finally {
output3.close();
}
image_lic.close();
File dataFolder = new File(folder.getPath().concat(File.separator).concat("data"));
if (!dataFolder.isDirectory()) {
dataFolder.mkdir();
}
for (Integer k : images.keySet()) {
ImageIcon icon = images.get(k);
Image img = icon.getImage();
String name = String.format("image_%d_%x.png", k, icon.hashCode());
File fi = new File(dataFolder.toString() + File.separator + name);
if (icon.getImageLoadStatus() == MediaTracker.COMPLETE) {
if (img instanceof RenderedImage) {
ImageIO.write((RenderedImage) img, "png", fi);
} else if (img instanceof VolatileImage) {
ImageIO.write(((VolatileImage) img).getSnapshot(), "png", fi);
} else {
try {
BufferedImage buffer = this.getDeviceConfiguration().createCompatibleImage(icon.getIconWidth(), icon.getIconHeight());
icon.paintIcon(null, buffer.createGraphics(), 0, 0);
ImageIO.write(buffer, "png", fi);
} catch (IOException ex) {
logger.error(String.format("Image format not supported yet: %s%n", img.getClass().toString()));
}
}
} else {
logger.error(String.format("Image not complete: %s [%s]%n", name, img.getClass().toString()));
}
}
File xml = new File(dataFolder.toString() + File.separator + "image" + ".xml");
OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(xml), "UTF-8");
try {
out.write("%n%n");
for (Integer k : images.keySet()) {
ImageIcon icon = images.get(k);
Rectangle2D r = imageDim.get(k);
out.write(String.format(" %n",
k,
icon.hashCode(),
r.getX(),
r.getY(),
r.getWidth(),
r.getHeight()));
}
} finally {
out.close();
}
}
if (htmlFlag) {
Formatter fileFormatter = new Formatter(new StringBuilder(1024));
fileFormatter.format("%n");
fileFormatter.format("%n");
fileFormatter.format("%n");
fileFormatter.format("%s %n", title);
fileFormatter.format("%n");
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");
fileFormatter.format("%s", keyWordString.toString());
fileFormatter.format("%n", jsLoc);
fileFormatter.format("%n");
fileFormatter.format("%n%n");
fileFormatter.format("%n");
fileFormatter.format("%n");
fileFormatter.format("%n");
fileFormatter.format("%n");
fileFormatter.format("