///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as published // by the Free Software Foundation; version 3 of the License. // // This community edition 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 General // Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.export; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.projectforge.common.StringHelper; import org.projectforge.gantt.GanttRelationType; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; public class SVGHelper { private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(SVGHelper.class); private static final String SVG_NS = SVGDOMImplementation.SVG_NAMESPACE_URI; private static final String XML_NS = "http://www.w3.org/1999/xlink"; public enum ArrowDirection { LEFT, RIGHT; } public static Document createDocument(final double width, final double height) { final DOMImplementation impl = SVGDOMImplementation.getDOMImplementation(); final Document doc = impl.createDocument(SVG_NS, "svg", null); final Element root = doc.getDocumentElement(); root.setAttributeNS(null, "xmlns:xlink", XML_NS); setAttribute(root, "width", width); setAttribute(root, "height", height); return doc; } public static void setAttribute(final Element element, final String attrName, final Object value) { if (value == null) { element.setAttribute(attrName, ""); } else { element.setAttribute(attrName, String.valueOf(value)); } } public static String drawHorizontalConnectionLine(final GanttRelationType type, final double x1, final double y1, final double x2, final double y2, final double minXDist) { checkNonNegativeValues("x1, y1, x2, y2", x1, y1, x2, y2); checkPositiveValues("mixXDist", minXDist); final double xHalf = (x2 - x1) / 2; final double yHalf = (y2 - y1) / 2; final StringBuffer buf = new StringBuffer(); buf.append("M ").append(round(x1)).append(" ").append(round(y1)); // (x1, y1) if (type == GanttRelationType.FINISH_START || type == null) { if (xHalf > minXDist) { buf.append(" L ").append(round(x1 + xHalf)).append(" ").append(round(y1)); // (x1 + x_half, y1), xHalf may be negative. buf.append(" L ").append(round(x1 + xHalf)).append(" ").append(round(y2)); // (x1 + x_half, y2), xHalf may be negative. } else { buf.append(" L ").append(round(x1 + minXDist)).append(" ").append(round(y1)); // (x1 + dist, y1) buf.append(" L ").append(round(x1 + minXDist)).append(" ").append(round(y1 + yHalf)); // (x1 + dist, y1 + yHalf), yHalf may be // negative. buf.append(" L ").append(round(x2 - minXDist)).append(" ").append(round(y1 + yHalf)); // (x1 - dist, y1 + yHalf), yHalf may be // negative. buf.append(" L ").append(round(x2 - minXDist)).append(" ").append(round(y2)); // (x1 - dist, y2), yHalf may be negative. } } else if (type == GanttRelationType.START_FINISH) { if (xHalf > minXDist && x2 < x1) { buf.append(" L ").append(round(x1 + xHalf)).append(" ").append(round(y1)); // (x1 + x_half, y1) buf.append(" L ").append(round(x1 + xHalf)).append(" ").append(round(y2)); // (x1 + x_half, y2) } else { buf.append(" L ").append(round(x1 - minXDist)).append(" ").append(round(y1)); // (x1 - dist, y1) buf.append(" L ").append(round(x1 - minXDist)).append(" ").append(round(y1 + yHalf)); // (x1 - dist, y1 + yHalf), yHalf may be // negative. buf.append(" L ").append(round(x2 + minXDist)).append(" ").append(round(y1 + yHalf)); // (x1 + dist, y1 + yHalf), yHalf may be // negative. buf.append(" L ").append(round(x2 + minXDist)).append(" ").append(round(y2)); // (x1 + dist, y2), yHalf may be negative. } } else if (type.isIn(GanttRelationType.START_START, GanttRelationType.FINISH_FINISH)) { final double x; if (type == GanttRelationType.START_START) { x = x1 < x2 ? x1 - minXDist : x2 - minXDist; } else { x = x1 < x2 ? x2 + minXDist : x1 + minXDist; } buf.append(" L ").append(round(x)).append(" ").append(round(y1)); // (x1 + x_half, y1), xHalf may be negative. buf.append(" L ").append(round(x)).append(" ").append(round(y2)); // (x1 + x_half, y2), xHalf may be negative. } buf.append(" L ").append(round(x2)).append(" ").append(round(y2)); // (x2, y2). return buf.toString(); } public static String drawArrow(final ArrowDirection direction, final double x, final double y, final double size) { checkNonNegativeValues("x, y", x, y); checkPositiveValues("size", size); final StringBuffer buf = new StringBuffer(); if (direction == ArrowDirection.LEFT) { buf.append("M ").append(round(x)).append(" ").append(round(y)); // (x, y) buf.append(" L ").append(round(x + size)).append(" ").append(round(y + size)); // (x + size, y + size) buf.append(" L ").append(round(x + size)).append(" ").append(round(y - size)); // (x + size, y - size) } else if (direction == ArrowDirection.RIGHT) { buf.append("M ").append(round(x)).append(" ").append(round(y)); // (x, y) buf.append(" L ").append(round(x - size)).append(" ").append(round(y - size)); // (x - size, y - size) buf.append(" L ").append(round(x - size)).append(" ").append(round(y + size)); // (x - size, y + size) } else { log.error("Unsupported arrow direction: " + direction); } buf.append(" z"); return buf.toString(); } public static Element createText(final Document document, final double x, final double y, final String text, final String... attributes) { if (text == null) { throw new IllegalArgumentException("text shouldn't be null."); } checkNonNegativeValues("x, y", x, y); if (log.isDebugEnabled() == true) { log.debug("createText: x=" + x + ", y=" + y + ", text=" + text); } final Element el = createElement(document, "text", prepend(attributes, "x", round(x), "y", round(y))); el.appendChild(document.createTextNode(text)); return el; } public static Element createPath(final Document document, final String fill, final double strokeWidth, final String stroke, final String path, final String... attributes) { checkPositiveValues("strokeWidth", strokeWidth); final Element el = createElement(document, "path", prepend(attributes, "fill", fill, "stroke-width", strokeWidth, "stroke", stroke, "d", path)); return el; } public static Element createPath(final Document document, final SVGColor fillColor, final double strokeWidth, final SVGColor strokeColor, final String path, final String... attributes) { return createPath(document, fillColor.getName(), strokeWidth, strokeColor.getName(), path, attributes); } public static Element createRect(final Document document, final double x, final double y, final double width, final double height, final String fill, final String... attributes) { checkPositiveValues("width, height", width, height); checkNonNegativeValues("x, y", x, y); if (log.isDebugEnabled() == true) { log.debug("createRect: x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ", fill=" + fill + ", attributes=" + StringHelper.listToString(",", StringHelper.listToString(",", attributes))); } final Element el = createElement(document, "rect", prepend(attributes, "x", round(x), "y", round(y), "width", round(width), "height", round(height), "fill", fill)); return el; } public static Element createRect(final Document document, final double x, final double y, final double width, final double height, final SVGColor fillColor, final String... attributes) { return createRect(document, x, y, width, height, fillColor.getName(), attributes); } public static Element createRect(final Document document, final double x, final double y, final double width, final double height, final SVGColor fillColor, final SVGColor strokeColor, final String... attributes) { return createRect(document, x, y, width, height, fillColor.getName(), prepend(attributes, "stroke", strokeColor.getName())); } public static Element createLine(final Document document, final double x1, final double y1, final double x2, final double y2, final SVGColor strokeColor, final String... attributes) { return createLine(document, x1, y1, x2, y2, prepend(attributes, "stroke", strokeColor.getName())); } public static Element createLine(final Document document, final double x1, final double y1, final double x2, final double y2, final String... attributes) { checkNonNegativeValues("x1, y1, x2, y2", x1, y1, x2, y2); if (log.isDebugEnabled() == true) { log.debug("createLine: x1=" + x1 + ", y1=" + y1 + ", x2=" + x2 + ", y2=" + y2 + ", attributes=" + StringHelper.listToString(",", attributes)); } final Element el = createElement(document, "line", prepend(attributes, "x1", round(x1), "y1", round(y1), "x2", round(x2), "y2", round(y2))); return el; } public static Element createUse(final Document document, final String id, final double x, final double y, final String... attributes) { checkNonNegativeValues("x, y", x, y); final Element el = createElement(document, "use", prepend(attributes, "xlink:href", id, "x", round(x), "y", round(y))); return el; } public static Element createElement(final Document document, final String tag, final SVGColor fillColor, final String... attributes) { return createElement(document, tag, prepend(attributes, "fill", fillColor.getName())); } public static Element createElement(final Document document, final String tag, final String... attributes) { final Element el = document.createElementNS(SVG_NS, tag); if (attributes != null) { for (int i = 0; i < attributes.length - 1; i += 2) { final String attr = attributes[i]; final String value = attributes[i + 1]; final String ns; if ("xlink:href".equals(attr) == true) { ns = XML_NS; } else { ns = null; } el.setAttributeNS(ns, attr, value); } } return el; } static String[] prepend(final String[] array, final Object... values) { if (values == null) { return array; } final String[] joinedArray = new String[values.length + array.length]; for (int i = 0; i < values.length; i++) { joinedArray[i] = String.valueOf(values[i]); } System.arraycopy(array, 0, joinedArray, values.length, array.length); return joinedArray; } static String[] append(final String[] array, final Object... values) { if (values == null) { return array; } final String[] joinedArray = new String[values.length + array.length]; System.arraycopy(array, 0, joinedArray, 0, array.length); for (int i = 0; i < values.length; i++) { joinedArray[array.length + i] = String.valueOf(values[i]); } return joinedArray; } static void checkNonNegativeValues(final String varnames, final double... values) { for (final double value : values) { if (value < 0 || Double.isNaN(value) == true || Double.isInfinite(value) == true) { throw new IllegalArgumentException("Values should be positive and valid: {" + varnames + "}=" + StringHelper.doublesToString(", ", values)); } } } static void checkPositiveValues(final String varnames, final double... values) { for (final double value : values) { if (value <= 0 || Double.isNaN(value) == true || Double.isInfinite(value) == true) { throw new IllegalArgumentException("Values should be positive or zero and valid: {" + varnames + "}=" + StringHelper.doublesToString(", ", values)); } } } public static String round(final double value) { return FORMAT_PRECISION_2.get().format(value); } /** * yyyy-MM-dd HH:mm:ss.S in UTC. Thread safe usage: FOR_TESTCASE_OUTPUT_FORMATTER.get().format(date) */ private static final ThreadLocal<DecimalFormat> FORMAT_PRECISION_2 = new ThreadLocal<DecimalFormat>() { @Override protected DecimalFormat initialValue() { final DecimalFormatSymbols symbols = new DecimalFormatSymbols(); symbols.setDecimalSeparator('.'); return new DecimalFormat("###.##", symbols); } }; }