From 500caaeda74dd9c660279036293f4b2997cf0b03 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sat, 9 Sep 2017 09:42:37 +0200 Subject: Add vendor --- vendor/golang.org/x/image/font/sfnt/sfnt.go | 1503 +++++++++++++++++++++++++++ 1 file changed, 1503 insertions(+) create mode 100644 vendor/golang.org/x/image/font/sfnt/sfnt.go (limited to 'vendor/golang.org/x/image/font/sfnt/sfnt.go') diff --git a/vendor/golang.org/x/image/font/sfnt/sfnt.go b/vendor/golang.org/x/image/font/sfnt/sfnt.go new file mode 100644 index 0000000..53ac94b --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/sfnt.go @@ -0,0 +1,1503 @@ +// 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" + "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") + 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: + // 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 { + glyphData glyphData + glyphIndex glyphIndexFunc + bounds [4]int16 + indexToLocFormat bool // false means short, true means long. + isColorBitmap bool + isPostScript bool + kernNumPairs int32 + kernOffset int32 + numHMetrics int32 + postTableVersion uint32 + unitsPerEm Units + } +} + +// 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, numHMetrics, err := f.parseHhea(buf, numGlyphs) + if err != nil { + return err + } + buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics) + if err != nil { + return err + } + buf, postTableVersion, err := f.parsePost(buf, numGlyphs) + if err != nil { + return err + } + + f.cached.glyphData = glyphData + f.cached.glyphIndex = glyphIndex + f.cached.bounds = bounds + f.cached.indexToLocFormat = indexToLocFormat + f.cached.isColorBitmap = isColorBitmap + f.cached.isPostScript = isPostScript + f.cached.kernNumPairs = kernNumPairs + f.cached.kernOffset = kernOffset + f.cached.numHMetrics = numHMetrics + f.cached.postTableVersion = postTableVersion + f.cached.unitsPerEm = unitsPerEm + + 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 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, numHMetrics int32, err error) { + // https://www.microsoft.com/typography/OTSPEC/hhea.htm + + if f.hhea.length != 36 { + return nil, 0, errInvalidHheaTable + } + u, err := f.src.u16(buf, f.hhea, 34) + if err != nil { + return nil, 0, err + } + if int32(u) > numGlyphs || u == 0 { + return nil, 0, errInvalidHheaTable + } + return buf, int32(u), nil +} + +func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) { + // https://www.microsoft.com/typography/OTSPEC/hmtx.htm + + if f.hmtx.length != uint32(2*numGlyphs+2*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) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVersion uint32, err error) { + // https://www.microsoft.com/typography/otspec/post.htm + + const headerSize = 32 + if f.post.length < headerSize { + return nil, 0, errInvalidPostTable + } + u, err := f.src.u32(buf, f.post, 0) + if err != nil { + return nil, 0, err + } + switch u { + case 0x20000: + if f.post.length < headerSize+2+2*uint32(numGlyphs) { + return nil, 0, errInvalidPostTable + } + case 0x30000: + // No-op. + default: + return nil, 0, errUnsupportedPostTable + } + return buf, u, 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 +} + +// 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.postTableVersion != 0x20000 { + return "", nil + } + 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-- + } +} + +// 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 +} + +// 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), + } +} -- cgit v1.2.3