Paths

Paths are the geometric backbone of PyX. A path describes a sequence of connected or disconnected line segments, circular or elliptical arcs, and cubic Bézier curves in the plane.

PyX provides two complementary representations of paths:

  1. path.path: A PostScript-style path composed of arbitrary pathitem instances (moveto, lineto, curveto, arc, closepath, etc.). When written to PostScript or SVG, these path items map directly to native PostScript operators or SVG path commands.

  2. normpath.normpath: A normalized path consisting of a list of normpath.normsubpath objects, which in turn contain only straight lines (normpath.normline_pt) and cubic Bézier curves (normpath.normcurve_pt) in absolute point coordinates, subject to a numerical accuracy epsilon (default \(10^{-5}\,\text{pt}\)). All advanced geometric operations—such as computing arc lengths, finding intersections, splitting paths, or evaluating tangents and curvatures—are performed on normalized paths.

Because path.path automatically delegates geometric queries to its cached path.normpath() representation, you can call methods like path.intersect(), path.at(), path.split(), or path.tangent() directly on any path.path instance.

Constructing paths

A path can be constructed either by passing pathitem instances to path or by using one of the predefined shape classes such as line, rect, circle, ellipse, or curve.

For example, to construct a triangle with a rounded top corner and intersect it with a circle:

from pyx import *

c = canvas.canvas()
triangle = path.path(path.moveto(0, 0),
                     path.lineto(4, 0),
                     path.arct(2, 3, 0, 0, 0.5),
                     path.closepath())
circle = path.circle(2, 1, 1.5)

c.stroke(triangle)
c.stroke(circle, [color.rgb.blue])

# Mark all intersection points with small red circles
for p in triangle.intersect(circle)[0]:
    x, y = triangle.at(p)
    c.fill(path.circle(x, y, 0.08), [color.rgb.red])

c.writePDFfile("intersect")

Two paths can be combined in two different ways:

  • Addition (p = p1 + p2 or p1 += p2): Concatenates the subpaths of p1 and p2 into a single compound path without connecting the end of p1 to the start of p2.

  • Joining (p = p1 << p2 or p = p1.joined(p2) or p1.join(p2)): Connects the end of the last open subpath of p1 to the beginning of the first open subpath of p2 (inserting a straight line segment if the two endpoints do not coincide) so that they form a single continuous subpath.

Path items

Every element passed to path must be an instance of a subclass of pathitem. Each coordinate-accepting class has both a standard version (taking coordinates in PyX user lengths or explicit unit lengths) and a _pt version (taking coordinates in PostScript points).

Subpath control

class path.moveto(x, y)
class path.moveto_pt(x_pt, y_pt)

Starts a new subpath and sets the current point to \((x, y)\).

class path.rmoveto(dx, dy)
class path.rmoveto_pt(dx_pt, dy_pt)

Starts a new subpath and moves the current point by \((\Delta x, \Delta y)\) relative to the previous current point.

class path.closepath

Closes the current subpath by connecting the current point back to the starting point of the subpath with a straight line segment.

Straight lines

class path.lineto(x, y)
class path.lineto_pt(x_pt, y_pt)

Appends a straight line segment from the current point to \((x, y)\), which becomes the new current point.

class path.rlineto(dx, dy)
class path.rlineto_pt(dx_pt, dy_pt)

Appends a straight line segment from the current point \((x_0, y_0)\) to \((x_0 + \Delta x, y_0 + \Delta y)\), which becomes the new current point.

class path.multilineto_pt(points_pt)

Appends straight line segments from the current point through a sequence of coordinate pairs [(x1_pt, y1_pt), (x2_pt, y2_pt), ...] in points. This class is provided for performance when constructing polylines with many vertices.

Circular arcs

class path.arc(x, y, r, angle1, angle2)
class path.arc_pt(x_pt, y_pt, r_pt, angle1, angle2)

Appends a counterclockwise circular arc centered at \((x, y)\) with radius r from angle1 to angle2 (in degrees). If the path already has a current point in an open subpath, a straight line segment is first added from the current point to the starting point of the arc; otherwise, the arc starts a new subpath. After the arc is added, the current point is the endpoint of the arc at angle2.

class path.arcn(x, y, r, angle1, angle2)
class path.arcn_pt(x_pt, y_pt, r_pt, angle1, angle2)

Same as arc, except that the arc is drawn in the clockwise direction from angle1 down to angle2.

class path.arct(x1, y1, x2, y2, r)
class path.arct_pt(x1_pt, y1_pt, x2_pt, y2_pt, r_pt)

Appends a circular arc of radius r that is tangent both to the line from the current point \((x_0, y_0)\) to \((x_1, y_1)\) and to the line from \((x_1, y_1)\) to \((x_2, y_2)\). A straight line segment is first drawn from \((x_0, y_0)\) to the first point of tangency \((x_{t,1}, y_{t,1})\), followed by the tangent arc ending at the second point of tangency \((x_{t,2}, y_{t,2})\), which becomes the new current point:

_images/arct.png

Bézier curves

class path.curveto(x1, y1, x2, y2, x3, y3)
class path.curveto_pt(x1_pt, y1_pt, x2_pt, y2_pt, x3_pt, y3_pt)

Appends a cubic Bézier curve from the current point \((x_0, y_0)\) to \((x_3, y_3)\) with first control point \((x_1, y_1)\) and second control point \((x_2, y_2)\). The parametric curve is given by

\[\mathbf{B}(t) = (1-t)^3 \mathbf{P}_0 + 3(1-t)^2 t \mathbf{P}_1 + 3(1-t) t^2 \mathbf{P}_2 + t^3 \mathbf{P}_3, \quad t \in [0, 1].\]

After the curve is added, \((x_3, y_3)\) becomes the new current point.

class path.rcurveto(dx1, dy1, dx2, dy2, dx3, dy3)
class path.rcurveto_pt(dx1_pt, dy1_pt, dx2_pt, dy2_pt, dx3_pt, dy3_pt)

Appends a cubic Bézier curve whose control points and endpoint are specified relative to the current point \((x_0, y_0)\) before the operation: \((x_0 + \Delta x_1, y_0 + \Delta y_1)\), \((x_0 + \Delta x_2, y_0 + \Delta y_2)\), and \((x_0 + \Delta x_3, y_0 + \Delta y_3)\).

class path.multicurveto_pt(points_pt)

Appends multiple cubic Bézier curves from a sequence of 6-tuples [(x1_pt, y1_pt, x2_pt, y2_pt, x3_pt, y3_pt), ...] in points, provided for performance when constructing paths with many curve segments.

Class path

class path.path(*pathitems)

Constructs a path from the given pathitems.

A path instance supports sequence indexing (p[i] returns the i-th pathitem), len(p) (number of path items), p1 + p2 and p1 += p2 (concatenating subpaths), and p1 << p2 (joining paths via joined()).

Note

In the methods below, any parameter params that specifies a location (or a list/tuple of locations) along the path accepts either:

Furthermore, methods decorated to accept params or lengths (such as at(), arclentoparam(), paramtoarclen(), curvature_pt(), rotation(), tangent(), and trafo()) return a single value when passed a single parameter, and a list of values when passed a sequence of parameters.

append(apathitem)

Appends a single pathitem to the path in place.

extend(pathitems)

Appends an iterable of pathitem instances to the path in place.

arclen()
arclen_pt()

Returns the total arc length of the path as a PyX length (or in points).

arclentoparam(lengths)
arclentoparam_pt(lengths_pt)

Returns the normpath.normpathparam instance(s) corresponding to the given arc length(s).

paramtoarclen(params)
paramtoarclen_pt(params)

Returns the arc length(s) (as PyX lengths or in points) corresponding to the given normpath.normpathparam instance(s).

at(params)
at_pt(params)

Returns the coordinate tuple (x, y) of PyX lengths (or (x_pt, y_pt) in points) at the specified parameter(s) or arc length(s).

atbegin()
atbegin_pt()

Returns the coordinate tuple (x, y) (or (x_pt, y_pt)) of the start point of the first subpath.

atend()
atend_pt()

Returns the coordinate tuple (x, y) (or (x_pt, y_pt)) of the end point of the last subpath.

begin()

Returns the normpath.normpathparam corresponding to the beginning of the path.

end()

Returns the normpath.normpathparam corresponding to the end of the path.

bbox()

Returns the exact bbox.bbox enclosing the path.

curvature_pt(params)

Returns the signed curvature in \(\text{pt}^{-1}\) at the specified parameter(s) or arc length(s) in points. The curvature is positive when the path turns to the left (counterclockwise) and negative when it turns to the right.

intersect(other)

Intersects the path with other (another path or normpath.normpath) and returns a 2-tuple (params_self, params_other) of lists of normpath.normpathparam instances for the intersection points along self and other, sorted along self.

join(other)

Joins other to the end of self in place and returns self.

joined(other)

Returns a new path consisting of self and other joined together (also available via the << operator: self << other).

normpath(epsilon=...)

Converts the path into a normpath.normpath instance. When epsilon is omitted, the default normalized path is cached on the path instance.

path()

Returns self (provided so that path and normpath.normpath can be used interchangeably).

reversed()

Returns a new normpath.normpath with the orientation of all subpaths reversed.

rotation(params)
rotation_pt(params)

Returns a trafo.trafo rotation at params (or arc lengths) that rotates the positive x-axis onto the unit tangent vector of the path and the positive y-axis onto the left-hand normal vector.

split(params)
split_pt(params)

Splits the normalized path at the given parameter(s) or arc length(s) and returns a list of normpath.normpath segments. Note thateven when a single split point is passed, a list of two path segments is returned.

tangent(params, length=1)
tangent_pt(params, length_pt)

Returns a normpath.normpath line segment of length length (default 1 user unit) starting at the point params and pointing in the tangent direction of the path.

trafo(params)
trafo_pt(params)

Returns a trafo.trafo transformation at params (or arc lengths) that maps the origin \((0, 0)\) to the point on the path and rotates the coordinate axes so that the local x-axis points along the path tangent and the local y-axis points along the left-hand normal.

transformed(trafo)

Returns a new normpath.normpath transformed according to the affine transformation trafo.

Normalized paths: normpath

While casual users rarely need to instantiate classes from normpath directly, PyX developers and advanced users working with intersections, path splitting, or custom decorators and deformers interact closely with normpath, normsubpath, and normpathparam.

Why normalized paths?

PostScript paths can contain arcs, relative displacements, and unclosed or implicitly connected subpaths. Evaluating intersections or arc-length integrals directly on such heterogeneous items would be error-prone and slow. PyX therefore normalizes any path.path into a normpath:

  • A normpath is a list of normsubpath instances.

  • Each normsubpath is a contiguous open or closed chain of normsubpathitem instances—exclusively normline_pt (straight lines) and normcurve_pt (cubic Bézier curves)—together with a precision length epsilon (default \(10^{-5}\,\text{pt}\)).

  • Line or curve segments shorter than epsilon are accumulated as a skippedline rather than creating degenerate zero-length items with undefined tangents, and are merged into the adjacent segment once the accumulated displacement exceeds epsilon.

normpath.set(epsilon=None)

Sets the global default accuracy epsilon in points (initially 1e-5) used when constructing new normsubpath instances.

Class normpathparam

class normpath.normpathparam(normpath, normsubpathindex, normsubpathparam)

Identifies a point along normpath. Here normsubpathindex is the integer index of the normsubpath within normpath, and normsubpathparam is a floating-point parameter \(t\) along that subpath, where the integer part \(\lfloor t \rfloor\) selects the normsubpathitem and the fractional part \(t - \lfloor t \rfloor \in [0, 1]\) is the parameter on that line or Bézier curve.

normpathparam instances support rich arithmetic and comparisons via arc length:

  • Adding or subtracting a PyX length (e.g. p + 0.5*unit.t_cm or p - 1) moves along the path by that arc length and returns the resulting normpathparam!

  • Adding or subtracting two normpathparam instances belonging to the same normpath, or multiplying/dividing a normpathparam by a scalar factor, operates on their arc lengths and returns the corresponding normpathparam.

  • Comparing two normpathparam instances (p1 < p2, p1 == p2) compares their positions (normsubpathindex, normsubpathparam) along the path; comparing a normpathparam with a length compares its arc length from the start of the path with that length.

arclen()
arclen_pt()

Returns the arc length from the beginning of the normpath up to this parameter position as a PyX length (or in points).

Class normpath

class normpath.normpath(normsubpaths=None)

Constructs a normalized path from a list of normsubpath instances (or an empty list if None).

normpath provides all the geometric and manipulation methods of path.path (path.path.append(), path.path.extend(), path.path.arclen(), path.path.arclen_pt(), path.path.arclentoparam(), path.path.arclentoparam_pt(), path.path.at(), path.path.at_pt(), path.path.atbegin(), path.path.atbegin_pt(), path.path.atend(), path.path.atend_pt(), path.path.bbox(), path.path.begin(), path.path.end(), path.path.curvature_pt(), path.path.intersect(), path.path.join(), path.path.joined(), path.path.normpath(), path.path.paramtoarclen(), path.path.paramtoarclen_pt(), path.path.reversed(), path.path.rotation(), path.path.rotation_pt(), path.path.split(), path.path.split_pt(), path.path.tangent(), path.path.tangent_pt(), path.path.trafo(), path.path.trafo_pt(), and path.path.transformed()), plus:

copy()

Returns a deep copy of the normpath and all its subpaths.

path()

Converts the normpath back into an equivalent path.path consisting of path.moveto_pt, path.lineto_pt, path.curveto_pt, and path.closepath items.

Indexing np[i] returns the i-th normsubpath, and len(np) returns the number of subpaths.

Class normsubpath and subpath items

class normpath.normsubpath(normsubpathitems=[], closed=0, epsilon=_marker)

Represents a single connected open or closed normalized subpath.

  • normsubpathitems: List of normline_pt and normcurve_pt instances. Adjacent items must share endpoints within epsilon.

  • closed: Boolean or integer flag indicating whether the subpath is closed.

  • epsilon: Precision threshold in points (defaults to the module-level normpath._epsilon = 1e-5, or None to disable filtering of short segments).

Key attributes and methods:

normsubpathitems

The list of normline_pt and normcurve_pt items in the subpath.

closed

Whether the subpath is closed.

epsilon

Accuracy threshold in points for this subpath.

append(anormsubpathitem)
extend(normsubpathitems)

Appends one or more normsubpathitem instances to an open subpath, automatically coalescing segments shorter than epsilon.

close()

Closes the subpath in place, appending a closing normline_pt back to the start point if necessary.

segments(params)

Takes a sequence of at least two subpath parameter floats and returns a list of normsubpath instances for the segments between consecutive parameters.

pathitems()

Returns the list of path.pathitem instances representing this subpath.

class normpath.normline_pt(x0_pt, y0_pt, x1_pt, y1_pt)

A normalized straight line segment from (x0_pt, y0_pt) to (x1_pt, y1_pt) in points.

class normpath.normcurve_pt(x0_pt, y0_pt, x1_pt, y1_pt, x2_pt, y2_pt, x3_pt, y3_pt)

A normalized cubic Bézier curve segment with control points (x0_pt, y0_pt) through (x3_pt, y3_pt) in points.

Predefined shapes

For convenience, path provides subclasses of path for common geometric primitives, each in a user-length version and a _pt version:

class path.line(x1, y1, x2, y2)
class path.line_pt(x1_pt, y1_pt, x2_pt, y2_pt)

A straight line segment from \((x_1, y_1)\) to \((x_2, y_2)\).

class path.curve(x0, y0, x1, y1, x2, y2, x3, y3)
class path.curve_pt(x0_pt, y0_pt, x1_pt, y1_pt, x2_pt, y2_pt, x3_pt, y3_pt)

A single cubic Bézier curve from \((x_0, y_0)\) to \((x_3, y_3)\) with control points \((x_1, y_1)\) and \((x_2, y_2)\).

class path.rect(x, y, width, height)
class path.rect_pt(x_pt, y_pt, width_pt, height_pt)

A closed axis-aligned rectangle with lower-left corner \((x, y)\), width, and height.

class path.circle(x, y, radius, arcepsilon=0.1)
class path.circle_pt(x_pt, y_pt, radius_pt, arcepsilon=0.1)

A closed circle with center \((x, y)\) and radius (constructed from a counterclockwise arc from arcepsilon to 360 - arcepsilon degrees closed with closepath).

class path.ellipse(x, y, a, b, angle, **kwargs)
class path.ellipse_pt(x_pt, y_pt, a_pt, b_pt, angle, **kwargs)

A closed ellipse centered at \((x, y)\) with semi-axes a and b, where the first axis a is rotated counterclockwise by angle degrees relative to the positive x-axis.