magory.svg.Sovery.java Source code

Java tutorial

Introduction

Here is the source code for magory.svg.Sovery.java

Source

package magory.svg;

/*
 * Sovery - SVG loader for level & interface design  
 * version 3.0
 * author: Tomasz Kucza
 * license: CC0 - just copy it and do whatever you want
 * 
 * Tips, bugs and stuff:
 * 1) make sure your SVG has scale set to 1 (default), otherwise it will be scaled
 * 2) override this class and write your own code for the newImage/newPath/newRect/newText
 * 3) width/height can be below zero - it means the image has to be flipped when drawing 
 * 4) the library depends on libGDX but should be easy to convert to another library
 */

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.XmlReader;
import com.badlogic.gdx.utils.XmlReader.Element;

public abstract class Sovery {
    public String getLayerName(Element el) {
        if (el == null)
            return "";
        Element parent = el.getParent();
        if (parent == null)
            return "";
        String s = parent.getAttribute("inkscape:label", "");
        if (s == null)
            return getLayerName(parent);
        return s;
    }

    public String getTitle(Element el) {
        if (el == null)
            return "";
        Element tit = getChild(el, "title");
        if (tit != null)
            return tit.getText();
        return "";
    }

    public String getParentTitle(Element el) {
        if (el == null)
            return "";
        if (el.getParent() == null)
            return "";
        Element tit = getChild(el.getParent(), "title");
        if (tit != null)
            return tit.getText();
        return "";
    }

    public void load(String name) {
        load(Gdx.files.internal(name));
    }

    public void loadFromString(Element lev) {
        loadElement(lev, null);
        onFinish();
    }

    public void load(FileHandle file) {
        XmlReader xr = new XmlReader();
        Element lev = new Element("a", null);
        try {
            lev = xr.parse(file);
        } catch (Exception e) {
            Gdx.app.log("Solvery", "Error loading " + file);
        }

        loadElement(lev, null);
        onFinish();
    }

    // override if you want
    public void onFinish() {
    }

    // statics

    public String getImageName(String trans) {
        // example: /media/amon/praca/android/fluffy/topack-game/tile-grass.png
        String name = "";
        try {
            int en = trans.indexOf(".png");
            if (en < 0)
                en = trans.indexOf(".jpg");
            int start = trans.lastIndexOf('/');
            int st2 = trans.lastIndexOf('\\'); // for SVGs saved on Windows - not tested, might not work!
            if (st2 > start)
                start = st2;
            name = trans.substring(start + 1, en);

        } catch (Exception e) {
            Gdx.app.log("Solvery", "Error getting filename: " + trans);
        }
        return name;
    }

    public static boolean isDigitOrSign(char a) {
        return Character.isDigit(a) || a == '-';
    }

    public static Array<Vector2> parsePathToArray(String d, float x, float y, float screenheight) {
        Array<Vector2> path = new Array<Vector2>();
        if (!d.equals("")) {
            String commands[] = d.split("\\ ");
            if (commands.length > 0) {
                String lastCommand = "";

                // only m,M,l,L,z,Z commands are supported
                for (int pos = 0; pos < commands.length; pos++) {
                    if (commands[pos].equals("m"))
                        lastCommand = "m";
                    else if (commands[pos].equals("M"))
                        lastCommand = "M";
                    else if (commands[pos].equals("L"))
                        lastCommand = "L";
                    else if (commands[pos].equals("l"))
                        lastCommand = "l";
                    else if (commands[pos].equals("Z") || commands[pos].equals("z")) {
                        // close path
                        if (path.size > 0)
                            path.add(new Vector2(path.get(1)));
                    } else if (commands[pos].length() > 0 && isDigitOrSign(commands[pos].charAt(0)))// assume coordinates
                    {
                        String coords[] = commands[pos].split(",");
                        if (coords.length > 1) {
                            Vector2 c = new Vector2(getFloat(coords[0]), getFloat(coords[1]));
                            if (path.size > 0 && lastCommand.equals("m") || lastCommand.equals("l")) // relative coords
                            {
                                c.x = path.get(path.size - 1).x + c.x;
                                c.y = path.get(path.size - 1).y + c.y;
                            }
                            path.add(c);
                        }
                    }
                }
                for (int i = 0; i < path.size; i++) {
                    path.get(i).x += x;
                    path.get(i).y += y;
                    path.get(i).y = screenheight - path.get(i).y;
                }
            }
        }
        return path;
    }

    public boolean isRotation(String trans) {
        return trans.contains("rotate(");
    }

    public boolean isScale(String trans) {
        return trans.contains("scale(");
    }

    public boolean isMatrix(String trans) {
        return trans.contains("matrix(");
    }

    public boolean isTranslate(String trans) {
        return trans.contains("translate(");
    }

    public float[] getScaleFloats(String t) {
        return getTwoFloats(t, "scale(");
    }

    public float getRotationFloat(String t) {
        return getOneFloat(t, "rotate(");
    }

    public float[] getRotationFloats(String t) {
        return getThreeFloats(t, "rotate(");
    }

    public float[] getTranslateFloats(String t) {
        return getTwoFloats(t, "translate(");
    }

    public float[] getMatrixFloats(String t) {
        return getSixFloats(t, "matrix(");
    }

    public boolean isRect(String elname) {
        return elname.equals("rect");
    }

    public boolean isImage(String elname) {
        return elname.equals("image");
    }

    public boolean isText(String elname) {
        return elname.equals("text");
    }

    public boolean isStyle(String s, String style) {
        if (s.startsWith(style + ":"))
            return true;
        return false;
    }

    public Element getChild(Element el, String name) {
        return el.getChildByName(name);
    }

    public String getAttribute(Element el, String name, String defaultValue, boolean parent) {
        return el.getAttribute(name, defaultValue);
    }

    public String getAttributeValue(Element el, String attribute, String name, String defaultValue) {
        // style="image-rendering:optimizeQuality;opacity:0.613"
        String att = getAttribute(el, "style", "", false);
        String value = "";
        try {
            int st = att.indexOf(name + ":");
            if (st == -1)
                return value;

            int en = att.indexOf(';', st);
            if (en == -1)
                en = att.indexOf('"', st);
            if (en == -1)
                return value;

            value = att.substring(st + 1, en);
        } catch (Exception e) {
            Gdx.app.log("Solvery", "Error getting value: " + attribute + "=" + name + ":VALUE;");
        }
        return value;
    }

    // gets opacity from image element
    public float getOpacity(Element el) {
        float opacity = 1;
        String op = getAttributeValue(el, "style", "opacity", "1");
        if (!op.equals("")) {
            opacity = getFloat(op, 1);
        }
        return opacity;
    }

    public String getColor(String s) {
        String f = "f:";
        int start = s.indexOf(f); // compressed
        if (start == -1) {
            f = "fill:";
            start = s.indexOf(f); // not compressed
        }
        int end = s.indexOf(";", start);
        if (end > start)
            return s.substring(start + f.length(), end);
        return "";
    }

    public static int getHexValue(char c) {
        if (c >= 'a')
            return c - 'a' + 10;
        else
            return c - '0';
    }

    public static Color getColorFromString(String c) {
        if (c.length() > 5) {
            //Gdx.app.log("Solvery", c);
            float r = getHexValue(c.charAt(0)) * 16 + getHexValue(c.charAt(1));
            float g = getHexValue(c.charAt(2)) * 16 + getHexValue(c.charAt(3));
            float b = getHexValue(c.charAt(4)) * 16 + getHexValue(c.charAt(5));
            r /= 256f;
            g /= 256f;
            b /= 256f;
            //Gdx.app.log("Solvery", "r"+r+"g"+g+"b"+b);
            return new Color(r, g, b, 1);
        } else
            return new Color(1, 1, 1, 1);
    }

    public static float[] getSixFloats(String trans, String search) {
        int st = trans.indexOf(search);
        float[] fl = new float[6];
        float xx = 0;
        float yy = 0;

        float zz = 0;
        float x2 = 0;
        float y2 = 0;
        float z2 = 0;

        if (st != -1) // jest translate
        {
            int comma = trans.indexOf(",", st);
            int bracket = trans.indexOf(")", st);
            int comma2 = trans.indexOf(",", comma + 1);
            try {

                xx = new Float(trans.substring(st + search.length(), comma));
                yy = new Float(trans.substring(comma + 1, comma2));
                comma = comma2;
                comma2 = trans.indexOf(",", comma + 1);
                zz = new Float(trans.substring(comma + 1, comma2));

                comma = comma2;
                comma2 = trans.indexOf(",", comma + 1);
                x2 = new Float(trans.substring(comma + 1, comma2));

                comma = comma2;
                comma2 = trans.indexOf(",", comma + 1);
                y2 = new Float(trans.substring(comma + 1, comma2));

                comma = comma2;
                z2 = new Float(trans.substring(comma + 1, bracket));

            } catch (Exception e) {

            }
        }
        fl[0] = xx;
        fl[1] = yy;
        fl[2] = zz;
        fl[3] = x2;
        fl[4] = y2;
        fl[5] = z2;
        return fl;
    }

    public static float getOneFloat(String trans, String search) {
        int st = trans.indexOf(search);
        float xx = 0;
        if (st != -1) // jest translate
        {
            int comma = trans.indexOf(")", st);
            try {
                xx = getFloat(trans.substring(st + search.length(), comma));
                return xx;
            } catch (Exception e) {

            }
        }
        return 0;
    }

    public static float[] getTwoFloats(String trans, String search) {
        int st = trans.indexOf(search);
        float[] fl = new float[2];
        float xx = 0;
        float yy = 0;
        if (st != -1) // jest translate
        {
            int comma = trans.indexOf(",", st); // TODO: SVG also allows space to be used instead of ,
            int bracket = trans.indexOf(")", st);
            try {
                if (comma != -1) {
                    xx = getFloat(trans.substring(st + search.length(), comma));
                    yy = getFloat(trans.substring(comma + 1, bracket));
                } else {
                    xx = getFloat(trans.substring(st + search.length(), bracket));
                    yy = xx; // imporant - because SVG assumes scale(1) = scale(1,1)
                }
            } catch (Exception e) {
                xx = 0;
                yy = 0;
                Gdx.app.log("Solvery", "Error getting floats: " + trans + ":" + search + "; Assuming 0,0;");
            }
        }
        fl[0] = xx;
        fl[1] = yy;
        return fl;
    }

    public static float[] getThreeFloats(String trans, String search) {
        int st = trans.indexOf(search);
        float[] fl = new float[3];
        float xx = 0;
        float yy = 0;
        float zz = 0;
        if (st != -1) // jest translate
        {
            try {
                int comma = trans.indexOf(",", st);
                int bracket = trans.indexOf(")", st);
                int comma2 = trans.indexOf(",", comma + 1);
                xx = getFloat(trans.substring(st + search.length(), comma));
                yy = getFloat(trans.substring(comma + 1, comma2));
                comma = comma2;
                comma2 = trans.indexOf(",", comma + 1);
                zz = getFloat(trans.substring(comma + 1, bracket));
            } catch (Exception e) {
                xx = 0;
                yy = 0;
                zz = 0;
                Gdx.app.log("Solvery", "Error getting floats: " + trans + ":" + search + "; Assuming 0,0,0;");
            }
        }
        fl[0] = xx;
        fl[1] = yy;
        fl[2] = zz;
        return fl;
    }

    public static int getInt(String t, String beg) {
        return getInt(t.substring(beg.length()));
    }

    public static int getInt(String trans) {
        try {
            return new Integer(trans);
        } catch (Exception e) {
        }
        return 0;
    }

    public static float getFloat(String t, String beg) {
        return getFloat(t.substring(beg.length()));
    }

    public static float getFloat(String trans) {
        if (trans.startsWith("--"))
            trans = trans.substring(1);
        try {
            return new Float(trans);
        } catch (Exception e) {
        }
        return 0;
    }

    public static float getFloat(String trans, float defaultValue) {
        if (trans.startsWith("--"))
            trans = trans.substring(1);
        try {
            return new Float(trans);
        } catch (Exception e) {
        }
        return defaultValue;
    }

    // FIELDS 

    String layerName;
    public float SVGWidth;
    public float SVGHeight;
    // MAIN FUNCTIONS - the most important part of code

    public void getSVGDimentions(Element el) {
        String w = getAttribute(el, "width", "1280", false);
        String h = getAttribute(el, "height", "800", false);
        w = w.replace("px", "");
        h = h.replace("px", "");
        SVGWidth = getFloat(w);
        SVGHeight = getFloat(h);
    }

    public Matrix3 getTransformMatrix(String trans) {
        float matrixNew[] = new float[9];

        matrixNew[0] = 1;
        matrixNew[1] = 0;
        matrixNew[2] = 0;
        matrixNew[3] = 0;
        matrixNew[4] = 1;
        matrixNew[5] = 0;
        matrixNew[6] = 0;
        matrixNew[7] = 0;
        matrixNew[8] = 1;

        if (!trans.equals("")) {
            if (isRotation(trans)) {
                // if is rotation 3 values
                if (trans.contains(",")) {
                    // translate(<cx>, <cy>) rotate(<rotate-angle>) translate(-<cx>, -<cy>)
                    float r[] = getRotationFloats(trans);
                    Matrix3 t2 = getTransformMatrix("translate(" + r[1] + "," + r[2] + ")");
                    Matrix3 rr = getTransformMatrix("rotate(" + r[0] + ")");
                    Matrix3 t1 = getTransformMatrix("translate(" + (-r[1]) + "," + (-r[2]) + ")");
                    /*Gdx.app.log("test", "translate("+r[1]+","+r[2]+")");
                    Gdx.app.log("test", "rotate("+r[0]+")");
                    Gdx.app.log("test", "translate(-"+r[1]+",-"+r[2]+")");*/
                    rr.mulLeft(t2);
                    t1.mulLeft(rr);
                    return t1;

                } else {
                    // -> rotation matrix
                    float r = getRotationFloat(trans);
                    matrixNew[0] = (float) Math.cos(Math.toRadians(r));
                    matrixNew[1] = (float) Math.sin(Math.toRadians(r));
                    matrixNew[2] = 0;
                    matrixNew[3] = (float) -Math.sin(Math.toRadians(r));
                    matrixNew[4] = (float) Math.cos(Math.toRadians(r));
                    matrixNew[5] = 0;
                    matrixNew[6] = 0;
                    matrixNew[7] = 0;
                    matrixNew[8] = 1;
                }
            } else if (isScale(trans)) {
                // -> scale matrix
                float fl[] = getScaleFloats(trans);
                matrixNew[0] = fl[0];
                matrixNew[1] = 0;
                matrixNew[2] = 0;
                matrixNew[3] = 0;
                matrixNew[4] = fl[1];
                matrixNew[5] = 0;
                matrixNew[6] = 0;
                matrixNew[7] = 0;
                matrixNew[8] = 1;
            } else if (isTranslate(trans)) {
                // -> translate matrix matrix(1 0 0 1 x y)
                float fl[] = getTranslateFloats(trans);
                matrixNew[0] = 1;
                matrixNew[1] = 0;
                matrixNew[2] = 0;
                matrixNew[3] = 0;
                matrixNew[4] = 1;
                matrixNew[5] = 0;
                matrixNew[6] = fl[0];
                matrixNew[7] = fl[1];
                matrixNew[8] = 1;
            } else if (isMatrix(trans)) {
                // just read matrix
                float fl[] = getMatrixFloats(trans);
                matrixNew[0] = fl[0];
                matrixNew[1] = fl[1];
                matrixNew[2] = 0;
                matrixNew[3] = fl[2];
                matrixNew[4] = fl[3];
                matrixNew[5] = 0;
                matrixNew[6] = fl[4];
                matrixNew[7] = fl[5];
                matrixNew[8] = 1;
            }
        }
        return new Matrix3(matrixNew);
    }

    public Vector2 deltaTransformPoint(Matrix3 matrix, Vector2 point) {
        float dx = point.x * matrix.val[MA] + point.y * matrix.val[MC] + 0;
        float dy = point.x * matrix.val[MB] + point.y * matrix.val[MD] + 0;
        return new Vector2(dx, dy);
    }

    final static int MA = 0;
    final static int MB = 3;
    final static int MC = 1;
    final static int MD = 4;
    final static int ME = 6;
    final static int MF = 7;

    public Matrix3 translate(float x, float y) {
        // translate to x,y of the object
        float[] matrixNew = new float[9];

        matrixNew[0] = 1;
        matrixNew[1] = 0;
        matrixNew[2] = 0;
        matrixNew[3] = 0;
        matrixNew[4] = 1;
        matrixNew[5] = 0;
        matrixNew[6] = x;
        matrixNew[7] = y;
        matrixNew[8] = 1;

        return new Matrix3(matrixNew);
    }

    public void getScale(Matrix3 matrix, Vector2 scale) {
        matrix.getScale(scale);
        float determinant = ((matrix.val[0] * matrix.val[4] - matrix.val[3] * matrix.val[1]) < 0) ? -1 : 1;
        if (determinant < 0) {
            if (matrix.val[0] < matrix.val[4])
                scale.x = -scale.x;
            else
                scale.y = -scale.y;
        }
    }

    public void loadElement(Element el, float[] matrixOld) {
        String elname = el.getName();
        if (elname.equals("svg"))
            getSVGDimentions(el);

        int count = el.getChildCount();

        // magic for transforms and matrixes, don't touch unless you know what you are doing
        String trans = getAttribute(el, "transform", "", false);
        String title = getTitle(el);
        float elementX = getFloat(el.getAttribute("x", "0"));
        float elementY = getFloat(el.getAttribute("y", "0"));
        float elementWidth = getFloat(getAttribute(el, "width", "", false));
        float elementHeight = getFloat(getAttribute(el, "height", "", false));
        Vector2 origin = new Vector2(elementWidth / 2, elementHeight / 2);
        Vector2 scale = new Vector2(1, 1);
        Vector2 scaleOld = new Vector2(1, 1);
        Vector2 translate = new Vector2();
        float rr; // rotation

        Matrix3 matrix = getTransformMatrix(trans);
        float oldR = 0;
        if (matrixOld != null) // is a matrix
        {
            Matrix3 mOld = new Matrix3(matrixOld);
            oldR = mOld.getRotation();
            getScale(mOld, scaleOld);

            if (scaleOld.y < 0 || scaleOld.x < 0) {
                origin.rotate(oldR);
                matrix.mulLeft(translate(-origin.x, -origin.y));
                origin.rotate(-oldR);
                matrix.mulLeft(mOld);
                matrix.mul(translate(elementX, elementY));
                matrix.translate(origin.x, origin.y);
            } else {
                origin.rotate(-oldR);
                matrix.mulLeft(translate(-origin.x, -origin.y));
                origin.rotate(oldR);
                matrix.mulLeft(mOld);
                matrix.mul(translate(elementX, elementY));
                matrix.translate(origin.x, origin.y);
            }
        } else {
            matrix.mulLeft(translate(-origin.x, -origin.y));
            matrix.mul(translate(elementX, elementY));
            matrix.translate(origin.x, origin.y);
        }

        if (count != 0)
            for (int i = 0; i < count; i++)
                loadElement(el.getChild(i), matrix.getValues());

        // important magic for width,height and x,y
        // inspired by this: http://stackoverflow.com/questions/16359246/how-to-extract-position-rotation-and-scale-from-matrix-svg
        // and this: http://www.w3.org/TR/css3-transforms/

        matrix.getScale(scale);
        rr = matrix.getRotation();

        // magic which I don't understand
        getScale(matrix, scale);
        if (scale.y < 0 && scaleOld.y > 0) {
            Vector2 det = new Vector2(0, elementHeight * Math.signum(scale.y) * 2); // because y flip uses top border of the image 
            det.rotate(rr);
            matrix.translate(det);
        }

        if (scaleOld.y < 0 || scaleOld.x < 0) {
            Vector2 det = new Vector2(0, scaleOld.y * elementHeight / scale.y); // because... well, I don't know why
            if (scale.y < 0)
                det.rotate(rr + 180);
            else if (scale.x < 0)
                det.rotate(rr);
            else {
                if (scaleOld.x < 0)
                    det.rotate(-rr + 180);
                else
                    det.rotate(-rr);
            }

            matrix.translate(det);
        }

        matrix.getTranslation(translate);

        float width = elementWidth * scale.x;
        float height = elementHeight * scale.y;
        float yyy = SVGHeight - translate.y - height;
        float xxx = translate.x;

        if (title.startsWith("testing")) {
            Gdx.app.log("test", "oldR:" + oldR + " sx:" + scaleOld.x + " sy:" + scaleOld.y);
            Gdx.app.log("test", ":::" + title + ":" + xxx + "," + yyy + ":" + rr + " " + width + "x" + height
                    + " rr:" + rr + " scalex:" + scale.x + " scaley:" + scale.y);
        }
        rr = -rr;
        if (width < 0)
            rr = rr + 180;
        if (rr < 0)
            rr += 360;

        if (title.startsWith("testing"))
            Gdx.app.log("test", ":::newrr:" + rr);

        if (elname.equals("path")) // path
        {
            String d = el.getAttribute("d", "");
            newPath(parsePathToArray(d, elementX, elementY, SVGHeight), el, title);
        } else if (isText(elname)) // text
        {
            Element e = getChild(el, "tspan");
            if (e == null)
                return;

            String text;
            if (e.getText() == null)
                text = "";
            else
                text = e.getText().trim();

            // font-size as height! - width not set unfortunately
            // example: font-size:44.03109741px;
            String style = getAttribute(el, "style", "", false);
            String styles[] = style.split("\\;");
            Color color = new Color(1, 1, 1, 1);
            if (styles != null && styles.length > 0) {
                for (int i = 0; i < styles.length; i++) {
                    if (isStyle(styles[i], "font-size")) {
                        String stylesdata[] = styles[i].split("\\:");
                        stylesdata[1] = stylesdata[1].replace("px", "");
                        stylesdata[1] = stylesdata[1].replace(";", "");
                        height = getFloat(stylesdata[1].trim());
                    } else if (isStyle(styles[i], "fill")) {
                        //fill:#effffa
                        String stylesdata[] = styles[i].split("\\:");
                        stylesdata[1] = stylesdata[1].replace("#", "");
                        stylesdata[1] = stylesdata[1].replace(";", "");
                        color = getColorFromString(stylesdata[1].trim());
                    }
                }
            }
            newText(text, el, xxx, yyy, width, height, rr, color);
        } else if (isImage(elname)) // obraz
        {
            String name = getImageName(getAttribute(el, "xlink:href", "", false));
            newImage(name, el, xxx, yyy, width, height, rr);
        } else if (isRect(elname)) // obraz
        {
            Element title2 = getChild(el, "title");
            if (title2 != null)
                newRect(title2.getText(), el, xxx, yyy, width, height, rr);
            else
                newRect("", el, xxx, yyy, width, height, rr);
        }
    }

    // actions to override

    abstract public void newImage(String name, Element el, float xxx, float yyy, float width, float height,
            float rr);

    abstract public void newRect(String name, Element el, float xxx, float yyy, float width, float height,
            float rr);

    abstract public void newText(String text, Element el, float xxx, float yyy, float width, float height, float rr,
            Color color);

    abstract public void newPath(Array<Vector2> path, Element el, String title);
}