Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

example/text2svg: example of how to extract quadratic splines from TTF #24

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
174 changes: 174 additions & 0 deletions example/text2svg/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// 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)
}
}