2 Paths
dxtr edited this page 2026-10-18 16:44:24 +02:00

Paths

A path holds a shape's curves in plain arrays, in the shape's own coordinates, as FreeType's FT_Outline holds a glyph's:

(let ((path (cl-svgread:shape-path
             (aref (cl-svgread:drawing-shapes
                    (cl-svgread:read-svg "<svg><path d='M 0 0 L 4 0 Q 4 4 0 4 Z'/></svg>"))
                   0))))
  (list (cl-svgread:path-points path)
        (cl-svgread:path-tags path)
        (cl-svgread:path-contour-ends path)
        (cl-svgread:path-closed path)))
;; => (#(0.0 0.0 4.0 0.0 4.0 4.0 0.0 4.0) #(1 1 0 1) #(3) #*1)
Reader What it holds
path-points Two single-floats for each point, x then y.
path-tags An octet for each point: +on-curve+ (1) for a point the path passes through, +quadratic-control+ (0) for the control point of a quadratic Bézier curve, +cubic-control+ (2) for each of a cubic one's two, which come in pairs.
path-contour-ends For each contour, the index of its last point, as (unsigned-byte 32)s: a contour runs from the point after the previous contour's end.
path-closed A bit vector, 1 for each closed contour.

The tags have FreeType's values, so path-points, path-tags and path-contour-ends describe an FT_Outline as they are: cl-ft2's make-outline takes them.

Contours

A contour starts with a point on the curve. A segment runs from a point on the curve to the next, through the control points between them. A closed contour ends where it started: after its last point comes a segment back to its first, a line if the last point is on the curve, or a curve through the control points it ends with. An open contour ends with a point on the curve. A contour of one point is a subpath of no length, which a stroke with round or square caps draws as a dot.

Curves

Lines, quadratic curves and cubic curves stay as the SVG gives them. Arcs, and the rounded corners, circles and ellipses made of them, become cubic curves, one for each quarter turn or less, which stray from the true ellipse by at most 0.0273 % of its larger radius:

(let ((path (cl-svgread:shape-path
             (aref (cl-svgread:drawing-shapes
                    (cl-svgread:read-svg "<svg><circle cx='8' cy='8' r='4'/></svg>"))
                   0))))
  (list (cl-svgread:path-point-count path)
        (cl-svgread:path-contour-count path)
        (cl-svgread:path-tags path)))
;; => (12 1 #(1 2 2 1 2 2 1 2 2 1 2 2))

Four cubic curves, a quarter turn each, and the last back to the start. A renderer that draws only quadratic curves, such as cl-slug's Slug, turns the cubics into quadratics itself, as cl-textrender does for fonts' outlines.

Contours as lists

(path-contours path) returns the contours as lists of segments with explicit start points, the format cl-sdf and cl-textrender use:

(:line x0 y0 x1 y1)
(:quadratic x0 y0 cx cy x1 y1)
(:cubic x0 y0 c1x c1y c2x c2y x1 y1)

Its second value is a list of booleans, true for each contour the path closes itself. A closed contour ends with the segment back to its start; an open one does not, unless :closed t is given. SVG fills an open contour as if a line closed it, so (path-contours path :closed t) gives the contours as a fill sees them:

(let ((path (cl-svgread:shape-path
             (aref (cl-svgread:drawing-shapes
                    (cl-svgread:read-svg "<svg><polyline points='0 0 4 0 4 4'/></svg>"))
                   0))))
  (list (multiple-value-list (cl-svgread:path-contours path))
        (cl-svgread:path-contours path :closed t)))
;; => ((((:LINE 0.0 0.0 4.0 0.0) (:LINE 4.0 0.0 4.0 4.0))) (NIL))
;;     (((:LINE 0.0 0.0 4.0 0.0) (:LINE 4.0 0.0 4.0 4.0) (:LINE 4.0 4.0 0.0 0.0))))

A contour of one point has no segments.

Bounds and counts

  • (path-bounds path &optional transform) returns the box around every point of the path, control points included, as four values xmin ymin xmax ymax: in the path's coordinates, or transformed by transform, such as the shape's. The box holds the curves, though it may be larger than they are; all four are zero for a path with no points. A transform that takes the box beyond what single-floats hold is an svg-error that says so.
  • path-point-count and path-contour-count count the points, control points included, and the contours.