@@ -15,18 +15,18 @@ type Service struct{}
var s = struct {
changes []struct {
change.Change
Timeline []interface{}
Commits []change.Commit
Diff []byte
Diffs map[string][]byte // Key is commit SHA. "all" is diff of all commits combined.
}
}{
changes: []struct {
change.Change
Timeline []interface{}
Commits []change.Commit
Diff []byte
Diffs map[string][]byte
}{
{
Change: change.Change{
ID: 1,
State: change.OpenState,
@@ -34,11 +34,11 @@ var s = struct {
Labels: nil,
Author: shurcool,
CreatedAt: time.Date(2018, 2, 12, 0, 9, 19, 621031866, time.UTC),
Replies: 0,
Commits: 1,
Commits: 2,
},
Timeline: []interface{}{
change.Comment{
User: shurcool,
CreatedAt: time.Date(2018, 2, 12, 0, 9, 19, 621031866, time.UTC),
@@ -90,12 +90,24 @@ https://go.googlesource.com/image/+/master/font/gofont/ttfs/.
Add basic test coverage.
Helps https://github.com/ConradIrwin/font/issues/1.`,
Author: shurcool,
AuthorTime: time.Date(2018, 2, 11, 20, 10, 28, 0, time.UTC),
}, {
SHA: "61339d441b319cd6ca35d952522f86cc42ad4b6e",
Message: `Update test for new API.
The API had changed earlier, the test wasn't updated for it. This
change fixes that, allowing tests to pass.`,
Author: shurcool,
AuthorTime: time.Date(2018, 2, 12, 20, 22, 29, 674711268, time.UTC),
}},
Diff: []byte(diff),
Diffs: map[string][]byte{
"all": []byte(diffAll),
"d2568fb6f10921b2d0c84d58bad14b2fadb88aa7": []byte(diffCommit1),
"61339d441b319cd6ca35d952522f86cc42ad4b6e": []byte(diffCommit2),
},
},
},
}
// List changes.
@@ -179,11 +191,16 @@ func (*Service) ListCommits(ctx context.Context, repo string, id uint64) ([]chan
// Get a change diff.
func (*Service) GetDiff(ctx context.Context, repo string, id uint64, opt *change.GetDiffOptions) ([]byte, error) {
if repo != "dmitri.shuralyov.com/font/woff2" || id != 1 {
return nil, os.ErrNotExist
}
return s.changes[0].Diff, nil
switch opt {
case nil:
return s.changes[0].Diffs["all"], nil
default:
return s.changes[0].Diffs[opt.Commit], nil
}
}
// threadType is the notifications thread type for this service.
const threadType = "Change"
@@ -201,11 +218,11 @@ var shurcool = users.User{
AvatarURL: "https://dmitri.shuralyov.com/avatar.jpg",
HTMLURL: "https://dmitri.shuralyov.com",
SiteAdmin: true,
}
const diff = `diff --git a/Commit Message b/Commit Message
const diffAll = `diff --git a/Commit Message b/Commit Message
new file mode 100644
index 0000000..dfb31fe
--- /dev/null
+++ b/Commit Message
@@ -0,0 +1,27 @@
@@ -762,11 +779,11 @@ index 0000000..87545cf
+ dumpHeader(f.Header)
+ fmt.Println()
+ dumpTableDirectory(f.TableDirectory)
+ fmt.Println()
+ fmt.Println("CollectionDirectory:", f.CollectionDirectory)
+ fmt.Println("CompressedFontData:", len(f.CompressedFontData.Data), "bytes (uncompressed size)")
+ fmt.Println("CompressedFontData:", len(f.FontData), "bytes (uncompressed size)")
+ fmt.Println("ExtendedMetadata:", f.ExtendedMetadata)
+ fmt.Println("PrivateData:", f.PrivateData)
+}
+
+func dumpHeader(hdr woff2.Header) {
@@ -905,5 +922,692 @@ index 0000000..3980f39
+ if got := len(knownTableTags); got != want {
+ t.Errorf("got len(knownTableTags): %v, want: %v", got, want)
+ }
+}
`
const diffCommit1 = `diff --git a/doc.go b/doc.go
index fd35888..a751214 100644
--- a/doc.go
+++ b/doc.go
@@ -2,5 +2,3 @@
//
// The WOFF2 font packaging format is specified at https://www.w3.org/TR/WOFF2/.
package woff2
-
-// TODO: Implement.
diff --git a/parse.go b/parse.go
new file mode 100644
index 0000000..498a4a8
--- /dev/null
+++ b/parse.go
@@ -0,0 +1,438 @@
+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
+ }
+
+ 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
diff --git a/parse_test.go b/parse_test.go
new file mode 100644
index 0000000..87545cf
--- /dev/null
+++ b/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.CompressedFontData.Data), "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")
+ }
+}
diff --git a/tags.go b/tags.go
new file mode 100644
index 0000000..3c13a55
--- /dev/null
+++ b/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'),
+}
diff --git a/tags_test.go b/tags_test.go
new file mode 100644
index 0000000..3980f39
--- /dev/null
+++ b/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)
+ }
+}
`
const diffCommit2 = `diff --git a/parse_test.go b/parse_test.go
index 87545cf..14e2830 100644
--- a/parse_test.go
+++ b/parse_test.go
@@ -66,7 +66,7 @@ func Dump(f woff2.File) {
dumpTableDirectory(f.TableDirectory)
fmt.Println()
fmt.Println("CollectionDirectory:", f.CollectionDirectory)
- fmt.Println("CompressedFontData:", len(f.CompressedFontData.Data), "bytes (uncompressed size)")
+ fmt.Println("CompressedFontData:", len(f.FontData), "bytes (uncompressed size)")
fmt.Println("ExtendedMetadata:", f.ExtendedMetadata)
fmt.Println("PrivateData:", f.PrivateData)
}
`