text/glyph_outline.go
Functions
func Clone
func (o *GlyphOutline) Clone() *GlyphOutline {
if o == nil {
return nil
}
clone := &GlyphOutline{
Segments: make([]OutlineSegment, len(o.Segments)),
Bounds: o.Bounds,
Advance: o.Advance,
LSB: o.LSB,
GID: o.GID,
Type: o.Type,
}
copy(clone.Segments, o.Segments)
return clone
}
func Error
func (e *FontError) Error() string {
return "text: " + e.Reason
}
func ExtractOutline
ExtractOutline extracts the outline for a glyph at the given size.
The size is in pixels (ppem - pixels per em).
Returns nil if the glyph has no outline (e.g., space character).
func (e *OutlineExtractor) ExtractOutline(parsedFont ParsedFont, gid GlyphID, size float64) (*GlyphOutline, error) {
return e.ExtractOutlineHinted(parsedFont, gid, size, HintingNone)
}
func ExtractOutlineHinted
ExtractOutlineHinted extracts the outline for a glyph at the given size
with the specified hinting mode.
When hinting is enabled:
- Advance widths are grid-fitted to integer pixel boundaries (via sfnt)
- Y-coordinates of horizontal segments are snapped to pixel grid
(crisp baselines, x-heights, cap-heights)
- HintingVertical snaps only Y-coordinates (horizontal stems)
- HintingFull snaps both X and Y coordinates
Hinting should be disabled for rotated/scaled text where grid-fitting
doesn't apply (the pixel grid is no longer axis-aligned).
func (e *OutlineExtractor) ExtractOutlineHinted(parsedFont ParsedFont, gid GlyphID, size float64, hinting Hinting) (*GlyphOutline, error) {
xiFont, ok := parsedFont.(*ximageParsedFont)
if !ok {
return nil, ErrUnsupportedFontType
}
outline, err := e.extractFromSFNT(xiFont.font, gid, size, hinting)
if err != nil {
return nil, err
}
if outline == nil || hinting == HintingNone {
return outline, nil
}
gridFitOutline(outline, hinting)
return outline, nil
}
func IdentityTransform
IdentityTransform returns the identity transformation.
func IdentityTransform() *AffineTransform {
return &AffineTransform{A: 1, D: 1}
}
func IsEmpty
IsEmpty returns true if the outline has no segments.
func (o *GlyphOutline) IsEmpty() bool {
return len(o.Segments) == 0
}
func Multiply
Multiply returns the composition of two transformations.
func (m *AffineTransform) Multiply(other *AffineTransform) *AffineTransform {
return &AffineTransform{
A: m.A*other.A + m.B*other.C,
B: m.A*other.B + m.B*other.D,
C: m.C*other.A + m.D*other.C,
D: m.C*other.B + m.D*other.D,
Tx: m.A*other.Tx + m.B*other.Ty + m.Tx,
Ty: m.C*other.Tx + m.D*other.Ty + m.Ty,
}
}
func NewOutlineExtractor
NewOutlineExtractor creates a new outline extractor.
func NewOutlineExtractor() *OutlineExtractor {
return &OutlineExtractor{}
}
func Scale
Scale returns a new outline with all coordinates scaled by the given factor.
func (o *GlyphOutline) Scale(factor float32) *GlyphOutline {
if o == nil {
return nil
}
scaled := &GlyphOutline{
Segments: make([]OutlineSegment, len(o.Segments)),
Bounds: Rect{
MinX: o.Bounds.MinX * float64(factor),
MinY: o.Bounds.MinY * float64(factor),
MaxX: o.Bounds.MaxX * float64(factor),
MaxY: o.Bounds.MaxY * float64(factor),
},
Advance: o.Advance * factor,
LSB: o.LSB * factor,
GID: o.GID,
Type: o.Type,
}
for i, seg := range o.Segments {
scaled.Segments[i] = OutlineSegment{
Op: seg.Op,
Points: [3]OutlinePoint{
{X: seg.Points[0].X * factor, Y: seg.Points[0].Y * factor},
{X: seg.Points[1].X * factor, Y: seg.Points[1].Y * factor},
{X: seg.Points[2].X * factor, Y: seg.Points[2].Y * factor},
},
}
}
return scaled
}
func ScaleTransform
ScaleTransform returns a scaling transformation.
func ScaleTransform(sx, sy float32) *AffineTransform {
return &AffineTransform{A: sx, D: sy}
}
func SegmentCount
SegmentCount returns the number of segments in the outline.
func (o *GlyphOutline) SegmentCount() int {
return len(o.Segments)
}
func String
String returns a string representation of the operation.
func (op OutlineOp) String() string {
switch op {
case OutlineOpMoveTo:
return "MoveTo"
case OutlineOpLineTo:
return "LineTo"
case OutlineOpQuadTo:
return "QuadTo"
case OutlineOpCubicTo:
return "CubicTo"
default:
return "Unknown"
}
}
func Transform
Transform returns a new outline with all coordinates transformed.
func (o *GlyphOutline) Transform(m *AffineTransform) *GlyphOutline {
if o == nil || m == nil {
return o.Clone()
}
transformed := &GlyphOutline{
Segments: make([]OutlineSegment, len(o.Segments)),
Advance: o.Advance,
LSB: o.LSB,
GID: o.GID,
Type: o.Type,
}
// Transform all segments and compute new bounds
minX, minY := float32(1e10), float32(1e10)
maxX, maxY := float32(-1e10), float32(-1e10)
for i, seg := range o.Segments {
transformed.Segments[i] = OutlineSegment{Op: seg.Op}
pointCount := 1
switch seg.Op {
case OutlineOpMoveTo, OutlineOpLineTo:
pointCount = 1
case OutlineOpQuadTo:
pointCount = 2
case OutlineOpCubicTo:
pointCount = 3
}
for j := 0; j < pointCount; j++ {
x, y := m.TransformPoint(seg.Points[j].X, seg.Points[j].Y)
transformed.Segments[i].Points[j] = OutlinePoint{X: x, Y: y}
updateMinMax(x, y, &minX, &minY, &maxX, &maxY)
}
}
if len(o.Segments) > 0 {
transformed.Bounds = Rect{
MinX: float64(minX),
MinY: float64(minY),
MaxX: float64(maxX),
MaxY: float64(maxY),
}
}
return transformed
}
func TransformPoint
TransformPoint applies the transformation to a point.
func (m *AffineTransform) TransformPoint(x, y float32) (float32, float32) {
return m.A*x + m.B*y + m.Tx, m.C*x + m.D*y + m.Ty
}
func Translate
Translate returns a new outline with all coordinates translated by (dx, dy).
func (o *GlyphOutline) Translate(dx, dy float32) *GlyphOutline {
if o == nil {
return nil
}
translated := &GlyphOutline{
Segments: make([]OutlineSegment, len(o.Segments)),
Bounds: Rect{
MinX: o.Bounds.MinX + float64(dx),
MinY: o.Bounds.MinY + float64(dy),
MaxX: o.Bounds.MaxX + float64(dx),
MaxY: o.Bounds.MaxY + float64(dy),
},
Advance: o.Advance,
LSB: o.LSB,
GID: o.GID,
Type: o.Type,
}
for i, seg := range o.Segments {
translated.Segments[i] = OutlineSegment{
Op: seg.Op,
Points: [3]OutlinePoint{
{X: seg.Points[0].X + dx, Y: seg.Points[0].Y + dy},
{X: seg.Points[1].X + dx, Y: seg.Points[1].Y + dy},
{X: seg.Points[2].X + dx, Y: seg.Points[2].Y + dy},
},
}
}
return translated
}
func TranslateTransform
TranslateTransform returns a translation transformation.
func TranslateTransform(tx, ty float32) *AffineTransform {
return &AffineTransform{A: 1, D: 1, Tx: tx, Ty: ty}
}
Structs
type OutlinePoint struct
OutlinePoint represents a point in a glyph outline.
All coordinates are in font units and should be scaled by size/unitsPerEm.
type OutlinePoint struct {
X, Y float32
}
type OutlineSegment struct
OutlineSegment represents a segment of a glyph outline.
type OutlineSegment struct {
// Op is the segment operation type.
Op OutlineOp
// Points contains the control and end points for this segment.
// - MoveTo: Points[0] is the target point
// - LineTo: Points[0] is the target point
// - QuadTo: Points[0] is control, Points[1] is target
// - CubicTo: Points[0], Points[1] are controls, Points[2] is target
Points [3]OutlinePoint
}
type GlyphOutline struct
GlyphOutline represents the vector outline of a glyph.
The outline consists of one or more closed contours.
type GlyphOutline struct {
// Segments is the list of path segments that make up the outline.
Segments []OutlineSegment
// Bounds is the bounding box of the outline in scaled units.
Bounds Rect
// Advance is the horizontal advance width of the glyph.
Advance float32
// LSB is the left side bearing.
LSB float32
// GID is the glyph ID this outline represents.
GID GlyphID
// Type indicates the type of glyph (outline, bitmap, COLR).
Type GlyphType
}
type AffineTransform struct
AffineTransform represents a 2D affine transformation matrix.
The matrix is:
[A B Tx]
[C D Ty]
[0 0 1 ]
type AffineTransform struct {
A, B, C, D float32 // Matrix coefficients
Tx, Ty float32 // Translation
}
type OutlineExtractor struct
OutlineExtractor extracts glyph outlines from fonts.
It uses a buffer pool internally for efficiency.
type OutlineExtractor struct {
// buffer is reused for sfnt operations
buffer sfnt.Buffer
}
type FontError struct
FontError represents a font-related error.
type FontError struct {
Reason string
}
Clone creates a deep copy of the outline.