/* * VectorGraphics2D: Vector export for Java(R) Graphics2D * * (C) Copyright 2010 Erich Seifert * * This file is part of VectorGraphics2D. * * VectorGraphics2D 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. * * VectorGraphics2D 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 VectorGraphics2D. If not, see . */ package de.erichseifert.vectorgraphics2d; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Image; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Map; import javax.imageio.ImageIO; import javax.xml.bind.DatatypeConverter; /** * Graphics2D implementation that saves all operations to a string * in the Scaled Vector Graphics (SVG) format. */ public class SVGGraphics2D extends VectorGraphics2D { /** Mapping of stroke endcap values from Java to SVG. */ private static final Map STROKE_ENDCAPS = DataUtils.map( new Integer[] { BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE }, new String[] { "butt", "round", "square" } ); /** Mapping of line join values for path drawing from Java to SVG. */ private static final Map STROKE_LINEJOIN = DataUtils.map( new Integer[] { BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL }, new String[] { "miter", "round", "bevel" } ); /** Prefix string for ids of clipping paths. */ private static final String CLIP_PATH_ID = "clip"; /** Number of the current clipping path. */ private long clipCounter; /** * Constructor that initializes a new SVGGraphics2D instance. * The document dimension must be specified as parameters. */ public SVGGraphics2D(double x, double y, double width, double height) { super(x, y, width, height); writeHeader(); } @Override protected void writeString(String str, double x, double y) { // Escape string str = str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); float fontSize = getFont().getSize2D(); //float leading = getFont().getLineMetrics("", getFontRenderContext()).getLeading(); /* // Extract lines String[] lines = str.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n"); // Output lines writeln(""); for (int i = 0; i < lines.length; i++) { String line = lines[i]; writeln(" 0) ? leading : 0f), "\">", line, ""); } writeln(""); */ str = str.replaceAll("[\r\n]", ""); writeln("", str, ""); } @Override protected void writeImage(Image img, int imgWidth, int imgHeight, double x, double y, double width, double height) { BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img); String imgData = getSvg(bufferedImg); write(""); } @Override public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { Path2D s = new Path2D.Double(Path2D.WIND_NON_ZERO, xPoints.length); write(""); writeShape(getClip()); writeln("/>"); writeln(""); } } @Override protected void setAffineTransform(AffineTransform tx) { if (getTransform().equals(tx)) { return; } // Close previous transformation group if (isTransformed()) { writeln(""); } // Set transformation matrix super.setAffineTransform(tx); // Begin new transformation group if (isTransformed()) { double[] matrix = new double[6]; getTransform().getMatrix(matrix); write(""); } } @Override protected void writeHeader() { Rectangle2D bounds = getBounds(); double x = bounds.getX(); double y = bounds.getY(); double w = bounds.getWidth(); double h = bounds.getHeight(); writeln(""); writeln(""); writeln(""); writeln(""); } @Override protected void writeClosingDraw(Shape s) { write("style=\"fill:none;stroke:", getSvg(getColor())); if (getStroke() instanceof BasicStroke) { BasicStroke stroke = (BasicStroke) getStroke(); if (stroke.getLineWidth() != 1f) { write(";stroke-width:", stroke.getLineWidth()); } if (stroke.getEndCap() != BasicStroke.CAP_BUTT) { write(";stroke-linecap:", STROKE_ENDCAPS.get(stroke.getEndCap())); } if (stroke.getLineJoin() != BasicStroke.JOIN_MITER) { write(";stroke-linejoin:", STROKE_LINEJOIN.get(stroke.getLineJoin())); } //write(";stroke-miterlimit:", s.getMiterLimit()); if (stroke.getDashArray() != null && stroke.getDashArray().length > 0) { write(";stroke-dasharray:", DataUtils.join(",", stroke.getDashArray())); write(";stroke-dashoffset:", stroke.getDashPhase()); } } if (getClip() != null) { write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")"); } writeln("\" />"); } @Override protected void writeClosingFill(Shape s) { if (getPaint() instanceof Color) { write("style=\"fill:", getSvg(getColor()), ";stroke:none"); if (getClip() != null) { write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")"); } writeln("\" />"); } else { write("style=\"stroke:none\" />"); super.writeClosingFill(s); } } @Override protected void writeShape(Shape s) { if (s instanceof Line2D) { Line2D l = (Line2D) s; double x1 = l.getX1(); double y1 = l.getY1(); double x2 = l.getX2(); double y2 = l.getY2(); write(" 0) { write(" "); } int segmentType = segments.currentSegment(coords); switch (segmentType) { case PathIterator.SEG_MOVETO: write("M", coords[0], ",", coords[1]); break; case PathIterator.SEG_LINETO: write("L", coords[0], ",", coords[1]); break; case PathIterator.SEG_CUBICTO: write("C", coords[0], ",", coords[1], " ", coords[2], ",", coords[3], " ", coords[4], ",", coords[5]); break; case PathIterator.SEG_QUADTO: write("Q", coords[0], ",", coords[1], " ", coords[2], ",", coords[3]); break; case PathIterator.SEG_CLOSE: write("Z"); break; default: throw new IllegalStateException("Unknown path operation."); } } write("\" "); } } private static String getSvg(Color c) { String color = "rgb(" + c.getRed() + "," + c.getGreen() + "," + c.getBlue() + ")"; if (c.getAlpha() < 255) { double opacity = c.getAlpha()/255.0; color += ";opacity:" + opacity; } return color; } private static String getSvg(BufferedImage bufferedImg) { ByteArrayOutputStream data = new ByteArrayOutputStream(); try { ImageIO.write(bufferedImg, "png", data); } catch (IOException e) { return ""; } String dataBase64 = DatatypeConverter .printBase64Binary(data.toByteArray()); return "data:image/png;base64," + dataBase64; } @Override protected String getFooter() { String footer = ""; // Close any previous transformation groups if (isTransformed()) { footer += "\n"; } footer += "\n"; return footer; } @Override public String toString() { String doc = super.toString(); doc = doc.replaceAll("\n", ""); return doc; } }