Skip to content
This repository was archived by the owner on Jun 18, 2025. It is now read-only.

Add stroke functionality to the glyphs #49

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions truetype/face.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ type Options struct {
//
// A zero value means to use 1 sub-pixel location.
SubPixelsY int

// Stroke is the number of pixels that the font glyphs are being stroked.
//
// A zero values means no stroke.
Stroke int
}

func (o *Options) size() float64 {
Expand Down Expand Up @@ -182,6 +187,7 @@ func NewFace(f *Font, opts *Options) font.Face {
hinting: opts.hinting(),
scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)),
glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()),
stroke: fixed.I(opts.Stroke * 2),
}
a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX()
a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY()
Expand All @@ -207,6 +213,10 @@ func NewFace(f *Font, opts *Options) font.Face {
a.r.SetBounds(a.maxw, a.maxh)
a.p = facePainter{a}

if a.stroke != 0 {
a.r.UseNonZeroWinding = true
}

return a
}

Expand All @@ -220,6 +230,7 @@ type face struct {
subPixelY uint32
subPixelBiasY fixed.Int26_6
subPixelMaskY fixed.Int26_6
stroke fixed.Int26_6
masks *image.Alpha
glyphCache []glyphCacheEntry
r raster.Rasterizer
Expand Down Expand Up @@ -329,6 +340,10 @@ func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.In
if xmin > xmax || ymin > ymax {
return fixed.Rectangle26_6{}, 0, false
}
xmin -= a.stroke
ymin -= a.stroke
xmax += a.stroke
ymax += a.stroke
return fixed.Rectangle26_6{
Min: fixed.Point26_6{
X: xmin,
Expand Down Expand Up @@ -364,6 +379,10 @@ func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v glyphCacheVal, ok
if xmin > xmax || ymin > ymax {
return glyphCacheVal{}, false
}
xmin -= int(a.stroke) >> 6
ymin -= int(a.stroke) >> 6
xmax += int(a.stroke) >> 6
ymax += int(a.stroke) >> 6
// A TrueType's glyph's nodes can have negative co-ordinates, but the
// rasterizer clips anything left of x=0 or above y=0. xmin and ymin are
// the pixel offsets, based on the font's FUnit metrics, that let a
Expand Down Expand Up @@ -434,7 +453,8 @@ func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) {
others = ps
}
}
a.r.Start(start)
path := raster.Path{}
path.Start(start)
q0, on0 := start, true
for _, p := range others {
q := fixed.Point26_6{
Expand All @@ -444,9 +464,9 @@ func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) {
on := p.Flags&0x01 != 0
if on {
if on0 {
a.r.Add1(q)
path.Add1(q)
} else {
a.r.Add2(q0, q)
path.Add2(q0, q)
}
} else {
if on0 {
Expand All @@ -456,16 +476,22 @@ func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) {
X: (q0.X + q.X) / 2,
Y: (q0.Y + q.Y) / 2,
}
a.r.Add2(q0, mid)
path.Add2(q0, mid)
}
}
q0, on0 = q, on
}
// Close the curve.
if on0 {
a.r.Add1(start)
path.Add1(start)
} else {
path.Add2(q0, start)
}

if a.stroke == 0 {
a.r.AddPath(path)
} else {
a.r.Add2(q0, start)
a.r.AddStroke(path, a.stroke, raster.RoundCapper, raster.RoundJoiner)
}
}

Expand Down