How to normalize SVG path data (cross-browser)?

I tried to find a way to implement a cross-browser normalizer. There is a custom method described here , and a functional example here , but it only works in the latest Opera (but not in IE, FF, Safari, Chrome).

The native method uses pathElm.normalizedPathSegList and converts all relative coordinates to absolute and represents all types of path segments as the following subset of types: M, L, C, z.

I found only one javascript code and jsfiddled its example , but it only works in IE and FF. Chrome gives the error message "Fault: INDEX_SIZE_ERR: DOM 1 exception." How can this be fixed to work also in Opera, Safari and Chrome, or is there any other way to normalize SVG paths?

+4
source share
3 answers

EDIT: I fixed the Raphaël error and conducted rigorous testing with animated and non-animated complex paths, so I find it appropriate to use Raphaël to normalize the path. An explanation of the error and fix is ​​here: fooobar.com/questions/244445 / .... The Raphaël path2curve function can easily convert all path commands (also A, i.e. Arc) to normalized form (i.e. cubic curves). It's nice that Cubics can represent all the teams of the way!


Another way is to use the new Raphaël, where the interesting function is Raphael.path2curve() , which converts all the path commands into cubic curves, but it has an error. The following image displays an error:

enter image description here

The functional example is here and the code is as follows:

 <style>path {fill:none}</style> <script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script> <div id="res" style="width:800px"></div> <div id="raphael"></div> <script> window.onload = function () { var paper = Raphael("raphael", 400, 400); var original_path = "M30 30 S40 23 23 42 L23,42 C113.333,113.333 136.667,113.333 150,80 t40,50 T230,240 q20 20 54 20 s40 23 23 42 t20,30 a20,30 0,0,1 -50,-50"; var arr=Raphael.path2curve(original_path); var normalized_path = arr.toString(); var path1 = paper.path(normalized_path).attr({stroke: "red", "stroke-width":6}); var path2 = paper.path(original_path).attr({stroke: "black", "stroke-width":2}); document.getElementById("res").innerHTML="ORIGINAL PATH (black):<br>"+original_path+"<br><br>NORMALIZED PATH (red):<br>"+normalized_path; } </script> 

It would be very nice to normalize the path in Raphaël, because it supports a large number of browsers and uses arrays instead of segments of the DOM path (= speed and backward compatibility). I made a bug report . Hope this is fixed in some future releases.

+1
source

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 combinations of path commands fail. Use here !

It finally worked in Safari, Opera, IE9, Firefox and Chrome: http://jsfiddle.net/timo2012/M6Bhh/41/

The function normalizes the SVG path data, so that all segments of the path are converted to M, C, L, and z (= absolute coordinates, which means that all relative coordinates are converted to absolute). All other segments are trivial and 100% accurate, but arc (A) is a special case, and you can choose whether arcs are converted to rows (L), quadratic curves (Q) or cubic curves (C). The most accurate are the lines, but then we lose independence from the resolution. For some reason, squares fail in certain arcs, but the cubes are more accurate.

If we have the following path:

 <svg width="400" height="400"> <path stroke="red" stroke-width="3" d="M30 30 S40 23 23 42 L23,42 C113.333,113.333 136.667,113.333 150,80 t40,50 T230,240 q20 20 54 20 s40 23 23 42 t20,30 a20,30 0,0,1 -50,-50"/> </svg> 

and normalize it using:

 var path = document.querySelector('path'); path.normalizePath(3, 0.1); // 3 = C = cubic curves. Best alternative, rather good accuracy and path data remains reasonable sized 

The normalized version is as follows:

 <svg width="400" height="400"> <path stroke="red" stroke-width="3" d="M 30 30 C 30 30 40 23 23 42 L 23 42 C 113.333 113.333 136.667 113.333 150 80 C 150 80 163.333 96.6667 190 130 C 216.667 163.333 230 200 230 240 C 243.333 253.333 261.333 260 284 260 C 284 260 324 283 307 302 C 307 302 313.667 312 327 332 C 324.811 336.924 321.997 341.154 318.719 344.448 C 315.441 347.741 311.762 350.033 307.893 351.194 C 304.024 352.355 300.04 352.361 296.169 351.213 C 292.298 350.064 288.616 347.783 285.333 344.5 C 282.05 341.217 279.23 336.996 277.035 332.078 C 274.839 327.161 273.311 321.642 272.537 315.839 C 271.763 310.035 271.759 304.06 272.525 298.254 C 273.291 292.448 274.811 286.924 277 282"/> </svg> 

If we lay out the result on each other, this is the result (red is normalized, and black is the original):

Normalized path and original

Other features:

 path.normalizePath(1,0.5); // A->L, Many lines, high accuracy. Very good accuracy, but not so resolution independent, because when scaled, the corners become visible path.normalizePath(1,40); // A->L, Few lines, less accuracy path.normalizePath(2,0.5); // A->Q, quadratic curves. I tested this, but not good. Fails in some cases. 

And what are the advantages of this?

Own path to normalize the path data has not yet been implemented in all browsers, so we are on our own for now. And when our own path is implemented, we are not sure that all browsers do this the same way. SVG documentation talks about converting arcs to strings, but this is not very good, because the main advantage of SVG: permission independence - will be lost. We must fully control how the normalization of arcs is performed, and this script provides a way for it.

When data is normalized, it can be changed in the same way as the coordinates in bitmap images. If we want to deform the paths (Arc, Arch, Bulge, Shell, Flag, Wave, Fish, Rise, Fisheye, Inflate, Squeeze, Twist) in the Illustrator path or distort the paths to reach the illusion perspective, the normalized path data can be changed reliably.

The code is based on the YannickBochatay script , and I made it a more cross-browser.

+3
source

I think I found a reason for DOM exceptions in Opera, Safari and Chrome.

The SVG document states that getItem() Returns the specified item from the list. The returned item is the item itself, not a copy. Any changes made to an element are immediately reflected in the list.

And similarly appendItem() Inserts a new element at the end of the list. If newItem is already in the list, it is removed from the previous list before it is inserted into this list. The inserted item is the item itself, not a copy.

So this applies to the original element:

 seg = path1.pathSegList.getItem(i); 

And when this item is added to another list of path segments using

 newpath.pathSegList.appendItem(seg); 

Each browser has its own opinion on what to do with the original segment. IE9 and FF leave the original list of path1 segments intact (or at least keep indexes (i in the example above)), while Safari, Chrome, and Opera remove the segment from path1's list of segments. The SVG documentation clearly shows that the item is ( removed from the previous list ), so IE9 and FF seem to have the wrong implementation. Thus, I'm still not sure if the item is removed from the previous list or only indexing is saved (I will check this a bit later).

EDIT: check this and make sure that IE9 and FF do not contain the original path list (path1) if the segment (s) are added to another list of route segments, so the indexes are also saved. The behavior in Safari, Chrome, and Opera is different: the item is removed from the original list when added to another list, and, of course, the indexing is also updated (old indexes are no longer valid after adding). Made by jsfiddle , which confirms the difference. IE9 and FF return the lengths of the list of segments 1,1,10,10. Opera, Safari, Chrome returns 1,0,10,8.

And once again, we have to consider the differences of the browser in some way, for example. adding a dummy segment before the first element or querying the path1.numberOfItems () to determine if the original path was changed or not.

+1
source

Source: https://habr.com/ru/post/1440817/


All Articles