// SPDX-License-Identifier: Unlicense OR BSD-3-Clausepackage opentypeimport ()var (// TrueType is the first four bytes of an OpenType file containing a TrueType fontTrueType = Tag(0x00010000)// AppleTrueType is the first four bytes of an OpenType file containing a TrueType font // (specifically one designed for Apple products, it's recommended to use TrueType instead)AppleTrueType = MustNewTag("true")// PostScript1 is the first four bytes of an OpenType file containing a PostScript 1 fontPostScript1 = MustNewTag("typ1")// OpenType is the first four bytes of an OpenType file containing a PostScript Type 2 font // as specified by OpenTypeOpenType = MustNewTag("OTTO")// signatureWOFF is the magic number at the start of a WOFF file. signatureWOFF = MustNewTag("wOFF") ttcTag = MustNewTag("ttcf") errInvalidDfont = errors.New("invalid dfont"))// dfontResourceDataOffset is the assumed value of a dfont file's resource data// offset.//// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format// says that "A Mac OS resource file... [starts with an] offset from start of// file to start of resource data section... [usually] 0x0100". In theory,// 0x00000100 isn't always a magic number for identifying dfont files. In// practice, it seems to work.const dfontResourceDataOffset = 0x00000100typeResourceinterface {Read([]byte) (int, error)ReadAt([]byte, int64) (int, error)Seek(int64, int) (int64, error)}// tableSection represents a table within the font file.type tableSection struct { offset uint32// Offset into the file this table starts. length uint32// Length of this table within the file. zLength uint32// Uncompressed length of this table.}// Loader is the low level font reader, providing// full control over table loading.typeLoaderstruct { file Resource// source, needed to parse each table tables map[Tag]tableSection// header only, contents is processed on demand// Type represents the kind of this font being loaded. // It is one of TrueType, TrueTypeApple, PostScript1, OpenType Type Tag}// NewLoader reads the `file` header and returns// a new lazy ot.// `file` will be used to parse tables, and should not be close.func ( Resource) (*Loader, error) {returnparseOneFont(, 0, false)}// NewLoaders is the same as `NewLoader`, but supports collections.func ( Resource) ([]*Loader, error) { , := .Seek(0, io.SeekStart) // file might have been used beforeif != nil {returnnil, }var [4]byte _, = .Read([:])if != nil {returnnil, } := NewTag([0], [1], [2], [3]) .Seek(0, io.SeekStart)var ( *Loader []uint32bool )switch {casesignatureWOFF, TrueType, OpenType, PostScript1, AppleTrueType: , = parseOneFont(, 0, false)casettcTag: , = parseTTCHeader()casedfontResourceDataOffset: , = parseDfont() = truedefault:returnnil, fmt.Errorf("unsupported font format %v", ) }if != nil {returnnil, }// only one fontif != nil {return []*Loader{}, nil }// collection := make([]*Loader, len())for , := range { [], = parseOneFont(, , )if != nil {returnnil, } }return , nil}// dst is an optional storage which may be provided to reduce allocations.func ( *Loader) ( tableSection, []byte) ([]byte, error) {if .length != 0 && .length < .zLength { := io.NewSectionReader(.file, int64(.offset), int64(.length)) , := zlib.NewReader()if != nil {returnnil, }defer .Close()ifcap() < int(.zLength) { = make([]byte, .zLength) } = [0:.zLength]if , := io.ReadFull(, ); != nil {returnnil, } } else {ifcap() < int(.length) { = make([]byte, .length) } = [0:.length]if , := .file.ReadAt(, int64(.offset)); != nil {returnnil, } }return , nil}// HasTable returns true if [table] is present.func ( *Loader) ( Tag) bool { , := .tables[]return}// Tables returns all the tables found in the file,// as a sorted slice.func ( *Loader) () []Tag { := make([]Tag, 0, len(.tables))for := range .tables { = append(, ) }sort.Slice(, func(, int) bool { return [] < [] })return}// RawTable returns the binary content of the given table,// or an error if not found.func ( *Loader) ( Tag) ([]byte, error) {return .RawTableTo(, nil)}// RawTable writes the binary content of the given table to [dst], returning it,// or an error if not found.func ( *Loader) ( Tag, []byte) ([]byte, error) { , := .tables[]if ! {returnnil, fmt.Errorf("missing table %s", ) }return .findTableBuffer(, )}func parseOneFont( Resource, uint32, bool) ( *Loader, error) { _, = .Seek(int64(), io.SeekStart)if != nil {returnnil, fmt.Errorf("invalid offset: %s", ) }var [4]byte _, = .Read([:])if != nil {returnnil, } := NewTag([0], [1], [2], [3])switch {casesignatureWOFF: , = parseWOFF(, , )caseTrueType, OpenType, PostScript1, AppleTrueType: , = parseOTF(, , )casettcTag, dfontResourceDataOffset: // no more collections allowed herereturnnil, errors.New("collections not allowed")default:returnnil, fmt.Errorf("unknown font format tag %v", ) }if != nil {returnnil, }return , nil}// support for collectionsconst maxNumFonts = 2048// security implementation limit// returns the offsets of each fontfunc parseTTCHeader( io.Reader) ([]uint32, error) {// The https://www.microsoft.com/typography/otspec/otff.htm "Font // Collections" section describes the TTC header.var [12]byteif , := .Read([:]); != nil {returnnil, }// skip versions := binary.BigEndian.Uint32([8:])if == 0 {returnnil, errors.New("empty font collection") }if > maxNumFonts {returnnil, fmt.Errorf("number of fonts (%d) in collection exceed implementation limit (%d)", , maxNumFonts) } := make([]byte, *4) , := io.ReadFull(, )if != nil {returnnil, }returnparseUint32s(, int()), nil}// parseDfont parses a dfont resource map, as per// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format//// That unofficial wiki page lists all of its fields as *signed* integers,// which looks unusual. The actual file format might use *unsigned* integers in// various places, but until we have either an official specification or an// actual dfont file where this matters, we'll use signed integers and treat// negative values as invalid.func parseDfont( Resource) ([]uint32, error) {var [16]byteif , := .Read([:]); != nil {returnnil, } := binary.BigEndian.Uint32([4:]) := binary.BigEndian.Uint32([12:])const (// (maxTableOffset + maxTableLength) will not overflow an int32. = 1 << 29 = 1 << 29 )if > || > {returnnil, errors.New("unsupported table offset or length") }const = 28if < {returnnil, errInvalidDfont } , := .ReadAt([:2], int64(+24))if != nil {returnnil, } := int64(int16(binary.BigEndian.Uint16([:])))if < || < uint32()+2 {returnnil, errInvalidDfont } _, = .ReadAt([:2], int64()+)if != nil {returnnil, } := int(binary.BigEndian.Uint16([:])) // The number of types, minus one.if == 0xFFFF {returnnil, errInvalidDfont } += 1const = 8if *uint32() > -uint32()-2 {returnnil, errInvalidDfont } := make([]byte, *) _, = .ReadAt(, int64()++2)if != nil {returnnil, } , := 0, 0for := 0; < ; ++ {ifbinary.BigEndian.Uint32([*:]) != 0x73666e74 { // "sfnt".continue } = int(int16(binary.BigEndian.Uint16([*+4:])))if < 0 {returnnil, errInvalidDfont }// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format // says that the value in the wire format is "the number of // resources of this type, minus one." ++ = int(int16(binary.BigEndian.Uint16(([*+6:]))))if < 0 {returnnil, errInvalidDfont } }if == 0 {returnnil, errInvalidDfont }if > maxNumFonts {returnnil, fmt.Errorf("number of fonts (%d) in collection exceed implementation limit (%d)", , maxNumFonts) }const = 12 , := uint32(int()+), *uint32()if > || > - {returnnil, errInvalidDfont } := make([]byte, ) _, = .ReadAt(, int64(+))if != nil {returnnil, } := make([]uint32, )for := range { := 0xffffff & binary.BigEndian.Uint32([*+4:])// Offsets are relative to the resource data start, not the file start. // A particular resource's data also starts with a 4-byte length, which // we skip. += dfontResourceDataOffset + 4if > {returnnil, errors.New("unsupported table offset or length") } [] = }return , nil}// data length must have been checkedfunc parseUint32s( []byte, int) []uint32 { := make([]uint32, )for := range { [] = binary.BigEndian.Uint32([4*:]) }return}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.