Complete rule
In a corner of the SVG standard, there lies an attribute, available as a CSS property as well, with a niche purpose: fill-rule. To understand the contribution, however, let me introduce an example.
Far from being a lesson on path commands, in SVG you can draw any shape with a path element.
<svg viewBox="0 0 20 20">
<path d="..." fill="currentColor" />
</svg> In the d attribute specifically you add a series of instructions, a sequence of letters and numbers starting with the letter M and a pair of coordinates.
<path d="M 10 0" fill="currentColor" /> The string works to set the beginning of the path, and from this point, you continue the drawing with more precise commands. To draw a line, for instance, you can follow up the origin with the L character, and the position where the line should end.
<path d="M 10 0 L 20 10" fill="currentColor" /> With just the two letters we can draw a rhombus quite rapidly, outlining the figure with fourth vertices and concluding the path with the letter Z.
<path d="M 10 0 L 20 10 L 10 20 L 0 10 Z" fill="currentColor" /> If you want to draw a shape point by point, in straight segments, you can actually forgo the L character and pile up the numbers.
<path d="M 10 0 20 10 10 20 0 10 Z" fill="currentColor" /> Following the origin with the M character, the values are assumed to describe absolute points in the canvas.
You can draw a rhombus with a short string, but can also continue with another series of instructions. Once again, the sequence starts with the letter M, but follows the first first stretch.
In this manner you can build complex shapes made of multiple sub-paths, opening the door to an intriguing option. If you wanted to draw the rotated square with a hole, for instance, you could try repeating the four-points structure within the larger figure.
<path
d="M 10 0 20 10 10 20 0 10 Z
M 10 5 15 10 10 15 5 10 Z"
fill="currentColor"
/> Here we draw a large rhombus and then a smaller, half-rhombus with the same center point, 5, 5. Test the code, however, and you see only one polygon, the larger one.
I marked the vertices and the general direction of the sub-paths to hint at the structure. And the direction proves to be particularly important to explain the issue at hand, why you don’t see the gap.
Scalable vector graphics paint shapes with the fill attribute in the so-called fill region. And to determine the area, the specification describes an algorithm also known as winding rule. Luckily, you don’t have to master complex mathematics to understand the concept, and the rhombus comes back to guide us.
Starting with a value of 0, you update a counter every time that you cross a path. If this path is drawn with a clockwise direction, add one. If the path is drawn counterclockwise, subtract the same amount.
With our path we drew both figures clockwise, and the tallying is quite easy to perform.
0 for the portions outside of the larger figure, 1 between the two sub-paths and 2 for the innermost portion.
How does this help? SVG makes a decision based on the integer value. And by default, declares that points which fall in the fill region, points which are drawn, are those where the counter is not zero — no wonder the code lead to the whole rhombus.
This is the default behavior, and is dictated by the starting value of fill-rule: nonzero.
But you have an alternative: evenodd.
<path fill-rule="evenodd" d="M 10 0 20 10 10 20 0 10 Z M 10 5 15 10 10 15 5 10 Z" fill="currentColor" /> In this instance what is in the fill region flips every time you intersect a path. Odd regions are in — the space between sub-paths —, and even areas are out — including our center.
As you break down the algorithm the logic is not that long-winded, and pretty straightforward. And once you understand the rule, you may realize that you might not even need the instruction.
Going back to the visual, the counter grows every time you cross a sub-path because every sub-path is drawn with the same twist.
What happens if you reverse the trend of the two rhombi? You might have anticipated the result, but this is exactly how you can “draw” the hole.
And while you may not need an explanation by now, I hope you won’t mind an additional graphic detailing the reason.
As the direction flips, the counter grows and recedes, meaning the inner portion is now 0.
The points within the area are out, not part of the fill region, and as your astute mind will realize, this is regardless of the fill rule. Zero and even.
You have a choice at this point. Pick the commands judiciously, flipping the direction to and fro, and you are assured of the outcome. Draw the sub-paths with the same winding, clockwise or counterclockwise, and rely on fill-rule to change the painting afterwards.
On the one end you have a predictable result. On the other, the option to choose a different spin. With the HTML attribute, but also CSS. I might have mentioned it, but you do have the opportunity to change the value in a stylesheet.
path {
fill-rule: evenodd;
} Thanks to CSS specificity, the declaration triumphs over the presentational attribute, letting you appreciate the different picture. Assuming of course, you have the freedom to do so.