I have a problem with Raphael.path2curve(). The function modifies SVG path string so that all path commands are converted to absolute Cubic curves (C). The function supports all path commands (mlcqahvstMLCQAHVST
, see SVG SPEC).
The Raphael.path2curve() can handle well paths in many cases, eg. It can even convert arcs to cubics correct, which is not an easy calculation. I have made many tests and realized that paths that consist of commands QT
, CS
or HT
converts well. There are no problems with also the following:MS, HS, VS, LS, TC, TH, TL, TV, QA, TA
.
But it cannot handle commands QS, TS, AS, TT
(in that order).
If we eg. Have a path like this, the conversion fails:
M 0 0 T 205.4 112.9 S 260.8 23.36 82.45 72.86
But this converts correct:
M 0 0 S 211.9 54.20 52.14 144.4 T 98.85 44.45
So, MTS is not OK, but MST is. The problematic ones are S and T, because they are always in question, when something fails.
I made a random path generator (slow, but use jsbin for speed), where you can get random path and get it converted to
Cubic commands using Raphael.path2curve(). In fiddle click the SVG or press enter on the input field to get a new random path. Repeat until you find an incorrect one. In jsfiddle in HTML window is a parameter var list = "st";
where you can set the path commands to be randomized.
This is an example image. The blue is original path and red is converted path. They should be identical.
What should I do to Raphael code to get the conversion correct?
(I made a bug report, but have now tried to solve the problem several hours without luck.
It seems that I finally got it fixed. Please test! I made two jsbin versions:
1) NON-FIXED version which uses unmodified Raphael lib: jsbin.com/oqojan/33.
2) FIXED version where path2curve() function is modified: jsbin.com/oqojan/32.
In both versions there are black (original) and white (normalized) path. If everything works okay, you shouldn't see the white path below black path. If you see the white path, the lib code has a bug (please see below the explanation of some minor flickering).
Please hold ENTER down on input field a minute or so. The code generates random paths repeatedly as long as ENTER is down. Change the attribute var list = "mlcqahvstz";
to change the base letters for randomization.
And here is the explanation of what I had to do with the lib code. In original Raphaël 2.1.0 lib code there is a function path2curve(), which has the following lines:
case "S":
nx = d.x + (d.x - (d.bx || d.x));
ny = d.y + (d.y - (d.by || d.y));
path = ["C", nx, ny][concat](path.slice(1));
break;
case "T":
d.qx = d.x + (d.x - (d.qx || d.x));
d.qy = d.y + (d.y - (d.qy || d.y));
path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
break;
When I changed them to:
case "S":
if (pcom == "C" || pcom == "S") { // In "S" case we have to take into
// account, if the previous command
// is C/S.
nx = d.x * 2 - d.bx; // And reflect the previous
ny = d.y * 2 - d.by; // command's control point relative
// to the current point.
}
else { // or some else or nothing
nx = d.x;
ny = d.y;
}
path = ["C", nx, ny][concat](path.slice(1));
break;
case "T":
if (pcom == "Q" || pcom == "T") { // In "T" case we have to take
// into account, if the
// previous command is Q/T.
d.qx = d.x * 2 - d.qx; // And make a reflection similar
d.qy = d.y * 2 - d.qy; // to case "S".
}
else { // or something else or nothing
d.qx = d.x;
d.qy = d.y;
}
path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
break;
the function worked as expected (ie respects the original path shape in every possible path command combination). pcom
variable refers to the previous segment of ORIGINAL path and I had to also add a way to get pcom
, which was rather easy, because regarding all other path commands than A, the conversion from original path segment type to cubic curve (C) produces only one cubic command. In case of A the function may produce more than one C command (short angles produces one or few C segments and larger angles produces more).
The only minor inconsistence comes from Z commands, because Raphaël converts every Z to C. This affects to the visual appearance of path starts (or ends), but the difference is not huge. I assume that it converts Z to C to make paths animatable. If animation is not needed, then you may consider to edit the function to leave Z:s unconverted, in which case the conversion fidelity is excellent.
I'm amazed that all path commands can be represented as Cubic curves so reliably!
I hope this bug gets fixed in some future release of Raphaël.
EDIT: Made testbed also for path animations:
1) NON-FIXED: http://jsbin.com/oqojan/44
2) FIXED: http://jsbin.com/oqojan/42
After thorough testing with both non-animated and animated paths, I can confirm that my fix to path2curve function is stable and can be implemented in production code. If you want to be sure, please use above mentioned testbeds.