/*
* @(#)SVGInputFormat.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.samples.svg.io;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.jhotdraw.gui.filechooser.ExtensionFileFilter;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.text.ParseException;
import java.util.*;
import javax.imageio.*;
import javax.swing.*;
import javax.swing.text.*;
import net.n3.nanoxml.*;
import org.jhotdraw.draw.*;
import org.jhotdraw.draw.io.InputFormat;
import org.jhotdraw.xml.css.StyleManager;
import org.jhotdraw.geom.*;
import org.jhotdraw.io.*;
import org.jhotdraw.samples.svg.*;
import org.jhotdraw.samples.svg.figures.*;
import org.jhotdraw.text.FontFormatter;
import org.jhotdraw.util.LocaleUtil;
import static org.jhotdraw.samples.svg.SVGConstants.*;
import static org.jhotdraw.samples.svg.SVGAttributeKeys.*;
import org.jhotdraw.xml.css.CSSParser;
/**
* SVGInputFormat.
* This format is aimed to comply to the Scalable Vector Graphics (SVG) Tiny 1.2
* Specification supporting the <code>SVG-static</code> feature string.
* <a href="http://www.w3.org/TR/SVGMobile12/">http://www.w3.org/TR/SVGMobile12/</a>
* <p>
* Design pattern:<br>
* Name: Abstract Factory.<br>
* Role: Client.<br>
* Partners: {@link SVGFigureFactory} as Abstract Factory.
*
*
* @author Werner Randelshofer
* @version $Id$
*/
public class SVGInputFormat implements InputFormat {
/**
* Set this to true, to getChild debug output on if (DEBUG) System.out.
*/
private static final boolean DEBUG = false;
/**
* The SVGFigure factory is used to create Figure's for the drawing.
*/
private SVGFigureFactory factory;
/**
* URL pointing to the SVG input file. This is used as a base URL for
* resources that are referenced from the SVG file.
*/
@Nullable
private URL url;
// FIXME - Move these maps to SVGConstants or to SVGAttributeKeys.
/**
* Maps to all XML elements that are identified by an xml:id.
*/
@Nullable
private HashMap<String, IXMLElement> identifiedElements;
/**
* Maps to all drawing objects from the XML elements they were created from.
*/
@Nullable
private HashMap<IXMLElement, Object> elementObjects;
/**
* Tokenizer for parsing SVG path expressions.
*
*/
private StreamPosTokenizer toPathTokenizer;
/** FontFormatter for parsing font family names. */
private FontFormatter fontFormatter = new FontFormatter();
/**
* Each SVG element establishes a new Viewport.
*/
private static class Viewport {
/**
* The width of the Viewport.
*/
public double width = 640d;
/**
* The height of the Viewport.
*/
public double height = 480d;
/**
* The viewBox specifies the coordinate system within the Viewport.
*/
public Rectangle2D.Double viewBox = new Rectangle2D.Double(0d, 0d, 640d, 480d);
/**
* Factor for percent values relative to Viewport width.
*/
public double widthPercentFactor = 640d / 100d;
/**
* Factor for percent values relative to Viewport height.
*/
public double heightPercentFactor = 480d / 100d;
/**
* Factor for number values in the user coordinate system.
* This is the smaller value of width / viewBox.width and height / viewBox.height.
*/
public double numberFactor;
/**
* http://www.w3.org/TR/SVGMobile12/coords.html#PreserveAspectRatioAttribute
* XXX - use a more sophisticated variable here
*/
public boolean isPreserveAspectRatio = true;
private HashMap<AttributeKey<?>, Object> attributes = new HashMap<AttributeKey<?>, Object>();
@Override
public String toString() {
return "widthPercentFactor:" + widthPercentFactor + ";"
+ "heightPercentFactor:" + heightPercentFactor + ";"
+ "numberFactor:" + numberFactor + ";"
+ attributes;
}
}
/**
* Each SVG element creates a new Viewport that we store
* here.
*/
@Nullable
private Stack<Viewport> viewportStack;
/**
* Holds the style manager used for applying cascading style sheet CSS rules
* to the document.
*/
@Nullable
private StyleManager styleManager;
/**
* Holds the figures that are currently being read.
*/
private LinkedList<Figure> figures;
/**
* Holds the document that is currently being read.
*/
@Nullable
private IXMLElement document;
/** Creates a new instance. */
public SVGInputFormat() {
this(new DefaultSVGFigureFactory());
}
public SVGInputFormat(SVGFigureFactory factory) {
this.factory = factory;
}
@Override
public void read(URI uri, Drawing drawing) throws IOException {
read(new File(uri), drawing);
}
@Override
public void read(URI uri, Drawing drawing, boolean replace) throws IOException {
read(new File(uri), drawing, replace);
}
public void read(File file, Drawing drawing) throws IOException {
read(file, drawing, true);
}
public void read(File file, Drawing drawing, boolean replace) throws IOException {
this.url = file.toURI().toURL();
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
try {
read(in, drawing, replace);
} finally {
in.close();
}
this.url = null;
}
public void read(URL url, Drawing drawing, boolean replace) throws IOException {
this.url = url;
InputStream in = url.openStream();
try {
read(in, drawing, replace);
} finally {
in.close();
}
this.url = null;
}
/**
* This is the main reading method.
*
* @param in The input stream.
* @param drawing The drawing to which this method adds figures.
* @param replace Whether attributes on the drawing object
* should by changed by this method. Set this to false, when reading individual
* images from the clipboard.
*/
@Override
public void read(InputStream in, Drawing drawing, boolean replace) throws IOException {
long start;
if (DEBUG) {
start = System.currentTimeMillis();
}
this.figures = new LinkedList<Figure>();
IXMLParser parser;
try {
parser = XMLParserFactory.createDefaultXMLParser();
} catch (Exception ex) {
InternalError e = new InternalError("Unable to instantiate NanoXML Parser");
e.initCause(ex);
throw e;
}
if (DEBUG) {
System.out.println("SVGInputFormat parser created " + (System.currentTimeMillis() - start));
}
IXMLReader reader = new StdXMLReader(in);
parser.setReader(reader);
if (DEBUG) {
System.out.println("SVGInputFormat reader created " + (System.currentTimeMillis() - start));
}
try {
document = (IXMLElement) parser.parse();
} catch (XMLException ex) {
IOException e = new IOException(ex.getMessage());
e.initCause(ex);
throw e;
}
if (DEBUG) {
System.out.println("SVGInputFormat document created " + (System.currentTimeMillis() - start));
}
// Search for the first 'svg' element in the XML document
// in preorder sequence
IXMLElement svg = document;
Stack<Iterator<IXMLElement>> stack = new Stack<Iterator<IXMLElement>>();
LinkedList<IXMLElement> ll = new LinkedList<IXMLElement>();
ll.add(document);
stack.push(ll.iterator());
while (!stack.empty() && stack.peek().hasNext()) {
Iterator<IXMLElement> iter = stack.peek();
IXMLElement node = iter.next();
Iterator<IXMLElement> children = (node.getChildren() == null) ? null : node.getChildren().iterator();
if (!iter.hasNext()) {
stack.pop();
}
if (children != null && children.hasNext()) {
stack.push(children);
}
if (node.getName() != null
&& node.getName().equals("svg")
&& (node.getNamespace() == null
|| node.getNamespace().equals(SVG_NAMESPACE))) {
svg = node;
break;
}
}
if (svg.getName() == null
|| !svg.getName().equals("svg")
|| (svg.getNamespace() != null
&& !svg.getNamespace().equals(SVG_NAMESPACE))) {
throw new IOException("'svg' element expected: " + svg.getName());
}
//long end1 = System.currentTimeMillis();
// Flatten CSS Styles
initStorageContext(document);
flattenStyles(svg);
//long end2 = System.currentTimeMillis();
readElement(svg);
if (DEBUG) {
long end = System.currentTimeMillis();
System.out.println("SVGInputFormat elapsed:" + (end - start));
}
/*if (DEBUG) System.out.println("SVGInputFormat read:"+(end1-start));
if (DEBUG) System.out.println("SVGInputFormat flatten:"+(end2-end1));
if (DEBUG) System.out.println("SVGInputFormat build:"+(end-end2));
*/
if (replace) {
drawing.removeAllChildren();
}
drawing.addAll(figures);
if (replace) {
Viewport viewport = viewportStack.firstElement();
drawing.set(VIEWPORT_FILL, VIEWPORT_FILL.get(viewport.attributes));
drawing.set(VIEWPORT_FILL_OPACITY, VIEWPORT_FILL_OPACITY.get(viewport.attributes));
drawing.set(VIEWPORT_HEIGHT, VIEWPORT_HEIGHT.get(viewport.attributes));
drawing.set(VIEWPORT_WIDTH, VIEWPORT_WIDTH.get(viewport.attributes));
}
// Get rid of all objects we don't need anymore to help garbage collector.
document.dispose();
identifiedElements.clear();
elementObjects.clear();
viewportStack.clear();
styleManager.clear();
document = null;
identifiedElements = null;
elementObjects = null;
viewportStack = null;
styleManager = null;
}
private void initStorageContext(IXMLElement root) {
identifiedElements = new HashMap<String, IXMLElement>();
identifyElements(root);
elementObjects = new HashMap<IXMLElement, Object>();
viewportStack = new Stack<Viewport>();
viewportStack.push(new Viewport());
styleManager = new StyleManager();
}
/**
* Flattens all CSS styles.
* Styles defined in a "style" attribute and in CSS rules are converted
* into attributes with the same name.
*/
private void flattenStyles(IXMLElement elem)
throws IOException {
if (elem.getName() != null && elem.getName().equals("style")
&& readAttribute(elem, "type", "").equals("text/css")
&& elem.getContent() != null) {
CSSParser cssParser = new CSSParser();
cssParser.parse(elem.getContent(), styleManager);
} else {
if (elem.getNamespace() == null
|| elem.getNamespace().equals(SVG_NAMESPACE)) {
String style = readAttribute(elem, "style", null);
if (style != null) {
for (String styleProperty : style.split(";")) {
String[] stylePropertyElements = styleProperty.split(":");
if (stylePropertyElements.length == 2
&& !elem.hasAttribute(stylePropertyElements[0].trim(), SVG_NAMESPACE)) {
//if (DEBUG) System.out.println("flatten:"+Arrays.toString(stylePropertyElements));
elem.setAttribute(stylePropertyElements[0].trim(), SVG_NAMESPACE, stylePropertyElements[1].trim());
}
}
}
styleManager.applyStylesTo(elem);
for (IXMLElement child : elem.getChildren()) {
flattenStyles(child);
}
}
}
}
/**
* Reads an SVG element of any kind.
* @return Returns the Figure, if the SVG element represents a Figure.
* Returns null in all other cases.
*/
@Nullable
private Figure readElement(IXMLElement elem)
throws IOException {
if (DEBUG) {
System.out.println("SVGInputFormat.readElement " + elem.getName() + " line:" + elem.getLineNr());
}
Figure f = null;
if (elem.getNamespace() == null
|| elem.getNamespace().equals(SVG_NAMESPACE)) {
String name = elem.getName();
if (name == null) {
if (DEBUG) {
System.err.println("SVGInputFormat warning: skipping nameless element at line " + elem.getLineNr());
}
} else if ("a".equals(name)) {
f = readAElement(elem);
} else if ("circle".equals(name)) {
f = readCircleElement(elem);
} else if ("defs".equals(name)) {
readDefsElement(elem);
f = null;
} else if ("ellipse".equals(name)) {
f = readEllipseElement(elem);
} else if ("g".equals(name)) {
f = readGElement(elem);
} else if ("image".equals(name)) {
f = readImageElement(elem);
} else if ("line".equals(name)) {
f = readLineElement(elem);
} else if ("linearGradient".equals(name)) {
readLinearGradientElement(elem);
f = null;
} else if ("path".equals(name)) {
f = readPathElement(elem);
} else if ("polygon".equals(name)) {
f = readPolygonElement(elem);
} else if ("polyline".equals(name)) {
f = readPolylineElement(elem);
} else if ("radialGradient".equals(name)) {
readRadialGradientElement(elem);
f = null;
} else if ("rect".equals(name)) {
f = readRectElement(elem);
} else if ("solidColor".equals(name)) {
readSolidColorElement(elem);
f = null;
} else if ("svg".equals(name)) {
f = readSVGElement(elem);
//f = readGElement(elem);
} else if ("switch".equals(name)) {
f = readSwitchElement(elem);
} else if ("text".equals(name)) {
f = readTextElement(elem);
} else if ("textArea".equals(name)) {
f = readTextAreaElement(elem);
} else if ("title".equals(name)) {
//FIXME - Implement reading of title element
//f = readTitleElement(elem);
} else if ("use".equals(name)) {
f = readUseElement(elem);
} else if ("style".equals(name)) {
// Nothing to do, style elements have been already
// processed in method flattenStyles
} else {
if (DEBUG) {
System.out.println("SVGInputFormat not implemented for <" + name + ">");
}
}
}
if (f instanceof SVGFigure) {
if (((SVGFigure) f).isEmpty()) {
// if (DEBUG) System.out.println("Empty figure "+f);
return null;
}
} else if (f != null) {
if (DEBUG) {
System.out.println("SVGInputFormat warning: not an SVGFigure " + f);
}
}
return f;
}
/**
* Reads an SVG "defs" element.
*/
private void readDefsElement(IXMLElement elem)
throws IOException {
for (IXMLElement child : elem.getChildren()) {
Figure childFigure = readElement(child);
}
}
/**
* Reads an SVG "g" element.
*/
private Figure readGElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readOpacityAttribute(elem, a);
CompositeFigure g = factory.createG(a);
for (IXMLElement child : elem.getChildren()) {
Figure childFigure = readElement(child);
// skip invisible elements
if (readAttribute(child, "visibility", "visible").equals("visible")
&& !readAttribute(child, "display", "inline").equals("none")) {
if (childFigure != null) {
g.basicAdd(childFigure);
}
}
}
readTransformAttribute(elem, a);
if (TRANSFORM.get(a) != null) {
g.transform(TRANSFORM.get(a));
}
return g;
}
/**
* Reads an SVG "a" element.
*/
private Figure readAElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
CompositeFigure g = factory.createG(a);
String href = readAttribute(elem, "xlink:href", null);
if (href == null) {
href = readAttribute(elem, "href", null);
}
String target = readAttribute(elem, "target", null);
if (DEBUG) {
System.out.println("SVGInputFormat.readAElement href=" + href);
}
for (IXMLElement child : elem.getChildren()) {
Figure childFigure = readElement(child);
// skip invisible elements
if (readAttribute(child, "visibility", "visible").equals("visible")
&& !readAttribute(child, "display", "inline").equals("none")) {
if (childFigure != null) {
g.basicAdd(childFigure);
}
}
if (childFigure != null) {
childFigure.set(LINK, href);
childFigure.set(LINK_TARGET, target);
} else {
if (DEBUG) {
System.out.println("SVGInputFormat <a> has no child figure");
}
}
}
return (g.getChildCount() == 1) ? g.getChild(0) : g;
}
/**
* Reads an SVG "svg" element.
*/
@Nullable
private Figure readSVGElement(IXMLElement elem)
throws IOException {
// Establish a new viewport
Viewport viewport = new Viewport();
String widthValue = readAttribute(elem, "width", "100%");
String heightValue = readAttribute(elem, "height", "100%");
viewport.width = toWidth(elem, widthValue);
viewport.height = toHeight(elem, heightValue);
if (readAttribute(elem, "viewBox", "none").equals("none")) {
viewport.viewBox.width = viewport.width;
viewport.viewBox.height = viewport.height;
} else {
String[] viewBoxValues = toWSOrCommaSeparatedArray(readAttribute(elem, "viewBox", "none"));
viewport.viewBox.x = toNumber(elem, viewBoxValues[0]);
viewport.viewBox.y = toNumber(elem, viewBoxValues[1]);
viewport.viewBox.width = toNumber(elem, viewBoxValues[2]);
viewport.viewBox.height = toNumber(elem, viewBoxValues[3]);
// FIXME - Calculate percentages
if (widthValue.indexOf('%') > 0) {
viewport.width = viewport.viewBox.width;
}
if (heightValue.indexOf('%') > 0) {
viewport.height = viewport.viewBox.height;
}
}
if (viewportStack.size() == 1) {
// We always preserve the aspect ratio for to the topmost SVG element.
// This is not compliant, but looks much better.
viewport.isPreserveAspectRatio = true;
} else {
viewport.isPreserveAspectRatio = !readAttribute(elem, "preserveAspectRatio", "none").equals("none");
}
viewport.widthPercentFactor = viewport.viewBox.width / 100d;
viewport.heightPercentFactor = viewport.viewBox.height / 100d;
viewport.numberFactor = Math.min(
viewport.width / viewport.viewBox.width,
viewport.height / viewport.viewBox.height);
AffineTransform viewBoxTransform = new AffineTransform();
viewBoxTransform.translate(
-viewport.viewBox.x * viewport.width / viewport.viewBox.width,
-viewport.viewBox.y * viewport.height / viewport.viewBox.height);
if (viewport.isPreserveAspectRatio) {
double factor = Math.min(
viewport.width / viewport.viewBox.width,
viewport.height / viewport.viewBox.height);
viewBoxTransform.scale(factor, factor);
} else {
viewBoxTransform.scale(
viewport.width / viewport.viewBox.width,
viewport.height / viewport.viewBox.height);
}
viewportStack.push(viewport);
readViewportAttributes(elem, viewportStack.firstElement().attributes);
// Read the figures
for (IXMLElement child : elem.getChildren()) {
Figure childFigure = readElement(child);
// skip invisible elements
if (readAttribute(child, "visibility", "visible").equals("visible")
&& !readAttribute(child, "display", "inline").equals("none")) {
if (childFigure != null) {
childFigure.transform(viewBoxTransform);
figures.add(childFigure);
}
}
}
viewportStack.pop();
return null;
}
/**
* Reads an SVG "rect" element.
*/
private Figure readRectElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readShapeAttributes(elem, a);
double x = toNumber(elem, readAttribute(elem, "x", "0"));
double y = toNumber(elem, readAttribute(elem, "y", "0"));
double w = toWidth(elem, readAttribute(elem, "width", "0"));
double h = toHeight(elem, readAttribute(elem, "height", "0"));
String rxValue = readAttribute(elem, "rx", "none");
String ryValue = readAttribute(elem, "ry", "none");
if ("none".equals(rxValue)) {
rxValue = ryValue;
}
if ("none".equals(ryValue)) {
ryValue = rxValue;
}
double rx = toNumber(elem, rxValue.equals("none") ? "0" : rxValue);
double ry = toNumber(elem, ryValue.equals("none") ? "0" : ryValue);
Figure figure = factory.createRect(x, y, w, h, rx, ry, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "circle" element.
*/
private Figure readCircleElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readShapeAttributes(elem, a);
double cx = toWidth(elem, readAttribute(elem, "cx", "0"));
double cy = toHeight(elem, readAttribute(elem, "cy", "0"));
double r = toWidth(elem, readAttribute(elem, "r", "0"));
Figure figure = factory.createCircle(cx, cy, r, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "ellipse" element.
*/
private Figure readEllipseElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readShapeAttributes(elem, a);
double cx = toWidth(elem, readAttribute(elem, "cx", "0"));
double cy = toHeight(elem, readAttribute(elem, "cy", "0"));
double rx = toWidth(elem, readAttribute(elem, "rx", "0"));
double ry = toHeight(elem, readAttribute(elem, "ry", "0"));
Figure figure = factory.createEllipse(cx, cy, rx, ry, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "image" element.
*/
private Figure readImageElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
double x = toNumber(elem, readAttribute(elem, "x", "0"));
double y = toNumber(elem, readAttribute(elem, "y", "0"));
double w = toWidth(elem, readAttribute(elem, "width", "0"));
double h = toHeight(elem, readAttribute(elem, "height", "0"));
String href = readAttribute(elem, "xlink:href", null);
if (href == null) {
href = readAttribute(elem, "href", null);
}
byte[] imageData = null;
if (href != null) {
if (href.startsWith("data:")) {
int semicolonPos = href.indexOf(';');
if (semicolonPos != -1) {
if (href.indexOf(";base64,") == semicolonPos) {
imageData = Base64.decode(href.substring(semicolonPos + 8));
} else {
throw new IOException("Unsupported encoding in data href in image element:" + href);
}
} else {
throw new IOException("Unsupported data href in image element:" + href);
}
} else {
URL imageUrl = new URL(url, href);
// Check whether the imageURL is an SVG image.
// Load it as a group.
if (imageUrl.getFile().endsWith("svg")) {
SVGInputFormat svgImage = new SVGInputFormat(factory);
Drawing svgDrawing = new DefaultDrawing();
svgImage.read(imageUrl, svgDrawing, true);
CompositeFigure svgImageGroup = factory.createG(a);
for (Figure f : svgDrawing.getChildren()) {
svgImageGroup.add(f);
}
svgImageGroup.setBounds(new Point2D.Double(x, y), new Point2D.Double(x + w, y + h));
return svgImageGroup;
}
// Read the image data from the URL into a byte array
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = new byte[512];
int len = 0;
try {
InputStream in = imageUrl.openStream();
try {
while ((len = in.read(buf)) > 0) {
bout.write(buf, 0, len);
}
imageData = bout.toByteArray();
} finally {
in.close();
}
} catch (FileNotFoundException e) {
// Use empty image
}
}
}
// Create a buffered image from the image data
BufferedImage bufferedImage = null;
if (imageData != null) {
try {
bufferedImage = ImageIO.read(new ByteArrayInputStream(imageData));
} catch (IIOException e) {
System.err.println("SVGInputFormat warning: skipped unsupported image format.");
e.printStackTrace();
}
}
// Delete the image data in case of failure
if (bufferedImage == null) {
imageData = null;
//if (DEBUG) System.out.println("FAILED:"+imageUrl);
}
// Create a figure from the image data and the buffered image.
Figure figure = factory.createImage(x, y, w, h, imageData, bufferedImage, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "line" element.
*/
private Figure readLineElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readLineAttributes(elem, a);
// Because 'line' elements are single lines and thus are geometrically
// one-dimensional, they have no interior; thus, 'line' elements are
// never filled (see the 'fill' property).
if (FILL_COLOR.get(a) != null && STROKE_COLOR.get(a) == null) {
STROKE_COLOR.put(a, FILL_COLOR.get(a));
}
if (FILL_GRADIENT.get(a) != null && STROKE_GRADIENT.get(a) == null) {
STROKE_GRADIENT.put(a, FILL_GRADIENT.get(a));
}
FILL_COLOR.put(a, null);
FILL_GRADIENT.put(a, null);
double x1 = toNumber(elem, readAttribute(elem, "x1", "0"));
double y1 = toNumber(elem, readAttribute(elem, "y1", "0"));
double x2 = toNumber(elem, readAttribute(elem, "x2", "0"));
double y2 = toNumber(elem, readAttribute(elem, "y2", "0"));
Figure figure = factory.createLine(x1, y1, x2, y2, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "polyline" element.
*/
private Figure readPolylineElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readLineAttributes(elem, a);
Point2D.Double[] points = toPoints(elem, readAttribute(elem, "points", ""));
Figure figure = factory.createPolyline(points, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "polygon" element.
*/
private Figure readPolygonElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readShapeAttributes(elem, a);
Point2D.Double[] points = toPoints(elem, readAttribute(elem, "points", ""));
Figure figure = factory.createPolygon(points, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "path" element.
*/
private Figure readPathElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readShapeAttributes(elem, a);
BezierPath[] beziers = toPath(elem, readAttribute(elem, "d", ""));
Figure figure = factory.createPath(beziers, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "text" element.
*/
private Figure readTextElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readShapeAttributes(elem, a);
readFontAttributes(elem, a);
readTextAttributes(elem, a);
String[] xStr = toCommaSeparatedArray(readAttribute(elem, "x", "0"));
String[] yStr = toCommaSeparatedArray(readAttribute(elem, "y", "0"));
Point2D.Double[] coordinates = new Point2D.Double[Math.max(xStr.length, yStr.length)];
double lastX = 0;
double lastY = 0;
for (int i = 0; i < coordinates.length; i++) {
if (xStr.length > i) {
try {
lastX = toNumber(elem, xStr[i]);
} catch (NumberFormatException ex) {
}
}
if (yStr.length > i) {
try {
lastY = toNumber(elem, yStr[i]);
} catch (NumberFormatException ex) {
}
}
coordinates[i] = new Point2D.Double(lastX, lastY);
}
String[] rotateStr = toCommaSeparatedArray(readAttribute(elem, "rotate", ""));
double[] rotate = new double[rotateStr.length];
for (int i = 0; i < rotateStr.length; i++) {
try {
rotate[i] = toDouble(elem, rotateStr[i]);
} catch (NumberFormatException ex) {
rotate[i] = 0;
}
}
DefaultStyledDocument doc = new DefaultStyledDocument();
try {
if (elem.getContent() != null) {
doc.insertString(0, toText(elem, elem.getContent()), null);
} else {
for (IXMLElement node : elem.getChildren()) {
if (node.getName() == null) {
doc.insertString(0, toText(elem, node.getContent()), null);
} else if ("tspan".equals(node.getName())) {
readTSpanElement(node, doc);
} else {
if (DEBUG) {
System.out.println("SVGInputFormat unsupported text node <" + node.getName() + ">");
}
}
}
}
} catch (BadLocationException e) {
InternalError ex = new InternalError(e.getMessage());
ex.initCause(e);
throw ex;
}
Figure figure = factory.createText(coordinates, rotate, doc, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "textArea" element.
*/
private Figure readTextAreaElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a);
readShapeAttributes(elem, a);
readFontAttributes(elem, a);
readTextAttributes(elem, a);
readTextFlowAttributes(elem, a);
double x = toNumber(elem, readAttribute(elem, "x", "0"));
double y = toNumber(elem, readAttribute(elem, "y", "0"));
// XXX - Handle "auto" width and height
double w = toWidth(elem, readAttribute(elem, "width", "0"));
double h = toHeight(elem, readAttribute(elem, "height", "0"));
DefaultStyledDocument doc = new DefaultStyledDocument();
try {
if (elem.getContent() != null) {
doc.insertString(0, toText(elem, elem.getContent()), null);
} else {
for (IXMLElement node : elem.getChildren()) {
if (node.getName() == null) {
doc.insertString(doc.getLength(), toText(elem, node.getContent()), null);
} else if ("tbreak".equals(node.getName())) {
doc.insertString(doc.getLength(), "\n", null);
} else if ("tspan".equals(node.getName())) {
readTSpanElement(node, doc);
} else {
if (DEBUG) {
System.out.println("SVGInputFormat unknown text node " + node.getName());
}
}
}
}
} catch (BadLocationException e) {
InternalError ex = new InternalError(e.getMessage());
ex.initCause(e);
throw ex;
}
Figure figure = factory.createTextArea(x, y, w, h, doc, a);
elementObjects.put(elem, figure);
return figure;
}
/**
* Reads an SVG "tspan" element.
*/
private void readTSpanElement(IXMLElement elem, DefaultStyledDocument doc)
throws IOException {
try {
if (elem.getContent() != null) {
doc.insertString(doc.getLength(), toText(elem, elem.getContent()), null);
} else {
for (IXMLElement node : elem.getChildren()) {
if (node.getName() != null && node.getName().equals("tspan")) {
readTSpanElement(node, doc);
} else {
if (DEBUG) {
System.out.println("SVGInputFormat unknown text node " + node.getName());
}
}
}
}
} catch (BadLocationException e) {
InternalError ex = new InternalError(e.getMessage());
ex.initCause(e);
throw ex;
}
}
private static final HashSet<String> supportedFeatures = new HashSet<String>(
Arrays.asList(new String[]{
"http://www.w3.org/Graphics/SVG/feature/1.2/#SVG-static",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#SVG-static-DOM",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#SVG-animated",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#SVG-all",
"http://www.w3.org/Graphics/SVG/feature/1.2/#CoreAttribute",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#NavigationAttribute",
"http://www.w3.org/Graphics/SVG/feature/1.2/#Structure",
"http://www.w3.org/Graphics/SVG/feature/1.2/#ConditionalProcessing",
"http://www.w3.org/Graphics/SVG/feature/1.2/#ConditionalProcessingAttribute",
"http://www.w3.org/Graphics/SVG/feature/1.2/#Image",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Prefetch",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Discard",
"http://www.w3.org/Graphics/SVG/feature/1.2/#Shape",
"http://www.w3.org/Graphics/SVG/feature/1.2/#Text",
"http://www.w3.org/Graphics/SVG/feature/1.2/#PaintAttribute",
"http://www.w3.org/Graphics/SVG/feature/1.2/#OpacityAttribute",
"http://www.w3.org/Graphics/SVG/feature/1.2/#GraphicsAttribute",
"http://www.w3.org/Graphics/SVG/feature/1.2/#Gradient",
"http://www.w3.org/Graphics/SVG/feature/1.2/#SolidColor",//
"http://www.w3.org/Graphics/SVG/feature/1.2/#Hyperlinking",//
//"http://www.w3.org/Graphics/SVG/feature/1.2/#XlinkAttribute",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#ExternalResourcesRequired",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Scripting",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Handler",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Listener",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#TimedAnimation",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Animation",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Audio",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Video",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Font",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#Extensibility",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#MediaAttribute",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#TextFlow",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#TransformedVideo",
//"http://www.w3.org/Graphics/SVG/feature/1.2/#ComposedVideo",
}));
/**
* Evaluates an SVG "switch" element.
*
*/
@Nullable
private Figure readSwitchElement(IXMLElement elem)
throws IOException {
for (IXMLElement child : elem.getChildren()) {
String[] requiredFeatures = toWSOrCommaSeparatedArray(readAttribute(child, "requiredFeatures", ""));
String[] requiredExtensions = toWSOrCommaSeparatedArray(readAttribute(child, "requiredExtensions", ""));
String[] systemLanguage = toWSOrCommaSeparatedArray(readAttribute(child, "systemLanguage", ""));
String[] requiredFormats = toWSOrCommaSeparatedArray(readAttribute(child, "requiredFormats", ""));
String[] requiredFonts = toWSOrCommaSeparatedArray(readAttribute(child, "requiredFonts", ""));
boolean isMatch;
isMatch = supportedFeatures.containsAll(Arrays.asList(requiredFeatures))
&& requiredExtensions.length == 0
&& requiredFormats.length == 0
&& requiredFonts.length == 0;
if (isMatch && systemLanguage.length > 0) {
isMatch = false;
Locale locale = LocaleUtil.getDefault();
for (String lng : systemLanguage) {
int p = lng.indexOf('-');
if (p == -1) {
if (locale.getLanguage().equals(lng)) {
isMatch = true;
break;
}
} else {
if (locale.getLanguage().equals(lng.substring(0, p))
&& locale.getCountry().toLowerCase().equals(lng.substring(p + 1))) {
isMatch = true;
break;
}
}
}
}
if (isMatch) {
Figure figure = readElement(child);
if (readAttribute(child, "visibility", "visible").equals("visible")
&& !readAttribute(child, "display", "inline").equals("none")) {
return figure;
} else {
return null;
}
}
}
return null;
}
/**
* Reads an SVG "use" element.
*/
@Nullable
@SuppressWarnings("unchecked")
private Figure readUseElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
HashMap<AttributeKey<?>, Object> a2 = new HashMap<AttributeKey<?>, Object>();
readTransformAttribute(elem, a);
readOpacityAttribute(elem, a2);
readUseShapeAttributes(elem, a2);
readFontAttributes(elem, a2);
String href = readAttribute(elem, "xlink:href", null);
if (href != null && href.startsWith("#")) {
IXMLElement refElem = identifiedElements.get(href.substring(1));
if (refElem == null) {
if (DEBUG) {
System.out.println("SVGInputFormat couldn't find href for <use> element:" + href);
}
} else {
Figure obj = readElement(refElem);
if (obj != null) {
Figure figure = obj.clone();
for (Map.Entry<AttributeKey<?>, Object> entry : a2.entrySet()) {
figure.set((AttributeKey<Object>)entry.getKey(), entry.getValue());
}
AffineTransform tx =
(TRANSFORM.get(a) == null) ? new AffineTransform() : TRANSFORM.get(a);
double x = toNumber(elem, readAttribute(elem, "x", "0"));
double y = toNumber(elem, readAttribute(elem, "y", "0"));
tx.translate(x, y);
figure.transform(tx);
return figure;
}
}
}
return null;
}
/**
* Reads an attribute that is inherited.
*/
@Nullable
private String readInheritAttribute(IXMLElement elem, String attributeName, @Nullable String defaultValue) {
if (elem.hasAttribute(attributeName, SVG_NAMESPACE)) {
String value = elem.getAttribute(attributeName, SVG_NAMESPACE, null);
if ("inherit".equals(value)) {
return readInheritAttribute(elem.getParent(), attributeName, defaultValue);
} else {
return value;
}
} else if (elem.hasAttribute(attributeName)) {
String value = elem.getAttribute(attributeName, "");
if ("inherit".equals(value)) {
return readInheritAttribute(elem.getParent(), attributeName, defaultValue);
} else {
return value;
}
} else if (elem.getParent() != null
&& (elem.getParent().getNamespace() == null
|| elem.getParent().getNamespace().equals(SVG_NAMESPACE))) {
return readInheritAttribute(elem.getParent(), attributeName, defaultValue);
} else {
return defaultValue;
}
}
/**
* Reads a color attribute that is inherited.
* This is similar to {@code readInheritAttribute}, but takes care of the
* "currentColor" magic attribute value.
*/
@Nullable
private String readInheritColorAttribute(IXMLElement elem, String attributeName, @Nullable String defaultValue) {
String value = null;
if (elem.hasAttribute(attributeName, SVG_NAMESPACE)) {
value = elem.getAttribute(attributeName, SVG_NAMESPACE, null);
if ("inherit".equals(value)) {
return readInheritColorAttribute(elem.getParent(), attributeName, defaultValue);
}
} else if (elem.hasAttribute(attributeName)) {
value = elem.getAttribute(attributeName, "");
if ("inherit".equals(value)) {
return readInheritColorAttribute(elem.getParent(), attributeName, defaultValue);
}
} else if (elem.getParent() != null
&& (elem.getParent().getNamespace() == null
|| elem.getParent().getNamespace().equals(SVG_NAMESPACE))) {
value = readInheritColorAttribute(elem.getParent(), attributeName, defaultValue);
} else {
value = defaultValue;
}
if (value != null && value.toLowerCase().equals("currentcolor") && !attributeName.equals("color")) {
// Lets do some magic stuff for "currentColor" attribute value
value = readInheritColorAttribute(elem, "color", "defaultValue");
}
return value;
}
/**
* Reads a font size attribute that is inherited.
* As specified by
* http://www.w3.org/TR/SVGMobile12/text.html#FontPropertiesUsedBySVG
* http://www.w3.org/TR/2006/CR-xsl11-20060220/#font-getChildCount
*/
private double readInheritFontSizeAttribute(IXMLElement elem, String attributeName, String defaultValue)
throws IOException {
String value = null;
if (elem.hasAttribute(attributeName, SVG_NAMESPACE)) {
value = elem.getAttribute(attributeName, SVG_NAMESPACE, null);
} else if (elem.hasAttribute(attributeName)) {
value = elem.getAttribute(attributeName, null);
} else if (elem.getParent() != null
&& (elem.getParent().getNamespace() == null
|| elem.getParent().getNamespace().equals(SVG_NAMESPACE))) {
return readInheritFontSizeAttribute(elem.getParent(), attributeName, defaultValue);
} else {
value = defaultValue;
}
if ("inherit".equals(value)) {
return readInheritFontSizeAttribute(elem.getParent(), attributeName, defaultValue);
} else if (SVG_ABSOLUTE_FONT_SIZES.containsKey(value)) {
return SVG_ABSOLUTE_FONT_SIZES.get(value);
} else if (SVG_RELATIVE_FONT_SIZES.containsKey(value)) {
return SVG_RELATIVE_FONT_SIZES.get(value) * readInheritFontSizeAttribute(elem.getParent(), attributeName, defaultValue);
} else if (value.endsWith("%")) {
double factor = Double.valueOf(value.substring(0, value.length() - 1));
return factor * readInheritFontSizeAttribute(elem.getParent(), attributeName, defaultValue);
} else {
//return toScaledNumber(elem, value);
return toNumber(elem, value);
}
}
/**
* Reads an attribute that is not inherited, unless its value is "inherit".
*/
@Nullable
private String readAttribute(IXMLElement elem, String attributeName, @Nullable String defaultValue) {
if (elem.hasAttribute(attributeName, SVG_NAMESPACE)) {
String value = elem.getAttribute(attributeName, SVG_NAMESPACE, null);
if ("inherit".equals(value)) {
return readAttribute(elem.getParent(), attributeName, defaultValue);
} else {
return value;
}
} else if (elem.hasAttribute(attributeName)) {
String value = elem.getAttribute(attributeName, null);
if ("inherit".equals(value)) {
return readAttribute(elem.getParent(), attributeName, defaultValue);
} else {
return value;
}
} else {
return defaultValue;
}
}
/**
* Returns a value as a width.
* http://www.w3.org/TR/SVGMobile12/types.html#DataTypeLength
*/
private double toWidth(IXMLElement elem, String str) throws IOException {
// XXX - Compute xPercentFactor from viewport
return toLength(elem, str,
viewportStack.peek().widthPercentFactor);
}
/**
* Returns a value as a height.
* http://www.w3.org/TR/SVGMobile12/types.html#DataTypeLength
*/
private double toHeight(IXMLElement elem, String str) throws IOException {
// XXX - Compute yPercentFactor from viewport
return toLength(elem, str,
viewportStack.peek().heightPercentFactor);
}
/**
* Returns a value as a number.
* http://www.w3.org/TR/SVGMobile12/types.html#DataTypeNumber
*/
private double toNumber(IXMLElement elem, String str) throws IOException {
return toLength(elem, str, viewportStack.peek().numberFactor);
}
/**
* Returns a value as a length.
* http://www.w3.org/TR/SVGMobile12/types.html#DataTypeLength
*/
private double toLength(IXMLElement elem, String str, double percentFactor) throws IOException {
double scaleFactor = 1d;
if (str == null || str.length() == 0 || str.equals("none")) {
return 0d;
}
if (str.endsWith("%")) {
str = str.substring(0, str.length() - 1);
scaleFactor = percentFactor;
} else if (str.endsWith("px")) {
str = str.substring(0, str.length() - 2);
} else if (str.endsWith("pt")) {
str = str.substring(0, str.length() - 2);
scaleFactor = 1.25;
} else if (str.endsWith("pc")) {
str = str.substring(0, str.length() - 2);
scaleFactor = 15;
} else if (str.endsWith("mm")) {
str = str.substring(0, str.length() - 2);
scaleFactor = 3.543307;
} else if (str.endsWith("cm")) {
str = str.substring(0, str.length() - 2);
scaleFactor = 35.43307;
} else if (str.endsWith("in")) {
str = str.substring(0, str.length() - 2);
scaleFactor = 90;
} else if (str.endsWith("em")) {
str = str.substring(0, str.length() - 2);
// XXX - This doesn't work
scaleFactor = toLength(elem, readAttribute(elem, "font-size", "0"), percentFactor);
} else {
scaleFactor = 1d;
}
return Double.parseDouble(str) * scaleFactor;
}
/**
* Returns a value as a String array.
* The values are separated by commas with optional white space.
*/
public static String[] toCommaSeparatedArray(String str) throws IOException {
return str.split("\\s*,\\s*");
}
/**
* Returns a value as a String array.
* The values are separated by whitespace or by commas with optional white
* space.
*/
public static String[] toWSOrCommaSeparatedArray(String str) throws IOException {
String[] result = str.split("(\\s*,\\s*|\\s+)");
if (result.length == 1 && result[0].equals("")) {
return new String[0];
} else {
return result;
}
}
/**
* Returns a value as a String array.
* The values are separated by commas with optional quotes and white space.
*/
public static String[] toQuotedAndCommaSeparatedArray(String str) throws IOException {
LinkedList<String> values = new LinkedList<String>();
StreamTokenizer tt = new StreamTokenizer(new StringReader(str));
tt.wordChars('a', 'z');
tt.wordChars('A', 'Z');
tt.wordChars(128 + 32, 255);
tt.whitespaceChars(0, ' ');
tt.quoteChar('"');
tt.quoteChar('\'');
while (tt.nextToken() != StreamTokenizer.TT_EOF) {
switch (tt.ttype) {
case StreamTokenizer.TT_WORD:
case '"':
case '\'':
values.add(tt.sval);
break;
}
}
return values.toArray(new String[values.size()]);
}
/**
* Returns a value as a Point2D.Double array.
* as specified in http://www.w3.org/TR/SVGMobile12/shapes.html#PointsBNF
*/
private Point2D.Double[] toPoints(IXMLElement elem, String str) throws IOException {
StringTokenizer tt = new StringTokenizer(str, " ,");
Point2D.Double[] points = new Point2D.Double[tt.countTokens() / 2];
for (int i = 0; i < points.length; i++) {
points[i] = new Point2D.Double(
toNumber(elem, tt.nextToken()),
toNumber(elem, tt.nextToken()));
}
return points;
}
/**
* Returns a value as a BezierPath array.
* as specified in http://www.w3.org/TR/SVGMobile12/paths.html#PathDataBNF
*
* Also supports elliptical arc commands 'a' and 'A' as specified in
* http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
*/
private BezierPath[] toPath(IXMLElement elem, String str) throws IOException {
LinkedList<BezierPath> paths = new LinkedList<BezierPath>();
BezierPath path = null;
Point2D.Double p = new Point2D.Double();
Point2D.Double c1 = new Point2D.Double();
Point2D.Double c2 = new Point2D.Double();
StreamPosTokenizer tt;
if (toPathTokenizer == null) {
tt = new StreamPosTokenizer(new StringReader(str));
tt.resetSyntax();
tt.parseNumbers();
tt.parseExponents();
tt.parsePlusAsNumber();
tt.whitespaceChars(0, ' ');
tt.whitespaceChars(',', ',');
toPathTokenizer = tt;
} else {
tt = toPathTokenizer;
tt.setReader(new StringReader(str));
}
char nextCommand = 'M';
char command = 'M';
Commands:
while (tt.nextToken() != StreamPosTokenizer.TT_EOF) {
if (tt.ttype > 0) {
command = (char) tt.ttype;
} else {
command = nextCommand;
tt.pushBack();
}
BezierPath.Node node;
switch (command) {
case 'M':
// absolute-moveto x y
if (path != null) {
paths.add(path);
}
path = new BezierPath();
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'M' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'M' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.moveTo(p.x, p.y);
nextCommand = 'L';
break;
case 'm':
// relative-moveto dx dy
if (path != null) {
paths.add(path);
}
path = new BezierPath();
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx coordinate missing for 'm' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy coordinate missing for 'm' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.moveTo(p.x, p.y);
nextCommand = 'l';
break;
case 'Z':
case 'z':
// close path
p.x = path.get(0).x[0];
p.y = path.get(0).y[0];
// If the last point and the first point are the same, we
// can merge them
if (path.size() > 1) {
BezierPath.Node first = path.get(0);
BezierPath.Node last = path.get(path.size() - 1);
if (first.x[0] == last.x[0]
&& first.y[0] == last.y[0]) {
if ((last.mask & BezierPath.C1_MASK) != 0) {
first.mask |= BezierPath.C1_MASK;
first.x[1] = last.x[1];
first.y[1] = last.y[1];
}
path.remove(path.size() - 1);
}
}
path.setClosed(true);
break;
case 'L':
// absolute-lineto x y
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'L' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'L' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.lineTo(p.x, p.y);
nextCommand = 'L';
break;
case 'l':
// relative-lineto dx dy
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx coordinate missing for 'l' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy coordinate missing for 'l' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.lineTo(p.x, p.y);
nextCommand = 'l';
break;
case 'H':
// absolute-horizontal-lineto x
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'H' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
path.lineTo(p.x, p.y);
nextCommand = 'H';
break;
case 'h':
// relative-horizontal-lineto dx
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx coordinate missing for 'h' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
path.lineTo(p.x, p.y);
nextCommand = 'h';
break;
case 'V':
// absolute-vertical-lineto y
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'V' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.lineTo(p.x, p.y);
nextCommand = 'V';
break;
case 'v':
// relative-vertical-lineto dy
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy coordinate missing for 'v' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.lineTo(p.x, p.y);
nextCommand = 'v';
break;
case 'C':
// absolute-curveto x1 y1 x2 y2 x y
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x1 coordinate missing for 'C' at position " + tt.getStartPosition() + " in " + str);
}
c1.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y1 coordinate missing for 'C' at position " + tt.getStartPosition() + " in " + str);
}
c1.y = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x2 coordinate missing for 'C' at position " + tt.getStartPosition() + " in " + str);
}
c2.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y2 coordinate missing for 'C' at position " + tt.getStartPosition() + " in " + str);
}
c2.y = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'C' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'C' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
nextCommand = 'C';
break;
case 'c':
// relative-curveto dx1 dy1 dx2 dy2 dx dy
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx1 coordinate missing for 'c' at position " + tt.getStartPosition() + " in " + str);
}
c1.x = p.x + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy1 coordinate missing for 'c' at position " + tt.getStartPosition() + " in " + str);
}
c1.y = p.y + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx2 coordinate missing for 'c' at position " + tt.getStartPosition() + " in " + str);
}
c2.x = p.x + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy2 coordinate missing for 'c' at position " + tt.getStartPosition() + " in " + str);
}
c2.y = p.y + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx coordinate missing for 'c' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy coordinate missing for 'c' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
nextCommand = 'c';
break;
case 'S':
// absolute-shorthand-curveto x2 y2 x y
node = path.get(path.size() - 1);
c1.x = node.x[0] * 2d - node.x[1];
c1.y = node.y[0] * 2d - node.y[1];
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x2 coordinate missing for 'S' at position " + tt.getStartPosition() + " in " + str);
}
c2.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y2 coordinate missing for 'S' at position " + tt.getStartPosition() + " in " + str);
}
c2.y = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'S' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'S' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
nextCommand = 'S';
break;
case 's':
// relative-shorthand-curveto dx2 dy2 dx dy
node = path.get(path.size() - 1);
c1.x = node.x[0] * 2d - node.x[1];
c1.y = node.y[0] * 2d - node.y[1];
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx2 coordinate missing for 's' at position " + tt.getStartPosition() + " in " + str);
}
c2.x = p.x + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy2 coordinate missing for 's' at position " + tt.getStartPosition() + " in " + str);
}
c2.y = p.y + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx coordinate missing for 's' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy coordinate missing for 's' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
nextCommand = 's';
break;
case 'Q':
// absolute-quadto x1 y1 x y
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x1 coordinate missing for 'Q' at position " + tt.getStartPosition() + " in " + str);
}
c1.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y1 coordinate missing for 'Q' at position " + tt.getStartPosition() + " in " + str);
}
c1.y = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'Q' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'Q' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.quadTo(c1.x, c1.y, p.x, p.y);
nextCommand = 'Q';
break;
case 'q':
// relative-quadto dx1 dy1 dx dy
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx1 coordinate missing for 'q' at position " + tt.getStartPosition() + " in " + str);
}
c1.x = p.x + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy1 coordinate missing for 'q' at position " + tt.getStartPosition() + " in " + str);
}
c1.y = p.y + tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx coordinate missing for 'q' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy coordinate missing for 'q' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.quadTo(c1.x, c1.y, p.x, p.y);
nextCommand = 'q';
break;
case 'T':
// absolute-shorthand-quadto x y
node = path.get(path.size() - 1);
c1.x = node.x[0] * 2d - node.x[1];
c1.y = node.y[0] * 2d - node.y[1];
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'T' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'T' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.quadTo(c1.x, c1.y, p.x, p.y);
nextCommand = 'T';
break;
case 't':
// relative-shorthand-quadto dx dy
node = path.get(path.size() - 1);
c1.x = node.x[0] * 2d - node.x[1];
c1.y = node.y[0] * 2d - node.y[1];
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dx coordinate missing for 't' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("dy coordinate missing for 't' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.quadTo(c1.x, c1.y, p.x, p.y);
nextCommand = 's';
break;
case 'A': {
// absolute-elliptical-arc rx ry x-axis-rotation large-arc-flag sweep-flag x y
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("rx coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
// If rX or rY have negative signs, these are dropped;
// the absolute value is used instead.
double rx = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("ry coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
double ry = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x-axis-rotation missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
double xAxisRotation = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("large-arc-flag missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
boolean largeArcFlag = tt.nval != 0;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("sweep-flag missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
boolean sweepFlag = tt.nval != 0;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
p.x = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
p.y = tt.nval;
path.arcTo(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, p.x, p.y);
nextCommand = 'A';
break;
}
case 'a': {
// absolute-elliptical-arc rx ry x-axis-rotation large-arc-flag sweep-flag x y
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("rx coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
// If rX or rY have negative signs, these are dropped;
// the absolute value is used instead.
double rx = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("ry coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
double ry = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x-axis-rotation missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
double xAxisRotation = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("large-arc-flag missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
boolean largeArcFlag = tt.nval != 0;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("sweep-flag missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
boolean sweepFlag = tt.nval != 0;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("x coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
p.x += tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("y coordinate missing for 'A' at position " + tt.getStartPosition() + " in " + str);
}
p.y += tt.nval;
path.arcTo(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, p.x, p.y);
nextCommand = 'a';
break;
}
default:
if (DEBUG) {
System.out.println("SVGInputFormat.toPath aborting after illegal path command: " + command + " found in path " + str);
}
break Commands;
//throw new IOException("Illegal command: "+command);
}
}
if (path != null) {
paths.add(path);
}
return paths.toArray(new BezierPath[paths.size()]);
}
/* Reads core attributes as listed in
* http://www.w3.org/TR/SVGMobile12/feature.html#CoreAttribute
*/
private void readCoreAttributes(IXMLElement elem, HashMap<AttributeKey<?>, Object> a)
throws IOException {
// read "id" or "xml:id"
//identifiedElements.putx(elem.get("id"), elem);
//identifiedElements.putx(elem.get("xml:id"), elem);
// XXX - Add
// xml:base
// xml:lang
// xml:space
// class
}
/**
* Puts all elments with an "id" or an "xml:id" attribute into the
* hashtable {@code identifiedElements}.
*/
private void identifyElements(IXMLElement elem) {
identifiedElements.put(elem.getAttribute("id", ""), elem);
identifiedElements.put(elem.getAttribute("xml:id", ""), elem);
for (IXMLElement child : elem.getChildren()) {
identifyElements(child);
}
}
/* Reads object/group opacity as described in
* http://www.w3.org/TR/SVGMobile12/painting.html#groupOpacity
*/
private void readOpacityAttribute(IXMLElement elem, Map<AttributeKey<?>, Object> a)
throws IOException {
//'opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: 'image' element
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
//<opacity-value>
//The uniform opacity setting must be applied across an entire object.
//Any values outside the range 0.0 (fully transparent) to 1.0
//(fully opaque) shall be clamped to this range.
//(See Clamping values which are restricted to a particular range.)
double value = toDouble(elem, readAttribute(elem, "opacity", "1"), 1, 0, 1);
OPACITY.put(a, value);
}
/* Reads text attributes as listed in
* http://www.w3.org/TR/SVGMobile12/feature.html#Text
*/
private void readTextAttributes(IXMLElement elem, Map<AttributeKey<?>, Object> a)
throws IOException {
Object value;
//'text-anchor'
//Value: start | middle | end | inherit
//Initial: start
//Applies to: 'text' IXMLElement
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "text-anchor", "start");
if (SVG_TEXT_ANCHORS.get(value) != null) {
TEXT_ANCHOR.put(a, SVG_TEXT_ANCHORS.get(value));
}
//'display-align'
//Value: auto | before | center | after | inherit
//Initial: auto
//Applies to: 'textArea'
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "display-align", "auto");
// XXX - Implement me properly
if (!value.equals("auto")) {
if ("center".equals(value)) {
TEXT_ANCHOR.put(a, TextAnchor.MIDDLE);
} else if ("before".equals(value)) {
TEXT_ANCHOR.put(a, TextAnchor.END);
}
}
//text-align
//Value: start | end | center | inherit
//Initial: start
//Applies to: textArea elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
value = readInheritAttribute(elem, "text-align", "start");
// XXX - Implement me properly
if (!value.equals("start")) {
TEXT_ALIGN.put(a, SVG_TEXT_ALIGNS.get(value));
}
}
/* Reads text flow attributes as listed in
* http://www.w3.org/TR/SVGMobile12/feature.html#TextFlow
*/
private void readTextFlowAttributes(IXMLElement elem, HashMap<AttributeKey<?>, Object> a)
throws IOException {
Object value;
//'line-increment'
//Value: auto | <number> | inherit
//Initial: auto
//Applies to: 'textArea'
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "line-increment", "auto");
if (DEBUG) {
System.out.println("SVGInputFormat not implemented line-increment=" + value);
}
}
/* Reads the transform attribute as specified in
* http://www.w3.org/TR/SVGMobile12/coords.html#TransformAttribute
*/
private void readTransformAttribute(IXMLElement elem, HashMap<AttributeKey<?>, Object> a)
throws IOException {
String value;
value = readAttribute(elem, "transform", "none");
if (!value.equals("none")) {
TRANSFORM.put(a, toTransform(elem, value));
}
}
/* Reads solid color attributes.
*/
private void readSolidColorElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
// 'solid-color'
//Value: currentColor | <color> | inherit
//Initial: black
//Applies to: 'solidColor' elements
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified <color> value, except inherit
Color color = toColor(elem, readAttribute(elem, "solid-color", "black"));
//'solid-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: 'solidColor' elements
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
double opacity = toDouble(elem, readAttribute(elem, "solid-opacity", "1"), 1, 0, 1);
if (opacity != 1) {
color = new Color(((int) (255 * opacity) << 24) | (0xffffff & color.getRGB()), true);
}
elementObjects.put(elem, color);
}
/** Reads shape attributes.
*/
private void readShapeAttributes(IXMLElement elem, HashMap<AttributeKey<?>, Object> a)
throws IOException {
Object objectValue;
String value;
double doubleValue;
//'color'
// Value: <color> | inherit
// Initial: depends on user agent
// Applies to: None. Indirectly affects other properties via currentColor
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified <color> value, except inherit
//
// value = readInheritAttribute(elem, "color", "black");
// if (DEBUG) System.out.println("color="+value);
//'color-rendering'
// Value: auto | optimizeSpeed | optimizeQuality | inherit
// Initial: auto
// Applies to: container elements , graphics elements and 'animateColor'
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
//
// value = readInheritAttribute(elem, "color-rendering", "auto");
// if (DEBUG) System.out.println("color-rendering="+value);
// 'fill'
// Value: <paint> | inherit (See Specifying paint)
// Initial: black
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: "none", system paint, specified <color> value or absolute IRI
objectValue = toPaint(elem, readInheritColorAttribute(elem, "fill", "black"));
if (objectValue instanceof Color) {
FILL_COLOR.put(a, (Color) objectValue);
} else if (objectValue instanceof Gradient) {
FILL_GRADIENT.putClone(a, (Gradient) objectValue);
} else if (objectValue == null) {
FILL_COLOR.put(a, null);
} else {
FILL_COLOR.put(a, null);
if (DEBUG) {
System.out.println("SVGInputFormat not implemented fill=" + objectValue);
}
}
//'fill-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "fill-opacity", "1");
FILL_OPACITY.put(a, toDouble(elem, (String) objectValue, 1d, 0d, 1d));
// 'fill-rule'
// Value: nonzero | evenodd | inherit
// Initial: nonzero
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "fill-rule", "nonzero");
WINDING_RULE.put(a, SVG_FILL_RULES.get(value));
//'stroke'
//Value: <paint> | inherit (See Specifying paint)
//Initial: none
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: "none", system paint, specified <color> value
// or absolute IRI
objectValue = toPaint(elem, readInheritColorAttribute(elem, "stroke", "none"));
if (objectValue instanceof Color) {
STROKE_COLOR.put(a, (Color) objectValue);
} else if (objectValue instanceof Gradient) {
STROKE_GRADIENT.putClone(a, (Gradient) objectValue);
} else if (objectValue == null) {
STROKE_COLOR.put(a, null);
} else {
STROKE_COLOR.put(a, null);
if (DEBUG) {
System.out.println("SVGInputFormat not implemented stroke=" + objectValue);
}
}
//'stroke-dasharray'
//Value: none | <dasharray> | inherit
//Initial: none
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes (non-additive)
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-dasharray", "none");
if (!value.equals("none")) {
String[] values = toWSOrCommaSeparatedArray(value);
double[] dashes = new double[values.length];
for (int i = 0; i < values.length; i++) {
dashes[i] = toNumber(elem, values[i]);
}
STROKE_DASHES.put(a, dashes);
}
//'stroke-dashoffset'
//Value: <length> | inherit
//Initial: 0
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
doubleValue = toNumber(elem, readInheritAttribute(elem, "stroke-dashoffset", "0"));
STROKE_DASH_PHASE.put(a, doubleValue);
IS_STROKE_DASH_FACTOR.put(a, false);
//'stroke-linecap'
//Value: butt | round | square | inherit
//Initial: butt
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-linecap", "butt");
STROKE_CAP.put(a, SVG_STROKE_LINECAPS.get(value));
//'stroke-linejoin'
//Value: miter | round | bevel | inherit
//Initial: miter
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-linejoin", "miter");
STROKE_JOIN.put(a, SVG_STROKE_LINEJOINS.get(value));
//'stroke-miterlimit'
//Value: <miterlimit> | inherit
//Initial: 4
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
doubleValue = toDouble(elem, readInheritAttribute(elem, "stroke-miterlimit", "4"), 4d, 1d, Double.MAX_VALUE);
STROKE_MITER_LIMIT.put(a, doubleValue);
IS_STROKE_MITER_LIMIT_FACTOR.put(a, false);
//'stroke-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "stroke-opacity", "1");
STROKE_OPACITY.put(a, toDouble(elem, (String) objectValue, 1d, 0d, 1d));
//'stroke-width'
//Value: <length> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
doubleValue = toNumber(elem, readInheritAttribute(elem, "stroke-width", "1"));
STROKE_WIDTH.put(a, doubleValue);
}
/* Reads shape attributes for the SVG "use" element.
*/
private void readUseShapeAttributes(IXMLElement elem, HashMap<AttributeKey<?>, Object> a)
throws IOException {
Object objectValue;
String value;
double doubleValue;
//'color'
// Value: <color> | inherit
// Initial: depends on user agent
// Applies to: None. Indirectly affects other properties via currentColor
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified <color> value, except inherit
//
// value = readInheritAttribute(elem, "color", "black");
// if (DEBUG) System.out.println("color="+value);
//'color-rendering'
// Value: auto | optimizeSpeed | optimizeQuality | inherit
// Initial: auto
// Applies to: container elements , graphics elements and 'animateColor'
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
//
// value = readInheritAttribute(elem, "color-rendering", "auto");
// if (DEBUG) System.out.println("color-rendering="+value);
// 'fill'
// Value: <paint> | inherit (See Specifying paint)
// Initial: black
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: "none", system paint, specified <color> value or absolute IRI
objectValue = readInheritColorAttribute(elem, "fill", null);
if (objectValue != null) {
objectValue = toPaint(elem, (String) objectValue);
if (objectValue instanceof Color) {
FILL_COLOR.put(a, (Color) objectValue);
} else if (objectValue instanceof Gradient) {
FILL_GRADIENT.put(a, (Gradient) objectValue);
} else if (objectValue == null) {
FILL_COLOR.put(a, null);
} else {
FILL_COLOR.put(a, null);
if (DEBUG) {
System.out.println("SVGInputFormat not implemented fill=" + objectValue);
}
}
}
//'fill-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "fill-opacity", null);
if (objectValue != null) {
FILL_OPACITY.put(a, toDouble(elem, (String) objectValue, 1d, 0d, 1d));
}
// 'fill-rule'
// Value: nonzero | evenodd | inherit
// Initial: nonzero
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "fill-rule", null);
if (value != null) {
WINDING_RULE.put(a, SVG_FILL_RULES.get(value));
}
//'stroke'
//Value: <paint> | inherit (See Specifying paint)
//Initial: none
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: "none", system paint, specified <color> value
// or absolute IRI
objectValue = toPaint(elem, readInheritColorAttribute(elem, "stroke", null));
if (objectValue != null) {
if (objectValue instanceof Color) {
STROKE_COLOR.put(a, (Color) objectValue);
} else if (objectValue instanceof Gradient) {
STROKE_GRADIENT.put(a, (Gradient) objectValue);
}
}
//'stroke-dasharray'
//Value: none | <dasharray> | inherit
//Initial: none
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes (non-additive)
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-dasharray", null);
if (value != null && !value.equals("none")) {
String[] values = toCommaSeparatedArray(value);
double[] dashes = new double[values.length];
for (int i = 0; i < values.length; i++) {
dashes[i] = toNumber(elem, values[i]);
}
STROKE_DASHES.put(a, dashes);
}
//'stroke-dashoffset'
//Value: <length> | inherit
//Initial: 0
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "stroke-dashoffset", null);
if (objectValue != null) {
doubleValue = toNumber(elem, (String) objectValue);
STROKE_DASH_PHASE.put(a, doubleValue);
IS_STROKE_DASH_FACTOR.put(a, false);
}
//'stroke-linecap'
//Value: butt | round | square | inherit
//Initial: butt
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-linecap", null);
if (value != null) {
STROKE_CAP.put(a, SVG_STROKE_LINECAPS.get(value));
}
//'stroke-linejoin'
//Value: miter | round | bevel | inherit
//Initial: miter
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-linejoin", null);
if (value != null) {
STROKE_JOIN.put(a, SVG_STROKE_LINEJOINS.get(value));
}
//'stroke-miterlimit'
//Value: <miterlimit> | inherit
//Initial: 4
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "stroke-miterlimit", null);
if (objectValue != null) {
doubleValue = toDouble(elem, (String) objectValue, 4d, 1d, Double.MAX_VALUE);
STROKE_MITER_LIMIT.put(a, doubleValue);
IS_STROKE_MITER_LIMIT_FACTOR.put(a, false);
}
//'stroke-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "stroke-opacity", null);
if (objectValue != null) {
STROKE_OPACITY.put(a, toDouble(elem, (String) objectValue, 1d, 0d, 1d));
}
//'stroke-width'
//Value: <length> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "stroke-width", null);
if (objectValue != null) {
doubleValue = toNumber(elem, (String) objectValue);
STROKE_WIDTH.put(a, doubleValue);
}
}
/** Reads line and polyline attributes.
*/
private void readLineAttributes(IXMLElement elem, HashMap<AttributeKey<?>, Object> a)
throws IOException {
Object objectValue;
String value;
double doubleValue;
//'color'
// Value: <color> | inherit
// Initial: depends on user agent
// Applies to: None. Indirectly affects other properties via currentColor
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified <color> value, except inherit
//
// value = readInheritAttribute(elem, "color", "black");
// if (DEBUG) System.out.println("color="+value);
//'color-rendering'
// Value: auto | optimizeSpeed | optimizeQuality | inherit
// Initial: auto
// Applies to: container elements , graphics elements and 'animateColor'
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
//
// value = readInheritAttribute(elem, "color-rendering", "auto");
// if (DEBUG) System.out.println("color-rendering="+value);
// 'fill'
// Value: <paint> | inherit (See Specifying paint)
// Initial: black
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: "none", system paint, specified <color> value or absolute IRI
objectValue = toPaint(elem, readInheritColorAttribute(elem, "fill", "none"));
if (objectValue instanceof Color) {
FILL_COLOR.put(a, (Color) objectValue);
} else if (objectValue instanceof Gradient) {
FILL_GRADIENT.putClone(a, (Gradient) objectValue);
} else if (objectValue == null) {
FILL_COLOR.put(a, null);
} else {
FILL_COLOR.put(a, null);
if (DEBUG) {
System.out.println("SVGInputFormat not implemented fill=" + objectValue);
}
}
//'fill-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "fill-opacity", "1");
FILL_OPACITY.put(a, toDouble(elem, (String) objectValue, 1d, 0d, 1d));
// 'fill-rule'
// Value: nonzero | evenodd | inherit
// Initial: nonzero
// Applies to: shapes and text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "fill-rule", "nonzero");
WINDING_RULE.put(a, SVG_FILL_RULES.get(value));
//'stroke'
//Value: <paint> | inherit (See Specifying paint)
//Initial: none
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: "none", system paint, specified <color> value
// or absolute IRI
objectValue = toPaint(elem, readInheritColorAttribute(elem, "stroke", "black"));
if (objectValue instanceof Color) {
STROKE_COLOR.put(a, (Color) objectValue);
} else if (objectValue instanceof Gradient) {
STROKE_GRADIENT.putClone(a, (Gradient) objectValue);
} else if (objectValue == null) {
STROKE_COLOR.put(a, null);
} else {
STROKE_COLOR.put(a, null);
if (DEBUG) {
System.out.println("SVGInputFormat not implemented stroke=" + objectValue);
}
}
//'stroke-dasharray'
//Value: none | <dasharray> | inherit
//Initial: none
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes (non-additive)
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-dasharray", "none");
if (!value.equals("none")) {
String[] values = toWSOrCommaSeparatedArray(value);
double[] dashes = new double[values.length];
for (int i = 0; i < values.length; i++) {
dashes[i] = toNumber(elem, values[i]);
}
STROKE_DASHES.put(a, dashes);
}
//'stroke-dashoffset'
//Value: <length> | inherit
//Initial: 0
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
doubleValue = toNumber(elem, readInheritAttribute(elem, "stroke-dashoffset", "0"));
STROKE_DASH_PHASE.put(a, doubleValue);
IS_STROKE_DASH_FACTOR.put(a, false);
//'stroke-linecap'
//Value: butt | round | square | inherit
//Initial: butt
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-linecap", "butt");
STROKE_CAP.put(a, SVG_STROKE_LINECAPS.get(value));
//'stroke-linejoin'
//Value: miter | round | bevel | inherit
//Initial: miter
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "stroke-linejoin", "miter");
STROKE_JOIN.put(a, SVG_STROKE_LINEJOINS.get(value));
//'stroke-miterlimit'
//Value: <miterlimit> | inherit
//Initial: 4
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
doubleValue = toDouble(elem, readInheritAttribute(elem, "stroke-miterlimit", "4"), 4d, 1d, Double.MAX_VALUE);
STROKE_MITER_LIMIT.put(a, doubleValue);
IS_STROKE_MITER_LIMIT_FACTOR.put(a, false);
//'stroke-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
objectValue = readInheritAttribute(elem, "stroke-opacity", "1");
STROKE_OPACITY.put(a, toDouble(elem, (String) objectValue, 1d, 0d, 1d));
//'stroke-width'
//Value: <length> | inherit
//Initial: 1
//Applies to: shapes and text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
doubleValue = toNumber(elem, readInheritAttribute(elem, "stroke-width", "1"));
STROKE_WIDTH.put(a, doubleValue);
}
/* Reads viewport attributes.
*/
private void readViewportAttributes(IXMLElement elem, HashMap<AttributeKey<?>, Object> a)
throws IOException {
Object value;
Double doubleValue;
// width of the viewport
value = readAttribute(elem, "width", null);
if (DEBUG) {
System.out.println("SVGInputFormat READ viewport w/h factors:" + viewportStack.peek().widthPercentFactor + "," + viewportStack.peek().heightPercentFactor);
}
if (value != null) {
doubleValue = toLength(elem, (String) value, viewportStack.peek().widthPercentFactor);
VIEWPORT_WIDTH.put(a, doubleValue);
}
// height of the viewport
value = readAttribute(elem, "height", null);
if (value != null) {
doubleValue = toLength(elem, (String) value, viewportStack.peek().heightPercentFactor);
VIEWPORT_HEIGHT.put(a, doubleValue);
}
//'viewport-fill'
//Value: "none" | <color> | inherit
//Initial: none
//Applies to: viewport-creating elements
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: "none" or specified <color> value, except inherit
value = toPaint(elem, readInheritColorAttribute(elem, "viewport-fill", "none"));
if (value == null || (value instanceof Color)) {
VIEWPORT_FILL.put(a, (Color) value);
}
//'viewport-fill-opacity'
//Value: <opacity-value> | inherit
//Initial: 1.0
//Applies to: viewport-creating elements
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
doubleValue = toDouble(elem, readAttribute(elem, "viewport-fill-opacity", "1.0"));
VIEWPORT_FILL_OPACITY.put(a, doubleValue);
}
/* Reads graphics attributes as listed in
* http://www.w3.org/TR/SVGMobile12/feature.html#GraphicsAttribute
*/
private void readGraphicsAttributes(IXMLElement elem, Figure f)
throws IOException {
Object value;
// 'display'
// Value: inline | block | list-item |
// run-in | compact | marker |
// table | inline-table | table-row-group | table-header-group |
// table-footer-group | table-row | table-column-group | table-column |
// table-cell | table-caption | none | inherit
// Initial: inline
// Applies to: 'svg' , 'g' , 'switch' , 'a' , 'foreignObject' ,
// graphics elements (including the text content block elements) and text
// sub-elements (for example, 'tspan' and 'a' )
// Inherited: no
// Percentages: N/A
// Media: all
// Animatable: yes
// Computed value: Specified value, except inherit
value = readAttribute(elem, "display", "inline");
if (DEBUG) {
System.out.println("SVGInputFormat not implemented display=" + value);
}
//'image-rendering'
//Value: auto | optimizeSpeed | optimizeQuality | inherit
//Initial: auto
//Applies to: images
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "image-rendering", "auto");
if (DEBUG) {
System.out.println("SVGInputFormat not implemented image-rendering=" + value);
}
//'pointer-events'
//Value: boundingBox | visiblePainted | visibleFill | visibleStroke | visible |
//painted | fill | stroke | all | none | inherit
//Initial: visiblePainted
//Applies to: graphics elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "pointer-events", "visiblePainted");
if (DEBUG) {
System.out.println("SVGInputFormat not implemented pointer-events=" + value);
}
// 'shape-rendering'
//Value: auto | optimizeSpeed | crispEdges |
//geometricPrecision | inherit
//Initial: auto
//Applies to: shapes
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "shape-rendering", "auto");
if (DEBUG) {
System.out.println("SVGInputFormat not implemented shape-rendering=" + value);
}
//'text-rendering'
//Value: auto | optimizeSpeed | optimizeLegibility |
//geometricPrecision | inherit
//Initial: auto
//Applies to: text content block elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "text-rendering", "auto");
if (DEBUG) {
System.out.println("SVGInputFormat not implemented text-rendering=" + value);
}
//'vector-effect'
//Value: non-scaling-stroke | none | inherit
//Initial: none
//Applies to: graphics elements
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readAttribute(elem, "vector-effect", "none");
if (DEBUG) {
System.out.println("SVGInputFormat not implemented vector-effect=" + value);
}
//'visibility'
//Value: visible | hidden | collapse | inherit
//Initial: visible
//Applies to: graphics elements (including the text content block
// elements) and text sub-elements (for example, 'tspan' and 'a' )
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "visibility", null);
if (DEBUG) {
System.out.println("SVGInputFormat not implemented visibility=" + value);
}
}
/**
* Reads an SVG "linearGradient" element.
*/
private void readLinearGradientElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
double x1 = toLength(elem, readAttribute(elem, "x1", "0"), 0.01);
double y1 = toLength(elem, readAttribute(elem, "y1", "0"), 0.01);
double x2 = toLength(elem, readAttribute(elem, "x2", "1"), 0.01);
double y2 = toLength(elem, readAttribute(elem, "y2", "0"), 0.01);
boolean isRelativeToFigureBounds = readAttribute(elem, "gradientUnits", "objectBoundingBox").equals("objectBoundingBox");
ArrayList<IXMLElement> stops = elem.getChildrenNamed("stop", SVG_NAMESPACE);
if (stops.size() == 0) {
stops = elem.getChildrenNamed("stop");
}
if (stops.size() == 0) {
// FIXME - Implement xlink support throughouth SVGInputFormat
String xlink = readAttribute(elem, "xlink:href", "");
if (xlink.startsWith("#")
&& identifiedElements.get(xlink.substring(1)) != null) {
stops = identifiedElements.get(xlink.substring(1)).getChildrenNamed("stop", SVG_NAMESPACE);
if (stops.size() == 0) {
stops = identifiedElements.get(xlink.substring(1)).getChildrenNamed("stop");
}
}
}
if (stops.size() == 0) {
if (DEBUG) {
System.out.println("SVGInpuFormat: Warning no stops in linearGradient " + elem);
}
}
double[] stopOffsets = new double[stops.size()];
Color[] stopColors = new Color[stops.size()];
double[] stopOpacities = new double[stops.size()];
for (int i = 0; i < stops.size(); i++) {
IXMLElement stopElem = stops.get(i);
String offsetStr = readAttribute(stopElem, "offset", "0");
if (offsetStr.endsWith("%")) {
stopOffsets[i] = toDouble(stopElem, offsetStr.substring(0, offsetStr.length() - 1), 0, 0, 100) / 100d;
} else {
stopOffsets[i] = toDouble(stopElem, offsetStr, 0, 0, 1);
}
// 'stop-color'
// Value: currentColor | <color> | inherit
// Initial: black
// Applies to: 'stop' elements
// Inherited: no
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified <color> value, except i
stopColors[i] = toColor(stopElem, readAttribute(stopElem, "stop-color", "black"));
if (stopColors[i] == null) {
stopColors[i] = new Color(0x0, true);
//throw new IOException("stop color missing in "+stopElem);
}
//'stop-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: 'stop' elements
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
stopOpacities[i] = toDouble(stopElem, readAttribute(stopElem, "stop-opacity", "1"), 1, 0, 1);
}
AffineTransform tx = toTransform(elem, readAttribute(elem, "gradientTransform", "none"));
Gradient gradient = factory.createLinearGradient(
x1, y1, x2, y2,
stopOffsets, stopColors, stopOpacities,
isRelativeToFigureBounds, tx);
elementObjects.put(elem, gradient);
}
/**
* Reads an SVG "radialGradient" element.
*/
private void readRadialGradientElement(IXMLElement elem)
throws IOException {
HashMap<AttributeKey<?>, Object> a = new HashMap<AttributeKey<?>, Object>();
readCoreAttributes(elem, a);
double cx = toLength(elem, readAttribute(elem, "cx", "0.5"), 0.01);
double cy = toLength(elem, readAttribute(elem, "cy", "0.5"), 0.01);
double fx = toLength(elem, readAttribute(elem, "fx", readAttribute(elem, "cx", "0.5")), 0.01);
double fy = toLength(elem, readAttribute(elem, "fy", readAttribute(elem, "cy", "0.5")), 0.01);
double r = toLength(elem, readAttribute(elem, "r", "0.5"), 0.01);
boolean isRelativeToFigureBounds =
readAttribute(elem, "gradientUnits", "objectBoundingBox").equals("objectBoundingBox");
ArrayList<IXMLElement> stops = elem.getChildrenNamed("stop", SVG_NAMESPACE);
if (stops.size() == 0) {
stops = elem.getChildrenNamed("stop");
}
if (stops.size() == 0) {
// FIXME - Implement xlink support throughout SVGInputFormat
String xlink = readAttribute(elem, "xlink:href", "");
if (xlink.startsWith("#")
&& identifiedElements.get(xlink.substring(1)) != null) {
stops = identifiedElements.get(xlink.substring(1)).getChildrenNamed("stop", SVG_NAMESPACE);
if (stops.size() == 0) {
stops = identifiedElements.get(xlink.substring(1)).getChildrenNamed("stop");
}
}
}
double[] stopOffsets = new double[stops.size()];
Color[] stopColors = new Color[stops.size()];
double[] stopOpacities = new double[stops.size()];
for (int i = 0; i < stops.size(); i++) {
IXMLElement stopElem = stops.get(i);
String offsetStr = readAttribute(stopElem, "offset", "0");
if (offsetStr.endsWith("%")) {
stopOffsets[i] = toDouble(stopElem, offsetStr.substring(0, offsetStr.length() - 1), 0, 0, 100) / 100d;
} else {
stopOffsets[i] = toDouble(stopElem, offsetStr, 0, 0, 1);
}
// 'stop-color'
// Value: currentColor | <color> | inherit
// Initial: black
// Applies to: 'stop' elements
// Inherited: no
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified <color> value, except i
stopColors[i] = toColor(stopElem, readAttribute(stopElem, "stop-color", "black"));
if (stopColors[i] == null) {
stopColors[i] = new Color(0x0, true);
//throw new IOException("stop color missing in "+stopElem);
}
//'stop-opacity'
//Value: <opacity-value> | inherit
//Initial: 1
//Applies to: 'stop' elements
//Inherited: no
//Percentages: N/A
//Media: visual
//Animatable: yes
//Computed value: Specified value, except inherit
stopOpacities[i] = toDouble(stopElem, readAttribute(stopElem, "stop-opacity", "1"), 1, 0, 1);
}
AffineTransform tx = toTransform(elem, readAttribute(elem, "gradientTransform", "none"));
Gradient gradient = factory.createRadialGradient(
cx, cy, fx, fy, r,
stopOffsets, stopColors, stopOpacities,
isRelativeToFigureBounds,
tx);
elementObjects.put(elem, gradient);
}
/* Reads font attributes as listed in
* http://www.w3.org/TR/SVGMobile12/feature.html#Font
*/
private void readFontAttributes(IXMLElement elem, Map<AttributeKey<?>, Object> a)
throws IOException {
String value;
double doubleValue;
// 'font-family'
// Value: [[ <family-name> |
// <generic-family> ],]* [<family-name> |
// <generic-family>] | inherit
// Initial: depends on user agent
// Applies to: text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "font-family", "Dialog");
String[] familyNames = toQuotedAndCommaSeparatedArray(value);
Font font = null;
// Try to find a font with exactly matching name
for (int i = 0; i < familyNames.length; i++) {
try {
font = (Font) fontFormatter.stringToValue(familyNames[i]);
break;
} catch (ParseException e) {
}
}
if (font == null) {
// Try to create a similar font using the first name in the list
if (familyNames.length > 0) {
fontFormatter.setAllowsUnknownFont(true);
try {
font = (Font) fontFormatter.stringToValue(familyNames[0]);
} catch (ParseException e) {
}
fontFormatter.setAllowsUnknownFont(false);
}
}
if (font == null) {
// Fallback to the system Dialog font
font = new Font("Dialog", Font.PLAIN, 12);
}
FONT_FACE.put(a, font);
// 'font-getChildCount'
// Value: <absolute-getChildCount> | <relative-getChildCount> |
// <length> | inherit
// Initial: medium
// Applies to: text content elements
// Inherited: yes, the computed value is inherited
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Absolute length
doubleValue = readInheritFontSizeAttribute(elem, "font-size", "medium");
FONT_SIZE.put(a, doubleValue);
// 'font-style'
// Value: normal | italic | oblique | inherit
// Initial: normal
// Applies to: text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "font-style", "normal");
FONT_ITALIC.put(a, value.equals("italic"));
//'font-variant'
//Value: normal | small-caps | inherit
//Initial: normal
//Applies to: text content elements
//Inherited: yes
//Percentages: N/A
//Media: visual
//Animatable: no
//Computed value: Specified value, except inherit
value = readInheritAttribute(elem, "font-variant", "normal");
// if (DEBUG) System.out.println("font-variant="+value);
// 'font-weight'
// Value: normal | bold | bolder | lighter | 100 | 200 | 300
// | 400 | 500 | 600 | 700 | 800 | 900 | inherit
// Initial: normal
// Applies to: text content elements
// Inherited: yes
// Percentages: N/A
// Media: visual
// Animatable: yes
// Computed value: one of the legal numeric values, non-numeric
// values shall be converted to numeric values according to the rules
// defined below.
value = readInheritAttribute(elem, "font-weight", "normal");
FONT_BOLD.put(a, value.equals("bold") || value.equals("bolder")
|| value.equals("400") || value.equals("500") || value.equals("600")
|| value.equals("700") || value.equals("800") || value.equals("900"));
// Note: text-decoration is an SVG 1.1 feature
//'text-decoration'
//Value: none | [ underline || overline || line-through || blink ] | inherit
//Initial: none
//Applies to: text content elements
//Inherited: no (see prose)
//Percentages: N/A
//Media: visual
//Animatable: yes
value = readAttribute(elem, "text-decoration", "none");
FONT_UNDERLINE.put(a, value.equals("underline"));
}
/**
* Reads a paint style attribute. This can be a Color or a Gradient or null.
* XXX - Doesn't support url(...) colors yet.
*/
@Nullable
private Object toPaint(IXMLElement elem, String value) throws IOException {
String str = value;
if (str == null) {
return null;
}
str = str.trim().toLowerCase();
if ("none".equals(str)) {
return null;
} else if ("currentcolor".equals(str)) {
String currentColor = readInheritAttribute(elem, "color", "black");
if (currentColor == null || currentColor.trim().toLowerCase().equals("currentColor")) {
return null;
} else {
return toPaint(elem, currentColor);
}
} else if (SVG_COLORS.containsKey(str)) {
return SVG_COLORS.get(str);
} else if (str.startsWith("#") && str.length() == 7) {
return new Color(Integer.decode(str));
} else if (str.startsWith("#") && str.length() == 4) {
// Three digits hex value
int th = Integer.decode(str);
return new Color(
(th & 0xf) | ((th & 0xf) << 4)
| ((th & 0xf0) << 4) | ((th & 0xf0) << 8)
| ((th & 0xf00) << 8) | ((th & 0xf00) << 12));
} else if (str.startsWith("rgb")) {
try {
StringTokenizer tt = new StringTokenizer(str, "() ,");
tt.nextToken();
String r = tt.nextToken();
String g = tt.nextToken();
String b = tt.nextToken();
Color c = new Color(
r.endsWith("%") ? (int) (Double.parseDouble(r.substring(0, r.length() - 1)) * 2.55) : Integer.decode(r),
g.endsWith("%") ? (int) (Double.parseDouble(g.substring(0, g.length() - 1)) * 2.55) : Integer.decode(g),
b.endsWith("%") ? (int) (Double.parseDouble(b.substring(0, b.length() - 1)) * 2.55) : Integer.decode(b));
return c;
} catch (Exception e) {
/*if (DEBUG)*/ System.out.println("SVGInputFormat.toPaint illegal RGB value " + str);
e.printStackTrace();
return null;
}
} else if (str.startsWith("url(")) {
String href = value.substring(4, value.length() - 1);
if (identifiedElements.containsKey(href.substring(1))
&& elementObjects.containsKey(identifiedElements.get(href.substring(1)))) {
Object obj = elementObjects.get(identifiedElements.get(href.substring(1)));
return obj;
}
// XXX - Implement me
if (DEBUG) {
System.out.println("SVGInputFormat.toPaint not implemented for " + href);
}
return null;
} else {
return null;
}
}
/**
* Reads a color style attribute. This can be a Color or null.
* FIXME - Doesn't support url(...) colors yet.
*/
@Nullable
private Color toColor(IXMLElement elem, String value) throws IOException {
String str = value;
if (str == null) {
return null;
}
str = str.trim().toLowerCase();
if ("currentcolor".equals(str)) {
String currentColor = readInheritAttribute(elem, "color", "black");
if (currentColor == null || currentColor.trim().toLowerCase().equals("currentColor")) {
return null;
} else {
return toColor(elem, currentColor);
}
} else if (SVG_COLORS.containsKey(str)) {
return SVG_COLORS.get(str);
} else if (str.startsWith("#") && str.length() == 7) {
return new Color(Integer.decode(str));
} else if (str.startsWith("#") && str.length() == 4) {
// Three digits hex value
int th = Integer.decode(str);
return new Color(
(th & 0xf) | ((th & 0xf) << 4)
| ((th & 0xf0) << 4) | ((th & 0xf0) << 8)
| ((th & 0xf00) << 8) | ((th & 0xf00) << 12));
} else if (str.startsWith("rgb")) {
try {
StringTokenizer tt = new StringTokenizer(str, "() ,");
tt.nextToken();
String r = tt.nextToken();
String g = tt.nextToken();
String b = tt.nextToken();
Color c = new Color(
r.endsWith("%") ? (int) (Integer.decode(r.substring(0, r.length() - 1)) * 2.55) : Integer.decode(r),
g.endsWith("%") ? (int) (Integer.decode(g.substring(0, g.length() - 1)) * 2.55) : Integer.decode(g),
b.endsWith("%") ? (int) (Integer.decode(b.substring(0, b.length() - 1)) * 2.55) : Integer.decode(b));
return c;
} catch (Exception e) {
if (DEBUG) {
System.out.println("SVGInputFormat.toColor illegal RGB value " + str);
}
return null;
}
} else if (str.startsWith("url")) {
// FIXME - Implement me
if (DEBUG) {
System.out.println("SVGInputFormat.toColor not implemented for " + str);
}
return null;
} else {
return null;
}
}
/**
* Reads a double attribute.
*/
private double toDouble(IXMLElement elem, String value) throws IOException {
return toDouble(elem, value, 0, Double.MIN_VALUE, Double.MAX_VALUE);
}
/**
* Reads a double attribute.
*/
private double toDouble(IXMLElement elem, String value, double defaultValue, double min, double max) throws IOException {
try {
double d = Double.valueOf(value);
return Math.max(Math.min(d, max), min);
} catch (NumberFormatException e) {
return defaultValue;
/*
IOException ex = new IOException(elem.getTagName()+"@"+elem.getLineNr()+" "+e.getMessage());
ex.initCause(e);
throw ex;*/
}
}
/**
* Reads a text attribute.
* This method takes the "xml:space" attribute into account.
* http://www.w3.org/TR/SVGMobile12/text.html#WhiteSpace
*/
private String toText(IXMLElement elem, String value) throws IOException {
String space = readInheritAttribute(elem, "xml:space", "default");
if ("default".equals(space)) {
return value.trim().replaceAll("\\s++", " ");
} else /*if ("preserve".equals(space))*/ {
return value;
}
}
/* Converts an SVG transform attribute value into an AffineTransform
* as specified in
* http://www.w3.org/TR/SVGMobile12/coords.html#TransformAttribute
*/
public static AffineTransform toTransform(IXMLElement elem, String str) throws IOException {
AffineTransform t = new AffineTransform();
if (str != null && !str.equals("none")) {
StreamPosTokenizer tt = new StreamPosTokenizer(new StringReader(str));
tt.resetSyntax();
tt.wordChars('a', 'z');
tt.wordChars('A', 'Z');
tt.wordChars(128 + 32, 255);
tt.whitespaceChars(0, ' ');
tt.whitespaceChars(',', ',');
tt.parseNumbers();
tt.parseExponents();
while (tt.nextToken() != StreamPosTokenizer.TT_EOF) {
if (tt.ttype != StreamPosTokenizer.TT_WORD) {
throw new IOException("Illegal transform " + str);
}
String type = tt.sval;
if (tt.nextToken() != '(') {
throw new IOException("'(' not found in transform " + str);
}
if ("matrix".equals(type)) {
double[] m = new double[6];
for (int i = 0; i < 6; i++) {
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("Matrix value " + i + " not found in transform " + str + " token:" + tt.ttype + " " + tt.sval);
}
m[i] = tt.nval;
}
t.concatenate(new AffineTransform(m));
} else if ("translate".equals(type)) {
double tx, ty;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("X-translation value not found in transform " + str);
}
tx = tt.nval;
if (tt.nextToken() == StreamPosTokenizer.TT_NUMBER) {
ty = tt.nval;
} else {
tt.pushBack();
ty = 0;
}
t.translate(tx, ty);
} else if ("scale".equals(type)) {
double sx, sy;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("X-scale value not found in transform " + str);
}
sx = tt.nval;
if (tt.nextToken() == StreamPosTokenizer.TT_NUMBER) {
sy = tt.nval;
} else {
tt.pushBack();
sy = sx;
}
t.scale(sx, sy);
} else if ("rotate".equals(type)) {
double angle, cx, cy;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("Angle value not found in transform " + str);
}
angle = tt.nval;
if (tt.nextToken() == StreamPosTokenizer.TT_NUMBER) {
cx = tt.nval;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("Y-center value not found in transform " + str);
}
cy = tt.nval;
} else {
tt.pushBack();
cx = cy = 0;
}
t.rotate(angle * Math.PI / 180d, cx, cy);
} else if ("skewX".equals(type)) {
double angle;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("Skew angle not found in transform " + str);
}
angle = tt.nval;
t.concatenate(new AffineTransform(
1, 0, Math.tan(angle * Math.PI / 180), 1, 0, 0));
} else if ("skewY".equals(type)) {
double angle;
if (tt.nextToken() != StreamPosTokenizer.TT_NUMBER) {
throw new IOException("Skew angle not found in transform " + str);
}
angle = tt.nval;
t.concatenate(new AffineTransform(
1, Math.tan(angle * Math.PI / 180), 0, 1, 0, 0));
} else if ("ref".equals(type)) {
System.err.println("SVGInputFormat warning: ignored ref(...) transform attribute in element " + elem);
while (tt.nextToken() != ')' && tt.ttype != StreamPosTokenizer.TT_EOF) {
// ignore tokens between brackets
}
tt.pushBack();
} else {
throw new IOException("Unknown transform " + type + " in " + str + " in element " + elem);
}
if (tt.nextToken() != ')') {
throw new IOException("')' not found in transform " + str);
}
}
}
return t;
}
@Override
public javax.swing.filechooser.FileFilter getFileFilter() {
return new ExtensionFileFilter("Scalable Vector Graphics (SVG)", "svg");
}
@Override
public JComponent getInputFormatAccessory() {
return null;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.getPrimaryType().equals("image")
&& flavor.getSubType().equals("svg+xml");
}
@Override
public void read(Transferable t, Drawing drawing, boolean replace) throws UnsupportedFlavorException, IOException {
InputStream in = (InputStream) t.getTransferData(new DataFlavor("image/svg+xml", "Image SVG"));
try {
read(in, drawing, false);
} finally {
in.close();
}
}
}