svgicons2svgfont: It seems that fill-rule="evenodd" does not works correctly

I have the following SVG:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg 
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  width="16"
  height="16"
>
  <symbol id="icon-search-16" viewBox="0 0 16 16" preserveAspectRatio="xMidYMin slice" width="100%">
      <path class="path1" fill="currentColor" fill-rule="evenodd" d="M6.50015 0c3.5902,0 6.50015,2.90995 6.50015,6.50015 0,1.36286 -0.418069,2.62651 -1.13493,3.6705l3.72837 3.79923c0.434603,0.485385 0.602303,1.15264 0.0791261,1.67582 -0.524358,0.525539 -1.1928,0.494833 -1.71715,-0.0295247l-3.75317 -3.77325c-1.05108,0.729849 -2.32654,1.15737 -3.70239,1.15737 -3.5902,0 -6.50015,-2.90995 -6.50015,-6.50015 0,-3.5902 2.90995,-6.50015 6.50015,-6.50015zm0 1.99941c2.48479,0 4.49956,2.01476 4.49956,4.49956 0,2.48479 -2.01476,4.49956 -4.49956,4.49956 -2.48479,0 -4.49956,-2.01476 -4.49956,-4.49956 0,-2.48479 2.01476,-4.49956 4.49956,-4.49956z"></path>
  </symbol>
  <use xlink:href="#icon-search-16" />
</svg> 

The result is:

<?xml version="1.0" standalone="no"?> 
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
  <font id="iconfont" horiz-adv-x="16">
    <font-face font-family="iconfont"
      units-per-em="16" ascent="16"
      descent="0" />
    <missing-glyph horiz-adv-x="0" />
    <glyph glyph-name="1"
      unicode="&#xEA01;"
      horiz-adv-x="16" d=" M6.50015 16C10.09035 16 13.0003 13.09005 13.0003 9.49985C13.0003 8.13699 12.582231 6.87334 11.86537 5.82935L15.59374 2.03012C16.028343 1.544735 16.196043 0.87748 15.6728661 0.3543C15.1485081 -0.171239 14.4800661 -0.140533 13.9557161 0.3838247L10.2025461 4.1570747C9.1514661 3.4272257 7.8760061 2.9997047 6.5001561 2.9997047C2.9099561 2.9997047 0.0000061 5.9096547 0.0000061 9.4998547C0.0000061 13.0900547 2.9099561 16.0000047 6.5001561 16.0000047zM6.50015 14.00059C8.98494 14.00059 10.99971 11.98583 10.99971 9.50103C10.99971 7.01624 8.98495 5.00147 6.50015 5.00147C4.01536 5.00147 2.00059 7.01623 2.00059 9.50103C2.00059 11.98582 4.01535 14.00059 6.50015 14.00059z" />
  </font>
</defs>
</svg>

It seems that a property fill-rule=“evenodd” does not used in converted font. Is it possible to convert it correctly, or it requires a lot of work?

PS: Thank you for the awesome library.

About this issue

  • Original URL
  • State: open
  • Created 8 years ago
  • Reactions: 12
  • Comments: 21 (2 by maintainers)

Most upvoted comments

If you’re using Figma, you can use the Fill Rule Editor plugin to reverse paths. Here’s how to use it: https://youtu.be/j6dZw3K_E3M

If someone else needs clear details on how to fix it, this post explains it also https://hinty.io/brucehardywald/how-to-generate-a-webfont-automated-setup/

I had the same problem and can fix it in Inkscape. In Inkscape you can turn on show direction of paths.

The path of the hole on the left is in the opposite direction to the one on the right image which causes the icon to be rendered as follows image

To fix this I just need to select the hole on the right and reverse the path direction. What would be nice is if we had a svgo plugin that could do this automatically.

Wow! That’s actually a really crazy approach! I love it. I did some googling (mostly focused on github) to try to find literally anyone else that had implemented an evenodd “cleaner” or anything similar, but I actually…ended up getting linked back to my own issue over here: https://github.com/svg/svgo/issues/1092

Kind of weird to realize how poor support for evenodd really is.

So the algorithm you mentioned really does depend on being able to render / rasterize svgs at some level. One interesting thing that I note is that this algorithm would not handle cases where the same path crosses back over itself creating a void (as is seen in the first example here). I wonder if doing a series of split operations on each path, we could end up at a point where all possible insides / outsides exist as separate paths – but we would still need to figure out which ones were which.

So yeah, back where we started – basically browsers support evenodd because they’re processing it as they rasterize the image. Any other approach will require understanding how the paths affect one another better.

I used picosvg to circumvent this problem, just simplify the SVG by running picosvg on it, and then use the simplified version.

https://github.com/googlefonts/picosvg

picosvg your_broken.svg > healthy.svg

If you’re using Figma, you can use the Fill Rule Editor plugin to reverse paths. Here’s how to use it: https://youtu.be/j6dZw3K_E3M

That’s actually the solution for us!

I uhm… researched a bit, like totally not reverse engineering icomoon code because that would be against their terms… and I found out that one solution would be:

  1. splitting up the path of an svg into subpaths
  2. drawing the path to two different canvas elements, one with evenodd one without
  3. if there are differences, reverse one subpath after another until there are no differences between the two canvases

I spent some time on this and for now I can say that the pure node implementation of canvg (the one backed by canvas / cairo) fails to correctly draw an evenodd svg… just like this package here so basically we’re back at the beginning

@nfroidure It looks like this is an essential feature for compound paths. Here’s a doc: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule

And here’s an example svg I’ve been having trouble with:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="400px" height="400px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
    <rect id="socioeconomics" x="0.988" y="0" width="400" height="400" style="fill:none;"/>
    <path d="M385.603,188.852l-64.265,-55.425l0,-83.346c0,-3.961 -3.259,-7.22 -7.221,-7.22l-33.696,0c-3.961,0 -7.221,3.259 -7.221,7.22l0,43.692l-53.873,-44.323c-10.621,-8.741 -26.084,-8.741 -36.705,0l-166.294,139.42c-1.659,1.371 -2.62,3.414 -2.62,5.566c0,1.68 0.586,3.309 1.657,4.603l15.344,18.654c1.371,1.667 3.419,2.634 5.577,2.634c1.678,0 3.305,-0.585 4.599,-1.653l155.517,-130.557c2.677,-2.158 6.529,-2.158 9.206,0l155.505,130.539c1.294,1.071 2.923,1.658 4.603,1.658c2.152,0 4.195,-0.962 5.566,-2.621l15.344,-18.654c1.062,-1.291 1.642,-2.912 1.642,-4.584c0,-2.15 -0.959,-4.189 -2.665,-5.603Z" style="fill-rule:nonzero;"/>
    <path d="M195.677,112.748l-128.03,105.447l0,126.065c0,6.095 5.016,11.111 11.111,11.111l77.817,-0.201c6.074,-0.031 88.829,-0.014 88.829,-0.014l77.789,0.215c6.095,0 11.111,-5.016 11.111,-11.111l0,-126.141l-128.002,-105.371c-3.09,-2.49 -7.536,-2.49 -10.625,0Zm18.011,123.769l0,-57.15c0,-3.508 -2.841,-6.349 -6.35,-6.35l-12.7,0c-3.509,0.001 -6.35,2.842 -6.35,6.35l0,57.15l-57.15,0c-3.509,0 -6.35,2.842 -6.35,6.35l0,12.7c0,3.508 2.841,6.35 6.35,6.35l57.15,0l0,57.15c0,3.508 2.842,6.35 6.35,6.35l12.7,0c3.509,-0.001 6.35,-2.842 6.35,-6.35l0,-57.15l57.15,0c3.509,0 6.35,-2.842 6.35,-6.35l0,-12.7c0,-3.508 -2.841,-6.35 -6.35,-6.35l-57.15,0Z"/>
</svg>

I tried both Affinity Designer and Gravit for making this icon and exporting it and in both cases ran into issues. FontForge has a tool for “Correct Direction” which appears to convert “evenodd” paths to “nonzero” if that helps you figure out how to implement this.

edit: here is the code behind that “Correct Direction” tool: https://github.com/fontforge/fontforge/blob/ae2f40b2f213b2d645d5a9f1e7a55475cbfe84ea/fontforge/scripting.c#L5505 (line 5505)