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

Add stroke functionality to glyphs #48

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
The Freetype font rasterizer in the Go programming language.

To download and install from source:
$ go get github.com/golang/freetype
$ go get github.com/tdewolff/freetype

It is an incomplete port:
* It only supports TrueType fonts, and not Type 1 fonts nor bitmap fonts.
Expand Down
4 changes: 2 additions & 2 deletions example/capjoin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -21,7 +21,7 @@ import (
"log"
"os"

"github.com/golang/freetype/raster"
"github.com/tdewolff/freetype/raster"
"golang.org/x/image/math/fixed"
)

Expand Down
4 changes: 2 additions & 2 deletions example/drawer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -24,7 +24,7 @@ import (
"math"
"os"

"github.com/golang/freetype/truetype"
"github.com/tdewolff/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
Expand Down
4 changes: 2 additions & 2 deletions example/freetype/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -23,7 +23,7 @@ import (
"log"
"os"

"github.com/golang/freetype"
"github.com/tdewolff/freetype"
"golang.org/x/image/font"
)

Expand Down
4 changes: 2 additions & 2 deletions example/gamma/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -20,7 +20,7 @@ import (
"log"
"os"

"github.com/golang/freetype/raster"
"github.com/tdewolff/freetype/raster"
"golang.org/x/image/math/fixed"
)

Expand Down
4 changes: 2 additions & 2 deletions example/genbasicfont/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -26,7 +26,7 @@ import (
"strings"
"unicode"

"github.com/golang/freetype/truetype"
"github.com/tdewolff/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
Expand Down
4 changes: 2 additions & 2 deletions example/raster/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -21,7 +21,7 @@ import (
"log"
"os"

"github.com/golang/freetype/raster"
"github.com/tdewolff/freetype/raster"
"golang.org/x/image/math/fixed"
)

Expand Down
4 changes: 2 additions & 2 deletions example/round/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -27,7 +27,7 @@ import (
"math"
"os"

"github.com/golang/freetype/raster"
"github.com/tdewolff/freetype/raster"
"golang.org/x/image/math/fixed"
)

Expand Down
4 changes: 2 additions & 2 deletions example/truetype/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// +build example
//
// This build tag means that "go install github.com/golang/freetype/..."
// This build tag means that "go install github.com/tdewolff/freetype/..."
// doesn't install this example program. Use "go run main.go" to run it or "go
// install -tags=example" to install it.

Expand All @@ -17,7 +17,7 @@ import (
"io/ioutil"
"log"

"github.com/golang/freetype/truetype"
"github.com/tdewolff/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
Expand Down
6 changes: 3 additions & 3 deletions freetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
// The freetype package provides a convenient API to draw text onto an image.
// Use the freetype/raster and freetype/truetype packages for lower level
// control over rasterization and TrueType parsing.
package freetype // import "github.com/golang/freetype"
package freetype // import "github.com/tdewolff/freetype"

import (
"errors"
"image"
"image/draw"

"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
"github.com/tdewolff/freetype/raster"
"github.com/tdewolff/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
Expand Down
2 changes: 1 addition & 1 deletion raster/raster.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// the Freetype "smooth" module, and the Anti-Grain Geometry library. A
// description of the area/coverage algorithm is at
// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm
package raster // import "github.com/golang/freetype/raster"
package raster // import "github.com/tdewolff/freetype/raster"

import (
"strconv"
Expand Down
17 changes: 12 additions & 5 deletions raster/stroke.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,12 +438,19 @@ func (k *stroker) stroke(q Path) {
if len(k.r) == 0 {
return
}
// TODO(nigeltao): if q is a closed curve then we should join the first and
// last segments instead of capping them.
k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm))

closed := q.firstPoint() == q.lastPoint()
if !closed {
k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm))
} else {
pivot := q.firstPoint()
k.jr.Join(k.p, &k.r, k.u, pivot, k.anorm, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]}))
}
addPathReversed(k.p, k.r)
pivot := q.firstPoint()
k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]}))
if !closed {
pivot := q.firstPoint()
k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]}))
}
}

// Stroke adds q stroked with the given width to p. The result is typically
Expand Down
40 changes: 33 additions & 7 deletions truetype/face.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"image"
"math"

"github.com/golang/freetype/raster"
"github.com/tdewolff/freetype/raster"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
Expand Down 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
2 changes: 1 addition & 1 deletion truetype/truetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//
// To measure a TrueType font in ideal FUnit space, use scale equal to
// font.FUnitsPerEm().
package truetype // import "github.com/golang/freetype/truetype"
package truetype // import "github.com/tdewolff/freetype/truetype"

import (
"fmt"
Expand Down