Glyph Outlines
Glyph outlines give you the actual vector path of a glyph — the Bezier curves that define its shape. This is useful for rendering text as filled paths (e.g., in SVG or MetaPost), hit testing, computing exact bounding boxes, or any scenario where you need the geometry of a character, not just its advance width.
Extracting an Outline
Call GlyphOutline on a Face with a glyph ID. Glyph IDs come from shaping (via buf.Info[i].GlyphID) or from a cmap lookup.
face, _ := ot.NewFace(font)
// Look up glyph ID for 'A'
cmap := face.Cmap()
gid, ok := cmap.Lookup(ot.Codepoint('A'))
if !ok {
log.Fatal("no glyph for 'A'")
}
outline, ok := face.GlyphOutline(gid)
if !ok {
// Empty glyph (e.g. space)
return
}
for _, seg := range outline.Segments {
switch seg.Op {
case ot.SegmentMoveTo:
fmt.Printf("M %.1f,%.1f\n", seg.Args[0].X, seg.Args[0].Y)
case ot.SegmentLineTo:
fmt.Printf("L %.1f,%.1f\n", seg.Args[0].X, seg.Args[0].Y)
case ot.SegmentQuadTo:
fmt.Printf("Q %.1f,%.1f %.1f,%.1f\n",
seg.Args[0].X, seg.Args[0].Y,
seg.Args[1].X, seg.Args[1].Y)
case ot.SegmentCubeTo:
fmt.Printf("C %.1f,%.1f %.1f,%.1f %.1f,%.1f\n",
seg.Args[0].X, seg.Args[0].Y,
seg.Args[1].X, seg.Args[1].Y,
seg.Args[2].X, seg.Args[2].Y)
}
}Combining Shaping and Outlines
A typical workflow shapes text first, then extracts outlines for each glyph and positions them using the shaping output:
font, _ := ot.ParseFont(fontData, 0)
face, _ := ot.NewFace(font)
shaper, _ := ot.NewShaperFromFace(face)
buf := ot.NewBuffer()
buf.AddString("Hello")
buf.GuessSegmentProperties()
shaper.Shape(buf, nil)
upem := float64(face.Upem())
fontSize := 12.0
scale := fontSize / upem
curX := 0.0
for i := range buf.Info {
gid := buf.Info[i].GlyphID
pos := buf.Pos[i]
outline, ok := face.GlyphOutline(gid)
if ok {
ox := curX + float64(pos.XOffset)*scale
oy := float64(pos.YOffset) * scale
for _, seg := range outline.Segments {
// Scale and translate each point by (ox, oy, scale)
x := float64(seg.Args[0].X)*scale + ox
y := float64(seg.Args[0].Y)*scale + oy
_ = x
_ = y
// ... render segment
}
}
curX += float64(pos.XAdvance) * scale
}Segment Types
Each segment in a GlyphOutline has an operation and up to three argument points:
| Op | Name | Args used | Description |
|---|---|---|---|
SegmentMoveTo |
Move | [0] |
Start a new contour at this point |
SegmentLineTo |
Line | [0] |
Straight line to endpoint |
SegmentQuadTo |
Quad | [0], [1] |
Quadratic Bezier: control point, endpoint |
SegmentCubeTo |
Cubic | [0], [1], [2] |
Cubic Bezier: control 1, control 2, endpoint |
TrueType fonts use quadratic Bezier curves (SegmentQuadTo). CFF/OpenType fonts (.otf) use cubic curves (SegmentCubeTo). Both formats are fully supported.
All coordinates are in font units. Scale by fontSize / upem to convert to points or pixels.
Composite Glyphs
Some glyphs are built from other glyphs — for example, “e” (U+00E9) may combine the base “e” glyph with an acute accent component. GlyphOutline resolves composite glyphs recursively and applies the affine transform (translation, scale, rotation) of each component. The returned segments are the fully flattened outline — you don’t need to handle composition yourself.
Empty Glyphs
GlyphOutline returns false (second return value) for empty glyphs like space or .notdef that have no outline data.
Converting Quadratic to Cubic Curves
If your rendering pipeline only supports cubic Bezier curves (common in PDF and PostScript), convert quadratic segments with the standard degree elevation formula:
// Quadratic control point and endpoint
qx, qy := seg.Args[0].X, seg.Args[0].Y
ex, ey := seg.Args[1].X, seg.Args[1].Y
// Previous point (start of this segment)
sx, sy := lastX, lastY
// Cubic control points
c1x := sx + 2.0/3.0*(qx-sx)
c1y := sy + 2.0/3.0*(qy-sy)
c2x := ex + 2.0/3.0*(qx-ex)
c2y := ey + 2.0/3.0*(qy-ey)This is an exact conversion — no approximation error.