// 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 SFNT font file formats, including // TrueType and OpenType. 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 ( "errors" "image" "io" "golang.org/x/image/font" "golang.org/x/image/math/fixed" "golang.org/x/text/encoding/charmap" ) // 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") 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") errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings") errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph") errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length") errUnsupportedKernTable = errors.New("sfnt: unsupported kern table") errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding") 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") 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 = 1 NameIDSubfamily = 2 NameIDUniqueIdentifier = 3 NameIDFull = 4 NameIDVersion = 5 NameIDPostScript = 6 NameIDTrademark = 7 NameIDManufacturer = 8 NameIDDesigner = 9 NameIDDescription = 10 NameIDVendorURL = 11 NameIDDesignerURL = 12 NameIDLicense = 13 NameIDLicenseURL = 14 NameIDTypographicFamily = 16 NameIDTypographicSubfamily = 17 NameIDCompatibleFull = 18 NameIDSampleText = 19 NameIDPostScriptCID = 20 NameIDWWSFamily = 21 NameIDWWSSubfamily = 22 NameIDLightBackgroundPalette = 23 NameIDDarkBackgroundPalette = 24 NameIDVariationsPostScriptPrefix = 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(x fixed.Int26_6, unitsPerEm Units) fixed.Int26_6 { if x >= 0 { x += fixed.Int26_6(unitsPerEm) / 2 } else { x -= fixed.Int26_6(unitsPerEm) / 2 } return x / fixed.Int26_6(unitsPerEm) } func u16(b []byte) uint16 { _ = b[1] // Bounds check hint to compiler. return uint16(b[0])<<8 | uint16(b[1])<<0 } func u32(b []byte) uint32 { _ = b[3] // Bounds check hint to compiler. return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[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 (s *source) valid() bool { return (s.b == nil) != (s.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 (s *source) viewBufferWritable() bool { return s.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 (s *source) view(buf []byte, offset, length int) ([]byte, error) { if 0 > offset || offset > offset+length { return nil, errInvalidBounds } // Try reading from the []byte. if s.b != nil { if offset+length > len(s.b) { return nil, errInvalidBounds } return s.b[offset : offset+length], nil } // Read from the io.ReaderAt. if length <= cap(buf) { buf = buf[:length] } 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. n := length n += 1023 n &^= 1023 buf = make([]byte, length, n) } if n, err := s.r.ReadAt(buf, int64(offset)); n != length { return nil, err } return buf, 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 (s *source) u16(buf []byte, t table, i int) (uint16, error) { if i < 0 || uint(t.length) < uint(i+2) { return 0, errInvalidBounds } buf, err := s.view(buf, int(t.offset)+i, 2) if err != nil { return 0, err } return u16(buf), 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 (s *source) u32(buf []byte, t table, i int) (uint32, error) { if i < 0 || uint(t.length) < uint(i+4) { return 0, errInvalidBounds } buf, err := s.view(buf, int(t.offset)+i, 4) if err != nil { return 0, err } return u32(buf), 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. func ParseCollection(src []byte) (*Collection, error) { c := &Collection{src: source{b: src}} if err := c.initialize(); err != nil { return nil, err } return c, 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. func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) { c := &Collection{src: source{r: src}} if err := c.initialize(); err != nil { return nil, err } return c, 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 (c *Collection) NumFonts() int { return len(c.offsets) } func (c *Collection) initialize() 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). buf, err := c.src.view(nil, 0, 16) if err != nil { return err } // These cases match the switch statement in Font.initializeTables. switch u32(buf) { default: return errInvalidFontCollection case dfontResourceDataOffset: return c.parseDfont(buf, u32(buf[4:]), u32(buf[12:])) case 0x00010000, 0x4f54544f, 0x74727565: // 0x10000, "OTTO", "true" // Try parsing it as a single font instead of a collection. c.offsets = []uint32{0} case 0x74746366: // "ttcf". numFonts := u32(buf[8:]) if numFonts == 0 || numFonts > maxNumFonts { return errUnsupportedNumberOfFonts } buf, err = c.src.view(nil, 12, int(4*numFonts)) if err != nil { return err } c.offsets = make([]uint32, numFonts) for i := range c.offsets { o := u32(buf[4*i:]) if o > maxTableOffset { return errUnsupportedTableOffsetLength } c.offsets[i] = o } } 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 (c *Collection) parseDfont(buf []byte, resourceMapOffset, resourceMapLength uint32) error { if resourceMapOffset > maxTableOffset || resourceMapLength > maxTableLength { return errUnsupportedTableOffsetLength } const headerSize = 28 if resourceMapLength < headerSize { return errInvalidDfont } buf, err := c.src.view(buf, int(resourceMapOffset+24), 2) if err != nil { return err } typeListOffset := int(int16(u16(buf))) if typeListOffset < headerSize || resourceMapLength < uint32(typeListOffset)+2 { return errInvalidDfont } buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset, 2) if err != nil { return err } typeCount := int(int16(u16(buf))) const tSize = 8 if typeCount < 0 || tSize*uint32(typeCount) > resourceMapLength-uint32(typeListOffset)-2 { return errInvalidDfont } buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset+2, tSize*typeCount) if err != nil { return err } resourceCount, resourceListOffset := 0, 0 for i := 0; i < typeCount; i++ { if u32(buf[tSize*i:]) != 0x73666e74 { // "sfnt". continue } resourceCount = int(int16(u16(buf[tSize*i+4:]))) if resourceCount < 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." resourceCount++ resourceListOffset = int(int16(u16(buf[tSize*i+6:]))) if resourceListOffset < 0 { return errInvalidDfont } break } if resourceCount == 0 { return errInvalidDfont } if resourceCount > maxNumFonts { return errUnsupportedNumberOfFonts } const rSize = 12 if o, n := uint32(typeListOffset+resourceListOffset), rSize*uint32(resourceCount); o > resourceMapLength || n > resourceMapLength-o { return errInvalidDfont } else { buf, err = c.src.view(buf, int(resourceMapOffset+o), int(n)) if err != nil { return err } } c.offsets = make([]uint32, resourceCount) for i := range c.offsets { o := 0xffffff & u32(buf[rSize*i+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. o += dfontResourceDataOffset + 4 if o > maxTableOffset { return errUnsupportedTableOffsetLength } c.offsets[i] = o } c.isDfont = true return nil } // Font returns the i'th font in the collection. func (c *Collection) Font(i int) (*Font, error) { if i < 0 || len(c.offsets) <= i { return nil, ErrNotFound } f := &Font{src: c.src} if err := f.initialize(int(c.offsets[i]), c.isDfont); err != nil { return nil, err } return f, nil } // Parse parses an SFNT font, such as TTF or OTF data, from a []byte data // source. func Parse(src []byte) (*Font, error) { f := &Font{src: source{b: src}} if err := f.initialize(0, false); err != nil { return nil, err } return f, nil } // ParseReaderAt parses an SFNT font, such as TTF or OTF data, from an // io.ReaderAt data source. func ParseReaderAt(src io.ReaderAt) (*Font, error) { f := &Font{src: source{r: src}} if err := f.initialize(0, false); err != nil { return nil, err } return f, 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 // 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, gpos, gsub, jstf, math? // https://www.microsoft.com/typography/otspec/otff.htm#otttables // "Other OpenType Tables". // // TODO: hdmx, vmtx? Others? kern table cached struct { ascent int32 capHeight 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 lineGap int32 numHMetrics int32 post *PostTable slope [2]int32 unitsPerEm Units xHeight int32 } } // NumGlyphs returns the number of glyphs in f. func (f *Font) NumGlyphs() int { return len(f.cached.glyphData.locations) - 1 } // UnitsPerEm returns the number of units per em for f. func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm } func (f *Font) initialize(offset int, isDfont bool) error { if !f.src.valid() { return errInvalidSourceData } buf, isPostScript, err := f.initializeTables(offset, isDfont) if err != nil { return err } // 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. buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf) if err != nil { return err } buf, numGlyphs, err := f.parseMaxp(buf, isPostScript) if err != nil { return err } buf, glyphData, isColorBitmap, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript) if err != nil { return err } buf, glyphIndex, err := f.parseCmap(buf) if err != nil { return err } buf, kernNumPairs, kernOffset, err := f.parseKern(buf) if err != nil { return err } buf, ascent, descent, lineGap, run, rise, numHMetrics, err := f.parseHhea(buf, numGlyphs) if err != nil { return err } buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics) if err != nil { return err } buf, hasXHeightCapHeight, xHeight, capHeight, err := f.parseOS2(buf) if err != nil { return err } buf, post, err := f.parsePost(buf, numGlyphs) if err != nil { return err } f.cached.ascent = ascent f.cached.capHeight = capHeight f.cached.glyphData = glyphData f.cached.glyphIndex = glyphIndex f.cached.bounds = bounds f.cached.descent = descent f.cached.indexToLocFormat = indexToLocFormat f.cached.isColorBitmap = isColorBitmap f.cached.isPostScript = isPostScript f.cached.kernNumPairs = kernNumPairs f.cached.kernOffset = kernOffset f.cached.lineGap = lineGap f.cached.numHMetrics = numHMetrics f.cached.post = post f.cached.slope = [2]int32{run, rise} f.cached.unitsPerEm = unitsPerEm f.cached.xHeight = xHeight if !hasXHeightCapHeight { xh, ch, err := f.initOS2Version1() if err != nil { return err } f.cached.xHeight = xh f.cached.capHeight = ch } return nil } func (f *Font) initializeTables(offset int, isDfont bool) (buf1 []byte, isPostScript bool, err error) { // 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. buf, err := f.src.view(nil, offset, 12) if err != nil { return nil, false, err } // When updating the cases in this switch statement, also update the // Collection.initialize method. switch u32(buf) { default: return nil, false, errInvalidFont case dfontResourceDataOffset: return nil, false, errInvalidSingleFont case 0x00010000: // No-op. case 0x4f54544f: // "OTTO". isPostScript = true case 0x74727565: // "true" // No-op. case 0x74746366: // "ttcf". return nil, false, errInvalidSingleFont } numTables := int(u16(buf[4:])) if numTables > maxNumTables { return nil, false, errUnsupportedNumberOfTables } // "The Offset Table is followed immediately by the Table Record entries... // sorted in ascending order by tag", 16 bytes each. buf, err = f.src.view(buf, offset+12, 16*numTables) if err != nil { return nil, false, err } for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] { tag := u32(b) if first { first = false } else if tag <= prevTag { return nil, false, errInvalidTableTagOrder } prevTag = tag o, n := u32(b[8:12]), u32(b[12:16]) // For dfont files, the offset is relative to the resource, not the // file. if isDfont { origO := o o += uint32(offset) if o < origO { return nil, false, errUnsupportedTableOffsetLength } } if o > maxTableOffset || n > maxTableLength { return nil, false, errUnsupportedTableOffsetLength } // We ignore the checksums, but "all tables must begin on four byte // boundries [sic]". if o&3 != 0 { return nil, false, errInvalidTableOffset } // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32. switch tag { case 0x43424c43: f.cblc = table{o, n} case 0x43464620: f.cff = table{o, n} case 0x4f532f32: f.os2 = table{o, n} case 0x636d6170: f.cmap = table{o, n} case 0x676c7966: f.glyf = table{o, n} case 0x68656164: f.head = table{o, n} case 0x68686561: f.hhea = table{o, n} case 0x686d7478: f.hmtx = table{o, n} case 0x6b65726e: f.kern = table{o, n} case 0x6c6f6361: f.loca = table{o, n} case 0x6d617870: f.maxp = table{o, n} case 0x6e616d65: f.name = table{o, n} case 0x706f7374: f.post = table{o, n} } } return buf, isPostScript, nil } func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, err error) { // https://www.microsoft.com/typography/OTSPEC/cmap.htm const headerSize, entrySize = 4, 8 if f.cmap.length < headerSize { return nil, nil, errInvalidCmapTable } u, err := f.src.u16(buf, f.cmap, 2) if err != nil { return nil, nil, err } numSubtables := int(u) if f.cmap.length < headerSize+entrySize*uint32(numSubtables) { return nil, nil, errInvalidCmapTable } var ( bestWidth int bestOffset uint32 bestLength uint32 bestFormat uint16 ) // Scan all of the subtables, picking the widest supported one. See the // platformEncodingWidth comment for more discussion of width. for i := 0; i < numSubtables; i++ { buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize) if err != nil { return nil, nil, err } pid := u16(buf) psid := u16(buf[2:]) width := platformEncodingWidth(pid, psid) if width <= bestWidth { continue } offset := u32(buf[4:]) if offset > f.cmap.length-4 { return nil, nil, errInvalidCmapTable } buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4) if err != nil { return nil, nil, err } format := u16(buf) if !supportedCmapFormat(format, pid, psid) { continue } length := uint32(u16(buf[2:])) bestWidth = width bestOffset = offset bestLength = length bestFormat = format } if bestWidth == 0 { return nil, nil, errUnsupportedCmapEncodings } return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat) } func (f *Font) parseHead(buf []byte) (buf1 []byte, bounds [4]int16, indexToLocFormat bool, unitsPerEm Units, err error) { // https://www.microsoft.com/typography/otspec/head.htm if f.head.length != 54 { return nil, [4]int16{}, false, 0, errInvalidHeadTable } u, err := f.src.u16(buf, f.head, 18) if err != nil { return nil, [4]int16{}, false, 0, err } if u == 0 { return nil, [4]int16{}, false, 0, errInvalidHeadTable } unitsPerEm = Units(u) for i := range bounds { u, err := f.src.u16(buf, f.head, 36+2*i) if err != nil { return nil, [4]int16{}, false, 0, err } bounds[i] = int16(u) } u, err = f.src.u16(buf, f.head, 50) if err != nil { return nil, [4]int16{}, false, 0, err } indexToLocFormat = u != 0 return buf, bounds, indexToLocFormat, unitsPerEm, nil } func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, ascent, descent, lineGap, run, rise, numHMetrics int32, err error) { // https://www.microsoft.com/typography/OTSPEC/hhea.htm if f.hhea.length != 36 { return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable } u, err := f.src.u16(buf, f.hhea, 34) if err != nil { return nil, 0, 0, 0, 0, 0, 0, err } if int32(u) > numGlyphs || u == 0 { return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable } a, err := f.src.u16(buf, f.hhea, 4) if err != nil { return nil, 0, 0, 0, 0, 0, 0, err } d, err := f.src.u16(buf, f.hhea, 6) if err != nil { return nil, 0, 0, 0, 0, 0, 0, err } l, err := f.src.u16(buf, f.hhea, 8) if err != nil { return nil, 0, 0, 0, 0, 0, 0, err } ru, err := f.src.u16(buf, f.hhea, 20) if err != nil { return nil, 0, 0, 0, 0, 0, 0, err } ri, err := f.src.u16(buf, f.hhea, 18) if err != nil { return nil, 0, 0, 0, 0, 0, 0, err } return buf, int32(int16(a)), int32(int16(d)), int32(int16(l)), int32(int16(ru)), int32(int16(ri)), int32(u), nil } func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err 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 f.hmtx.length != uint32(4*numHMetrics) && f.hmtx.length != uint32(4*numHMetrics+2*(numGlyphs-numHMetrics)) { return nil, errInvalidHmtxTable } return buf, nil } func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) { // https://www.microsoft.com/typography/otspec/kern.htm if f.kern.length == 0 { return buf, 0, 0, nil } const headerSize = 4 if f.kern.length < headerSize { return nil, 0, 0, errInvalidKernTable } buf, err = f.src.view(buf, int(f.kern.offset), headerSize) if err != nil { return nil, 0, 0, err } offset := int(f.kern.offset) + headerSize length := int(f.kern.length) - headerSize switch version := u16(buf); version { case 0: if numTables := int(u16(buf[2:])); numTables == 0 { return buf, 0, 0, nil } else if numTables > 1 { // TODO: support multiple subtables. For now, fall through and use // only the first one. } return f.parseKernVersion0(buf, offset, length) case 1: if buf[2] != 0 || buf[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 buf, 0, 0, nil } return nil, 0, 0, errUnsupportedKernTable } func (f *Font) parseKernVersion0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) { const headerSize = 6 if length < headerSize { return nil, 0, 0, errInvalidKernTable } buf, err = f.src.view(buf, offset, headerSize) if err != nil { return nil, 0, 0, err } if version := u16(buf); version != 0 { return nil, 0, 0, errUnsupportedKernTable } subtableLength := int(u16(buf[2:])) if subtableLength < headerSize || length < subtableLength { return nil, 0, 0, errInvalidKernTable } if coverageBits := buf[5]; coverageBits != 0x01 { // We only support horizontal kerning. return nil, 0, 0, errUnsupportedKernTable } offset += headerSize length -= headerSize subtableLength -= headerSize switch format := buf[4]; format { case 0: return f.parseKernFormat0(buf, offset, subtableLength) 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 (f *Font) parseKernFormat0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) { const headerSize, entrySize = 8, 6 if length < headerSize { return nil, 0, 0, errInvalidKernTable } buf, err = f.src.view(buf, offset, headerSize) if err != nil { return nil, 0, 0, err } kernNumPairs = int32(u16(buf)) if length != headerSize+entrySize*int(kernNumPairs) { return nil, 0, 0, errInvalidKernTable } return buf, kernNumPairs, int32(offset) + headerSize, nil } func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int32, err error) { // https://www.microsoft.com/typography/otspec/maxp.htm if isPostScript { if f.maxp.length != 6 { return nil, 0, errInvalidMaxpTable } } else { if f.maxp.length != 32 { return nil, 0, errInvalidMaxpTable } } u, err := f.src.u16(buf, f.maxp, 4) if err != nil { return nil, 0, err } return buf, int32(u), 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 (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, isColorBitmap bool, err error) { if isPostScript { p := cffParser{ src: &f.src, base: int(f.cff.offset), offset: int(f.cff.offset), end: int(f.cff.offset + f.cff.length), } ret, err = p.parse(numGlyphs) if err != nil { return nil, glyphData{}, false, err } } else if f.loca.length != 0 { ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs) if err != nil { return nil, glyphData{}, false, err } } else if f.cblc.length != 0 { isColorBitmap = true // TODO: parse the CBLC (and CBDT) tables. For now, we return a font // with empty glyphs. ret.locations = make([]uint32, numGlyphs+1) } if len(ret.locations) != int(numGlyphs+1) { return nil, glyphData{}, false, errInvalidLocationData } return buf, ret, isColorBitmap, nil } func (f *Font) glyphTopOS2(b *Buffer, ppem fixed.Int26_6, r rune) (int32, error) { ind, err := f.GlyphIndex(b, r) if err != nil && err != ErrNotFound { return 0, err } else if ind == 0 { return 0, nil } // Y axis points down var min fixed.Int26_6 seg, err := f.LoadGlyph(b, ind, ppem, nil) if err != nil { return 0, err } for _, s := range seg { for _, p := range s.Args { if p.Y < min { min = p.Y } } } return int32(min), nil } func (f *Font) initOS2Version1() (xHeight, capHeight int32, err error) { ppem := fixed.Int26_6(f.UnitsPerEm()) var b 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). xh, err := f.glyphTopOS2(&b, ppem, 'x') if err != nil { return 0, 0, err } // 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). ch, err := f.glyphTopOS2(&b, ppem, 'H') if err != nil { return 0, 0, err } return int32(xh), int32(ch), nil } func (f *Font) parseOS2(buf []byte) (buf1 []byte, hasXHeightCapHeight bool, xHeight, capHeight int32, err error) { // https://docs.microsoft.com/da-dk/typography/opentype/spec/os2 if f.os2.length == 0 { // Apple TrueType fonts might omit the OS/2 table. return buf, false, 0, 0, nil } else if f.os2.length < 2 { return nil, false, 0, 0, errInvalidOS2Table } vers, err := f.src.u16(buf, f.os2, 0) if err != nil { return nil, false, 0, 0, err } if vers <= 1 { const headerSize = 86 if f.os2.length < headerSize { return nil, false, 0, 0, errInvalidOS2Table } // Will resolve xHeight and capHeight later, see initOS2Version1. return buf, false, 0, 0, nil } const headerSize = 96 if f.os2.length < headerSize { return nil, false, 0, 0, errInvalidOS2Table } xh, err := f.src.u16(buf, f.os2, 86) if err != nil { return nil, false, 0, 0, err } ch, err := f.src.u16(buf, f.os2, 88) if err != nil { return nil, false, 0, 0, err } return buf, true, int32(int16(xh)), int32(int16(ch)), 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 (f *Font) PostTable() *PostTable { return f.cached.post } func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, post *PostTable, err error) { // https://www.microsoft.com/typography/otspec/post.htm const headerSize = 32 if f.post.length < headerSize { return nil, nil, errInvalidPostTable } u, err := f.src.u32(buf, f.post, 0) if err != nil { return nil, nil, err } switch u { case 0x10000: // No-op. case 0x20000: if f.post.length < headerSize+2+2*uint32(numGlyphs) { return nil, nil, errInvalidPostTable } case 0x30000: // No-op. default: return nil, nil, errUnsupportedPostTable } ang, err := f.src.u32(buf, f.post, 4) if err != nil { return nil, nil, err } up, err := f.src.u16(buf, f.post, 8) if err != nil { return nil, nil, err } ut, err := f.src.u16(buf, f.post, 10) if err != nil { return nil, nil, err } fp, err := f.src.u32(buf, f.post, 12) if err != nil { return nil, nil, err } post = &PostTable{ Version: u, ItalicAngle: float64(int32(ang)) / 0x10000, UnderlinePosition: int16(up), UnderlineThickness: int16(ut), IsFixedPitch: fp != 0, } return buf, post, 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 (f *Font) Bounds(b *Buffer, ppem fixed.Int26_6, h 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. r := fixed.Rectangle26_6{ Min: fixed.Point26_6{ X: +scale(fixed.Int26_6(f.cached.bounds[0])*ppem, f.cached.unitsPerEm), Y: -scale(fixed.Int26_6(f.cached.bounds[3])*ppem, f.cached.unitsPerEm), }, Max: fixed.Point26_6{ X: +scale(fixed.Int26_6(f.cached.bounds[2])*ppem, f.cached.unitsPerEm), Y: -scale(fixed.Int26_6(f.cached.bounds[1])*ppem, f.cached.unitsPerEm), }, } if h == font.HintingFull { // Quantize the Min down and Max up to a whole pixel. r.Min.X = (r.Min.X + 0) &^ 63 r.Min.Y = (r.Min.Y + 0) &^ 63 r.Max.X = (r.Max.X + 63) &^ 63 r.Max.Y = (r.Max.Y + 63) &^ 63 } return r, 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 (f *Font) GlyphIndex(b *Buffer, r rune) (GlyphIndex, error) { return f.cached.glyphIndex(f, b, r) } func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) { xx := int(x) if f.NumGlyphs() <= xx { return nil, 0, 0, ErrNotFound } i := f.cached.glyphData.locations[xx+0] j := f.cached.glyphData.locations[xx+1] if j < i { return nil, 0, 0, errInvalidGlyphDataLength } if j-i > maxGlyphDataLength { return nil, 0, 0, errUnsupportedGlyphDataLength } buf, err = b.view(&f.src, int(i), int(j-i)) return buf, i, j - i, err } // 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 (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) { if b == nil { b = &Buffer{} } b.segments = b.segments[:0] if f.cached.isColorBitmap { return nil, ErrColoredGlyph } if f.cached.isPostScript { buf, offset, length, err := f.viewGlyphData(b, x) if err != nil { return nil, err } b.psi.type2Charstrings.initialize(f, b, x) if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil { return nil, err } if !b.psi.type2Charstrings.ended { return nil, errInvalidCFFTable } } else if err := loadGlyf(f, b, x, 0, 0); err != nil { return nil, err } // 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 i := range b.segments { a := &b.segments[i].Args for j := range a { a[j].X = +scale(a[j].X*ppem, f.cached.unitsPerEm) a[j].Y = -scale(a[j].Y*ppem, f.cached.unitsPerEm) } } // TODO: look at opts to transform / hint the Buffer.segments. return b.segments, nil } func (f *Font) glyphNameFormat10(x GlyphIndex) (string, error) { if x >= numBuiltInPostNames { return "", ErrNotFound } // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html i := builtInPostNamesOffsets[x+0] j := builtInPostNamesOffsets[x+1] return builtInPostNamesData[i:j], nil } func (f *Font) glyphNameFormat20(b *Buffer, x GlyphIndex) (string, error) { if b == nil { b = &Buffer{} } // The wire format for a Version 2 post table is documented at: // https://www.microsoft.com/typography/otspec/post.htm const glyphNameIndexOffset = 34 buf, err := b.view(&f.src, int(f.post.offset)+glyphNameIndexOffset+2*int(x), 2) if err != nil { return "", err } u := u16(buf) if u < numBuiltInPostNames { i := builtInPostNamesOffsets[u+0] j := builtInPostNamesOffsets[u+1] return builtInPostNamesData[i:j], nil } // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html // says that "32768 through 65535 are reserved for future use". if u > 32767 { return "", errUnsupportedPostTable } u -= 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. offset := glyphNameIndexOffset + 2*f.NumGlyphs() buf, err = b.view(&f.src, int(f.post.offset)+offset, int(f.post.length)-offset) if err != nil { return "", err } for { if len(buf) == 0 { return "", errInvalidPostTable } n := 1 + int(buf[0]) if len(buf) < n { return "", errInvalidPostTable } if u == 0 { return string(buf[1:n]), nil } buf = buf[n:] u-- } } // 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 (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) { if int(x) >= f.NumGlyphs() { return "", ErrNotFound } if f.cached.post == nil { return "", nil } switch f.cached.post.Version { case 0x10000: return f.glyphNameFormat10(x) case 0x20000: return f.glyphNameFormat20(b, x) default: return "", 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 (f *Font) GlyphAdvance(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) { if int(x) >= f.NumGlyphs() { return 0, ErrNotFound } if b == nil { b = &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 n := GlyphIndex(f.cached.numHMetrics - 1); x > n { x = n } buf, err := b.view(&f.src, int(f.hmtx.offset)+int(4*x), 2) if err != nil { return 0, err } adv := fixed.Int26_6(u16(buf)) adv = scale(adv*ppem, f.cached.unitsPerEm) if h == font.HintingFull { // Quantize the fixed.Int26_6 value to the nearest pixel. adv = (adv + 32) &^ 63 } return adv, 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 (f *Font) Kern(b *Buffer, x0, x1 GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) { // TODO: how should this work with the GPOS table and CFF fonts? // https://www.microsoft.com/typography/otspec/kern.htm says that // "OpenType™ fonts containing CFF outlines are not supported by the 'kern' // table and must use the 'GPOS' OpenType Layout table." if n := f.NumGlyphs(); int(x0) >= n || int(x1) >= n { 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 f.cached.kernNumPairs == 0 { return 0, nil } if b == nil { b = &Buffer{} } key := uint32(x0)<<16 | uint32(x1) lo, hi := int32(0), f.cached.kernNumPairs for lo < hi { i := (lo + hi) / 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 entrySize = 6 buf, err := b.view(&f.src, int(f.cached.kernOffset+i*entrySize), entrySize) if err != nil { return 0, err } k := u32(buf) if k < key { lo = i + 1 } else if k > key { hi = i } else { kern := fixed.Int26_6(int16(u16(buf[4:]))) kern = scale(kern*ppem, f.cached.unitsPerEm) if h == font.HintingFull { // Quantize the fixed.Int26_6 value to the nearest pixel. kern = (kern + 32) &^ 63 } return kern, nil } } return 0, nil } // Metrics returns the metrics of this font. func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metrics, error) { m := font.Metrics{ Height: scale(fixed.Int26_6(f.cached.ascent-f.cached.descent+f.cached.lineGap)*ppem, f.cached.unitsPerEm), Ascent: +scale(fixed.Int26_6(f.cached.ascent)*ppem, f.cached.unitsPerEm), Descent: -scale(fixed.Int26_6(f.cached.descent)*ppem, f.cached.unitsPerEm), XHeight: scale(fixed.Int26_6(f.cached.xHeight)*ppem, f.cached.unitsPerEm), CapHeight: scale(fixed.Int26_6(f.cached.capHeight)*ppem, f.cached.unitsPerEm), CaretSlope: image.Point{X: int(f.cached.slope[0]), Y: int(f.cached.slope[1])}, } if h == font.HintingFull { // Quantize up to a whole pixel. m.Height = (m.Height + 63) &^ 63 m.Ascent = (m.Ascent + 63) &^ 63 m.Descent = (m.Descent + 63) &^ 63 m.XHeight = (m.XHeight + 63) &^ 63 m.CapHeight = (m.CapHeight + 63) &^ 63 } return m, nil } // Name returns the name value keyed by the given NameID. // // It returns ErrNotFound if there is no value for that key. func (f *Font) Name(b *Buffer, id NameID) (string, error) { if b == nil { b = &Buffer{} } const headerSize, entrySize = 6, 12 if f.name.length < headerSize { return "", errInvalidNameTable } buf, err := b.view(&f.src, int(f.name.offset), headerSize) if err != nil { return "", err } numSubtables := u16(buf[2:]) if f.name.length < headerSize+entrySize*uint32(numSubtables) { return "", errInvalidNameTable } stringOffset := u16(buf[4:]) seen := false for i, n := 0, int(numSubtables); i < n; i++ { buf, err := b.view(&f.src, int(f.name.offset)+headerSize+entrySize*i, entrySize) if err != nil { return "", err } if u16(buf[6:]) != uint16(id) { continue } seen = true var stringify func([]byte) (string, error) switch u32(buf) { default: continue case pidMacintosh<<16 | psidMacintoshRoman: stringify = stringifyMacintosh case pidWindows<<16 | psidWindowsUCS2: stringify = stringifyUCS2 } nameLength := u16(buf[8:]) nameOffset := u16(buf[10:]) buf, err = b.view(&f.src, int(f.name.offset)+int(nameOffset)+int(stringOffset), int(nameLength)) if err != nil { return "", err } return stringify(buf) } if seen { return "", errUnsupportedPlatformEncoding } return "", ErrNotFound } func stringifyMacintosh(b []byte) (string, error) { for _, c := range b { if c >= 0x80 { // b contains some non-ASCII bytes. s, _ := charmap.Macintosh.NewDecoder().Bytes(b) return string(s), nil } } // b contains only ASCII bytes. return string(b), nil } func stringifyUCS2(b []byte) (string, error) { if len(b)&1 != 0 { return "", errInvalidUCS2String } r := make([]rune, len(b)/2) for i := range r { r[i] = rune(u16(b)) b = b[2:] } return string(r), 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 []Segment // 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 (b *Buffer) view(src *source, offset, length int) ([]byte, error) { buf, err := src.view(b.buf, offset, length) if err != nil { return nil, err } // Only update b.buf if it is safe to re-use buf. if src.viewBufferWritable() { b.buf = buf } return buf, 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 ) // translateArgs applies a translation to args. func translateArgs(args *[3]fixed.Point26_6, dx, dy fixed.Int26_6) { args[0].X += dx args[0].Y += dy args[1].X += dx args[1].Y += dy args[2].X += dx args[2].Y += dy } // transformArgs applies an affine transformation to args. The t?? arguments // are 2.14 fixed point values. func transformArgs(args *[3]fixed.Point26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) { args[0] = tform(txx, txy, tyx, tyy, dx, dy, args[0]) args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[1]) args[2] = tform(txx, txy, tyx, tyy, dx, dy, args[2]) } func tform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, p fixed.Point26_6) fixed.Point26_6 { const half = 1 << 13 return fixed.Point26_6{ X: dx + fixed.Int26_6((int64(p.X)*int64(txx)+half)>>14) + fixed.Int26_6((int64(p.Y)*int64(tyx)+half)>>14), Y: dy + fixed.Int26_6((int64(p.X)*int64(txy)+half)>>14) + fixed.Int26_6((int64(p.Y)*int64(tyy)+half)>>14), } }