Skip to content

Commit 9b135c3

Browse files
authored
tinyfontgen: Added support for generating fonts from ttf (#37)
1 parent 7be7a47 commit 9b135c3

File tree

4 files changed

+392
-6
lines changed

4 files changed

+392
-6
lines changed

cmd/tinyfontgen-ttf/main.go

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"io"
7+
"io/ioutil"
8+
"log"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
13+
"golang.org/x/image/font/opentype"
14+
"golang.org/x/image/font/sfnt"
15+
"golang.org/x/image/math/fixed"
16+
)
17+
18+
var (
19+
fs = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
20+
pkgname = fs.String("package", "main", "package name")
21+
fontname = fs.String("fontname", "TinyFont", "font name")
22+
sz = fs.Int("size", 12, "size for font")
23+
dpi = fs.Int("dpi", 75, "dpi for font")
24+
str = fs.String("string", "", "strings for font")
25+
output = fs.String("output", "", "output path")
26+
ascii = fs.Bool("ascii", true, "include all glyphs in the font")
27+
all = fs.Bool("all", false, "include all ascii glyphs (codepoint <= 255) in the font")
28+
verbose = fs.Bool("verbose", false, "run verbosely")
29+
yadvance = fs.Int("yadvance", 0, "new line distance")
30+
31+
runesMap map[rune]bool
32+
)
33+
34+
type strslice struct {
35+
s []string
36+
}
37+
38+
func (s *strslice) String() string {
39+
return fmt.Sprintf("%v", s.s)
40+
}
41+
42+
func (s *strslice) Set(v string) error {
43+
s.s = append(s.s, v)
44+
return nil
45+
}
46+
47+
func usage() {
48+
fmt.Fprintf(os.Stderr, "usage: tinyfontgen-ttf [flags] [path ...]\n")
49+
fs.PrintDefaults()
50+
}
51+
52+
func main() {
53+
var strfiles strslice
54+
var fonts []string
55+
56+
fs.Var(&strfiles, "string-file", "strings file for font (can be set multiple times)")
57+
58+
fs.Usage = usage
59+
fs.Parse(os.Args[1:])
60+
for 0 < fs.NArg() {
61+
fonts = append(fonts, fs.Arg(0))
62+
fs.Parse(fs.Args()[1:])
63+
}
64+
65+
if *yadvance == 0 {
66+
*yadvance = *sz
67+
}
68+
69+
runesMap = map[rune]bool{}
70+
if *ascii {
71+
for i := rune(0); i < 256; i++ {
72+
runesMap[i] = true
73+
}
74+
}
75+
for _, r := range *str {
76+
runesMap[r] = true
77+
}
78+
for _, f := range strfiles.s {
79+
b, err := ioutil.ReadFile(f)
80+
if err != nil {
81+
log.Fatal(err)
82+
}
83+
for _, r := range string(b) {
84+
runesMap[r] = true
85+
}
86+
}
87+
88+
err := run(*sz, *dpi, fonts[0])
89+
if err != nil {
90+
log.Fatal(err)
91+
}
92+
}
93+
94+
func run(size, dpi int, fontfile string) error {
95+
bb, err := ioutil.ReadFile(fontfile)
96+
if err != nil {
97+
return err
98+
}
99+
100+
ft, err := opentype.Parse(bb)
101+
if err != nil {
102+
return err
103+
}
104+
105+
face, err := opentype.NewFace(ft, &opentype.FaceOptions{Size: float64(size), DPI: float64(dpi)})
106+
if err != nil {
107+
return err
108+
}
109+
110+
sfntBuf := &sfnt.Buffer{}
111+
type xx struct {
112+
Rune rune
113+
Index uint16
114+
}
115+
indexes := []xx{}
116+
117+
const runeMax = 0x3FFFF
118+
for r := rune(0); r < runeMax; r++ {
119+
idx, err := ft.GlyphIndex(sfntBuf, r)
120+
if err != nil {
121+
return err
122+
}
123+
if idx != 0 && (*all || runesMap[r]) {
124+
indexes = append(indexes, xx{
125+
Rune: r,
126+
Index: uint16(idx),
127+
})
128+
}
129+
}
130+
131+
fontBuffer := [256]uint8{}
132+
font := Font{
133+
Glyphs: make([]Glyph, len(indexes)),
134+
}
135+
for i, xxx := range indexes {
136+
dr, img, _, adv, ok := face.Glyph(fixed.Point26_6{}, xxx.Rune)
137+
if !ok {
138+
continue
139+
}
140+
141+
// font
142+
fontBuf := fontBuffer[:0]
143+
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
144+
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
145+
z := img.At(x, y)
146+
r, _, _, _ := z.RGBA()
147+
fontBuf = append(fontBuf, byte((r&0xC000)>>14))
148+
}
149+
}
150+
151+
switch len(fontBuf) % 4 {
152+
case 0:
153+
// skip
154+
case 1:
155+
fontBuf = append(fontBuf, 0, 0, 0)
156+
case 2:
157+
fontBuf = append(fontBuf, 0, 0)
158+
case 3:
159+
fontBuf = append(fontBuf, 0)
160+
}
161+
162+
font.Glyphs[i].Rune = xxx.Rune
163+
font.Glyphs[i].Width = uint8(img.Bounds().Max.X - img.Bounds().Min.X)
164+
font.Glyphs[i].Height = uint8(img.Bounds().Max.Y - img.Bounds().Min.Y)
165+
font.Glyphs[i].XAdvance = uint8(adv.Ceil())
166+
font.Glyphs[i].XOffset = int8(dr.Min.X)
167+
font.Glyphs[i].YOffset = int8(dr.Min.Y)
168+
//font.Glyphs[i].Bitmaps = make([]byte, len(fontBuf))
169+
//copy(font.Glyphs[i].Bitmaps, fontBuf)
170+
font.Glyphs[i].Bitmaps = make([]byte, len(fontBuf)/4)
171+
for j := range font.Glyphs[i].Bitmaps {
172+
font.Glyphs[i].Bitmaps[j] = fontBuf[j*4] << 6
173+
font.Glyphs[i].Bitmaps[j] += fontBuf[j*4+1] << 4
174+
font.Glyphs[i].Bitmaps[j] += fontBuf[j*4+2] << 2
175+
font.Glyphs[i].Bitmaps[j] += fontBuf[j*4+3]
176+
}
177+
}
178+
179+
var w io.Writer
180+
w = os.Stdout
181+
if *output != "" {
182+
os.MkdirAll(filepath.Dir(*output), 0666)
183+
wc, err := os.Create(*output)
184+
if err != nil {
185+
return err
186+
}
187+
defer wc.Close()
188+
w = wc
189+
}
190+
font.SaveTo(w)
191+
192+
return nil
193+
}
194+
195+
// Glyph is a struct that implements Glypher interface.
196+
type Glyph struct {
197+
Rune rune
198+
Width uint8
199+
Height uint8
200+
XAdvance uint8
201+
XOffset int8
202+
YOffset int8
203+
Bitmaps []byte
204+
}
205+
206+
// Font is a struct that implements Fonter interface.
207+
type Font struct {
208+
BBox [4]int8 // width, height, minX, minY
209+
Glyphs []Glyph
210+
YAdvance uint8
211+
}
212+
213+
func (f Font) SaveTo(w io.Writer) {
214+
pkg := *pkgname
215+
fontname := *fontname
216+
yadv := *yadvance
217+
218+
fmt.Fprintf(w, "// ttfgen %s\n", strings.Join(os.Args[1:], " "))
219+
fmt.Fprintf(w, "\n")
220+
221+
fmt.Fprintf(w, "package %s\n", pkg)
222+
fmt.Fprintf(w, "\n")
223+
fmt.Fprintf(w, "import (\n")
224+
fmt.Fprintf(w, " \"tinygo.org/x/tinyfont/const2bit\"\n")
225+
fmt.Fprintf(w, ")\n")
226+
fmt.Fprintf(w, "\n")
227+
228+
fmt.Fprintf(w, "var %s = const2bit.Font{\n", fontname)
229+
fmt.Fprintf(w, " OffsetMap: m%s,\n", fontname)
230+
fmt.Fprintf(w, " Data: d%s,\n", fontname)
231+
fmt.Fprintf(w, " YAdvance: %d,\n", yadv)
232+
fmt.Fprintf(w, " Name: %q,\n", fontname)
233+
fmt.Fprintf(w, "}\n")
234+
fmt.Fprintf(w, "\n")
235+
236+
offset := 0
237+
fmt.Fprintf(w, "// rune (3byte) + offset (3byte)\n")
238+
fmt.Fprintf(w, "const m%s = \"\" +\n", fontname)
239+
for _, x := range f.Glyphs {
240+
fmt.Fprintf(w, ` "\x%02X\x%02X\x%02X" + `, byte(x.Rune>>16), byte(x.Rune>>8), byte(x.Rune))
241+
fmt.Fprintf(w, `"\x%02X\x%02X\x%02X" + `, byte(offset>>16), byte(offset>>8), byte(offset))
242+
fmt.Fprintf(w, `// %c`, x.Rune)
243+
fmt.Fprintf(w, "\n")
244+
245+
// width + height + xadvance + xoffset + yoffset + len([]bitmaps)
246+
offset += 1 + 1 + 1 + 1 + 1 + len(x.Bitmaps)
247+
}
248+
fmt.Fprintf(w, " \"\"\n")
249+
250+
fmt.Fprintf(w, "\n")
251+
fmt.Fprintf(w, "// width + height + xadvance + xoffset + yoffset + []bitmaps\n")
252+
fmt.Fprintf(w, "const d%s = \"\" +\n", fontname)
253+
for _, x := range f.Glyphs {
254+
fmt.Fprintf(w, ` "\x%02X\x%02X\x%02X\x%02X\x%02X" + `, x.Width, x.Height, x.XAdvance, byte(x.XOffset), byte(x.YOffset))
255+
fmt.Fprintf(w, `"`)
256+
for _, y := range x.Bitmaps {
257+
fmt.Fprintf(w, `\x%02X`, y)
258+
}
259+
fmt.Fprintf(w, `" +`)
260+
fmt.Fprintf(w, "\n")
261+
}
262+
fmt.Fprintf(w, " \"\"\n")
263+
}

const2bit/const2bit.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Package const2bit is for fonts that use 2 bits per pixel.
2+
package const2bit
3+
4+
import (
5+
"image/color"
6+
7+
"tinygo.org/x/drivers"
8+
"tinygo.org/x/tinyfont"
9+
)
10+
11+
// Glyph is a struct that implements Glypher interface.
12+
type Glyph struct {
13+
Rune rune
14+
Width uint8
15+
Height uint8
16+
XAdvance uint8
17+
XOffset int8
18+
YOffset int8
19+
Bitmaps []byte
20+
}
21+
22+
// Font is a struct that implements Fonter interface.
23+
type Font struct {
24+
BBox [4]int8 // width, height, minX, minY
25+
Glyphs []Glyph
26+
YAdvance uint8
27+
28+
OffsetMap string
29+
Data string
30+
Name string
31+
}
32+
33+
// Draw sets a single glyph in the buffer of the display.
34+
// TODO: allow the user to set the color
35+
func (glyph Glyph) Draw(display drivers.Displayer, x int16, y int16, c color.RGBA) {
36+
bitmapOffset := 0
37+
bitmap := byte(0)
38+
if len(glyph.Bitmaps) > 0 {
39+
bitmap = glyph.Bitmaps[bitmapOffset]
40+
}
41+
bit := uint8(0)
42+
for j := int16(0); j < int16(glyph.Height); j++ {
43+
for i := int16(0); i < int16(glyph.Width); i++ {
44+
switch bitmap & 0xC0 {
45+
case 0xC0:
46+
display.SetPixel(x+int16(glyph.XOffset)+i, y+int16(glyph.YOffset)+j, color.RGBA{0x00, 0x00, 0x00, 0xFF})
47+
case 0x80:
48+
display.SetPixel(x+int16(glyph.XOffset)+i, y+int16(glyph.YOffset)+j, color.RGBA{0x80, 0x80, 0x80, 0xFF})
49+
case 0x40:
50+
display.SetPixel(x+int16(glyph.XOffset)+i, y+int16(glyph.YOffset)+j, color.RGBA{0xD0, 0xD0, 0xD0, 0xFF})
51+
default:
52+
}
53+
54+
bitmap <<= 2
55+
bit += 2
56+
if bit > 7 {
57+
bitmapOffset++
58+
if bitmapOffset < len(glyph.Bitmaps) {
59+
bitmap = glyph.Bitmaps[bitmapOffset]
60+
}
61+
bit = 0
62+
}
63+
}
64+
}
65+
}
66+
67+
// Info returns glyph information.
68+
func (glyph Glyph) Info() *tinyfont.GlyphInfo {
69+
return &tinyfont.GlyphInfo{
70+
Rune: glyph.Rune,
71+
Width: glyph.Width,
72+
Height: glyph.Height,
73+
XAdvance: glyph.XAdvance,
74+
XOffset: glyph.XOffset,
75+
YOffset: glyph.YOffset,
76+
}
77+
}
78+
79+
// GetYAdvance returns YAdvance of f.
80+
func (f *Font) GetYAdvance() uint8 {
81+
return f.YAdvance
82+
}
83+
84+
// GetGlyph returns the glyph corresponding to the specified rune in the font.
85+
func (font *Font) GetGlyph(r rune) tinyfont.Glypher {
86+
s := 0
87+
e := len(font.OffsetMap)/6 - 1
88+
89+
for s <= e {
90+
m := (s + e) / 2
91+
92+
r2 := rune(font.OffsetMap[m*6])<<16 + rune(font.OffsetMap[m*6+1])<<8 + rune(font.OffsetMap[m*6+2])
93+
if r2 < r {
94+
s = m + 1
95+
} else {
96+
e = m - 1
97+
}
98+
}
99+
100+
if s > len(font.OffsetMap)/6-1 {
101+
s = 0
102+
}
103+
104+
offset := uint32(font.OffsetMap[s*6+3])<<16 + uint32(font.OffsetMap[s*6+4])<<8 + uint32(font.OffsetMap[s*6+5])
105+
sz := uint32(len(font.Data[offset+5:]))
106+
if s*6+6 < len(font.OffsetMap) {
107+
sz = uint32(font.OffsetMap[s*6+9])<<16 + uint32(font.OffsetMap[s*6+10])<<8 + uint32(font.OffsetMap[s*6+11]) - offset
108+
}
109+
110+
g := Glyph{
111+
Rune: r,
112+
Width: font.Data[offset+0],
113+
Height: font.Data[offset+1],
114+
XAdvance: font.Data[offset+2],
115+
XOffset: int8(font.Data[offset+3]),
116+
YOffset: int8(font.Data[offset+4]),
117+
Bitmaps: []byte(font.Data[offset+5 : offset+5+sz]),
118+
}
119+
return g
120+
}

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ go 1.17
55
require (
66
github.com/hajimehoshi/go-jisx0208 v1.0.0
77
github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4
8-
tinygo.org/x/drivers v0.19.0
8+
golang.org/x/image v0.0.0-20220617043117-41969df76e82
9+
tinygo.org/x/drivers v0.21.0
910
)
1011

11-
require golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
12+
require golang.org/x/text v0.3.7 // indirect

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy