mp

package
v0.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 7, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

Documentation

Overview

Package mp implements MetaPost's Hobby-Knuth curve-solving algorithm in Go.

This package is a port of the core MetaPost engine, providing the same smooth curve generation that MetaPost is famous for. Given a sequence of points with optional direction and tension constraints, the solver computes optimal cubic Bézier control points.

Architecture

The package is organized around these core concepts:

  • Knot: A point on a path with coordinates, control points, and type information
  • Path: A linked list of knots forming an open or closed curve
  • Engine: The solver that computes Bézier control points
  • Transform: Affine transformations (scale, rotate, shift, etc.)

Quick Start

The simplest way to create paths is using the higher-level draw package:

import "github.com/boxesandglue/mpgo/draw"

path, _ := draw.NewPath().
    MoveTo(mp.P(0, 0)).
    CurveTo(mp.P(100, 50)).
    CurveTo(mp.P(200, 0)).
    Solve()

For direct use of the mp package:

engine := mp.NewEngine()
path := mp.NewPath()
// ... add knots ...
engine.AddPath(path)
engine.Solve()

Predefined Paths

The package provides MetaPost's standard path primitives:

mp.FullCircle()    // Unit circle (diameter 1) centered at origin
mp.HalfCircle()    // Upper half of unit circle
mp.QuarterCircle() // First quadrant arc
mp.UnitSquare()    // Unit square from (0,0) to (1,1)

These can be transformed using Transform:

circle := mp.FullCircle()
circle = mp.Scaled(50).ApplyToPath(circle)           // Scale to diameter 50
circle = mp.Shifted(100, 100).ApplyToPath(circle)    // Move center to (100,100)

Transformations

Affine transformations mirror MetaPost's transform operations:

mp.Scaled(s)           // Uniform scaling
mp.XScaled(s)          // Horizontal scaling
mp.YScaled(s)          // Vertical scaling
mp.Shifted(dx, dy)     // Translation
mp.Rotated(degrees)    // Rotation around origin
mp.RotatedAround(p, d) // Rotation around point p
mp.Slanted(s)          // Slant (shear) transformation

Transformations can be combined:

t := mp.Scaled(2).Concat(mp.Rotated(45)).Concat(mp.Shifted(10, 20))
path = t.ApplyToPath(path)

Points and Colors

Helper functions for creating points and colors:

mp.P(x, y)              // Create a point
mp.ColorRGB(r, g, b)    // RGB color (0-1 range)
mp.ColorCSS("red")      // CSS color name or hex
mp.ColorCMYK(c, m, y, k) // CMYK color

Labels

Text labels can be attached to points with anchor positioning:

label := mp.NewLabel("A", mp.P(0, 0), mp.AnchorLowerLeft)

Available anchors match MetaPost's label suffixes:

mp.AnchorCenter      // label(s, z)
mp.AnchorLeft        // label.lft(s, z)
mp.AnchorRight       // label.rt(s, z)
mp.AnchorTop         // label.top(s, z)
mp.AnchorBottom      // label.bot(s, z)
mp.AnchorUpperLeft   // label.ulft(s, z)
mp.AnchorUpperRight  // label.urt(s, z)
mp.AnchorLowerLeft   // label.llft(s, z)
mp.AnchorLowerRight  // label.lrt(s, z)

Labels can be converted to glyph outline paths using the font package:

import "github.com/boxesandglue/mpgo/font"

face, _ := font.Load(fontFile)
paths, _ := label.ToPaths(face)

Path Styling

Paths have a Style field for stroke, fill, and other attributes:

path.Style.Stroke = mp.ColorCSS("blue")
path.Style.Fill = mp.ColorCSS("yellow")
path.Style.StrokeWidth = 2.0
path.Style.Dash = &mp.DashPattern{Array: []float64{5, 3}}
path.Style.Arrow.End = true  // Arrow at end of path

Path Operations

The package provides path manipulation functions:

mp.Reverse(path)           // Reverse path direction
mp.Subpath(path, t1, t2)   // Extract portion of path
mp.ArcLength(path)         // Total arc length
mp.PointAt(path, t)        // Point at parameter t
mp.DirectionAt(path, t)    // Tangent direction at t

Pens and Envelopes

Non-circular pens create envelope paths (like MetaPost's pencircle transformations):

pen := mp.PenCircle()
pen = mp.XScaled(3).ApplyToPen(pen)  // Elliptical pen
path.Style.Pen = pen

References

This implementation follows the algorithms described in:

  • John D. Hobby, "Smooth, Easy to Compute Interpolating Splines" (1986)
  • Donald E. Knuth, "The METAFONTbook" (1986)
  • The MetaPost source code (mp.w, mp.c)

Index

Constants

View Source
const (
	LineCapDefault = 0 // Unset - uses MetaPost default (rounded)
	LineCapButt    = 1 // MetaPost linecap 0
	LineCapRounded = 2 // MetaPost linecap 1 (MetaPost default)
	LineCapSquared = 3 // MetaPost linecap 2
)

LineCap constants. Values are offset by 1 so that Go's zero value (0) means "unset" and defaults to LineCapRounded (matching MetaPost's default). Internal MetaPost values: butt=0, rounded=1, squared=2.

View Source
const (
	LineJoinDefault = 0 // Unset - uses MetaPost default (rounded)
	LineJoinMiter   = 1 // MetaPost linejoin 0
	LineJoinRound   = 2 // MetaPost linejoin 1 (MetaPost default)
	LineJoinBevel   = 3 // MetaPost linejoin 2
)

LineJoin constants. Note: MetaPost's default is linejoin=1 (rounded), not 0 (mitered). Go's zero value means "unset", which defaults to rounded (MetaPost behavior).

View Source
const (
	DefaultAHLength = 4.0  // default arrowhead length (4bp)
	DefaultAHAngle  = 45.0 // default arrowhead angle (45 degrees)
)

Arrow constants (MetaPost defaults from plain.mp)

View Source
const DefaultDotLabelDiam = 3.0

DefaultDotLabelDiam is the default diameter for dots in DotLabel. Mirrors MetaPost's dotlabeldiam (3bp in plain.mp).

View Source
const DefaultFontSize = 10.0

DefaultFontSize is the default font size for labels. Corresponds to MetaPost's defaultscale with cmr10 (10pt).

View Source
const DefaultLabelOffset = 3.0

DefaultLabelOffset is the default distance between the reference point and the label text. Mirrors MetaPost's labeloffset (3bp in plain.mp).

Variables

This section is empty.

Functions

func Distance

func Distance(a, b Point) float64

Distance returns the Euclidean distance between two points.

func LabelAnchorFactors added in v0.1.2

func LabelAnchorFactors(anchor Anchor) (xf, yf float64)

LabelAnchorFactors returns the anchor factors (labxf, labyf) for positioning. These determine which point of the label's bounding box is placed at the offset position. Values mirror MetaPost's labxf/labyf from plain.mp.

Returns (xf, yf) where:

  • xf=0 means left edge of text, xf=1 means right edge, xf=0.5 means center
  • yf=0 means top edge of text, yf=1 means bottom edge, yf=0.5 means middle

func LabelOffsetVector added in v0.1.2

func LabelOffsetVector(anchor Anchor) (dx, dy float64)

LabelOffsetVector returns the offset direction vector for an anchor. These values mirror MetaPost's laboff pairs from plain.mp.

func PathPoints

func PathPoints(path *Path) [][2]Number

PathPoints collects knot coordinates (ignores controls) for polygon export.

func PenEnvelopeHull

func PenEnvelopeHull(path *Path, pen *Pen) [][2]Number

PenEnvelopeHull builds a convex hull over the pen translated to each knot position of the given path. This is a coarse approximation of the swept area MetaPost computes in the offset phase for non-elliptical pens (mp_offset_prep/mp_apply_offset, mp.c ~15800ff). For now this serves to emit a fill outline instead of stroking.

Types

type Anchor added in v0.1.2

type Anchor int

Anchor specifies the positioning of a label relative to its reference point. These mirror MetaPost's label suffixes (.lft, .rt, .top, .bot, etc.).

const (
	AnchorCenter     Anchor = iota // label(s, z) - centered at z
	AnchorLeft                     // label.lft(s, z) - label to the left of z
	AnchorRight                    // label.rt(s, z) - label to the right of z
	AnchorTop                      // label.top(s, z) - label above z
	AnchorBottom                   // label.bot(s, z) - label below z
	AnchorUpperLeft                // label.ulft(s, z) - label upper-left of z
	AnchorUpperRight               // label.urt(s, z) - label upper-right of z
	AnchorLowerLeft                // label.llft(s, z) - label lower-left of z
	AnchorLowerRight               // label.lrt(s, z) - label lower-right of z
)

type ArrowStyle

type ArrowStyle struct {
	Start  bool   // arrow at start of path (for drawdblarrow)
	End    bool   // arrow at end of path (for drawarrow)
	Length Number // ahlength - arrow head length
	Angle  Number // ahangle - arrow head angle in degrees
}

ArrowStyle defines arrow head appearance.

type Color

type Color struct {
	// contains filtered or unexported fields
}

Color carries color information in an output-agnostic way. CSS() returns a CSS-compatible color string for backends like SVG; Opacity() exposes a separate opacity channel when set.

func ColorCMYK

func ColorCMYK(c, m, y, k float64) Color

ColorCMYK converts CMYK [0,1] to RGB and formats an rgb() string.

func ColorCSS

func ColorCSS(css string) Color

ColorCSS uses the provided CSS color string verbatim.

func ColorGray

func ColorGray(gray float64) Color

ColorGray builds a grayscale rgb() string from gray in [0,1].

func ColorRGB

func ColorRGB(r, g, b float64) Color

ColorRGB expects components in [0,1] and formats an rgb() string.

func ColorRGBA

func ColorRGBA(r, g, b, a float64) Color

ColorRGBA expects components in [0,1] and formats an rgba() string.

func (Color) CSS

func (c Color) CSS() string

func (Color) Opacity

func (c Color) Opacity() (float64, bool)

type DashPattern

type DashPattern struct {
	// Array contains alternating on/off lengths: [on1, off1, on2, off2, ...]
	// This mirrors mp_dash_object.array from psout.w:5219ff
	Array []float64
	// Offset is the starting offset into the pattern (for phase shifting)
	// Mirrors mp_dash_object.offset
	Offset float64
}

DashPattern represents a dash pattern for stroked paths. Mirrors MetaPost's picture-based dash pattern (plain.mp dashpattern macro).

In MetaPost, a dash pattern is a picture containing horizontal line segments where the y-coordinate encodes the cumulative position (total pattern length). The pattern is built using "on" (visible) and "off" (gap) segments.

Internal structure (mp.w:11778ff):

  • dash_node: start_x, stop_x, dash_y (period)
  • mp_export_dashes converts to offset + array[] for SVG output

Example: dashpattern(on 3 off 3) creates evenly spaced dashes.

func DashEvenly

func DashEvenly() *DashPattern

Evenly returns the standard "evenly" dash pattern (on 3 off 3). This is the MetaPost default: dashpattern(on 3 off 3)

func DashWithDots

func DashWithDots() *DashPattern

WithDots returns the "withdots" dash pattern (off 2.5 on 0 off 2.5). In MetaPost: dashpattern(off 2.5 on 0 off 2.5) Note: "on 0" creates a dot when linecap is round.

func NewDashPattern

func NewDashPattern(onOff ...float64) *DashPattern

NewDashPattern creates a dash pattern from alternating on/off lengths. Example: NewDashPattern(3, 3) creates "on 3 off 3" (evenly spaced dashes)

func (*DashPattern) Scaled

func (d *DashPattern) Scaled(factor float64) *DashPattern

Scaled returns a new dash pattern with all values multiplied by factor. Mirrors MetaPost's "dashed evenly scaled 2" syntax.

func (*DashPattern) Shifted

func (d *DashPattern) Shifted(offset float64) *DashPattern

Shifted returns a new dash pattern with the offset adjusted. Mirrors MetaPost's phase shifting.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

func NewEngine

func NewEngine() *Engine

func (*Engine) AddPath

func (e *Engine) AddPath(p *Path)

AddPath appends a path to the engine queue.

func (*Engine) Solve

func (e *Engine) Solve() error

Solve runs the curve-solving and envelope pipeline on all paths.

type FontRenderer added in v0.1.2

type FontRenderer interface {
	// TextToPaths converts a text string to a slice of filled paths (one per glyph).
	// Each path represents a glyph outline positioned correctly for the text layout.
	TextToPaths(text string, opts TextToPathsOptions) ([]*Path, error)

	// TextBounds returns the bounding box of shaped text.
	// Returns (width, height) in output units.
	TextBounds(text string, fontSize float64) (width, height float64)
}

FontRenderer is the interface for converting text to glyph paths. This allows the font support to be in a separate package (github.com/boxesandglue/mpgo/font) which users can optionally import.

type Knot

type Knot struct {
	XCoord Number
	YCoord Number
	LeftX  Number
	LeftY  Number
	RightX Number
	RightY Number
	Next   *Knot
	Prev   *Knot
	Info   int32
	LType  KnotType
	RType  KnotType
	Origin KnotOrigin
}

mplib.h 304

func CopyKnot

func CopyKnot(p *Knot) *Knot

func NewKnot

func NewKnot() *Knot

type KnotOrigin

type KnotOrigin uint8
const (
	OriginProgram KnotOrigin = iota
	OriginUser
)

type KnotType

type KnotType uint16
const (
	KnotEndpoint KnotType = iota
	KnotExplicit
	KnotGiven
	KnotCurl
	KnotOpen
	KnotEndCycle
)

type Label added in v0.1.2

type Label struct {
	Text        string  // The label text
	Position    Point   // Reference point (z in MetaPost's label(s, z))
	Anchor      Anchor  // Positioning relative to the reference point
	Color       Color   // Text color (default: black)
	FontSize    float64 // Font size in points (default: 10)
	FontFamily  string  // Font family (default: sans-serif for SVG)
	LabelOffset float64 // Distance from reference point (default: 3bp)
}

Label represents a text label positioned near a point. This is a simplified version of MetaPost's label that works with plain text instead of btex...etex typeset content.

func NewLabel added in v0.1.2

func NewLabel(text string, pos Point, anchor Anchor) *Label

NewLabel creates a new label with default settings.

func (*Label) EstimateBounds added in v0.1.2

func (l *Label) EstimateBounds() (minX, minY, maxX, maxY float64)

EstimateBounds returns an estimated bounding box for the label. Since we don't have actual font metrics, this uses approximations:

  • Character width ≈ fontSize * 0.6 (average for sans-serif)
  • Character height ≈ fontSize

Returns (minX, minY, maxX, maxY) in the same coordinate system as Position.

func (*Label) ToPaths added in v0.1.2

func (l *Label) ToPaths(f FontRenderer) ([]*Path, error)

ToPaths converts the label text to glyph outline paths using the provided font. The paths are positioned according to the label's Position, Anchor, and LabelOffset. Returns a slice of filled paths representing each glyph.

The font parameter must implement the FontRenderer interface. Use the font package to load fonts:

import "github.com/boxesandglue/mpgo/font"
face, _ := font.Load(fontReader)
paths, _ := label.ToPaths(face)

func (*Label) WithColor added in v0.1.2

func (l *Label) WithColor(c Color) *Label

WithColor sets the label color.

func (*Label) WithFontFamily added in v0.1.2

func (l *Label) WithFontFamily(family string) *Label

WithFontFamily sets the font family.

func (*Label) WithFontSize added in v0.1.2

func (l *Label) WithFontSize(size float64) *Label

WithFontSize sets the font size.

func (*Label) WithOffset added in v0.1.2

func (l *Label) WithOffset(offset float64) *Label

WithOffset sets the label offset distance.

type Number

type Number = float64

Number aliases float64 to mirror the double backend only.

const Eps Number = 0.00049

Eps is MetaPost's epsilon value - a very small positive number. Used for penspeck and other near-zero comparisons.

func AngleMultiplier

func AngleMultiplier() Number

AngleMultiplier exposes the angle scaling used for MetaPost angles (mpmathdouble angle_multiplier).

func GetPenScale

func GetPenScale(pen *Pen) Number

GetPenScale computes the scale factor of an elliptical pen, matching mp_get_pen_scale (mp.w:11529-11547).

For elliptical pens, this returns sqrt(|det(M)|) where M is the transformation matrix:

| a  b |   | left_x - x_coord    right_x - x_coord |
| c  d | = | left_y - y_coord    right_y - y_coord |

For an untransformed pencircle with diameter d, this returns d. For polygonal pens, this returns 0 (use PenBBox instead).

func Inf

func Inf() Number

Inf exposes a large sentinel value used when mapping MetaPost "infinity".

func PenBBox

func PenBBox(pen *Pen) (minx, miny, maxx, maxy Number, ok bool)

PenBBox returns the axis-aligned bounding box of a pen outline, equivalent to mp_pen_bbox (mp.c:10670ff) but without transforming; used in offset prep.

type Path

type Path struct {
	Head     *Knot
	Style    Style
	Envelope *Path // optional precomputed offset/envelope (mp_apply_offset analogue)
}

func ArrowHeadEnd

func ArrowHeadEnd(p *Path, ahLength, ahAngle Number) *Path

ArrowHeadEnd creates an arrowhead path at the end of path p. The arrowhead is a filled triangle with apex at the endpoint. Uses ahLength for the arrow length and ahAngle for the head angle (degrees).

func ArrowHeadStart

func ArrowHeadStart(p *Path, ahLength, ahAngle Number) *Path

ArrowHeadStart creates an arrowhead path at the start of path p. The arrowhead is a filled triangle with apex at the start point.

func BuildCycle

func BuildCycle(paths ...*Path) *Path

BuildCycle constructs a cyclic path from multiple paths by finding their intersection points and connecting them. This mirrors MetaPost's buildcycle macro from plain.mp.

The algorithm:

  1. For each consecutive pair of paths (wrapping around), find intersection
  2. Extract the subpath of each path between its two intersection points
  3. Join them into a closed cycle

Returns nil if any consecutive pair of paths doesn't intersect.

func FullCircle

func FullCircle() *Path

FullCircle returns a unit circle (diameter 1) centered at the origin. Equivalent to MetaPost's `fullcircle` (= makepath pencircle). The path starts at (0.5, 0) and goes counterclockwise with 8 knots. Control points are computed by the Hobby-Knuth solver, matching MetaPost.

func HalfCircle

func HalfCircle() *Path

HalfCircle returns the upper half of a unit circle. Equivalent to MetaPost's `halfcircle` (= subpath (0,4) of fullcircle). The path starts at (0.5, 0), goes through (0, 0.5), and ends at (-0.5, 0).

func MakeEnvelope

func MakeEnvelope(path *Path, pen *Pen) *Path

MakeEnvelope creates an envelope outline by walking the pen around the path. Mirrors mp_make_envelope (mp.c:13304ff / mp.w:14748ff).

func NewPath

func NewPath() *Path

func OffsetOutline

func OffsetOutline(path *Path, pen *Pen) *Path

OffsetOutline builds a swept outline for a non-elliptical pen by using makeEnvelope (mp.c:13445ff). Falls back to polygon approximation if needed.

func QuarterCircle

func QuarterCircle() *Path

QuarterCircle returns the first quadrant arc of a unit circle. Equivalent to MetaPost's `quartercircle` (= subpath (0,2) of fullcircle). The path starts at (0.5, 0) and ends at (0, 0.5).

func ShortenPathForArrow

func ShortenPathForArrow(p *Path, shortenStart, shortenEnd Number) *Path

ShortenPathForArrow creates a copy of path p with endpoints moved inward to make room for arrowheads. This mimics MetaPost's "cutafter" behavior. shortenStart/shortenEnd specify how much to shorten at each end.

func UnitSquare

func UnitSquare() *Path

UnitSquare returns a unit square from (0,0) to (1,1). Equivalent to MetaPost's `unitsquare`. The path is (0,0)--(1,0)--(1,1)--(0,1)--cycle.

func (*Path) Append

func (p *Path) Append(k *Knot)

func (*Path) ArcLength

func (p *Path) ArcLength() Number

ArcLength returns the total arc length of the path. Mirrors MetaPost's "arclength p" (mp.w:10197ff).

Uses adaptive Simpson's rule to integrate |B'(t)| along each segment.

func (*Path) ArcLengthSegment

func (p *Path) ArcLengthSegment(segIdx int) Number

ArcLengthSegment returns the arc length of a single segment starting at parameter t. This is useful for computing arc length of subpaths.

func (*Path) ArcTime

func (p *Path) ArcTime(arcLen Number) Number

ArcTime returns the time parameter t where the arc length from the start of the path reaches the given value arcLen. Mirrors MetaPost's "arctime x of p" (mp.w:10255ff).

For non-cyclic paths:

  • If arcLen < 0, returns 0
  • If arcLen > total arc length, returns path length

For cyclic paths:

  • Negative arcLen traverses backwards
  • arcLen > total wraps around multiple times

func (*Path) Copy

func (p *Path) Copy() *Path

func (*Path) CutAfter added in v0.1.2

func (p *Path) CutAfter(q *Path) *Path

CutAfter returns the portion of path p before its first intersection with path q. Mirrors MetaPost's "p cutafter q" (plain.mp).

If there is no intersection, returns a copy of p.

Example:

result := p.CutAfter(q)  // p from start to intersection

func (*Path) CutBefore added in v0.1.2

func (p *Path) CutBefore(q *Path) *Path

CutBefore returns the portion of path p after its first intersection with path q. Mirrors MetaPost's "p cutbefore q" (plain.mp).

If there is no intersection, returns a copy of p.

Example:

result := p.CutBefore(q)  // p from intersection to end

func (*Path) DirectionOf

func (p *Path) DirectionOf(t Number) (dx, dy Number)

DirectionOf returns the tangent direction at parameter t on the path. Mirrors MetaPost's "direction t of p" defined in plain.mp as:

postcontrol t of p - precontrol t of p

Returns (dx, dy) representing the tangent vector (not normalized).

func (*Path) DirectionPointOf added in v0.1.2

func (p *Path) DirectionPointOf(dx, dy Number) (x, y Number, found bool)

DirectionPointOf returns the first point on the path where it has the given direction. Mirrors MetaPost's "directionpoint (dx,dy) of p" macro.

Returns (0, 0) and false if the direction is never achieved.

Example:

x, y, ok := path.DirectionPointOf(1, 0)  // Point where tangent is horizontal

func (*Path) DirectionTimeOf added in v0.1.2

func (p *Path) DirectionTimeOf(dx, dy Number) Number

DirectionTimeOf returns the first time t when the path has the given direction. Mirrors MetaPost's "directiontime (dx,dy) of p" (mp.w:9593ff).

Returns -1 if the direction is never achieved on the path.

The direction vector (dx, dy) does not need to be normalized. For example, directiontime (1, 1) finds where the tangent is at 45°.

Example:

t := path.DirectionTimeOf(1, 0)  // Find where tangent is horizontal (rightward)
t := path.DirectionTimeOf(0, 1)  // Find where tangent is vertical (upward)

func (*Path) IntersectionPoint

func (p *Path) IntersectionPoint(q *Path) (x, y Number, found bool)

IntersectionPoint returns the point where paths p and q intersect. Mirrors MetaPost's "intersectionpoint (p, q)".

Returns:

  • (x, y, true) if an intersection exists
  • (0, 0, false) if no intersection exists

func (*Path) IntersectionTimes

func (p *Path) IntersectionTimes(q *Path) (t1, t2 Number)

IntersectionTimes returns the time parameters (t1, t2) where paths p and q intersect. Mirrors MetaPost's "intersectiontimes (p, q)" (mp.w:16130ff).

Returns:

  • (t1, t2) where p.PointOf(t1) == q.PointOf(t2) (within tolerance)
  • (-1, -1) if no intersection exists

The algorithm iterates over all pairs of segments and uses recursive bisection to find the intersection point.

func (*Path) PathLength

func (p *Path) PathLength() int

PathLength returns the number of segments in the path. For a path with n knots, there are n-1 segments (open) or n segments (cycle). This corresponds to the maximum integer value of the path parameter t.

func (*Path) PointOf

func (p *Path) PointOf(t Number) (x, y Number)

PointOf returns the point at parameter t on the path. Mirrors MetaPost's "point t of p" (mp.c:8750ff / mp.w:9401ff).

The parameter t has integer part selecting the segment (0-based) and fractional part [0,1) selecting position within that segment. For a path z0..z1..z2:

  • t=0 gives z0
  • t=0.5 gives midpoint of first curve
  • t=1 gives z1
  • t=1.5 gives midpoint of second curve
  • t=2 gives z2

For values outside [0, length], the path is linearly extrapolated along the tangent at the endpoint.

func (*Path) PostcontrolOf

func (p *Path) PostcontrolOf(t Number) (x, y Number)

PostcontrolOf returns the control point "going out of" parameter t on the path. Mirrors MetaPost's "postcontrol t of p" (mp.w:9533ff).

At integer t values, this is the right control point of that knot. At fractional t values, we split the cubic and return the postcontrol of the split point.

func (*Path) PrecontrolOf

func (p *Path) PrecontrolOf(t Number) (x, y Number)

PrecontrolOf returns the control point "coming into" parameter t on the path. Mirrors MetaPost's "precontrol t of p" (mp.w:9523ff).

At integer t values, this is the left control point of that knot. At fractional t values, we split the cubic and return the precontrol of the split point.

func (*Path) ReflectedAbout

func (p *Path) ReflectedAbout(x1, y1, x2, y2 Number) *Path

ReflectedAbout returns a new path reflected about the line through (x1,y1) and (x2,y2).

func (*Path) Reversed

func (p *Path) Reversed() *Path

Reversed returns a copy of the path with direction reversed. Used for subpath when t1 > t2.

func (*Path) Rotated

func (p *Path) Rotated(angleDeg Number) *Path

Rotated returns a new path rotated around the origin. Angle is in degrees (positive = counter-clockwise).

func (*Path) RotatedAround

func (p *Path) RotatedAround(cx, cy, angleDeg Number) *Path

RotatedAround returns a new path rotated around a given point.

func (*Path) Scaled

func (p *Path) Scaled(s Number) *Path

Scaled returns a new path scaled uniformly around the origin.

func (*Path) ScaledAround

func (p *Path) ScaledAround(cx, cy, s Number) *Path

ScaledAround returns a new path scaled around a given point.

func (*Path) Shifted

func (p *Path) Shifted(dx, dy Number) *Path

Shifted returns a new path shifted by (dx, dy).

func (*Path) Slanted

func (p *Path) Slanted(s Number) *Path

Slanted returns a new path with horizontal shear applied.

func (*Path) String

func (p *Path) String() string

func (*Path) Subpath

func (p *Path) Subpath(t1, t2 Number) *Path

Subpath returns a new path representing the portion from t1 to t2. Mirrors MetaPost's "subpath (t1,t2) of p" (mp.c:8869ff / mp.w:9543ff).

If t1 > t2, the subpath runs backwards. The returned path is always open (non-cyclic).

func (*Path) Transformed

func (p *Path) Transformed(t Transform) *Path

Transformed returns a new path with the given transformation applied.

func (*Path) XScaled

func (p *Path) XScaled(s Number) *Path

XScaled returns a new path scaled horizontally.

func (*Path) YScaled

func (p *Path) YScaled(s Number) *Path

YScaled returns a new path scaled vertically.

func (*Path) ZScaled

func (p *Path) ZScaled(a, b Number) *Path

ZScaled returns a new path scaled and rotated using complex multiplication.

type PathNormal

type PathNormal struct {
	DX, DY Number // original edge delta (mp.delta_x/delta_y analogue)
	Len    Number // edge length
	NX, NY Number // unit normal (rotated left)
}

PathNormal holds a normalized direction and its length for a path edge.

func PathNormals

func PathNormals(path *Path) []PathNormal

PathNormals computes edge deltas and unit normals for a path, mirroring the delta/psi preparation leading into offset computations (mp.c:7398ff before mp_offset_prep). This is a building block for mp_offset_prep/mp_apply_offset.

type Pen

type Pen struct {
	Head       *Knot
	Elliptical bool // mirrors pen_is_elliptical macro (mp.c:444)
}

Pen mirrors MetaPost's pen objects: a closed knot list describing the pen shape. MetaPost stores this as pen_p on stroke/fill nodes (mp.c:564,1056ff). Here we keep a minimal container with the pen's knot head.

func MakePen

func MakePen(path *Path) *Pen

MakePen builds a pen from an arbitrary path by taking its convex hull, akin to mp_make_pen(mp.c:9290ff). We ignore Bezier controls and use knot coordinates only, mirroring mp_convex_hull call when need_hull=true.

func NewPenFromPath

func NewPenFromPath(p *Path) *Pen

NewPenFromPath builds a Pen from an existing path (the path should be a closed convex outline, similar to the expectation in mp_make_pen, mp.c:10964ff).

func PenCircle

func PenCircle(d Number) *Pen

PenCircle constructs an elliptical pen with diameter d, matching MetaPost's pencircle (mp.w:10440-10452, mp_get_pen_circle).

MetaPost stores elliptical pens as a single knot where:

  • (x_coord, y_coord) = center (translation)
  • (left_x, left_y) = where (1,0) transforms to (first basis vector)
  • (right_x, right_y) = where (0,1) transforms to (second basis vector)

For an untransformed pencircle of diameter d:

  • center = (0, 0)
  • (1,0) -> (d, 0) i.e. left_x=d, left_y=0
  • (0,1) -> (0, d) i.e. right_x=0, right_y=d

The transformation matrix is thus:

| left_x   right_x |   | d  0 |
| left_y   right_y | = | 0  d |

func PenRazor

func PenRazor(size Number) *Pen

PenRazor constructs a razor pen (horizontal line segment), matching makepen((-.5,0)--(.5,0)--cycle) (penrazor) in MetaPost. The size parameter scales the pen (default penrazor has length 1). Use PenRazorRotated for calligraphic effects at an angle.

func PenRazorRotated

func PenRazorRotated(size Number, angleDegrees Number) *Pen

PenRazorRotated constructs a razor pen rotated by the given angle (in degrees). This is equivalent to penrazor scaled size rotated angle in MetaPost.

func PenSpeck

func PenSpeck() *Pen

PenSpeck constructs a nearly invisible point pen, matching penspeck = pensquare scaled eps in MetaPost. Useful for drawing paths without visible stroke.

func PenSquare

func PenSquare(size Number) *Pen

PenSquare constructs a square pen of side length size, matching makepen(unitsquare shifted -(.5,.5)) (pensquare) in MetaPost. For now this returns a 4-knot closed path (axis-aligned).

type Point

type Point struct {
	X, Y float64
}

Point represents a 2D coordinate pair. This is the basic type for geometric helper functions.

func Dir

func Dir(angle float64) Point

Dir returns a unit vector at the given angle in degrees. Equivalent to MetaPost's "dir(angle)".

func LineIntersection

func LineIntersection(p1, p2, p3, p4 Point) (Point, bool)

LineIntersection returns the intersection point of two lines. Line 1 passes through p1 and p2. Line 2 passes through p3 and p4. Returns the intersection point and true if lines intersect. Returns zero point and false if lines are parallel.

func MidPoint

func MidPoint(a, b Point) Point

MidPoint returns the midpoint between two points. Equivalent to MetaPost's "0.5[a,b]".

func P

func P(x, y float64) Point

P creates a Point from x, y coordinates.

func PerpendicularFoot

func PerpendicularFoot(p, p1, p2 Point) Point

PerpendicularFoot returns the point on the line through p1 and p2 that is closest to point p (the foot of the perpendicular).

func PointBetween

func PointBetween(a, b Point, t float64) Point

PointBetween returns the point at parameter t along the line from a to b. Equivalent to MetaPost's "t[a,b]".

  • t=0 returns a
  • t=1 returns b
  • t=0.5 returns midpoint
  • t<0 or t>1 extrapolates beyond the segment

func PointOnLineAtX

func PointOnLineAtX(p1, p2 Point, x float64) (Point, bool)

PointOnLineAtX returns the point on the line through p1 and p2 at the given x coordinate. Returns the point and true if the line is not vertical. Returns zero point and false if the line is vertical (infinite or no solutions).

func PointOnLineAtY

func PointOnLineAtY(p1, p2 Point, y float64) (Point, bool)

PointOnLineAtY returns the point on the line through p1 and p2 at the given y coordinate. Returns the point and true if the line is not horizontal. Returns zero point and false if the line is horizontal (infinite or no solutions).

func Reflection

func Reflection(p, p1, p2 Point) Point

Reflection returns the reflection of point p about the line through p1 and p2.

func Rotate

func Rotate(p Point, angle float64) Point

Rotate returns point p rotated by angle degrees around the origin.

func RotateAround

func RotateAround(p, c Point, angle float64) Point

RotateAround returns point p rotated by angle degrees around center point c.

func Scale

func Scale(p Point, s float64) Point

Scale returns point p scaled by factor s from the origin.

func ScaleAround

func ScaleAround(p, c Point, s float64) Point

ScaleAround returns point p scaled by factor s from center point c.

func (Point) Add

func (p Point) Add(q Point) Point

Add returns the vector sum of two points.

func (Point) Angle

func (p Point) Angle() float64

Angle returns the angle of the vector in degrees (0-360).

func (Point) Cross

func (p Point) Cross(q Point) float64

Cross returns the 2D cross product (z-component of 3D cross product). Positive if q is counter-clockwise from p.

func (Point) Dot

func (p Point) Dot(q Point) float64

Dot returns the dot product of two points (as vectors).

func (Point) Length

func (p Point) Length() float64

Length returns the distance from the origin (vector magnitude).

func (Point) Mul

func (p Point) Mul(s float64) Point

Mul returns the point scaled by scalar s.

func (Point) Normalized

func (p Point) Normalized() Point

Normalized returns a unit vector in the same direction. Returns zero vector if p is zero.

func (Point) Sub

func (p Point) Sub(q Point) Point

Sub returns the vector difference p - q.

type Style

type Style struct {
	Stroke      Color
	StrokeWidth float64
	Fill        Color
	Pen         *Pen // mirrors pen_p in mp.c (mp.c:564)
	// LineJoin/LineCap mirror MetaPost linejoin/linecap (mp.c:23894ff).
	// LineCap uses offset constants (0=default/unset → rounded).
	// LineJoin uses direct MetaPost values (0=miter, 1=round, 2=bevel).
	LineJoin int
	LineCap  int // Use LineCapButt, LineCapRounded, LineCapSquared constants
	Arrow    ArrowStyle
	Dash     *DashPattern // dash pattern for stroked paths (mp.w:11362ff)
}

Style holds drawing attributes attached to a path.

type TextToPathsOptions added in v0.1.2

type TextToPathsOptions struct {
	FontSize float64 // Font size in points (default: 10)
	X, Y     float64 // Starting position
	Color    Color   // Fill color for the glyphs
}

TextToPathsOptions configures text-to-path conversion.

type Transform

type Transform struct {
	Txx, Txy, Tx Number // first row: x' = Txx*x + Txy*y + Tx
	Tyx, Tyy, Ty Number // second row: y' = Tyx*x + Tyy*y + Ty
}

Transform represents an affine transformation matrix. The transformation is applied as:

x' = Txx*x + Txy*y + Tx
y' = Tyx*x + Tyy*y + Ty

This mirrors MetaPost's transform type (mp.w:6196ff). The matrix form is:

| Txx  Txy  Tx |
| Tyx  Tyy  Ty |
| 0    0    1  |

func Identity

func Identity() Transform

Identity returns the identity transformation. (mp.w:6367ff mp_id_transform)

func ReflectedAbout

func ReflectedAbout(x1, y1, x2, y2 Number) Transform

ReflectedAbout returns a reflection transformation about the line passing through points (x1,y1) and (x2,y2). Mirrors MetaPost's "reflectedabout(p1, p2)" (plain.mp).

func Rotated

func Rotated(angleDeg Number) Transform

Rotated returns a rotation transformation around the origin. Angle is in degrees (positive = counter-clockwise). Mirrors MetaPost's "rotated angle" (mp.w:28537ff).

func RotatedAround

func RotatedAround(cx, cy, angleDeg Number) Transform

RotatedAround returns a rotation transformation around a given point. This is equivalent to: shifted(-cx,-cy) rotated(angle) shifted(cx,cy)

func Scaled

func Scaled(s Number) Transform

Scaled returns a uniform scaling transformation around the origin. Mirrors MetaPost's "scaled s" (mp.w:28502ff).

func ScaledAround

func ScaledAround(cx, cy, s Number) Transform

ScaledAround returns a scaling transformation around a given point. This is equivalent to: shifted(-cx,-cy) scaled(s) shifted(cx,cy)

func Shifted

func Shifted(dx, dy Number) Transform

Shifted returns a translation transformation. Mirrors MetaPost's "shifted (dx, dy)" (mp.w:28509ff).

func Slanted

func Slanted(s Number) Transform

Slanted returns a horizontal shear transformation. Mirrors MetaPost's "slanted s" (mp.w:28496ff). The transformation is: x' = x + s*y, y' = y

func XScaled

func XScaled(s Number) Transform

XScaled returns a horizontal scaling transformation. Mirrors MetaPost's "xscaled s".

func YScaled

func YScaled(s Number) Transform

YScaled returns a vertical scaling transformation. Mirrors MetaPost's "yscaled s".

func ZScaled

func ZScaled(a, b Number) Transform

ZScaled returns a scaling+rotation transformation using a complex number. Mirrors MetaPost's "zscaled (a, b)" which scales by sqrt(a²+b²) and rotates by atan2(b, a).

func (Transform) ApplyToKnot

func (t Transform) ApplyToKnot(k *Knot)

ApplyToKnot applies the transformation to all coordinates of a knot.

func (Transform) ApplyToPath

func (t Transform) ApplyToPath(p *Path) *Path

ApplyToPath applies the transformation to all knots in a path. Mirrors MetaPost's mp_do_path_trans (mp.w:28647ff). Returns a new transformed path (does not modify the original).

func (Transform) ApplyToPoint

func (t Transform) ApplyToPoint(x, y Number) (Number, Number)

ApplyToPoint applies the transformation to a point (x, y). Returns the transformed coordinates (x', y'). Mirrors MetaPost's mp_number_trans (mp.w:28617ff).

func (Transform) Determinant

func (t Transform) Determinant() Number

Determinant returns the determinant of the transformation matrix. This represents the scaling factor for areas.

func (Transform) Inverse

func (t Transform) Inverse() Transform

Inverse returns the inverse transformation, if it exists. Returns Identity() if the transformation is singular (determinant = 0).

func (Transform) Then

func (t Transform) Then(other Transform) Transform

Then composes this transformation with another. Returns a transformation equivalent to applying t first, then other. (i.e., other ∘ t in mathematical notation)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL