// Copyright 2000-2007 FreeHEP
package org.freehep.graphicsio.svg;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.zip.GZIPOutputStream;
import org.freehep.graphics2d.font.FontUtilities;
import org.freehep.graphicsio.AbstractVectorGraphicsIO;
import org.freehep.graphicsio.FontConstants;
import org.freehep.graphicsio.ImageConstants;
import org.freehep.graphicsio.ImageGraphics2D;
import org.freehep.graphicsio.InfoConstants;
import org.freehep.graphicsio.PageConstants;
import org.freehep.util.UserProperties;
import org.freehep.util.Value;
import org.freehep.util.io.Base64OutputStream;
import org.freehep.util.io.WriterOutputStream;
import org.freehep.xml.util.XMLWriter;
/**
* This class implements the Scalable Vector Graphics output. SVG specifications
* can be found at http://www.w3c.org/Graphics/SVG/
*
* The current implementation is based on REC-SVG11-20030114
*
* @author Mark Donszelmann
* @version $Id: SVGGraphics2D.java 12753 2007-06-12 22:32:31Z duns $
*/
public class SVGGraphics2D extends AbstractVectorGraphicsIO {
public static final String VERSION_1_1 = "Version 1.1 (REC-SVG11-20030114)";
private static final String rootKey = SVGGraphics2D.class.getName();
public static final String TRANSPARENT = rootKey + "."
+ PageConstants.TRANSPARENT;
public static final String BACKGROUND = rootKey + "."
+ PageConstants.BACKGROUND;
public static final String BACKGROUND_COLOR = rootKey + "."
+ PageConstants.BACKGROUND_COLOR;
public static final String VERSION = rootKey + ".Version";
public static final String COMPRESS = rootKey + ".Binary";
/**
* use style="font-size:20" instaed of font-size="20"
* see {@link #style(java.util.Properties)} for details
*/
public static final String STYLABLE = rootKey + ".Stylable";
public static final String IMAGE_SIZE = rootKey + "."
+ ImageConstants.IMAGE_SIZE;
public static final String EXPORT_IMAGES = rootKey + ".ExportImages";
public static final String EXPORT_SUFFIX = rootKey + ".ExportSuffix";
public static final String WRITE_IMAGES_AS = rootKey + "."
+ ImageConstants.WRITE_IMAGES_AS;
public static final String FOR = rootKey + "." + InfoConstants.FOR;
public static final String TITLE = rootKey + "." + InfoConstants.TITLE;
private BasicStroke defaultStroke = new BasicStroke();
public static final String EMBED_FONTS = rootKey + "."
+ FontConstants.EMBED_FONTS;
private SVGFontTable fontTable;
private static final UserProperties defaultProperties = new UserProperties();
static {
defaultProperties.setProperty(TRANSPARENT, true);
defaultProperties.setProperty(BACKGROUND, false);
defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY);
defaultProperties.setProperty(VERSION, VERSION_1_1);
defaultProperties.setProperty(COMPRESS, false);
defaultProperties.setProperty(STYLABLE, false);
defaultProperties.setProperty(IMAGE_SIZE, new Dimension(0, 0)); // ImageSize
defaultProperties.setProperty(EXPORT_IMAGES, false);
defaultProperties.setProperty(EXPORT_SUFFIX, "image");
defaultProperties.setProperty(WRITE_IMAGES_AS, ImageConstants.SMALLEST);
defaultProperties.setProperty(FOR, "");
defaultProperties.setProperty(TITLE, "");
defaultProperties.setProperty(CLIP, true);
defaultProperties.setProperty(EMBED_FONTS, false);
defaultProperties.setProperty(TEXT_AS_SHAPES, true);
}
public static Properties getDefaultProperties() {
return defaultProperties;
}
public static void setDefaultProperties(Properties newProperties) {
defaultProperties.setProperties(newProperties);
}
public static final String version = "$Revision: 12753 $";
// current filename including path
private String filename;
// The lowerleft and upper right points of the bounding box.
private int bbx, bby, bbw, bbh;
// The private writer used for this file.
private OutputStream ros;
private PrintWriter os;
// table for gradients
Hashtable gradients = new Hashtable();
// table for textures
Hashtable textures = new Hashtable();
private Stack closeTags = new Stack();
private int imageNumber = 0;
private Value clipNumber;
private int width, height;
/*
* ================================================================================ |
* 1. Constructors & Factory Methods
* ================================================================================
*/
public SVGGraphics2D(File file, Dimension size) throws IOException {
this(new FileOutputStream(file), size);
this.filename = file.getPath();
}
public SVGGraphics2D(File file, Component component) throws IOException {
this(new FileOutputStream(file), component);
this.filename = file.getPath();
}
public SVGGraphics2D(OutputStream os, Dimension size) {
super(size, false);
init(os);
width = size.width;
height = size.height;
}
public SVGGraphics2D(OutputStream os, Component component) {
super(component, false);
init(os);
width = getSize().width;
height = getSize().height;
}
private void init(OutputStream os) {
this.ros = os;
initProperties(getDefaultProperties());
this.filename = null;
this.clipNumber = new Value().set(0);
}
protected SVGGraphics2D(SVGGraphics2D graphics, boolean doRestoreOnDispose) {
super(graphics, doRestoreOnDispose);
// Now initialize the new object.
filename = graphics.filename;
os = graphics.os;
bbx = graphics.bbx;
bby = graphics.bby;
bbw = graphics.bbw;
bbh = graphics.bbh;
gradients = graphics.gradients;
textures = graphics.textures;
clipNumber = graphics.clipNumber;
fontTable = graphics.fontTable;
}
/*
* ================================================================================ |
* 2. Document Settings
* ================================================================================
*/
/**
* Get the bounding box for this image.
*/
public void setBoundingBox() {
bbx = 0;
bby = 0;
Dimension size = getSize();
bbw = size.width;
bbh = size.height;
}
/*
* ================================================================================ |
* 3. Header, Trailer, Multipage & Comments
* ================================================================================
*/
/*--------------------------------------------------------------------------------
| 3.1 Header & Trailer
*--------------------------------------------------------------------------------*/
/**
* Write out the header of this SVG file.
*/
public void writeHeader() throws IOException {
ros = new BufferedOutputStream(ros);
if (isProperty(COMPRESS)) {
ros = new GZIPOutputStream(ros);
}
os = new PrintWriter(ros, true);
fontTable = new SVGFontTable();
// Do the bounding box calculation.
setBoundingBox();
imageNumber = 0;
os.println("");
if (getProperty(VERSION).equals(VERSION_1_1)) {
// no DTD anymore
} else {
// FIXME experimental version
}
os.println();
int x = 0;
int y = 0;
Dimension size = getPropertyDimension(IMAGE_SIZE);
int w = size.width;
if (w <= 0)
w = width;
int h = size.height;
if (h <= 0)
h = height;
os.println(" ");
os.print("
");
os.print(XMLWriter.normalizeText(getProperty(TITLE)));
os.println("");
String producer = getClass().getName();
if (!isDeviceIndependent()) {
producer += " " + version.substring(1, version.length() - 1);
}
os.print("");
os.print("Creator: " + XMLWriter.normalizeText(getCreator()));
os.print(" Producer: " + XMLWriter.normalizeText(producer));
os.print(" Source: " + XMLWriter.normalizeText(getProperty(FOR)));
if (!isDeviceIndependent()) {
os.print(" Date: "
+ DateFormat.getDateTimeInstance(DateFormat.FULL,
DateFormat.FULL).format(new Date()));
}
os.println("");
// write default stroke
os.print("");
// close default settings at the end
closeTags.push(" ");
}
public void writeBackground() throws IOException {
if (isProperty(TRANSPARENT)) {
setBackground(null);
} else if (isProperty(BACKGROUND)) {
setBackground(getPropertyColor(BACKGROUND_COLOR));
clearRect(0.0, 0.0, getSize().width, getSize().height);
} else {
setBackground(getComponent() != null ? getComponent()
.getBackground() : Color.WHITE);
clearRect(0.0, 0.0, getSize().width, getSize().height);
}
}
/**
* Writes the font definitions and calls {@link #writeGraphicsRestore()} to
* close all open XML Tags
*
* @throws IOException
*/
public void writeTrailer() throws IOException {
// write font definition
if (isProperty(EMBED_FONTS)) {
os.println("");
os.println(fontTable.toString());
os.println(" ");
}
// restor graphic
writeGraphicsRestore();
}
public void closeStream() throws IOException {
os.close();
}
/*
* ================================================================================ |
* 4. Create
* ================================================================================
*/
public Graphics create() {
try {
writeGraphicsSave();
} catch (IOException e) {
handleException(e);
}
return new SVGGraphics2D(this, true);
}
public Graphics create(double x, double y, double width, double height) {
try {
writeGraphicsSave();
} catch (IOException e) {
handleException(e);
}
SVGGraphics2D graphics = new SVGGraphics2D(this, true);
// FIXME: All other drivers have a translate(x,y), clip(0,0,w,h) here
os.println(" ");
// write default stroke
os.print("");
graphics.closeTags.push(" ");
return graphics;
}
protected void writeGraphicsSave() throws IOException {
// not applicable
}
protected void writeGraphicsRestore() throws IOException {
while (!closeTags.empty()) {
os.println(closeTags.pop());
}
}
/*
* ================================================================================ |
* 5. Drawing Methods
* ================================================================================
*/
/* 5.1 shapes */
/* 5.1.4. shapes */
/**
* Draws the shape using the current paint as border
*
* @param shape Shape to draw
*/
public void draw(Shape shape) {
// others than BasicStrokes are written by its
// {@link Stroke#createStrokedShape()}
if (getStroke() instanceof BasicStroke) {
PathIterator path = shape.getPathIterator(null);
Properties style = new Properties();
if (getPaint() != null) {
style.put("stroke", hexColor(getPaint()));
style.put("stroke-opacity", fixedPrecision(alphaColor(getPaint())));
}
// no filling
style.put("fill", "none");
style.putAll(getStrokeProperties(getStroke(), false));
writePathIterator(path, style);
} else if (getStroke() != null) {
// fill the shape created by stroke
fill(getStroke().createStrokedShape(shape));
} else {
// FIXME: do nothing or draw using defaultStroke?
fill(defaultStroke.createStrokedShape(shape));
}
}
/**
* Fills the shape without a border using the current paint
*
* @param shape Shape to be filled with the current paint
*/
public void fill(Shape shape) {
// draw paint as image if needed
if (!(getPaint() instanceof Color || getPaint() instanceof GradientPaint)) {
// draw paint as image
fill(shape, getPaint());
} else {
PathIterator path = shape.getPathIterator(null);
Properties style = new Properties();
if (path.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
style.put("fill-rule", "evenodd");
} else {
style.put("fill-rule", "nonzero");
}
// fill with paint
if (getPaint() != null) {
style.put("fill", hexColor(getPaint()));
style.put("fill-opacity", fixedPrecision(alphaColor(getPaint())));
}
// no border
style.put("stroke", "none");
writePathIterator(path, style);
}
}
/**
* writes a path using {@link #getPath(java.awt.geom.PathIterator)}
* and the given style
*
* @param pi PathIterator
* @param style Properties for tag
*/
private void writePathIterator(PathIterator pi, Properties style) {
StringBuffer result = new StringBuffer();
// write style
result.append("\n ");
// draw shape
result.append(getPath(pi));
// close style
result.append("\n ");
boolean drawClipped = false;
// test if clip intersects pi
if (getClip() != null) {
GeneralPath gp = new GeneralPath();
gp.append(pi, true);
// create the stroked shape
Stroke stroke = getStroke() == null? defaultStroke : getStroke();
Rectangle2D bounds = stroke.createStrokedShape(gp).getBounds();
// clip should intersect the path
// if clip contains the bounds completely, clipping is not needed
drawClipped = getClip().intersects(bounds) && !getClip().contains(bounds);
}
if (drawClipped) {
// write in a transformed and clipped context
os.println(
getTransformedString(
getTransform(),
getClippedString(result.toString())));
} else {
// write in a transformed context
os.println(
getTransformedString(
getTransform(),
result.toString()));
}
}
/* 5.2. Images */
public void copyArea(int x, int y, int width, int height, int dx, int dy) {
writeWarning(getClass()
+ ": copyArea(int, int, int, int, int, int) not implemented.");
}
protected void writeImage(RenderedImage image, AffineTransform xform,
Color bkg) throws IOException {
StringBuffer result = new StringBuffer();
result.append("");
os.println(getTransformedString(getTransform(),
getClippedString(getTransformedString(xform, result
.toString()))));
}
/* 5.3. Strings */
protected void writeString(String str, double x, double y)
throws IOException {
// str = FontEncoder.getEncodedString(str, getFont().getName());
if (isProperty(EMBED_FONTS)) {
fontTable.addGlyphs(str, getFont());
}
// font transformation should _not_ transform string position
// so we draw at 0:0 and translate _before_ using getFont().getTransform()
// we could not just translate before and reverse translation after
// writing because the clipping area
// create font properties
Properties style = getFontProperties(getFont());
// add stroke properties
if (getPaint() != null) {
style.put("fill", hexColor(getPaint()));
style.put("fill-opacity", fixedPrecision(alphaColor(getPaint())));
} else {
style.put("fill", "none");
}
style.put("stroke", "none");
// convert tags to string values
str = XMLWriter.normalizeText(str);
// replace leading space by a0; otherwise firefox 1.5 fails
if (str.startsWith(" ")) {
str = " " + str.substring(1);
}
os.println(getTransformedString(
// general transformation
getTransform(),
// general clip
getClippedString(
getTransformedString(
// text offset
new AffineTransform(1, 0, 0, 1, x, y),
getTransformedString(
// font transformation and text
getFont().getTransform(),
""
// text
+ str
+ "")))));
}
/**
* Creates the properties list for the given font.
* Family, size, bold italic, underline and strikethrough are converted.
* {@link java.awt.font.TextAttribute#SUPERSCRIPT}
* is handled by {@link java.awt.Font#getTransform()}
*
* @return properties in svg style for the font
* @param font Font to
*/
private Properties getFontProperties(Font font) {
Properties result = new Properties();
// attribute for font properties
Map /**/ attributes = FontUtilities.getAttributes(font);
// dialog.bold -> Helvetica with TextAttribute.WEIGHT_BOLD
SVGFontTable.normalize(attributes);
// family
result.put("font-family", attributes.get(TextAttribute.FAMILY));
// weight
if (TextAttribute.WEIGHT_BOLD.equals(attributes.get(TextAttribute.WEIGHT))) {
result.put("font-weight", "bold");
} else {
result.put("font-weight", "normal");
}
// posture
if (TextAttribute.POSTURE_OBLIQUE.equals(attributes.get(TextAttribute.POSTURE))) {
result.put("font-style", "italic");
} else {
result.put("font-style", "normal");
}
Object ul = attributes.get(TextAttribute.UNDERLINE);
if (ul != null) {
// underline style, only supported by CSS 3
if (TextAttribute.UNDERLINE_LOW_DOTTED.equals(ul)) {
result.put("text-underline-style", "dotted");
} else if (TextAttribute.UNDERLINE_LOW_DASHED.equals(ul)) {
result.put("text-underline-style", "dashed");
} else if (TextAttribute.UNDERLINE_ON.equals(ul)) {
result.put("text-underline-style", "solid");
}
// the underline itself, supported by CSS 2
result.put("text-decoration", "underline");
}
if (attributes.get(TextAttribute.STRIKETHROUGH) != null) {
// is the property allready witten?
if (ul == null) {
result.put("text-decoration", "underline, line-through");
} else {
result.put("text-decoration", "line-through");
}
}
Float size = (Float) attributes.get(TextAttribute.SIZE);
result.put("font-size", fixedPrecision(size.floatValue()));
return result;
}
/*
* ================================================================================ |
* 6. Transformations
* ================================================================================
*/
protected void writeTransform(AffineTransform transform) throws IOException {
// written when needed
}
protected void writeSetTransform(AffineTransform transform)
throws IOException {
// written when needed
}
/*
* ================================================================================ |
* 7. Clipping
* ================================================================================
*/
protected void writeClip(Shape s) throws IOException {
// written when needed
}
protected void writeSetClip(Shape s) throws IOException {
// written when needed
}
/*
* ================================================================================ |
* 8. Graphics State
* ================================================================================
*/
/* 8.1. stroke/linewidth */
protected void writeWidth(float width) throws IOException {
// written when needed
}
protected void writeCap(int cap) throws IOException {
// Written when needed
}
protected void writeJoin(int join) throws IOException {
// written when needed
}
protected void writeMiterLimit(float limit) throws IOException {
// written when needed
}
protected void writeDash(float[] dash, float phase) throws IOException {
// written when needed
}
/**
* return the style tag for the stroke
*
* @param s
* Stroke to convert
* @param all
* all attributes (not only the differences to defaultStroke) are
* handled
* @return corresponding style string
*/
private Properties getStrokeProperties(Stroke s, boolean all) {
Properties result = new Properties();
// only BasisStrokes are written
if (!(s instanceof BasicStroke)) {
return result;
}
BasicStroke stroke = (BasicStroke) s;
// append linecap
if (all || (stroke.getEndCap() != defaultStroke.getEndCap())) {
// append cap
switch (stroke.getEndCap()) {
default:
case BasicStroke.CAP_BUTT:
result.put("stroke-linecap", "butt");
break;
case BasicStroke.CAP_ROUND:
result.put("stroke-linecap", "round");
break;
case BasicStroke.CAP_SQUARE:
result.put("stroke-linecap", "square");
break;
}
}
// append dasharray
if (all
|| !Arrays.equals(stroke.getDashArray(), defaultStroke
.getDashArray())) {
if (stroke.getDashArray() != null
&& stroke.getDashArray().length > 0) {
StringBuffer array = new StringBuffer();
for (int i = 0; i < stroke.getDashArray().length; i++) {
if (i > 0) {
array.append(",");
}
// SVG does not allow dash entry to be zero (Firefox 2.0).
float dash = stroke.getDashArray()[i];
array.append(fixedPrecision(dash > 0 ? dash : 0.1));
}
result.put("stroke-dasharray", array.toString());
} else {
result.put("stroke-dasharray", "none");
}
}
if (all || (stroke.getDashPhase() != defaultStroke.getDashPhase())) {
result.put("stroke-dashoffset", fixedPrecision(stroke.getDashPhase()));
}
// append meter limit
if (all || (stroke.getMiterLimit() != defaultStroke.getMiterLimit())) {
result.put("stroke-miterlimit", fixedPrecision(stroke.getMiterLimit()));
}
// append join
if (all || (stroke.getLineJoin() != defaultStroke.getLineJoin())) {
switch (stroke.getLineJoin()) {
default:
case BasicStroke.JOIN_MITER:
result.put("stroke-linejoin", "miter");
break;
case BasicStroke.JOIN_ROUND:
result.put("stroke-linejoin", "round");
break;
case BasicStroke.JOIN_BEVEL:
result.put("stroke-linejoin", "bevel");
break;
}
}
// append linewidth
if (all || (stroke.getLineWidth() != defaultStroke.getLineWidth())) {
// width of 0 means thinnest line, which does not exist in SVG
if (stroke.getLineWidth() == 0) {
result.put("stroke-width", fixedPrecision(0.000001f));
} else {
result.put("stroke-width", fixedPrecision(stroke.getLineWidth()));
}
}
return result;
}
/* 8.2. paint/color */
public void setPaintMode() {
writeWarning(getClass() + ": setPaintMode() not implemented.");
}
public void setXORMode(Color c1) {
writeWarning(getClass() + ": setXORMode(Color) not implemented.");
}
protected void writePaint(Color c) throws IOException {
// written with every draw
}
protected void writePaint(GradientPaint paint) throws IOException {
if (gradients.get(paint) == null) {
String name = "gradient-" + gradients.size();
gradients.put(paint, name);
Point2D p1 = paint.getPoint1();
Point2D p2 = paint.getPoint2();
os.println("");
os.print(" ");
os.println(" ");
os.println(" ");
os.println(" ");
os.println("");
}
// create style
Properties style = new Properties();
style.put("stroke", hexColor(getPaint()));
// write style
os.print("");
// close color later
closeTags.push(" ");
}
protected void writePaint(TexturePaint paint) throws IOException {
// written when needed
}
protected void writePaint(Paint p) throws IOException {
// written when needed
}
/* 8.3. font */
protected void writeFont(Font font) throws IOException {
// written when needed
}
/*
* ================================================================================ |
* 9. Auxiliary
* ================================================================================
*/
public GraphicsConfiguration getDeviceConfiguration() {
writeWarning(getClass() + ": getDeviceConfiguration() not implemented.");
return null;
}
public void writeComment(String s) throws IOException {
os.println("");
}
public String toString() {
return "SVGGraphics2D";
}
/*
* ================================================================================ |
* 10. Private/Utility Methos
* ================================================================================
*/
/**
* Encapsulates a SVG-Tag by the given transformation matrix
*
* @param t
* Transformation
* @param s
* SVG-Tag
*/
private String getTransformedString(AffineTransform t, String s) {
StringBuffer result = new StringBuffer();
if (t != null && !t.isIdentity()) {
result.append("\n");
}
result.append(s);
if (t != null && !t.isIdentity()) {
result.append("\n ");
}
return result.toString();
}
/**
* Encapsulates a SVG-Tag by the current clipping area matrix
*
* @param s SVG-Tag
* @return SVG Tag encapsulated by the current clip
*/
private String getClippedString(String s) {
StringBuffer result = new StringBuffer();
// clipping
if (isProperty(CLIP) && getClip() != null) {
// SVG uses unique lip numbers, don't reset allways increment them
clipNumber.set(clipNumber.getInt() + 1);
// define clip
result.append("\n ");
result.append(getPath(getClip().getPathIterator(null)));
result.append("\n\n");
// use clip
result.append("\n");
}
// append the string
result.append(s);
// close clipping
if (isProperty(CLIP) && getClip() != null) {
result.append("\n ");
}
return result.toString();
}
private float alphaColor(Paint p) {
if (p instanceof Color) {
return (float) (getPrintColor((Color) p).getAlpha() / 255.0);
} else if (p instanceof GradientPaint) {
return 1.0f;
} else if (p instanceof TexturePaint) {
return 1.0f;
}
writeWarning(getClass() + ": alphaColor() not implemented for "
+ p.getClass() + ".");
return 1.0f;
}
private String hexColor(Paint p) {
if (p instanceof Color) {
return hexColor(getPrintColor((Color) p));
} else if (p instanceof GradientPaint) {
return hexColor((GradientPaint) p);
} else if (p instanceof TexturePaint) {
return hexColor((TexturePaint) p);
}
writeWarning(getClass() + ": hexColor() not implemented for "
+ p.getClass() + ".");
return "#000000";
}
private String hexColor(Color c) {
String s1 = Integer.toHexString(c.getRed());
s1 = (s1.length() != 2) ? "0" + s1 : s1;
String s2 = Integer.toHexString(c.getGreen());
s2 = (s2.length() != 2) ? "0" + s2 : s2;
String s3 = Integer.toHexString(c.getBlue());
s3 = (s3.length() != 2) ? "0" + s3 : s3;
return "#" + s1 + s2 + s3;
}
private String hexColor(GradientPaint p) {
return "url(#" + gradients.get(p) + ")";
}
private String hexColor(TexturePaint p) {
return "url(#" + textures.get(p) + ")";
}
protected static String getPathContent(PathIterator path) {
StringBuffer result = new StringBuffer();
double[] coords = new double[6];
result.append("d=\"");
while (!path.isDone()) {
int segType = path.currentSegment(coords);
switch (segType) {
case PathIterator.SEG_MOVETO:
result.append("M ");
result.append(fixedPrecision(coords[0]));
result.append(" ");
result.append(fixedPrecision(coords[1]));
break;
case PathIterator.SEG_LINETO:
result.append("L ");
result.append(fixedPrecision(coords[0]));
result.append(" ");
result.append(fixedPrecision(coords[1]));
break;
case PathIterator.SEG_CUBICTO:
result.append("C ");
result.append(fixedPrecision(coords[0]));
result.append(" ");
result.append(fixedPrecision(coords[1]));
result.append(" ");
result.append(fixedPrecision(coords[2]));
result.append(" ");
result.append(fixedPrecision(coords[3]));
result.append(" ");
result.append(fixedPrecision(coords[4]));
result.append(" ");
result.append(fixedPrecision(coords[5]));
break;
case PathIterator.SEG_QUADTO:
result.append("Q ");
result.append(fixedPrecision(coords[0]));
result.append(" ");
result.append(fixedPrecision(coords[1]));
result.append(" ");
result.append(fixedPrecision(coords[2]));
result.append(" ");
result.append(fixedPrecision(coords[3]));
break;
case PathIterator.SEG_CLOSE:
result.append("z");
break;
}
// Move to the next segment.
path.next();
// Not needed but makes the output readable
if (!path.isDone()) {
result.append(" ");
}
}
result.append("\"");
return result.toString();
}
protected String getPath(PathIterator path) {
StringBuffer result = new StringBuffer();
result.append("");
return result.toString();
}
/**
* For a given "key -> value" property set the
* method creates
* style="key1:value1;key2:value2;" or
* key2="value2" key2="value2" depending on
* {@link #STYLABLE}.
*
* @param style properties to convert
* @return String
*/
private String style(Properties style) {
if (style == null || style.isEmpty()) {
return "";
}
StringBuffer result = new StringBuffer();
boolean styleable = isProperty(STYLABLE);
// embed everything in a "style" attribute
if (styleable) {
result.append("style=\"");
}
Enumeration keys = style.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = style.getProperty(key);
result.append(key);
if (styleable) {
result.append(":");
result.append(value);
result.append(";");
} else {
result.append("=\"");
result.append(value);
result.append("\"");
if (keys.hasMoreElements()) {
result.append(" ");
}
}
}
// close the style attribute
if (styleable) {
result.append("\"");
}
return result.toString();
}
/**
* for fixedPrecision(double d), SVG does not understand "1E-7"
* we have to use ".0000007" instead
*/
private static DecimalFormat scientific = new DecimalFormat(
"#.####################",
new DecimalFormatSymbols(Locale.US));
/**
* converts the double value to a representing string
*
* @param d double value to convert
* @return same as string
*/
public static String fixedPrecision(double d) {
return scientific.format(d);
}
protected PrintWriter getOutputStream() {
return os;
}
}