// 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.txtimport ()// 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.typeGlyphIndexuint16// NameID identifies a name table entry.//// See the "Name IDs" section of// https://www.microsoft.com/typography/otspec/name.htmtypeNameIDuint16const (NameIDCopyrightNameID = 0NameIDFamilyNameID = 1NameIDSubfamilyNameID = 2NameIDUniqueIdentifierNameID = 3NameIDFullNameID = 4NameIDVersionNameID = 5NameIDPostScriptNameID = 6NameIDTrademarkNameID = 7NameIDManufacturerNameID = 8NameIDDesignerNameID = 9NameIDDescriptionNameID = 10NameIDVendorURLNameID = 11NameIDDesignerURLNameID = 12NameIDLicenseNameID = 13NameIDLicenseURLNameID = 14NameIDTypographicFamilyNameID = 16NameIDTypographicSubfamilyNameID = 17NameIDCompatibleFullNameID = 18NameIDSampleTextNameID = 19NameIDPostScriptCIDNameID = 20NameIDWWSFamilyNameID = 21NameIDWWSSubfamilyNameID = 22NameIDLightBackgroundPaletteNameID = 23NameIDDarkBackgroundPaletteNameID = 24NameIDVariationsPostScriptPrefixNameID = 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).typeUnitsint32// 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.returnuint16([0])<<8 | uint16([1])<<0}func u32( []byte) uint32 { _ = [3] // Bounds check hint to compiler.returnuint32([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) {if0 > || > + {returnnil, errInvalidBounds }// Try reading from the []byte.if .b != nil {if + > len(.b) {returnnil, 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()); != {returnnil, }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) {if0 > || > + {returnnil, 0, errInvalidBounds }if0 > || +1 >= {returnnil, 0, errInvalidBounds }// read static part which contains our count , := .view(, , )if != nil {returnnil, 0, } := int(u16([:])) , = .view(, , +*)if != nil {returnnil, 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) {return0, errInvalidBounds } , := .view(, int(.offset)+, 2)if != nil {return0, }returnu16(), 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) {return0, errInvalidBounds } , := .view(, int(.offset)+, 4)if != nil {return0, }returnu32(), 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 {returnnil, }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 {returnnil, }return , nil}// Collection is a collection of one or more fonts.//// All of the Collection methods are safe to call concurrently.typeCollectionstruct { src source offsets []uint32 isDfont bool}// NumFonts returns the number of fonts in the collection.func ( *Collection) () int { returnlen(.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.switchu32() {default:returnerrInvalidFontCollectioncasedfontResourceDataOffset:return .parseDfont(, u32([4:]), u32([12:]))case0x00010000, 0x4f54544f, 0x74727565: // 0x10000, "OTTO", "true"// Try parsing it as a single font instead of a collection. .offsets = []uint32{0}case0x74746366: // "ttcf". := u32([8:])if == 0 || > maxNumFonts {returnerrUnsupportedNumberOfFonts } , = .src.view(nil, 12, int(4*))if != nil {return } .offsets = make([]uint32, )for := range .offsets { := u32([4*:])if > maxTableOffset {returnerrUnsupportedTableOffsetLength } .offsets[] = } }returnnil}// 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 {returnerrUnsupportedTableOffsetLength }const = 28if < {returnerrInvalidDfont } , := .src.view(, int(+24), 2)if != nil {return } := int(int16(u16()))if < || < uint32()+2 {returnerrInvalidDfont } , = .src.view(, int()+, 2)if != nil {return } := int(int16(u16()))const = 8if < 0 || *uint32() > -uint32()-2 {returnerrInvalidDfont } , = .src.view(, int()++2, *)if != nil {return } , := 0, 0for := 0; < ; ++ {ifu32([*:]) != 0x73666e74 { // "sfnt".continue } = int(int16(u16([*+4:])))if < 0 {returnerrInvalidDfont }// 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 {returnerrInvalidDfont }break }if == 0 {returnerrInvalidDfont }if > maxNumFonts {returnerrUnsupportedNumberOfFonts }const = 12if , := uint32(+), *uint32(); > || > - {returnerrInvalidDfont } 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 + 4if > maxTableOffset {returnerrUnsupportedTableOffsetLength } .offsets[] = } .isDfont = truereturnnil}// Font returns the i'th font in the collection.func ( *Collection) ( int) (*Font, error) {if < 0 || len(.offsets) <= {returnnil, ErrNotFound } := &Font{src: .src}if := .initialize(int(.offsets[]), .isDfont); != nil {returnnil, }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 {returnnil, }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 {returnnil, }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.typeFontstruct { 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 { returnlen(.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() {returnerrInvalidSourceData } , , , := .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 = }returnnil}func ( *Font) ( int, bool) ( []byte, int32, bool, error) { .initialOffset = int32()ifint(.initialOffset) != {returnnil, 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 {returnnil, 0, false, }// When updating the cases in this switch statement, also update the // Collection.initialize method.switchu32() {default:returnnil, 0, false, errInvalidFontcasedfontResourceDataOffset:returnnil, 0, false, errInvalidSingleFontcase0x00010000:// No-op.case0x4f54544f: // "OTTO". = truecase0x74727565: // "true"// No-op.case0x74746366: // "ttcf".returnnil, 0, false, errInvalidSingleFont } := int(u16([4:]))if > maxNumTables {returnnil, 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 {returnnil, 0, false, }for , , := , true, uint32(0); len() > 0; = [16:] { := u32()if { = false } elseif <= {returnnil, 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 < {returnnil, 0, false, errUnsupportedTableOffsetLength } }if > maxTableOffset || > maxTableLength {returnnil, 0, false, errUnsupportedTableOffsetLength }// We ignore the checksums, but "all tables must begin on four byte // boundries [sic]".if &3 != 0 {returnnil, 0, false, errInvalidTableOffset }if < int32(+) { = int32( + ) }// Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.switch {case0x43424c43: .cblc = table{, }case0x43464620: .cff = table{, }case0x4f532f32: .os2 = table{, }case0x636d6170: .cmap = table{, }case0x676c7966: .glyf = table{, }case0x47504f53: .gpos = table{, }case0x68656164: .head = table{, }case0x68686561: .hhea = table{, }case0x686d7478: .hmtx = table{, }case0x6b65726e: .kern = table{, }case0x6c6f6361: .loca = table{, }case0x6d617870: .maxp = table{, }case0x6e616d65: .name = table{, }case0x706f7374: .post = table{, } } }if (.src.b != nil) && (int() > len(.src.b)) {returnnil, 0, false, errInvalidSourceData }return , , , nil}func ( *Font) ( []byte) ( []byte, glyphIndexFunc, error) {// https://www.microsoft.com/typography/OTSPEC/cmap.htmconst , = 4, 8if .cmap.length < {returnnil, nil, errInvalidCmapTable } , := .src.u16(, .cmap, 2)if != nil {returnnil, nil, } := int()if .cmap.length < +*uint32() {returnnil, nil, errInvalidCmapTable }var (intuint32uint32uint16 )// 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 {returnnil, nil, } := u16() := u16([2:]) := platformEncodingWidth(, )if <= {continue } := u32([4:])if > .cmap.length-4 {returnnil, nil, errInvalidCmapTable } , = .src.view(, int(.cmap.offset+), 4)if != nil {returnnil, nil, } := u16()if !supportedCmapFormat(, , ) {continue } := uint32(u16([2:])) = = = = }if == 0 {returnnil, nil, errUnsupportedCmapEncodings }return .makeCachedGlyphIndex(, , , )}func ( *Font) ( []byte) ( []byte, [4]int16, bool, Units, error) {// https://www.microsoft.com/typography/otspec/head.htmif .head.length != 54 {returnnil, [4]int16{}, false, 0, errInvalidHeadTable } , := .src.u16(, .head, 18)if != nil {returnnil, [4]int16{}, false, 0, }if == 0 {returnnil, [4]int16{}, false, 0, errInvalidHeadTable } = Units()for := range { , := .src.u16(, .head, 36+2*)if != nil {returnnil, [4]int16{}, false, 0, } [] = int16() } , = .src.u16(, .head, 50)if != nil {returnnil, [4]int16{}, false, 0, } = != 0return , , , , nil}func ( *Font) ( []byte, int32) ( []byte, , , , , , int32, error) {// https://www.microsoft.com/typography/OTSPEC/hhea.htmif .hhea.length != 36 {returnnil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable } , := .src.u16(, .hhea, 34)if != nil {returnnil, 0, 0, 0, 0, 0, 0, }ifint32() > || == 0 {returnnil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable } , := .src.u16(, .hhea, 4)if != nil {returnnil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 6)if != nil {returnnil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 8)if != nil {returnnil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 20)if != nil {returnnil, 0, 0, 0, 0, 0, 0, } , := .src.u16(, .hhea, 18)if != nil {returnnil, 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/28379if .hmtx.length != uint32(4*) && .hmtx.length != uint32(4*+2*(-)) {returnnil, errInvalidHmtxTable }return , nil}func ( *Font) ( []byte) ( []byte, , int32, error) {// https://www.microsoft.com/typography/otspec/kern.htmif .kern.length == 0 {return , 0, 0, nil }const = 4if .kern.length < {returnnil, 0, 0, errInvalidKernTable } , = .src.view(, int(.kern.offset), )if != nil {returnnil, 0, 0, } := int(.kern.offset) + := int(.kern.length) - switch := u16(); {case0:if := int(u16([2:])); == 0 {return , 0, 0, nil } elseif > 1 {// TODO: support multiple subtables. For now, fall through and use // only the first one. }return .parseKernVersion0(, , )case1:if [2] != 0 || [3] != 0 {returnnil, 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 }returnnil, 0, 0, errUnsupportedKernTable}func ( *Font) ( []byte, , int) ( []byte, , int32, error) {const = 6if < {returnnil, 0, 0, errInvalidKernTable } , = .src.view(, , )if != nil {returnnil, 0, 0, }if := u16(); != 0 {returnnil, 0, 0, errUnsupportedKernTable } := u16([2:])ifint() < || < int() {returnnil, 0, 0, errInvalidKernTable }if := [5]; != 0x01 {// We only support horizontal kerning.returnnil, 0, 0, errUnsupportedKernTable } += -= -= switch := [4]; {case0:return .parseKernFormat0(, , , )case2:// 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. }returnnil, 0, 0, errUnsupportedKernTable}func ( *Font) ( []byte, , int, uint16) ( []byte, , int32, error) {const , = 8, 6if < {returnnil, 0, 0, errInvalidKernTable } , = .src.view(, , )if != nil {returnnil, 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()) {returnnil, 0, 0, errInvalidKernTable }return , , int32() + , nil}func ( *Font) ( []byte, bool) ( []byte, int32, error) {// https://www.microsoft.com/typography/otspec/maxp.htmif {if .maxp.length != 6 {returnnil, 0, errInvalidMaxpTable } } else {if .maxp.length != 32 {returnnil, 0, errInvalidMaxpTable } } , := .src.u16(, .maxp, 4)if != nil {returnnil, 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 {returnnil, glyphData{}, false, } } elseif .loca.length != 0 { .locations, = parseLoca(&.src, .loca, .glyf.offset, , )if != nil {returnnil, glyphData{}, false, } } elseif .cblc.length != 0 { = true// TODO: parse the CBLC (and CBDT) tables. For now, we return a font // with empty glyphs. .locations = make([]uint32, +1) }iflen(.locations) != int(+1) {returnnil, glyphData{}, false, errInvalidLocationData }return , , , nil}func ( *Font) ( *Buffer, fixed.Int26_6, rune) (int32, error) { , := .GlyphIndex(, )if != nil && != ErrNotFound {return0, } elseif == 0 {return0, nil }// Y axis points downvarfixed.Int26_6 , := .LoadGlyph(, , , nil)if != nil {return0, }for , := range {for , := range .Args {if .Y < { = .Y } } }returnint32(), nil}func ( *Font) () (, int32, error) { := fixed.Int26_6(.UnitsPerEm())varBuffer// 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 {return0, 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 {return0, 0, }returnint32(), int32(), nil}func ( *Font) ( []byte) ( []byte, bool, , int32, error) {// https://docs.microsoft.com/da-dk/typography/opentype/spec/os2if .os2.length == 0 {// Apple TrueType fonts might omit the OS/2 table.return , false, 0, 0, nil } elseif .os2.length < 2 {returnnil, false, 0, 0, errInvalidOS2Table } , := .src.u16(, .os2, 0)if != nil {returnnil, 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.htmlconst = 68if .os2.length < {returnnil, false, 0, 0, errInvalidOS2Table }// Will resolve xHeight and capHeight later, see initOS2VersionBelow2.return , false, 0, 0, nil }const = 96if .os2.length < {returnnil, false, 0, 0, errInvalidOS2Table } , := .src.u16(, .os2, 86)if != nil {returnnil, false, 0, 0, } , := .src.u16(, .os2, 88)if != nil {returnnil, false, 0, 0, }return , true, int32(int16()), int32(int16()), nil}// PostTable represents an information stored in the PostScript font section.typePostTablestruct {// 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/postfunc ( *Font) () *PostTable {return .cached.post}func ( *Font) ( []byte, int32) ( []byte, *PostTable, error) {// https://www.microsoft.com/typography/otspec/post.htmconst = 32if .post.length < {returnnil, nil, errInvalidPostTable } , := .src.u32(, .post, 0)if != nil {returnnil, nil, }switch {case0x10000:// No-op.case0x20000:if .post.length < +2+2*uint32() {returnnil, nil, errInvalidPostTable }case0x30000:// No-op.default:returnnil, nil, errUnsupportedPostTable } , := .src.u32(, .post, 4)if != nil {returnnil, nil, } , := .src.u16(, .post, 8)if != nil {returnnil, nil, } , := .src.u16(, .post, 10)if != nil {returnnil, nil, } , := .src.u32(, .post, 12)if != nil {returnnil, 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() <= {returnnil, 0, 0, ErrNotFound } := .cached.glyphData.locations[+0] := .cached.glyphData.locations[+1]if < {returnnil, 0, 0, errInvalidGlyphDataLength }if - > maxGlyphDataLength {returnnil, 0, 0, errUnsupportedGlyphDataLength } , = .view(&.src, int(), int(-))return , , - , }// LoadGlyphOptions are the options to the Font.LoadGlyph method.typeLoadGlyphOptionsstruct {// 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 {returnnil, ErrColoredGlyph }if .cached.isPostScript { , , , := .viewGlyphData(, )if != nil {returnnil, } .psi.type2Charstrings.initialize(, , )if := .psi.run(psContextType2Charstring, , , ); != nil {returnnil, }if !.psi.type2Charstrings.ended {returnnil, errInvalidCFFTable } } elseif := loadGlyf(, , , 0, 0); != nil {returnnil, }// 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[].Argsfor := 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]returnbuiltInPostNamesData[:], 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.htmconst = 34 , := .view(&.src, int(.post.offset)++2*int(), 2)if != nil {return"", } := u16()if < numBuiltInPostNames { := builtInPostNamesOffsets[+0] := builtInPostNamesOffsets[+1]returnbuiltInPostNamesData[:], 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 {iflen() == 0 {return"", errInvalidPostTable } := 1 + int([0])iflen() < {return"", errInvalidPostTable }if == 0 {returnstring([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) {ifint() >= .NumGlyphs() {return"", ErrNotFound }if .cached.post == nil {return"", nil }switch .cached.post.Version {case0x10000:return .glyphNameFormat10()case0x20000: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.pngfunc ( *Font) ( *Buffer, GlyphIndex, fixed.Int26_6, font.Hinting) ( fixed.Rectangle26_6, fixed.Int26_6, error) {ifint() >= .NumGlyphs() {returnfixed.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 {returnfixed.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 {returnfixed.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) {ifint() >= .NumGlyphs() {return0, 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 {return0, } := 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 {return0, } := fixed.Int26_6() = scale(*, .cached.unitsPerEm)if == font.HintingFull {// Quantize the fixed.Int26_6 value to the nearest pixel. = ( + 32) &^ 63 }return , nil }return0, 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() >= {return0, 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 {return0, nil }if == nil { = &Buffer{} } := uint32()<<16 | uint32() , := int32(0), .cached.kernNumPairsfor < { := ( + ) / 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 {return0, } := u32()if < { = + 1 } elseif > { = } 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 } }return0, 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.return0, errUnsupportedCollection }if .src.b != nil { , := .Write(.src.b[:.cached.finalTableOffset])returnint64(), }// 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, 12if .name.length < {return"", errInvalidNameTable } , := .view(&.src, int(.name.offset), )if != nil {return"", } := u16([2:])if .name.length < +*uint32() {return"", errInvalidNameTable } := u16([4:]) := falsefor , := 0, int(); < ; ++ { , := .view(&.src, int(.name.offset)++*, )if != nil {return"", }ifu16([6:]) != uint16() {continue } = truevarfunc([]byte) (string, error)switchu32() {default:continuecasepidMacintosh<<16 | psidMacintoshRoman: = stringifyMacintoshcasepidWindows<<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()returnstring(), nil } }// b contains only ASCII bytes.returnstring(), nil}func stringifyUCS2( []byte) (string, error) {iflen()&1 != 0 {return"", errInvalidUCS2String } := make([]rune, len()/2)for := range { [] = rune(u16()) = [2:] }returnstring(), 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.typeBufferstruct {// 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 {returnnil, }// 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.typeSegmentstruct {// 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.typeSegmentOpuint32const (SegmentOpMoveToSegmentOp = iotaSegmentOpLineToSegmentOpQuadToSegmentOpCubeTo)// Segments is a slice of Segment.typeSegments []Segment// Bounds returns s' bounding box. It returns an empty rectangle if s is empty.func ( Segments) () ( fixed.Rectangle26_6) {iflen() == 0 {returnfixed.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 { := 1switch .Op {caseSegmentOpQuadTo: = 2caseSegmentOpCubeTo: = 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 << 13returnfixed.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), }}
The pages are generated with Goldsv0.8.2. (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.