-
Notifications
You must be signed in to change notification settings - Fork 0
/
font.go
249 lines (221 loc) · 6.44 KB
/
font.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package dosfont
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"image"
"io"
"os"
"strings"
"golang.org/x/image/font/basicfont"
"github.com/lunixbochs/struc"
)
// location of the font header
const fontHeader = 0x76
// type of font resource in mz file
const fontType = 0x8008
// Font contains all the PC Screen Font (PSF) meta data and font face.
type Font struct {
Version uint16 `struc:"uint16,little"`
Size uint32 `struc:"uint32,little"`
Copyright string `struc:"[60]byte"`
Type uint16 `struc:"uint16,little"`
Points uint16 `struc:"uint16,little"`
VertRes uint16 `struc:"uint16,little"`
HorizRes uint16 `struc:"uint16,little"`
Ascent uint16 `struc:"uint16,little"`
InternalLeading uint16 `struc:"uint16,little"`
ExternalLeading uint16 `struc:"uint16,little"`
Italic bool
Underline bool
StrikeOut bool
Weight uint16 `struc:"uint16,little"`
CharSet uint8
PixWidth uint16 `struc:"uint16,little"`
PixHeight uint16 `struc:"uint16,little"`
PitchAndFamily uint8
AvgWidth uint16 `struc:"uint16,little"`
MaxWidth uint16 `struc:"uint16,little"`
FirstChar uint8
LastChar uint8
DefaultChar uint8
BreakChar uint8
WidthBytes uint16 `struc:"uint16,little"`
Device uint16 `struc:"uint16,little"`
FaceData uint16 `struc:"uint16,little"`
BitsPointer uint16 `struc:"uint16,little"`
BitsOffset uint16 `struc:"uint16,little"`
Reserved uint8
Name string `struc:"skip"`
basicfont.Face `struc:"skip"`
}
// ErrFontHeaderTooShort error in case the header is to short
var ErrFontHeaderTooShort = errors.New("Error psf header too short")
// OpenFonts opens the file under given location and returns contained fonts
func OpenFonts(path string) ([]Font, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Failed to open font file %s: %v", path, err)
}
defer file.Close()
return ReadFonts(file)
}
// ReadFonts reads font faces from a read seeker
func ReadFonts(r io.ReadSeeker) ([]Font, error) {
mz, err := ReadMZ(r)
if err != nil {
return nil, err
}
fontResources := mz.Resources(fontType)
fonts := make([]Font, len(fontResources))
for i, font := range fontResources {
// seek to font
_, err := r.Seek(int64(font.Start), io.SeekStart)
if err != nil {
return nil, err
}
// read and parse header
var buf bytes.Buffer
n, err := io.CopyN(&buf, r, fontHeader)
if err != nil {
return nil, err
}
if n != fontHeader {
return nil, ErrFontHeaderTooShort
}
err = struc.Unpack(&buf, &fonts[i])
if err != nil {
return nil, err
}
// seek to font Name and fix copyright
fonts[i].Copyright = strings.Trim(fonts[i].Copyright, " ")
if fonts[i].BitsPointer > 0 {
_, err = r.Seek(int64(font.Start+fonts[i].BitsPointer), io.SeekStart)
if err != nil {
return nil, err
}
fonts[i].Name, err = bufio.NewReader(r).ReadString(0x00)
if err != nil {
return nil, err
}
// remove delimiter (null byte)
fonts[i].Name = fonts[i].Name[:len(fonts[i].Name)-1]
}
// read chars
var char Resource
if fonts[i].Version == 0x200 {
char.Start = 0x76
char.Size = 4
} else {
char.Start = 0x94
char.Size = 6
}
glyphCount := int(fonts[i].LastChar - fonts[i].FirstChar)
// Advance is the glyph advance, in pixels.
fonts[i].Face.Advance = int(fonts[i].AvgWidth)
// Width is the glyph width, in pixels.
fonts[i].Face.Width = int(fonts[i].PixWidth)
// Height is the inter-line height, in pixels.
fonts[i].Face.Height = int(fonts[i].PixHeight)
// Ascent is the glyph ascent, in pixels.
fonts[i].Face.Ascent = int(fonts[i].PixHeight)
// Descent is the glyph descent, in pixels.
fonts[i].Face.Descent = 0
// Left is the left side bearing, in pixels. A positive value means that
// all of a glyph is to the right of the dot.
fonts[i].Face.Left = 0
// Mask contains all of the glyph masks. Its width is typically the Face's
// Width, and its height a multiple of the Face's Height.
fontmask := &image.Alpha{
Stride: fonts[i].Face.Width,
Rect: image.Rectangle{
Max: image.Point{
fonts[i].Face.Width, glyphCount * fonts[i].Face.Height,
},
},
Pix: make([]byte, glyphCount*fonts[i].Face.Width*fonts[i].Face.Height),
}
fonts[i].Face.Mask = fontmask
// Mask image.Image
// Ranges map runes to sub-images of Mask. The rune ranges must not
// overlap, and must be in increasing rune order.
fonts[i].Face.Ranges = []basicfont.Range{
{
Low: rune(fonts[i].FirstChar),
High: rune(fonts[i].LastChar),
Offset: 0,
},
}
for j := 0; j < glyphCount; j++ {
// address of the glyph in the mask
maskOffsetAddress := j * (fonts[i].Face.Height) * fonts[i].Face.Width
charPos := int(font.Start) + int(char.Start) + int(char.Size)*j
_, err = r.Seek(int64(charPos), io.SeekStart)
if err != nil {
return nil, err
}
// read width
var width uint16
err = binary.Read(r, binary.LittleEndian, &width)
if err != nil {
return nil, err
}
// read offset
var offset int
if char.Size == 4 {
var off uint16
err = binary.Read(r, binary.LittleEndian, &off)
offset = int(off)
} else {
var off uint32
err = binary.Read(r, binary.LittleEndian, &off)
offset = int(off)
}
if err != nil {
return nil, err
}
// read data
charBitPos := int(font.Start) + offset
_, err = r.Seek(int64(charBitPos), io.SeekStart)
if err != nil {
return nil, err
}
mask := uint8(0x80)
pk := int((width + 7) / 8)
// read column
for column := 0; column < pk; column++ {
// read row
for row := 0; row < int(fonts[i].Face.Height); row++ {
// read byte
var b uint8
err = binary.Read(r, binary.LittleEndian, &b)
if err != nil {
return nil, err
}
rowAddr := maskOffsetAddress + row*(fonts[i].Face.Width)
for bits := 0; bits < 8; bits++ {
// don't try to put filler bits into mask
if bits+column*8 >= fonts[i].Face.Width {
break
}
addr := rowAddr + bits + column*8
// check if first bit is set
if b&mask == mask {
fontmask.Pix[addr] = 0xff
// fmt.Print("1")
} else {
fontmask.Pix[addr] = 0x00
// fmt.Print("0")
}
b <<= 1 // goto next bit
}
// fmt.Print(" (", column, row, rowAddr, ")\n")
}
}
// fmt.Print("--- \n\n")
}
}
return fonts, nil
}