// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:generate go run gen.go

// Package sfnt implements a decoder for TTF (TrueType Fonts) and OTF (OpenType // Fonts). Such fonts are also known as SFNT fonts. // // This package provides a low-level API and does not depend on vector // rasterization packages. Glyphs are represented as vectors, not pixels. // // The sibling golang.org/x/image/font/opentype package provides a high-level // API, including glyph rasterization. // // This package provides a decoder in that it produces a TTF's glyphs (and // other metadata such as advance width and kerning pairs): give me the 'A' // from times_new_roman.ttf. // // Unlike the image.Image decoder functions (gif.Decode, jpeg.Decode and // png.Decode) in Go's standard library, an sfnt.Font needs ongoing access to // the TTF data (as a []byte or io.ReaderAt) after the sfnt.ParseXxx functions // return. If parsing a []byte, its elements are assumed immutable while the // sfnt.Font remains in use. If parsing an *os.File, you should not close the // file until after you're done with the sfnt.Font. // // The []byte or io.ReaderAt data given to ParseXxx can be re-written to // another io.Writer, copying the underlying TTF file, but this package does // not provide an encoder. Specifically, there is no API to build a different // TTF file, whether 'from scratch' or by modifying an existing one.
package sfnt // import "golang.org/x/image/font/sfnt" // This implementation was written primarily to the // https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx // specification. Additional documentation is at // http://developer.apple.com/fonts/TTRefMan/ // // The pyftinspect tool from https://github.com/fonttools/fonttools is useful // for inspecting SFNT fonts. // // The ttfdump tool is also useful. For example: // ttfdump -t cmap ../testdata/CFFTest.otf dump.txt import ( ) // These constants are not part of the specifications, but are limitations used // by this implementation. const ( // This value is arbitrary, but defends against parsing malicious font // files causing excessive memory allocations. For reference, Adobe's // SourceHanSansSC-Regular.otf has 65535 glyphs and: // - its format-4 cmap table has 1581 segments. // - its format-12 cmap table has 16498 segments. // // TODO: eliminate this constraint? If the cmap table is very large, load // some or all of it lazily (at the time Font.GlyphIndex is called) instead // of all of it eagerly (at the time Font.initialize is called), while // keeping an upper bound on the memory used? This will make the code in // cmap.go more complicated, considering that all of the Font methods are // safe to call concurrently, as long as each call has a different *Buffer. maxCmapSegments = 20000 // TODO: similarly, load subroutine locations lazily. Adobe's // SourceHanSansSC-Regular.otf has up to 30000 subroutines. maxNumSubroutines = 40000 maxCompoundRecursionDepth = 8 maxCompoundStackSize = 64 maxGlyphDataLength = 64 * 1024 maxHintBits = 256 maxNumFontDicts = 256 maxNumFonts = 256 maxNumTables = 256 maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation. // (maxTableOffset + maxTableLength) will not overflow an int32. maxTableLength = 1 << 29 maxTableOffset = 1 << 29 ) var ( // ErrColoredGlyph indicates that the requested glyph is not a monochrome // vector glyph, such as a colored (bitmap or vector) emoji glyph. ErrColoredGlyph = errors.New("sfnt: colored glyph") // ErrNotFound indicates that the requested value was not found. ErrNotFound = errors.New("sfnt: not found") errInvalidBounds = errors.New("sfnt: invalid bounds") errInvalidCFFTable = errors.New("sfnt: invalid CFF table") errInvalidCmapTable = errors.New("sfnt: invalid cmap table") errInvalidDfont = errors.New("sfnt: invalid dfont") errInvalidFont = errors.New("sfnt: invalid font") errInvalidFontCollection = errors.New("sfnt: invalid font collection") errInvalidGPOSTable = errors.New("sfnt: invalid GPOS table") errInvalidGlyphData = errors.New("sfnt: invalid glyph data") errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length") errInvalidHeadTable = errors.New("sfnt: invalid head table") errInvalidHheaTable = errors.New("sfnt: invalid hhea table") errInvalidHmtxTable = errors.New("sfnt: invalid hmtx table") errInvalidKernTable = errors.New("sfnt: invalid kern table") errInvalidLocaTable = errors.New("sfnt: invalid loca table") errInvalidLocationData = errors.New("sfnt: invalid location data") errInvalidMaxpTable = errors.New("sfnt: invalid maxp table") errInvalidNameTable = errors.New("sfnt: invalid name table") errInvalidOS2Table = errors.New("sfnt: invalid OS/2 table") errInvalidPostTable = errors.New("sfnt: invalid post table") errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)") errInvalidSourceData = errors.New("sfnt: invalid source data") errInvalidTableOffset = errors.New("sfnt: invalid table offset") errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order") errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string") errUnsupportedCFFFDSelectTable = errors.New("sfnt: unsupported CFF FDSelect table") errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version") errUnsupportedClassDefFormat = errors.New("sfnt: unsupported class definition format") errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings") errUnsupportedCollection = errors.New("sfnt: unsupported collection") errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph") errUnsupportedCoverageFormat = errors.New("sfnt: unsupported coverage format") errUnsupportedExtensionPosFormat = errors.New("sfnt: unsupported extension positioning format") errUnsupportedGPOSTable = errors.New("sfnt: unsupported GPOS table") errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length") errUnsupportedKernTable = errors.New("sfnt: unsupported kern table") errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments") errUnsupportedNumberOfFontDicts = errors.New("sfnt: unsupported number of font dicts") errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts") errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints") errUnsupportedNumberOfSubroutines = errors.New("sfnt: unsupported number of subroutines") errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables") errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding") errUnsupportedPostTable = errors.New("sfnt: unsupported post table") errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding") errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length") errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring") ) // GlyphIndex is a glyph index in a Font. type GlyphIndex uint16 // NameID identifies a name table entry. // // See the "Name IDs" section of // https://www.microsoft.com/typography/otspec/name.htm type NameID uint16 const ( NameIDCopyright NameID = 0 NameIDFamily NameID = 1 NameIDSubfamily NameID = 2 NameIDUniqueIdentifier NameID = 3 NameIDFull NameID = 4 NameIDVersion NameID = 5 NameIDPostScript NameID = 6 NameIDTrademark NameID = 7 NameIDManufacturer NameID = 8 NameIDDesigner NameID = 9 NameIDDescription NameID = 10 NameIDVendorURL NameID = 11 NameIDDesignerURL NameID = 12 NameIDLicense NameID = 13 NameIDLicenseURL NameID = 14 NameIDTypographicFamily NameID = 16 NameIDTypographicSubfamily NameID = 17 NameIDCompatibleFull NameID = 18 NameIDSampleText NameID = 19 NameIDPostScriptCID NameID = 20 NameIDWWSFamily NameID = 21 NameIDWWSSubfamily NameID = 22 NameIDLightBackgroundPalette NameID = 23 NameIDDarkBackgroundPalette NameID = 24 NameIDVariationsPostScriptPrefix NameID = 25 ) // Units are an integral number of abstract, scalable "font units". The em // square is typically 1000 or 2048 "font units". This would map to a certain // number (e.g. 30 pixels) of physical pixels, depending on things like the // display resolution (DPI) and font size (e.g. a 12 point font). type Units int32 // scale returns x divided by unitsPerEm, rounded to the nearest fixed.Int26_6 // value (1/64th of a pixel). func scale( fixed.Int26_6, Units) fixed.Int26_6 { if >= 0 { += fixed.Int26_6() / 2 } else { -= fixed.Int26_6() / 2 } return / fixed.Int26_6() } func u16( []byte) uint16 { _ = [1] // Bounds check hint to compiler. return uint16([0])<<8 | uint16([1])<<0 } func u32( []byte) uint32 { _ = [3] // Bounds check hint to compiler. return uint32([0])<<24 | uint32([1])<<16 | uint32([2])<<8 | uint32([3])<<0 } // source is a source of byte data. Conceptually, it is like an io.ReaderAt, // except that a common source of SFNT font data is in-memory instead of // on-disk: a []byte containing the entire data, either as a global variable // (e.g. "goregular.TTF") or the result of an ioutil.ReadFile call. In such // cases, as an optimization, we skip the io.Reader / io.ReaderAt model of // copying from the source to a caller-supplied buffer, and instead provide // direct access to the underlying []byte data. type source struct { b []byte r io.ReaderAt // TODO: add a caching layer, if we're using the io.ReaderAt? Note that // this might make a source no longer safe to use concurrently. } // valid returns whether exactly one of s.b and s.r is nil. func ( *source) () bool { return (.b == nil) != (.r == nil) } // viewBufferWritable returns whether the []byte returned by source.view can be // written to by the caller, including by passing it to the same method // (source.view) on other receivers (i.e. different sources). // // In other words, it returns whether the source's underlying data is an // io.ReaderAt, not a []byte. func ( *source) () bool { return .b == nil } // view returns the length bytes at the given offset. buf is an optional // scratch buffer to reduce allocations when calling view multiple times. A nil // buf is valid. The []byte returned may be a sub-slice of buf[:cap(buf)], or // it may be an unrelated slice. In any case, the caller should not modify the // contents of the returned []byte, other than passing that []byte back to this // method on the same source s. func ( *source) ( []byte, , int) ([]byte, error) { if 0 > || > + { return nil, errInvalidBounds } // Try reading from the []byte. if .b != nil { if + > len(.b) { return nil, errInvalidBounds } return .b[ : +], nil } // Read from the io.ReaderAt. if <= cap() { = [:] } else { // Round length up to the nearest KiB. The slack can lead to fewer // allocations if the buffer is re-used for multiple source.view calls. := += 1023 &^= 1023 = make([]byte, , ) } if , := .r.ReadAt(, int64()); != { return nil, } return , nil } // varLenView returns bytes from the given offset for sub-tables with varying // length. The length of bytes is determined by staticLength plus n*itemLength, // where n is read as uint16 from countOffset (relative to offset). buf is an // optional scratch buffer (see source.view()) func ( *source) ( []byte, , , , int) ([]byte, int, error) { if 0 > || > + { return nil, 0, errInvalidBounds } if 0 > || +1 >= { return nil, 0, errInvalidBounds } // read static part which contains our count , := .view(, , ) if != nil { return nil, 0, } := int(u16([:])) , = .view(, , +*) if != nil { return nil, 0, } return , , nil } // u16 returns the uint16 in the table t at the relative offset i. // // buf is an optional scratch buffer as per the source.view method. func ( *source) ( []byte, table, int) (uint16, error) { if < 0 || uint(.length) < uint(+2) { return 0, errInvalidBounds } , := .view(, int(.offset)+, 2) if != nil { return 0, } return u16(), nil } // u32 returns the uint32 in the table t at the relative offset i. // // buf is an optional scratch buffer as per the source.view method. func ( *source) ( []byte, table, int) (uint32, error) { if < 0 || uint(.length) < uint(+4) { return 0, errInvalidBounds } , := .view(, int(.offset)+, 4) if != nil { return 0, } return u32(), nil } // table is a section of the font data. type table struct { offset, length uint32 } // ParseCollection parses an SFNT font collection, such as TTC or OTC data, // from a []byte data source. // // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it // will return a collection containing 1 font. // // The caller should not modify src while the Collection or its Fonts remain in // use. See the package documentation for details. func ( []byte) (*Collection, error) { := &Collection{src: source{b: }} if := .initialize(); != nil { return nil, } return , nil } // ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data, // from an io.ReaderAt data source. // // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it // will return a collection containing 1 font. // // The caller should not modify or close src while the Collection or its Fonts // remain in use. See the package documentation for details. func ( io.ReaderAt) (*Collection, error) { := &Collection{src: source{r: }} if := .initialize(); != nil { return nil, } return , nil } // Collection is a collection of one or more fonts. // // All of the Collection methods are safe to call concurrently. type Collection struct { src source offsets []uint32 isDfont bool } // NumFonts returns the number of fonts in the collection. func ( *Collection) () int { return len(.offsets) } func ( *Collection) () error { // The https://www.microsoft.com/typography/otspec/otff.htm "Font // Collections" section describes the TTC header. // // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format // describes the dfont header. // // 16 is the maximum of sizeof(TTCHeader) and sizeof(DfontHeader). , := .src.view(nil, 0, 16) if != nil { return } // These cases match the switch statement in Font.initializeTables. switch u32() { default: return errInvalidFontCollection case dfontResourceDataOffset: return .parseDfont(, u32([4:]), u32([12:])) case 0x00010000, 0x4f54544f, 0x74727565: // 0x10000, "OTTO", "true" // Try parsing it as a single font instead of a collection. .offsets = []uint32{0} case 0x74746366: // "ttcf". := u32([8:]) if == 0 || > maxNumFonts { return errUnsupportedNumberOfFonts } , = .src.view(nil, 12, int(4*)) if != nil { return } .offsets = make([]uint32, ) for := range .offsets { := u32([4*:]) if > maxTableOffset { return errUnsupportedTableOffsetLength } .offsets[] = } } return nil } // 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 = 0x00000100 // 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 ( *Collection) ( []byte, , uint32) error { if > maxTableOffset || > maxTableLength { return errUnsupportedTableOffsetLength } const = 28 if < { return errInvalidDfont } , := .src.view(, int(+24), 2) if != nil { return } := int(int16(u16())) if < || < uint32()+2 { return errInvalidDfont } , = .src.view(, int()+, 2) if != nil { return } := int(int16(u16())) const = 8 if < 0 || *uint32() > -uint32()-2 { return errInvalidDfont } , = .src.view(, int()++2, *) if != nil { return } , := 0, 0 for := 0; < ; ++ { if u32([*:]) != 0x73666e74 { // "sfnt". continue } = int(int16(u16([*+4:]))) if < 0 { return 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(u16([*+6:]))) if < 0 { return errInvalidDfont } break } if == 0 { return errInvalidDfont } if > maxNumFonts { return errUnsupportedNumberOfFonts } const = 12 if , := uint32(+), *uint32(); > || > - { return errInvalidDfont } else { , = .src.view(, int(+), int()) if != nil { return } } .offsets = make([]uint32, ) for := range .offsets { := 0xffffff & u32([*+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 + 4 if > maxTableOffset { return errUnsupportedTableOffsetLength } .offsets[] = } .isDfont = true return nil } // Font returns the i'th font in the collection. func ( *Collection) ( int) (*Font, error) { if < 0 || len(.offsets) <= { return nil, ErrNotFound } := &Font{src: .src} if := .initialize(int(.offsets[]), .isDfont); != nil { return nil, } return , nil } // Parse parses an SFNT font, such as TTF or OTF data, from a []byte data // source. // // The caller should not modify src while the Font remains in use. See the // package documentation for details. func ( []byte) (*Font, error) { := &Font{src: source{b: }} if := .initialize(0, false); != nil { return nil, } return , nil } // ParseReaderAt parses an SFNT font, such as TTF or OTF data, from an // io.ReaderAt data source. // // The caller should not modify or close src while the Font remains in use. See // the package documentation for details. func ( io.ReaderAt) (*Font, error) { := &Font{src: source{r: }} if := .initialize(0, false); != nil { return nil, } return , nil } // Font is an SFNT font. // // Many of its methods take a *Buffer argument, as re-using buffers can reduce // the total memory allocation of repeated Font method calls, such as measuring // and rasterizing every unique glyph in a string of text. If efficiency is not // a concern, passing a nil *Buffer is valid, and implies using a temporary // buffer for a single call. // // It is valid to re-use a *Buffer with multiple Font method calls, even with // different *Font receivers, as long as they are not concurrent calls. // // All of the Font methods are safe to call concurrently, as long as each call // has a different *Buffer (or nil). // // The Font methods that don't take a *Buffer argument are always safe to call // concurrently. // // Some methods provide lengths or coordinates, e.g. bounds, font metrics and // control points. All of these methods take a ppem parameter, which is the // number of pixels in 1 em, expressed as a 26.6 fixed point value. For // example, if 1 em is 10 pixels then ppem is fixed.I(10), which equals // fixed.Int26_6(10 << 6). // // To get those lengths or coordinates in terms of font units instead of // pixels, use ppem = fixed.Int26_6(f.UnitsPerEm()) and if those methods take a // font.Hinting parameter, use font.HintingNone. The return values will have // type fixed.Int26_6, but those numbers can be converted back to Units with no // further scaling necessary. type Font struct { src source // initialOffset is the file offset of the start of the font. This may be // non-zero for fonts within a font collection. initialOffset int32 // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Required Tables". cmap table head table hhea table hmtx table maxp table name table os2 table post table // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Tables Related to TrueType Outlines". // // This implementation does not support hinting, so it does not read the // cvt, fpgm gasp or prep tables. glyf table loca table // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Tables Related to PostScript Outlines". // // TODO: cff2, vorg? cff table // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Tables Related to Bitmap Glyphs". // // TODO: Others? cblc table // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Advanced Typographic Tables". // // TODO: base, gdef, gsub, jstf, math? gpos table // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Other OpenType Tables". // // TODO: hdmx, vmtx? Others? kern table cached struct { ascent int32 capHeight int32 finalTableOffset int32 glyphData glyphData glyphIndex glyphIndexFunc bounds [4]int16 descent int32 indexToLocFormat bool // false means short, true means long. isColorBitmap bool isPostScript bool kernNumPairs int32 kernOffset int32 kernFuncs []kernFunc lineGap int32 numHMetrics int32 post *PostTable slope [2]int32 unitsPerEm Units xHeight int32 } } // NumGlyphs returns the number of glyphs in f. func ( *Font) () int { return len(.cached.glyphData.locations) - 1 } // UnitsPerEm returns the number of units per em for f. func ( *Font) () Units { return .cached.unitsPerEm } func ( *Font) ( int, bool) error { if !.src.valid() { return errInvalidSourceData } , , , := .initializeTables(, ) if != nil { return } // The order of these parseXxx calls matters. Later calls may depend on // information parsed by earlier calls, such as the maxp table's numGlyphs. // To enforce these dependencies, such information is passed and returned // explicitly, and the f.cached fields are only set afterwards. // // When implementing new parseXxx methods, take care not to call methods // such as Font.NumGlyphs that implicitly depend on f.cached fields. , , , , := .parseHead() if != nil { return } , , := .parseMaxp(, ) if != nil { return } , , , := .parseGlyphData(, , , ) if != nil { return } , , := .parseCmap() if != nil { return } , , , := .parseKern() if != nil { return } , , := .parseGPOSKern() if != nil { return } , , , , , , , := .parseHhea(, ) if != nil { return } , = .parseHmtx(, , ) if != nil { return } , , , , := .parseOS2() if != nil { return } , , := .parsePost(, ) if != nil { return } .cached.ascent = .cached.capHeight = .cached.finalTableOffset = .cached.glyphData = .cached.glyphIndex = .cached.bounds = .cached.descent = .cached.indexToLocFormat = .cached.isColorBitmap = .cached.isPostScript = .cached.kernNumPairs = .cached.kernOffset = .cached.kernFuncs = .cached.lineGap = .cached.numHMetrics = .cached.post = .cached.slope = [2]int32{, } .cached.unitsPerEm = .cached.xHeight = if ! { , , := .initOS2VersionBelow2() if != nil { return } .cached.xHeight = .cached.capHeight = } return nil } func ( *Font) ( int, bool) ( []byte, int32, bool, error) { .initialOffset = int32() if int(.initialOffset) != { return nil, 0, false, errUnsupportedTableOffsetLength } // https://www.microsoft.com/typography/otspec/otff.htm "Organization of an // OpenType Font" says that "The OpenType font starts with the Offset // Table", which is 12 bytes. , := .src.view(nil, , 12) if != nil { return nil, 0, false, } // When updating the cases in this switch statement, also update the // Collection.initialize method. switch u32() { default: return nil, 0, false, errInvalidFont case dfontResourceDataOffset: return nil, 0, false, errInvalidSingleFont case 0x00010000: // No-op. case 0x4f54544f: // "OTTO". = true case 0x74727565: // "true" // No-op. case 0x74746366: // "ttcf". return nil, 0, false, errInvalidSingleFont } := int(u16([4:])) if > maxNumTables { return nil, 0, false, errUnsupportedNumberOfTables } // "The Offset Table is followed immediately by the Table Record entries... // sorted in ascending order by tag", 16 bytes each. , = .src.view(, +12, 16*) if != nil { return nil, 0, false, } for , , := , true, uint32(0); len() > 0; = [16:] { := u32() if { = false } else if <= { return nil, 0, false, errInvalidTableTagOrder } = , := u32([8:12]), u32([12:16]) // For dfont files, the offset is relative to the resource, not the // file. if { := += uint32() if < { return nil, 0, false, errUnsupportedTableOffsetLength } } if > maxTableOffset || > maxTableLength { return nil, 0, false, errUnsupportedTableOffsetLength } // We ignore the checksums, but "all tables must begin on four byte // boundries [sic]". if &3 != 0 { return nil, 0, false, errInvalidTableOffset } if < int32(+) { = int32( + ) } // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32. switch { case 0x43424c43: .cblc = table{, } case 0x43464620: .cff = table{, } case 0x4f532f32: .os2 = table{, } case 0x636d6170: .cmap = table{, } case 0x676c7966: .glyf = table{, } case 0x47504f53: .gpos = table{, } case 0x68656164: .head = table{, } case 0x68686561: .hhea = table{, } case 0x686d7478: .hmtx = table{, } case 0x6b65726e: .kern = table{, } case 0x6c6f6361: .loca = table{, } case 0x6d617870: .maxp = table{, } case 0x6e616d65: .name = table{, } case 0x706f7374: .post = table{, } } } if (.src.b != nil) && (int() > len(.src.b)) { return nil, 0, false, errInvalidSourceData } return , , , nil } func ( *Font) ( []byte) ( []byte, glyphIndexFunc, error) { // https://www.microsoft.com/typography/OTSPEC/cmap.htm const , = 4, 8 if .cmap.length < { return nil, nil, errInvalidCmapTable } , := .src.u16(, .cmap, 2) if != nil { return nil, nil, } := int() if .cmap.length < +*uint32() { return nil, nil, errInvalidCmapTable } var ( int uint32 uint32 uint16 ) // Scan all of the subtables, picking the widest supported one. See the // platformEncodingWidth comment for more discussion of width. for := 0; < ; ++ { , = .src.view(, int(.cmap.offset)++*, ) if != nil { return nil, nil, } := u16() := u16([2:]) := platformEncodingWidth(, ) if <= { continue } := u32([4:]) if > .cmap.length-4 { return nil, nil, errInvalidCmapTable } , = .src.view(, int(.cmap.offset+), 4) if != nil { return nil, nil, } := u16() if !supportedCmapFormat(, , ) { continue } := uint32(u16([2:])) = = = = } if == 0 { return nil, nil, errUnsupportedCmapEncodings } return .makeCachedGlyphIndex(, , , ) } func ( *Font) ( []byte) ( []byte, [4]int16, bool, Units, error) { // https://www.microsoft.com/typography/otspec/head.htm if .head.length != 54 { return nil, [4]int16{}, false, 0, errInvalidHeadTable } , := .src.u16(, .head, 18) if != nil { return nil, [4]int16{}, false, 0, } if == 0 { return nil, [4]int16{}, false, 0, errInvalidHeadTable } = Units() for := range { , := .src.u16(, .head, 36+2*) if != nil { return nil, [4]int16{}, false, 0, } [] = int16() } , = .src.u16(, .head, 50) if != nil { return nil, [4]int16{}, false, 0, } = != 0 return , , , , nil } func ( *Font) ( []byte, int32) ( []byte, , , , , , int32, error) { // https://www.microsoft.com/typography/OTSPEC/hhea.htm if .hhea.length != 36 { return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable } , := .src.u16(, .hhea, 34) if != nil { return nil, 0, 0, 0, 0, 0, 0, } if int32() > || == 0 { return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable } , := .src.u16(, .hhea, 4) if != nil { return nil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 6) if != nil { return nil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 8) if != nil { return nil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 20) if != nil { return nil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 18) if != nil { return nil, 0, 0, 0, 0, 0, 0, } return , int32(int16()), int32(int16()), int32(int16()), int32(int16()), int32(int16()), int32(), nil } func ( *Font) ( []byte, , int32) ( []byte, error) { // https://www.microsoft.com/typography/OTSPEC/hmtx.htm // The spec says that the hmtx table's length should be // "4*numHMetrics+2*(numGlyphs-numHMetrics)". However, some fonts seen in the // wild omit the "2*(nG-nHM)". See https://github.com/golang/go/issues/28379 if .hmtx.length != uint32(4*) && .hmtx.length != uint32(4*+2*(-)) { return nil, errInvalidHmtxTable } return , nil } func ( *Font) ( []byte) ( []byte, , int32, error) { // https://www.microsoft.com/typography/otspec/kern.htm if .kern.length == 0 { return , 0, 0, nil } const = 4 if .kern.length < { return nil, 0, 0, errInvalidKernTable } , = .src.view(, int(.kern.offset), ) if != nil { return nil, 0, 0, } := int(.kern.offset) + := int(.kern.length) - switch := u16(); { case 0: if := int(u16([2:])); == 0 { return , 0, 0, nil } else if > 1 { // TODO: support multiple subtables. For now, fall through and use // only the first one. } return .parseKernVersion0(, , ) case 1: if [2] != 0 || [3] != 0 { return nil, 0, 0, errUnsupportedKernTable } // Microsoft's https://www.microsoft.com/typography/otspec/kern.htm // says that "Apple has extended the definition of the 'kern' table to // provide additional functionality. The Apple extensions are not // supported on Windows." // // The format is relatively complicated, including encoding a state // machine, but rarely seen. We follow Microsoft's and FreeType's // behavior and simply ignore it. Theoretically, we could follow // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html // but it doesn't seem worth the effort. return , 0, 0, nil } return nil, 0, 0, errUnsupportedKernTable } func ( *Font) ( []byte, , int) ( []byte, , int32, error) { const = 6 if < { return nil, 0, 0, errInvalidKernTable } , = .src.view(, , ) if != nil { return nil, 0, 0, } if := u16(); != 0 { return nil, 0, 0, errUnsupportedKernTable } := u16([2:]) if int() < || < int() { return nil, 0, 0, errInvalidKernTable } if := [5]; != 0x01 { // We only support horizontal kerning. return nil, 0, 0, errUnsupportedKernTable } += -= -= switch := [4]; { case 0: return .parseKernFormat0(, , , ) case 2: // If we could find such a font, we could write code to support it, but // a comment in the equivalent FreeType code (sfnt/ttkern.c) says that // they've never seen such a font. } return nil, 0, 0, errUnsupportedKernTable } func ( *Font) ( []byte, , int, uint16) ( []byte, , int32, error) { const , = 8, 6 if < { return nil, 0, 0, errInvalidKernTable } , = .src.view(, , ) if != nil { return nil, 0, 0, } = int32(u16()) // The subtable length from the kern table is only uint16. Fonts like // Cambria, Calibri or Corbel have more then 10k kerning pairs and the // actual subtable size is truncated to uint16. Compare size with KERN // length and truncated size with subtable length. := + *int() if ( < ) || ( != uint16()) { return nil, 0, 0, errInvalidKernTable } return , , int32() + , nil } func ( *Font) ( []byte, bool) ( []byte, int32, error) { // https://www.microsoft.com/typography/otspec/maxp.htm if { if .maxp.length != 6 { return nil, 0, errInvalidMaxpTable } } else { if .maxp.length != 32 { return nil, 0, errInvalidMaxpTable } } , := .src.u16(, .maxp, 4) if != nil { return nil, 0, } return , int32(), nil } type glyphData struct { // The glyph data for the i'th glyph index is in // src[locations[i+0]:locations[i+1]]. // // The slice length equals 1 plus the number of glyphs. locations []uint32 // For PostScript fonts, the bytecode for the i'th global or local // subroutine is in src[x[i+0]:x[i+1]]. // // The []uint32 slice length equals 1 plus the number of subroutines gsubrs []uint32 singleSubrs []uint32 multiSubrs [][]uint32 fdSelect fdSelect } func ( *Font) ( []byte, int32, , bool) ( []byte, glyphData, bool, error) { if { := cffParser{ src: &.src, base: int(.cff.offset), offset: int(.cff.offset), end: int(.cff.offset + .cff.length), } , = .parse() if != nil { return nil, glyphData{}, false, } } else if .loca.length != 0 { .locations, = parseLoca(&.src, .loca, .glyf.offset, , ) if != nil { return nil, glyphData{}, false, } } else if .cblc.length != 0 { = true // TODO: parse the CBLC (and CBDT) tables. For now, we return a font // with empty glyphs. .locations = make([]uint32, +1) } if len(.locations) != int(+1) { return nil, glyphData{}, false, errInvalidLocationData } return , , , nil } func ( *Font) ( *Buffer, fixed.Int26_6, rune) (int32, error) { , := .GlyphIndex(, ) if != nil && != ErrNotFound { return 0, } else if == 0 { return 0, nil } // Y axis points down var fixed.Int26_6 , := .LoadGlyph(, , , nil) if != nil { return 0, } for , := range { for , := range .Args { if .Y < { = .Y } } } return int32(), nil } func ( *Font) () (, int32, error) { := fixed.Int26_6(.UnitsPerEm()) var Buffer // sxHeight equal to the top of the unscaled and unhinted glyph bounding box // of the glyph encoded at U+0078 (LATIN SMALL LETTER X). , := .glyphTopOS2(&, , 'x') if != nil { return 0, 0, } // sCapHeight may be set equal to the top of the unscaled and unhinted glyph // bounding box of the glyph encoded at U+0048 (LATIN CAPITAL LETTER H). , := .glyphTopOS2(&, , 'H') if != nil { return 0, 0, } return int32(), int32(), nil } func ( *Font) ( []byte) ( []byte, bool, , int32, error) { // https://docs.microsoft.com/da-dk/typography/opentype/spec/os2 if .os2.length == 0 { // Apple TrueType fonts might omit the OS/2 table. return , false, 0, 0, nil } else if .os2.length < 2 { return nil, false, 0, 0, errInvalidOS2Table } , := .src.u16(, .os2, 0) if != nil { return nil, false, 0, 0, } if < 2 { // "The original TrueType specification had this table at 68 bytes long." // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6OS2.html const = 68 if .os2.length < { return nil, false, 0, 0, errInvalidOS2Table } // Will resolve xHeight and capHeight later, see initOS2VersionBelow2. return , false, 0, 0, nil } const = 96 if .os2.length < { return nil, false, 0, 0, errInvalidOS2Table } , := .src.u16(, .os2, 86) if != nil { return nil, false, 0, 0, } , := .src.u16(, .os2, 88) if != nil { return nil, false, 0, 0, } return , true, int32(int16()), int32(int16()), nil } // PostTable represents an information stored in the PostScript font section. type PostTable struct { // Version of the version tag of the "post" table. Version uint32 // ItalicAngle in counter-clockwise degrees from the vertical. Zero for // upright text, negative for text that leans to the right (forward). ItalicAngle float64 // UnderlinePosition is the suggested distance of the top of the // underline from the baseline (negative values indicate below baseline). UnderlinePosition int16 // Suggested values for the underline thickness. UnderlineThickness int16 // IsFixedPitch indicates that the font is not proportionally spaced // (i.e. monospaced). IsFixedPitch bool } // PostTable returns the information from the font's "post" table. It can // return nil, if the font doesn't have such a table. // // See https://docs.microsoft.com/en-us/typography/opentype/spec/post func ( *Font) () *PostTable { return .cached.post } func ( *Font) ( []byte, int32) ( []byte, *PostTable, error) { // https://www.microsoft.com/typography/otspec/post.htm const = 32 if .post.length < { return nil, nil, errInvalidPostTable } , := .src.u32(, .post, 0) if != nil { return nil, nil, } switch { case 0x10000: // No-op. case 0x20000: if .post.length < +2+2*uint32() { return nil, nil, errInvalidPostTable } case 0x30000: // No-op. default: return nil, nil, errUnsupportedPostTable } , := .src.u32(, .post, 4) if != nil { return nil, nil, } , := .src.u16(, .post, 8) if != nil { return nil, nil, } , := .src.u16(, .post, 10) if != nil { return nil, nil, } , := .src.u32(, .post, 12) if != nil { return nil, nil, } = &PostTable{ Version: , ItalicAngle: float64(int32()) / 0x10000, UnderlinePosition: int16(), UnderlineThickness: int16(), IsFixedPitch: != 0, } return , , nil } // Bounds returns the union of a Font's glyphs' bounds. // // In the returned Rectangle26_6's (x, y) coordinates, the Y axis increases // down. func ( *Font) ( *Buffer, fixed.Int26_6, font.Hinting) (fixed.Rectangle26_6, error) { // The 0, 3, 2, 1 indices are to flip the Y coordinates. OpenType's Y axis // increases up. Go's standard graphics libraries' Y axis increases down. := fixed.Rectangle26_6{ Min: fixed.Point26_6{ X: +scale(fixed.Int26_6(.cached.bounds[0])*, .cached.unitsPerEm), Y: -scale(fixed.Int26_6(.cached.bounds[3])*, .cached.unitsPerEm), }, Max: fixed.Point26_6{ X: +scale(fixed.Int26_6(.cached.bounds[2])*, .cached.unitsPerEm), Y: -scale(fixed.Int26_6(.cached.bounds[1])*, .cached.unitsPerEm), }, } if == font.HintingFull { // Quantize the Min down and Max up to a whole pixel. .Min.X = (.Min.X + 0) &^ 63 .Min.Y = (.Min.Y + 0) &^ 63 .Max.X = (.Max.X + 63) &^ 63 .Max.Y = (.Max.Y + 63) &^ 63 } return , nil } // TODO: API for looking up glyph variants?? For example, some fonts may // provide both slashed and dotted zero glyphs ('0'), or regular and 'old // style' numerals, and users can direct software to choose a variant. type glyphIndexFunc func(f *Font, b *Buffer, r rune) (GlyphIndex, error) // GlyphIndex returns the glyph index for the given rune. // // It returns (0, nil) if there is no glyph for r. // https://www.microsoft.com/typography/OTSPEC/cmap.htm says that "Character // codes that do not correspond to any glyph in the font should be mapped to // glyph index 0. The glyph at this location must be a special glyph // representing a missing character, commonly known as .notdef." func ( *Font) ( *Buffer, rune) (GlyphIndex, error) { return .cached.glyphIndex(, , ) } func ( *Font) ( *Buffer, GlyphIndex) ( []byte, , uint32, error) { := int() if .NumGlyphs() <= { return nil, 0, 0, ErrNotFound } := .cached.glyphData.locations[+0] := .cached.glyphData.locations[+1] if < { return nil, 0, 0, errInvalidGlyphDataLength } if - > maxGlyphDataLength { return nil, 0, 0, errUnsupportedGlyphDataLength } , = .view(&.src, int(), int(-)) return , , - , } // LoadGlyphOptions are the options to the Font.LoadGlyph method. type LoadGlyphOptions struct { // TODO: transform / hinting. } // LoadGlyph returns the vector segments for the x'th glyph. ppem is the number // of pixels in 1 em. // // If b is non-nil, the segments become invalid to use once b is re-used. // // In the returned Segments' (x, y) coordinates, the Y axis increases down. // // It returns ErrNotFound if the glyph index is out of range. It returns // ErrColoredGlyph if the glyph is not a monochrome vector glyph, such as a // colored (bitmap or vector) emoji glyph. func ( *Font) ( *Buffer, GlyphIndex, fixed.Int26_6, *LoadGlyphOptions) (Segments, error) { if == nil { = &Buffer{} } .segments = .segments[:0] if .cached.isColorBitmap { return nil, ErrColoredGlyph } if .cached.isPostScript { , , , := .viewGlyphData(, ) if != nil { return nil, } .psi.type2Charstrings.initialize(, , ) if := .psi.run(psContextType2Charstring, , , ); != nil { return nil, } if !.psi.type2Charstrings.ended { return nil, errInvalidCFFTable } } else if := loadGlyf(, , , 0, 0); != nil { return nil, } // Scale the segments. If we want to support hinting, we'll have to push // the scaling computation into the PostScript / TrueType specific glyph // loading code, such as the appendGlyfSegments body, since TrueType // hinting bytecode works on the scaled glyph vectors. For now, though, // it's simpler to scale as a post-processing step. // // We also flip the Y coordinates. OpenType's Y axis increases up. Go's // standard graphics libraries' Y axis increases down. for := range .segments { := &.segments[].Args for := range { [].X = +scale([].X*, .cached.unitsPerEm) [].Y = -scale([].Y*, .cached.unitsPerEm) } } // TODO: look at opts to transform / hint the Buffer.segments. return .segments, nil } func ( *Font) ( GlyphIndex) (string, error) { if >= numBuiltInPostNames { return "", ErrNotFound } // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html := builtInPostNamesOffsets[+0] := builtInPostNamesOffsets[+1] return builtInPostNamesData[:], nil } func ( *Font) ( *Buffer, GlyphIndex) (string, error) { if == nil { = &Buffer{} } // The wire format for a Version 2 post table is documented at: // https://www.microsoft.com/typography/otspec/post.htm const = 34 , := .view(&.src, int(.post.offset)++2*int(), 2) if != nil { return "", } := u16() if < numBuiltInPostNames { := builtInPostNamesOffsets[+0] := builtInPostNamesOffsets[+1] return builtInPostNamesData[:], nil } // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html // says that "32768 through 65535 are reserved for future use". if > 32767 { return "", errUnsupportedPostTable } -= numBuiltInPostNames // Iterate through the list of Pascal-formatted strings. A linear scan is // clearly O(u), which isn't great (as the obvious loop, calling // Font.GlyphName, to get all of the glyph names in a font has quadratic // complexity), but the wire format doesn't suggest a better alternative. := + 2*.NumGlyphs() , = .view(&.src, int(.post.offset)+, int(.post.length)-) if != nil { return "", } for { if len() == 0 { return "", errInvalidPostTable } := 1 + int([0]) if len() < { return "", errInvalidPostTable } if == 0 { return string([1:]), nil } = [:] -- } } // GlyphName returns the name of the x'th glyph. // // Not every font contains glyph names. If not present, GlyphName will return // ("", nil). // // If present, the glyph name, provided by the font, is assumed to follow the // Adobe Glyph List Specification: // https://github.com/adobe-type-tools/agl-specification/blob/master/README.md // // This is also known as the "Adobe Glyph Naming convention", the "Adobe // document [for] Unicode and Glyph Names" or "PostScript glyph names". // // It returns ErrNotFound if the glyph index is out of range. func ( *Font) ( *Buffer, GlyphIndex) (string, error) { if int() >= .NumGlyphs() { return "", ErrNotFound } if .cached.post == nil { return "", nil } switch .cached.post.Version { case 0x10000: return .glyphNameFormat10() case 0x20000: return .glyphNameFormat20(, ) default: return "", nil } } // GlyphBounds returns the bounding box of the x'th glyph, drawn at a dot equal // to the origin, and that glyph's advance width. ppem is the number of pixels // in 1 em. // // It returns ErrNotFound if the glyph index is out of range. // // The glyph's ascent and descent are equal to -bounds.Min.Y and +bounds.Max.Y. // The glyph's left-side and right-side bearings are equal to bounds.Min.X and // advance-bounds.Max.X. A visual depiction of what these metrics are is at // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png func ( *Font) ( *Buffer, GlyphIndex, fixed.Int26_6, font.Hinting) ( fixed.Rectangle26_6, fixed.Int26_6, error) { if int() >= .NumGlyphs() { return fixed.Rectangle26_6{}, 0, ErrNotFound } if == nil { = &Buffer{} } // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an // optimization, the number of records can be less than the number of // glyphs, in which case the advance width value of the last record applies // to all remaining glyph IDs." := if := GlyphIndex(.cached.numHMetrics - 1); > { = } , := .view(&.src, int(.hmtx.offset)+4*int(), 2) if != nil { return fixed.Rectangle26_6{}, 0, } = fixed.Int26_6(u16()) = scale(*, .cached.unitsPerEm) if == font.HintingFull { // Quantize the fixed.Int26_6 value to the nearest pixel. = ( + 32) &^ 63 } // Ignore the hmtx LSB entries and the glyf bounding boxes. Instead, always // calculate bounds from the segments. OpenType does contain the bounds for // each glyph in the glyf table, but the bounds are not available for // compound glyphs. CFF/PostScript also have no explicit bounds and must be // obtained from the segments. , := .LoadGlyph(, , , &LoadGlyphOptions{ // TODO: pass h, the font.Hinting. }) if != nil { return fixed.Rectangle26_6{}, 0, } return .Bounds(), , nil } // GlyphAdvance returns the advance width for the x'th glyph. ppem is the // number of pixels in 1 em. // // It returns ErrNotFound if the glyph index is out of range. func ( *Font) ( *Buffer, GlyphIndex, fixed.Int26_6, font.Hinting) (fixed.Int26_6, error) { if int() >= .NumGlyphs() { return 0, ErrNotFound } if == nil { = &Buffer{} } // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an // optimization, the number of records can be less than the number of // glyphs, in which case the advance width value of the last record applies // to all remaining glyph IDs." if := GlyphIndex(.cached.numHMetrics - 1); > { = } , := .view(&.src, int(.hmtx.offset)+4*int(), 2) if != nil { return 0, } := fixed.Int26_6(u16()) = scale(*, .cached.unitsPerEm) if == font.HintingFull { // Quantize the fixed.Int26_6 value to the nearest pixel. = ( + 32) &^ 63 } return , nil } // Kern returns the horizontal adjustment for the kerning pair (x0, x1). A // positive kern means to move the glyphs further apart. ppem is the number of // pixels in 1 em. // // It returns ErrNotFound if either glyph index is out of range. func ( *Font) ( *Buffer, , GlyphIndex, fixed.Int26_6, font.Hinting) (fixed.Int26_6, error) { // Use GPOS kern tables if available. if .cached.kernFuncs != nil { for , := range .cached.kernFuncs { , := (, ) if == ErrNotFound { continue } if != nil { return 0, } := fixed.Int26_6() = scale(*, .cached.unitsPerEm) if == font.HintingFull { // Quantize the fixed.Int26_6 value to the nearest pixel. = ( + 32) &^ 63 } return , nil } return 0, ErrNotFound } // Fallback to kern table. // TODO: Convert kern table handling into kernFunc and decide in Parse if // GPOS or kern should be used. if := .NumGlyphs(); int() >= || int() >= { return 0, ErrNotFound } // Not every font has a kern table. If it doesn't, or if that table is // ignored, there's no need to allocate a Buffer. if .cached.kernNumPairs == 0 { return 0, nil } if == nil { = &Buffer{} } := uint32()<<16 | uint32() , := int32(0), .cached.kernNumPairs for < { := ( + ) / 2 // TODO: this view call inside the inner loop can lead to many small // reads instead of fewer larger reads, which can be expensive. We // should be able to do better, although we don't want to make (one) // arbitrarily large read. Perhaps we should round up reads to 4K or 8K // chunks. For reference, Arial.ttf's kern table is 5472 bytes. // Times_New_Roman.ttf's kern table is 5220 bytes. const = 6 , := .view(&.src, int(.cached.kernOffset+*), ) if != nil { return 0, } := u32() if < { = + 1 } else if > { = } else { := fixed.Int26_6(int16(u16([4:]))) = scale(*, .cached.unitsPerEm) if == font.HintingFull { // Quantize the fixed.Int26_6 value to the nearest pixel. = ( + 32) &^ 63 } return , nil } } return 0, nil } // Metrics returns the metrics of this font. func ( *Font) ( *Buffer, fixed.Int26_6, font.Hinting) (font.Metrics, error) { := font.Metrics{ Height: scale(fixed.Int26_6(.cached.ascent-.cached.descent+.cached.lineGap)*, .cached.unitsPerEm), Ascent: +scale(fixed.Int26_6(.cached.ascent)*, .cached.unitsPerEm), Descent: -scale(fixed.Int26_6(.cached.descent)*, .cached.unitsPerEm), XHeight: scale(fixed.Int26_6(.cached.xHeight)*, .cached.unitsPerEm), CapHeight: scale(fixed.Int26_6(.cached.capHeight)*, .cached.unitsPerEm), CaretSlope: image.Point{X: int(.cached.slope[0]), Y: int(.cached.slope[1])}, } if == font.HintingFull { // Quantize up to a whole pixel. .Height = (.Height + 63) &^ 63 .Ascent = (.Ascent + 63) &^ 63 .Descent = (.Descent + 63) &^ 63 .XHeight = (.XHeight + 63) &^ 63 .CapHeight = (.CapHeight + 63) &^ 63 } return , nil } // WriteSourceTo writes the source data (the []byte or io.ReaderAt passed to // Parse or ParseReaderAt) to w. // // It returns the number of bytes written. On success, this is the final offset // of the furthest SFNT table in the source. This may be less than the length // of the []byte or io.ReaderAt originally passed. func ( *Font) ( *Buffer, io.Writer) (int64, error) { if .initialOffset != 0 { // TODO: when extracting a single font (i.e. TTF) out of a font // collection (i.e. TTC), write only the i'th font and not the (i-1) // previous fonts. Subtly, in the file format, table offsets may be // relative to the start of the resource (for dfont collections) or the // start of the file (otherwise). If we were to extract a single font // here, we might need to dynamically patch the table offsets, bearing // in mind that f.src.b is conceptually a 'read-only' slice of bytes. return 0, errUnsupportedCollection } if .src.b != nil { , := .Write(.src.b[:.cached.finalTableOffset]) return int64(), } // We have an io.ReaderAt source, not a []byte. It is tempting to see if // the io.ReaderAt optionally implements the io.WriterTo interface, but we // don't for two reasons: // - We want to write exactly f.cached.finalTableOffset bytes, even if the // underlying 'file' is larger, to be consistent with the []byte flavor. // - We document that "Font methods are safe to call concurrently" and // while io.ReaderAt is stateless (the offset is an argument), the // io.Reader / io.Writer abstractions are stateful (the current position // is a field) and mutable state generally isn't concurrent-safe. if == nil { = &Buffer{} } := int(.cached.finalTableOffset) := int64(0) for := 0; < ; { := - if > 4096 { = 4096 } , := .view(&.src, , ) if != nil { return , } , := .Write() += int64() if != nil { return , } += } return , nil } // Name returns the name value keyed by the given NameID. // // It returns ErrNotFound if there is no value for that key. func ( *Font) ( *Buffer, NameID) (string, error) { if == nil { = &Buffer{} } const , = 6, 12 if .name.length < { return "", errInvalidNameTable } , := .view(&.src, int(.name.offset), ) if != nil { return "", } := u16([2:]) if .name.length < +*uint32() { return "", errInvalidNameTable } := u16([4:]) := false for , := 0, int(); < ; ++ { , := .view(&.src, int(.name.offset)++*, ) if != nil { return "", } if u16([6:]) != uint16() { continue } = true var func([]byte) (string, error) switch u32() { default: continue case pidMacintosh<<16 | psidMacintoshRoman: = stringifyMacintosh case pidWindows<<16 | psidWindowsUCS2: = stringifyUCS2 } := u16([8:]) := u16([10:]) , = .view(&.src, int(.name.offset)+int()+int(), int()) if != nil { return "", } return () } if { return "", errUnsupportedPlatformEncoding } return "", ErrNotFound } func stringifyMacintosh( []byte) (string, error) { for , := range { if >= 0x80 { // b contains some non-ASCII bytes. , := charmap.Macintosh.NewDecoder().Bytes() return string(), nil } } // b contains only ASCII bytes. return string(), nil } func stringifyUCS2( []byte) (string, error) { if len()&1 != 0 { return "", errInvalidUCS2String } := make([]rune, len()/2) for := range { [] = rune(u16()) = [2:] } return string(), nil } // Buffer holds re-usable buffers that can reduce the total memory allocation // of repeated Font method calls. // // See the Font type's documentation comment for more details. type Buffer struct { // buf is a byte buffer for when a Font's source is an io.ReaderAt. buf []byte // segments holds glyph vector path segments. segments Segments // compoundStack holds the components of a TrueType compound glyph. compoundStack [maxCompoundStackSize]struct { glyphIndex GlyphIndex dx, dy int16 hasTransform bool transformXX int16 transformXY int16 transformYX int16 transformYY int16 } // psi is a PostScript interpreter for when the Font is an OpenType/CFF // font. psi psInterpreter } func ( *Buffer) ( *source, , int) ([]byte, error) { , := .view(.buf, , ) if != nil { return nil, } // Only update b.buf if it is safe to re-use buf. if .viewBufferWritable() { .buf = } return , nil } // Segment is a segment of a vector path. type Segment struct { // Op is the operator. Op SegmentOp // Args is up to three (x, y) coordinates. The Y axis increases down. Args [3]fixed.Point26_6 } // SegmentOp is a vector path segment's operator. type SegmentOp uint32 const ( SegmentOpMoveTo SegmentOp = iota SegmentOpLineTo SegmentOpQuadTo SegmentOpCubeTo ) // Segments is a slice of Segment. type Segments []Segment // Bounds returns s' bounding box. It returns an empty rectangle if s is empty. func ( Segments) () ( fixed.Rectangle26_6) { if len() == 0 { return fixed.Rectangle26_6{} } .Min.X = fixed.Int26_6(+(1 << 31) - 1) .Min.Y = fixed.Int26_6(+(1 << 31) - 1) .Max.X = fixed.Int26_6(-(1 << 31) + 0) .Max.Y = fixed.Int26_6(-(1 << 31) + 0) for , := range { := 1 switch .Op { case SegmentOpQuadTo: = 2 case SegmentOpCubeTo: = 3 } for := 0; < ; ++ { if .Max.X < .Args[].X { .Max.X = .Args[].X } if .Min.X > .Args[].X { .Min.X = .Args[].X } if .Max.Y < .Args[].Y { .Max.Y = .Args[].Y } if .Min.Y > .Args[].Y { .Min.Y = .Args[].Y } } } return } // translateArgs applies a translation to args. func translateArgs( *[3]fixed.Point26_6, , fixed.Int26_6) { [0].X += [0].Y += [1].X += [1].Y += [2].X += [2].Y += } // transformArgs applies an affine transformation to args. The t?? arguments // are 2.14 fixed point values. func transformArgs( *[3]fixed.Point26_6, , , , int16, , fixed.Int26_6) { [0] = tform(, , , , , , [0]) [1] = tform(, , , , , , [1]) [2] = tform(, , , , , , [2]) } func tform(, , , int16, , fixed.Int26_6, fixed.Point26_6) fixed.Point26_6 { const = 1 << 13 return fixed.Point26_6{ X: + fixed.Int26_6((int64(.X)*int64()+)>>14) + fixed.Int26_6((int64(.Y)*int64()+)>>14), Y: + fixed.Int26_6((int64(.X)*int64()+)>>14) + fixed.Int26_6((int64(.Y)*int64()+)>>14), } }