summaryrefslogtreecommitdiffstats
path: root/vendor/golang.org/x/image/font/sfnt/sfnt.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/image/font/sfnt/sfnt.go')
-rw-r--r--vendor/golang.org/x/image/font/sfnt/sfnt.go399
1 files changed, 327 insertions, 72 deletions
diff --git a/vendor/golang.org/x/image/font/sfnt/sfnt.go b/vendor/golang.org/x/image/font/sfnt/sfnt.go
index 356841db1..5cb1225ef 100644
--- a/vendor/golang.org/x/image/font/sfnt/sfnt.go
+++ b/vendor/golang.org/x/image/font/sfnt/sfnt.go
@@ -53,6 +53,7 @@ const (
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.
@@ -69,11 +70,14 @@ var (
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")
@@ -86,6 +90,7 @@ var (
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")
@@ -93,6 +98,7 @@ var (
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")
@@ -298,6 +304,7 @@ func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
type Collection struct {
src source
offsets []uint32
+ isDfont bool
}
// NumFonts returns the number of fonts in the collection.
@@ -305,8 +312,13 @@ 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.
- buf, err := c.src.view(nil, 0, 12)
+ // 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
}
@@ -314,6 +326,8 @@ func (c *Collection) initialize() error {
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}
@@ -338,13 +352,116 @@ func (c *Collection) initialize() error {
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])); err != nil {
+ if err := f.initialize(int(c.offsets[i]), c.isDfont); err != nil {
return nil, err
}
return f, nil
@@ -354,7 +471,7 @@ func (c *Collection) Font(i int) (*Font, error) {
// source.
func Parse(src []byte) (*Font, error) {
f := &Font{src: source{b: src}}
- if err := f.initialize(0); err != nil {
+ if err := f.initialize(0, false); err != nil {
return nil, err
}
return f, nil
@@ -364,7 +481,7 @@ func Parse(src []byte) (*Font, error) {
// io.ReaderAt data source.
func ParseReaderAt(src io.ReaderAt) (*Font, error) {
f := &Font{src: source{r: src}}
- if err := f.initialize(0); err != nil {
+ if err := f.initialize(0, false); err != nil {
return nil, err
}
return f, nil
@@ -438,39 +555,30 @@ type Font struct {
kern table
cached struct {
+ glyphData glyphData
glyphIndex glyphIndexFunc
+ bounds [4]int16
indexToLocFormat bool // false means short, true means long.
isPostScript bool
kernNumPairs int32
kernOffset int32
+ numHMetrics int32
postTableVersion uint32
unitsPerEm Units
-
- // 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 slice length equals 1 plus the number of subroutines
- gsubrs, subrs []uint32
}
}
// NumGlyphs returns the number of glyphs in f.
-func (f *Font) NumGlyphs() int { return len(f.cached.locations) - 1 }
+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) error {
+func (f *Font) initialize(offset int, isDfont bool) error {
if !f.src.valid() {
return errInvalidSourceData
}
- buf, isPostScript, err := f.initializeTables(offset)
+ buf, isPostScript, err := f.initializeTables(offset, isDfont)
if err != nil {
return err
}
@@ -483,11 +591,15 @@ func (f *Font) initialize(offset int) error {
// When implementing new parseXxx methods, take care not to call methods
// such as Font.NumGlyphs that implicitly depend on f.cached fields.
- buf, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
+ buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
if err != nil {
return err
}
- buf, numGlyphs, locations, gsubrs, subrs, err := f.parseMaxp(buf, indexToLocFormat, isPostScript)
+ buf, numGlyphs, err := f.parseMaxp(buf, isPostScript)
+ if err != nil {
+ return err
+ }
+ buf, glyphData, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
if err != nil {
return err
}
@@ -499,26 +611,34 @@ func (f *Font) initialize(offset int) error {
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.isPostScript = isPostScript
f.cached.kernNumPairs = kernNumPairs
f.cached.kernOffset = kernOffset
+ f.cached.numHMetrics = numHMetrics
f.cached.postTableVersion = postTableVersion
f.cached.unitsPerEm = unitsPerEm
- f.cached.locations = locations
- f.cached.gsubrs = gsubrs
- f.cached.subrs = subrs
return nil
}
-func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err error) {
+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.
@@ -531,6 +651,8 @@ func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err
switch u32(buf) {
default:
return nil, false, errInvalidFont
+ case dfontResourceDataOffset:
+ return nil, false, errInvalidSingleFont
case 0x00010000:
// No-op.
case 0x4f54544f: // "OTTO".
@@ -559,6 +681,15 @@ func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err
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
}
@@ -662,26 +793,61 @@ func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, er
return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
}
-func (f *Font) parseHead(buf []byte) (buf1 []byte, indexToLocFormat bool, unitsPerEm Units, err error) {
+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, false, 0, errInvalidHeadTable
+ return nil, [4]int16{}, false, 0, errInvalidHeadTable
}
+
u, err := f.src.u16(buf, f.head, 18)
if err != nil {
- return nil, false, 0, err
+ return nil, [4]int16{}, false, 0, err
}
if u == 0 {
- return nil, false, 0, errInvalidHeadTable
+ 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, false, 0, err
+ return nil, [4]int16{}, false, 0, err
}
indexToLocFormat = u != 0
- return buf, indexToLocFormat, unitsPerEm, nil
+ 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) {
@@ -778,24 +944,44 @@ func (f *Font) parseKernFormat0(buf []byte, offset, length int) (buf1 []byte, ke
return buf, kernNumPairs, int32(offset) + headerSize, nil
}
-func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1 []byte, numGlyphs int, locations, gsubrs, subrs []uint32, err error) {
+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, nil, nil, nil, errInvalidMaxpTable
+ return nil, 0, errInvalidMaxpTable
}
} else {
if f.maxp.length != 32 {
- return nil, 0, nil, nil, nil, errInvalidMaxpTable
+ return nil, 0, errInvalidMaxpTable
}
}
u, err := f.src.u16(buf, f.maxp, 4)
if err != nil {
- return nil, 0, nil, nil, nil, err
+ return nil, 0, err
}
- numGlyphs = int(u)
+ 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, err error) {
if isPostScript {
p := cffParser{
src: &f.src,
@@ -803,24 +989,24 @@ func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1
offset: int(f.cff.offset),
end: int(f.cff.offset + f.cff.length),
}
- locations, gsubrs, subrs, err = p.parse()
+ ret, err = p.parse(numGlyphs)
if err != nil {
- return nil, 0, nil, nil, nil, err
+ return nil, glyphData{}, err
}
} else {
- locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
+ ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
if err != nil {
- return nil, 0, nil, nil, nil, err
+ return nil, glyphData{}, err
}
}
- if len(locations) != numGlyphs+1 {
- return nil, 0, nil, nil, nil, errInvalidLocationData
+ if len(ret.locations) != int(numGlyphs+1) {
+ return nil, glyphData{}, errInvalidLocationData
}
- return buf, numGlyphs, locations, gsubrs, subrs, nil
+ return buf, ret, nil
}
-func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersion uint32, err error) {
+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
@@ -844,6 +1030,33 @@ func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersi
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.
@@ -866,8 +1079,8 @@ func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, lengt
if f.NumGlyphs() <= xx {
return nil, 0, 0, ErrNotFound
}
- i := f.cached.locations[xx+0]
- j := f.cached.locations[xx+1]
+ i := f.cached.glyphData.locations[xx+0]
+ j := f.cached.glyphData.locations[xx+1]
if j < i {
return nil, 0, 0, errInvalidGlyphDataLength
}
@@ -888,6 +1101,8 @@ type LoadGlyphOptions struct {
//
// 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.
func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) {
if b == nil {
@@ -900,7 +1115,7 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load
if err != nil {
return nil, err
}
- b.psi.type2Charstrings.initialize(f, b)
+ b.psi.type2Charstrings.initialize(f, b, x)
if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil {
return nil, err
}
@@ -918,10 +1133,14 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load
// 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 {
- s := &b.segments[i]
- for j := range s.Args {
- s.Args[j] = scale(s.Args[j]*ppem, f.cached.unitsPerEm)
+ 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)
}
}
@@ -1002,6 +1221,39 @@ func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
}
}
+// 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.
@@ -1180,8 +1432,10 @@ func (b *Buffer) view(src *source, offset, length int) ([]byte, error) {
// Segment is a segment of a vector path.
type Segment struct {
- Op SegmentOp
- Args [6]fixed.Int26_6
+ // 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.
@@ -1195,30 +1449,31 @@ const (
)
// translateArgs applies a translation to args.
-func translateArgs(args *[6]fixed.Int26_6, dx, dy fixed.Int26_6) {
- args[0] += dx
- args[1] += dy
- args[2] += dx
- args[3] += dy
- args[4] += dx
- args[5] += dy
+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 *[6]fixed.Int26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) {
- args[0], args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[0], args[1])
- args[2], args[3] = tform(txx, txy, tyx, tyy, dx, dy, args[2], args[3])
- args[4], args[5] = tform(txx, txy, tyx, tyy, dx, dy, args[4], args[5])
+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, x, y fixed.Int26_6) (newX, newY fixed.Int26_6) {
+func tform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, p fixed.Point26_6) fixed.Point26_6 {
const half = 1 << 13
- newX = dx +
- fixed.Int26_6((int64(x)*int64(txx)+half)>>14) +
- fixed.Int26_6((int64(y)*int64(tyx)+half)>>14)
- newY = dy +
- fixed.Int26_6((int64(x)*int64(txy)+half)>>14) +
- fixed.Int26_6((int64(y)*int64(tyy)+half)>>14)
- return newX, newY
+ 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),
+ }
}