dmitri.shuralyov.com/font/woff2/...

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

Mergeddmitshur opened this change 6 years ago
Patch Set 1
dmitshur committed 6 years ago commit 9c3d0c544dfa2ce1d0fe7c381f8a87b18e9ab48c
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: Sun Feb 11 15:10:28 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,438 @@
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
	n, err := io.Copy(discardZeroes{}, r)
59
	if err != nil {
60
		return File{}, fmt.Errorf("Parse: %v", err)
61
	}
62
	if n > 3 {
63
		return File{}, fmt.Errorf("Parse: %d bytes left remaining, want no more than 3", n)
64
	}
65

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

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

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

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

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

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

125
// TableDirectory is the directory of font tables, containing size and other info.
126
type TableDirectory []TableDirectoryEntry
127

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

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

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

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

175
		td = append(td, e)
176
	}
177
	return td, nil
178
}
179

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

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

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

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

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

231
func (e TableDirectoryEntry) transformVersion() uint8 {
232
	return e.Flags >> 6 // Bits [6..7].
233
}
234

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

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

251
// CollectionHeader is a part of CollectionDirectory.
252
type CollectionHeader struct {
253
	Version  uint32
254
	NumFonts uint16
255
}
256

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

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

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

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

302
		cd.Entries = append(cd.Entries, e)
303
	}
304
	return &cd, nil
305
}
306

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

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

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

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

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

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

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

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

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

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

382
		accum = (accum << 7) | uint32(data)&0x7f
383

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

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

437
// WOFF2 uses big endian encoding.
438
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.CompressedFontData.Data), "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
}