File | File | |
| | @@ -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
|