From 3c361195d85786aa237b5c162bdee82f01875d65 Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Sat, 13 Apr 2019 09:12:00 +0200 Subject: [PATCH] Switch to androidsvg SVG library. This library has much better support for the SVG standard. --- DroidFishApp/build.gradle | 1 + .../larvalabs/svgandroid/ParserHelper.java | 306 ---- .../java/com/larvalabs/svgandroid/SVG.java | 122 -- .../svgandroid/SVGParseException.java | 23 - .../com/larvalabs/svgandroid/SVGParser.java | 1340 ----------------- .../larvalabs/svgandroid/package-info.java | 26 - .../java/org/petero/droidfish/DroidFish.java | 16 +- .../java/org/petero/droidfish/PieceSet.java | 84 +- .../petero/droidfish/SVGPictureDrawable.java | 27 +- build.gradle | 2 +- 10 files changed, 99 insertions(+), 1848 deletions(-) delete mode 100644 DroidFishApp/src/main/java/com/larvalabs/svgandroid/ParserHelper.java delete mode 100644 DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java delete mode 100644 DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParseException.java delete mode 100644 DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java delete mode 100644 DroidFishApp/src/main/java/com/larvalabs/svgandroid/package-info.java diff --git a/DroidFishApp/build.gradle b/DroidFishApp/build.gradle index dc1f924..162cc7e 100644 --- a/DroidFishApp/build.gradle +++ b/DroidFishApp/build.gradle @@ -57,6 +57,7 @@ dependencies { androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation project(':CuckooChessEngine') + implementation 'com.caverock:androidsvg-aar:1.3' } // Build the ECO database diff --git a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/ParserHelper.java b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/ParserHelper.java deleted file mode 100644 index 10a7d63..0000000 --- a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/ParserHelper.java +++ /dev/null @@ -1,306 +0,0 @@ -package com.larvalabs.svgandroid; - -/* - - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ -/** - * Parses numbers from SVG text. Based on the Batik Number Parser (Apache 2 License). - * - * @author Apache Software Foundation, Larva Labs LLC - */ -public class ParserHelper { - - private char current; - private CharSequence s; - public int pos; - private int n; - - public ParserHelper(CharSequence s, int pos) { - this.s = s; - this.pos = pos; - n = s.length(); - current = s.charAt(pos); - } - - private char read() { - if (pos < n) { - pos++; - } - if (pos == n) { - return '\0'; - } else { - return s.charAt(pos); - } - } - - public void skipWhitespace() { - while (pos < n) { - if (Character.isWhitespace(s.charAt(pos))) { - advance(); - } else { - break; - } - } - } - - public void skipNumberSeparator() { - while (pos < n) { - char c = s.charAt(pos); - switch (c) { - case ' ': - case ',': - case '\n': - case '\t': - advance(); - break; - default: - return; - } - } - } - - public void advance() { - current = read(); - } - - /** - * Parses the content of the buffer and converts it to a float. - */ - public float parseFloat() { - int mant = 0; - int mantDig = 0; - boolean mantPos = true; - boolean mantRead = false; - - int exp = 0; - int expDig = 0; - int expAdj = 0; - boolean expPos = true; - - switch (current) { - case '-': - mantPos = false; - // fallthrough - case '+': - current = read(); - } - - m1: switch (current) { - default: - return Float.NaN; - - case '.': - break; - - case '0': - mantRead = true; - l: for (;;) { - current = read(); - switch (current) { - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - break l; - case '.': case 'e': case 'E': - break m1; - default: - return 0.0f; - case '0': - } - } - - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - mantRead = true; - l: for (;;) { - if (mantDig < 9) { - mantDig++; - mant = mant * 10 + (current - '0'); - } else { - expAdj++; - } - current = read(); - switch (current) { - default: - break l; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - } - } - } - - if (current == '.') { - current = read(); - m2: switch (current) { - default: - case 'e': case 'E': - if (!mantRead) { - reportUnexpectedCharacterError( current ); - return 0.0f; - } - break; - - case '0': - if (mantDig == 0) { - l: for (;;) { - current = read(); - expAdj--; - switch (current) { - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - break l; - default: - if (!mantRead) { - return 0.0f; - } - break m2; - case '0': - } - } - } - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - l: for (;;) { - if (mantDig < 9) { - mantDig++; - mant = mant * 10 + (current - '0'); - expAdj--; - } - current = read(); - switch (current) { - default: - break l; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - } - } - } - } - - switch (current) { - case 'e': case 'E': - current = read(); - switch (current) { - default: - reportUnexpectedCharacterError( current ); - return 0f; - case '-': - expPos = false; - case '+': - current = read(); - switch (current) { - default: - reportUnexpectedCharacterError( current ); - return 0f; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - } - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - } - - en: switch (current) { - case '0': - l: for (;;) { - current = read(); - switch (current) { - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - break l; - default: - break en; - case '0': - } - } - - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - l: for (;;) { - if (expDig < 3) { - expDig++; - exp = exp * 10 + (current - '0'); - } - current = read(); - switch (current) { - default: - break l; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - } - } - } - default: - } - - if (!expPos) { - exp = -exp; - } - exp += expAdj; - if (!mantPos) { - mant = -mant; - } - - return buildFloat(mant, exp); - } - - private void reportUnexpectedCharacterError(char c) { - throw new RuntimeException("Unexpected char '" + c + "'."); - } - - /** - * Computes a float from mantissa and exponent. - */ - public static float buildFloat(int mant, int exp) { - if (exp < -125 || mant == 0) { - return 0.0f; - } - - if (exp >= 128) { - return (mant > 0) - ? Float.POSITIVE_INFINITY - : Float.NEGATIVE_INFINITY; - } - - if (exp == 0) { - return mant; - } - - if (mant >= (1 << 26)) { - mant++; // round up trailing bits if they will be dropped. - } - - return (float) ((exp > 0) ? mant * pow10[exp] : mant / pow10[-exp]); - } - - /** - * Array of powers of ten. Using double instead of float gives a tiny bit more precision. - */ - private static final double[] pow10 = new double[128]; - - static { - for (int i = 0; i < pow10.length; i++) { - pow10[i] = Math.pow(10, i); - } - } - - public float nextFloat() { - skipWhitespace(); - float f = parseFloat(); - skipNumberSeparator(); - return f; - } -} diff --git a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java deleted file mode 100644 index e5bc9c6..0000000 --- a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.larvalabs.svgandroid; - -import android.graphics.Picture; -import android.graphics.RectF; -import android.graphics.drawable.PictureDrawable; - -/* - - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ -/** - * Describes a vector Picture object, and optionally its bounds. - * - * @author Larva Labs, LLC - */ -public class SVG { - - /** - * The parsed Picture object. - */ - private Picture picture; - - /** - * These are the bounds for the SVG specified as a hidden "bounds" layer in the SVG. - */ - private RectF bounds; - - /** - * These are the estimated bounds of the SVG computed from the SVG elements while parsing. - * Note that this could be null if there was a failure to compute limits (ie. an empty SVG). - */ - private RectF limits = null; - - /** - * Construct a new SVG. - * @param picture the parsed picture object. - * @param bounds the bounds computed from the "bounds" layer in the SVG. - */ - SVG(Picture picture, RectF bounds) { - this.picture = picture; - this.bounds = bounds; - } - - /** - * Set the limits of the SVG, which are the estimated bounds computed by the parser. - * @param limits the bounds computed while parsing the SVG, may not be entirely accurate. - */ - void setLimits(RectF limits) { - this.limits = limits; - } - - /** - * Create a picture drawable from the SVG. - * @return the PictureDrawable. - */ - public PictureDrawable createPictureDrawable() { - return new PictureDrawable(picture); -// return new PictureDrawable(picture) { -// @Override -// public int getIntrinsicWidth() { -// if (bounds != null) { -// return (int) bounds.width(); -// } else if (limits != null) { -// return (int) limits.width(); -// } else { -// return -1; -// } -// } -// -// @Override -// public int getIntrinsicHeight() { -// if (bounds != null) { -// return (int) bounds.height(); -// } else if (limits != null) { -// return (int) limits.height(); -// } else { -// return -1; -// } -// } -// }; - } - - /** - * Get the parsed SVG picture data. - * @return the picture. - */ - public Picture getPicture() { - return picture; - } - - /** - * Gets the bounding rectangle for the SVG, if one was specified. - * @return rectangle representing the bounds. - */ - public RectF getBounds() { - return bounds; - } - - /** - * Gets the bounding rectangle for the SVG that was computed upon parsing. - * It may not be entirely accurate for certain curves or transformations, - * but is often better than nothing. - * @return rectangle representing the computed bounds. - */ - public RectF getLimits() { - return limits; - } -} diff --git a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParseException.java b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParseException.java deleted file mode 100644 index e13453d..0000000 --- a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParseException.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.larvalabs.svgandroid; - -/** - * Runtime exception thrown when there is a problem parsing an SVG. - * - * @author Larva Labs, LLC - */ -public class SVGParseException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public SVGParseException(String s) { - super(s); - } - - public SVGParseException(String s, Throwable throwable) { - super(s, throwable); - } - - public SVGParseException(Throwable throwable) { - super(throwable); - } -} diff --git a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java deleted file mode 100644 index db6fbfc..0000000 --- a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java +++ /dev/null @@ -1,1340 +0,0 @@ -package com.larvalabs.svgandroid; - -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.graphics.*; - -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.DefaultHandler; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/* - - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - */ - -/** - * Entry point for parsing SVG files for Android. - * Use one of the various static methods for parsing SVGs by resource, asset or input stream. - * Optionally, a single color can be searched and replaced in the SVG while parsing. - * You can also parse an svg path directly. - * - * @author Larva Labs, LLC - * @see #getSVGFromResource(android.content.res.Resources, int) - * @see #getSVGFromAsset(android.content.res.AssetManager, String) - * @see #getSVGFromString(String) - * @see #getSVGFromInputStream(java.io.InputStream) - * @see #parsePath(String) - */ -public class SVGParser { - - static final String TAG = "SVGAndroid"; - - /** - * Parse SVG data from an input stream. - * - * @param svgData the input stream, with SVG XML data in UTF-8 character encoding. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - */ - public static SVG getSVGFromInputStream(InputStream svgData) throws SVGParseException { - return SVGParser.parse(svgData, null, false); - } - - /** - * Parse SVG data from a string. - * - * @param svgData the string containing SVG XML data. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - */ - public static SVG getSVGFromString(String svgData) throws SVGParseException { - return SVGParser.parse(new ByteArrayInputStream(svgData.getBytes()), null, false); - } - - /** - * Parse SVG data from an Android application resource. - * - * @param resources the Android context resources. - * @param resId the ID of the raw resource SVG. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - */ - public static SVG getSVGFromResource(Resources resources, int resId) throws SVGParseException { - return SVGParser.parse(resources.openRawResource(resId), null, false); - } - - /** - * Parse SVG data from an Android application asset. - * - * @param assetMngr the Android asset manager. - * @param svgPath the path to the SVG file in the application's assets. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - * @throws IOException if there was a problem reading the file. - */ - public static SVG getSVGFromAsset(AssetManager assetMngr, String svgPath) throws SVGParseException, IOException { - InputStream inputStream = assetMngr.open(svgPath); - SVG svg = getSVGFromInputStream(inputStream); - inputStream.close(); - return svg; - } - - /** - * Parse SVG data from an input stream, replacing a single color with another color. - * - * @param svgData the input stream, with SVG XML data in UTF-8 character encoding. - * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - */ - public static SVG getSVGFromInputStream(InputStream svgData, - Map colorReplace) throws SVGParseException { - return SVGParser.parse(svgData, colorReplace, false); - } - - /** - * Parse SVG data from a string. - * - * @param svgData the string containing SVG XML data. - * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - */ - public static SVG getSVGFromString(String svgData, - HashMap colorReplace) throws SVGParseException { - return SVGParser.parse(new ByteArrayInputStream(svgData.getBytes()), colorReplace, false); - } - - /** - * Parse SVG data from an Android application resource. - * - * @param resources the Android context - * @param resId the ID of the raw resource SVG. - * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - */ - public static SVG getSVGFromResource(Resources resources, int resId, - HashMap colorReplace) throws SVGParseException { - return SVGParser.parse(resources.openRawResource(resId), colorReplace, false); - } - - /** - * Parse SVG data from an Android application asset. - * - * @param assetMngr the Android asset manager. - * @param svgPath the path to the SVG file in the application's assets. - * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. - * @return the parsed SVG. - * @throws SVGParseException if there is an error while parsing. - * @throws IOException if there was a problem reading the file. - */ - public static SVG getSVGFromAsset(AssetManager assetMngr, String svgPath, - Map colorReplace) throws SVGParseException, IOException { - InputStream inputStream = assetMngr.open(svgPath); - SVG svg = getSVGFromInputStream(inputStream, colorReplace); - inputStream.close(); - return svg; - } - - /** - * Parses a single SVG path and returns it as a android.graphics.Path object. - * An example path is M250,150L150,350L350,350Z, which draws a triangle. - * - * @param pathString the SVG path, see the specification here. - */ - public static Path parsePath(String pathString) { - return doPath(pathString); - } - - private static SVG parse(InputStream in, Map colorReplace, boolean whiteMode) throws SVGParseException { -// Util.debug("Parsing SVG..."); - try { -// long start = System.currentTimeMillis(); - SAXParserFactory spf = SAXParserFactory.newInstance(); - SAXParser sp = spf.newSAXParser(); - XMLReader xr = sp.getXMLReader(); - final Picture picture = new Picture(); - SVGHandler handler = new SVGHandler(picture); - handler.setColorSwap(colorReplace); - handler.setWhiteMode(whiteMode); - xr.setContentHandler(handler); - xr.parse(new InputSource(in)); -// Util.debug("Parsing complete in " + (System.currentTimeMillis() - start) + " millis."); - SVG result = new SVG(picture, handler.bounds); - // Skip bounds if it was an empty pic - if (!Float.isInfinite(handler.limits.top)) { - result.setLimits(handler.limits); - } - return result; - } catch (Exception e) { - throw new SVGParseException(e); - } - } - - private static NumberParse parseNumbers(String s) { - //Util.debug("Parsing numbers from: '" + s + "'"); - int n = s.length(); - int p = 0; - ArrayList numbers = new ArrayList<>(); - boolean skipChar = false; - for (int i = 1; i < n; i++) { - if (skipChar) { - skipChar = false; - continue; - } - char c = s.charAt(i); - switch (c) { - // This ends the parsing, as we are on the next element - case 'M': - case 'm': - case 'Z': - case 'z': - case 'L': - case 'l': - case 'H': - case 'h': - case 'V': - case 'v': - case 'C': - case 'c': - case 'S': - case 's': - case 'Q': - case 'q': - case 'T': - case 't': - case 'a': - case 'A': - case ')': { - String str = s.substring(p, i); - if (str.trim().length() > 0) { - //Util.debug(" Last: " + str); - Float f = Float.parseFloat(str); - numbers.add(f); - } - p = i; - return new NumberParse(numbers, p); - } - case '\n': - case '\t': - case ' ': - case ',': { - String str = s.substring(p, i); - // Just keep moving if multiple whitespace - if (str.trim().length() > 0) { - //Util.debug(" Next: " + str); - Float f = Float.parseFloat(str); - numbers.add(f); - if (c == '-') { - p = i; - } else { - p = i + 1; - skipChar = true; - } - } else { - p++; - } - break; - } - } - } - String last = s.substring(p); - if (last.length() > 0) { - //Util.debug(" Last: " + last); - try { - numbers.add(Float.parseFloat(last)); - } catch (NumberFormatException nfe) { - // Just white-space, forget it - } - p = s.length(); - } - return new NumberParse(numbers, p); - } - - private static Matrix parseTransform(String s) { - if (s.startsWith("matrix(")) { - NumberParse np = parseNumbers(s.substring("matrix(".length())); - if (np.numbers.size() == 6) { - Matrix matrix = new Matrix(); - matrix.setValues(new float[]{ - // Row 1 - np.numbers.get(0), - np.numbers.get(2), - np.numbers.get(4), - // Row 2 - np.numbers.get(1), - np.numbers.get(3), - np.numbers.get(5), - // Row 3 - 0, - 0, - 1, - }); - return matrix; - } - } else if (s.startsWith("translate(")) { - NumberParse np = parseNumbers(s.substring("translate(".length())); - if (np.numbers.size() > 0) { - float tx = np.numbers.get(0); - float ty = 0; - if (np.numbers.size() > 1) { - ty = np.numbers.get(1); - } - Matrix matrix = new Matrix(); - matrix.postTranslate(tx, ty); - return matrix; - } - } else if (s.startsWith("scale(")) { - NumberParse np = parseNumbers(s.substring("scale(".length())); - if (np.numbers.size() > 0) { - float sx = np.numbers.get(0); - float sy = 0; - if (np.numbers.size() > 1) { - sy = np.numbers.get(1); - } - Matrix matrix = new Matrix(); - matrix.postScale(sx, sy); - return matrix; - } - } else if (s.startsWith("skewX(")) { - NumberParse np = parseNumbers(s.substring("skewX(".length())); - if (np.numbers.size() > 0) { - float angle = np.numbers.get(0); - Matrix matrix = new Matrix(); - matrix.postSkew((float) Math.tan(angle), 0); - return matrix; - } - } else if (s.startsWith("skewY(")) { - NumberParse np = parseNumbers(s.substring("skewY(".length())); - if (np.numbers.size() > 0) { - float angle = np.numbers.get(0); - Matrix matrix = new Matrix(); - matrix.postSkew(0, (float) Math.tan(angle)); - return matrix; - } - } else if (s.startsWith("rotate(")) { - NumberParse np = parseNumbers(s.substring("rotate(".length())); - if (np.numbers.size() > 0) { - float angle = np.numbers.get(0); - float cx = 0; - float cy = 0; - if (np.numbers.size() > 2) { - cx = np.numbers.get(1); - cy = np.numbers.get(2); - } - Matrix matrix = new Matrix(); - matrix.postTranslate(cx, cy); - matrix.postRotate(angle); - matrix.postTranslate(-cx, -cy); - return matrix; - } - } - return null; - } - - /** - * This is where the hard-to-parse paths are handled. - * Uppercase rules are absolute positions, lowercase are relative. - * Types of path rules: - *

- *

    - *
  1. M/m - (x y)+ - Move to (without drawing) - *
  2. Z/z - (no params) - Close path (back to starting point) - *
  3. L/l - (x y)+ - Line to - *
  4. H/h - x+ - Horizontal ine to - *
  5. V/v - y+ - Vertical line to - *
  6. C/c - (x1 y1 x2 y2 x y)+ - Cubic bezier to - *
  7. S/s - (x2 y2 x y)+ - Smooth cubic bezier to (shorthand that assumes the x2, y2 from previous C/S is the x1, y1 of this bezier) - *
  8. Q/q - (x1 y1 x y)+ - Quadratic bezier to - *
  9. T/t - (x y)+ - Smooth quadratic bezier to (assumes previous control point is "reflection" of last one w.r.t. to current point) - *
- *

- * Numbers are separate by whitespace, comma or nothing at all (!) if they are self-delimiting, (ie. begin with a - sign) - * - * @param s the path string from the XML - */ - private static Path doPath(String s) { - int n = s.length(); - ParserHelper ph = new ParserHelper(s, 0); - ph.skipWhitespace(); - Path p = new Path(); - float lastX = 0; - float lastY = 0; - float lastX1 = 0; - float lastY1 = 0; - float subPathStartX = 0; - float subPathStartY = 0; - char prevCmd = 0; - while (ph.pos < n) { - char cmd = s.charAt(ph.pos); - switch (cmd) { - case '-': - case '+': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - boolean handled = true; - switch (prevCmd) { - case 'm': - cmd = 'l'; - break; - case 'M': - cmd = 'L'; - break; - case 'l': case 'L': - case 'c': case 'C': - case 's': case 'S': - case 'q': case 'Q': - case 't': case 'T': - cmd = prevCmd; - break; - default: - handled = false; - break; - } - if (handled) - break; - } - default: { - ph.advance(); - prevCmd = cmd; - } - } - - boolean wasCurve = false; - switch (cmd) { - case 'M': - case 'm': { - float x = ph.nextFloat(); - float y = ph.nextFloat(); - if (cmd == 'm') { - subPathStartX += x; - subPathStartY += y; - p.rMoveTo(x, y); - lastX += x; - lastY += y; - } else { - subPathStartX = x; - subPathStartY = y; - p.moveTo(x, y); - lastX = x; - lastY = y; - } - break; - } - case 'Z': - case 'z': { - p.close(); - p.moveTo(subPathStartX, subPathStartY); - lastX = subPathStartX; - lastY = subPathStartY; - lastX1 = subPathStartX; - lastY1 = subPathStartY; - wasCurve = true; - break; - } - case 'L': - case 'l': { - float x = ph.nextFloat(); - float y = ph.nextFloat(); - if (cmd == 'l') { - p.rLineTo(x, y); - lastX += x; - lastY += y; - } else { - p.lineTo(x, y); - lastX = x; - lastY = y; - } - break; - } - case 'H': - case 'h': { - float x = ph.nextFloat(); - if (cmd == 'h') { - p.rLineTo(x, 0); - lastX += x; - } else { - p.lineTo(x, lastY); - lastX = x; - } - break; - } - case 'V': - case 'v': { - float y = ph.nextFloat(); - if (cmd == 'v') { - p.rLineTo(0, y); - lastY += y; - } else { - p.lineTo(lastX, y); - lastY = y; - } - break; - } - case 'C': - case 'c': { - wasCurve = true; - float x1 = ph.nextFloat(); - float y1 = ph.nextFloat(); - float x2 = ph.nextFloat(); - float y2 = ph.nextFloat(); - float x = ph.nextFloat(); - float y = ph.nextFloat(); - if (cmd == 'c') { - x1 += lastX; - x2 += lastX; - x += lastX; - y1 += lastY; - y2 += lastY; - y += lastY; - } - p.cubicTo(x1, y1, x2, y2, x, y); - lastX1 = x2; - lastY1 = y2; - lastX = x; - lastY = y; - break; - } - case 'S': - case 's': { - wasCurve = true; - float x2 = ph.nextFloat(); - float y2 = ph.nextFloat(); - float x = ph.nextFloat(); - float y = ph.nextFloat(); - if (cmd == 's') { - x2 += lastX; - x += lastX; - y2 += lastY; - y += lastY; - } - float x1 = 2 * lastX - lastX1; - float y1 = 2 * lastY - lastY1; - p.cubicTo(x1, y1, x2, y2, x, y); - lastX1 = x2; - lastY1 = y2; - lastX = x; - lastY = y; - break; - } - case 'Q': - case 'q': { - wasCurve = true; - float x1 = ph.nextFloat(); - float y1 = ph.nextFloat(); - float x = ph.nextFloat(); - float y = ph.nextFloat(); - if (cmd == 'q') { - x1 += lastX; - x += lastX; - y1 += lastY; - y += lastY; - } - p.quadTo(x1, y1, x, y); - lastX1 = x1; - lastY1 = y1; - lastX = x; - lastY = y; - break; - } - case 'T': - case 't': { - wasCurve = true; - float x = ph.nextFloat(); - float y = ph.nextFloat(); - if (cmd == 't') { - x += lastX; - y += lastY; - } - float x1 = 2 * lastX - lastX1; - float y1 = 2 * lastY - lastY1; - p.quadTo(x1, y1, x, y); - lastX1 = x1; - lastY1 = y1; - lastX = x; - lastY = y; - break; - } - case 'A': - case 'a': { - float rx = ph.nextFloat(); - float ry = ph.nextFloat(); - float theta = ph.nextFloat(); - int largeArc = (int) ph.nextFloat(); - int sweepArc = (int) ph.nextFloat(); - float x = ph.nextFloat(); - float y = ph.nextFloat(); - drawArc(p, lastX, lastY, x, y, rx, ry, theta, largeArc, sweepArc); - lastX = x; - lastY = y; - break; - } - } - if (!wasCurve) { - lastX1 = lastX; - lastY1 = lastY; - } - ph.skipWhitespace(); - } - return p; - } - - private static void drawArc(Path p, float lastX, float lastY, float x, float y, float rx, float ry, float theta, int largeArc, int sweepArc) { - // todo - not implemented yet, may be very hard to do using Android drawing facilities. - } - - private static NumberParse getNumberParseAttr(String name, Attributes attributes) { - int n = attributes.getLength(); - for (int i = 0; i < n; i++) { - if (attributes.getLocalName(i).equals(name)) { - return parseNumbers(attributes.getValue(i)); - } - } - return null; - } - - private static String getStringAttr(String name, Attributes attributes) { - int n = attributes.getLength(); - for (int i = 0; i < n; i++) { - if (attributes.getLocalName(i).equals(name)) { - return attributes.getValue(i); - } - } - return null; - } - - private static Float getFloatAttr(String name, Attributes attributes) { - return getFloatAttr(name, attributes, null); - } - - private static Float getFloatAttr(String name, Attributes attributes, Float defaultValue) { - String v = getStringAttr(name, attributes); - if (v == null) { - return defaultValue; - } else { - if (v.endsWith("px") || v.endsWith("pt")) { - v = v.substring(0, v.length() - 2); - } -// Log.d(TAG, "Float parsing '" + name + "=" + v + "'"); - return Float.parseFloat(v); - } - } - - /* - private static Integer getHexAttr(String name, Attributes attributes) { - String v = getStringAttr(name, attributes); - //Util.debug("Hex parsing '" + name + "=" + v + "'"); - if (v == null) { - return null; - } else { - try { - return Integer.parseInt(v.substring(1), 16); - } catch (NumberFormatException nfe) { - // todo - parse word-based color here - return null; - } - } - } - */ - - private static class NumberParse { - private ArrayList numbers; -// private int nextCmd; - - public NumberParse(ArrayList numbers, int nextCmd) { - this.numbers = numbers; -// this.nextCmd = nextCmd; - } - - /* - public int getNextCmd() { - return nextCmd; - } - */ - - /* - public float getNumber(int index) { - return numbers.get(index); - } - */ - } - - private static class Gradient { - String id; - String xlink; - boolean isLinear; - float x1, y1, x2, y2; - float x, y, radius; - ArrayList positions = new ArrayList<>(); - ArrayList colors = new ArrayList<>(); - Matrix matrix = null; - - public Gradient createChild(Gradient g) { - Gradient child = new Gradient(); - child.id = g.id; - child.xlink = id; - child.isLinear = g.isLinear; - child.x1 = g.x1; - child.x2 = g.x2; - child.y1 = g.y1; - child.y2 = g.y2; - child.x = g.x; - child.y = g.y; - child.radius = g.radius; - child.positions = positions; - child.colors = colors; - child.matrix = matrix; - if (g.matrix != null) { - if (matrix == null) { - child.matrix = g.matrix; - } else { - Matrix m = new Matrix(matrix); - m.preConcat(g.matrix); - child.matrix = m; - } - } - return child; - } - } - - private static class StyleSet { - HashMap styleMap = new HashMap<>(); - - private StyleSet(String string) { - String[] styles = string.split(";"); - for (String s : styles) { - String[] style = s.split(":"); - if (style.length == 2) { - styleMap.put(style[0], style[1]); - } - } - } - - public String getStyle(String name) { - return styleMap.get(name); - } - } - - private static class Properties { - StyleSet styles = null; - Attributes atts; - - private Properties(Attributes atts) { - this.atts = atts; - String styleAttr = getStringAttr("style", atts); - if (styleAttr != null) { - styles = new StyleSet(styleAttr); - } - } - - public String getAttr(String name) { - String v = null; - if (styles != null) { - v = styles.getStyle(name); - } - if (v == null) { - v = getStringAttr(name, atts); - } - return v; - } - - public String getString(String name) { - return getAttr(name); - } - - public Integer getHex(String name) { - String v = getAttr(name); - if (v == null || !v.startsWith("#")) { - return null; - } else { - try { - return Integer.parseInt(v.substring(1), 16); - } catch (NumberFormatException nfe) { - // todo - parse word-based color here - return null; - } - } - } - - /* - public Float getFloat(String name, float defaultValue) { - Float v = getFloat(name); - if (v == null) { - return defaultValue; - } else { - return v; - } - } - */ - - public Float getFloat(String name, boolean handleUnits) { - String v = getAttr(name); - if (v == null) { - return null; - } else { - try { - if (handleUnits) - if (v.endsWith("px") || v.endsWith("pt")) - v = v.substring(0, v.length() - 2); - return Float.parseFloat(v); - } catch (NumberFormatException nfe) { - return null; - } - } - } - } - - private static class SVGHandler extends DefaultHandler { - - Picture picture; - Canvas canvas; - Paint paint; - // Scratch rect (so we aren't constantly making new ones) - RectF rect = new RectF(); - RectF bounds = null; - RectF limits = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - - Map colorReplace = null; - - boolean whiteMode = false; - - HashMap gradientMap = new HashMap<>(); - HashMap gradientRefMap = new HashMap<>(); - Gradient gradient = null; - - private SVGHandler(Picture picture) { - this.picture = picture; - paint = new Paint(); - paint.setAntiAlias(true); - } - - public void setColorSwap(Map colorReplace) { - this.colorReplace = colorReplace; - } - - public void setWhiteMode(boolean whiteMode) { - this.whiteMode = whiteMode; - } - - @Override - public void startDocument() throws SAXException { - // Set up prior to parsing a doc - } - - @Override - public void endDocument() throws SAXException { - // Clean up after parsing a doc - } - - private boolean doFill(Properties atts, HashMap gradients) { - if ("none".equals(atts.getString("display"))) { - return false; - } - if (whiteMode) { - paint.setStyle(Paint.Style.FILL); - paint.setColor(0xFFFFFFFF); - return true; - } - String fillString = atts.getString("fill"); - if (fillString != null && fillString.startsWith("url(#")) { - // It's a gradient fill, look it up in our map - String id = fillString.substring("url(#".length(), fillString.length() - 1); - Shader shader = gradients.get(id); - if (shader != null) { - //Util.debug("Found shader!"); - paint.setShader(shader); - paint.setStyle(Paint.Style.FILL); - return true; - } else { - //Util.debug("Didn't find shader!"); - return false; - } - } else { - paint.setShader(null); - Integer color = atts.getHex("fill"); - if (color != null) { - doColor(atts, color, true); - paint.setStyle(Paint.Style.FILL); - return true; - } else if (atts.getString("fill") == null && atts.getString("stroke") == null) { - // Default is black fill - paint.setStyle(Paint.Style.FILL); - paint.setColor(0xFF000000); - return true; - } - } - return false; - } - - private boolean doStroke(Properties atts, HashMap gradients) { - if (whiteMode) { - // Never stroke in white mode - return false; - } - if ("none".equals(atts.getString("display"))) { - return false; - } - String strokeString = atts.getString("stroke"); - if (strokeString == null) - return false; - if (strokeString.startsWith("url(#")) { - String id = strokeString.substring("url(#".length(), strokeString.length() - 1); - Shader shader = gradients.get(id); - if (shader == null) - return false; - paint.setShader(shader); - } else { - Integer color = atts.getHex("stroke"); - if (color == null) - return false; - paint.setShader(null); - doColor(atts, color, false); - } - - // Check for other stroke attributes - Float width = atts.getFloat("stroke-width", true); - // Set defaults - - if (width != null) { - paint.setStrokeWidth(width); - } - String linecap = atts.getString("stroke-linecap"); - if ("round".equals(linecap)) { - paint.setStrokeCap(Paint.Cap.ROUND); - } else if ("square".equals(linecap)) { - paint.setStrokeCap(Paint.Cap.SQUARE); - } else if ("butt".equals(linecap)) { - paint.setStrokeCap(Paint.Cap.BUTT); - } - String linejoin = atts.getString("stroke-linejoin"); - if ("miter".equals(linejoin)) { - paint.setStrokeJoin(Paint.Join.MITER); - } else if ("round".equals(linejoin)) { - paint.setStrokeJoin(Paint.Join.ROUND); - } else if ("bevel".equals(linejoin)) { - paint.setStrokeJoin(Paint.Join.BEVEL); - } - paint.setStyle(Paint.Style.STROKE); - return true; - } - - private Gradient doGradient(boolean isLinear, Attributes atts) { - Gradient gradient = new Gradient(); - gradient.id = getStringAttr("id", atts); - gradient.isLinear = isLinear; - if (isLinear) { - gradient.x1 = getFloatAttr("x1", atts, 0f); - gradient.x2 = getFloatAttr("x2", atts, 0f); - gradient.y1 = getFloatAttr("y1", atts, 0f); - gradient.y2 = getFloatAttr("y2", atts, 0f); - } else { - gradient.x = getFloatAttr("cx", atts, 0f); - gradient.y = getFloatAttr("cy", atts, 0f); - gradient.radius = getFloatAttr("r", atts, 0f); - } - String transform = getStringAttr("gradientTransform", atts); - if (transform != null) { - gradient.matrix = parseTransform(transform); - } - String xlink = getStringAttr("href", atts); - if (xlink != null) { - if (xlink.startsWith("#")) { - xlink = xlink.substring(1); - } - gradient.xlink = xlink; - } - return gradient; - } - - private void doColor(Properties atts, Integer color, boolean fillMode) { - int c = (0xFFFFFF & color) | 0xFF000000; - int opac = -1; - if (colorReplace != null) { - Integer replaceColor = colorReplace.get(c); - if (replaceColor != null) { - c = replaceColor; - opac = replaceColor >>> 24; - } - } - if (opac == -1) { - Float opacity = atts.getFloat("opacity", false); - if (opacity == null) { - opacity = atts.getFloat(fillMode ? "fill-opacity" : "stroke-opacity", false); - } - if (opacity == null) { - opac = 255; - } else { - opac = (int) (255 * opacity); - } - } - paint.setColor(c); - paint.setAlpha(opac); - } - - private boolean hidden = false; - private int hiddenLevel = 0; - private boolean boundsMode = false; - - private void doLimits(float x, float y) { - if (x < limits.left) { - limits.left = x; - } - if (x > limits.right) { - limits.right = x; - } - if (y < limits.top) { - limits.top = y; - } - if (y > limits.bottom) { - limits.bottom = y; - } - } - - private void doLimits(float x, float y, float width, float height) { - doLimits(x, y); - doLimits(x + width, y + height); - } - - private void doLimits(Path path) { - path.computeBounds(rect, false); - doLimits(rect.left, rect.top); - doLimits(rect.right, rect.bottom); - } - - private void pushTransform(Attributes atts) { - final String transform = getStringAttr("transform", atts); - canvas.save(); - if (transform != null) { - final Matrix matrix = parseTransform(transform); - canvas.concat(matrix); - } - } - - private void popTransform() { - canvas.restore(); - } - - @Override - public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { - // Reset paint opacity - paint.setAlpha(255); - // Ignore everything but rectangles in bounds mode - if (boundsMode) { - if (localName.equals("rect")) { - Float x = getFloatAttr("x", atts); - if (x == null) { - x = 0f; - } - Float y = getFloatAttr("y", atts); - if (y == null) { - y = 0f; - } - Float width = getFloatAttr("width", atts); - Float height = getFloatAttr("height", atts); - bounds = new RectF(x, y, x + width, y + height); - } - return; - } - if (localName.equals("svg")) { - int width = (int) Math.ceil(getFloatAttr("width", atts)); - int height = (int) Math.ceil(getFloatAttr("height", atts)); - canvas = picture.beginRecording(width, height); - } else if (localName.equals("defs")) { - // Ignore - } else if (localName.equals("linearGradient")) { - gradient = doGradient(true, atts); - } else if (localName.equals("radialGradient")) { - gradient = doGradient(false, atts); - } else if (localName.equals("stop")) { - if (gradient != null) { - float offset = getFloatAttr("offset", atts); - String styles = getStringAttr("style", atts); - StyleSet styleSet = new StyleSet(styles); - String colorStyle = styleSet.getStyle("stop-color"); - int color = Color.BLACK; - if (colorStyle != null) { - if (colorStyle.startsWith("#")) { - color = Integer.parseInt(colorStyle.substring(1), 16); - } else { - color = Integer.parseInt(colorStyle, 16); - } - } - String opacityStyle = styleSet.getStyle("stop-opacity"); - if (opacityStyle != null) { - float alpha = Float.parseFloat(opacityStyle); - int alphaInt = Math.round(255 * alpha); - color |= (alphaInt << 24); - } else { - color |= 0xFF000000; - } - gradient.positions.add(offset); - gradient.colors.add(color); - } - } else if (localName.equals("g")) { - // Check to see if this is the "bounds" layer - if ("bounds".equalsIgnoreCase(getStringAttr("id", atts))) { - boundsMode = true; - } - if (hidden) { - hiddenLevel++; - //Util.debug("Hidden up: " + hiddenLevel); - } - // Go in to hidden mode if display is "none" - if ("none".equals(getStringAttr("display", atts))) { - if (!hidden) { - hidden = true; - hiddenLevel = 1; - //Util.debug("Hidden up: " + hiddenLevel); - } - } - pushTransform(atts); - } else if (!hidden && localName.equals("rect")) { - Float x = getFloatAttr("x", atts); - if (x == null) { - x = 0f; - } - Float y = getFloatAttr("y", atts); - if (y == null) { - y = 0f; - } - Float width = getFloatAttr("width", atts); - Float height = getFloatAttr("height", atts); - Float rx = getFloatAttr("rx", atts); - Float ry = getFloatAttr("ry", atts); - if (rx == null) { - if (ry == null) - rx = ry = 0f; - else - rx = ry; - } else { - if (ry == null) - ry = rx; - } - if (rx > width / 2) rx = width * 0.5f; - if (ry > height / 2) ry = height * 0.5f; - pushTransform(atts); - Properties props = new Properties(atts); - if (doFill(props, gradientMap)) { - doLimits(x, y, width, height); - if (rx > 0 && ry > 0) - canvas.drawRoundRect(new RectF(x, y, x + width, y + height), rx, ry, paint); - else - canvas.drawRect(x, y, x + width, y + height, paint); - } - if (doStroke(props, gradientMap)) { - if (rx > 0 && ry > 0) - canvas.drawRoundRect(new RectF(x, y, x + width, y + height), rx, ry, paint); - else - canvas.drawRect(x, y, x + width, y + height, paint); - } - popTransform(); - } else if (!hidden && localName.equals("line")) { - Float x1 = getFloatAttr("x1", atts); - Float x2 = getFloatAttr("x2", atts); - Float y1 = getFloatAttr("y1", atts); - Float y2 = getFloatAttr("y2", atts); - Properties props = new Properties(atts); - if (doStroke(props, gradientMap)) { - pushTransform(atts); - doLimits(x1, y1); - doLimits(x2, y2); - canvas.drawLine(x1, y1, x2, y2, paint); - popTransform(); - } - } else if (!hidden && localName.equals("circle")) { - Float centerX = getFloatAttr("cx", atts); - Float centerY = getFloatAttr("cy", atts); - Float radius = getFloatAttr("r", atts); - if (centerX != null && centerY != null && radius != null) { - pushTransform(atts); - Properties props = new Properties(atts); - if (doFill(props, gradientMap)) { - doLimits(centerX - radius, centerY - radius); - doLimits(centerX + radius, centerY + radius); - canvas.drawCircle(centerX, centerY, radius, paint); - } - if (doStroke(props, gradientMap)) { - canvas.drawCircle(centerX, centerY, radius, paint); - } - popTransform(); - } - } else if (!hidden && localName.equals("ellipse")) { - Float centerX = getFloatAttr("cx", atts); - Float centerY = getFloatAttr("cy", atts); - Float radiusX = getFloatAttr("rx", atts); - Float radiusY = getFloatAttr("ry", atts); - if (centerX != null && centerY != null && radiusX != null && radiusY != null) { - pushTransform(atts); - Properties props = new Properties(atts); - rect.set(centerX - radiusX, centerY - radiusY, centerX + radiusX, centerY + radiusY); - if (doFill(props, gradientMap)) { - doLimits(centerX - radiusX, centerY - radiusY); - doLimits(centerX + radiusX, centerY + radiusY); - canvas.drawOval(rect, paint); - } - if (doStroke(props, gradientMap)) { - canvas.drawOval(rect, paint); - } - popTransform(); - } - } else if (!hidden && (localName.equals("polygon") || localName.equals("polyline"))) { - NumberParse numbers = getNumberParseAttr("points", atts); - if (numbers != null) { - Path p = new Path(); - ArrayList points = numbers.numbers; - if (points.size() > 1) { - pushTransform(atts); - Properties props = new Properties(atts); - p.moveTo(points.get(0), points.get(1)); - for (int i = 2; i < points.size(); i += 2) { - float x = points.get(i); - float y = points.get(i + 1); - p.lineTo(x, y); - } - // Don't close a polyline - if (localName.equals("polygon")) { - p.close(); - } - if (doFill(props, gradientMap)) { - doLimits(p); - canvas.drawPath(p, paint); - } - if (doStroke(props, gradientMap)) { - canvas.drawPath(p, paint); - } - popTransform(); - } - } - } else if (!hidden && localName.equals("path")) { - Path p = doPath(getStringAttr("d", atts)); - pushTransform(atts); - Properties props = new Properties(atts); - if (doFill(props, gradientMap)) { - doLimits(p); - canvas.drawPath(p, paint); - } - if (doStroke(props, gradientMap)) { - canvas.drawPath(p, paint); - } - popTransform(); - } else if (!hidden) { -// Log.d(TAG, "UNRECOGNIZED SVG COMMAND: " + localName); - } - } - - @Override - public void characters(char ch[], int start, int length) { - // no-op - } - - @Override - public void endElement(String namespaceURI, String localName, String qName) - throws SAXException { - if (localName.equals("svg")) { - picture.endRecording(); - } else if (localName.equals("linearGradient")) { - if (gradient.id != null) { - if (gradient.xlink != null) { - Gradient parent = gradientRefMap.get(gradient.xlink); - if (parent != null) { - gradient = parent.createChild(gradient); - } - } - int[] colors = new int[gradient.colors.size()]; - for (int i = 0; i < colors.length; i++) { - colors[i] = gradient.colors.get(i); - } - float[] positions = new float[gradient.positions.size()]; - for (int i = 0; i < positions.length; i++) { - positions[i] = gradient.positions.get(i); - } - LinearGradient g = new LinearGradient(gradient.x1, gradient.y1, gradient.x2, gradient.y2, colors, positions, Shader.TileMode.CLAMP); - if (gradient.matrix != null) { - g.setLocalMatrix(gradient.matrix); - } - gradientMap.put(gradient.id, g); - gradientRefMap.put(gradient.id, gradient); - } - } else if (localName.equals("radialGradient")) { - if (gradient.id != null) { - if (gradient.xlink != null) { - Gradient parent = gradientRefMap.get(gradient.xlink); - if (parent != null) { - gradient = parent.createChild(gradient); - } - } - int[] colors = new int[gradient.colors.size()]; - for (int i = 0; i < colors.length; i++) { - colors[i] = gradient.colors.get(i); - } - float[] positions = new float[gradient.positions.size()]; - for (int i = 0; i < positions.length; i++) { - positions[i] = gradient.positions.get(i); - } - if (gradient.xlink != null) { - Gradient parent = gradientRefMap.get(gradient.xlink); - if (parent != null) { - gradient = parent.createChild(gradient); - } - } - RadialGradient g = new RadialGradient(gradient.x, gradient.y, gradient.radius, colors, positions, Shader.TileMode.CLAMP); - if (gradient.matrix != null) { - g.setLocalMatrix(gradient.matrix); - } - gradientMap.put(gradient.id, g); - gradientRefMap.put(gradient.id, gradient); - } - } else if (localName.equals("g")) { - if (boundsMode) { - boundsMode = false; - } - // Break out of hidden mode - if (hidden) { - hiddenLevel--; - //Util.debug("Hidden down: " + hiddenLevel); - if (hiddenLevel == 0) { - hidden = false; - } - } - // Clear gradient map - gradientMap.clear(); - popTransform(); - } - } - } -} diff --git a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/package-info.java b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/package-info.java deleted file mode 100644 index 7dccb4f..0000000 --- a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Provides a mechanism to parse a limited version of SVG Basic 1.1 files in to android.graphics.Picture - * objects. - * - * This allows vector graphics files to be saved out of illustration software (such as Adobe Illustrator) as SVG Basic - * and then used directly in an Android app. The android.graphics.Picture is a very optimized and - * convenient vector graphics class. Performance is very good on a wide array of Android devices. - *

- * Note that only SVG features that can be directly converted in to Android graphics calls are supported. - * The following SVG Basic 1.1 features are not supported and will be ignored by the parser: - *

    - *
  • All text and font features. - *
  • Styles. - *
  • Symbols, conditional processing. - *
  • Patterns. - *
  • Masks, filters and views. - *
  • Interactivity, linking, scripting and animation. - *
- * Even with the above features missing, users will find that most Illustrator drawings will render perfectly well on - * Android with this library. - * - * See the {@link com.larvalabs.svgandroid.SVGParser SVGParser} class for instructions on how to use the parser. - * - * @see com.larvalabs.svgandroid.SVGParser - */ -package com.larvalabs.svgandroid; diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java index 81ef19a..c7a43b1 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java @@ -75,10 +75,10 @@ import tourguide.tourguide.Sequence; import tourguide.tourguide.ToolTip; import tourguide.tourguide.TourGuide; +import com.caverock.androidsvg.SVG; +import com.caverock.androidsvg.SVGParseException; import com.kalab.chess.enginesupport.ChessEngine; import com.kalab.chess.enginesupport.ChessEngineResolver; -import com.larvalabs.svgandroid.SVG; -import com.larvalabs.svgandroid.SVGParser; import android.Manifest; import android.annotation.SuppressLint; @@ -1458,7 +1458,11 @@ public class DroidFish extends Activity bHeight = bHeight * 3 / 2; } } - SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.touch); + SVG svg = null; + try { + svg = SVG.getFromResource(getResources(), R.raw.touch); + } catch (SVGParseException ignore) { + } setButtonData(custom1Button, bWidth, bHeight, custom1ButtonActions.getIcon(), svg); setButtonData(custom2Button, bWidth, bHeight, custom2ButtonActions.getIcon(), svg); setButtonData(custom3Button, bWidth, bHeight, custom3ButtonActions.getIcon(), svg); @@ -1470,7 +1474,11 @@ public class DroidFish extends Activity @SuppressWarnings("deprecation") private void setButtonData(ImageButton button, int bWidth, int bHeight, int svgResId, SVG touched) { - SVG svg = SVGParser.getSVGFromResource(getResources(), svgResId); + SVG svg = null; + try { + svg = SVG.getFromResource(getResources(), svgResId); + } catch (SVGParseException ignore) { + } button.setBackgroundDrawable(new SVGPictureDrawable(svg)); StateListDrawable sld = new StateListDrawable(); diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/PieceSet.java b/DroidFishApp/src/main/java/org/petero/droidfish/PieceSet.java index fd2451b..7525996 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/PieceSet.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/PieceSet.java @@ -3,10 +3,16 @@ package org.petero.droidfish; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import com.larvalabs.svgandroid.SVG; -import com.larvalabs.svgandroid.SVGParser; +import com.caverock.androidsvg.SVG; +import com.caverock.androidsvg.SVGParseException; import org.petero.droidfish.gamelogic.Piece; @@ -51,17 +57,20 @@ public class PieceSet { nameToPieceType.put("bn.svg", Piece.BKNIGHT); nameToPieceType.put("bp.svg", Piece.BPAWN); - parseSvgData(cachedWhiteColor, cachedBlackColor); + parseSvgData(); } /** Re-parse SVG data if piece properties have changed. */ final void readPrefs(SharedPreferences settings) { + boolean modified = false; // FIXME!! check for new piece set + if (modified) + parseSvgData(); + ColorTheme ct = ColorTheme.instance(); int whiteColor = ct.getColor(ColorTheme.BRIGHT_PIECE); int blackColor = ct.getColor(ColorTheme.DARK_PIECE); - if (whiteColor != cachedWhiteColor || blackColor != cachedBlackColor) { + if (modified || whiteColor != cachedWhiteColor || blackColor != cachedBlackColor) { recycleBitmaps(); - parseSvgData(whiteColor, blackColor); cachedWhiteColor = whiteColor; cachedBlackColor = blackColor; cachedSquareSize = -1; @@ -78,10 +87,7 @@ public class PieceSet { return bitmapTable[pType]; } - private void parseSvgData(int whiteColor, int blackColor) { - HashMap colorReplace = new HashMap<>(); - colorReplace.put(0xffffffff, whiteColor); - colorReplace.put(0xff000000, blackColor); + private void parseSvgData() { try { ZipInputStream zis = getZipStream(); ZipEntry entry; @@ -97,7 +103,10 @@ public class PieceSet { bos.write(buf, 0, len); buf = bos.toByteArray(); ByteArrayInputStream bis = new ByteArrayInputStream(buf); - svgTable[pType] = SVGParser.getSVGFromInputStream(bis, colorReplace); + try { + svgTable[pType] = SVG.getFromInputStream(bis); + } catch (SVGParseException ignore) { + } } } zis.closeEntry(); @@ -123,14 +132,65 @@ public class PieceSet { } private void createBitmaps(int sqSize) { + Paint colorPaint = new Paint(); + { + float[] f = new float[3]; + float[] o = new float[3]; + for (int i = 0; i < 3; i++) { + int shift = 16 - i * 8; + int w = (cachedWhiteColor >>> shift) & 0xff; + int b = (cachedBlackColor >>> shift) & 0xff; + o[i] = b; + f[i] = (w - b) / (float)255; + } + float[] cm = new float[] { + f[0], 0 , 0 , 0 , o[0], + 0 , f[1], 0 , 0 , o[1], + 0 , 0 , f[2], 0 , o[2], + 0 , 0 , 0 , 1 , 0 + }; + colorPaint.setColorFilter(new ColorMatrixColorFilter(cm)); + } + + Paint alphaPaint = null; + int wAlpha = cachedWhiteColor >>> 24; + int bAlpha = cachedBlackColor >>> 24; + if (wAlpha != 0xff || bAlpha != 0xff) { + float o = bAlpha; + float k = (wAlpha - bAlpha) / (float)255; + float kr = 0.299f, kg = 0.587f, kb = 0.114f; + float[] cm = new float[] { + 0 , 0 , 0 , 0 , 255, + 0 , 0 , 0 , 0 , 255, + 0 , 0 , 0 , 0 , 255, + kr*k, kg*k, kb*k, 0 , o + }; + alphaPaint = new Paint(); + alphaPaint.setColorFilter(new ColorMatrixColorFilter(cm)); + alphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); + } + + Bitmap svgBM = Bitmap.createBitmap(sqSize, sqSize, Bitmap.Config.ARGB_8888); + Matrix scaleMat = new Matrix(); + for (int i = 0; i < Piece.nPieceTypes; i++) { SVG svg = svgTable[i]; if (svg != null) { + svgBM.eraseColor(Color.TRANSPARENT); + Canvas canvas = new Canvas(svgBM); + canvas.drawPicture(svg.renderToPicture(), new Rect(0, 0, sqSize, sqSize)); + Bitmap bm = Bitmap.createBitmap(sqSize, sqSize, Bitmap.Config.ARGB_8888); - Canvas bmCanvas = new Canvas(bm); - bmCanvas.drawPicture(svg.getPicture(), new Rect(0, 0, sqSize, sqSize)); + canvas = new Canvas(bm); + canvas.drawBitmap(svgBM, scaleMat, colorPaint); + + if (alphaPaint != null) + canvas.drawBitmap(svgBM, scaleMat, alphaPaint); + bitmapTable[i] = bm; } } + + svgBM.recycle(); } } diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/SVGPictureDrawable.java b/DroidFishApp/src/main/java/org/petero/droidfish/SVGPictureDrawable.java index 0e371a2..fe2a258 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/SVGPictureDrawable.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/SVGPictureDrawable.java @@ -18,14 +18,14 @@ package org.petero.droidfish; -import com.larvalabs.svgandroid.SVG; - import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.PictureDrawable; +import com.caverock.androidsvg.SVG; + /** * Like PictureDrawable but scales the picture according to current drawing bounds. */ @@ -38,19 +38,18 @@ public class SVGPictureDrawable extends PictureDrawable { private Bitmap cachedBitmap; public SVGPictureDrawable(SVG svg) { - super(svg.getPicture()); - RectF bounds = svg.getBounds(); - RectF limits = svg.getLimits(); - if (bounds != null) { - iWidth = (int)bounds.width(); - iHeight = (int)bounds.height(); - } else if (limits != null) { - iWidth = (int)limits.width(); - iHeight = (int)limits.height(); - } else { - iWidth = -1; - iHeight = -1; + super(svg.renderToPicture()); + int w = (int)svg.getDocumentWidth(); + int h = (int)svg.getDocumentHeight(); + if (w == -1 || h == -1) { + RectF box = svg.getDocumentViewBox(); + if (box != null) { + w = (int)box.width(); + h = (int)box.height(); + } } + iWidth = w; + iHeight = h; } @Override diff --git a/build.gradle b/build.gradle index e11a5b3..1478bb8 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ allprojects { repositories { google() jcenter() - + mavenCentral() } }