package nodebox.graphics;
import nodebox.util.FileUtils;
import org.python.google.common.collect.ImmutableMap;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.*;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
public class SVGRenderer {
public static String XML_DECLARATION = "<?xml version=\"1.0\"?>\n";
public static String smartFloat(double v) {
if ((long) v == v) {
return String.valueOf((long) v);
} else {
return String.format(Locale.US, "%.2f", v);
}
}
public static void appendFloat(StringBuilder sb, double v) {
sb.append(smartFloat(v));
}
public static String renderPathData(Path path) {
StringBuilder sb = new StringBuilder();
for (Contour c : path.getContours()) {
List<Point> points = c.getPoints();
for (int i = 0; i < points.size(); i += 1) {
Point pt = points.get(i);
if (pt.getType() == Point.LINE_TO) {
if (i == 0) {
sb.append('M');
appendFloat(sb, pt.x);
sb.append(',');
appendFloat(sb, pt.y);
} else {
sb.append('L');
appendFloat(sb, pt.x);
sb.append(',');
appendFloat(sb, pt.y);
}
} else if (pt.getType() == Point.CURVE_DATA) {
// We expect three points.
sb.append('C');
appendFloat(sb, pt.x);
sb.append(',');
appendFloat(sb, pt.y);
pt = points.get(++i);
checkState(pt.getType() == Point.CURVE_DATA);
sb.append(' ');
appendFloat(sb, pt.x);
sb.append(',');
appendFloat(sb, pt.y);
pt = points.get(++i);
checkState(pt.getType() == Point.CURVE_TO);
sb.append(' ');
appendFloat(sb, pt.x);
sb.append(',');
appendFloat(sb, pt.y);
}
}
if (c.isClosed()) {
sb.append('Z');
}
}
return sb.toString();
}
public static Element renderPath(Path path) {
String d = renderPathData(path);
HashMap<String, String> attrs = new HashMap<String, String>();
attrs.put("d", d);
if (path.getFill() != null) {
if (!path.getFill().equals(Color.BLACK)) {
attrs.put("fill", path.getFill().toCSS());
}
} else {
attrs.put("fill", "none");
}
if (path.getStroke() != null && path.getStroke().isVisible()) {
attrs.put("stroke", path.getStroke().toCSS());
if (path.getStrokeWidth() != 1) {
attrs.put("stroke-width", smartFloat(path.getStrokeWidth()));
}
}
return new Element("path", attrs, null);
}
public static Element renderGeometry(Geometry geo) {
List<Element> elements = new LinkedList<Element>();
for (Path path : geo.getPaths()) {
elements.add(renderPath(path));
}
return new Element("g", null, elements);
}
public static Element renderSVG(Iterable<?> objects, Rectangle2D bounds) {
LinkedList<Element> elements = new LinkedList<Element>();
for (Object o : objects) {
if (o instanceof Geometry) {
elements.add(renderGeometry((Geometry) o));
} else if (o instanceof Path) {
elements.add(renderPath((Path) o));
} else {
throw new RuntimeException("Don't know how to render " + o.getClass().getName());
}
}
StringBuilder viewBox = new StringBuilder();
appendFloat(viewBox, bounds.getX());
viewBox.append(' ');
appendFloat(viewBox, bounds.getY());
viewBox.append(' ');
appendFloat(viewBox, bounds.getWidth());
viewBox.append(' ');
appendFloat(viewBox, bounds.getHeight());
Map<String, String> attrs = ImmutableMap.of(
"xmlns", "http://www.w3.org/2000/svg",
"width", smartFloat(bounds.getWidth()),
"height", smartFloat(bounds.getHeight()),
"viewBox", viewBox.toString());
return new Element("svg", attrs, elements);
}
public static String renderToString(Iterable<?> objects, Rectangle2D bounds) {
checkArgument(objects != null);
Element svg = renderSVG(objects, bounds);
return XML_DECLARATION + svg.toString(4, 0);
}
public static void renderToFile(Iterable<?> objects, Rectangle2D bounds, File file) {
checkArgument(objects != null);
FileUtils.writeFile(file, renderToString(objects, bounds));
}
public static class Element {
String tag;
Map<String, String> attributes;
List<Element> children;
public Element(String tag, Map<String, String> attributes, List<Element> children) {
this.tag = tag;
this.attributes = attributes;
this.children = children;
}
public boolean isSelfClosing() {
return this.children == null;
}
public String toString() {
return toString(4, 0);
}
public String toString(int indent, int start) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < start; i++) {
sb.append(' ');
}
sb.append("<");
sb.append(tag);
if (attributes != null) {
for (Map.Entry<String, String> entry : attributes.entrySet()) {
sb.append(" ");
sb.append(entry.getKey());
sb.append("=\"");
sb.append(entry.getValue());
sb.append("\"");
}
}
if (isSelfClosing()) {
sb.append("/>");
} else {
sb.append(">\n");
for (Element child : children) {
sb.append(child.toString(indent, start + indent));
sb.append('\n');
}
for (int i = 0; i < start; i++) {
sb.append(' ');
}
sb.append("</");
sb.append(tag);
sb.append(">");
}
return sb.toString();
}
}
}