1
2
3
4
5
6
7
8
9
10
11
12
13
14 package main
15
16 import (
17 "bytes"
18 "flag"
19 "fmt"
20 "go/format"
21 "image"
22 "image/draw"
23 "io/ioutil"
24 "log"
25 "net/http"
26 "strings"
27 "unicode"
28
29 "github.com/golang/freetype/truetype"
30 "golang.org/x/image/font"
31 "golang.org/x/image/math/fixed"
32 )
33
34 var (
35 fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename or URL of the TTF font")
36 hinting = flag.String("hinting", "none", "none, vertical or full")
37 pkg = flag.String("pkg", "example", "the package name for the generated code")
38 size = flag.Float64("size", 12, "the number of pixels in 1 em")
39 vr = flag.String("var", "example", "the variable name for the generated code")
40 )
41
42 func loadFontFile() ([]byte, error) {
43 if strings.HasPrefix(*fontfile, "http://") || strings.HasPrefix(*fontfile, "https://") {
44 resp, err := http.Get(*fontfile)
45 if err != nil {
46 return nil, err
47 }
48 defer resp.Body.Close()
49 return ioutil.ReadAll(resp.Body)
50 }
51 return ioutil.ReadFile(*fontfile)
52 }
53
54 func parseHinting(h string) font.Hinting {
55 switch h {
56 case "full":
57 return font.HintingFull
58 case "vertical":
59 log.Fatal("TODO: have package truetype implement vertical hinting")
60 return font.HintingVertical
61 }
62 return font.HintingNone
63 }
64
65 func privateUseArea(r rune) bool {
66 return 0xe000 <= r && r <= 0xf8ff ||
67 0xf0000 <= r && r <= 0xffffd ||
68 0x100000 <= r && r <= 0x10fffd
69 }
70
71 func loadRanges(f *truetype.Font) (ret [][2]rune) {
72 rr := [2]rune{-1, -1}
73 for r := rune(0); r <= unicode.MaxRune; r++ {
74 if privateUseArea(r) {
75 continue
76 }
77 if f.Index(r) == 0 {
78 continue
79 }
80 if rr[1] == r {
81 rr[1] = r + 1
82 continue
83 }
84 if rr[0] != -1 {
85 ret = append(ret, rr)
86 }
87 rr = [2]rune{r, r + 1}
88 }
89 if rr[0] != -1 {
90 ret = append(ret, rr)
91 }
92 return ret
93 }
94
95 func emptyCol(m *image.Gray, r image.Rectangle, x int) bool {
96 for y := r.Min.Y; y < r.Max.Y; y++ {
97 if m.GrayAt(x, y).Y > 0 {
98 return false
99 }
100 }
101 return true
102 }
103
104 func emptyRow(m *image.Gray, r image.Rectangle, y int) bool {
105 for x := r.Min.X; x < r.Max.X; x++ {
106 if m.GrayAt(x, y).Y > 0 {
107 return false
108 }
109 }
110 return true
111 }
112
113 func tightBounds(m *image.Gray) (r image.Rectangle) {
114 r = m.Bounds()
115 for ; r.Min.Y < r.Max.Y && emptyRow(m, r, r.Min.Y+0); r.Min.Y++ {
116 }
117 for ; r.Min.Y < r.Max.Y && emptyRow(m, r, r.Max.Y-1); r.Max.Y-- {
118 }
119 for ; r.Min.X < r.Max.X && emptyCol(m, r, r.Min.X+0); r.Min.X++ {
120 }
121 for ; r.Min.X < r.Max.X && emptyCol(m, r, r.Max.X-1); r.Max.X-- {
122 }
123 return r
124 }
125
126 func printPix(ranges [][2]rune, glyphs map[rune]*image.Gray, b image.Rectangle) []byte {
127 buf := new(bytes.Buffer)
128 for _, rr := range ranges {
129 for r := rr[0]; r < rr[1]; r++ {
130 m := glyphs[r]
131 fmt.Fprintf(buf, "// U+%08x '%c'\n", r, r)
132 for y := b.Min.Y; y < b.Max.Y; y++ {
133 for x := b.Min.X; x < b.Max.X; x++ {
134 fmt.Fprintf(buf, "%#02x, ", m.GrayAt(x, y).Y)
135 }
136 fmt.Fprintln(buf)
137 }
138 fmt.Fprintln(buf)
139 }
140 }
141 return buf.Bytes()
142 }
143
144 func printRanges(ranges [][2]rune) []byte {
145 buf := new(bytes.Buffer)
146 offset := 0
147 for _, rr := range ranges {
148 fmt.Fprintf(buf, "{'\\U%08x', '\\U%08x', %d},\n", rr[0], rr[1], offset)
149 offset += int(rr[1] - rr[0])
150 }
151 return buf.Bytes()
152 }
153
154 func main() {
155 flag.Parse()
156 b, err := loadFontFile()
157 if err != nil {
158 log.Fatal(err)
159 }
160 f, err := truetype.Parse(b)
161 if err != nil {
162 log.Fatal(err)
163 }
164 face := truetype.NewFace(f, &truetype.Options{
165 Size: *size,
166 Hinting: parseHinting(*hinting),
167 })
168 defer face.Close()
169
170 fBounds := f.Bounds(fixed.Int26_6(*size * 64))
171 iBounds := image.Rect(
172 +fBounds.Min.X.Floor(),
173 -fBounds.Max.Y.Ceil(),
174 +fBounds.Max.X.Ceil(),
175 -fBounds.Min.Y.Floor(),
176 )
177
178 tBounds := image.Rectangle{}
179 glyphs := map[rune]*image.Gray{}
180 advance := fixed.Int26_6(-1)
181
182 ranges := loadRanges(f)
183 for _, rr := range ranges {
184 for r := rr[0]; r < rr[1]; r++ {
185 dr, mask, maskp, adv, ok := face.Glyph(fixed.Point26_6{}, r)
186 if !ok {
187 log.Fatalf("could not load glyph for %U", r)
188 }
189 if advance < 0 {
190 advance = adv
191 } else if advance != adv {
192 log.Fatalf("advance was not constant: got %v and %v", advance, adv)
193 }
194 dst := image.NewGray(iBounds)
195 draw.DrawMask(dst, dr, image.White, image.Point{}, mask, maskp, draw.Src)
196 glyphs[r] = dst
197 tBounds = tBounds.Union(tightBounds(dst))
198 }
199 }
200
201
202 width, height := tBounds.Dx(), tBounds.Dy()
203
204 buf := new(bytes.Buffer)
205 fmt.Fprintf(buf, "// generated by go generate; DO NOT EDIT.\n\npackage %s\n\n", *pkg)
206 fmt.Fprintf(buf, "import (\n\"image\"\n\n\"golang.org/x/image/font/basicfont\"\n)\n\n")
207 fmt.Fprintf(buf, "// %s contains %d %d×%d glyphs in %d Pix bytes.\n",
208 *vr, len(glyphs), width, height, len(glyphs)*width*height)
209 fmt.Fprintf(buf, `var %s = basicfont.Face{
210 Advance: %d,
211 Width: %d,
212 Height: %d,
213 Ascent: %d,
214 Descent: %d,
215 Left: %d,
216 Mask: &image.Alpha{
217 Stride: %d,
218 Rect: image.Rectangle{Max: image.Point{%d, %d*%d}},
219 Pix: []byte{
220 %s
221 },
222 },
223 Ranges: []basicfont.Range{
224 %s
225 },
226 }`, *vr, advance.Ceil(), width, face.Metrics().Height.Ceil(), -tBounds.Min.Y, +tBounds.Max.Y, tBounds.Min.X,
227 width, width, len(glyphs), height,
228 printPix(ranges, glyphs, tBounds), printRanges(ranges))
229
230 fmted, err := format.Source(buf.Bytes())
231 if err != nil {
232 log.Fatalf("format.Source: %v", err)
233 }
234 if err := ioutil.WriteFile(*vr+".go", fmted, 0644); err != nil {
235 log.Fatalf("ioutil.WriteFile: %v", err)
236 }
237 }
238
View as plain text