freetype/example/text2svg/main.go

175 lines
3.8 KiB
Go
Raw Normal View History

// The text2svg command converts a text string to a stroked SVG path
// in a given TrueType v1 font.
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
// flags
var (
textFlag = flag.String("text", "Hamburger", "the text to print")
fontFlag = flag.String("font", "/Library/Fonts/Georgia Italic.ttf",
"file name of the TrueType v1 font to use")
scaleFlag = flag.Int("scale", 100, "scale in points")
)
func main() {
flag.Parse()
log.SetPrefix("text2svg: ")
log.SetFlags(0)
ttfdata, err := ioutil.ReadFile(*fontFlag)
if err != nil {
log.Fatalf("loading font: %v", err)
}
f, err := truetype.Parse(ttfdata)
if err != nil {
log.Fatalf("parsing font: %v", err)
}
fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
"style='fill: grey' width='%d' height='%d'>\n",
1000, 1000)
scale := fixed.I(*scaleFlag)
dy = scale // set the baseline one line below the origin
var prevIndex truetype.Index
for i, r := range *textFlag {
index := f.Index(r)
// Load the contours for a glyph.
var gbuf truetype.GlyphBuf
if err := gbuf.Load(f, scale, index, font.HintingNone); err != nil {
log.Fatalf("loading glyph: %v", err)
}
// Emit a single SVG <path> for all glyph contours.
fmt.Printf("<path d='")
prevEnd := 0
for _, end := range gbuf.Ends {
drawContour(gbuf.Points[prevEnd:end], drawSVG)
prevEnd = end
}
fmt.Printf("'/>\n")
// Advance the position.
dx += gbuf.AdvanceWidth
if i > 0 {
dx += f.Kern(scale, prevIndex, index)
}
prevIndex = index
}
fmt.Println("</svg>")
}
func drawSVG(cmd rune, p0, p1 fixed.Point26_6) {
switch cmd {
case 'M': // moveto
fmt.Printf("M%s ", p2svg(p0))
case 'L': // lineto
fmt.Printf("L%s ", p2svg(p0))
case 'Q': // quadratic spline
fmt.Printf("Q%s %s ", p2svg(p0), p2svg(p1))
}
}
var dx, dy fixed.Int26_6
func p2svg(p fixed.Point26_6) string {
return fmt.Sprintf("%v,%v",
float64(dx+p.X)/64,
float64(dy-p.Y)/64)
}
var dummy fixed.Point26_6
// drawContour calls the draw function for each moveto, lineto, or
// quadratic spline command in the specified contour.
//
// Stolen from drawContour in github.com/golang/freetype/freetype.go.
// It would be nice if that version was reusable.
func drawContour(ps []truetype.Point, draw func(cmd rune, p0, p1 fixed.Point26_6)) {
if len(ps) == 0 {
return
}
// The low bit of each point's Flags value is whether the
// point is on the curve. Truetype fonts only have quadratic
// Bézier curves, not cubics. Thus, two consecutive off-curve
// points imply an on-curve point in the middle of those two.
//
// See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details.
// ps[0] is a truetype.Point measured in FUnits and positive Y going
// upwards. start is the same thing measured in fixed point units and
// positive Y going downwards, and offset by (dx, dy).
start := fixed.Point26_6{
X: ps[0].X,
Y: ps[0].Y,
}
var others []truetype.Point
if ps[0].Flags&1 != 0 {
others = ps[1:]
} else {
last := fixed.Point26_6{
X: ps[len(ps)-1].X,
Y: ps[len(ps)-1].Y,
}
if ps[len(ps)-1].Flags&1 != 0 {
start = last
others = ps[:len(ps)-1]
} else {
start = fixed.Point26_6{
X: (start.X + last.X) / 2,
Y: (start.Y + last.Y) / 2,
}
others = ps
}
}
draw('M', start, dummy)
q0, on0 := start, true
for _, p := range others {
q := fixed.Point26_6{
X: p.X,
Y: p.Y,
}
on := p.Flags&1 != 0
if on {
if on0 {
draw('L', q, dummy)
} else {
draw('Q', q0, q)
}
} else {
if on0 {
// No-op.
} else {
mid := fixed.Point26_6{
X: (q0.X + q.X) / 2,
Y: (q0.Y + q.Y) / 2,
}
draw('Q', q0, mid)
}
}
q0, on0 = q, on
}
// Close the curve.
if on0 {
draw('L', start, dummy)
} else {
draw('Q', q0, start)
}
}