1
2
3
4
5 package font
6
7 import (
8 "bytes"
9 "compress/zlib"
10 "encoding/binary"
11 "fmt"
12 "math"
13 "sort"
14 )
15
16 var (
17
18 SFNT_OFFSET_TAG = 0
19 SFNT_OFFSET_CHECKSUM = 4
20 SFNT_OFFSET_OFFSET = 8
21 SFNT_OFFSET_LENGTH = 12
22
23
24 SFNT_ENTRY_OFFSET_FLAVOR = 0
25 SFNT_ENTRY_OFFSET_VERSION_MAJ = 4
26 SFNT_ENTRY_OFFSET_VERSION_MIN = 6
27 SFNT_ENTRY_OFFSET_CHECKSUM_ADJUSTMENT = 8
28
29
30 WOFF_OFFSET_MAGIC = 0
31 WOFF_OFFSET_FLAVOR = 4
32 WOFF_OFFSET_SIZE = 8
33 WOFF_OFFSET_NUM_TABLES = 12
34 WOFF_OFFSET_RESERVED = 14
35 WOFF_OFFSET_SFNT_SIZE = 16
36 WOFF_OFFSET_VERSION_MAJ = 20
37 WOFF_OFFSET_VERSION_MIN = 22
38 WOFF_OFFSET_META_OFFSET = 24
39 WOFF_OFFSET_META_LENGTH = 28
40 WOFF_OFFSET_META_ORIG_LENGTH = 32
41 WOFF_OFFSET_PRIV_OFFSET = 36
42 WOFF_OFFSET_PRIV_LENGTH = 40
43
44
45 WOFF_ENTRY_OFFSET_TAG = 0
46 WOFF_ENTRY_OFFSET_OFFSET = 4
47 WOFF_ENTRY_OFFSET_COMPR_LENGTH = 8
48 WOFF_ENTRY_OFFSET_LENGTH = 12
49 WOFF_ENTRY_OFFSET_CHECKSUM = 16
50
51
52 MAGIC_WOFF uint32 = 0x774f4646
53 MAGIC_CHECKSUM_ADJUSTMENT uint32 = 0xb1b0afba
54
55
56 SIZE_OF_WOFF_HEADER = 44
57 SIZE_OF_WOFF_ENTRY = 20
58 SIZE_OF_SFNT_HEADER = 12
59 SIZE_OF_SFNT_TABLE_ENTRY = 16
60 )
61
62 type TableEntry struct {
63 Tag []byte
64 CheckSum uint32
65 Offset uint32
66 Length uint32
67 }
68
69 func longAlign(n uint32) uint32 {
70 return (n + 3) & ^uint32(3)
71 }
72
73 func calcChecksum(buf []byte) uint32 {
74 var sum uint32 = 0
75 var nlongs = len(buf) / 4
76
77 for i := 0; i < nlongs; i++ {
78 var t = binary.BigEndian.Uint32(buf[i*4:])
79 sum = sum + t
80 }
81 return sum
82 }
83
84 func Sfnt2Woff(fontBuf []byte) ([]byte, error) {
85 numTables := binary.BigEndian.Uint16(fontBuf[4:])
86
87 woffHeader := make([]byte, SIZE_OF_WOFF_HEADER)
88 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_MAGIC:], MAGIC_WOFF)
89 binary.BigEndian.PutUint16(woffHeader[WOFF_OFFSET_NUM_TABLES:], numTables)
90 binary.BigEndian.PutUint16(woffHeader[WOFF_OFFSET_SFNT_SIZE:], 0)
91 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_META_OFFSET:], 0)
92 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_META_LENGTH:], 0)
93 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_META_ORIG_LENGTH:], 0)
94 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_PRIV_OFFSET:], 0)
95 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_PRIV_LENGTH:], 0)
96
97 var entries []TableEntry
98
99 for i := 0; i < int(numTables); i++ {
100 table := fontBuf[SIZE_OF_SFNT_HEADER+i*SIZE_OF_SFNT_TABLE_ENTRY:]
101
102 entry := TableEntry{
103 Tag: table[SFNT_OFFSET_TAG : SFNT_OFFSET_TAG+4],
104 CheckSum: binary.BigEndian.Uint32(table[SFNT_OFFSET_CHECKSUM:]),
105 Offset: binary.BigEndian.Uint32(table[SFNT_OFFSET_OFFSET:]),
106 Length: binary.BigEndian.Uint32(table[SFNT_OFFSET_LENGTH:]),
107 }
108
109 entries = append(entries, entry)
110 }
111 sort.Slice(entries, func(i, j int) bool {
112 return string(entries[i].Tag) < string(entries[j].Tag)
113 })
114
115 sfntSize := uint32(SIZE_OF_SFNT_HEADER + int(numTables)*SIZE_OF_SFNT_TABLE_ENTRY)
116 tableInfo := make([]byte, int(numTables)*SIZE_OF_WOFF_ENTRY)
117
118 for i := 0; i < len(entries); i++ {
119 tableEntry := entries[i]
120 if string(tableEntry.Tag) != "head" {
121 alignTable := fontBuf[tableEntry.Offset : tableEntry.Offset+longAlign(tableEntry.Length)]
122
123 if calcChecksum(alignTable) != tableEntry.CheckSum {
124 return nil, fmt.Errorf("checksum error in table: %v", string(tableEntry.Tag))
125 }
126 }
127
128 binary.BigEndian.PutUint32(tableInfo[i*SIZE_OF_WOFF_ENTRY+WOFF_ENTRY_OFFSET_TAG:], binary.BigEndian.Uint32(tableEntry.Tag))
129 binary.BigEndian.PutUint32(tableInfo[i*SIZE_OF_WOFF_ENTRY+WOFF_ENTRY_OFFSET_LENGTH:], tableEntry.Length)
130 binary.BigEndian.PutUint32(tableInfo[i*SIZE_OF_WOFF_ENTRY+WOFF_ENTRY_OFFSET_CHECKSUM:], tableEntry.CheckSum)
131
132 sfntSize += longAlign(tableEntry.Length)
133 }
134
135 sfntOffset := uint32(SIZE_OF_SFNT_HEADER + len(entries)*SIZE_OF_SFNT_TABLE_ENTRY)
136 csum := calcChecksum(fontBuf[:SIZE_OF_SFNT_HEADER])
137 for i := 0; i < len(entries); i++ {
138 tableEntry := entries[i]
139
140 b := make([]byte, SIZE_OF_SFNT_TABLE_ENTRY)
141 binary.BigEndian.PutUint32(b[SFNT_OFFSET_TAG:], binary.BigEndian.Uint32(tableEntry.Tag))
142 binary.BigEndian.PutUint32(b[SFNT_OFFSET_CHECKSUM:], tableEntry.CheckSum)
143 binary.BigEndian.PutUint32(b[SFNT_OFFSET_OFFSET:], sfntOffset)
144 binary.BigEndian.PutUint32(b[SFNT_OFFSET_LENGTH:], tableEntry.Length)
145
146 sfntOffset += longAlign(tableEntry.Length)
147 csum += calcChecksum(b)
148 csum += tableEntry.CheckSum
149 }
150
151 var checksumAdjustment = MAGIC_CHECKSUM_ADJUSTMENT - csum
152
153 majorVersion := uint16(0)
154 minVersion := uint16(1)
155 flavor := uint32(0)
156 offset := SIZE_OF_WOFF_HEADER + int(numTables)*SIZE_OF_WOFF_ENTRY
157 var tableBytes []byte
158
159 for i := 0; i < len(entries); i++ {
160 tableEntry := entries[i]
161
162 sfntData := fontBuf[tableEntry.Offset : tableEntry.Offset+tableEntry.Length]
163 if string(tableEntry.Tag) == "head" {
164 majorVersion = binary.BigEndian.Uint16(sfntData[SFNT_ENTRY_OFFSET_VERSION_MAJ:])
165 minVersion = binary.BigEndian.Uint16(sfntData[SFNT_ENTRY_OFFSET_VERSION_MIN:])
166 flavor = binary.BigEndian.Uint32(sfntData[SFNT_ENTRY_OFFSET_FLAVOR:])
167 binary.BigEndian.PutUint32(sfntData[SFNT_ENTRY_OFFSET_CHECKSUM_ADJUSTMENT:], uint32(checksumAdjustment))
168 }
169
170 var res bytes.Buffer
171 w := zlib.NewWriter(&res)
172 w.Write(sfntData)
173 w.Flush()
174 w.Close()
175
176 compLength := math.Min(float64(len(res.Bytes())), float64(len(sfntData)))
177 length := longAlign(uint32(compLength))
178
179 table := make([]byte, length)
180
181 if len(res.Bytes()) >= len(sfntData) {
182 copy(table, sfntData)
183 } else {
184 copy(table, res.Bytes())
185 }
186
187 binary.BigEndian.PutUint32(tableInfo[i*SIZE_OF_WOFF_ENTRY+WOFF_ENTRY_OFFSET_OFFSET:], uint32(offset))
188
189 offset += len(table)
190
191 binary.BigEndian.PutUint32(tableInfo[i*SIZE_OF_WOFF_ENTRY+WOFF_ENTRY_OFFSET_COMPR_LENGTH:], uint32(compLength))
192
193 tableBytes = append(tableBytes, table...)
194
195 }
196
197 woffSize := uint32(len(woffHeader) + len(tableInfo) + len(tableBytes))
198 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_SIZE:], woffSize)
199 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_SFNT_SIZE:], sfntSize)
200 binary.BigEndian.PutUint16(woffHeader[WOFF_OFFSET_VERSION_MAJ:], majorVersion)
201 binary.BigEndian.PutUint16(woffHeader[WOFF_OFFSET_VERSION_MIN:], minVersion)
202 binary.BigEndian.PutUint32(woffHeader[WOFF_OFFSET_FLAVOR:], flavor)
203
204 var out []byte
205 out = append(out, woffHeader...)
206 out = append(out, tableInfo...)
207 out = append(out, tableBytes...)
208
209 return out, nil
210 }
211
View as plain text