Path

Responsibility

Path is the freeform vector primitive. It contains one or more named contours, and each contour contains an explicit start point followed by line, quadratic, cubic or arc segments. A path can be filled, stroked, or both.

Complete path layer

{
  "id": "ribbon",
  "type": "path",
  "parentLayerId": null,
  "order": 0,
  "frame": { "width": 720, "height": 420 },
  "transform": {
    "translate": { "x": 360, "y": 240, "z": 0 },
    "rotate": { "x": 0, "y": 0, "z": -4 },
    "scale": { "x": 1, "y": 1, "z": 1 },
    "anchor": { "x": 360, "y": 210, "z": 0 },
    "skew": { "x": 0, "y": 0 }
  },
  "timing": { "start": 0, "duration": 5000 },
  "payload": {
    "fillRule": "nonzero",
    "pathLength": 1000,
    "contours": [{
      "id": "main-ribbon",
      "start": { "x": 40, "y": 300 },
      "closed": true,
      "segments": [
        {
          "kind": "cubic",
          "control1": { "x": 180, "y": 40 },
          "control2": { "x": 420, "y": 40 },
          "to": { "x": 680, "y": 250 }
        },
        {
          "kind": "quadratic",
          "control": { "x": 430, "y": 360 },
          "to": { "x": 40, "y": 300 }
        }
      ]
    }]
  },
  "style": {
    "fill": "#6257FF",
    "fillOpacity": 0.9,
    "stroke": "#D8FFF7",
    "strokeWidth": 5,
    "strokeOpacity": 1,
    "strokeJoin": "round",
    "strokeCap": "round"
  }
}

Contours and segments

SegmentRequired fieldsMeaning
linetoStraight line from the current point
quadraticcontrol, toOne control point
cubiccontrol1, control2, toTwo control points
arcradii, rotation, largeArc, sweep, toElliptical arc using SVG arc flags

Every contour requires id, start, at least one segments entry and closed. Segment to points become the next current point. A closed contour implicitly connects its final point back to start; an open contour remains open for stroke rendering.

{
  "contours": [{
    "id": "mixed-segments",
    "start": { "x": 80, "y": 180 },
    "closed": false,
    "segments": [
      { "kind": "line", "to": { "x": 180, "y": 80 } },
      { "kind": "quadratic", "control": { "x": 260, "y": 20 }, "to": { "x": 360, "y": 100 } },
      { "kind": "cubic", "control1": { "x": 440, "y": 180 }, "control2": { "x": 520, "y": 180 }, "to": { "x": 620, "y": 80 } },
      { "kind": "arc", "radii": { "x": 60, "y": 40 }, "rotation": 0, "largeArc": false, "sweep": true, "to": { "x": 700, "y": 160 } }
    ]
  }]
}

Fill rules and holes

nonzero and evenodd decide how multiple closed contours produce filled regions. Use evenodd for the common “outer contour plus inner hole” case:

{
  "payload": {
    "fillRule": "evenodd",
    "contours": [
      {
        "id": "outer",
        "start": { "x": 40, "y": 40 },
        "closed": true,
        "segments": [
          { "kind": "line", "to": { "x": 360, "y": 40 } },
          { "kind": "line", "to": { "x": 360, "y": 260 } },
          { "kind": "line", "to": { "x": 40, "y": 260 } }
        ]
      },
      {
        "id": "hole",
        "start": { "x": 120, "y": 100 },
        "closed": true,
        "segments": [
          { "kind": "line", "to": { "x": 280, "y": 100 } },
          { "kind": "line", "to": { "x": 280, "y": 200 } },
          { "kind": "line", "to": { "x": 120, "y": 200 } }
        ]
      }
    ]
  }
}

Stroke and drawing controls

Path style uses the same paint model as Shape:

FieldMeaning
fill, fillOpacityInterior paint and alpha
stroke, strokeWidth, strokeOpacityOutline paint, width and alpha
strokeJoinmiter, bevel or round
strokeCapbutt, square or round
strokeDashDash array and offset
paintOrderFill, stroke and marker ordering
vectorEffectnone or non-scaling-stroke
markerStart/Mid/EndReferences to root marker definitions

pathLength is an optional calibration length used by dash and motion semantics. It does not alter the geometry coordinates.

Morphing between paths

A path can carry a compatible target contour set and a normalized morph.progress value:

{
  "payload": {
    "contours": [{
      "id": "start",
      "start": { "x": 80, "y": 80 },
      "closed": true,
      "segments": [
        { "kind": "line", "to": { "x": 280, "y": 80 } },
        { "kind": "line", "to": { "x": 180, "y": 260 } }
      ]
    }],
    "morph": {
      "progress": 0.5,
      "targetContours": [{
        "id": "target",
        "start": { "x": 80, "y": 160 },
        "closed": true,
        "segments": [
          { "kind": "line", "to": { "x": 280, "y": 160 } },
          { "kind": "line", "to": { "x": 180, "y": 40 } }
        ]
      }]
    }
  }
}

Morphing requires matching contour and segment structure. If the topology differs, author a separate path or use a layer transition instead of expecting the compiler to invent correspondence.