SVG is a funny thing. You can create arbitrary vector shapes by drawing paths, but when it becomes time to get some cutouts going on (like, say, to generate the letter 'O') you get to pick the algorithm you want to use, and consequently how precise you want to be with path direction. SVG supports two algorithms for determining whether a point lies inside or outside a path shape, which determines whether or not the path the point lies in is filled or not.
1) The "even/odd" crossing number. For a point inside a closed shape this draws a virtual line from that point to some arbitrary point "infinitely" far away, and then counts how many path lines that crosses. If the number is odd, the point is "inside" the overall shape and that part of the shape is filled. If it's even, it's "outside" the shape and that part of the shape is left alone.
2) The "non-zero" crossing number. This does the same thing but then checks whether the number of paths seen is 0, or some non-zero value. If it's non-zero, the point lies inside the path shape, and the shape around it gets filled. However, there is an exception to that rule opposite-directed subpaths: if you draw a clockwise circle, and then put a counter-clockwise smaller circle inside of it, both algorithms will consider that a cut-out.
Canvas2D only supports one algorithm. The non-zero one. This is decidedly useless, but we're stuck with it. So what can we do? Well, we can find out which algorithm SVG uses, and then flip subpaths to make sure that the shape is correct under non-zero rules. All you need is some JavaScript (tm). The following shape is the letter "B" (upside down) from Adobe's "Kozuka Mincho Pro" Regular. This is an OpenType font with Type 2 charstring vector outlines, and these outlines are well designed: the outer path is drawn in one direction, and the two cutouts are drawn in the opposite direction. So... what happens when we tell an SVG file to use the "nonzero" algorithm, and reverse the various subpaths?
original This shape consists of three subpaths, one large shape (clockwise), one small cutout (counter-clockwise) and a slighty bigger cutout (counter-clockwise). | normalised Here, we've replaced all SVG shortcut commands with their their normal command counterparts, and turned all relative coordinates into absolute coordinates. This should look exactly the same. | normalised, reversed As you can see, reversing the direction uniformly for every subpath in the shape does absolutely nothing. Which makes sense, because the difference in direction is maintained. |
Using the even/odd algorithm, we can reverse as many subpaths as we want, and it won't affect the look of the shape:
first subpath reversed | second subpath reversed | third subpath reversed |
For the non-zero algorithm, however, reversals matter a great deal:
first subpath reversed When we reverse the direction of the first subpath, we have a counter-clockwise big outline, and two counter-clockwise cutouts. Since they're all in the same direction, the biggest outline determines the fill. | second subpath reversed When we reverse one of the cutouts, it no longer counts as cutout under nonzero algorithm rules. | third subpath reversed Of course it doesn't matter which of the cutouts we reverse. It's not going to count as cut-out. |
So, when we want to use an SVG image as data for drawing on a Canvas2D surface (because sometimes you want to rasterize that data for blending it with other graphics), we can now look at which algorithm the SVG uses, and whether that will cause problems when doing straight rasterisation to the canvas. If it will, we can reverse offending subpaths and make sweet, sweet HTML5 love happen anyway.
- Mike "Pomax" Kamermans