added sources for Slick

Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054
Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
Song Minjae
2016-12-30 23:29:12 +09:00
parent d1f01a203d
commit d3080ffb78
329 changed files with 58400 additions and 7 deletions

View File

@@ -0,0 +1,162 @@
package org.newdawn.slick.svg;
import java.util.ArrayList;
import java.util.HashMap;
/**
* A diagram read from SVG containing multiple figures
*
* @author kevin
*/
public class Diagram {
/** The figures in the diagram */
private ArrayList figures = new ArrayList();
/** The pattern definitions */
private HashMap patterns = new HashMap();
/** The linear gradients defined within the diagram */
private HashMap gradients = new HashMap();
/** The figures mapping */
private HashMap figureMap = new HashMap();
/** The width of the diagram */
private float width;
/** The height of the diagram */
private float height;
/**
* Create a new empty diagram
*
* @param width The width of the diagram
* @param height The height of the diagram
*/
public Diagram(float width, float height) {
this.width = width;
this.height = height;
}
/**
* Get the width of the diagram
*
* @return The width of the diagram
*/
public float getWidth() {
return width;
}
/**
* Get the height of the diagram
*
* @return The height of the diagram
*/
public float getHeight() {
return height;
}
/**
* Add a pattern definition basd on a image
*
* @param name The name of the pattern
* @param href The href to the image specified in the doc
*/
public void addPatternDef(String name, String href) {
patterns.put(name, href);
}
/**
* Add gradient to the diagram
*
* @param name The name of the gradient
* @param gradient The gradient to be added
*/
public void addGradient(String name, Gradient gradient) {
gradients.put(name, gradient);
}
/**
* Get a pattern definition from the diagram
*
* @param name The name of the pattern
* @return The href to the image that was specified for the given pattern
*/
public String getPatternDef(String name) {
return (String) patterns.get(name);
}
/**
* Get the gradient defined in this document
*
* @param name The name of the gradient
* @return The gradient definition
*/
public Gradient getGradient(String name) {
return (Gradient) gradients.get(name);
}
/**
* Get the names of the patterns defined
*
* @return The names of the pattern
*/
public String[] getPatternDefNames() {
return (String[]) patterns.keySet().toArray(new String[0]);
}
/**
* Get a figure by a given ID
*
* @param id The ID of the figure
* @return The figure with the given ID
*/
public Figure getFigureByID(String id) {
return (Figure) figureMap.get(id);
}
/**
* Add a figure to the diagram
*
* @param figure The figure to add
*/
public void addFigure(Figure figure) {
figures.add(figure);
figureMap.put(figure.getData().getAttribute(NonGeometricData.ID), figure);
String fillRef = figure.getData().getAsReference(NonGeometricData.FILL);
Gradient gradient = getGradient(fillRef);
if (gradient != null) {
if (gradient.isRadial()) {
for (int i=0;i<InkscapeLoader.RADIAL_TRIANGULATION_LEVEL;i++) {
figure.getShape().increaseTriangulation();
}
}
}
}
/**
* Get the number of figures in the diagram
*
* @return The number of figures in the diagram
*/
public int getFigureCount() {
return figures.size();
}
/**
* Get the figure at a given index
*
* @param index The index of the figure to retrieve
* @return The figure at the given index
*/
public Figure getFigure(int index) {
return (Figure) figures.get(index);
}
/**
* Remove a figure from the diagram
*
* @param figure The figure to be removed
*/
public void removeFigure(Figure figure) {
figures.remove(figure);
figureMap.remove(figure.getData().getAttribute("id"));
}
}

View File

@@ -0,0 +1,84 @@
package org.newdawn.slick.svg;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
/**
* A figure that is part of diagram loaded from SVG
*
* @author kevin
*/
public class Figure {
/** Ellipse Type */
public static final int ELLIPSE = 1;
/** Line Type */
public static final int LINE = 2;
/** Rectangle Type */
public static final int RECTANGLE = 3;
/** Path Type */
public static final int PATH = 4;
/** Polygon Type */
public static final int POLYGON = 5;
/** The type of this figure */
private int type;
/** The geometric shape of the figure */
private Shape shape;
/** The other bits of data assocaited with the SVG element */
private NonGeometricData data;
/** The transform that has already been applied to the shape */
private Transform transform;
/**
* Create a new figure
*
* @param type The type of the figure
* @param shape The shape of the figure
* @param data The other associated data
* @param transform The transform that was applied to the shape
*/
public Figure(int type, Shape shape, NonGeometricData data, Transform transform) {
this.shape = shape;
this.data = data;
this.type = type;
this.transform = transform;
}
/**
* Get the transform that was applied to the shape given in the SVG
* to get it to it's currently state
*
* @return The transform specified in the SVG
*/
public Transform getTransform() {
return transform;
}
/**
* Get the type of this figure
*
* @return The type of this figure
*/
public int getType() {
return type;
}
/**
* Get the shape of this figure
*
* @return The shape of this figure
*/
public Shape getShape() {
return shape;
}
/**
* Get the data associated with this figure
*
* @return The data associated with this figure
*/
public NonGeometricData getData() {
return data;
}
}

View File

@@ -0,0 +1,289 @@
package org.newdawn.slick.svg;
import java.util.ArrayList;
import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
import org.newdawn.slick.ImageBuffer;
import org.newdawn.slick.geom.Transform;
/**
* A gradient definition from an SVG file, includes the stops, name and transform.
*
* @author kevin
*/
public class Gradient {
/** The name/id given to the gradient */
private String name;
/** The steps in colour of the gradient */
private ArrayList steps = new ArrayList();
/** The first x coordiante given in the gradient (cx in radial) */
private float x1;
/** The second x coordiante given in the gradient (fx in radial) */
private float x2;
/** The first y coordiante given in the gradient (cy in radial) */
private float y1;
/** The first y coordiante given in the gradient (fy in radial) */
private float y2;
/** The radius given if any */
private float r;
/** The texture representing this gradient */
private Image image;
/** True if this gradient is radial in nature */
private boolean radial;
/** The transform specified for the gradient */
private Transform transform;
/** The name of the referenced gradient */
private String ref;
/**
* Create a new gradient definition
*
* @param name The name of the gradient
* @param radial True if the gradient is radial
*/
public Gradient(String name, boolean radial) {
this.name = name;
this.radial = radial;
}
/**
* Check if the gradient is radial
*
* @return True if the gradient is radial
*/
public boolean isRadial() {
return radial;
}
/**
* Set the transform given for this definition
*
* @param trans The transform given for this definition
*/
public void setTransform(Transform trans) {
this.transform = trans;
}
/**
* Get the transform to apply during this gradient application
*
* @return The transform given for this gradient
*/
public Transform getTransform() {
return transform;
}
/**
* Reference another gradient, i.e. use it's colour stops
*
* @param ref The name of the other gradient to reference
*/
public void reference(String ref) {
this.ref = ref;
}
/**
* Resolve the gradient reference
*
* @param diagram The diagram to resolve against
*/
public void resolve(Diagram diagram) {
if (ref == null) {
return;
}
Gradient other = diagram.getGradient(ref);
for (int i=0;i<other.steps.size();i++) {
steps.add(other.steps.get(i));
}
}
/**
* Generate the image used for texturing the gradient across shapes
*/
public void genImage() {
if (image == null) {
ImageBuffer buffer = new ImageBuffer(128,16);
for (int i=0;i<128;i++) {
Color col = getColorAt(i / 128.0f);
for (int j=0;j<16;j++) {
buffer.setRGBA(i, j, col.getRedByte(), col.getGreenByte(), col.getBlueByte(), col.getAlphaByte());
}
}
image = buffer.getImage();
}
}
/**
* Get the image generated for this gradient
*
* @return The image generated for the gradient
*/
public Image getImage() {
genImage();
return image;
}
/**
* Set the radius given in the SVG
*
* @param r The radius for radial gradients
*/
public void setR(float r) {
this.r = r;
}
/**
* Set the first x value given for the gradient (cx in the case of radial)
*
* @param x1 The first x value given for the gradient
*/
public void setX1(float x1) {
this.x1 = x1;
}
/**
* Set the second x value given for the gradient (fx in the case of radial)
*
* @param x2 The second x value given for the gradient
*/
public void setX2(float x2) {
this.x2 = x2;
}
/**
* Set the first y value given for the gradient (cy in the case of radial)
*
* @param y1 The first y value given for the gradient
*/
public void setY1(float y1) {
this.y1 = y1;
}
/**
* Set the second y value given for the gradient (fy in the case of radial)
*
* @param y2 The second y value given for the gradient
*/
public void setY2(float y2) {
this.y2 = y2;
}
/**
* Get the radius value given for this gradient
*
* @return The radius value given for this gradient
*/
public float getR() {
return r;
}
/**
* Get the first x value given for this gradient (cx in the case of radial)
*
* @return The first x value given for this gradient
*/
public float getX1() {
return x1;
}
/**
* Get the second x value given for this gradient (fx in the case of radial)
*
* @return The second x value given for this gradient
*/
public float getX2() {
return x2;
}
/**
* Get the first y value given for this gradient (cy in the case of radial)
*
* @return The first y value given for this gradient
*/
public float getY1() {
return y1;
}
/**
* Get the second y value given for this gradient (fy in the case of radial)
*
* @return The second y value given for this gradient
*/
public float getY2() {
return y2;
}
/**
* Add a colour step/stop to the gradient
*
* @param location The location on the gradient the colour affects
* @param c The color to apply
*/
public void addStep(float location, Color c) {
steps.add(new Step(location, c));
}
/**
* Get the intepolated colour at the given location on the gradient
*
* @param p The point of the gradient (0 >= n >= 1)
* @return The interpolated colour at the given location
*/
public Color getColorAt(float p) {
if (p <= 0) {
return ((Step) steps.get(0)).col;
}
if (p > 1) {
return ((Step) steps.get(steps.size()-1)).col;
}
for (int i=1;i<steps.size();i++) {
Step prev = ((Step) steps.get(i-1));
Step current = ((Step) steps.get(i));
if (p <= current.location) {
float dis = current.location - prev.location;
p -= prev.location;
float v = p / dis;
Color c = new Color(1,1,1,1);
c.a = (prev.col.a * (1 - v)) + (current.col.a * (v));
c.r = (prev.col.r * (1 - v)) + (current.col.r * (v));
c.g = (prev.col.g * (1 - v)) + (current.col.g * (v));
c.b = (prev.col.b * (1 - v)) + (current.col.b * (v));
return c;
}
}
// shouldn't ever happen
return Color.black;
}
/**
* The description of a single step on the gradient
*
* @author kevin
*/
private class Step {
/** The location on the gradient */
float location;
/** The colour applied */
Color col;
/**
* Create a new step
*
* @param location The location on the gradient the colour affects
* @param c The colour to apply
*/
public Step(float location, Color c) {
this.location = location;
this.col = c;
}
}
}

View File

@@ -0,0 +1,226 @@
package org.newdawn.slick.svg;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.inkscape.DefsProcessor;
import org.newdawn.slick.svg.inkscape.ElementProcessor;
import org.newdawn.slick.svg.inkscape.EllipseProcessor;
import org.newdawn.slick.svg.inkscape.GroupProcessor;
import org.newdawn.slick.svg.inkscape.LineProcessor;
import org.newdawn.slick.svg.inkscape.PathProcessor;
import org.newdawn.slick.svg.inkscape.PolygonProcessor;
import org.newdawn.slick.svg.inkscape.RectProcessor;
import org.newdawn.slick.svg.inkscape.UseProcessor;
import org.newdawn.slick.util.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* A loader specifically for the SVG that is produced from Inkscape
*
* @author kevin
*/
public class InkscapeLoader implements Loader {
/**
* The number of times to over trigulate to get enough tesselation for
* smooth shading
*/
public static int RADIAL_TRIANGULATION_LEVEL = 1;
/** The list of XML element processors */
private static ArrayList processors = new ArrayList();
/** The diagram loaded */
private Diagram diagram;
static {
addElementProcessor(new RectProcessor());
addElementProcessor(new EllipseProcessor());
addElementProcessor(new PolygonProcessor());
addElementProcessor(new PathProcessor());
addElementProcessor(new LineProcessor());
addElementProcessor(new GroupProcessor());
addElementProcessor(new DefsProcessor());
addElementProcessor(new UseProcessor());
}
/**
* Add an <code>ElementProcessor</code> which will be passed
* each element read as the Inkscape SVG document is processed.
*
* @param proc The processor to be added
*/
public static void addElementProcessor(ElementProcessor proc) {
processors.add(proc);
}
/**
* Load a SVG document into a diagram
*
* @param ref
* The reference in the classpath to load the diagram from
* @param offset
* Offset the diagram for the height of the document
* @return The diagram loaded
* @throws SlickException
* Indicates a failure to process the document
*/
public static Diagram load(String ref, boolean offset)
throws SlickException {
return load(ResourceLoader.getResourceAsStream(ref), offset);
}
/**
* Load a SVG document into a diagram
*
* @param ref
* The reference in the classpath to load the diagram from
* @return The diagram loaded
* @throws SlickException
* Indicates a failure to process the document
*/
public static Diagram load(String ref) throws SlickException {
return load(ResourceLoader.getResourceAsStream(ref), false);
}
/**
* Load a SVG document into a diagram
*
* @param offset
* Offset the diagram for the height of the document
* @param in
* The input stream from which to read the SVG
* @return The diagram loaded
* @throws SlickException
* Indicates a failure to process the document
*/
public static Diagram load(InputStream in, boolean offset)
throws SlickException {
return new InkscapeLoader().loadDiagram(in, offset);
}
/**
* Private, you should be using the static method
*/
private InkscapeLoader() {
}
/**
* Load a SVG document into a diagram
*
* @param in
* The input stream from which to read the SVG
* @return The diagram loaded
* @throws SlickException
* Indicates a failure to process the document
*/
private Diagram loadDiagram(InputStream in) throws SlickException {
return loadDiagram(in, false);
}
/**
* Load a SVG document into a diagram
*
* @param in
* The input stream from which to read the SVG
* @param offset
* Offset the diagram for the height of the document
* @return The diagram loaded
* @throws SlickException
* Indicates a failure to process the document
*/
private Diagram loadDiagram(InputStream in, boolean offset)
throws SlickException {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new EntityResolver() {
public InputSource resolveEntity(String publicId,
String systemId) throws SAXException, IOException {
return new InputSource(
new ByteArrayInputStream(new byte[0]));
}
});
Document doc = builder.parse(in);
Element root = doc.getDocumentElement();
String widthString = root.getAttribute("width");
while (Character.isLetter(widthString
.charAt(widthString.length() - 1))) {
widthString = widthString.substring(0, widthString.length() - 1);
}
String heightString = root.getAttribute("height");
while (Character.isLetter(heightString
.charAt(heightString.length() - 1))) {
heightString = heightString.substring(0,heightString.length() - 1);
}
float docWidth = Float.parseFloat(widthString);
float docHeight = Float.parseFloat(heightString);
diagram = new Diagram(docWidth, docHeight);
if (!offset) {
docHeight = 0;
}
loadChildren(root, Transform
.createTranslateTransform(0, -docHeight));
return diagram;
} catch (Exception e) {
throw new SlickException("Failed to load inkscape document", e);
}
}
/**
* @see org.newdawn.slick.svg.Loader#loadChildren(org.w3c.dom.Element,
* org.newdawn.slick.geom.Transform)
*/
public void loadChildren(Element element, Transform t)
throws ParsingException {
NodeList list = element.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
loadElement((Element) list.item(i), t);
}
}
}
/**
* Load a single element into the diagram
*
* @param element
* The element ot be loaded
* @param t
* The transform to apply to the loaded element from the parent
* @throws ParsingException
* Indicates a failure to parse the element
*/
private void loadElement(Element element, Transform t)
throws ParsingException {
for (int i = 0; i < processors.size(); i++) {
ElementProcessor processor = (ElementProcessor) processors.get(i);
if (processor.handles(element)) {
processor.process(this, element, diagram, t);
}
}
}
}

View File

@@ -0,0 +1,69 @@
package org.newdawn.slick.svg;
import org.newdawn.slick.geom.Line;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.TexCoordGenerator;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.geom.Vector2f;
/**
* A filler for shapes that applys SVG linear gradients
*
* @author kevin
*/
public class LinearGradientFill implements TexCoordGenerator {
/** The start position of the gradient line */
private Vector2f start;
/** The ends position of the gradient line */
private Vector2f end;
/** The gradient being applied */
private Gradient gradient;
/** The line of the gradient */
private Line line;
/** The shape being filled with gradient */
private Shape shape;
/**
* Create a new fill for gradients
*
* @param shape The shape being filled
* @param trans The transform given for the shape
* @param gradient The gradient to apply
*/
public LinearGradientFill(Shape shape, Transform trans, Gradient gradient) {
this.gradient = gradient;
float x = gradient.getX1();
float y = gradient.getY1();
float mx = gradient.getX2();
float my = gradient.getY2();
float h = my - y;
float w = mx - x;
float[] s = new float[] {x,y+(h/2)};
gradient.getTransform().transform(s, 0, s, 0, 1);
trans.transform(s, 0, s, 0, 1);
float[] e = new float[] {x+w,y+(h/2)};
gradient.getTransform().transform(e, 0, e, 0, 1);
trans.transform(e, 0, e, 0, 1);
start = new Vector2f(s[0],s[1]);
end = new Vector2f(e[0],e[1]);
line = new Line(start, end);
}
/**
* @see org.newdawn.slick.geom.TexCoordGenerator#getCoordFor(float, float)
*/
public Vector2f getCoordFor(float x, float y) {
Vector2f result = new Vector2f();
line.getClosestPoint(new Vector2f(x,y), result);
float u = result.distance(start);
u /= line.length();
return new Vector2f(u,0);
}
}

View File

@@ -0,0 +1,20 @@
package org.newdawn.slick.svg;
import org.newdawn.slick.geom.Transform;
import org.w3c.dom.Element;
/**
* Description of a simple XML loader
*
* @author kevin
*/
public interface Loader {
/**
* Load the children of a given element
*
* @param element The element whose children should be loaded
* @param t The transform to apply to all the children
* @throws ParsingException Indicates a failure to read the XML
*/
public void loadChildren(Element element, Transform t) throws ParsingException;
}

View File

@@ -0,0 +1,210 @@
package org.newdawn.slick.svg;
import java.util.Properties;
import org.newdawn.slick.Color;
/**
* A set of data about a shape that doesn't fit into it's geometric
* configuration.
*
* @author kevin
*/
public class NonGeometricData {
/** The ID of the figure */
public static final String ID = "id";
/** The fill type */
public static final String FILL = "fill";
/** The stroke type */
public static final String STROKE = "stroke";
/** The alpha value for filling */
public static final String OPACITY = "opacity";
/** The width of the line to draw */
public static final String STROKE_WIDTH = "stroke-width";
/** The mitre of the line to draw */
public static final String STROKE_MITERLIMIT = "stroke-miterlimit";
/** The dash definition of the line to draw */
public static final String STROKE_DASHARRAY = "stroke-dasharray";
/** The offset into the dash definition of the line to draw */
public static final String STROKE_DASHOFFSET = "stroke-dashoffset";
/** The alpha value for drawing */
public static final String STROKE_OPACITY = "stroke-opacity";
/** Value indicating that no settings has been specified */
public static final String NONE = "none";
/** The meta data stored for the figure */
private String metaData = "";
/** The attributes stored for the figure */
private Properties props = new Properties();
/**
* Create a set of non-geometric data for a figure
*
* @param metaData The meta data (either label or id) for the figure
*/
public NonGeometricData(String metaData) {
this.metaData = metaData;
addAttribute(STROKE_WIDTH, "1");
}
/**
* Morph the color from a string
*
* @param str The string to morph
* @return The new color string
*/
private String morphColor(String str) {
if (str.equals("")) {
return "#000000";
}
if (str.equals("white")) {
return "#ffffff";
}
if (str.equals("black")) {
return "#000000";
}
return str;
}
/**
* Add a configured style attribute into the data set
*
* @param attribute The attribute to add
* @param value The value to assign
*/
public void addAttribute(String attribute, String value) {
if (value == null) {
value = "";
}
if (attribute.equals(FILL) ) {
value = morphColor(value);
}
if (attribute.equals(STROKE_OPACITY)) {
if (value.equals("0")) {
props.setProperty(STROKE, "none");
}
}
if (attribute.equals(STROKE_WIDTH)) {
if (value.equals("")) {
value = "1";
}
if (value.endsWith("px")) {
value = value.substring(0,value.length()-2);
}
}
if (attribute.equals(STROKE)) {
if ("none".equals(props.getProperty(STROKE))) {
return;
}
if ("".equals(props.getProperty(STROKE))) {
return;
}
value = morphColor(value);
}
props.setProperty(attribute, value);
}
/**
* Check if a given attribute is in colour format
*
* @param attribute The attribute to check
* @return True if the attirbute value is in colour format
*/
public boolean isColor(String attribute) {
return getAttribute(attribute).startsWith("#");
}
/**
* Get the meta data assigned to the figure. Either the label or
* the id value.
*
* @return The meta data assigned to the figure
*/
public String getMetaData() {
return metaData;
}
/**
* Get the attribtue value for a given attribute
*
* @param attribute The attribute whose value should be obtained
* @return The value for the given attribute
*/
public String getAttribute(String attribute) {
return props.getProperty(attribute);
}
/**
* Get an attribute value converted to a color. isColor should first be checked
*
* @param attribute The attribute whose value should be interpreted as a color
* @return The color based on the attribute
*/
public Color getAsColor(String attribute) {
if (!isColor(attribute)) {
throw new RuntimeException("Attribute "+attribute+" is not specified as a color:"+getAttribute(attribute));
}
int col = Integer.parseInt(getAttribute(attribute).substring(1), 16);
return new Color(col);
}
/**
* Get the attribute value as a reference to another entity
*
* @param attribute The name of the attribute to retrieve
* @return The reference part of the attribute value
*/
public String getAsReference(String attribute) {
String value = getAttribute(attribute);
if (value.length() < 7) {
return "";
}
value = value.substring(5, value.length()-1);
return value;
}
/**
* Get an attribute converted to a float value
*
* @param attribute The attribute to retrieve
* @return The float value derived from the attribute
*/
public float getAsFloat(String attribute) {
String value = getAttribute(attribute);
if (value == null) {
return 0;
}
try {
return Float.parseFloat(value);
} catch (NumberFormatException e) {
throw new RuntimeException("Attribute "+attribute+" is not specified as a float:"+getAttribute(attribute));
}
}
/**
* True if the shape is meant to be filled
*
* @return True if the shape is meant to be filled
*/
public boolean isFilled() {
return isColor(NonGeometricData.FILL);
}
/**
* True if the shape is meant to be outlined
*
* @return True if the shape is meant to be outlined
*/
public boolean isStroked() {
return isColor(NonGeometricData.STROKE) && (getAsFloat(NonGeometricData.STROKE_WIDTH) > 0);
}
}

View File

@@ -0,0 +1,54 @@
package org.newdawn.slick.svg;
import org.newdawn.slick.SlickException;
import org.w3c.dom.Element;
/**
* Exception indicating a failure to parse XML, giving element information
*
* @author kevin
*/
public class ParsingException extends SlickException {
/**
* Create a new exception
*
* @param nodeID The ID of the node that failed validation
* @param message The description of the failure
* @param cause The exception causing this one
*/
public ParsingException(String nodeID, String message, Throwable cause) {
super("("+nodeID+") "+message, cause);
}
/**
* Create a new exception
*
* @param element The element that failed validation
* @param message The description of the failure
* @param cause The exception causing this one
*/
public ParsingException(Element element, String message, Throwable cause) {
super("("+element.getAttribute("id")+") "+message, cause);
}
/**
* Create a new exception
*
* @param nodeID The ID of the node that failed validation
* @param message The description of the failure
*/
public ParsingException(String nodeID, String message) {
super("("+nodeID+") "+message);
}
/**
* Create a new exception
*
* @param element The element that failed validation
* @param message The description of the failure
*/
public ParsingException(Element element, String message) {
super("("+element.getAttribute("id")+") "+message);
}
}

View File

@@ -0,0 +1,63 @@
package org.newdawn.slick.svg;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.TexCoordGenerator;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.geom.Vector2f;
/**
* A filler to apply a SVG radial gradient across a shape
*
* @author kevin
*/
public class RadialGradientFill implements TexCoordGenerator {
/** The centre of the gradient */
private Vector2f centre;
/** The radius before the gradient is complete */
private float radius;
/** The gradient to apply */
private Gradient gradient;
/** The shape being filled */
private Shape shape;
/**
* Create a new fill for a radial gradient
*
* @param shape The shape being filled
* @param trans The transform given for the shape in the SVG
* @param gradient The gradient to apply across the shape
*/
public RadialGradientFill(Shape shape, Transform trans, Gradient gradient) {
this.gradient = gradient;
radius = gradient.getR();
float x = gradient.getX1();
float y = gradient.getY1();
float[] c = new float[] {x,y};
gradient.getTransform().transform(c, 0, c, 0, 1);
trans.transform(c, 0, c, 0, 1);
float[] rt = new float[] {x,y-radius};
gradient.getTransform().transform(rt, 0, rt, 0, 1);
trans.transform(rt, 0, rt, 0, 1);
centre = new Vector2f(c[0],c[1]);
Vector2f dis = new Vector2f(rt[0],rt[1]);
radius = dis.distance(centre);
}
/**
* @see org.newdawn.slick.geom.TexCoordGenerator#getCoordFor(float, float)
*/
public Vector2f getCoordFor(float x, float y) {
float u = centre.distance(new Vector2f(x,y));
u /= radius;
if (u > 0.99f) {
u = 0.99f;
}
return new Vector2f(u,0);
}
}

View File

@@ -0,0 +1,118 @@
package org.newdawn.slick.svg;
import java.util.ArrayList;
import org.newdawn.slick.geom.MorphShape;
/**
* A utility to allow morphing between a set of similar SVG diagrams
*
* @author kevin
*/
public class SVGMorph extends Diagram {
/** The list of figures being morphed */
private ArrayList figures = new ArrayList();
/**
* Create a new morph with a first diagram base
*
* @param diagram The base diagram which provides the first step of the morph
*/
public SVGMorph(Diagram diagram) {
super(diagram.getWidth(), diagram.getHeight());
for (int i=0;i<diagram.getFigureCount();i++) {
Figure figure = diagram.getFigure(i);
Figure copy = new Figure(figure.getType(), new MorphShape(figure.getShape()), figure.getData(), figure.getTransform());
figures.add(copy);
}
}
/**
* Add a subsquent step to the morphing
*
* @param diagram The diagram to add as the next step in the morph
*/
public void addStep(Diagram diagram) {
if (diagram.getFigureCount() != figures.size()) {
throw new RuntimeException("Mismatched diagrams, missing ids");
}
for (int i=0;i<diagram.getFigureCount();i++) {
Figure figure = diagram.getFigure(i);
String id = figure.getData().getMetaData();
for (int j=0;j<figures.size();j++) {
Figure existing = (Figure) figures.get(j);
if (existing.getData().getMetaData().equals(id)) {
MorphShape morph = (MorphShape) existing.getShape();
morph.addShape(figure.getShape());
break;
}
}
}
}
/**
* Set the current diagram we should morph from. This only really works with
* updateMorphTime() but can be used for smooth transitions between
* morphs.
*
* @param diagram The diagram to use as the base of the morph
*/
public void setExternalDiagram(Diagram diagram) {
for (int i=0;i<figures.size();i++) {
Figure figure = (Figure) figures.get(i);
for (int j=0;j<diagram.getFigureCount();j++) {
Figure newBase = diagram.getFigure(j);
if (newBase.getData().getMetaData().equals(figure.getData().getMetaData())) {
MorphShape shape = (MorphShape) figure.getShape();
shape.setExternalFrame(newBase.getShape());
break;
}
}
}
}
/**
* Update the morph time index by the amount specified
*
* @param delta The amount to update the morph by
*/
public void updateMorphTime(float delta) {
for (int i=0;i<figures.size();i++) {
Figure figure = (Figure) figures.get(i);
MorphShape shape = (MorphShape) figure.getShape();
shape.updateMorphTime(delta);
}
}
/**
* Set the "time" index for this morph. This is given in terms of diagrams, so
* 0.5f would give you the position half way between the first and second diagrams.
*
* @param time The time index to represent on this diagrams
*/
public void setMorphTime(float time) {
for (int i=0;i<figures.size();i++) {
Figure figure = (Figure) figures.get(i);
MorphShape shape = (MorphShape) figure.getShape();
shape.setMorphTime(time);
}
}
/**
* @see Diagram#getFigureCount()
*/
public int getFigureCount() {
return figures.size();
}
/**
* @see Diagram#getFigure(int)
*/
public Figure getFigure(int index) {
return (Figure) figures.get(index);
}
}

View File

@@ -0,0 +1,111 @@
package org.newdawn.slick.svg;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.ShapeRenderer;
import org.newdawn.slick.geom.TexCoordGenerator;
import org.newdawn.slick.opengl.TextureImpl;
import org.newdawn.slick.opengl.renderer.Renderer;
import org.newdawn.slick.opengl.renderer.SGL;
/**
* A very primtive implementation for rendering a diagram. This simply
* sticks the shapes on the screen in the right fill and stroke colours
*
* @author kevin
*/
public class SimpleDiagramRenderer {
/** The renderer to use for all GL operations */
protected static SGL GL = Renderer.get();
/** The diagram to be rendered */
public Diagram diagram;
/** The display list representing the diagram */
public int list = -1;
/**
* Create a new simple renderer
*
* @param diagram The diagram to be rendered
*/
public SimpleDiagramRenderer(Diagram diagram) {
this.diagram = diagram;
}
/**
* Render the diagram to the given graphics context
*
* @param g The graphics context to which we should render the diagram
*/
public void render(Graphics g) {
// last list generation
if (list == -1) {
list = GL.glGenLists(1);
GL.glNewList(list, SGL.GL_COMPILE);
render(g, diagram);
GL.glEndList();
}
GL.glCallList(list);
TextureImpl.bindNone();
}
/**
* Utility method to render a diagram in immediate mode
*
* @param g The graphics context to render to
* @param diagram The diagram to render
*/
public static void render(Graphics g, Diagram diagram) {
for (int i=0;i<diagram.getFigureCount();i++) {
Figure figure = diagram.getFigure(i);
if (figure.getData().isFilled()) {
if (figure.getData().isColor(NonGeometricData.FILL)) {
g.setColor(figure.getData().getAsColor(NonGeometricData.FILL));
g.fill(diagram.getFigure(i).getShape());
g.setAntiAlias(true);
g.draw(diagram.getFigure(i).getShape());
g.setAntiAlias(false);
}
String fill = figure.getData().getAsReference(NonGeometricData.FILL);
if (diagram.getPatternDef(fill) != null){
System.out.println("PATTERN");
}
if (diagram.getGradient(fill) != null) {
Gradient gradient = diagram.getGradient(fill);
Shape shape = diagram.getFigure(i).getShape();
TexCoordGenerator fg = null;
if (gradient.isRadial()) {
fg = new RadialGradientFill(shape, diagram.getFigure(i).getTransform(), gradient);
} else {
fg = new LinearGradientFill(shape, diagram.getFigure(i).getTransform(), gradient);
}
Color.white.bind();
ShapeRenderer.texture(shape, gradient.getImage(), fg);
}
}
if (figure.getData().isStroked()) {
if (figure.getData().isColor(NonGeometricData.STROKE)) {
g.setColor(figure.getData().getAsColor(NonGeometricData.STROKE));
g.setLineWidth(figure.getData().getAsFloat(NonGeometricData.STROKE_WIDTH));
g.setAntiAlias(true);
g.draw(diagram.getFigure(i).getShape());
g.setAntiAlias(false);
g.resetLineWidth();
}
}
// DEBUG VERSION
// g.setColor(Color.black);
// g.draw(diagram.getFigure(i).getShape());
// g.setColor(Color.red);
// g.fill(diagram.getFigure(i).getShape());
}
}
}

View File

@@ -0,0 +1,171 @@
package org.newdawn.slick.svg.inkscape;
import java.util.ArrayList;
import org.newdawn.slick.Color;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Gradient;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.ParsingException;
import org.newdawn.slick.util.Log;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* A processor for the defs node
*
* @author kevin
*/
public class DefsProcessor implements ElementProcessor {
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
if (element.getNodeName().equals("defs")) {
return true;
}
return false;
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram, Transform transform) throws ParsingException {
NodeList patterns = element.getElementsByTagName("pattern");
for (int i=0;i<patterns.getLength();i++) {
Element pattern = (Element) patterns.item(i);
NodeList list = pattern.getElementsByTagName("image");
if (list.getLength() == 0) {
Log.warn("Pattern 1981 does not specify an image. Only image patterns are supported.");
continue;
}
Element image = (Element) list.item(0);
String patternName = pattern.getAttribute("id");
String ref = image.getAttributeNS(Util.XLINK, "href");
diagram.addPatternDef(patternName, ref);
}
NodeList linear = element.getElementsByTagName("linearGradient");
ArrayList toResolve = new ArrayList();
for (int i=0;i<linear.getLength();i++) {
Element lin = (Element) linear.item(i);
String name = lin.getAttribute("id");
Gradient gradient = new Gradient(name, false);
gradient.setTransform(Util.getTransform(lin, "gradientTransform"));
if (stringLength(lin.getAttribute("x1")) > 0) {
gradient.setX1(Float.parseFloat(lin.getAttribute("x1")));
}
if (stringLength(lin.getAttribute("x2")) > 0) {
gradient.setX2(Float.parseFloat(lin.getAttribute("x2")));
}
if (stringLength(lin.getAttribute("y1")) > 0) {
gradient.setY1(Float.parseFloat(lin.getAttribute("y1")));
}
if (stringLength(lin.getAttribute("y2")) > 0) {
gradient.setY2(Float.parseFloat(lin.getAttribute("y2")));
}
String ref = lin.getAttributeNS("http://www.w3.org/1999/xlink", "href");
if (stringLength(ref) > 0) {
gradient.reference(ref.substring(1));
toResolve.add(gradient);
} else {
NodeList steps = lin.getElementsByTagName("stop");
for (int j=0;j<steps.getLength();j++) {
Element s = (Element) steps.item(j);
float offset = Float.parseFloat(s.getAttribute("offset"));
String colInt = Util.extractStyle(s.getAttribute("style"),"stop-color");
String opaInt = Util.extractStyle(s.getAttribute("style"),"stop-opacity");
int col = Integer.parseInt(colInt.substring(1), 16);
Color stopColor = new Color(col);
stopColor.a = Float.parseFloat(opaInt);
gradient.addStep(offset, stopColor);
}
gradient.getImage();
}
diagram.addGradient(name, gradient);
}
NodeList radial = element.getElementsByTagName("radialGradient");
for (int i=0;i<radial.getLength();i++) {
Element rad = (Element) radial.item(i);
String name = rad.getAttribute("id");
Gradient gradient = new Gradient(name, true);
gradient.setTransform(Util.getTransform(rad, "gradientTransform"));
if (stringLength(rad.getAttribute("cx")) > 0) {
gradient.setX1(Float.parseFloat(rad.getAttribute("cx")));
}
if (stringLength(rad.getAttribute("cy")) > 0) {
gradient.setY1(Float.parseFloat(rad.getAttribute("cy")));
}
if (stringLength(rad.getAttribute("fx")) > 0) {
gradient.setX2(Float.parseFloat(rad.getAttribute("fx")));
}
if (stringLength(rad.getAttribute("fy")) > 0) {
gradient.setY2(Float.parseFloat(rad.getAttribute("fy")));
}
if (stringLength(rad.getAttribute("r")) > 0) {
gradient.setR(Float.parseFloat(rad.getAttribute("r")));
}
String ref = rad.getAttributeNS("http://www.w3.org/1999/xlink", "href");
if (stringLength(ref) > 0) {
gradient.reference(ref.substring(1));
toResolve.add(gradient);
} else {
NodeList steps = rad.getElementsByTagName("stop");
for (int j=0;j<steps.getLength();j++) {
Element s = (Element) steps.item(j);
float offset = Float.parseFloat(s.getAttribute("offset"));
String colInt = Util.extractStyle(s.getAttribute("style"),"stop-color");
String opaInt = Util.extractStyle(s.getAttribute("style"),"stop-opacity");
int col = Integer.parseInt(colInt.substring(1), 16);
Color stopColor = new Color(col);
stopColor.a = Float.parseFloat(opaInt);
gradient.addStep(offset, stopColor);
}
gradient.getImage();
}
diagram.addGradient(name, gradient);
}
for (int i=0;i<toResolve.size();i++) {
((Gradient) toResolve.get(i)).resolve(diagram);
((Gradient) toResolve.get(i)).getImage();
}
}
/**
* Utility to cope with null values
*
* @param value The value to get the length of
* @return The length of the string
*/
private int stringLength(String value) {
if (value == null) {
return 0;
}
return value.length();
}
}

View File

@@ -0,0 +1,36 @@
package org.newdawn.slick.svg.inkscape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* The description of a module which processes a single XML element from a SVG (inkscape)
* document.
*
* @author kevin
*/
public interface ElementProcessor {
/**
* Process a document extracting all the elements that the processor is
* interested in and producing appropriate diagram components for the
* element.
*
* @param loader The loader/context of the parsing
* @param element The element to be processed
* @param diagram The diagram to be built
* @param transform The transform to apply to all elements at this level
* @throws ParsingException Indicates an invalid content to an element
*/
public void process(Loader loader, Element element, Diagram diagram, Transform transform) throws ParsingException;
/**
* Check if this processor handles the element specified
*
* @param element The element to check
* @return True if this processor can handle the given element
*/
public boolean handles(Element element);
}

View File

@@ -0,0 +1,60 @@
package org.newdawn.slick.svg.inkscape;
import org.newdawn.slick.geom.Ellipse;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Figure;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.NonGeometricData;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* Processor for <ellipse> and <path> nodes marked as arcs
*
* @author kevin
*/
public class EllipseProcessor implements ElementProcessor {
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram, Transform t) throws ParsingException {
Transform transform = Util.getTransform(element);
transform = new Transform(t, transform);
float x = Util.getFloatAttribute(element,"cx");
float y = Util.getFloatAttribute(element,"cy");
float rx = Util.getFloatAttribute(element,"rx");
float ry = Util.getFloatAttribute(element,"ry");
Ellipse ellipse = new Ellipse(x,y,rx,ry);
Shape shape = ellipse.transform(transform);
NonGeometricData data = Util.getNonGeometricData(element);
data.addAttribute("cx", ""+x);
data.addAttribute("cy", ""+y);
data.addAttribute("rx", ""+rx);
data.addAttribute("ry", ""+ry);
diagram.addFigure(new Figure(Figure.ELLIPSE, shape, data, transform));
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
if (element.getNodeName().equals("ellipse")) {
return true;
}
if (element.getNodeName().equals("path")) {
if ("arc".equals(element.getAttributeNS(Util.SODIPODI, "type"))) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,36 @@
package org.newdawn.slick.svg.inkscape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* TODO: Document this class
*
* @author kevin
*/
public class GroupProcessor implements ElementProcessor {
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
if (element.getNodeName().equals("g")) {
return true;
}
return false;
}
/**O
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram, Transform t) throws ParsingException {
Transform transform = Util.getTransform(element);
transform = new Transform(t, transform);
loader.loadChildren(element, transform);
}
}

View File

@@ -0,0 +1,49 @@
package org.newdawn.slick.svg.inkscape;
import org.newdawn.slick.svg.NonGeometricData;
import org.w3c.dom.Element;
/**
* A custom non-geometric data type that can pass back any attribute
* on the field.
*
* @author kevin
*/
public class InkscapeNonGeometricData extends NonGeometricData {
/** The element read from the SVG */
private Element element;
/**
* Create a new non-geometric data holder
*
* @param metaData The metadata provided
* @param element The XML element from the SVG document
*/
public InkscapeNonGeometricData(String metaData, Element element) {
super(metaData);
this.element = element;
}
/**
* @see org.newdawn.slick.svg.NonGeometricData#getAttribute(java.lang.String)
*/
public String getAttribute(String attribute) {
String result = super.getAttribute(attribute);
if (result == null) {
result = element.getAttribute(attribute);
}
return result;
}
/**
* Returns the XML element that is wrapped by this instance.
*
* @return The XML element for this instance
*/
public Element getElement() {
return element;
}
}

View File

@@ -0,0 +1,127 @@
package org.newdawn.slick.svg.inkscape;
import java.util.StringTokenizer;
import org.newdawn.slick.geom.Line;
import org.newdawn.slick.geom.Polygon;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Figure;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.NonGeometricData;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* A processor for the <line> element
*
* @author kevin
*/
public class LineProcessor implements ElementProcessor {
/**
* Process the points in a polygon definition
*
* @param poly The polygon being built
* @param element The XML element being read
* @param tokens The tokens representing the path
* @return The number of points found
* @throws ParsingException Indicates an invalid token in the path
*/
private static int processPoly(Polygon poly, Element element, StringTokenizer tokens) throws ParsingException {
int count = 0;
while (tokens.hasMoreTokens()) {
String nextToken = tokens.nextToken();
if (nextToken.equals("L")) {
continue;
}
if (nextToken.equals("z")) {
break;
}
if (nextToken.equals("M")) {
continue;
}
if (nextToken.equals("C")) {
return 0;
}
String tokenX = nextToken;
String tokenY = tokens.nextToken();
try {
float x = Float.parseFloat(tokenX);
float y = Float.parseFloat(tokenY);
poly.addPoint(x,y);
count++;
} catch (NumberFormatException e) {
throw new ParsingException(element.getAttribute("id"), "Invalid token in points list", e);
}
}
return count;
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram, Transform t) throws ParsingException {
Transform transform = Util.getTransform(element);
transform = new Transform(t, transform);
float x1;
float y1;
float x2;
float y2;
if (element.getNodeName().equals("line")) {
x1 = Float.parseFloat(element.getAttribute("x1"));
x2 = Float.parseFloat(element.getAttribute("x2"));
y1 = Float.parseFloat(element.getAttribute("y1"));
y2 = Float.parseFloat(element.getAttribute("y2"));
} else {
String points = element.getAttribute("d");
StringTokenizer tokens = new StringTokenizer(points, ", ");
Polygon poly = new Polygon();
if (processPoly(poly, element, tokens) == 2) {
x1 = poly.getPoint(0)[0];
y1 = poly.getPoint(0)[1];
x2 = poly.getPoint(1)[0];
y2 = poly.getPoint(1)[1];
} else {
return;
}
}
float[] in = new float[] {x1,y1,x2,y2};
float[] out = new float[4];
transform.transform(in,0,out,0,2);
Line line = new Line(out[0],out[1],out[2],out[3]);
NonGeometricData data = Util.getNonGeometricData(element);
data.addAttribute("x1",""+x1);
data.addAttribute("x2",""+x2);
data.addAttribute("y1",""+y1);
data.addAttribute("y2",""+y2);
diagram.addFigure(new Figure(Figure.LINE, line, data, transform));
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
if (element.getNodeName().equals("line")) {
return true;
}
if (element.getNodeName().equals("path")) {
if (!"arc".equals(element.getAttributeNS(Util.SODIPODI, "type"))) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,127 @@
package org.newdawn.slick.svg.inkscape;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.newdawn.slick.geom.Path;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Figure;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.NonGeometricData;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* A processor for the <polygon> and <path> elements marked as not an arc.
*
* @author kevin
*/
public class PathProcessor implements ElementProcessor {
/**
* Process the points in a polygon definition
*
* @param element The XML element being read
* @param tokens The tokens representing the path
* @return The number of points found
* @throws ParsingException Indicates an invalid token in the path
*/
private static Path processPoly(Element element, StringTokenizer tokens) throws ParsingException {
int count = 0;
ArrayList pts = new ArrayList();
boolean moved = false;
boolean reasonToBePath = false;
Path path = null;
while (tokens.hasMoreTokens()) {
try {
String nextToken = tokens.nextToken();
if (nextToken.equals("L")) {
float x = Float.parseFloat(tokens.nextToken());
float y = Float.parseFloat(tokens.nextToken());
path.lineTo(x,y);
continue;
}
if (nextToken.equals("z")) {
path.close();
continue;
}
if (nextToken.equals("M")) {
if (!moved) {
moved = true;
float x = Float.parseFloat(tokens.nextToken());
float y = Float.parseFloat(tokens.nextToken());
path = new Path(x,y);
continue;
}
reasonToBePath = true;
float x = Float.parseFloat(tokens.nextToken());
float y = Float.parseFloat(tokens.nextToken());
path.startHole(x,y);
continue;
}
if (nextToken.equals("C")) {
reasonToBePath = true;
float cx1 = Float.parseFloat(tokens.nextToken());
float cy1 = Float.parseFloat(tokens.nextToken());
float cx2 = Float.parseFloat(tokens.nextToken());
float cy2 = Float.parseFloat(tokens.nextToken());
float x = Float.parseFloat(tokens.nextToken());
float y = Float.parseFloat(tokens.nextToken());
path.curveTo(x,y,cx1,cy1,cx2,cy2);
continue;
}
} catch (NumberFormatException e) {
throw new ParsingException(element.getAttribute("id"), "Invalid token in points list", e);
}
}
if (!reasonToBePath) {
return null;
}
return path;
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram, Transform t) throws ParsingException {
Transform transform = Util.getTransform(element);
transform = new Transform(t, transform);
String points = element.getAttribute("points");
if (element.getNodeName().equals("path")) {
points = element.getAttribute("d");
}
StringTokenizer tokens = new StringTokenizer(points, ", ");
Path path = processPoly(element, tokens);
NonGeometricData data = Util.getNonGeometricData(element);
if (path != null) {
Shape shape = path.transform(transform);
diagram.addFigure(new Figure(Figure.PATH, shape, data, transform));
}
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
if (element.getNodeName().equals("path")) {
if (!"arc".equals(element.getAttributeNS(Util.SODIPODI, "type"))) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,119 @@
package org.newdawn.slick.svg.inkscape;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.newdawn.slick.geom.Polygon;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Figure;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.NonGeometricData;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* A processor for the <polygon> and <path> elements marked as not an arc.
*
* @author kevin
*/
public class PolygonProcessor implements ElementProcessor {
/**
* Process the points in a polygon definition
*
* @param poly The polygon being built
* @param element The XML element being read
* @param tokens The tokens representing the path
* @return The number of points found
* @throws ParsingException Indicates an invalid token in the path
*/
private static int processPoly(Polygon poly, Element element, StringTokenizer tokens) throws ParsingException {
int count = 0;
ArrayList pts = new ArrayList();
boolean moved = false;
boolean closed = false;
while (tokens.hasMoreTokens()) {
String nextToken = tokens.nextToken();
if (nextToken.equals("L")) {
continue;
}
if (nextToken.equals("z")) {
closed = true;
break;
}
if (nextToken.equals("M")) {
if (!moved) {
moved = true;
continue;
}
return 0;
}
if (nextToken.equals("C")) {
return 0;
}
String tokenX = nextToken;
String tokenY = tokens.nextToken();
try {
float x = Float.parseFloat(tokenX);
float y = Float.parseFloat(tokenY);
poly.addPoint(x,y);
count++;
} catch (NumberFormatException e) {
throw new ParsingException(element.getAttribute("id"), "Invalid token in points list", e);
}
}
poly.setClosed(closed);
return count;
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram, Transform t) throws ParsingException {
Transform transform = Util.getTransform(element);
transform = new Transform(t, transform);
String points = element.getAttribute("points");
if (element.getNodeName().equals("path")) {
points = element.getAttribute("d");
}
StringTokenizer tokens = new StringTokenizer(points, ", ");
Polygon poly = new Polygon();
int count = processPoly(poly, element, tokens);
NonGeometricData data = Util.getNonGeometricData(element);
if (count > 3) {
Shape shape = poly.transform(transform);
diagram.addFigure(new Figure(Figure.POLYGON, shape, data, transform));
}
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
if (element.getNodeName().equals("polygon")) {
return true;
}
if (element.getNodeName().equals("path")) {
if (!"arc".equals(element.getAttributeNS(Util.SODIPODI, "type"))) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,54 @@
package org.newdawn.slick.svg.inkscape;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Figure;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.NonGeometricData;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* A processor for the <rect> element.
*
* @author kevin
*/
public class RectProcessor implements ElementProcessor {
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram, Transform t) throws ParsingException {
Transform transform = Util.getTransform(element);
transform = new Transform(t, transform);
float width = Float.parseFloat(element.getAttribute("width"));
float height = Float.parseFloat(element.getAttribute("height"));
float x = Float.parseFloat(element.getAttribute("x"));
float y = Float.parseFloat(element.getAttribute("y"));
Rectangle rect = new Rectangle(x,y,width+1,height+1);
Shape shape = rect.transform(transform);
NonGeometricData data = Util.getNonGeometricData(element);
data.addAttribute("width", ""+width);
data.addAttribute("height", ""+height);
data.addAttribute("x", ""+x);
data.addAttribute("y", ""+y);
diagram.addFigure(new Figure(Figure.RECTANGLE, shape, data, transform));
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
if (element.getNodeName().equals("rect")) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,55 @@
package org.newdawn.slick.svg.inkscape;
import org.newdawn.slick.geom.Shape;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.Diagram;
import org.newdawn.slick.svg.Figure;
import org.newdawn.slick.svg.Loader;
import org.newdawn.slick.svg.NonGeometricData;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* Processor for the "use", a tag that allows references to other elements
* and cloning.
*
* @author kevin
*/
public class UseProcessor implements ElementProcessor {
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#handles(org.w3c.dom.Element)
*/
public boolean handles(Element element) {
return element.getNodeName().equals("use");
}
/**
* @see org.newdawn.slick.svg.inkscape.ElementProcessor#process(org.newdawn.slick.svg.Loader, org.w3c.dom.Element, org.newdawn.slick.svg.Diagram, org.newdawn.slick.geom.Transform)
*/
public void process(Loader loader, Element element, Diagram diagram,
Transform transform) throws ParsingException {
String ref = element.getAttributeNS("http://www.w3.org/1999/xlink", "href");
String href = Util.getAsReference(ref);
Figure referenced = diagram.getFigureByID(href);
if (referenced == null) {
throw new ParsingException(element, "Unable to locate referenced element: "+href);
}
Transform local = Util.getTransform(element);
Transform trans = local.concatenate(referenced.getTransform());
NonGeometricData data = Util.getNonGeometricData(element);
Shape shape = referenced.getShape().transform(trans);
data.addAttribute(NonGeometricData.FILL, referenced.getData().getAttribute(NonGeometricData.FILL));
data.addAttribute(NonGeometricData.STROKE, referenced.getData().getAttribute(NonGeometricData.STROKE));
data.addAttribute(NonGeometricData.OPACITY, referenced.getData().getAttribute(NonGeometricData.OPACITY));
data.addAttribute(NonGeometricData.STROKE_WIDTH, referenced.getData().getAttribute(NonGeometricData.STROKE_WIDTH));
Figure figure = new Figure(referenced.getType(), shape, data, trans);
diagram.addFigure(figure);
}
}

View File

@@ -0,0 +1,198 @@
package org.newdawn.slick.svg.inkscape;
import java.util.StringTokenizer;
import org.newdawn.slick.geom.Transform;
import org.newdawn.slick.svg.NonGeometricData;
import org.newdawn.slick.svg.ParsingException;
import org.w3c.dom.Element;
/**
* A set of utility for processing the SVG documents produced by Inkscape
*
* @author kevin
*/
public class Util {
/** The namespace for inkscape */
public static final String INKSCAPE = "http://www.inkscape.org/namespaces/inkscape";
/** The namespace for sodipodi */
public static final String SODIPODI = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
/** The namespace for xlink */
public static final String XLINK = "http://www.w3.org/1999/xlink";
/**
* Get the non-geometric data information from an XML element
*
* @param element The element to be processed
* @return The non-geometric data (i.e. stroke, fill, etc)
*/
static NonGeometricData getNonGeometricData(Element element) {
String meta = getMetaData(element);
NonGeometricData data = new InkscapeNonGeometricData(meta, element);
data.addAttribute(NonGeometricData.ID, element.getAttribute("id"));
data.addAttribute(NonGeometricData.FILL, getStyle(element, NonGeometricData.FILL));
data.addAttribute(NonGeometricData.STROKE, getStyle(element, NonGeometricData.STROKE));
data.addAttribute(NonGeometricData.OPACITY, getStyle(element, NonGeometricData.OPACITY));
data.addAttribute(NonGeometricData.STROKE_DASHARRAY, getStyle(element, NonGeometricData.STROKE_DASHARRAY));
data.addAttribute(NonGeometricData.STROKE_DASHOFFSET, getStyle(element, NonGeometricData.STROKE_DASHOFFSET));
data.addAttribute(NonGeometricData.STROKE_MITERLIMIT, getStyle(element, NonGeometricData.STROKE_MITERLIMIT));
data.addAttribute(NonGeometricData.STROKE_OPACITY, getStyle(element, NonGeometricData.STROKE_OPACITY));
data.addAttribute(NonGeometricData.STROKE_WIDTH, getStyle(element, NonGeometricData.STROKE_WIDTH));
return data;
}
/**
* Get the meta data store within an element either in the label or
* id atributes
*
* @param element The element to be processed
* @return The meta data stored
*/
static String getMetaData(Element element) {
String label = element.getAttributeNS(INKSCAPE, "label");
if ((label != null) && (!label.equals(""))) {
return label;
}
return element.getAttribute("id");
}
/**
* Get the style attribute setting for a given style information element (i.e. fill, stroke)
*
* @param element The element to be processed
* @param styleName The name of the attribute to retrieve
* @return The style value
*/
static String getStyle(Element element, String styleName) {
String value = element.getAttribute(styleName);
if ((value != null) && (value.length() > 0)) {
return value;
}
String style = element.getAttribute("style");
return extractStyle(style, styleName);
}
/**
* Extract the style value from a Inkscape encoded string
*
* @param style The style string to be decoded
* @param attribute The style attribute to retrieve
* @return The value for the given attribute
*/
static String extractStyle(String style, String attribute) {
if (style == null) {
return "";
}
StringTokenizer tokens = new StringTokenizer(style,";");
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken();
String key = token.substring(0,token.indexOf(':'));
if (key.equals(attribute)) {
return token.substring(token.indexOf(':')+1);
}
}
return "";
}
/**
* Get a transform defined in the XML
*
* @param element The element from which the transform should be read
* @return The transform to be applied
*/
static Transform getTransform(Element element) {
return getTransform(element, "transform");
}
/**
* Get a transform defined in the XML
*
* @param element The element from which the transform should be read
* @param attribute The name of the attribute holding the transform
* @return The transform to be applied
*/
static Transform getTransform(Element element, String attribute) {
String str = element.getAttribute(attribute);
if (str == null) {
return new Transform();
}
if (str.equals("")) {
return new Transform();
} else if (str.startsWith("translate")) {
str = str.substring(0, str.length()-1);
str = str.substring("translate(".length());
StringTokenizer tokens = new StringTokenizer(str, ", ");
float x = Float.parseFloat(tokens.nextToken());
float y = Float.parseFloat(tokens.nextToken());
return Transform.createTranslateTransform(x,y);
} else if (str.startsWith("matrix")) {
float[] pose = new float[6];
str = str.substring(0, str.length()-1);
str = str.substring("matrix(".length());
StringTokenizer tokens = new StringTokenizer(str, ", ");
float[] tr = new float[6];
for (int j=0;j<tr.length;j++) {
tr[j] = Float.parseFloat(tokens.nextToken());
}
pose[0] = tr[0];
pose[1] = tr[2];
pose[2] = tr[4];
pose[3] = tr[1];
pose[4] = tr[3];
pose[5] = tr[5];
return new Transform(pose);
}
return new Transform();
}
/**
* Get a floating point attribute that may appear in either the default or
* SODIPODI namespace
*
* @param element The element from which the attribute should be read
* @param attr The attribute to be read
* @return The value from the given attribute
* @throws ParsingException Indicates the value in the attribute was not a float
*/
static float getFloatAttribute(Element element, String attr) throws ParsingException {
String cx = element.getAttribute(attr);
if ((cx == null) || (cx.equals(""))) {
cx = element.getAttributeNS(SODIPODI, attr);
}
try {
return Float.parseFloat(cx);
} catch (NumberFormatException e) {
throw new ParsingException(element, "Invalid value for: "+attr, e);
}
}
/**
* Get the attribute value as a reference to another entity
*
* @param value The value to treat as reference
* @return The reference part of the attribute value
*/
public static String getAsReference(String value) {
if (value.length() < 2) {
return "";
}
value = value.substring(1, value.length());
return value;
}
}

View File

@@ -0,0 +1,4 @@
<BODY>
Demo/Test SVG area. Tiny line is integrated to render-to-texture - however a full implementation is
to follow rendering SVG to OpenGL geometric shapes.
</BODY>