Using SVG with CSS3 and HTML5 — Supplementary Material

Example code and online extras for the O'Reilly Media book by Amelia Bellamy-Royds, Kurt Cagle, and Dudley Storey.

The Winding Order of the Fill Rule

SVG paths, polygons, and polylines can include criss-crossing lines. Paths can also include intersecting or overlapping subpaths. In any of these cases, it can be a bit confusing to determine which sections of a shape are inside or outside.

There are two different options for calculating which parts of a path are “inside” the shape. They are defined as the values of the fill-rule style property:

The same two keywords (and the same default) are used in CSS shapes functions and in the SVG clip-rule property, which determines which parts are inside or outside for SVG shapes that are children of a <clipPath>.

To show how the fill region is calculated with each fill rule, Example 6-X1 creates four different paths that look suspiciously the same, but have very different structures:

With fill-rule: evenodd, all four paths have the same stroke and fill area, as shown in Figure 6-X1. The outer region of the shape is filled, but the central triangle isn’t.

Four identical shapes.  The two on the left are labelled one subpath, either crossing or reversing.  The two on the right are labelled two subpaths, either same-direction or opposite.  The shapes appear to consist of a single stroke starting from top left, to top right, crossing on a diagonal towards the bottom left, but not all the way to the corner, then straight up and a then diagonal back towards bottom right crossing back over the previous diagonal, before closing the shape across the bottom and back up the left edge.  The shapes are stroked in red and are filled in blue, except for the triangular cut-out area inside the criss-crossed strokes, which is unfilled white.
Figure 6-X1. Four suspiciously identical shapes, with evenodd fill

If you switch to fill-rule: nonzero, as shown in Figure 6-X2, the crossing-over path and the same-direction subpath switch to being filled in. The other two shapes don’t change.

The same layout as the previous figure, except that now the criss-cross triangle region is filled in blue in the shape labelled 'one subpath crossing' and the shape labelled 'two subpaths same-direction'.  The other two shapes are unchanged, the center region unfilled white.
Figure 6-X2. Four similar-but-not-identical shapes, with nonzero fill

What’s going on? First the code, then the explanation.

Example 6-X1. Defining paths with winding order in mind
<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"
     viewBox="0 0 540 170" width="4in" height="1.25in">
  <title>Fill-rule shapes</title>
  <style>
path {
  fill: lightSkyBlue;
  stroke: darkRed;
  stroke-width: 4;
  fill-rule: evenodd;                  1
}
text {
  fill: indigo;
  font: bold 18px Segoe UI, sans-serif;
}
  </style>
  <g>
    <text x="70" y="164">one sub-path</text>

    <path d="M 10,5
             l 100,10 -80,80 0,-60 80,80 -100,10 z"/>
    <text x="5" y="144">crossing</text>               2

    <path d="M 150,5
             l 100,10 -50,50 -30,-30 0,60 30,-30 50,50 -100,10 z"/>
    <text x="145" y="144">reversing</text>            3
  </g>
  <g>
    <text x="350" y="164">two sub-paths</text>

    <path d="M 290,5
             l 100,10 -50,50 50,50 -100,10 z
             m 50,60
             l-30,30 0,-60 z" />
    <text x="285" y="144">same-direction</text>       4

    <path d="M 430,5
             l 100,10 -50,50 50,50 -100,10 z
             m 50,60
             l -30,-30 0,60 z" />
    <text x="425" y="144">opposite</text>             5
  </g>
</svg>
1

This version of the code sets the evenodd value of fill-rule. To see the difference, change it to nonzero; or just remove this line, as nonzero is the default.

2

The first shape is a single subpath that starts from the top left and proceeds in a clockwise direction. The path crosses over itself to form a triangular loop in the middle, and then wraps around the outside.

3

The second shape is also a single subpath, but it never crosses itself. Instead, when the paths reach the point where they would intersect, they reverse course, and wrap around the inner triangle in the opposite direction before continuing with the outside edge of the path.

4

The remaining paths are drawn as two subpaths each. Both have an outer subpath that wraps around in a clockwise direction, and an inner triangular subpath. In the same-direction shape, the inner triangle is also drawn in a clockwise direction, starting from the intersection point on the right, then the bottom left, then vertically up (negative y direction), before closing it off.

5

In the final shape, with the opposite-direction subpath, the inner triangle is drawn in a counter clockwise direction, touching the points in the same direction as the “reversing” single-path shape: from the intersection point, going to the top left of the triangle, then down, and finally closed.

The path data in Example 6-X1 uses relative coordinates so you can more easily compare the differences in shapes, and track the direction of each line segment: up and down correspond to negative versus positive y values, while negative versus positive x values map to left versus right.

The example also shows the relative m move-to command. As we mentioned in the book, this moves the pen relative to the final point of the previous subpath. Here, that’s the implicit point created by the z close-path commands.

To understand how path direction and fill-rule options are connected, you need to determine the winding order of different regions.

For any region within the shape, the winding order is calculated from the directions of every path segment you need to cross to reach that point. A point outside the graphic has a winding-order value of 0. Starting from there, every clockwise path boundary you cross over will add one to your winding order. Every counter-clockwise path that you cross over subtracts one from your winding order. The more loops there are in your shape, the more “wound up” you get.

In Figure 6-X3, we show how the winding order is calculated for each version of our shape. Arrowheads mark the directions of each line segment. To make things a little easier to follow, the points have been shifted slightly, to separate the strokes which don’t actually intersect.

Approximately the same shapes as before, but enlarged in a grid.  Arrows on each stroke show the direction.  The shifted points clearly show the difference between the single path and two subpath shapes, and also make it clear that the cut-out in the 'reversing' shape is really just an inlet from the outside.  The different regions of each shape are labelled 0, +1, or +2, and a curved arrow from the outside to inside of each shape passes by the different labels as it crosses each stroke.
Figure 6-X3. Four no-longer identical shapes, with nonzero fill, showing the winding order calculations

If the value of fill-rule is evenodd, regions will be filled if their winding order is an odd number; since the winding order changes by 1 every time you cross a path segment, the fill alternates between inside and out. However, if the value of fill-rule is nonzero (the default, as shown in Figure 6-X3), regions will be filled unless the winding-order sum cancels out back to zero. Paths that loop in the same direction will add together to create a value farther from zero.

Winding orders are related to the rasterization process we mentioned in Chapter 1 and in the “Understanding Vector Graphics” article. When a computer fills in a vector shape, it doesn’t see the entire shape at once. Instead, it scans across it one row of pixels at a time, looking at each edge independently.

Looking at Figure 6-X3, with the slightly adjusted points, it is now obvious that the central triangle in the “reversing” path is actually just an extension of the outside. Of course it isn’t ever filled in.

But to a rasterizer scanning across the top half of the shape, it looks identical to the shape with the opposite-direction subpath. The edges all follow the same directions. So the opposite-direction subpath is also never filled in.

One final note on fill-rule: neither of the fill-rule values care if your winding order is positive or negative. If all the paths were reversed, the result would be the same.

Tip

For SVG, the important thing isn’t clockwise versus counter-clockwise, it’s same versus opposite direction. (And even that can be ignored when you set fill-rule to evenodd.)

Positive versus negative winding order becomes much more important in mapping, where a single path can wrap all the way around the globe and back again. In that case, it isn’t clear which side of the path is “outside” to start with. For example, if you created a path that traced around the coastlines of every continent, which would be inside the path: the oceans or the land?

Many mapping applications—including the d3.js modules that convert map data to SVG—rely on the rule that clockwise paths enclose content and counter-clockwise paths exclude them.