I've been bashing my head against this for 24hours and there seem to be no answers that I can find.
Basically, I'm trying to convert hand drawn lines in inkscape to Android canvas paths (using Roman Nurik's SVG Parser algorithm). However, when I copy the SVG path data from inkscape into android it all appears incorrectly. however, loading said SVG into a browser is fine.
A problem with the algorithm? No, as loading data from gimp is fine.
Any ideas what might be going on here?
My best guess is that there's some issue with the relative positioning bit in the algorithm, but it seems to behave like I'd expect. I've pasted that code in the bottom too.
It'd be nice not to have to go via gimp with every one of these SVGs - so hopefully someone can shed some light!
Mike
Path Data from Inkscape:
m 76.428571,980.93364 c -6.457974,-3.14816 -14.113763,-4.36636 -21.11213,-2.44465 -5.255395,1.7711 -9.566057,5.66601 -12.985096,9.92918 -3.860239,5.09234 -6.618858,11.31544 -6.748316,17.76983 0.393758,9.4379 9.378738,17.1342 18.660733,16.9792 5.333419,-0.092 11.156101,-1.68 14.661052,-5.9507 3.504093,-5.4571 4.356843,-12.0806 5.197735,-18.38493 0.748875,-6.02282 1.067527,-12.11238 2.348042,-18.05803 1.45938,6.45521 -0.181845,13.07935 -1.481234,19.41996 -1.325591,6.0779 -3.388929,12.6011 -0.915871,18.669 1.262838,3.2956 4.405436,6.6312 8.241935,6.0059 1.295522,-0.2024 2.345532,-1.0901 3.06172,-2.1488
Path Data from Exported Standard SVG from inkscape (renders correctly in Chrome)
m 76.428571,980.93364 c -6.889253,-3.5627 -17.922438,-4.9003 -24.481516,-0.9809 -8.805509,5.1074 -15.110471,14.449 -16.336043,24.56986 -0.650029,8.9445 6.859479,17.0136 15.47339,18.3631 6.286812,1.1833 16.016458,-1.3892 18.77027,-7.2237 4.929541,-9.4018 4.293263,-24.63276 6.595919,-34.88846 1.82465,6.8346 -1.147558,17.648 -2.571582,24.29476 -1.236496,5.2917 -1.814733,11.3739 1.425864,16.1011 1.567378,2.6597 5.113898,4.7499 8.147238,3.3231 0.77782,-0.4101 1.41025,-1.0537 1.90503,-1.7729
Same Path Loaded Into Gimp and Exported from there:
M 74.64,71.07
C 74.15,71.79 73.52,72.43 72.74,72.84
69.70,74.27 66.16,72.18 64.59,69.52
61.35,64.79 61.93,58.71 63.16,53.42
64.59,46.77 67.56,35.96 65.74,29.13
63.43,39.38 64.07,54.61 59.14,64.01
56.39,69.85 46.66,72.42 40.37,71.24
31.76,69.89 24.25,61.82 24.90,52.87
26.12,42.75 32.43,33.41 41.23,28.30
47.79,24.39 58.83,25.72 65.71,29.29
Code: Select all
package com.mike.testapps.learntowrite;
/*
* Copyright 2014 Google Inc.
*
* Licensed 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.
*/
import android.graphics.Path;
import android.graphics.PointF;
import android.util.Log;
import java.text.ParseException;
class SvgPathParser {
private static final int TOKEN_ABSOLUTE_COMMAND = 1;
private static final int TOKEN_RELATIVE_COMMAND = 2;
private static final int TOKEN_VALUE = 3;
private static final int TOKEN_EOF = 4;
private int mCurrentToken;
private PointF mCurrentPoint = new PointF();
private int mLength;
private int mIndex;
private String mPathString;
protected float transformX(float x) {
return x;
}
protected float transformY(float y) {
return y;
}
public Path parsePath(String s) throws ParseException {
mCurrentPoint.set(Float.NaN, Float.NaN);
mPathString = s;
mIndex = 0;
mLength = mPathString.length();
PointF tempPoint1 = new PointF();
PointF tempPoint2 = new PointF();
PointF tempPoint3 = new PointF();
Path p = new Path();
p.setFillType(Path.FillType.WINDING);
boolean firstMove = true;
while (mIndex < mLength) {
char command = consumeCommand();
boolean relative = (mCurrentToken == TOKEN_RELATIVE_COMMAND);
switch (command) {
case 'M':
case 'm': {
// move command
boolean firstPoint = true;
while (advanceToNextToken() == TOKEN_VALUE) {
consumeAndTransformPoint(tempPoint1,
relative && !firstPoint);//mCurrentPoint.x != Float.NaN);
if (firstPoint) {
p.moveTo(tempPoint1.x, tempPoint1.y);
firstPoint = false;
if (firstMove) {
mCurrentPoint.set(tempPoint1);
firstMove = false;
}
} else {
p.lineTo(tempPoint1.x, tempPoint1.y);
}
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'C':
case 'c': {
// curve command
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
consumeAndTransformPoint(tempPoint1, relative);
consumeAndTransformPoint(tempPoint2, relative);
consumeAndTransformPoint(tempPoint3, relative);
Log.d("SVG Parser", "tempPoint1: " + tempPoint1.x + ", " + tempPoint1.y);
Log.d("SVG Parser", "tempPoint2: " + tempPoint2.x + ", " + tempPoint2.y);
Log.d("SVG Parser", "tempPoint3: " + tempPoint3.x + ", " + tempPoint3.y);
p.cubicTo(tempPoint1.x, tempPoint1.y, tempPoint2.x, tempPoint2.y,
tempPoint3.x, tempPoint3.y);
}
mCurrentPoint.set(tempPoint3);
break;
}
case 'L':
case 'l': {
// line command
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
consumeAndTransformPoint(tempPoint1, relative);
p.lineTo(tempPoint1.x, tempPoint1.y);
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'H':
case 'h': {
// horizontal line command
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
float x = transformX(consumeValue());
if (relative) {
x += mCurrentPoint.x;
}
p.lineTo(x, mCurrentPoint.y);
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'V':
case 'v': {
// vertical line command
if (mCurrentPoint.x == Float.NaN) {
throw new ParseException("Relative commands require current point", mIndex);
}
while (advanceToNextToken() == TOKEN_VALUE) {
float y = transformY(consumeValue());
if (relative) {
y += mCurrentPoint.y;
}
p.lineTo(mCurrentPoint.x, y);
}
mCurrentPoint.set(tempPoint1);
break;
}
case 'Z':
case 'z': {
// close command
p.close();
break;
}
}
}
return p;
}
private int advanceToNextToken() {
while (mIndex < mLength) {
char c = mPathString.charAt(mIndex);
if ('a' <= c && c <= 'z') {
return (mCurrentToken = TOKEN_RELATIVE_COMMAND);
} else if ('A' <= c && c <= 'Z') {
return (mCurrentToken = TOKEN_ABSOLUTE_COMMAND);
} else if (('0' <= c && c <= '9') || c == '.' || c == '-') {
return (mCurrentToken = TOKEN_VALUE);
}
// skip unrecognized character
++mIndex;
}
return (mCurrentToken = TOKEN_EOF);
}
private char consumeCommand() throws ParseException {
advanceToNextToken();
if (mCurrentToken != TOKEN_RELATIVE_COMMAND && mCurrentToken != TOKEN_ABSOLUTE_COMMAND) {
throw new ParseException("Expected command", mIndex);
}
return mPathString.charAt(mIndex++);
}
private void consumeAndTransformPoint(PointF out, boolean relative) throws ParseException {
out.x = transformX(consumeValue());
out.y = transformY(consumeValue());
if (relative) {
out.x += mCurrentPoint.x;
out.y += mCurrentPoint.y;
}
}
private float consumeValue() throws ParseException {
advanceToNextToken();
if (mCurrentToken != TOKEN_VALUE) {
throw new ParseException("Expected value", mIndex);
}
boolean start = true;
boolean seenDot = false;
int index = mIndex;
while (index < mLength) {
char c = mPathString.charAt(index);
if (!('0' <= c && c <= '9') && (c != '.' || seenDot) && (c != '-' || !start)) {
// end of value
break;
}
if (c == '.') {
seenDot = true;
}
start = false;
++index;
}
if (index == mIndex) {
throw new ParseException("Expected value", mIndex);
}
String str = mPathString.substring(mIndex, index);
try {
float value = Float.parseFloat(str);
mIndex = index;
return value;
} catch (NumberFormatException e) {
throw new ParseException("Invalid float value '" + str + "'.", mIndex);
}
}
}