dmitri.shuralyov.com/font/woff2

initial implementation of woff2 dmitri.shuralyov.com/font/woff2#1

Mergeddmitshur opened this change 6 years ago
Collapse all
Commit Message
FileFile
@@ -0,0 +1,27 @@
1
Parent:     e9561ae (Initial commit.)
2
Author:     Dmitri Shuralyov <dmitri@shuralyov.com>
3
AuthorDate: Sun Feb 11 15:10:28 2018 -0500
4
Commit:     Dmitri Shuralyov <dmitri@shuralyov.com>
5
CommitDate: Tue Feb 20 16:46:47 2018 -0500
6

7
Add initial parser implementation.
8

9
This is an initial implementation of a parser for the WOFF2 font
10
packaging format.
11

12
It is incomplete; further work will come later. The scope for this
13
milestone was to be able to parse .woff2 files for the needs of the
14
github.com/ConradIrwin/font/sfnt package.
15

16
At this time, the API is very low level and maps directly to the binary
17
format of the file, as described in its specification. This API is in
18
early development and is expected to change as further progress is made.
19

20
It successfully parses some Go font family .woff2 files that were
21
generated using the https://github.com/google/woff2 encoder
22
from the Go font source .ttf files located at
23
https://go.googlesource.com/image/+/master/font/gofont/ttfs/.
24

25
Add basic test coverage.
26

27
Helps https://github.com/ConradIrwin/font/issues/1.
doc.go
FileFile
@@ -1,6 +1,4 @@
11
// Package woff2 implements a WOFF2 font decoder.
22
//
33
// The WOFF2 font packaging format is specified at https://www.w3.org/TR/WOFF2/.
44
package woff2
5

6
// TODO: Implement.
parse.go
FileFile
@@ -0,0 +1,441 @@
1
package woff2
2

3
import (
4
	"bytes"
5
	"encoding/binary"
6
	"fmt"
7
	"io"
8

9
	"github.com/dsnet/compress/brotli"
10
)
11

12
// File represents a parsed WOFF2 file.
13
type File struct {
14
	Header         Header
15
	TableDirectory TableDirectory
16
	// CollectionDirectory is present only if the font is a collection,
17
	// as reported by Header.IsCollection.
18
	CollectionDirectory *CollectionDirectory
19

20
	// FontData is the concatenation of data for each table in the font.
21
	// During storage, it's compressed using Brotli.
22
	FontData []byte
23

24
	ExtendedMetadata *ExtendedMetadata
25

26
	// PrivateData is an optional block of private data for the font designer,
27
	// foundry, or vendor to use.
28
	PrivateData []byte
29
}
30

31
// Parse parses the WOFF2 data from r.
32
func Parse(r io.Reader) (File, error) {
33
	hdr, err := parseHeader(r)
34
	if err != nil {
35
		return File{}, err
36
	}
37
	td, err := parseTableDirectory(r, hdr)
38
	if err != nil {
39
		return File{}, err
40
	}
41
	cd, err := parseCollectionDirectory(r, hdr)
42
	if err != nil {
43
		return File{}, err
44
	}
45
	fd, err := parseCompressedFontData(r, hdr, td)
46
	if err != nil {
47
		return File{}, err
48
	}
49
	em, err := parseExtendedMetadata(r, hdr)
50
	if err != nil {
51
		return File{}, err
52
	}
53
	pd, err := parsePrivateData(r, hdr)
54
	if err != nil {
55
		return File{}, err
56
	}
57

58
	// Check for padding with a maximum of three null bytes.
59
	// TODO: This check needs to be moved to Extended Metadata and Private Data blocks,
60
	//       and made more precise (i.e., the beginning of those blocks must be 4-byte aligned).
61
	n, err := io.Copy(discardZeroes{}, r)
62
	if err != nil {
63
		return File{}, fmt.Errorf("Parse: %v", err)
64
	}
65
	if n > 3 {
66
		return File{}, fmt.Errorf("Parse: %d bytes left remaining, want no more than 3", n)
67
	}
68

69
	return File{
70
		Header:              hdr,
71
		TableDirectory:      td,
72
		CollectionDirectory: cd,
73
		FontData:            fd,
74
		ExtendedMetadata:    em,
75
		PrivateData:         pd,
76
	}, nil
77
}
78

79
// discardZeroes is an io.Writer that returns an error if any non-zero bytes are written to it.
80
type discardZeroes struct{}
81

82
func (discardZeroes) Write(p []byte) (int, error) {
83
	for _, b := range p {
84
		if b != 0 {
85
			return 0, fmt.Errorf("encountered non-zero byte %d", b)
86
		}
87
	}
88
	return len(p), nil
89
}
90

91
// Header is the file header with basic font type and version,
92
// along with offsets to metadata and private data blocks.
93
type Header struct {
94
	Signature           uint32 // The identifying signature; must be 0x774F4632 ('wOF2').
95
	Flavor              uint32 // The "sfnt version" of the input font.
96
	Length              uint32 // Total size of the WOFF file.
97
	NumTables           uint16 // Number of entries in directory of font tables.
98
	Reserved            uint16 // Reserved; set to 0.
99
	TotalSfntSize       uint32 // Total size needed for the uncompressed font data, including the sfnt header, directory, and font tables (including padding).
100
	TotalCompressedSize uint32 // Total length of the compressed data block.
101
	MajorVersion        uint16 // Major version of the WOFF file.
102
	MinorVersion        uint16 // Minor version of the WOFF file.
103
	MetaOffset          uint32 // Offset to metadata block, from beginning of WOFF file.
104
	MetaLength          uint32 // Length of compressed metadata block.
105
	MetaOrigLength      uint32 // Uncompressed size of metadata block.
106
	PrivOffset          uint32 // Offset to private data block, from beginning of WOFF file.
107
	PrivLength          uint32 // Length of private data block.
108
}
109

110
func parseHeader(r io.Reader) (Header, error) {
111
	var hdr Header
112
	err := binary.Read(r, order, &hdr)
113
	if err != nil {
114
		return Header{}, err
115
	}
116
	if hdr.Signature != signature {
117
		return Header{}, fmt.Errorf("parseHeader: invalid signature: got %#08x, want %#08x", hdr.Signature, signature)
118
	}
119
	return hdr, nil
120
}
121

122
// IsCollection reports whether this is a font collection, i.e.,
123
// if the value of Flavor field is set to the TrueType Collection flavor 'ttcf'.
124
func (hdr Header) IsCollection() bool {
125
	return hdr.Flavor == ttcfFlavor
126
}
127

128
// TableDirectory is the directory of font tables, containing size and other info.
129
type TableDirectory []TableDirectoryEntry
130

131
func parseTableDirectory(r io.Reader, hdr Header) (TableDirectory, error) {
132
	var td TableDirectory
133
	for i := 0; i < int(hdr.NumTables); i++ {
134
		var e TableDirectoryEntry
135

136
		err := readU8(r, &e.Flags)
137
		if err != nil {
138
			return nil, err
139
		}
140
		if e.Flags&0x3f == 0x3f {
141
			e.Tag = new(uint32)
142
			err := readU32(r, e.Tag)
143
			if err != nil {
144
				return nil, err
145
			}
146
		}
147
		err = readBase128(r, &e.OrigLength)
148
		if err != nil {
149
			return nil, err
150
		}
151

152
		switch tag, transformVersion := e.tag(), e.transformVersion(); tag {
153
		case glyfTable, locaTable:
154
			// 0 means transform for glyf/loca tables.
155
			if transformVersion == 0 {
156
				e.TransformLength = new(uint32)
157
				err := readBase128(r, e.TransformLength)
158
				if err != nil {
159
					return nil, err
160
				}
161

162
				// The transform length of the transformed loca table MUST always be zero.
163
				if tag == locaTable && *e.TransformLength != 0 {
164
					return nil, fmt.Errorf("parseTableDirectory: 'loca' table has non-zero transform length %d", *e.TransformLength)
165
				}
166
			}
167
		default:
168
			// Non-0 means transform for other tables.
169
			if transformVersion != 0 {
170
				e.TransformLength = new(uint32)
171
				err := readBase128(r, e.TransformLength)
172
				if err != nil {
173
					return nil, err
174
				}
175
			}
176
		}
177

178
		td = append(td, e)
179
	}
180
	return td, nil
181
}
182

183
// Table is a high-level representation of a table.
184
type Table struct {
185
	Tag    uint32
186
	Offset int
187
	Length int
188
}
189

190
// Tables returns the derived high-level information
191
// about the tables in the table directory.
192
func (td TableDirectory) Tables() []Table {
193
	var ts []Table
194
	var offset int
195
	for _, t := range td {
196
		length := int(t.length())
197
		ts = append(ts, Table{
198
			Tag:    t.tag(),
199
			Offset: offset,
200
			Length: length,
201
		})
202
		offset += length
203
	}
204
	return ts
205
}
206

207
// uncompressedSize computes the total uncompressed size
208
// of the tables in the table directory.
209
func (td TableDirectory) uncompressedSize() int64 {
210
	var n int64
211
	for _, t := range td {
212
		n += int64(t.length())
213
	}
214
	return n
215
}
216

217
// TableDirectoryEntry is a table directory entry.
218
type TableDirectoryEntry struct {
219
	Flags           uint8   // Table type and flags.
220
	Tag             *uint32 // 4-byte tag (optional).
221
	OrigLength      uint32  // Length of original table.
222
	TransformLength *uint32 // Transformed length (optional).
223
}
224

225
func (e TableDirectoryEntry) tag() uint32 {
226
	switch e.Tag {
227
	case nil:
228
		return knownTableTags[e.Flags&0x3f] // Bits [0..5].
229
	default:
230
		return *e.Tag
231
	}
232
}
233

234
func (e TableDirectoryEntry) transformVersion() uint8 {
235
	return e.Flags >> 6 // Bits [6..7].
236
}
237

238
func (e TableDirectoryEntry) length() uint32 {
239
	switch e.TransformLength {
240
	case nil:
241
		return e.OrigLength
242
	default:
243
		return *e.TransformLength
244
	}
245
}
246

247
// CollectionDirectory is an optional table containing the font fragment descriptions
248
// of font collection entries.
249
type CollectionDirectory struct {
250
	Header  CollectionHeader
251
	Entries []CollectionFontEntry
252
}
253

254
// CollectionHeader is a part of CollectionDirectory.
255
type CollectionHeader struct {
256
	Version  uint32
257
	NumFonts uint16
258
}
259

260
// CollectionFontEntry represents a CollectionFontEntry record.
261
type CollectionFontEntry struct {
262
	NumTables    uint16   // The number of tables in this font.
263
	Flavor       uint32   // The "sfnt version" of the font.
264
	TableIndices []uint16 // The indicies identifying an entry in the Table Directory for each table in this font.
265
}
266

267
func parseCollectionDirectory(r io.Reader, hdr Header) (*CollectionDirectory, error) {
268
	// CollectionDirectory is present only if the input font is a collection.
269
	if !hdr.IsCollection() {
270
		return nil, nil
271
	}
272

273
	var cd CollectionDirectory
274
	err := readU32(r, &cd.Header.Version)
275
	if err != nil {
276
		return nil, err
277
	}
278
	err = read255UShort(r, &cd.Header.NumFonts)
279
	if err != nil {
280
		return nil, err
281
	}
282
	for i := 0; i < int(cd.Header.NumFonts); i++ {
283
		var e CollectionFontEntry
284

285
		err := read255UShort(r, &e.NumTables)
286
		if err != nil {
287
			return nil, err
288
		}
289
		err = readU32(r, &e.Flavor)
290
		if err != nil {
291
			return nil, err
292
		}
293
		for j := 0; j < int(e.NumTables); j++ {
294
			var tableIndex uint16
295
			err := read255UShort(r, &tableIndex)
296
			if err != nil {
297
				return nil, err
298
			}
299
			if tableIndex >= hdr.NumTables {
300
				return nil, fmt.Errorf("parseCollectionDirectory: tableIndex >= hdr.NumTables")
301
			}
302
			e.TableIndices = append(e.TableIndices, tableIndex)
303
		}
304

305
		cd.Entries = append(cd.Entries, e)
306
	}
307
	return &cd, nil
308
}
309

310
func parseCompressedFontData(r io.Reader, hdr Header, td TableDirectory) ([]byte, error) {
311
	// Compressed font data.
312
	br, err := brotli.NewReader(io.LimitReader(r, int64(hdr.TotalCompressedSize)), nil)
313
	//br, err := brotli.NewReader(&exactReader{R: r, N: int64(hdr.TotalCompressedSize)}, nil)
314
	if err != nil {
315
		return nil, err
316
	}
317
	var buf bytes.Buffer
318
	n, err := io.Copy(&buf, br)
319
	if err != nil {
320
		return nil, fmt.Errorf("parseCompressedFontData: io.Copy: %v", err)
321
	}
322
	err = br.Close()
323
	if err != nil {
324
		return nil, fmt.Errorf("parseCompressedFontData: br.Close: %v", err)
325
	}
326
	if uncompressedSize := td.uncompressedSize(); n != uncompressedSize {
327
		return nil, fmt.Errorf("parseCompressedFontData: unexpected size of uncompressed data: got %d, want %d", n, uncompressedSize)
328
	}
329
	return buf.Bytes(), nil
330
}
331

332
// ExtendedMetadata is an optional block of extended metadata,
333
// represented in XML format and compressed for storage in the WOFF2 file.
334
type ExtendedMetadata struct{}
335

336
func parseExtendedMetadata(r io.Reader, hdr Header) (*ExtendedMetadata, error) {
337
	if hdr.MetaLength == 0 {
338
		return nil, nil
339
	}
340
	return nil, fmt.Errorf("parseExtendedMetadata: not implemented")
341
}
342

343
func parsePrivateData(r io.Reader, hdr Header) ([]byte, error) {
344
	if hdr.PrivLength == 0 {
345
		return nil, nil
346
	}
347
	return nil, fmt.Errorf("parsePrivateData: not implemented")
348
}
349

350
// readU8 reads a UInt8 value.
351
func readU8(r io.Reader, v *uint8) error {
352
	return binary.Read(r, order, v)
353
}
354

355
// readU16 reads a UInt16 value.
356
func readU16(r io.Reader, v *uint16) error {
357
	return binary.Read(r, order, v)
358
}
359

360
// readU32 reads a UInt32 value.
361
func readU32(r io.Reader, v *uint32) error {
362
	return binary.Read(r, order, v)
363
}
364

365
// readBase128 reads a UIntBase128 value.
366
func readBase128(r io.Reader, v *uint32) error {
367
	var accum uint32
368
	for i := 0; i < 5; i++ {
369
		var data uint8
370
		err := binary.Read(r, order, &data)
371
		if err != nil {
372
			return err
373
		}
374

375
		// Leading zeros are invalid.
376
		if i == 0 && data == 0x80 {
377
			return fmt.Errorf("leading zero is invalid")
378
		}
379

380
		// If any of top 7 bits are set then accum << 7 would overflow.
381
		if accum&0xfe000000 != 0 {
382
			return fmt.Errorf("top seven bits are set, about to overflow")
383
		}
384

385
		accum = (accum << 7) | uint32(data)&0x7f
386

387
		// Spin until most significant bit of data byte is false.
388
		if (data & 0x80) == 0 {
389
			*v = accum
390
			return nil
391
		}
392
	}
393
	return fmt.Errorf("UIntBase128 sequence exceeds 5 bytes")
394
}
395

396
// read255UShort reads a 255UInt16 value.
397
func read255UShort(r io.Reader, v *uint16) error {
398
	const (
399
		oneMoreByteCode1 = 255
400
		oneMoreByteCode2 = 254
401
		wordCode         = 253
402
		lowestUCode      = 253
403
	)
404
	var code uint8
405
	err := binary.Read(r, order, &code)
406
	if err != nil {
407
		return err
408
	}
409
	switch code {
410
	case wordCode:
411
		var value uint16
412
		err := binary.Read(r, order, &value)
413
		if err != nil {
414
			return err
415
		}
416
		*v = value
417
		return nil
418
	case oneMoreByteCode1:
419
		var value uint8
420
		err := binary.Read(r, order, &value)
421
		if err != nil {
422
			return err
423
		}
424
		*v = uint16(value) + lowestUCode
425
		return nil
426
	case oneMoreByteCode2:
427
		var value uint8
428
		err := binary.Read(r, order, &value)
429
		if err != nil {
430
			return err
431
		}
432
		*v = uint16(value) + lowestUCode*2
433
		return nil
434
	default:
435
		*v = uint16(code)
436
		return nil
437
	}
438
}
439

440
// WOFF2 uses big endian encoding.
441
var order binary.ByteOrder = binary.BigEndian
parse_test.go
FileFile
@@ -0,0 +1,109 @@
1
package woff2_test
2

3
import (
4
	"fmt"
5
	"log"
6

7
	"dmitri.shuralyov.com/font/woff2"
8
	"github.com/shurcooL/gofontwoff"
9
)
10

11
func ExampleParse() {
12
	f, err := gofontwoff.Assets.Open("/Go-Regular.woff2")
13
	if err != nil {
14
		log.Fatalln(err)
15
	}
16
	defer f.Close()
17

18
	font, err := woff2.Parse(f)
19
	if err != nil {
20
		log.Fatalln(err)
21
	}
22
	Dump(font)
23

24
	// Output:
25
	//
26
	// Signature:           0x774f4632
27
	// Flavor:              0x00010000
28
	// Length:              46132
29
	// NumTables:           14
30
	// Reserved:            0
31
	// TotalSfntSize:       140308
32
	// TotalCompressedSize: 46040
33
	// MajorVersion:        1
34
	// MinorVersion:        0
35
	// MetaOffset:          0
36
	// MetaLength:          0
37
	// MetaOrigLength:      0
38
	// PrivOffset:          0
39
	// PrivLength:          0
40
	//
41
	// TableDirectory: 14 entries
42
	// 	{Flags: 0x06, Tag: <nil>, OrigLength: 96, TransformLength: <nil>}
43
	// 	{Flags: 0x00, Tag: <nil>, OrigLength: 1318, TransformLength: <nil>}
44
	// 	{Flags: 0x08, Tag: <nil>, OrigLength: 176, TransformLength: <nil>}
45
	// 	{Flags: 0x09, Tag: <nil>, OrigLength: 3437, TransformLength: <nil>}
46
	// 	{Flags: 0x11, Tag: <nil>, OrigLength: 8, TransformLength: <nil>}
47
	// 	{Flags: 0x0a, Tag: <nil>, OrigLength: 118912, TransformLength: 105020}
48
	// 	{Flags: 0x0b, Tag: <nil>, OrigLength: 1334, TransformLength: 0}
49
	// 	{Flags: 0x01, Tag: <nil>, OrigLength: 54, TransformLength: <nil>}
50
	// 	{Flags: 0x02, Tag: <nil>, OrigLength: 36, TransformLength: <nil>}
51
	// 	{Flags: 0x03, Tag: <nil>, OrigLength: 2662, TransformLength: <nil>}
52
	// 	{Flags: 0x04, Tag: <nil>, OrigLength: 32, TransformLength: <nil>}
53
	// 	{Flags: 0x05, Tag: <nil>, OrigLength: 6967, TransformLength: <nil>}
54
	// 	{Flags: 0x07, Tag: <nil>, OrigLength: 4838, TransformLength: <nil>}
55
	// 	{Flags: 0x0c, Tag: <nil>, OrigLength: 188, TransformLength: <nil>}
56
	//
57
	// CollectionDirectory: <nil>
58
	// CompressedFontData: 124832 bytes (uncompressed size)
59
	// ExtendedMetadata: <nil>
60
	// PrivateData: []
61
}
62

63
func Dump(f woff2.File) {
64
	dumpHeader(f.Header)
65
	fmt.Println()
66
	dumpTableDirectory(f.TableDirectory)
67
	fmt.Println()
68
	fmt.Println("CollectionDirectory:", f.CollectionDirectory)
69
	fmt.Println("CompressedFontData:", len(f.FontData), "bytes (uncompressed size)")
70
	fmt.Println("ExtendedMetadata:", f.ExtendedMetadata)
71
	fmt.Println("PrivateData:", f.PrivateData)
72
}
73

74
func dumpHeader(hdr woff2.Header) {
75
	fmt.Printf("Signature:           %#08x\n", hdr.Signature)
76
	fmt.Printf("Flavor:              %#08x\n", hdr.Flavor)
77
	fmt.Printf("Length:              %d\n", hdr.Length)
78
	fmt.Printf("NumTables:           %d\n", hdr.NumTables)
79
	fmt.Printf("Reserved:            %d\n", hdr.Reserved)
80
	fmt.Printf("TotalSfntSize:       %d\n", hdr.TotalSfntSize)
81
	fmt.Printf("TotalCompressedSize: %d\n", hdr.TotalCompressedSize)
82
	fmt.Printf("MajorVersion:        %d\n", hdr.MajorVersion)
83
	fmt.Printf("MinorVersion:        %d\n", hdr.MinorVersion)
84
	fmt.Printf("MetaOffset:          %d\n", hdr.MetaOffset)
85
	fmt.Printf("MetaLength:          %d\n", hdr.MetaLength)
86
	fmt.Printf("MetaOrigLength:      %d\n", hdr.MetaOrigLength)
87
	fmt.Printf("PrivOffset:          %d\n", hdr.PrivOffset)
88
	fmt.Printf("PrivLength:          %d\n", hdr.PrivLength)
89
}
90

91
func dumpTableDirectory(td woff2.TableDirectory) {
92
	fmt.Println("TableDirectory:", len(td), "entries")
93
	for _, t := range td {
94
		fmt.Printf("\t{")
95
		fmt.Printf("Flags: %#02x, ", t.Flags)
96
		if t.Tag != nil {
97
			fmt.Printf("Tag: %v, ", *t.Tag)
98
		} else {
99
			fmt.Printf("Tag: <nil>, ")
100
		}
101
		fmt.Printf("OrigLength: %v, ", t.OrigLength)
102
		if t.TransformLength != nil {
103
			fmt.Printf("TransformLength: %v", *t.TransformLength)
104
		} else {
105
			fmt.Printf("TransformLength: <nil>")
106
		}
107
		fmt.Printf("}\n")
108
	}
109
}
tags.go
FileFile
@@ -0,0 +1,79 @@
1
package woff2
2

3
const (
4
	// signature is the WOFF 2.0 file identifying signature 'wOF2'.
5
	signature = uint32('w'<<24 | 'O'<<16 | 'F'<<8 | '2')
6

7
	// ttcfFlavor is the TrueType Collection flavor 'ttcf'.
8
	ttcfFlavor = uint32('t'<<24 | 't'<<16 | 'c'<<8 | 'f')
9

10
	glyfTable = uint32('g'<<24 | 'l'<<16 | 'y'<<8 | 'f')
11
	locaTable = uint32('l'<<24 | 'o'<<16 | 'c'<<8 | 'a')
12
)
13

14
// knownTableTags is the "Known Table Tags" table.
15
var knownTableTags = [...]uint32{
16
	0:  uint32('c'<<24 | 'm'<<16 | 'a'<<8 | 'p'),
17
	1:  uint32('h'<<24 | 'e'<<16 | 'a'<<8 | 'd'),
18
	2:  uint32('h'<<24 | 'h'<<16 | 'e'<<8 | 'a'),
19
	3:  uint32('h'<<24 | 'm'<<16 | 't'<<8 | 'x'),
20
	4:  uint32('m'<<24 | 'a'<<16 | 'x'<<8 | 'p'),
21
	5:  uint32('n'<<24 | 'a'<<16 | 'm'<<8 | 'e'),
22
	6:  uint32('O'<<24 | 'S'<<16 | '/'<<8 | '2'),
23
	7:  uint32('p'<<24 | 'o'<<16 | 's'<<8 | 't'),
24
	8:  uint32('c'<<24 | 'v'<<16 | 't'<<8 | ' '),
25
	9:  uint32('f'<<24 | 'p'<<16 | 'g'<<8 | 'm'),
26
	10: uint32('g'<<24 | 'l'<<16 | 'y'<<8 | 'f'),
27
	11: uint32('l'<<24 | 'o'<<16 | 'c'<<8 | 'a'),
28
	12: uint32('p'<<24 | 'r'<<16 | 'e'<<8 | 'p'),
29
	13: uint32('C'<<24 | 'F'<<16 | 'F'<<8 | ' '),
30
	14: uint32('V'<<24 | 'O'<<16 | 'R'<<8 | 'G'),
31
	15: uint32('E'<<24 | 'B'<<16 | 'D'<<8 | 'T'),
32
	16: uint32('E'<<24 | 'B'<<16 | 'L'<<8 | 'C'),
33
	17: uint32('g'<<24 | 'a'<<16 | 's'<<8 | 'p'),
34
	18: uint32('h'<<24 | 'd'<<16 | 'm'<<8 | 'x'),
35
	19: uint32('k'<<24 | 'e'<<16 | 'r'<<8 | 'n'),
36
	20: uint32('L'<<24 | 'T'<<16 | 'S'<<8 | 'H'),
37
	21: uint32('P'<<24 | 'C'<<16 | 'L'<<8 | 'T'),
38
	22: uint32('V'<<24 | 'D'<<16 | 'M'<<8 | 'X'),
39
	23: uint32('v'<<24 | 'h'<<16 | 'e'<<8 | 'a'),
40
	24: uint32('v'<<24 | 'm'<<16 | 't'<<8 | 'x'),
41
	25: uint32('B'<<24 | 'A'<<16 | 'S'<<8 | 'E'),
42
	26: uint32('G'<<24 | 'D'<<16 | 'E'<<8 | 'F'),
43
	27: uint32('G'<<24 | 'P'<<16 | 'O'<<8 | 'S'),
44
	28: uint32('G'<<24 | 'S'<<16 | 'U'<<8 | 'B'),
45
	29: uint32('E'<<24 | 'B'<<16 | 'S'<<8 | 'C'),
46
	30: uint32('J'<<24 | 'S'<<16 | 'T'<<8 | 'F'),
47
	31: uint32('M'<<24 | 'A'<<16 | 'T'<<8 | 'H'),
48
	32: uint32('C'<<24 | 'B'<<16 | 'D'<<8 | 'T'),
49
	33: uint32('C'<<24 | 'B'<<16 | 'L'<<8 | 'C'),
50
	34: uint32('C'<<24 | 'O'<<16 | 'L'<<8 | 'R'),
51
	35: uint32('C'<<24 | 'P'<<16 | 'A'<<8 | 'L'),
52
	36: uint32('S'<<24 | 'V'<<16 | 'G'<<8 | ' '),
53
	37: uint32('s'<<24 | 'b'<<16 | 'i'<<8 | 'x'),
54
	38: uint32('a'<<24 | 'c'<<16 | 'n'<<8 | 't'),
55
	39: uint32('a'<<24 | 'v'<<16 | 'a'<<8 | 'r'),
56
	40: uint32('b'<<24 | 'd'<<16 | 'a'<<8 | 't'),
57
	41: uint32('b'<<24 | 'l'<<16 | 'o'<<8 | 'c'),
58
	42: uint32('b'<<24 | 's'<<16 | 'l'<<8 | 'n'),
59
	43: uint32('c'<<24 | 'v'<<16 | 'a'<<8 | 'r'),
60
	44: uint32('f'<<24 | 'd'<<16 | 's'<<8 | 'c'),
61
	45: uint32('f'<<24 | 'e'<<16 | 'a'<<8 | 't'),
62
	46: uint32('f'<<24 | 'm'<<16 | 't'<<8 | 'x'),
63
	47: uint32('f'<<24 | 'v'<<16 | 'a'<<8 | 'r'),
64
	48: uint32('g'<<24 | 'v'<<16 | 'a'<<8 | 'r'),
65
	49: uint32('h'<<24 | 's'<<16 | 't'<<8 | 'y'),
66
	50: uint32('j'<<24 | 'u'<<16 | 's'<<8 | 't'),
67
	51: uint32('l'<<24 | 'c'<<16 | 'a'<<8 | 'r'),
68
	52: uint32('m'<<24 | 'o'<<16 | 'r'<<8 | 't'),
69
	53: uint32('m'<<24 | 'o'<<16 | 'r'<<8 | 'x'),
70
	54: uint32('o'<<24 | 'p'<<16 | 'b'<<8 | 'd'),
71
	55: uint32('p'<<24 | 'r'<<16 | 'o'<<8 | 'p'),
72
	56: uint32('t'<<24 | 'r'<<16 | 'a'<<8 | 'k'),
73
	57: uint32('Z'<<24 | 'a'<<16 | 'p'<<8 | 'f'),
74
	58: uint32('S'<<24 | 'i'<<16 | 'l'<<8 | 'f'),
75
	59: uint32('G'<<24 | 'l'<<16 | 'a'<<8 | 't'),
76
	60: uint32('G'<<24 | 'l'<<16 | 'o'<<8 | 'c'),
77
	61: uint32('F'<<24 | 'e'<<16 | 'a'<<8 | 't'),
78
	62: uint32('S'<<24 | 'i'<<16 | 'l'<<8 | 'l'),
79
}
tags_test.go
FileFile
@@ -0,0 +1,10 @@
1
package woff2
2

3
import "testing"
4

5
func TestKnownTableTagsLength(t *testing.T) {
6
	const want = 63
7
	if got := len(knownTableTags); got != want {
8
		t.Errorf("got len(knownTableTags): %v, want: %v", got, want)
9
	}
10
}