Skip to content

SVG paths with relative moveto drawn incorrectly #2377

Description

@pxfx

Summary

Paths in an SVG file are not drawn correctly in Processing (PShape Implementation) (v 2.1.1, 32-bit and 64-bit on Windows 7) when the path contains more than one relative moveto instruction (m).

Section 8.3.2 of the W3C SVG recommendation specifies that

If a relative moveto (m) appears as the first element of the path, then it is treated as a pair of absolute coordinates. In this case, subsequent pairs of coordinates are treated as relative even though the initial moveto is interpreted as an absolute moveto.

When a path begins with a relative moveto, Processing treats it as relative to the last absolute path. More generally, Processing uses the starting point of the last absolute subpath (or (0,0) if none is defined) for relatively-defined moveto commands.

Demonstration

The following SVG file is an example of a file that will not draw correctly as a result of this issue:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="80" height="60">
<path d="m 20,20 0,10 10,0 0,-10 z
         m 15,0 0,10 10,0 0,-10 z
         m 15,0 0,10 10,0 0,-10 z" />
</svg>

The expected output is three horizontally-aligned squares:

image

The Processing output is two offset squares (the second and third subpaths are overlaid).

image

void draw() {
  size(80, 60);
  background(255);
  shape(loadShape("<path to svg>"));
}

The issue can also be observed in running the USA map example on the Wikipedia Processing page. The states of Virginia and Hawaii are offset from their expected positions, with shapes clustered in the top left corner instead:

image

Patch

The error appears to be in processing.core.PShapeSvg beginning at line 586:

      case 'm':  // m - move to (relative)
        cx = cx + PApplet.parseFloat(pathTokens[i + 1]);
        cy = cy + PApplet.parseFloat(pathTokens[i + 2]);
        parsePathMoveto(cx, cy);
        implicitCommand = 'l';
        i += 3;
        break;

The movetoX and movetoY variables should be set as the initial point of the new subpath:

      case 'm':  // m - move to (relative)
        cx = cx + PApplet.parseFloat(pathTokens[i + 1]);
        cy = cy + PApplet.parseFloat(pathTokens[i + 2]);
        // need to add next two lines
        movetoX = cx;
        movetoY = cy;
        parsePathMoveto(cx, cy);
        implicitCommand = 'l';
        i += 3;
        break;

Workaround

The issue can be worked around by replacing the relative moveto commands with an absolute moveto command followed by a relative path beginning with (0, 0). For example,

<path d="m 20,20 0,10 10,0 0,-10 z
         m 15,0 0,10 10,0 0,-10 z
         m 15,0 0,10 10,0 0,-10 z" />

can be equivalently written as:

<path d="M 20, 20 m 0,0 0,10 10,0 0,-10 z
         M 35, 20 m 0,0 0,10 10,0 0,-10 z
         M 50, 20 m 0,0 0,10 10,0 0,-10 z" />

This will draw correctly in Processing and other SVG viewers. It requires that the absolute starting point of each subpath be computed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions