tituli.geometry

Boxes, anchors, safe areas and parametric paths — the coordinate vocabulary.

Two coordinate systems, deliberately:

  • normalised (x, y, w, h) in 0..1 of the frame, top-left origin — the resolution-independent form used by specs, by burns.salient_box and by the lacing body. This is what a request is written in.

  • pixel Box (x0, y0, x1, y1) floats — what layout and rendering compute in once a Frame size is known.

A Path is a curve you can lay glyphs along: point(s) and angle(s) by arc length. Constructors cover lines, arcs, waves, Béziers, polylines and SVG d strings, which is what lets a calligram be any shape rather than a hard-coded sine.

>>> b = Box(10, 20, 110, 70)
>>> b.width, b.height, b.center
(100, 50, (60.0, 45.0))
>>> Box.from_norm((0.25, 0.5, 0.5, 0.25), 1920, 1080)
Box(x0=480.0, y0=540.0, x1=1440.0, y1=810.0)
>>> p = Path.line((0, 0), (100, 0))
>>> p.length, p.point(50), p.angle(50)
(100.0, (50.0, 0.0), 0.0)

Functions

anchor_box(region, size, anchor)

Place a block of size inside region at a named anchor.

safe_area(width, height, *[, fraction])

The centred box holding fraction of each dimension.

Classes

Box(x0, y0, x1, y1)

A pixel-space rectangle (x0, y0, x1, y1); edges are floats.

Path(points)

A polyline with an arc-length parameterisation.

class tituli.geometry.Box(x0, y0, x1, y1)[source]

Bases: object

A pixel-space rectangle (x0, y0, x1, y1); edges are floats.

classmethod from_norm(nb, width, height)[source]

Scale a normalised (x, y, w, h) into a frame of that size.

Return type:

Box

classmethod from_size(x, y, w, h)[source]

Build from an origin and a size.

Return type:

Box

inset(dx, dy=None)[source]

Shrink by dx horizontally and dy (default dx) vertically.

Return type:

Box

overlap_fraction(other)[source]

Fraction of this box’s area covered by other.

Return type:

float

>>> Box(0, 0, 10, 10).overlap_fraction(Box(5, 0, 20, 10))
0.5
to_norm(width, height)[source]

The inverse of from_norm().

Return type:

tuple[float, float, float, float]

class tituli.geometry.Path(points)[source]

Bases: object

A polyline with an arc-length parameterisation.

Build one with the constructors (line(), arc(), wave(), bezier(), polyline(), from_svg(), from_function()) and query it by distance along the curve: point(s) and angle(s) (degrees, clockwise-positive on screen because y points down).

angle(s)[source]

Tangent direction at s in degrees (0 = rightwards, 90 = down).

Return type:

float

classmethod arc(center, radius, start_deg, end_deg, *, samples=512)[source]

Circular arc; angles in degrees, 0 = right, 90 = down (screen coords).

Return type:

Path

classmethod bezier(*controls, samples=512)[source]

A Bézier curve of any degree through controls (De Casteljau).

Return type:

Path

classmethod circle(center, radius, *, start_deg=-90.0)[source]

A full circle starting at the top by default, running clockwise.

Return type:

Path

property closed: bool

Whether the path ends where it starts (a circle, a closed SVG path).

>>> Path.circle((0, 0), 10).closed, Path.line((0, 0), (1, 1)).closed
(True, False)
fit(box, *, keep_aspect=True)[source]

Scale and translate so the path’s bounding box fills box.

Degenerate axes (a horizontal line has zero height) are centred rather than scaled.

Return type:

Path

classmethod from_function(fn, *, samples=512)[source]

Sample fn(t) for t in [0, 1].

Return type:

Path

classmethod from_svg(d)[source]

Parse an SVG path d string (M L H V C S Q T Z, absolute or relative).

Arcs (A) are not supported — approximate them with a Bézier.

Return type:

Path

>>> Path.from_svg("M 0 0 L 10 0 L 10 10").length
20.0
point(s)[source]

Position at arc length s (clamped to the path).

Return type:

tuple[float, float]

classmethod wave(start, end, *, amplitude, cycles=1.0, samples=512)[source]

A sine wave along the segment start -> end.

Return type:

Path

tituli.geometry.anchor_box(region, size, anchor)[source]

Place a block of size inside region at a named anchor.

Return type:

Box

>>> anchor_box(Box(0, 0, 100, 100), (20, 10), "bottom-right")
Box(x0=80.0, y0=90.0, x1=100.0, y1=100.0)
tituli.geometry.safe_area(width, height, *, fraction=0.9)[source]

The centred box holding fraction of each dimension.

Return type:

Box

>>> safe_area(1000, 500)
Box(x0=50.0, y0=25.0, x1=950.0, y1=475.0)