Caius Jard
Caius Jard

Reputation: 74605

SVG filled path - if it loops back over itself the overlap is not filled

I maintain a drawing tool written in flash that saves its data for the highlighter tool as a series of points describing the outline of a thick filled freehand/point-to-point line. For example if the user drew a horizontal line, the path would be a sequence of points from left to right, then a line down 10 pixels, the same sequence of points in reverse to get back to 10px below the origin, then he path is closed

Another tool periodically rasterizes flash's data by converting the json list of points to an svg path. The path command is like "M 123,456 l 1,2 3,5 -2,-5..." and describes the complete outer edge of the shape. There's no stroke, and the fill is opacity 0.4

I've noticed that if the user has drawn a single line that overlaps itself, like the greek symbol alpha, the overlapping portion is not filled. I was expecting it to become more opaque as would a real highlighter.

Is this behaviour by design in SVG? Or could it be a bug in the library i use? If by design, what alternative tactic can i use to render a part opaque highlighter line that progressively becomes more opaque if it crosses over itself?

I should note that two separate lines (svg doc contains two path elements that cross over) drawn over each other DO become more opaque as expected, its just if its a single path element, this odd behaviour occurs

Here is an example SVG document demonstrating the behaviour. It can be visualized on something like http://www.rapidtables.com/web/tools/svg-viewer-editor.htm (paste the code into the top text box and click the magnifying glass icon. Requires a browser that understands SVG)

    <svg width="680" height="465" version="1.1" xmlns="http://www.w3.org/2000/svg">
      <g>
        <path d="M 283,29 l -2,2 -1,1 -5,4 -5,4 -4,3 -4,2 -3,2 -4,3 -6,3 -4,2 -5,3 -6,5 -3,2 -4,2 -1,1 -5,2 -4,3 -4,2 -3,1 -3,2 -3,2 -4,1 -8,3 -3,1 -5,1 -6,1 -1,0 -5,1 -4,0 -5,0 -6,1 -6,0 -7,0 -10,0 -7,0 -7,0 -5,0 -5,0 -9,0 -2,0 -3,0 -8,0 -3,0 -6,0 -4,0 -4,0 -3,-1 -3,-1 -2,-2 -2,-1 -3,-4 0,-1 -2,-3 -2,-4 -1,-2 -1,-3 -1,-5 0,-6 0,-4 0,-4 0,-4 0,-5 0,-5 0,-4 1,-4 3,-4 3,-4 2,-3 2,-3 2,-2 1,-1 3,-1 2,-1 2,-1 8,-3 7,-1 5,-1 4,0 3,0 3,0 2,0 2,0 2,0 2,0 3,0 2,0 3,0 2,0 5,0 3,0 1,0 2,0 3,1 4,2 3,0 2,2 4,2 4,2 3,1 2,2 2,2 3,2 4,3 3,3 2,2 3,3 2,3 2,2 2,3 1,3 2,3 2,3 3,3 2,3 1,3 2,3 2,3 2,3 2,3 1,3 1,3 2,2 2,2 2,3 1,3 3,3 2,3 1,2 2,1 1,2 2,1 2,2 3,1 2,1 2,2 2,1 3,1 3,1 2,2 3,1 4,2 4,2 3,1 3,1 3,1 3,0 2,1 2,1 2,0 4,1 4,1 4,0 3,0 3,0 4,0 2,0 4,0 3,0 1,0 2,0 3,0 3,0 2,0 2,0 3,0 2,0 2,0 1,0 1,-1 1,0 2,-1 2,0 2,-1 1,0 1,-1 1,0 1,-1 1,0 1,0 0,25 -1,0 -1,0 -1,1 -1,0 -1,1 -1,0 -2,1 -2,0 -2,1 -1,0 -1,1 -1,0 -2,0 -2,0 -3,0 -2,0 -2,0 -3,0 -3,0 -2,0 -1,0 -3,0 -4,0 -2,0 -4,0 -3,0 -3,0 -4,0 -4,-1 -4,-1 -2,0 -2,-1 -2,-1 -3,0 -3,-1 -3,-1 -3,-1 -4,-2 -4,-2 -3,-1 -2,-2 -3,-1 -3,-1 -2,-1 -2,-2 -2,-1 -3,-1 -2,-2 -2,-1 -1,-2 -2,-1 -1,-2 -2,-3 -3,-3 -1,-3 -2,-3 -2,-2 -2,-2 -1,-3 -1,-3 -2,-3 -2,-3 -2,-3 -2,-3 -1,-3 -2,-3 -3,-3 -2,-3 -2,-3 -1,-3 -2,-3 -2,-2 -2,-3 -3,-3 -2,-2 -3,-3 -4,-3 -3,-2 -2,-2 -2,-2 -3,-1 -4,-2 -4,-2 -2,-2 -3,0 -4,-2 -3,-1 -2,0 -1,0 -3,0 -5,0 -2,0 -3,0 -2,0 -3,0 -2,0 -2,0 -2,0 -2,0 -3,0 -3,0 -4,0 -5,1 -7,1 -8,3 -2,1 -2,1 -3,1 -1,1 -2,2 -2,3 -2,3 -3,4 -3,4 -1,4 0,4 0,5 0,5 0,4 0,4 0,4 0,6 1,5 1,3 1,2 2,4 2,3 0,1 3,4 2,1 2,2 3,1 3,1 4,0 4,0 6,0 3,0 8,0 3,0 2,0 9,0 5,0 5,0 7,0 7,0 10,0 7,0 6,0 6,-1 5,0 4,0 5,-1 1,0 6,-1 5,-1 3,-1 8,-3 4,-1 3,-2 3,-2 3,-1 4,-2 4,-3 5,-2 1,-1 4,-2 3,-2 6,-5 5,-3 4,-2 6,-3 4,-3 3,-2 4,-2 4,-3 5,-4 5,-4 1,-1 2,-2" style="stroke-linecap: round; stroke-linejoin: round; fill: #0000CC; fill-opacity: 0.4; fill-rule: evenodd; stroke: none" />
    
        <path d="M 583,29 l -2,2 -1,1 -5,4 -5,4 -4,3 -4,2 -3,2 -4,3 -6,3 -4,2 -5,3 -6,5 -3,2 -4,2 -1,1 -5,2 -4,3 -4,2 -3,1 -3,2 -3,2 -4,1 -8,3 -3,1 -5,1 -6,1 -1,0 -5,1 -4,0 -5,0 -6,1 -6,0 -7,0 -10,0 -7,0 -7,0 -5,0 -5,0 -9,0 -2,0 -3,0 -8,0 -3,0 -6,0 -4,0 -4,0 -3,-1 -3,-1 -2,-2 -2,-1 -3,-4 0,-1 -2,-3 -2,-4 -1,-2 -1,-3 -1,-5 0,-6 0,-4 0,-4 0,-4 0,-5 0,-5 0,-4 1,-4 3,-4 3,-4 2,-3 2,-3 2,-2 1,-1 3,-1 2,-1 2,-1 8,-3 7,-1 5,-1 4,0 3,0 3,0 2,0 2,0 2,0 2,0 3,0 2,0 3,0 2,0 5,0 3,0 1,0 2,0 3,1 4,2 3,0 2,2 4,2 4,2 3,1 2,2 2,2 3,2 4,3 3,3 2,2 3,3 2,3 2,2 2,3 1,3 2,3 2,3 3,3 2,3 1,3 2,3 2,3 2,3 2,3 1,3 1,3 2,2 2,2 2,3 1,3 3,3 2,3 1,2 2,1 1,2 2,1 2,2 3,1 2,1 2,2 2,1 3,1 3,1 2,2 3,1 4,2 4,2 3,1 3,1 3,1 3,0 2,1 2,1 2,0 4,1 4,1 4,0 3,0 3,0 4,0 2,0 4,0 3,0 1,0 2,0 3,0 3,0 2,0 2,0 3,0 2,0 2,0 1,0 1,-1 1,0 2,-1 2,0 2,-1 1,0 1,-1 1,0 1,-1 1,0 1,0 0,25 -1,0 -1,0 -1,1 -1,0 -1,1 -1,0 -2,1 -2,0 -2,1 -1,0 -1,1 -1,0 -2,0 -2,0 -3,0 -2,0 -2,0 -3,0 -3,0 -2,0 -1,0 -3,0 -4,0 -2,0 -4,0 -3,0 -3,0 -4,0 -4,-1 -4,-1 -2,0 -2,-1 -2,-1 -3,0 -3,-1 -3,-1 -3,-1 -4,-2 -4,-2 -3,-1 -2,-2 -3,-1 -3,-1 -2,-1 -2,-2 -2,-1 -3,-1 -2,-2 -2,-1 -1,-2 -2,-1 -1,-2 -2,-3 -3,-3 -1,-3 -2,-3 -2,-2 -2,-2 -1,-3 -1,-3 -2,-3 -2,-3 -2,-3 -2,-3 -1,-3 -2,-3 -3,-3 -2,-3 -2,-3 -1,-3 -2,-3 -2,-2 -2,-3 -3,-3 -2,-2 -3,-3 -4,-3 -3,-2 -2,-2 -2,-2 -3,-1 -4,-2 -4,-2 -2,-2 -3,0 -4,-2 -3,-1 -2,0 -1,0 -3,0 -5,0 -2,0 -3,0 -2,0 -3,0 -2,0 -2,0 -2,0 -2,0 -3,0 -3,0 -4,0 -5,1 -7,1 -8,3 -2,1 -2,1 -3,1 -1,1 -2,2 -2,3 -2,3 -3,4 -3,4 -1,4 0,4 0,5 0,5 0,4 0,4 0,4 0,6 1,5 1,3 1,2 2,4 2,3 0,1 3,4 2,1 2,2 3,1 3,1 4,0 4,0 6,0 3,0 8,0 3,0 2,0 9,0 5,0 5,0 7,0 7,0 10,0 7,0 6,0 6,-1 5,0 4,0 5,-1 1,0 6,-1 5,-1 3,-1 8,-3 4,-1 3,-2 3,-2 3,-1 4,-2 4,-3 5,-2 1,-1 4,-2 3,-2 6,-5 5,-3 4,-2 6,-3 4,-3 3,-2 4,-2 4,-3 5,-4 5,-4 1,-1 2,-2" style="stroke-linecap: round; stroke-linejoin: round; fill: #0000CC; fill-opacity: 0.4; fill-rule: nonzero; stroke: none" />
      </g>
    </svg>

Regarding the comments: The doc essentially draws two alpha symbols using a filled path, though the fill-rule of each symbol differs. There isn't an appreciable difference in the appearance, though for more complex shapes that cross back over themselves several times there is a difference in output - all examples fail to replicate a highlighter pen that darkens whenever the filled area passes back over an already-filled area of the same path

Upvotes: 0

Views: 871

Answers (1)

Paul LeBeau
Paul LeBeau

Reputation: 101820

I understand your problem now. Unfortunately fill-rule: nonzero doesn't help in your case because your path doesn't just self-intersect (which would normally be fine), but it also crosses over itself on the left hand side. That messes up the fill rule "nonzero" algorithm.

There isn't really a simple solution either. The best I can suggest is to detect when the highlighter path hits a horizontal inflection point (ie changes direction horizontally) and split the path into two paths (or sub-paths) at that point.

Upvotes: 2

Related Questions