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

Add initial parser implementation.

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

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

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

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

Add basic test coverage.

Helps https://github.com/ConradIrwin/font/issues/1.
dmitshur committed 6 years ago commit 957792cbbdabb084d484a7dcfd1e5b1a739a0ced
Collapse all
doc.go
@@ -1,6 +1,4 @@
// Package woff2 implements a WOFF2 font decoder.
//
// The WOFF2 font packaging format is specified at https://www.w3.org/TR/WOFF2/.
package woff2

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

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"

	"github.com/dsnet/compress/brotli"
)

// File represents a parsed WOFF2 file.
type File struct {
	Header         Header
	TableDirectory TableDirectory
	// CollectionDirectory is present only if the font is a collection,
	// as reported by Header.IsCollection.
	CollectionDirectory *CollectionDirectory

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

	ExtendedMetadata *ExtendedMetadata

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

// Parse parses the WOFF2 data from r.
func Parse(r io.Reader) (File, error) {
	hdr, err := parseHeader(r)
	if err != nil {
		return File{}, err
	}
	td, err := parseTableDirectory(r, hdr)
	if err != nil {
		return File{}, err
	}
	cd, err := parseCollectionDirectory(r, hdr)
	if err != nil {
		return File{}, err
	}
	fd, err := parseCompressedFontData(r, hdr, td)
	if err != nil {
		return File{}, err
	}
	em, err := parseExtendedMetadata(r, hdr)
	if err != nil {
		return File{}, err
	}
	pd, err := parsePrivateData(r, hdr)
	if err != nil {
		return File{}, err
	}

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

	return File{
		Header:              hdr,
		TableDirectory:      td,
		CollectionDirectory: cd,
		FontData:            fd,
		ExtendedMetadata:    em,
		PrivateData:         pd,
	}, nil
}

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

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

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

func parseHeader(r io.Reader) (Header, error) {
	var hdr Header
	err := binary.Read(r, order, &hdr)
	if err != nil {
		return Header{}, err
	}
	if hdr.Signature != signature {
		return Header{}, fmt.Errorf("parseHeader: invalid signature: got %#08x, want %#08x", hdr.Signature, signature)
	}
	return hdr, nil
}

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

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

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

		err := readU8(r, &e.Flags)
		if err != nil {
			return nil, err
		}
		if e.Flags&0x3f == 0x3f {
			e.Tag = new(uint32)
			err := readU32(r, e.Tag)
			if err != nil {
				return nil, err
			}
		}
		err = readBase128(r, &e.OrigLength)
		if err != nil {
			return nil, err
		}

		switch tag, transformVersion := e.tag(), e.transformVersion(); tag {
		case glyfTable, locaTable:
			// 0 means transform for glyf/loca tables.
			if transformVersion == 0 {
				e.TransformLength = new(uint32)
				err := readBase128(r, e.TransformLength)
				if err != nil {
					return nil, err
				}

				// The transform length of the transformed loca table MUST always be zero.
				if tag == locaTable && *e.TransformLength != 0 {
					return nil, fmt.Errorf("parseTableDirectory: 'loca' table has non-zero transform length %d", *e.TransformLength)
				}
			}
		default:
			// Non-0 means transform for other tables.
			if transformVersion != 0 {
				e.TransformLength = new(uint32)
				err := readBase128(r, e.TransformLength)
				if err != nil {
					return nil, err
				}
			}
		}

		td = append(td, e)
	}
	return td, nil
}

// Table is a high-level representation of a table.
type Table struct {
	Tag    uint32
	Offset int
	Length int
}

// Tables returns the derived high-level information
// about the tables in the table directory.
func (td TableDirectory) Tables() []Table {
	var ts []Table
	var offset int
	for _, t := range td {
		length := int(t.length())
		ts = append(ts, Table{
			Tag:    t.tag(),
			Offset: offset,
			Length: length,
		})
		offset += length
	}
	return ts
}

// uncompressedSize computes the total uncompressed size
// of the tables in the table directory.
func (td TableDirectory) uncompressedSize() int64 {
	var n int64
	for _, t := range td {
		n += int64(t.length())
	}
	return n
}

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

func (e TableDirectoryEntry) tag() uint32 {
	switch e.Tag {
	case nil:
		return knownTableTags[e.Flags&0x3f] // Bits [0..5].
	default:
		return *e.Tag
	}
}

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

func (e TableDirectoryEntry) length() uint32 {
	switch e.TransformLength {
	case nil:
		return e.OrigLength
	default:
		return *e.TransformLength
	}
}

// CollectionDirectory is an optional table containing the font fragment descriptions
// of font collection entries.
type CollectionDirectory struct {
	Header  CollectionHeader
	Entries []CollectionFontEntry
}

// CollectionHeader is a part of CollectionDirectory.
type CollectionHeader struct {
	Version  uint32
	NumFonts uint16
}

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

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

	var cd CollectionDirectory
	err := readU32(r, &cd.Header.Version)
	if err != nil {
		return nil, err
	}
	err = read255UShort(r, &cd.Header.NumFonts)
	if err != nil {
		return nil, err
	}
	for i := 0; i < int(cd.Header.NumFonts); i++ {
		var e CollectionFontEntry

		err := read255UShort(r, &e.NumTables)
		if err != nil {
			return nil, err
		}
		err = readU32(r, &e.Flavor)
		if err != nil {
			return nil, err
		}
		for j := 0; j < int(e.NumTables); j++ {
			var tableIndex uint16
			err := read255UShort(r, &tableIndex)
			if err != nil {
				return nil, err
			}
			if tableIndex >= hdr.NumTables {
				return nil, fmt.Errorf("parseCollectionDirectory: tableIndex >= hdr.NumTables")
			}
			e.TableIndices = append(e.TableIndices, tableIndex)
		}

		cd.Entries = append(cd.Entries, e)
	}
	return &cd, nil
}

func parseCompressedFontData(r io.Reader, hdr Header, td TableDirectory) ([]byte, error) {
	// Compressed font data.
	br, err := brotli.NewReader(io.LimitReader(r, int64(hdr.TotalCompressedSize)), nil)
	//br, err := brotli.NewReader(&exactReader{R: r, N: int64(hdr.TotalCompressedSize)}, nil)
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	n, err := io.Copy(&buf, br)
	if err != nil {
		return nil, fmt.Errorf("parseCompressedFontData: io.Copy: %v", err)
	}
	err = br.Close()
	if err != nil {
		return nil, fmt.Errorf("parseCompressedFontData: br.Close: %v", err)
	}
	if uncompressedSize := td.uncompressedSize(); n != uncompressedSize {
		return nil, fmt.Errorf("parseCompressedFontData: unexpected size of uncompressed data: got %d, want %d", n, uncompressedSize)
	}
	return buf.Bytes(), nil
}

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

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

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

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

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

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

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

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

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

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

		// Spin until most significant bit of data byte is false.
		if (data & 0x80) == 0 {
			*v = accum
			return nil
		}
	}
	return fmt.Errorf("UIntBase128 sequence exceeds 5 bytes")
}

// read255UShort reads a 255UInt16 value.
func read255UShort(r io.Reader, v *uint16) error {
	const (
		oneMoreByteCode1 = 255
		oneMoreByteCode2 = 254
		wordCode         = 253
		lowestUCode      = 253
	)
	var code uint8
	err := binary.Read(r, order, &code)
	if err != nil {
		return err
	}
	switch code {
	case wordCode:
		var value uint16
		err := binary.Read(r, order, &value)
		if err != nil {
			return err
		}
		*v = value
		return nil
	case oneMoreByteCode1:
		var value uint8
		err := binary.Read(r, order, &value)
		if err != nil {
			return err
		}
		*v = uint16(value) + lowestUCode
		return nil
	case oneMoreByteCode2:
		var value uint8
		err := binary.Read(r, order, &value)
		if err != nil {
			return err
		}
		*v = uint16(value) + lowestUCode*2
		return nil
	default:
		*v = uint16(code)
		return nil
	}
}

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

import (
	"fmt"
	"log"

	"dmitri.shuralyov.com/font/woff2"
	"github.com/shurcooL/gofontwoff"
)

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

	font, err := woff2.Parse(f)
	if err != nil {
		log.Fatalln(err)
	}
	Dump(font)

	// Output:
	//
	// Signature:           0x774f4632
	// Flavor:              0x00010000
	// Length:              46132
	// NumTables:           14
	// Reserved:            0
	// TotalSfntSize:       140308
	// TotalCompressedSize: 46040
	// MajorVersion:        1
	// MinorVersion:        0
	// MetaOffset:          0
	// MetaLength:          0
	// MetaOrigLength:      0
	// PrivOffset:          0
	// PrivLength:          0
	//
	// TableDirectory: 14 entries
	// 	{Flags: 0x06, Tag: <nil>, OrigLength: 96, TransformLength: <nil>}
	// 	{Flags: 0x00, Tag: <nil>, OrigLength: 1318, TransformLength: <nil>}
	// 	{Flags: 0x08, Tag: <nil>, OrigLength: 176, TransformLength: <nil>}
	// 	{Flags: 0x09, Tag: <nil>, OrigLength: 3437, TransformLength: <nil>}
	// 	{Flags: 0x11, Tag: <nil>, OrigLength: 8, TransformLength: <nil>}
	// 	{Flags: 0x0a, Tag: <nil>, OrigLength: 118912, TransformLength: 105020}
	// 	{Flags: 0x0b, Tag: <nil>, OrigLength: 1334, TransformLength: 0}
	// 	{Flags: 0x01, Tag: <nil>, OrigLength: 54, TransformLength: <nil>}
	// 	{Flags: 0x02, Tag: <nil>, OrigLength: 36, TransformLength: <nil>}
	// 	{Flags: 0x03, Tag: <nil>, OrigLength: 2662, TransformLength: <nil>}
	// 	{Flags: 0x04, Tag: <nil>, OrigLength: 32, TransformLength: <nil>}
	// 	{Flags: 0x05, Tag: <nil>, OrigLength: 6967, TransformLength: <nil>}
	// 	{Flags: 0x07, Tag: <nil>, OrigLength: 4838, TransformLength: <nil>}
	// 	{Flags: 0x0c, Tag: <nil>, OrigLength: 188, TransformLength: <nil>}
	//
	// CollectionDirectory: <nil>
	// CompressedFontData: 124832 bytes (uncompressed size)
	// ExtendedMetadata: <nil>
	// PrivateData: []
}

func Dump(f woff2.File) {
	dumpHeader(f.Header)
	fmt.Println()
	dumpTableDirectory(f.TableDirectory)
	fmt.Println()
	fmt.Println("CollectionDirectory:", f.CollectionDirectory)
	fmt.Println("CompressedFontData:", len(f.FontData), "bytes (uncompressed size)")
	fmt.Println("ExtendedMetadata:", f.ExtendedMetadata)
	fmt.Println("PrivateData:", f.PrivateData)
}

func dumpHeader(hdr woff2.Header) {
	fmt.Printf("Signature:           %#08x\n", hdr.Signature)
	fmt.Printf("Flavor:              %#08x\n", hdr.Flavor)
	fmt.Printf("Length:              %d\n", hdr.Length)
	fmt.Printf("NumTables:           %d\n", hdr.NumTables)
	fmt.Printf("Reserved:            %d\n", hdr.Reserved)
	fmt.Printf("TotalSfntSize:       %d\n", hdr.TotalSfntSize)
	fmt.Printf("TotalCompressedSize: %d\n", hdr.TotalCompressedSize)
	fmt.Printf("MajorVersion:        %d\n", hdr.MajorVersion)
	fmt.Printf("MinorVersion:        %d\n", hdr.MinorVersion)
	fmt.Printf("MetaOffset:          %d\n", hdr.MetaOffset)
	fmt.Printf("MetaLength:          %d\n", hdr.MetaLength)
	fmt.Printf("MetaOrigLength:      %d\n", hdr.MetaOrigLength)
	fmt.Printf("PrivOffset:          %d\n", hdr.PrivOffset)
	fmt.Printf("PrivLength:          %d\n", hdr.PrivLength)
}

func dumpTableDirectory(td woff2.TableDirectory) {
	fmt.Println("TableDirectory:", len(td), "entries")
	for _, t := range td {
		fmt.Printf("\t{")
		fmt.Printf("Flags: %#02x, ", t.Flags)
		if t.Tag != nil {
			fmt.Printf("Tag: %v, ", *t.Tag)
		} else {
			fmt.Printf("Tag: <nil>, ")
		}
		fmt.Printf("OrigLength: %v, ", t.OrigLength)
		if t.TransformLength != nil {
			fmt.Printf("TransformLength: %v", *t.TransformLength)
		} else {
			fmt.Printf("TransformLength: <nil>")
		}
		fmt.Printf("}\n")
	}
}
tags.go
@@ -0,0 +1,79 @@
package woff2

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

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

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

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

import "testing"

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