I have tried to find a way to implement cross browser path normalizer. There IS a native way which is described here and functional example is here, but it works only in newest Opera (but not in IE, FF, Safari, Chrome).
The native way uses pathElm.normalizedPathSegList
and it converts all relative coordinates to absolute ones and represents all path segment types as a following subset of types: M,L,C,z.
I have found only one javascript code and jsfiddled functional example of it, but it works only in IE and FF. Chrome gives "Uncaught Error: INDEX_SIZE_ERR: DOM Exception 1". How this could be fixed to work also in Opera, Safari and Chrome or is there any other way for normalizing SVG paths?
EDIT: Do not use this! I tested it more and realized that the A->C conversion is not reliable in all cases and also some other path command combinations fail. Please use this instead!
Finally got it to work in Safari, Opera, IE9, Firefox and Chrome: http://jsfiddle.net/timo2012/M6Bhh/41/
The function normalizes SVG path's data, so that all path segments are converted to M, C, L and z ( = absolute coordinates, which means that all relative coordinates are converted to absolute ones). All other segments are trivial and 100% accurate, but arc (A) is a special case and you can select whether arcs are converted to lines (L), quadratic curves (Q) or cubic curves (C). The most accurate are lines, but then we lose resolution independence. Quadratics for some reason fails in certain arcs, but cubics are more accurate.
If we have the following path:
and normalize it using:
the normalized version is this:
If we layout both on top of each other the result is this (red is normalized and black is original):
Other possibilities are these:
And what are the benefits of this?
The native way for normalizing path data is not implemented yet in all browsers, so we are on our own so far. And when the native way is implemented, we are not sure that all browsers makes it same way. The SVG documentation speaks of converting arcs to lines, but this is not a good way, because SVG:s main advantage - resolution independence - will be lost. We should have a full control how normalization of arcs is done, and this script provides a way to it.
When data is normalized, it can be altered exactly the same way as coordinates in bitmap images. If we want to warp (Arc, Arch, Bulge, Shell, Flag, Wave, Fish, Rise, Fisheye, Inflate, Squeeze, Twist) paths in a Illustrator way or distort paths to achieve perspective illusion, the normalized path data can be modified reliably.
The code is based on YannickBochatay's script and I made it more cross browser.
EDIT: I got the Raphaël bug fixed and made thorough testing with animated and non-animated complex paths, so I think it is wise to use Raphaël for path normalization. The explanation of bug and it's fix is here: https://stackoverflow.com/a/13079377/1691517. The Raphaël's path2curve function can easily convert all path commands (also A ie Arc) to normalized form (ie Cubic curves). It's nice, that Cubics can represent all path commands!
The other way is to use new Raphaël, where is an interesting function
Raphael.path2curve()
, which converts all path commands to Cubic curves, but it has some bug. The following image visualizes the bug:The functional example is here and the code is as follows:
It would be very nice to could make a path normalization in Raphaël, because it supports large amount of browsers and uses arrays instead of DOM path segments (= speed and backward compatibility). I made a bug report. Hope it is fixed in some future release.
I think I've found the reason for DOM Exceptions in Opera, Safari and Chrome.
The SVG doc says that
getItem()
Returns the specified item from the list. The returned item is the item itself and not a copy. Any changes made to the item are immediately reflected in the list.And similarly
appendItem()
Inserts a new item at the end of the list. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy.So this referes to the original item:
And when this item is appended to other path's segment list using
every browser has own opinion what to do to original seg. IE9 and FF leaves original path1's segment list intact (or at least preserves indexes (i in the above example)) and Safari, Chrome and Opera removes seg from path1's segment list. The SVG documentation explicates clearly that the item (is removed from its previous list), so IE9 and FF seem to have a wrong implementation. Thus I'm not (yet) very sure if the item is removed from previous list or is only the indexing preserved (I'll check this a little later).
EDIT: checked this, and confirm that IE9 and FF leaves the original path's (path1) segment list intact, when it's segment(s) are appended to other path's segment list, so also the indexes are preserved. The behavior in Safari, Chrome and Opera is different: the item is removed from original list when appended to other list, and so far of course the indexing is updated also (the old indexes are not valid anymore after appending). Made jsfiddle which confirms the difference. IE9 and FF returns seg list lengths 1,1,10,10. Opera, Safari, Chrome returns 1,0,10,8.
And once again we have to take into account the browser differences some way, eg. adding dummy seg before first item or querying path1.numberOfItems() to determine if the original path is modified or not.