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
- func Distance(a, b Point) float64
- func LabelAnchorFactors(anchor Anchor) (xf, yf float64)
- func LabelOffsetVector(anchor Anchor) (dx, dy float64)
- func PathPoints(path *Path) [][2]Number
- func PenEnvelopeHull(path *Path, pen *Pen) [][2]Number
- type Anchor
- type ArrowStyle
- type Color
- type DashPattern
- type Engine
- type FontRenderer
- type Knot
- type KnotOrigin
- type KnotType
- type Label
- func (l *Label) EstimateBounds() (minX, minY, maxX, maxY float64)
- func (l *Label) ToPaths(f FontRenderer) ([]*Path, error)
- func (l *Label) WithColor(c Color) *Label
- func (l *Label) WithFontFamily(family string) *Label
- func (l *Label) WithFontSize(size float64) *Label
- func (l *Label) WithOffset(offset float64) *Label
- type Number
- type Path
- func ArrowHeadEnd(p *Path, ahLength, ahAngle Number) *Path
- func ArrowHeadStart(p *Path, ahLength, ahAngle Number) *Path
- func BuildCycle(paths ...*Path) *Path
- func FullCircle() *Path
- func HalfCircle() *Path
- func MakeEnvelope(path *Path, pen *Pen) *Path
- func NewPath() *Path
- func OffsetOutline(path *Path, pen *Pen) *Path
- func QuarterCircle() *Path
- func ShortenPathForArrow(p *Path, shortenStart, shortenEnd Number) *Path
- func UnitSquare() *Path
- func (p *Path) Append(k *Knot)
- func (p *Path) ArcLength() Number
- func (p *Path) ArcLengthSegment(segIdx int) Number
- func (p *Path) ArcTime(arcLen Number) Number
- func (p *Path) Copy() *Path
- func (p *Path) CutAfter(q *Path) *Path
- func (p *Path) CutBefore(q *Path) *Path
- func (p *Path) DirectionOf(t Number) (dx, dy Number)
- func (p *Path) DirectionPointOf(dx, dy Number) (x, y Number, found bool)
- func (p *Path) DirectionTimeOf(dx, dy Number) Number
- func (p *Path) IntersectionPoint(q *Path) (x, y Number, found bool)
- func (p *Path) IntersectionTimes(q *Path) (t1, t2 Number)
- func (p *Path) PathLength() int
- func (p *Path) PointOf(t Number) (x, y Number)
- func (p *Path) PostcontrolOf(t Number) (x, y Number)
- func (p *Path) PrecontrolOf(t Number) (x, y Number)
- func (p *Path) ReflectedAbout(x1, y1, x2, y2 Number) *Path
- func (p *Path) Reversed() *Path
- func (p *Path) Rotated(angleDeg Number) *Path
- func (p *Path) RotatedAround(cx, cy, angleDeg Number) *Path
- func (p *Path) Scaled(s Number) *Path
- func (p *Path) ScaledAround(cx, cy, s Number) *Path
- func (p *Path) Shifted(dx, dy Number) *Path
- func (p *Path) Slanted(s Number) *Path
- func (p *Path) String() string
- func (p *Path) Subpath(t1, t2 Number) *Path
- func (p *Path) Transformed(t Transform) *Path
- func (p *Path) XScaled(s Number) *Path
- func (p *Path) YScaled(s Number) *Path
- func (p *Path) ZScaled(a, b Number) *Path
- type PathNormal
- type Pen
- type Point
- func Dir(angle float64) Point
- func LineIntersection(p1, p2, p3, p4 Point) (Point, bool)
- func MidPoint(a, b Point) Point
- func P(x, y float64) Point
- func PerpendicularFoot(p, p1, p2 Point) Point
- func PointBetween(a, b Point, t float64) Point
- func PointOnLineAtX(p1, p2 Point, x float64) (Point, bool)
- func PointOnLineAtY(p1, p2 Point, y float64) (Point, bool)
- func Reflection(p, p1, p2 Point) Point
- func Rotate(p Point, angle float64) Point
- func RotateAround(p, c Point, angle float64) Point
- func Scale(p Point, s float64) Point
- func ScaleAround(p, c Point, s float64) Point
- type Style
- type TextToPathsOptions
- type Transform
- func Identity() Transform
- func ReflectedAbout(x1, y1, x2, y2 Number) Transform
- func Rotated(angleDeg Number) Transform
- func RotatedAround(cx, cy, angleDeg Number) Transform
- func Scaled(s Number) Transform
- func ScaledAround(cx, cy, s Number) Transform
- func Shifted(dx, dy Number) Transform
- func Slanted(s Number) Transform
- func XScaled(s Number) Transform
- func YScaled(s Number) Transform
- func ZScaled(a, b Number) Transform
Constants ¶
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.
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).
const ( DefaultAHLength = 4.0 // default arrowhead length (4bp) DefaultAHAngle = 45.0 // default arrowhead angle (45 degrees) )
Arrow constants (MetaPost defaults from plain.mp)
const DefaultDotLabelDiam = 3.0
DefaultDotLabelDiam is the default diameter for dots in DotLabel. Mirrors MetaPost's dotlabeldiam (3bp in plain.mp).
const DefaultFontSize = 10.0
DefaultFontSize is the default font size for labels. Corresponds to MetaPost's defaultscale with cmr10 (10pt).
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 LabelAnchorFactors ¶ added in v0.1.2
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
LabelOffsetVector returns the offset direction vector for an anchor. These values mirror MetaPost's laboff pairs from plain.mp.
func PathPoints ¶
PathPoints collects knot coordinates (ignores controls) for polygon export.
func PenEnvelopeHull ¶
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.
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
}
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
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 (*Label) EstimateBounds ¶ added in v0.1.2
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) WithFontFamily ¶ added in v0.1.2
WithFontFamily sets the font family.
func (*Label) WithFontSize ¶ added in v0.1.2
WithFontSize sets the font size.
func (*Label) WithOffset ¶ added in v0.1.2
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 ¶
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).
type Path ¶
type Path struct {
Head *Knot
Style Style
Envelope *Path // optional precomputed offset/envelope (mp_apply_offset analogue)
}
func ArrowHeadEnd ¶
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 ¶
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 ¶
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:
- For each consecutive pair of paths (wrapping around), find intersection
- Extract the subpath of each path between its two intersection points
- 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 ¶
MakeEnvelope creates an envelope outline by walking the pen around the path. Mirrors mp_make_envelope (mp.c:13304ff / mp.w:14748ff).
func OffsetOutline ¶
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 ¶
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) ArcLength ¶
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 ¶
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 ¶
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) CutAfter ¶ added in v0.1.2
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
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 ¶
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
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
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ReflectedAbout returns a new path reflected about the line through (x1,y1) and (x2,y2).
func (*Path) Reversed ¶
Reversed returns a copy of the path with direction reversed. Used for subpath when t1 > t2.
func (*Path) Rotated ¶
Rotated returns a new path rotated around the origin. Angle is in degrees (positive = counter-clockwise).
func (*Path) RotatedAround ¶
RotatedAround returns a new path rotated around a given point.
func (*Path) ScaledAround ¶
ScaledAround returns a new path scaled around a given point.
func (*Path) Subpath ¶
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 ¶
Transformed returns a new path with the given transformation applied.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
PenRazorRotated constructs a razor pen rotated by the given angle (in degrees). This is equivalent to penrazor scaled size rotated angle in MetaPost.
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 ¶
Dir returns a unit vector at the given angle in degrees. Equivalent to MetaPost's "dir(angle)".
func LineIntersection ¶
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 ¶
MidPoint returns the midpoint between two points. Equivalent to MetaPost's "0.5[a,b]".
func PerpendicularFoot ¶
PerpendicularFoot returns the point on the line through p1 and p2 that is closest to point p (the foot of the perpendicular).
func PointBetween ¶
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 ¶
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 ¶
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 ¶
Reflection returns the reflection of point p about the line through p1 and p2.
func RotateAround ¶
RotateAround returns point p rotated by angle degrees around center point c.
func ScaleAround ¶
ScaleAround returns point p scaled by factor s from center point c.
func (Point) Cross ¶
Cross returns the 2D cross product (z-component of 3D cross product). Positive if q is counter-clockwise from p.
func (Point) Normalized ¶
Normalized returns a unit vector in the same direction. Returns zero vector if p is zero.
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 ¶
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 ¶
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 ¶
RotatedAround returns a rotation transformation around a given point. This is equivalent to: shifted(-cx,-cy) rotated(angle) shifted(cx,cy)
func Scaled ¶
Scaled returns a uniform scaling transformation around the origin. Mirrors MetaPost's "scaled s" (mp.w:28502ff).
func ScaledAround ¶
ScaledAround returns a scaling transformation around a given point. This is equivalent to: shifted(-cx,-cy) scaled(s) shifted(cx,cy)
func Shifted ¶
Shifted returns a translation transformation. Mirrors MetaPost's "shifted (dx, dy)" (mp.w:28509ff).
func Slanted ¶
Slanted returns a horizontal shear transformation. Mirrors MetaPost's "slanted s" (mp.w:28496ff). The transformation is: x' = x + s*y, y' = y
func ZScaled ¶
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 ¶
ApplyToKnot applies the transformation to all coordinates of a knot.
func (Transform) ApplyToPath ¶
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 ¶
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 ¶
Determinant returns the determinant of the transformation matrix. This represents the scaling factor for areas.