draw

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: 4 Imported by: 1

Documentation

Overview

Package draw provides a high-level fluent API for constructing MetaPost-style paths.

This package wraps the low-level mp package with a builder pattern that makes path construction more intuitive and Go-idiomatic. It also provides equation solving for geometric constraints.

Path Building

The PathBuilder provides a fluent interface for constructing paths:

path, err := draw.NewPath().
    MoveTo(mp.P(0, 0)).
    CurveTo(mp.P(50, 30)).
    CurveTo(mp.P(100, 0)).
    WithStrokeColor(mp.ColorCSS("blue")).
    Solve()

Curve Types

Different curve segment types are supported:

CurveTo(pt)           // Smooth curve (Hobby algorithm)
LineTo(pt)            // Straight line segment
Controls(c1, c2, pt)  // Explicit Bézier control points

Direction and Tension

Control the curve shape with directions and tensions:

draw.NewPath().
    MoveTo(mp.P(0, 0)).
    WithOutDir(45).          // Leave at 45°
    CurveTo(mp.P(100, 0)).
    WithInDir(-45).          // Arrive at -45°
    Solve()

draw.NewPath().
    MoveTo(mp.P(0, 0)).
    WithTension(2).          // Tighter curve
    CurveTo(mp.P(100, 0)).
    Solve()

Closed Paths

Create closed paths with PathBuilder.Close:

triangle, _ := draw.NewPath().
    MoveTo(mp.P(0, 0)).
    LineTo(mp.P(100, 0)).
    LineTo(mp.P(50, 86)).
    Close().
    Solve()

Styling

Apply stroke, fill, and other styling:

draw.NewPath().
    MoveTo(mp.P(0, 0)).
    CurveTo(mp.P(100, 0)).
    WithStrokeColor(mp.ColorCSS("red")).
    WithFillColor(mp.ColorCSS("yellow")).
    WithStrokeWidth(2.0).
    WithDash([]float64{5, 3}, 0).
    WithArrowEnd().
    Solve()

Pictures

The Picture type collects multiple paths and labels, similar to MetaPost's picture:

pic := draw.NewPicture()
pic.AddPath(path1)
pic.AddPath(path2)
pic.Label("A", mp.P(0, 0), mp.AnchorLowerLeft)
pic.DotLabel("B", mp.P(100, 0), mp.AnchorRight, mp.ColorCSS("blue"))

Label Conversion

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

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

face, _ := font.Load(fontFile)
pic.ConvertLabelsToPathsWithFont(face)

Equation Solving

The Context type provides linear equation solving for geometric constraints:

ctx := draw.NewContext()
z0 := ctx.Known(0, 0)      // Fixed point
z1 := ctx.Unknown()        // Unknown point
z2 := ctx.Known(100, 100)  // Fixed point

ctx.Collinear(z1, z0, z2)  // z1 lies on line z0--z2
ctx.EqX(z1, 50)            // z1.x = 50
ctx.Solve()

fmt.Println(z1.XY())       // (50, 50)

Variables can be used in path building:

path, _ := draw.NewPath().
    WithContext(ctx).
    MoveToVar(z0).
    CurveToVar(z1).
    CurveToVar(z2).
    Solve()

Transformations

Apply transformations to paths:

draw.NewPath().
    MoveTo(mp.P(0, 0)).
    LineTo(mp.P(10, 0)).
    Scaled(5).
    Rotated(45).
    Shifted(100, 100).
    Solve()

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Context

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

Context provides an equation solver for geometric constraints. It tracks variables (points) that can be known or unknown, and solves linear equations to determine unknown values.

Usage:

ctx := draw.NewContext()
z0 := ctx.Known(0, 0)
z1 := ctx.Unknown()
z2 := ctx.Known(100, 100)
ctx.Collinear(z1, z0, z2)  // z1 on line z0--z2
ctx.EqX(z1, 50)            // z1.x = 50
ctx.Solve()
fmt.Println(z1.XY())       // (50, 50)

func NewContext

func NewContext() *Context

NewContext creates a new equation-solving context.

func (*Context) Between

func (c *Context) Between(p, a, b *Var, t float64)

Between constrains p to lie at parameter t on the line from a to b. Equivalent to: p = t[a, b] = (1-t)*a + t*b

func (*Context) BetweenAt

func (c *Context) BetweenAt(a, b *Var, t float64) *Var

BetweenAt returns a new variable at parameter t on line from a to b.

func (*Context) Collinear

func (c *Context) Collinear(p, a, b *Var)

Collinear constrains p to lie on the line through a and b. This adds the constraint that p, a, b are collinear, but doesn't determine WHERE on the line p is - you need another constraint for that.

func (*Context) Diff

func (c *Context) Diff(result, a, b *Var)

Diff constrains: result = a - b (vector subtraction)

func (*Context) Eq

func (c *Context) Eq(v *Var, p mp.Point)

Eq constrains a variable to equal a known point.

func (*Context) EqVar

func (c *Context) EqVar(a, b *Var)

EqVar constrains two variables to be equal.

func (*Context) EqVarX

func (c *Context) EqVarX(a, b *Var)

EqVarX constrains two variables to have equal x-coordinates.

func (*Context) EqVarY

func (c *Context) EqVarY(a, b *Var)

EqVarY constrains two variables to have equal y-coordinates.

func (*Context) EqX

func (c *Context) EqX(v *Var, x float64)

EqX constrains a variable's x-coordinate to a value.

func (*Context) EqY

func (c *Context) EqY(v *Var, y float64)

EqY constrains a variable's y-coordinate to a value.

func (*Context) Intersection

func (c *Context) Intersection(p, a1, a2, b1, b2 *Var) error

Intersection constrains p to be the intersection of line a1-a2 and line b1-b2. All of a1, a2, b1, b2 must be known.

func (*Context) IntersectionOf

func (c *Context) IntersectionOf(a1, a2, b1, b2 *Var) (*Var, error)

IntersectionOf returns a new variable at the intersection of lines a1-a2 and b1-b2.

func (*Context) Known

func (c *Context) Known(x, y float64) *Var

Known creates a new point variable with known coordinates.

func (*Context) LinearXY added in v0.1.5

func (c *Context) LinearXY(v *Var, cx, cy, constant float64)

LinearXY adds the constraint: cx*v.x + cy*v.y = constant. This allows mixing x and y coordinates in a single equation, e.g. LinearXY(z, 1, 1, 79.2) encodes x+y = 79.2 (MetaPost: x3+y3=1.1in).

func (*Context) MidPoint

func (c *Context) MidPoint(m, a, b *Var)

MidPoint constrains m to be the midpoint of a and b. Equivalent to: m = 0.5[a, b]

func (*Context) MidPointOf

func (c *Context) MidPointOf(a, b *Var) *Var

MidPointOf returns a new variable constrained to be the midpoint of a and b.

func (*Context) NewPath

func (c *Context) NewPath() *PathBuilder

NewPath creates a PathBuilder linked to this context. Use MoveToVar/CurveToVar/LineToVar to reference context variables. Call Solve() before building the path to resolve all variables.

func (*Context) Point

func (c *Context) Point() *Var

Point creates a new unknown point variable (alias for Unknown).

func (*Context) Points

func (c *Context) Points(n int) []*Var

Points creates n unknown point variables.

func (*Context) Scaled

func (c *Context) Scaled(result, v *Var, t float64)

Scaled constrains: result = t * v (scalar multiplication)

func (*Context) Solve

func (c *Context) Solve() error

Solve solves the system of equations and updates all variables. Returns an error if the system is unsolvable or underdetermined.

func (*Context) Sum

func (c *Context) Sum(result, a, b *Var)

Sum constrains: result = a + b (vector addition)

func (*Context) Unknown

func (c *Context) Unknown() *Var

Unknown creates a new unknown point variable.

type PathBuilder

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

PathBuilder constructs a path using outgoing/incoming directions and delegates solving to mp.Engine.

func NewPath

func NewPath() *PathBuilder

func (*PathBuilder) BuildPath

func (p *PathBuilder) BuildPath() *mp.Path

BuildPath constructs an mp.Path with knots configured for given directions. Directions are converted to MetaPost's scaled degrees. If the path uses context variables, the context must be solved first.

func (*PathBuilder) Close

func (p *PathBuilder) Close() *PathBuilder

Close marks the path as cyclic; use the currently set outDir/inDir as the outgoing/incoming directions for the closing segment back to the start.

func (*PathBuilder) CurveTo

func (p *PathBuilder) CurveTo(pt mp.Point) *PathBuilder

CurveTo adds a segment to pt with the stored directions.

func (*PathBuilder) CurveToDir

func (p *PathBuilder) CurveToDir(pt mp.Point, outDeg, inDeg float64) *PathBuilder

CurveToDir adds a segment and sets outgoing/incoming directions just for this segment.

func (*PathBuilder) CurveToVar

func (p *PathBuilder) CurveToVar(v *Var) *PathBuilder

CurveToVar adds a curve segment to a context variable.

func (*PathBuilder) CurveToWithControls

func (p *PathBuilder) CurveToWithControls(pt mp.Point, c1, c2 mp.Point) *PathBuilder

CurveToWithControls adds a segment with explicit control points (skips solving).

func (*PathBuilder) Dashed

func (p *PathBuilder) Dashed(onOff ...float64) *PathBuilder

Dashed sets a custom dash pattern. The pattern is given as alternating on/off lengths: on1, off1, on2, off2, ... Example: Dashed(6, 3) creates "on 6 off 3" (long dashes with short gaps)

func (*PathBuilder) DashedEvenly

func (p *PathBuilder) DashedEvenly() *PathBuilder

DashedEvenly sets the standard "evenly" dash pattern (on 3 off 3). This mirrors MetaPost's "dashed evenly" from plain.mp.

func (*PathBuilder) DashedWithDots

func (p *PathBuilder) DashedWithDots() *PathBuilder

DashedWithDots sets the "withdots" dash pattern. This creates dots when used with round linecap. Mirrors MetaPost's "dashed withdots" from plain.mp.

func (*PathBuilder) LineTo

func (p *PathBuilder) LineTo(pt mp.Point) *PathBuilder

LineTo adds a straight segment (MetaPost "--", i.e., {curl 1}..{curl 1}) to (x,y).

func (*PathBuilder) LineToVar

func (p *PathBuilder) LineToVar(v *Var) *PathBuilder

LineToVar adds a straight segment to a context variable.

func (*PathBuilder) MoveTo

func (p *PathBuilder) MoveTo(pt mp.Point) *PathBuilder

func (*PathBuilder) MoveToVar

func (p *PathBuilder) MoveToVar(v *Var) *PathBuilder

MoveToVar sets the start point from a context variable. The variable must be resolved (via ctx.Solve()) before BuildPath is called.

func (*PathBuilder) ReflectedAbout

func (p *PathBuilder) ReflectedAbout(x1, y1, x2, y2 float64) *PathBuilder

ReflectedAbout adds a reflection about the line through (x1,y1) and (x2,y2). Mirrors MetaPost's "reflectedabout(z1, z2)".

func (*PathBuilder) Rotated

func (p *PathBuilder) Rotated(angleDeg float64) *PathBuilder

Rotated adds a rotation transformation around the origin. Angle is in degrees (positive = counter-clockwise). Mirrors MetaPost's "path rotated angle".

func (*PathBuilder) RotatedAround

func (p *PathBuilder) RotatedAround(cx, cy, angleDeg float64) *PathBuilder

RotatedAround adds a rotation around a given point. Equivalent to: shifted(-cx,-cy) rotated(angle) shifted(cx,cy)

func (*PathBuilder) Scaled

func (p *PathBuilder) Scaled(s float64) *PathBuilder

Scaled adds a uniform scaling transformation around the origin. Mirrors MetaPost's "path scaled s".

func (*PathBuilder) ScaledAround

func (p *PathBuilder) ScaledAround(cx, cy, s float64) *PathBuilder

ScaledAround adds a scaling around a given point. Equivalent to: shifted(-cx,-cy) scaled(s) shifted(cx,cy)

func (*PathBuilder) Shifted

func (p *PathBuilder) Shifted(dx, dy float64) *PathBuilder

Shifted adds a translation transformation to be applied after solving. Mirrors MetaPost's "path shifted (dx, dy)".

func (*PathBuilder) Slanted

func (p *PathBuilder) Slanted(s float64) *PathBuilder

Slanted adds a horizontal shear transformation. Mirrors MetaPost's "path slanted s".

func (*PathBuilder) Solve

func (p *PathBuilder) Solve() (*mp.Path, error)

Solve builds the path, solves it with a new engine, and applies any pending transformations. For better performance when solving many paths, use SolveWithEngine to reuse an engine.

func (*PathBuilder) SolveWithEngine

func (p *PathBuilder) SolveWithEngine(e *mp.Engine) (*mp.Path, error)

SolveWithEngine appends the built path to the engine, runs Solve, and applies any pending transformations.

func (*PathBuilder) Transformed

func (p *PathBuilder) Transformed(t mp.Transform) *PathBuilder

Transformed adds a custom transformation.

func (*PathBuilder) WithArrow

func (p *PathBuilder) WithArrow() *PathBuilder

WithArrow adds an arrowhead at the end of the path (like drawarrow).

func (*PathBuilder) WithArrowStyle

func (p *PathBuilder) WithArrowStyle(length, angle float64) *PathBuilder

WithArrowStyle sets custom arrow head dimensions. length is the arrow head length (default 4), angle is the head angle in degrees (default 45).

func (*PathBuilder) WithContext

func (p *PathBuilder) WithContext(ctx *Context) *PathBuilder

WithContext links this path builder to an equation context. Variables used in MoveToVar/CurveToVar/LineToVar will be resolved when the context is solved.

func (*PathBuilder) WithCurl

func (p *PathBuilder) WithCurl(c float64) *PathBuilder

WithCurl sets both outgoing and incoming curl for the next segment.

func (*PathBuilder) WithDashPattern

func (p *PathBuilder) WithDashPattern(d *mp.DashPattern) *PathBuilder

WithDashPattern sets a pre-created dash pattern. Use this for patterns created with mp.NewDashPattern() or mp.DashEvenly().Scaled(2), etc.

func (*PathBuilder) WithDirection

func (p *PathBuilder) WithDirection(deg float64) *PathBuilder

WithDirection sets the outgoing direction in degrees for the next segment.

func (*PathBuilder) WithDoubleArrow

func (p *PathBuilder) WithDoubleArrow() *PathBuilder

WithDoubleArrow adds arrowheads at both ends of the path (like drawdblarrow).

func (*PathBuilder) WithFill

func (p *PathBuilder) WithFill(c mp.Color) *PathBuilder

WithFill sets a fill color for this path.

func (*PathBuilder) WithIncomingCurl

func (p *PathBuilder) WithIncomingCurl(c float64) *PathBuilder

WithIncomingCurl sets incoming curl for the next segment.

func (*PathBuilder) WithIncomingDirection

func (p *PathBuilder) WithIncomingDirection(deg float64) *PathBuilder

WithIncomingDirection sets the incoming direction in degrees for the next segment.

func (*PathBuilder) WithIncomingTension

func (p *PathBuilder) WithIncomingTension(t float64) *PathBuilder

WithIncomingTension sets incoming tension for the next segment.

func (*PathBuilder) WithLineCap

func (p *PathBuilder) WithLineCap(cap int) *PathBuilder

WithLineCap sets the line cap style for endpoints. Use mp.LineCapButt (1), mp.LineCapRounded (2), or mp.LineCapSquared (3). 0 means default (rounded).

func (*PathBuilder) WithLineJoin

func (p *PathBuilder) WithLineJoin(join int) *PathBuilder

WithLineJoin sets the line join style for corners. Use mp.LineJoinMiter (0), mp.LineJoinRound (1), or mp.LineJoinBevel (2).

func (*PathBuilder) WithOutgoingCurl

func (p *PathBuilder) WithOutgoingCurl(c float64) *PathBuilder

WithOutgoingCurl sets outgoing curl for the next segment.

func (*PathBuilder) WithOutgoingTension

func (p *PathBuilder) WithOutgoingTension(t float64) *PathBuilder

WithOutgoingTension sets outgoing tension for the next segment.

func (*PathBuilder) WithPen

func (p *PathBuilder) WithPen(pen *mp.Pen) *PathBuilder

WithPen attaches a pen to this path style (mirrors pen_p in mp.c:564).

func (*PathBuilder) WithStrokeColor

func (p *PathBuilder) WithStrokeColor(c mp.Color) *PathBuilder

WithStrokeColor stores a stroke color for this path (MetaPost: withcolor).

func (*PathBuilder) WithStrokeWidth

func (p *PathBuilder) WithStrokeWidth(w float64) *PathBuilder

WithStrokeWidth sets the stroke width for this path.

func (*PathBuilder) WithTension

func (p *PathBuilder) WithTension(t float64) *PathBuilder

WithTension sets both outgoing and incoming tension for the next segment.

func (*PathBuilder) WithTensionAtLeast

func (p *PathBuilder) WithTensionAtLeast(t float64) *PathBuilder

WithTensionAtLeast mirrors MetaPost's "tension atleast t"; currently treated by storing a negative tension to signal "atleast" to the solver (mp.c uses negative values for the flag).

func (*PathBuilder) WithTensionInfinity

func (p *PathBuilder) WithTensionInfinity() *PathBuilder

WithTensionInfinity mirrors MetaPost's "tension infinity"; mapped to a large tension value.

func (*PathBuilder) XScaled

func (p *PathBuilder) XScaled(s float64) *PathBuilder

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

func (*PathBuilder) YScaled

func (p *PathBuilder) YScaled(s float64) *PathBuilder

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

func (*PathBuilder) ZScaled

func (p *PathBuilder) ZScaled(a, b float64) *PathBuilder

ZScaled adds a scaling+rotation using complex multiplication. Mirrors MetaPost's "path zscaled (a, b)".

type Picture

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

Picture mirrors MetaPost's picture container: it collects solved paths that can be drawn together. Tracks are stored as-is (no copying) similar to how MetaPost chains edge objects into a picture (mp.c around mp_make_dashes/export_dashes).

func NewPicture

func NewPicture() *Picture

NewPicture constructs an empty picture.

func (*Picture) AddLabel added in v0.1.2

func (p *Picture) AddLabel(label *mp.Label) *Picture

AddLabel adds a pre-configured label to the picture.

func (*Picture) AddPath

func (p *Picture) AddPath(path *mp.Path) *Picture

AddPath appends a solved path to the picture.

func (*Picture) AddPicture

func (p *Picture) AddPicture(other *Picture) *Picture

AddPicture appends all paths from another picture (no copies; mirrors MetaPost's picture addition semantics where edges are shared until output).

func (*Picture) Clip

func (p *Picture) Clip(clipPath *mp.Path) *Picture

Clip sets the clipping path for this picture. Mirrors MetaPost's "clip p to q" where q is the clipping boundary. All paths in the picture will be clipped to this boundary when rendered.

func (*Picture) ClipPath

func (p *Picture) ClipPath() *mp.Path

ClipPath returns the current clipping path, or nil if none is set.

func (*Picture) ConvertLabelsToPathsWithFont added in v0.1.2

func (p *Picture) ConvertLabelsToPathsWithFont(f mp.FontRenderer) error

ConvertLabelsToPathsWithFont converts all labels to glyph outline paths using the provided font. The converted paths are added to the picture's path list, and the labels are cleared. This mirrors MetaPost's behavior where text becomes a picture with glyph paths.

The font parameter must implement mp.FontRenderer. Use the font package:

import "github.com/boxesandglue/mpgo/font"
face, _ := font.Load(fontReader)
pic.ConvertLabelsToPathsWithFont(face)

func (*Picture) DotLabel added in v0.1.2

func (p *Picture) DotLabel(text string, pos mp.Point, anchor mp.Anchor, color mp.Color) *Picture

DotLabel adds a text label with a dot at the reference point. Mirrors MetaPost's dotlabel@#(s, z) command.

Example:

pic.DotLabel("$z_0$", z0, mp.AnchorLowerRight)  // dotlabel.lrt("$z_0$", z0)

func (*Picture) Label added in v0.1.2

func (p *Picture) Label(text string, pos mp.Point, anchor mp.Anchor) *Picture

Label adds a text label to the picture at the given position. Mirrors MetaPost's label@#(s, z) command.

Example:

pic.Label("A", mp.P(0, 0), mp.AnchorTop)      // label.top("A", origin)
pic.Label("B", mp.P(100, 0), mp.AnchorRight)  // label.rt("B", z1)

func (*Picture) LabelWithStyle added in v0.1.2

func (p *Picture) LabelWithStyle(text string, pos mp.Point, anchor mp.Anchor) *mp.Label

LabelWithStyle adds a styled text label to the picture. Returns the created label for further customization.

func (*Picture) Labels added in v0.1.2

func (p *Picture) Labels() []*mp.Label

Labels returns all labels in the picture.

func (*Picture) Paths

func (p *Picture) Paths() []*mp.Path

Paths exposes the collected paths.

type Point

type Point = mp.Point

Point is an alias for mp.Point for convenience in the fluent API.

func P

func P(x, y float64) Point

P creates a Point from x, y coordinates. This is a convenience re-export of mp.P for use in fluent path building.

type Var

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

Var represents a point variable with x and y components. Components can be known (fixed value) or unknown (to be solved).

func (*Var) Point

func (v *Var) Point() mp.Point

Point returns the variable as an mp.Point. Only valid after Solve().

func (*Var) SetX

func (v *Var) SetX(x float64) *Var

SetX sets the x-coordinate to a known value.

func (*Var) SetXY

func (v *Var) SetXY(x, y float64) *Var

SetXY sets both coordinates to known values.

func (*Var) SetY

func (v *Var) SetY(y float64) *Var

SetY sets the y-coordinate to a known value.

func (*Var) X

func (v *Var) X() float64

X returns the x-coordinate. Only valid after Solve().

func (*Var) XY

func (v *Var) XY() (float64, float64)

XY returns the point's coordinates. Only valid after Solve().

func (*Var) Y

func (v *Var) Y() float64

Y returns the y-coordinate. Only valid after Solve().

Jump to

Keyboard shortcuts

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