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/cmap.go | 259 ++++ vendor/golang.org/x/image/font/sfnt/data.go | 68 + .../golang.org/x/image/font/sfnt/example_test.go | 131 ++ vendor/golang.org/x/image/font/sfnt/gen.go | 321 +++++ vendor/golang.org/x/image/font/sfnt/postscript.go | 1414 ++++++++++++++++++ .../x/image/font/sfnt/proprietary_test.go | 1390 ++++++++++++++++++ vendor/golang.org/x/image/font/sfnt/sfnt.go | 1503 ++++++++++++++++++++ vendor/golang.org/x/image/font/sfnt/sfnt_test.go | 805 +++++++++++ vendor/golang.org/x/image/font/sfnt/truetype.go | 572 ++++++++ 9 files changed, 6463 insertions(+) create mode 100644 vendor/golang.org/x/image/font/sfnt/cmap.go create mode 100644 vendor/golang.org/x/image/font/sfnt/data.go create mode 100644 vendor/golang.org/x/image/font/sfnt/example_test.go create mode 100644 vendor/golang.org/x/image/font/sfnt/gen.go create mode 100644 vendor/golang.org/x/image/font/sfnt/postscript.go create mode 100644 vendor/golang.org/x/image/font/sfnt/proprietary_test.go create mode 100644 vendor/golang.org/x/image/font/sfnt/sfnt.go create mode 100644 vendor/golang.org/x/image/font/sfnt/sfnt_test.go create mode 100644 vendor/golang.org/x/image/font/sfnt/truetype.go (limited to 'vendor/golang.org/x/image/font/sfnt') diff --git a/vendor/golang.org/x/image/font/sfnt/cmap.go b/vendor/golang.org/x/image/font/sfnt/cmap.go new file mode 100644 index 0000000..797e9d1 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/cmap.go @@ -0,0 +1,259 @@ +// Copyright 2017 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. + +package sfnt + +import ( + "golang.org/x/text/encoding/charmap" +) + +// Platform IDs and Platform Specific IDs as per +// https://www.microsoft.com/typography/otspec/name.htm +const ( + pidUnicode = 0 + pidMacintosh = 1 + pidWindows = 3 + + psidUnicode2BMPOnly = 3 + psidUnicode2FullRepertoire = 4 + // Note that FontForge may generate a bogus Platform Specific ID (value 10) + // for the Unicode Platform ID (value 0). See + // https://github.com/fontforge/fontforge/issues/2728 + + psidMacintoshRoman = 0 + + psidWindowsSymbol = 0 + psidWindowsUCS2 = 1 + psidWindowsUCS4 = 10 +) + +// platformEncodingWidth returns the number of bytes per character assumed by +// the given Platform ID and Platform Specific ID. +// +// Very old fonts, from before Unicode was widely adopted, assume only 1 byte +// per character: a character map. +// +// Old fonts, from when Unicode meant the Basic Multilingual Plane (BMP), +// assume that 2 bytes per character is sufficient. +// +// Recent fonts naturally support the full range of Unicode code points, which +// can take up to 4 bytes per character. Such fonts might still choose one of +// the legacy encodings if e.g. their repertoire is limited to the BMP, for +// greater compatibility with older software, or because the resultant file +// size can be smaller. +func platformEncodingWidth(pid, psid uint16) int { + switch pid { + case pidUnicode: + switch psid { + case psidUnicode2BMPOnly: + return 2 + case psidUnicode2FullRepertoire: + return 4 + } + + case pidMacintosh: + switch psid { + case psidMacintoshRoman: + return 1 + } + + case pidWindows: + switch psid { + case psidWindowsSymbol: + return 2 + case psidWindowsUCS2: + return 2 + case psidWindowsUCS4: + return 4 + } + } + return 0 +} + +// The various cmap formats are described at +// https://www.microsoft.com/typography/otspec/cmap.htm + +var supportedCmapFormat = func(format, pid, psid uint16) bool { + switch format { + case 0: + return pid == pidMacintosh && psid == psidMacintoshRoman + case 4: + return true + case 12: + return true + } + return false +} + +func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, glyphIndexFunc, error) { + switch format { + case 0: + return f.makeCachedGlyphIndexFormat0(buf, offset, length) + case 4: + return f.makeCachedGlyphIndexFormat4(buf, offset, length) + case 12: + return f.makeCachedGlyphIndexFormat12(buf, offset, length) + } + panic("unreachable") +} + +func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) { + if length != 6+256 || offset+length > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length)) + if err != nil { + return nil, nil, err + } + var table [256]byte + copy(table[:], buf[6:]) + return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + x, ok := charmap.Macintosh.EncodeRune(r) + if !ok { + // The source rune r is not representable in the Macintosh-Roman encoding. + return 0, nil + } + return GlyphIndex(table[x]), nil + }, nil +} + +func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) { + const headerSize = 14 + if offset+headerSize > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) + if err != nil { + return nil, nil, err + } + offset += headerSize + + segCount := u16(buf[6:]) + if segCount&1 != 0 { + return nil, nil, errInvalidCmapTable + } + segCount /= 2 + if segCount > maxCmapSegments { + return nil, nil, errUnsupportedNumberOfCmapSegments + } + + eLength := 8*uint32(segCount) + 2 + if offset+eLength > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) + if err != nil { + return nil, nil, err + } + offset += eLength + + entries := make([]cmapEntry16, segCount) + for i := range entries { + entries[i] = cmapEntry16{ + end: u16(buf[0*len(entries)+0+2*i:]), + start: u16(buf[2*len(entries)+2+2*i:]), + delta: u16(buf[4*len(entries)+2+2*i:]), + offset: u16(buf[6*len(entries)+2+2*i:]), + } + } + indexesBase := f.cmap.offset + offset + indexesLength := f.cmap.length - offset + + return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + if uint32(r) > 0xffff { + return 0, nil + } + + c := uint16(r) + for i, j := 0, len(entries); i < j; { + h := i + (j-i)/2 + entry := &entries[h] + if c < entry.start { + j = h + } else if entry.end < c { + i = h + 1 + } else if entry.offset == 0 { + return GlyphIndex(c + entry.delta), nil + } else { + offset := uint32(entry.offset) + 2*uint32(h-len(entries)+int(c-entry.start)) + if offset > indexesLength || offset+2 > indexesLength { + return 0, errInvalidCmapTable + } + x, err := b.view(&f.src, int(indexesBase+offset), 2) + if err != nil { + return 0, err + } + return GlyphIndex(u16(x)), nil + } + } + return 0, nil + }, nil +} + +func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, glyphIndexFunc, error) { + const headerSize = 16 + if offset+headerSize > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) + if err != nil { + return nil, nil, err + } + length := u32(buf[4:]) + if f.cmap.length < offset || length > f.cmap.length-offset { + return nil, nil, errInvalidCmapTable + } + offset += headerSize + + numGroups := u32(buf[12:]) + if numGroups > maxCmapSegments { + return nil, nil, errUnsupportedNumberOfCmapSegments + } + + eLength := 12 * numGroups + if headerSize+eLength != length { + return nil, nil, errInvalidCmapTable + } + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) + if err != nil { + return nil, nil, err + } + offset += eLength + + entries := make([]cmapEntry32, numGroups) + for i := range entries { + entries[i] = cmapEntry32{ + start: u32(buf[0+12*i:]), + end: u32(buf[4+12*i:]), + delta: u32(buf[8+12*i:]), + } + } + + return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + c := uint32(r) + for i, j := 0, len(entries); i < j; { + h := i + (j-i)/2 + entry := &entries[h] + if c < entry.start { + j = h + } else if entry.end < c { + i = h + 1 + } else { + return GlyphIndex(c - entry.start + entry.delta), nil + } + } + return 0, nil + }, nil +} + +type cmapEntry16 struct { + end, start, delta, offset uint16 +} + +type cmapEntry32 struct { + start, end, delta uint32 +} diff --git a/vendor/golang.org/x/image/font/sfnt/data.go b/vendor/golang.org/x/image/font/sfnt/data.go new file mode 100644 index 0000000..ad0c139 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/data.go @@ -0,0 +1,68 @@ +// generated by go run gen.go; DO NOT EDIT + +package sfnt + +const numBuiltInPostNames = 258 + +const builtInPostNamesData = "" + + ".notdef.nullnonmarkingreturnspaceexclamquotedblnumbersigndollarp" + + "ercentampersandquotesingleparenleftparenrightasteriskpluscommahy" + + "phenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemi" + + "colonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracket" + + "leftbackslashbracketrightasciicircumunderscoregraveabcdefghijklm" + + "nopqrstuvwxyzbraceleftbarbracerightasciitildeAdieresisAringCcedi" + + "llaEacuteNtildeOdieresisUdieresisaacuteagraveacircumflexadieresi" + + "satildearingccedillaeacuteegraveecircumflexedieresisiacuteigrave" + + "icircumflexidieresisntildeoacuteograveocircumflexodieresisotilde" + + "uacuteugraveucircumflexudieresisdaggerdegreecentsterlingsectionb" + + "ulletparagraphgermandblsregisteredcopyrighttrademarkacutedieresi" + + "snotequalAEOslashinfinityplusminuslessequalgreaterequalyenmupart" + + "ialdiffsummationproductpiintegralordfeminineordmasculineOmegaaeo" + + "slashquestiondownexclamdownlogicalnotradicalflorinapproxequalDel" + + "taguillemotleftguillemotrightellipsisnonbreakingspaceAgraveAtild" + + "eOtildeOEoeendashemdashquotedblleftquotedblrightquoteleftquoteri" + + "ghtdividelozengeydieresisYdieresisfractioncurrencyguilsinglleftg" + + "uilsinglrightfifldaggerdblperiodcenteredquotesinglbasequotedblba" + + "seperthousandAcircumflexEcircumflexAacuteEdieresisEgraveIacuteIc" + + "ircumflexIdieresisIgraveOacuteOcircumflexappleOgraveUacuteUcircu" + + "mflexUgravedotlessicircumflextildemacronbrevedotaccentringcedill" + + "ahungarumlautogonekcaronLslashlslashScaronscaronZcaronzcaronbrok" + + "enbarEthethYacuteyacuteThornthornminusmultiplyonesuperiortwosupe" + + "riorthreesuperioronehalfonequarterthreequartersfrancGbrevegbreve" + + "IdotaccentScedillascedillaCacutecacuteCcaronccarondcroat" + +var builtInPostNamesOffsets = [...]uint16{ + 0x0000, 0x0007, 0x000c, 0x001c, 0x0021, 0x0027, 0x002f, 0x0039, + 0x003f, 0x0046, 0x004f, 0x005a, 0x0063, 0x006d, 0x0075, 0x0079, + 0x007e, 0x0084, 0x008a, 0x008f, 0x0093, 0x0096, 0x0099, 0x009e, + 0x00a2, 0x00a6, 0x00a9, 0x00ae, 0x00b3, 0x00b7, 0x00bc, 0x00c5, + 0x00c9, 0x00ce, 0x00d5, 0x00dd, 0x00df, 0x00e0, 0x00e1, 0x00e2, + 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, + 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2, + 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x0104, + 0x010d, 0x0119, 0x0124, 0x012e, 0x0133, 0x0134, 0x0135, 0x0136, + 0x0137, 0x0138, 0x0139, 0x013a, 0x013b, 0x013c, 0x013d, 0x013e, + 0x013f, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, + 0x0147, 0x0148, 0x0149, 0x014a, 0x014b, 0x014c, 0x014d, 0x0156, + 0x0159, 0x0163, 0x016d, 0x0176, 0x017b, 0x0183, 0x0189, 0x018f, + 0x0198, 0x01a1, 0x01a7, 0x01ad, 0x01b8, 0x01c1, 0x01c7, 0x01cc, + 0x01d4, 0x01da, 0x01e0, 0x01eb, 0x01f4, 0x01fa, 0x0200, 0x020b, + 0x0214, 0x021a, 0x0220, 0x0226, 0x0231, 0x023a, 0x0240, 0x0246, + 0x024c, 0x0257, 0x0260, 0x0266, 0x026c, 0x0270, 0x0278, 0x027f, + 0x0285, 0x028e, 0x0298, 0x02a2, 0x02ab, 0x02b4, 0x02b9, 0x02c1, + 0x02c9, 0x02cb, 0x02d1, 0x02d9, 0x02e2, 0x02eb, 0x02f7, 0x02fa, + 0x02fc, 0x0307, 0x0310, 0x0317, 0x0319, 0x0321, 0x032c, 0x0338, + 0x033d, 0x033f, 0x0345, 0x0351, 0x035b, 0x0365, 0x036c, 0x0372, + 0x037d, 0x0382, 0x038f, 0x039d, 0x03a5, 0x03b5, 0x03bb, 0x03c1, + 0x03c7, 0x03c9, 0x03cb, 0x03d1, 0x03d7, 0x03e3, 0x03f0, 0x03f9, + 0x0403, 0x0409, 0x0410, 0x0419, 0x0422, 0x042a, 0x0432, 0x043f, + 0x044d, 0x044f, 0x0451, 0x045a, 0x0468, 0x0476, 0x0482, 0x048d, + 0x0498, 0x04a3, 0x04a9, 0x04b2, 0x04b8, 0x04be, 0x04c9, 0x04d2, + 0x04d8, 0x04de, 0x04e9, 0x04ee, 0x04f4, 0x04fa, 0x0505, 0x050b, + 0x0513, 0x051d, 0x0522, 0x0528, 0x052d, 0x0536, 0x053a, 0x0541, + 0x054d, 0x0553, 0x0558, 0x055e, 0x0564, 0x056a, 0x0570, 0x0576, + 0x057c, 0x0585, 0x0588, 0x058b, 0x0591, 0x0597, 0x059c, 0x05a1, + 0x05a6, 0x05ae, 0x05b9, 0x05c4, 0x05d1, 0x05d8, 0x05e2, 0x05ef, + 0x05f4, 0x05fa, 0x0600, 0x060a, 0x0612, 0x061a, 0x0620, 0x0626, + 0x062c, 0x0632, 0x0638, +} diff --git a/vendor/golang.org/x/image/font/sfnt/example_test.go b/vendor/golang.org/x/image/font/sfnt/example_test.go new file mode 100644 index 0000000..baddcfe --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/example_test.go @@ -0,0 +1,131 @@ +// Copyright 2017 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. + +package sfnt_test + +import ( + "image" + "image/draw" + "log" + "os" + + "golang.org/x/image/font/gofont/goregular" + "golang.org/x/image/font/sfnt" + "golang.org/x/image/math/fixed" + "golang.org/x/image/vector" +) + +func ExampleRasterizeGlyph() { + const ( + ppem = 32 + width = 24 + height = 36 + originX = 0 + originY = 32 + ) + + f, err := sfnt.Parse(goregular.TTF) + if err != nil { + log.Fatalf("Parse: %v", err) + } + var b sfnt.Buffer + x, err := f.GlyphIndex(&b, 'Ġ') + if err != nil { + log.Fatalf("GlyphIndex: %v", err) + } + if x == 0 { + log.Fatalf("GlyphIndex: no glyph index found for the rune 'Ġ'") + } + segments, err := f.LoadGlyph(&b, x, fixed.I(ppem), nil) + if err != nil { + log.Fatalf("LoadGlyph: %v", err) + } + + r := vector.NewRasterizer(width, height) + r.DrawOp = draw.Src + for _, seg := range segments { + // The divisions by 64 below is because the seg.Args values have type + // fixed.Int26_6, a 26.6 fixed point number, and 1<<6 == 64. + switch seg.Op { + case sfnt.SegmentOpMoveTo: + r.MoveTo( + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, + ) + case sfnt.SegmentOpLineTo: + r.LineTo( + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, + ) + case sfnt.SegmentOpQuadTo: + r.QuadTo( + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, + originX+float32(seg.Args[1].X)/64, + originY+float32(seg.Args[1].Y)/64, + ) + case sfnt.SegmentOpCubeTo: + r.CubeTo( + originX+float32(seg.Args[0].X)/64, + originY+float32(seg.Args[0].Y)/64, + originX+float32(seg.Args[1].X)/64, + originY+float32(seg.Args[1].Y)/64, + originX+float32(seg.Args[2].X)/64, + originY+float32(seg.Args[2].Y)/64, + ) + } + } + + dst := image.NewAlpha(image.Rect(0, 0, width, height)) + r.Draw(dst, dst.Bounds(), image.Opaque, image.Point{}) + + const asciiArt = ".++8" + buf := make([]byte, 0, height*(width+1)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + a := dst.AlphaAt(x, y).A + buf = append(buf, asciiArt[a>>6]) + } + buf = append(buf, '\n') + } + os.Stdout.Write(buf) + + // Output: + // ........................ + // ........................ + // ........................ + // ............888......... + // ............888......... + // ............888......... + // ............+++......... + // ........................ + // ..........+++++++++..... + // .......+8888888888888+.. + // ......8888888888888888.. + // ....+8888+........++88.. + // ....8888................ + // ...8888................. + // ..+888+................. + // ..+888.................. + // ..888+.................. + // .+888+.................. + // .+888................... + // .+888................... + // .+888................... + // .+888..........+++++++.. + // .+888..........8888888.. + // .+888+.........+++8888.. + // ..888+............+888.. + // ..8888............+888.. + // ..+888+...........+888.. + // ...8888+..........+888.. + // ...+8888+.........+888.. + // ....+88888+.......+888.. + // .....+8888888888888888.. + // .......+888888888888++.. + // ..........++++++++...... + // ........................ + // ........................ + // ........................ +} diff --git a/vendor/golang.org/x/image/font/sfnt/gen.go b/vendor/golang.org/x/image/font/sfnt/gen.go new file mode 100644 index 0000000..12587d4 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/gen.go @@ -0,0 +1,321 @@ +// Copyright 2017 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. + +// +build ignore + +package main + +import ( + "bytes" + "fmt" + "go/format" + "io/ioutil" + "log" +) + +func main() { + data, offsets := []byte(nil), []int{0} + for _, name := range names { + data = append(data, name...) + offsets = append(offsets, len(data)) + } + + b := new(bytes.Buffer) + fmt.Fprintf(b, "// generated by go run gen.go; DO NOT EDIT\n\n") + fmt.Fprintf(b, "package sfnt\n\n") + + fmt.Fprintf(b, "const numBuiltInPostNames = %d\n\n", len(names)) + + fmt.Fprintf(b, "const builtInPostNamesData = \"\" +\n") + for s := data; ; { + if len(s) <= 64 { + fmt.Fprintf(b, "%q\n", s) + break + } + fmt.Fprintf(b, "%q +\n", s[:64]) + s = s[64:] + } + fmt.Fprintf(b, "\n") + + fmt.Fprintf(b, "var builtInPostNamesOffsets = [...]uint16{\n") + for i, o := range offsets { + fmt.Fprintf(b, "%#04x,", o) + if i%8 == 7 { + fmt.Fprintf(b, "\n") + } + } + fmt.Fprintf(b, "\n}\n") + + dstUnformatted := b.Bytes() + dst, err := format.Source(dstUnformatted) + if err != nil { + log.Fatalf("format.Source: %v\n\n----\n%s\n----", err, dstUnformatted) + } + if err := ioutil.WriteFile("data.go", dst, 0666); err != nil { + log.Fatalf("ioutil.WriteFile: %v", err) + } +} + +// names is the built-in post table names listed at +// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html +var names = [258]string{ + ".notdef", + ".null", + "nonmarkingreturn", + "space", + "exclam", + "quotedbl", + "numbersign", + "dollar", + "percent", + "ampersand", + "quotesingle", + "parenleft", + "parenright", + "asterisk", + "plus", + "comma", + "hyphen", + "period", + "slash", + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "colon", + "semicolon", + "less", + "equal", + "greater", + "question", + "at", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "bracketleft", + "backslash", + "bracketright", + "asciicircum", + "underscore", + "grave", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "braceleft", + "bar", + "braceright", + "asciitilde", + "Adieresis", + "Aring", + "Ccedilla", + "Eacute", + "Ntilde", + "Odieresis", + "Udieresis", + "aacute", + "agrave", + "acircumflex", + "adieresis", + "atilde", + "aring", + "ccedilla", + "eacute", + "egrave", + "ecircumflex", + "edieresis", + "iacute", + "igrave", + "icircumflex", + "idieresis", + "ntilde", + "oacute", + "ograve", + "ocircumflex", + "odieresis", + "otilde", + "uacute", + "ugrave", + "ucircumflex", + "udieresis", + "dagger", + "degree", + "cent", + "sterling", + "section", + "bullet", + "paragraph", + "germandbls", + "registered", + "copyright", + "trademark", + "acute", + "dieresis", + "notequal", + "AE", + "Oslash", + "infinity", + "plusminus", + "lessequal", + "greaterequal", + "yen", + "mu", + "partialdiff", + "summation", + "product", + "pi", + "integral", + "ordfeminine", + "ordmasculine", + "Omega", + "ae", + "oslash", + "questiondown", + "exclamdown", + "logicalnot", + "radical", + "florin", + "approxequal", + "Delta", + "guillemotleft", + "guillemotright", + "ellipsis", + "nonbreakingspace", + "Agrave", + "Atilde", + "Otilde", + "OE", + "oe", + "endash", + "emdash", + "quotedblleft", + "quotedblright", + "quoteleft", + "quoteright", + "divide", + "lozenge", + "ydieresis", + "Ydieresis", + "fraction", + "currency", + "guilsinglleft", + "guilsinglright", + "fi", + "fl", + "daggerdbl", + "periodcentered", + "quotesinglbase", + "quotedblbase", + "perthousand", + "Acircumflex", + "Ecircumflex", + "Aacute", + "Edieresis", + "Egrave", + "Iacute", + "Icircumflex", + "Idieresis", + "Igrave", + "Oacute", + "Ocircumflex", + "apple", + "Ograve", + "Uacute", + "Ucircumflex", + "Ugrave", + "dotlessi", + "circumflex", + "tilde", + "macron", + "breve", + "dotaccent", + "ring", + "cedilla", + "hungarumlaut", + "ogonek", + "caron", + "Lslash", + "lslash", + "Scaron", + "scaron", + "Zcaron", + "zcaron", + "brokenbar", + "Eth", + "eth", + "Yacute", + "yacute", + "Thorn", + "thorn", + "minus", + "multiply", + "onesuperior", + "twosuperior", + "threesuperior", + "onehalf", + "onequarter", + "threequarters", + "franc", + "Gbreve", + "gbreve", + "Idotaccent", + "Scedilla", + "scedilla", + "Cacute", + "cacute", + "Ccaron", + "ccaron", + "dcroat", +} diff --git a/vendor/golang.org/x/image/font/sfnt/postscript.go b/vendor/golang.org/x/image/font/sfnt/postscript.go new file mode 100644 index 0000000..b686e60 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/postscript.go @@ -0,0 +1,1414 @@ +// 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. + +package sfnt + +// Compact Font Format (CFF) fonts are written in PostScript, a stack-based +// programming language. +// +// A fundamental concept is a DICT, or a key-value map, expressed in reverse +// Polish notation. For example, this sequence of operations: +// - push the number 379 +// - version operator +// - push the number 392 +// - Notice operator +// - etc +// - push the number 100 +// - push the number 0 +// - push the number 500 +// - push the number 800 +// - FontBBox operator +// - etc +// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to +// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc. +// +// The first 391 String IDs (starting at 0) are predefined as per the CFF spec +// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means +// "001.000". String ID 392 is not predefined, and is mapped by a separate +// structure, the "String INDEX", inside the CFF data. (String ID 391 is also +// not predefined. Specifically for ../testdata/CFFTest.otf, 391 means +// "uni4E2D", as this font contains a glyph for U+4E2D). +// +// The actual glyph vectors are similarly encoded (in PostScript), in a format +// called Type 2 Charstrings. The wire encoding is similar to but not exactly +// the same as CFF's. For example, the byte 0x05 means FontBBox for CFF DICTs, +// but means rlineto (relative line-to) for Type 2 Charstrings. See +// 5176.CFF.pdf Appendix H and 5177.Type2.pdf Appendix A in the PDF files +// referenced below. +// +// CFF is a stand-alone format, but CFF as used in SFNT fonts have further +// restrictions. For example, a stand-alone CFF can contain multiple fonts, but +// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The Name +// INDEX in the CFF must contain only one entry; that is, there must be only +// one font in the CFF FontSet". +// +// The relevant specifications are: +// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf +// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf + +import ( + "fmt" + "math" + "strconv" + + "golang.org/x/image/math/fixed" +) + +const ( + // psArgStackSize is the argument stack size for a PostScript interpreter. + // 5176.CFF.pdf section 4 "DICT Data" says that "An operator may be + // preceded by up to a maximum of 48 operands". 5177.Type2.pdf Appendix B + // "Type 2 Charstring Implementation Limits" says that "Argument stack 48". + psArgStackSize = 48 + + // Similarly, Appendix B says "Subr nesting, stack limit 10". + psCallStackSize = 10 +) + +func bigEndian(b []byte) uint32 { + switch len(b) { + case 1: + return uint32(b[0]) + case 2: + return uint32(b[0])<<8 | uint32(b[1]) + case 3: + return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]) + case 4: + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) + } + panic("unreachable") +} + +// fdSelect holds a CFF font's Font Dict Select data. +type fdSelect struct { + format uint8 + numRanges uint16 + offset int32 +} + +func (t *fdSelect) lookup(f *Font, b *Buffer, x GlyphIndex) (int, error) { + switch t.format { + case 0: + buf, err := b.view(&f.src, int(t.offset)+int(x), 1) + if err != nil { + return 0, err + } + return int(buf[0]), nil + case 3: + lo, hi := 0, int(t.numRanges) + for lo < hi { + i := (lo + hi) / 2 + buf, err := b.view(&f.src, int(t.offset)+3*i, 3+2) + if err != nil { + return 0, err + } + // buf holds the range [xlo, xhi). + if xlo := GlyphIndex(u16(buf[0:])); x < xlo { + hi = i + continue + } + if xhi := GlyphIndex(u16(buf[3:])); xhi <= x { + lo = i + 1 + continue + } + return int(buf[2]), nil + } + } + return 0, ErrNotFound +} + +// cffParser parses the CFF table from an SFNT font. +type cffParser struct { + src *source + base int + offset int + end int + err error + + buf []byte + locBuf [2]uint32 + + psi psInterpreter +} + +func (p *cffParser) parse(numGlyphs int32) (ret glyphData, err error) { + // Parse the header. + { + if !p.read(4) { + return glyphData{}, p.err + } + if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 { + return glyphData{}, errUnsupportedCFFVersion + } + } + + // Parse the Name INDEX. + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + // https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The + // Name INDEX in the CFF must contain only one entry". + if count != 1 { + return glyphData{}, errInvalidCFFTable + } + if !p.parseIndexLocations(p.locBuf[:2], count, offSize) { + return glyphData{}, p.err + } + p.offset = int(p.locBuf[1]) + } + + // Parse the Top DICT INDEX. + p.psi.topDict.initialize() + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + // 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here + // should match the count of the Name INDEX, which is 1. + if count != 1 { + return glyphData{}, errInvalidCFFTable + } + if !p.parseIndexLocations(p.locBuf[:2], count, offSize) { + return glyphData{}, p.err + } + if !p.read(int(p.locBuf[1] - p.locBuf[0])) { + return glyphData{}, p.err + } + if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil { + return glyphData{}, p.err + } + } + + // Skip the String INDEX. + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count != 0 { + // Read the last location. Locations are off by 1 byte. See the + // comment in parseIndexLocations. + if !p.skip(int(count * offSize)) { + return glyphData{}, p.err + } + if !p.read(int(offSize)) { + return glyphData{}, p.err + } + loc := bigEndian(p.buf) - 1 + // Check that locations are in bounds. + if uint32(p.end-p.offset) < loc { + return glyphData{}, errInvalidCFFTable + } + // Skip the index data. + if !p.skip(int(loc)) { + return glyphData{}, p.err + } + } + } + + // Parse the Global Subrs [Subroutines] INDEX. + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count != 0 { + if count > maxNumSubroutines { + return glyphData{}, errUnsupportedNumberOfSubroutines + } + ret.gsubrs = make([]uint32, count+1) + if !p.parseIndexLocations(ret.gsubrs, count, offSize) { + return glyphData{}, p.err + } + } + } + + // Parse the CharStrings INDEX, whose location was found in the Top DICT. + { + if !p.seekFromBase(p.psi.topDict.charStringsOffset) { + return glyphData{}, errInvalidCFFTable + } + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count == 0 || int32(count) != numGlyphs { + return glyphData{}, errInvalidCFFTable + } + ret.locations = make([]uint32, count+1) + if !p.parseIndexLocations(ret.locations, count, offSize) { + return glyphData{}, p.err + } + } + + if !p.psi.topDict.isCIDFont { + // Parse the Private DICT, whose location was found in the Top DICT. + ret.singleSubrs, err = p.parsePrivateDICT( + p.psi.topDict.privateDictOffset, + p.psi.topDict.privateDictLength, + ) + if err != nil { + return glyphData{}, err + } + + } else { + // Parse the Font Dict Select data, whose location was found in the Top + // DICT. + ret.fdSelect, err = p.parseFDSelect(p.psi.topDict.fdSelect, numGlyphs) + if err != nil { + return glyphData{}, err + } + + // Parse the Font Dicts. Each one contains its own Private DICT. + if !p.seekFromBase(p.psi.topDict.fdArray) { + return glyphData{}, errInvalidCFFTable + } + + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count > maxNumFontDicts { + return glyphData{}, errUnsupportedNumberOfFontDicts + } + + fdLocations := make([]uint32, count+1) + if !p.parseIndexLocations(fdLocations, count, offSize) { + return glyphData{}, p.err + } + + privateDicts := make([]struct { + offset, length int32 + }, count) + + for i := range privateDicts { + length := fdLocations[i+1] - fdLocations[i] + if !p.read(int(length)) { + return glyphData{}, errInvalidCFFTable + } + p.psi.topDict.initialize() + if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil { + return glyphData{}, p.err + } + privateDicts[i].offset = p.psi.topDict.privateDictOffset + privateDicts[i].length = p.psi.topDict.privateDictLength + } + + ret.multiSubrs = make([][]uint32, count) + for i, pd := range privateDicts { + ret.multiSubrs[i], err = p.parsePrivateDICT(pd.offset, pd.length) + if err != nil { + return glyphData{}, err + } + } + } + + return ret, err +} + +// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section +// 19 "FDSelect". +func (p *cffParser) parseFDSelect(offset int32, numGlyphs int32) (ret fdSelect, err error) { + if !p.seekFromBase(p.psi.topDict.fdSelect) { + return fdSelect{}, errInvalidCFFTable + } + if !p.read(1) { + return fdSelect{}, p.err + } + ret.format = p.buf[0] + switch ret.format { + case 0: + if p.end-p.offset < int(numGlyphs) { + return fdSelect{}, errInvalidCFFTable + } + ret.offset = int32(p.offset) + return ret, nil + case 3: + if !p.read(2) { + return fdSelect{}, p.err + } + ret.numRanges = u16(p.buf) + if p.end-p.offset < 3*int(ret.numRanges)+2 { + return fdSelect{}, errInvalidCFFTable + } + ret.offset = int32(p.offset) + return ret, nil + } + return fdSelect{}, errUnsupportedCFFFDSelectTable +} + +func (p *cffParser) parsePrivateDICT(offset, length int32) (subrs []uint32, err error) { + p.psi.privateDict.initialize() + if length != 0 { + fullLength := int32(p.end - p.base) + if offset <= 0 || fullLength < offset || fullLength-offset < length || length < 0 { + return nil, errInvalidCFFTable + } + p.offset = p.base + int(offset) + if !p.read(int(length)) { + return nil, p.err + } + if p.err = p.psi.run(psContextPrivateDict, p.buf, 0, 0); p.err != nil { + return nil, p.err + } + } + + // Parse the Local Subrs [Subroutines] INDEX, whose location was found in + // the Private DICT. + if p.psi.privateDict.subrsOffset != 0 { + if !p.seekFromBase(offset + p.psi.privateDict.subrsOffset) { + return nil, errInvalidCFFTable + } + count, offSize, ok := p.parseIndexHeader() + if !ok { + return nil, p.err + } + if count != 0 { + if count > maxNumSubroutines { + return nil, errUnsupportedNumberOfSubroutines + } + subrs = make([]uint32, count+1) + if !p.parseIndexLocations(subrs, count, offSize) { + return nil, p.err + } + } + } + + return subrs, err +} + +// read sets p.buf to view the n bytes from p.offset to p.offset+n. It also +// advances p.offset by n. +// +// As per the source.view method, the caller should not modify the contents of +// p.buf after read returns, other than by calling read again. +// +// The caller should also avoid modifying the pointer / length / capacity of +// the p.buf slice, not just avoid modifying the slice's contents, in order to +// maximize the opportunity to re-use p.buf's allocated memory when viewing the +// underlying source data for subsequent read calls. +func (p *cffParser) read(n int) (ok bool) { + if n < 0 || p.end-p.offset < n { + p.err = errInvalidCFFTable + return false + } + p.buf, p.err = p.src.view(p.buf, p.offset, n) + // TODO: if p.err == io.EOF, change that to a different error?? + p.offset += n + return p.err == nil +} + +func (p *cffParser) skip(n int) (ok bool) { + if p.end-p.offset < n { + p.err = errInvalidCFFTable + return false + } + p.offset += n + return true +} + +func (p *cffParser) seekFromBase(offset int32) (ok bool) { + if offset < 0 || int32(p.end-p.base) < offset { + return false + } + p.offset = p.base + int(offset) + return true +} + +func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) { + if !p.read(2) { + return 0, 0, false + } + count = int32(u16(p.buf[:2])) + // 5176.CFF.pdf section 5 "INDEX Data" says that "An empty INDEX is + // represented by a count field with a 0 value and no additional fields. + // Thus, the total size of an empty INDEX is 2 bytes". + if count == 0 { + return count, 0, true + } + if !p.read(1) { + return 0, 0, false + } + offSize = int32(p.buf[0]) + if offSize < 1 || 4 < offSize { + p.err = errInvalidCFFTable + return 0, 0, false + } + return count, offSize, true +} + +func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok bool) { + if count == 0 { + return true + } + if len(dst) != int(count+1) { + panic("unreachable") + } + if !p.read(len(dst) * int(offSize)) { + return false + } + + buf, prev := p.buf, uint32(0) + for i := range dst { + loc := bigEndian(buf[:offSize]) + buf = buf[offSize:] + + // Locations are off by 1 byte. 5176.CFF.pdf section 5 "INDEX Data" + // says that "Offsets in the offset array are relative to the byte that + // precedes the object data... This ensures that every object has a + // corresponding offset which is always nonzero". + if loc == 0 { + p.err = errInvalidCFFTable + return false + } + loc-- + + // In the same paragraph, "Therefore the first element of the offset + // array is always 1" before correcting for the off-by-1. + if i == 0 { + if loc != 0 { + p.err = errInvalidCFFTable + break + } + } else if loc <= prev { // Check that locations are increasing. + p.err = errInvalidCFFTable + break + } + + // Check that locations are in bounds. + if uint32(p.end-p.offset) < loc { + p.err = errInvalidCFFTable + break + } + + dst[i] = uint32(p.offset) + loc + prev = loc + } + return p.err == nil +} + +type psCallStackEntry struct { + offset, length uint32 +} + +type psContext uint32 + +const ( + psContextTopDict psContext = iota + psContextPrivateDict + psContextType2Charstring +) + +// psTopDictData contains fields specific to the Top DICT context. +type psTopDictData struct { + charStringsOffset int32 + fdArray int32 + fdSelect int32 + isCIDFont bool + privateDictOffset int32 + privateDictLength int32 +} + +func (d *psTopDictData) initialize() { + *d = psTopDictData{} +} + +// psPrivateDictData contains fields specific to the Private DICT context. +type psPrivateDictData struct { + subrsOffset int32 +} + +func (d *psPrivateDictData) initialize() { + *d = psPrivateDictData{} +} + +// psType2CharstringsData contains fields specific to the Type 2 Charstrings +// context. +type psType2CharstringsData struct { + f *Font + b *Buffer + x int32 + y int32 + firstX int32 + firstY int32 + hintBits int32 + seenWidth bool + ended bool + glyphIndex GlyphIndex + // fdSelectIndexPlusOne is the result of the Font Dict Select lookup, plus + // one. That plus one lets us use the zero value to denote either unused + // (for CFF fonts with a single Font Dict) or lazily evaluated. + fdSelectIndexPlusOne int32 +} + +func (d *psType2CharstringsData) initialize(f *Font, b *Buffer, glyphIndex GlyphIndex) { + *d = psType2CharstringsData{ + f: f, + b: b, + glyphIndex: glyphIndex, + } +} + +func (d *psType2CharstringsData) closePath() { + if d.x != d.firstX || d.y != d.firstY { + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{{ + X: fixed.Int26_6(d.firstX), + Y: fixed.Int26_6(d.firstY), + }}, + }) + } +} + +func (d *psType2CharstringsData) moveTo(dx, dy int32) { + d.closePath() + d.x += dx + d.y += dy + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{{ + X: fixed.Int26_6(d.x), + Y: fixed.Int26_6(d.y), + }}, + }) + d.firstX = d.x + d.firstY = d.y +} + +func (d *psType2CharstringsData) lineTo(dx, dy int32) { + d.x += dx + d.y += dy + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{{ + X: fixed.Int26_6(d.x), + Y: fixed.Int26_6(d.y), + }}, + }) +} + +func (d *psType2CharstringsData) cubeTo(dxa, dya, dxb, dyb, dxc, dyc int32) { + d.x += dxa + d.y += dya + xa := fixed.Int26_6(d.x) + ya := fixed.Int26_6(d.y) + d.x += dxb + d.y += dyb + xb := fixed.Int26_6(d.x) + yb := fixed.Int26_6(d.y) + d.x += dxc + d.y += dyc + xc := fixed.Int26_6(d.x) + yc := fixed.Int26_6(d.y) + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpCubeTo, + Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}}, + }) +} + +// psInterpreter is a PostScript interpreter. +type psInterpreter struct { + ctx psContext + instructions []byte + instrOffset uint32 + instrLength uint32 + argStack struct { + a [psArgStackSize]int32 + top int32 + } + callStack struct { + a [psCallStackSize]psCallStackEntry + top int32 + } + parseNumberBuf [maxRealNumberStrLen]byte + + topDict psTopDictData + privateDict psPrivateDictData + type2Charstrings psType2CharstringsData +} + +func (p *psInterpreter) hasMoreInstructions() bool { + if len(p.instructions) != 0 { + return true + } + for i := int32(0); i < p.callStack.top; i++ { + if p.callStack.a[i].length != 0 { + return true + } + } + return false +} + +// run runs the instructions in the given PostScript context. For the +// psContextType2Charstring context, offset and length give the location of the +// instructions in p.type2Charstrings.f.src. +func (p *psInterpreter) run(ctx psContext, instructions []byte, offset, length uint32) error { + p.ctx = ctx + p.instructions = instructions + p.instrOffset = offset + p.instrLength = length + p.argStack.top = 0 + p.callStack.top = 0 + +loop: + for len(p.instructions) > 0 { + // Push a numeric operand on the stack, if applicable. + if hasResult, err := p.parseNumber(); hasResult { + if err != nil { + return err + } + continue + } + + // Otherwise, execute an operator. + b := p.instructions[0] + p.instructions = p.instructions[1:] + + for escaped, ops := false, psOperators[ctx][0]; ; { + if b == escapeByte && !escaped { + if len(p.instructions) <= 0 { + return errInvalidCFFTable + } + b = p.instructions[0] + p.instructions = p.instructions[1:] + escaped = true + ops = psOperators[ctx][1] + continue + } + + if int(b) < len(ops) { + if op := ops[b]; op.name != "" { + if p.argStack.top < op.numPop { + return errInvalidCFFTable + } + if op.run != nil { + if err := op.run(p); err != nil { + return err + } + } + if op.numPop < 0 { + p.argStack.top = 0 + } else { + p.argStack.top -= op.numPop + } + continue loop + } + } + + if escaped { + return fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b) + } else { + return fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b) + } + } + } + return nil +} + +// See 5176.CFF.pdf section 4 "DICT Data". +func (p *psInterpreter) parseNumber() (hasResult bool, err error) { + number := int32(0) + switch b := p.instructions[0]; { + case b == 28: + if len(p.instructions) < 3 { + return true, errInvalidCFFTable + } + number, hasResult = int32(int16(u16(p.instructions[1:]))), true + p.instructions = p.instructions[3:] + + case b == 29 && p.ctx != psContextType2Charstring: + if len(p.instructions) < 5 { + return true, errInvalidCFFTable + } + number, hasResult = int32(u32(p.instructions[1:])), true + p.instructions = p.instructions[5:] + + case b == 30 && p.ctx != psContextType2Charstring: + // Parse a real number. This isn't listed in 5176.CFF.pdf Table 3 + // "Operand Encoding" but that table lists integer encodings. Further + // down the page it says "A real number operand is provided in addition + // to integer operands. This operand begins with a byte value of 30 + // followed by a variable-length sequence of bytes." + + s := p.parseNumberBuf[:0] + p.instructions = p.instructions[1:] + loop: + for { + if len(p.instructions) == 0 { + return true, errInvalidCFFTable + } + b := p.instructions[0] + p.instructions = p.instructions[1:] + // Process b's two nibbles, high then low. + for i := 0; i < 2; i++ { + nib := b >> 4 + b = b << 4 + if nib == 0x0f { + f, err := strconv.ParseFloat(string(s), 32) + if err != nil { + return true, errInvalidCFFTable + } + number, hasResult = int32(math.Float32bits(float32(f))), true + break loop + } + if nib == 0x0d { + return true, errInvalidCFFTable + } + if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) { + return true, errUnsupportedRealNumberEncoding + } + s = append(s, nibbleDefs[nib]...) + } + } + + case b < 32: + // No-op. + + case b < 247: + p.instructions = p.instructions[1:] + number, hasResult = int32(b)-139, true + + case b < 251: + if len(p.instructions) < 2 { + return true, errInvalidCFFTable + } + b1 := p.instructions[1] + p.instructions = p.instructions[2:] + number, hasResult = +int32(b-247)*256+int32(b1)+108, true + + case b < 255: + if len(p.instructions) < 2 { + return true, errInvalidCFFTable + } + b1 := p.instructions[1] + p.instructions = p.instructions[2:] + number, hasResult = -int32(b-251)*256-int32(b1)-108, true + + case b == 255 && p.ctx == psContextType2Charstring: + if len(p.instructions) < 5 { + return true, errInvalidCFFTable + } + number, hasResult = int32(u32(p.instructions[1:])), true + p.instructions = p.instructions[5:] + } + + if hasResult { + if p.argStack.top == psArgStackSize { + return true, errInvalidCFFTable + } + p.argStack.a[p.argStack.top] = number + p.argStack.top++ + } + return hasResult, nil +} + +const maxNibbleDefsLength = len("E-") + +// nibbleDefs encodes 5176.CFF.pdf Table 5 "Nibble Definitions". +var nibbleDefs = [16]string{ + 0x00: "0", + 0x01: "1", + 0x02: "2", + 0x03: "3", + 0x04: "4", + 0x05: "5", + 0x06: "6", + 0x07: "7", + 0x08: "8", + 0x09: "9", + 0x0a: ".", + 0x0b: "E", + 0x0c: "E-", + 0x0d: "", + 0x0e: "-", + 0x0f: "", +} + +type psOperator struct { + // numPop is the number of stack values to pop. -1 means "array" and -2 + // means "delta" as per 5176.CFF.pdf Table 6 "Operand Types". + numPop int32 + // name is the operator name. An empty name (i.e. the zero value for the + // struct overall) means an unrecognized 1-byte operator. + name string + // run is the function that implements the operator. Nil means that we + // ignore the operator, other than popping its arguments off the stack. + run func(*psInterpreter) error +} + +// psOperators holds the 1-byte and 2-byte operators for PostScript interpreter +// contexts. +var psOperators = [...][2][]psOperator{ + // The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT + // Operator Entries" and Table 10 "CIDFont Operator Extensions". + psContextTopDict: {{ + // 1-byte operators. + 0: {+1, "version", nil}, + 1: {+1, "Notice", nil}, + 2: {+1, "FullName", nil}, + 3: {+1, "FamilyName", nil}, + 4: {+1, "Weight", nil}, + 5: {-1, "FontBBox", nil}, + 13: {+1, "UniqueID", nil}, + 14: {-1, "XUID", nil}, + 15: {+1, "charset", nil}, + 16: {+1, "Encoding", nil}, + 17: {+1, "CharStrings", func(p *psInterpreter) error { + p.topDict.charStringsOffset = p.argStack.a[p.argStack.top-1] + return nil + }}, + 18: {+2, "Private", func(p *psInterpreter) error { + p.topDict.privateDictLength = p.argStack.a[p.argStack.top-2] + p.topDict.privateDictOffset = p.argStack.a[p.argStack.top-1] + return nil + }}, + }, { + // 2-byte operators. The first byte is the escape byte. + 0: {+1, "Copyright", nil}, + 1: {+1, "isFixedPitch", nil}, + 2: {+1, "ItalicAngle", nil}, + 3: {+1, "UnderlinePosition", nil}, + 4: {+1, "UnderlineThickness", nil}, + 5: {+1, "PaintType", nil}, + 6: {+1, "CharstringType", nil}, + 7: {-1, "FontMatrix", nil}, + 8: {+1, "StrokeWidth", nil}, + 20: {+1, "SyntheticBase", nil}, + 21: {+1, "PostScript", nil}, + 22: {+1, "BaseFontName", nil}, + 23: {-2, "BaseFontBlend", nil}, + 30: {+3, "ROS", func(p *psInterpreter) error { + p.topDict.isCIDFont = true + return nil + }}, + 31: {+1, "CIDFontVersion", nil}, + 32: {+1, "CIDFontRevision", nil}, + 33: {+1, "CIDFontType", nil}, + 34: {+1, "CIDCount", nil}, + 35: {+1, "UIDBase", nil}, + 36: {+1, "FDArray", func(p *psInterpreter) error { + p.topDict.fdArray = p.argStack.a[p.argStack.top-1] + return nil + }}, + 37: {+1, "FDSelect", func(p *psInterpreter) error { + p.topDict.fdSelect = p.argStack.a[p.argStack.top-1] + return nil + }}, + 38: {+1, "FontName", nil}, + }}, + + // The Private DICT operators are defined by 5176.CFF.pdf Table 23 "Private + // DICT Operators". + psContextPrivateDict: {{ + // 1-byte operators. + 6: {-2, "BlueValues", nil}, + 7: {-2, "OtherBlues", nil}, + 8: {-2, "FamilyBlues", nil}, + 9: {-2, "FamilyOtherBlues", nil}, + 10: {+1, "StdHW", nil}, + 11: {+1, "StdVW", nil}, + 19: {+1, "Subrs", func(p *psInterpreter) error { + p.privateDict.subrsOffset = p.argStack.a[p.argStack.top-1] + return nil + }}, + 20: {+1, "defaultWidthX", nil}, + 21: {+1, "nominalWidthX", nil}, + }, { + // 2-byte operators. The first byte is the escape byte. + 9: {+1, "BlueScale", nil}, + 10: {+1, "BlueShift", nil}, + 11: {+1, "BlueFuzz", nil}, + 12: {-2, "StemSnapH", nil}, + 13: {-2, "StemSnapV", nil}, + 14: {+1, "ForceBold", nil}, + 17: {+1, "LanguageGroup", nil}, + 18: {+1, "ExpansionFactor", nil}, + 19: {+1, "initialRandomSeed", nil}, + }}, + + // The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A + // "Type 2 Charstring Command Codes". + psContextType2Charstring: {{ + // 1-byte operators. + 0: {}, // Reserved. + 1: {-1, "hstem", t2CStem}, + 2: {}, // Reserved. + 3: {-1, "vstem", t2CStem}, + 4: {-1, "vmoveto", t2CVmoveto}, + 5: {-1, "rlineto", t2CRlineto}, + 6: {-1, "hlineto", t2CHlineto}, + 7: {-1, "vlineto", t2CVlineto}, + 8: {-1, "rrcurveto", t2CRrcurveto}, + 9: {}, // Reserved. + 10: {+1, "callsubr", t2CCallsubr}, + 11: {+0, "return", t2CReturn}, + 12: {}, // escape. + 13: {}, // Reserved. + 14: {-1, "endchar", t2CEndchar}, + 15: {}, // Reserved. + 16: {}, // Reserved. + 17: {}, // Reserved. + 18: {-1, "hstemhm", t2CStem}, + 19: {-1, "hintmask", t2CMask}, + 20: {-1, "cntrmask", t2CMask}, + 21: {-1, "rmoveto", t2CRmoveto}, + 22: {-1, "hmoveto", t2CHmoveto}, + 23: {-1, "vstemhm", t2CStem}, + 24: {-1, "rcurveline", t2CRcurveline}, + 25: {-1, "rlinecurve", t2CRlinecurve}, + 26: {-1, "vvcurveto", t2CVvcurveto}, + 27: {-1, "hhcurveto", t2CHhcurveto}, + 28: {}, // shortint. + 29: {+1, "callgsubr", t2CCallgsubr}, + 30: {-1, "vhcurveto", t2CVhcurveto}, + 31: {-1, "hvcurveto", t2CHvcurveto}, + }, { + // 2-byte operators. The first byte is the escape byte. + 34: {+7, "hflex", t2CHflex}, + 36: {+9, "hflex1", t2CHflex1}, + // TODO: more operators. + }}, +} + +// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an +// initial escape byte of 12". +const escapeByte = 12 + +// t2CReadWidth reads the optional width adjustment. If present, it is on the +// bottom of the arg stack. nArgs is the expected number of arguments on the +// stack. A negative nArgs means a multiple of 2. +// +// 5177.Type2.pdf page 16 Note 4 says: "The first stack-clearing operator, +// which must be one of hstem, hstemhm, vstem, vstemhm, cntrmask, hintmask, +// hmoveto, vmoveto, rmoveto, or endchar, takes an additional argument — the +// width... which may be expressed as zero or one numeric argument." +func t2CReadWidth(p *psInterpreter, nArgs int32) { + if p.type2Charstrings.seenWidth { + return + } + p.type2Charstrings.seenWidth = true + if nArgs >= 0 { + if p.argStack.top != nArgs+1 { + return + } + } else if p.argStack.top&1 == 0 { + return + } + // When parsing a standalone CFF, we'd save the value of p.argStack.a[0] + // here as it defines the glyph's width (horizontal advance). Specifically, + // if present, it is a delta to the font-global nominalWidthX value found + // in the Private DICT. If absent, the glyph's width is the defaultWidthX + // value in that dict. See 5176.CFF.pdf section 15 "Private DICT Data". + // + // For a CFF embedded in an SFNT font (i.e. an OpenType font), glyph widths + // are already stored in the hmtx table, separate to the CFF table, and it + // is simpler to parse that table for all OpenType fonts (PostScript and + // TrueType). We therefore ignore the width value here, and just remove it + // from the bottom of the argStack. + copy(p.argStack.a[:p.argStack.top-1], p.argStack.a[1:p.argStack.top]) + p.argStack.top-- +} + +func t2CStem(p *psInterpreter) error { + t2CReadWidth(p, -1) + if p.argStack.top%2 != 0 { + return errInvalidCFFTable + } + // We update the number of hintBits need to parse hintmask and cntrmask + // instructions, but this Type 2 Charstring implementation otherwise + // ignores the stem hints. + p.type2Charstrings.hintBits += p.argStack.top / 2 + if p.type2Charstrings.hintBits > maxHintBits { + return errUnsupportedNumberOfHints + } + return nil +} + +func t2CMask(p *psInterpreter) error { + // 5176.CFF.pdf section 4.3 "Hint Operators" says that "If hstem and vstem + // hints are both declared at the beginning of a charstring, and this + // sequence is followed directly by the hintmask or cntrmask operators, the + // vstem hint operator need not be included." + // + // What we implement here is more permissive (but the same as what the + // FreeType implementation does, and simpler than tracking the previous + // operator and other hinting state): if a hintmask is given any arguments + // (i.e. the argStack is non-empty), we run an implicit vstem operator. + // + // Note that the vstem operator consumes from p.argStack, but the hintmask + // or cntrmask operators consume from p.instructions. + if p.argStack.top != 0 { + if err := t2CStem(p); err != nil { + return err + } + } else if !p.type2Charstrings.seenWidth { + p.type2Charstrings.seenWidth = true + } + + hintBytes := (p.type2Charstrings.hintBits + 7) / 8 + if len(p.instructions) < int(hintBytes) { + return errInvalidCFFTable + } + p.instructions = p.instructions[hintBytes:] + return nil +} + +func t2CHmoveto(p *psInterpreter) error { + t2CReadWidth(p, 1) + if p.argStack.top != 1 { + return errInvalidCFFTable + } + p.type2Charstrings.moveTo(p.argStack.a[0], 0) + return nil +} + +func t2CVmoveto(p *psInterpreter) error { + t2CReadWidth(p, 1) + if p.argStack.top != 1 { + return errInvalidCFFTable + } + p.type2Charstrings.moveTo(0, p.argStack.a[0]) + return nil +} + +func t2CRmoveto(p *psInterpreter) error { + t2CReadWidth(p, 2) + if p.argStack.top != 2 { + return errInvalidCFFTable + } + p.type2Charstrings.moveTo(p.argStack.a[0], p.argStack.a[1]) + return nil +} + +func t2CHlineto(p *psInterpreter) error { return t2CLineto(p, false) } +func t2CVlineto(p *psInterpreter) error { return t2CLineto(p, true) } + +func t2CLineto(p *psInterpreter, vertical bool) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 1 { + return errInvalidCFFTable + } + for i := int32(0); i < p.argStack.top; i, vertical = i+1, !vertical { + dx, dy := p.argStack.a[i], int32(0) + if vertical { + dx, dy = dy, dx + } + p.type2Charstrings.lineTo(dx, dy) + } + return nil +} + +func t2CRlineto(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 2 || p.argStack.top%2 != 0 { + return errInvalidCFFTable + } + for i := int32(0); i < p.argStack.top; i += 2 { + p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1]) + } + return nil +} + +// As per 5177.Type2.pdf section 4.1 "Path Construction Operators", +// +// rcurveline is: +// - {dxa dya dxb dyb dxc dyc}+ dxd dyd +// +// rlinecurve is: +// - {dxa dya}+ dxb dyb dxc dyc dxd dyd + +func t2CRcurveline(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 8 || p.argStack.top%6 != 2 { + return errInvalidCFFTable + } + i := int32(0) + for iMax := p.argStack.top - 2; i < iMax; i += 6 { + p.type2Charstrings.cubeTo( + p.argStack.a[i+0], + p.argStack.a[i+1], + p.argStack.a[i+2], + p.argStack.a[i+3], + p.argStack.a[i+4], + p.argStack.a[i+5], + ) + } + p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1]) + return nil +} + +func t2CRlinecurve(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 8 || p.argStack.top%2 != 0 { + return errInvalidCFFTable + } + i := int32(0) + for iMax := p.argStack.top - 6; i < iMax; i += 2 { + p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1]) + } + p.type2Charstrings.cubeTo( + p.argStack.a[i+0], + p.argStack.a[i+1], + p.argStack.a[i+2], + p.argStack.a[i+3], + p.argStack.a[i+4], + p.argStack.a[i+5], + ) + return nil +} + +// As per 5177.Type2.pdf section 4.1 "Path Construction Operators", +// +// hhcurveto is: +// - dy1 {dxa dxb dyb dxc}+ +// +// vvcurveto is: +// - dx1 {dya dxb dyb dyc}+ +// +// hvcurveto is one of: +// - dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? +// - {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? +// +// vhcurveto is one of: +// - dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? +// - {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? + +func t2CHhcurveto(p *psInterpreter) error { return t2CCurveto(p, false, false) } +func t2CVvcurveto(p *psInterpreter) error { return t2CCurveto(p, false, true) } +func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, true, false) } +func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true, true) } + +// t2CCurveto implements the hh / vv / hv / vh xxcurveto operators. N relative +// cubic curve requires 6*N control points, but only 4*N+0 or 4*N+1 are used +// here: all (or all but one) of the piecewise cubic curve's tangents are +// implicitly horizontal or vertical. +// +// swap is whether that implicit horizontal / vertical constraint swaps as you +// move along the piecewise cubic curve. If swap is false, the constraints are +// either all horizontal or all vertical. If swap is true, it alternates. +// +// vertical is whether the first implicit constraint is vertical. +func t2CCurveto(p *psInterpreter, swap, vertical bool) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 4 { + return errInvalidCFFTable + } + + i := int32(0) + switch p.argStack.top & 3 { + case 0: + // No-op. + case 1: + if swap { + break + } + i = 1 + if vertical { + p.type2Charstrings.x += p.argStack.a[0] + } else { + p.type2Charstrings.y += p.argStack.a[0] + } + default: + return errInvalidCFFTable + } + + for i != p.argStack.top { + i = t2CCurveto4(p, swap, vertical, i) + if i < 0 { + return errInvalidCFFTable + } + if swap { + vertical = !vertical + } + } + return nil +} + +func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32) { + if i+4 > p.argStack.top { + return -1 + } + dxa := p.argStack.a[i+0] + dya := int32(0) + dxb := p.argStack.a[i+1] + dyb := p.argStack.a[i+2] + dxc := p.argStack.a[i+3] + dyc := int32(0) + i += 4 + + if vertical { + dxa, dya = dya, dxa + } + + if swap { + if i+1 == p.argStack.top { + dyc = p.argStack.a[i] + i++ + } + } + + if swap != vertical { + dxc, dyc = dyc, dxc + } + + p.type2Charstrings.cubeTo(dxa, dya, dxb, dyb, dxc, dyc) + return i +} + +func t2CRrcurveto(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 6 || p.argStack.top%6 != 0 { + return errInvalidCFFTable + } + for i := int32(0); i != p.argStack.top; i += 6 { + p.type2Charstrings.cubeTo( + p.argStack.a[i+0], + p.argStack.a[i+1], + p.argStack.a[i+2], + p.argStack.a[i+3], + p.argStack.a[i+4], + p.argStack.a[i+5], + ) + } + return nil +} + +// For the flex operators, we ignore the flex depth and always produce cubic +// segments, not linear segments. It's not obvious why the Type 2 Charstring +// format cares about switching behavior based on a metric in pixels, not in +// ideal font units. The Go vector rasterizer has no problems with almost +// linear cubic segments. + +func t2CHflex(p *psInterpreter) error { + p.type2Charstrings.cubeTo( + p.argStack.a[0], 0, + p.argStack.a[1], +p.argStack.a[2], + p.argStack.a[3], 0, + ) + p.type2Charstrings.cubeTo( + p.argStack.a[4], 0, + p.argStack.a[5], -p.argStack.a[2], + p.argStack.a[6], 0, + ) + return nil +} + +func t2CHflex1(p *psInterpreter) error { + dy1 := p.argStack.a[1] + dy2 := p.argStack.a[3] + dy5 := p.argStack.a[7] + dy6 := -dy1 - dy2 - dy5 + p.type2Charstrings.cubeTo( + p.argStack.a[0], dy1, + p.argStack.a[2], dy2, + p.argStack.a[4], 0, + ) + p.type2Charstrings.cubeTo( + p.argStack.a[5], 0, + p.argStack.a[6], dy5, + p.argStack.a[8], dy6, + ) + return nil +} + +// subrBias returns the subroutine index bias as per 5177.Type2.pdf section 4.7 +// "Subroutine Operators". +func subrBias(numSubroutines int) int32 { + if numSubroutines < 1240 { + return 107 + } + if numSubroutines < 33900 { + return 1131 + } + return 32768 +} + +func t2CCallgsubr(p *psInterpreter) error { + return t2CCall(p, p.type2Charstrings.f.cached.glyphData.gsubrs) +} + +func t2CCallsubr(p *psInterpreter) error { + t := &p.type2Charstrings + d := &t.f.cached.glyphData + subrs := d.singleSubrs + if d.multiSubrs != nil { + if t.fdSelectIndexPlusOne == 0 { + index, err := d.fdSelect.lookup(t.f, t.b, t.glyphIndex) + if err != nil { + return err + } + if index < 0 || len(d.multiSubrs) <= index { + return errInvalidCFFTable + } + t.fdSelectIndexPlusOne = int32(index + 1) + } + subrs = d.multiSubrs[t.fdSelectIndexPlusOne-1] + } + return t2CCall(p, subrs) +} + +func t2CCall(p *psInterpreter, subrs []uint32) error { + if p.callStack.top == psCallStackSize || len(subrs) == 0 { + return errInvalidCFFTable + } + length := uint32(len(p.instructions)) + p.callStack.a[p.callStack.top] = psCallStackEntry{ + offset: p.instrOffset + p.instrLength - length, + length: length, + } + p.callStack.top++ + + subrIndex := p.argStack.a[p.argStack.top-1] + subrBias(len(subrs)-1) + if subrIndex < 0 || int32(len(subrs)-1) <= subrIndex { + return errInvalidCFFTable + } + i := subrs[subrIndex+0] + j := subrs[subrIndex+1] + if j < i { + return errInvalidCFFTable + } + if j-i > maxGlyphDataLength { + return errUnsupportedGlyphDataLength + } + buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(i), int(j-i)) + if err != nil { + return err + } + + p.instructions = buf + p.instrOffset = i + p.instrLength = j - i + return nil +} + +func t2CReturn(p *psInterpreter) error { + if p.callStack.top <= 0 { + return errInvalidCFFTable + } + p.callStack.top-- + o := p.callStack.a[p.callStack.top].offset + n := p.callStack.a[p.callStack.top].length + buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(o), int(n)) + if err != nil { + return err + } + + p.instructions = buf + p.instrOffset = o + p.instrLength = n + return nil +} + +func t2CEndchar(p *psInterpreter) error { + t2CReadWidth(p, 0) + if p.argStack.top != 0 || p.hasMoreInstructions() { + if p.argStack.top == 4 { + // TODO: process the implicit "seac" command as per 5177.Type2.pdf + // Appendix C "Compatibility and Deprecated Operators". + return errUnsupportedType2Charstring + } + return errInvalidCFFTable + } + p.type2Charstrings.closePath() + p.type2Charstrings.ended = true + return nil +} diff --git a/vendor/golang.org/x/image/font/sfnt/proprietary_test.go b/vendor/golang.org/x/image/font/sfnt/proprietary_test.go new file mode 100644 index 0000000..bb14a34 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/proprietary_test.go @@ -0,0 +1,1390 @@ +// Copyright 2017 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. + +package sfnt + +/* +This file contains opt-in tests for popular, high quality, proprietary fonts, +made by companies such as Adobe and Microsoft. These fonts are generally +available, but copies are not explicitly included in this repository due to +licensing differences or file size concerns. To opt-in, run: + +go test golang.org/x/image/font/sfnt -args -proprietary + +Not all tests pass out-of-the-box on all systems. For example, the Microsoft +Times New Roman font is downloadable gratis even on non-Windows systems, but as +per the ttf-mscorefonts-installer Debian package, this requires accepting an +End User License Agreement (EULA) and a CAB format decoder. These tests assume +that such fonts have already been installed. You may need to specify the +directories for these fonts: + +go test golang.org/x/image/font/sfnt -args -proprietary \ + -adobeDir=$HOME/fonts/adobe \ + -appleDir=$HOME/fonts/apple \ + -dejavuDir=$HOME/fonts/dejavu \ + -microsoftDir=$HOME/fonts/microsoft \ + -notoDir=$HOME/fonts/noto + +To only run those tests for the Microsoft fonts: + +go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary etc +*/ + +// TODO: enable Apple/Microsoft tests by default on Darwin/Windows? + +import ( + "errors" + "flag" + "io/ioutil" + "path/filepath" + "strconv" + "strings" + "testing" + + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" +) + +var ( + proprietary = flag.Bool("proprietary", false, "test proprietary fonts not included in this repository") + + adobeDir = flag.String( + "adobeDir", + // This needs to be set explicitly. There is no default dir on Debian: + // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=736680 + // + // Get the fonts from https://github.com/adobe-fonts, e.g.: + // - https://github.com/adobe-fonts/source-code-pro/releases/latest + // - https://github.com/adobe-fonts/source-han-sans/releases/latest + // - https://github.com/adobe-fonts/source-sans-pro/releases/latest + // + // Copy all of the TTF and OTF files to the one directory, such as + // $HOME/adobe-fonts, and pass that as the -adobeDir flag here. + "", + "directory name for the Adobe proprietary fonts", + ) + + appleDir = flag.String( + "appleDir", + // This needs to be set explicitly. These fonts come with macOS, which + // is widely available but not freely available. + // + // On a Mac, set this to "/System/Library/Fonts/". + "", + "directory name for the Apple proprietary fonts", + ) + + dejavuDir = flag.String( + "dejavuDir", + // Get the fonts from https://dejavu-fonts.github.io/ + "", + "directory name for the DejaVu proprietary fonts", + ) + + microsoftDir = flag.String( + "microsoftDir", + "/usr/share/fonts/truetype/msttcorefonts", + "directory name for the Microsoft proprietary fonts", + ) + + notoDir = flag.String( + "notoDir", + // Get the fonts from https://www.google.com/get/noto/ + "", + "directory name for the Noto proprietary fonts", + ) +) + +func TestProprietaryAdobeSourceCodeProRegularOTF(t *testing.T) { + testProprietary(t, "adobe", "SourceCodePro-Regular.otf", 1500, -1) +} + +func TestProprietaryAdobeSourceCodeProRegularTTF(t *testing.T) { + testProprietary(t, "adobe", "SourceCodePro-Regular.ttf", 1500, -1) +} + +func TestProprietaryAdobeSourceHanSansSCRegularOTF(t *testing.T) { + testProprietary(t, "adobe", "SourceHanSansSC-Regular.otf", 65535, -1) +} + +func TestProprietaryAdobeSourceSansProBlackOTF(t *testing.T) { + testProprietary(t, "adobe", "SourceSansPro-Black.otf", 1900, -1) +} + +func TestProprietaryAdobeSourceSansProBlackTTF(t *testing.T) { + testProprietary(t, "adobe", "SourceSansPro-Black.ttf", 1900, -1) +} + +func TestProprietaryAdobeSourceSansProRegularOTF(t *testing.T) { + testProprietary(t, "adobe", "SourceSansPro-Regular.otf", 1900, -1) +} + +func TestProprietaryAdobeSourceSansProRegularTTF(t *testing.T) { + testProprietary(t, "adobe", "SourceSansPro-Regular.ttf", 1900, -1) +} + +func TestProprietaryAppleAppleSymbols(t *testing.T) { + testProprietary(t, "apple", "Apple Symbols.ttf", 4600, -1) +} + +func TestProprietaryAppleGeezaPro0(t *testing.T) { + testProprietary(t, "apple", "GeezaPro.ttc?0", 1700, -1) +} + +func TestProprietaryAppleGeezaPro1(t *testing.T) { + testProprietary(t, "apple", "GeezaPro.ttc?1", 1700, -1) +} + +func TestProprietaryAppleHelvetica0(t *testing.T) { + testProprietary(t, "apple", "Helvetica.dfont?0", 2100, -1) +} + +func TestProprietaryAppleHelvetica1(t *testing.T) { + testProprietary(t, "apple", "Helvetica.dfont?1", 2100, -1) +} + +func TestProprietaryAppleHelvetica2(t *testing.T) { + testProprietary(t, "apple", "Helvetica.dfont?2", 2100, -1) +} + +func TestProprietaryAppleHelvetica3(t *testing.T) { + testProprietary(t, "apple", "Helvetica.dfont?3", 2100, -1) +} + +func TestProprietaryAppleHelvetica4(t *testing.T) { + testProprietary(t, "apple", "Helvetica.dfont?4", 1300, -1) +} + +func TestProprietaryAppleHelvetica5(t *testing.T) { + testProprietary(t, "apple", "Helvetica.dfont?5", 1300, -1) +} + +func TestProprietaryAppleHiragino0(t *testing.T) { + testProprietary(t, "apple", "ヒラギノ角ゴシック W0.ttc?0", 9000, -1) +} + +func TestProprietaryAppleHiragino1(t *testing.T) { + testProprietary(t, "apple", "ヒラギノ角ゴシック W0.ttc?1", 9000, -1) +} + +func TestProprietaryDejaVuSansExtraLight(t *testing.T) { + testProprietary(t, "dejavu", "DejaVuSans-ExtraLight.ttf", 2000, -1) +} + +func TestProprietaryDejaVuSansMono(t *testing.T) { + testProprietary(t, "dejavu", "DejaVuSansMono.ttf", 3300, -1) +} + +func TestProprietaryDejaVuSerif(t *testing.T) { + testProprietary(t, "dejavu", "DejaVuSerif.ttf", 3500, -1) +} + +func TestProprietaryMicrosoftArial(t *testing.T) { + testProprietary(t, "microsoft", "Arial.ttf", 1200, -1) +} + +func TestProprietaryMicrosoftArialAsACollection(t *testing.T) { + testProprietary(t, "microsoft", "Arial.ttf?0", 1200, -1) +} + +func TestProprietaryMicrosoftComicSansMS(t *testing.T) { + testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, -1) +} + +func TestProprietaryMicrosoftTimesNewRoman(t *testing.T) { + testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, -1) +} + +func TestProprietaryMicrosoftWebdings(t *testing.T) { + testProprietary(t, "microsoft", "Webdings.ttf", 200, -1) +} + +func TestProprietaryNotoColorEmoji(t *testing.T) { + testProprietary(t, "noto", "NotoColorEmoji.ttf", 2300, -1) +} + +func TestProprietaryNotoSansRegular(t *testing.T) { + testProprietary(t, "noto", "NotoSans-Regular.ttf", 2400, -1) +} + +// testProprietary tests that we can load every glyph in the named font. +// +// The exact number of glyphs in the font can differ across its various +// versions, but as a sanity check, there should be at least minNumGlyphs. +// +// While this package is a work-in-progress, not every glyph can be loaded. The +// firstUnsupportedGlyph argument, if non-negative, is the index of the first +// unsupported glyph in the font. This number should increase over time (or set +// negative), as the TODO's in this package are done. +func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, firstUnsupportedGlyph int) { + if !*proprietary { + t.Skip("skipping proprietary font test") + } + + basename, fontIndex, err := filename, -1, error(nil) + if i := strings.IndexByte(filename, '?'); i >= 0 { + fontIndex, err = strconv.Atoi(filename[i+1:]) + if err != nil { + t.Fatalf("could not parse collection font index from filename %q", filename) + } + basename = filename[:i] + } + + dir := "" + switch proprietor { + case "adobe": + dir = *adobeDir + case "apple": + dir = *appleDir + case "dejavu": + dir = *dejavuDir + case "microsoft": + dir = *microsoftDir + case "noto": + dir = *notoDir + default: + panic("unreachable") + } + file, err := ioutil.ReadFile(filepath.Join(dir, basename)) + if err != nil { + t.Fatalf("%v\nPerhaps you need to set the -%sDir flag?", err, proprietor) + } + qualifiedFilename := proprietor + "/" + filename + + f := (*Font)(nil) + if fontIndex >= 0 { + c, err := ParseCollection(file) + if err != nil { + t.Fatalf("ParseCollection: %v", err) + } + if want, ok := proprietaryNumFonts[qualifiedFilename]; ok { + if got := c.NumFonts(); got != want { + t.Fatalf("NumFonts: got %d, want %d", got, want) + } + } + f, err = c.Font(fontIndex) + if err != nil { + t.Fatalf("Font: %v", err) + } + } else { + f, err = Parse(file) + if err != nil { + t.Fatalf("Parse: %v", err) + } + } + + ppem := fixed.Int26_6(f.UnitsPerEm()) + var buf Buffer + + // Some of the tests below, such as which glyph index a particular rune + // maps to, can depend on the specific version of the proprietary font. If + // tested against a different version of that font, the test might (but not + // necessarily will) fail, even though the Go code is good. If so, log a + // message, but don't automatically fail (i.e. dont' call t.Fatalf). + gotVersion, err := f.Name(&buf, NameIDVersion) + if err != nil { + t.Fatalf("Name(Version): %v", err) + } + wantVersion := proprietaryVersions[qualifiedFilename] + if gotVersion != wantVersion { + t.Logf("font version provided differs from the one the tests were written against:"+ + "\ngot %q\nwant %q", gotVersion, wantVersion) + } + + gotFull, err := f.Name(&buf, NameIDFull) + if err != nil { + t.Fatalf("Name(Full): %v", err) + } + wantFull := proprietaryFullNames[qualifiedFilename] + if gotFull != wantFull { + t.Fatalf("Name(Full):\ngot %q\nwant %q", gotFull, wantFull) + } + + numGlyphs := f.NumGlyphs() + if numGlyphs < minNumGlyphs { + t.Fatalf("NumGlyphs: got %d, want at least %d", numGlyphs, minNumGlyphs) + } + + iMax := numGlyphs + if firstUnsupportedGlyph >= 0 { + iMax = firstUnsupportedGlyph + } + for i, numErrors := 0, 0; i < iMax; i++ { + if _, err := f.LoadGlyph(&buf, GlyphIndex(i), ppem, nil); err != nil && err != ErrColoredGlyph { + t.Errorf("LoadGlyph(%d): %v", i, err) + numErrors++ + } + if numErrors == 10 { + t.Fatal("LoadGlyph: too many errors") + } + } + + for r, want := range proprietaryGlyphIndexTestCases[qualifiedFilename] { + got, err := f.GlyphIndex(&buf, r) + if err != nil { + t.Errorf("GlyphIndex(%q): %v", r, err) + continue + } + if got != want { + t.Errorf("GlyphIndex(%q): got %d, want %d", r, got, want) + continue + } + } + + for r, want := range proprietaryGlyphTestCases[qualifiedFilename] { + x, err := f.GlyphIndex(&buf, r) + if err != nil { + t.Errorf("GlyphIndex(%q): %v", r, err) + continue + } + got, err := f.LoadGlyph(&buf, x, ppem, nil) + if err != nil { + t.Errorf("LoadGlyph(%q): %v", r, err) + continue + } + if err := checkSegmentsEqual(got, want); err != nil { + t.Errorf("LoadGlyph(%q): %v", r, err) + continue + } + } + +kernLoop: + for _, tc := range proprietaryKernTestCases[qualifiedFilename] { + var indexes [2]GlyphIndex + for i := range indexes { + x, err := f.GlyphIndex(&buf, tc.runes[i]) + if x == 0 && err == nil { + err = errors.New("no glyph index found") + } + if err != nil { + t.Errorf("GlyphIndex(%q): %v", tc.runes[0], err) + continue kernLoop + } + indexes[i] = x + } + kern, err := f.Kern(&buf, indexes[0], indexes[1], tc.ppem, tc.hinting) + if err != nil { + t.Errorf("Kern(%q, %q, ppem=%d, hinting=%v): %v", + tc.runes[0], tc.runes[1], tc.ppem, tc.hinting, err) + continue + } + if got := Units(kern); got != tc.want { + t.Errorf("Kern(%q, %q, ppem=%d, hinting=%v): got %d, want %d", + tc.runes[0], tc.runes[1], tc.ppem, tc.hinting, got, tc.want) + continue + } + } + + for x, want := range proprietaryFDSelectTestCases[qualifiedFilename] { + got, err := f.cached.glyphData.fdSelect.lookup(f, &buf, x) + if err != nil { + t.Errorf("fdSelect.lookup(%d): %v", x, err) + continue + } + if got != want { + t.Errorf("fdSelect.lookup(%d): got %d, want %d", x, got, want) + continue + } + } +} + +// proprietaryNumFonts holds the expected number of fonts in each collection, +// or 1 for a single font. It is not necessarily an exhaustive list of all +// proprietary fonts tested. +var proprietaryNumFonts = map[string]int{ + "apple/Helvetica.dfont?0": 6, + "apple/ヒラギノ角ゴシック W0.ttc?0": 2, + "microsoft/Arial.ttf?0": 1, +} + +// proprietaryVersions holds the expected version string of each proprietary +// font tested. If third parties such as Adobe or Microsoft update their fonts, +// and the tests subsequently fail, these versions should be updated too. +// +// Updates are expected to be infrequent. For example, as of 2017, the fonts +// installed by the Debian ttf-mscorefonts-installer package have last modified +// times no later than 2001. +var proprietaryVersions = map[string]string{ + "adobe/SourceCodePro-Regular.otf": "Version 2.030;PS 1.0;hotconv 16.6.51;makeotf.lib2.5.65220", + "adobe/SourceCodePro-Regular.ttf": "Version 2.030;PS 1.000;hotconv 16.6.51;makeotf.lib2.5.65220", + "adobe/SourceHanSansSC-Regular.otf": "Version 1.004;PS 1.004;hotconv 1.0.82;makeotf.lib2.5.63406", + "adobe/SourceSansPro-Black.otf": "Version 2.020;PS 2.0;hotconv 1.0.86;makeotf.lib2.5.63406", + "adobe/SourceSansPro-Black.ttf": "Version 2.020;PS 2.000;hotconv 1.0.86;makeotf.lib2.5.63406", + "adobe/SourceSansPro-Regular.otf": "Version 2.020;PS 2.0;hotconv 1.0.86;makeotf.lib2.5.63406", + "adobe/SourceSansPro-Regular.ttf": "Version 2.020;PS 2.000;hotconv 1.0.86;makeotf.lib2.5.63406", + + "apple/Apple Symbols.ttf": "12.0d3e10", + "apple/GeezaPro.ttc?0": "12.0d1e3", + "apple/GeezaPro.ttc?1": "12.0d1e3", + "apple/Helvetica.dfont?0": "12.0d1e3", + "apple/Helvetica.dfont?1": "12.0d1e3", + "apple/Helvetica.dfont?2": "12.0d1e3", + "apple/Helvetica.dfont?3": "12.0d1e3", + "apple/Helvetica.dfont?4": "12.0d1e3", + "apple/Helvetica.dfont?5": "12.0d1e3", + "apple/ヒラギノ角ゴシック W0.ttc?0": "11.0d7e1", + "apple/ヒラギノ角ゴシック W0.ttc?1": "11.0d7e1", + + "dejavu/DejaVuSans-ExtraLight.ttf": "Version 2.37", + "dejavu/DejaVuSansMono.ttf": "Version 2.37", + "dejavu/DejaVuSerif.ttf": "Version 2.37", + + "microsoft/Arial.ttf": "Version 2.82", + "microsoft/Arial.ttf?0": "Version 2.82", + "microsoft/Comic_Sans_MS.ttf": "Version 2.10", + "microsoft/Times_New_Roman.ttf": "Version 2.82", + "microsoft/Webdings.ttf": "Version 1.03", + + "noto/NotoColorEmoji.ttf": "Version 1.33", + "noto/NotoSans-Regular.ttf": "Version 1.06", +} + +// proprietaryFullNames holds the expected full name of each proprietary font +// tested. +var proprietaryFullNames = map[string]string{ + "adobe/SourceCodePro-Regular.otf": "Source Code Pro", + "adobe/SourceCodePro-Regular.ttf": "Source Code Pro", + "adobe/SourceHanSansSC-Regular.otf": "Source Han Sans SC Regular", + "adobe/SourceSansPro-Black.otf": "Source Sans Pro Black", + "adobe/SourceSansPro-Black.ttf": "Source Sans Pro Black", + "adobe/SourceSansPro-Regular.otf": "Source Sans Pro", + "adobe/SourceSansPro-Regular.ttf": "Source Sans Pro", + + "apple/Apple Symbols.ttf": "Apple Symbols", + "apple/GeezaPro.ttc?0": "Geeza Pro Regular", + "apple/GeezaPro.ttc?1": "Geeza Pro Bold", + "apple/Helvetica.dfont?0": "Helvetica", + "apple/Helvetica.dfont?1": "Helvetica Bold", + "apple/Helvetica.dfont?2": "Helvetica Oblique", + "apple/Helvetica.dfont?3": "Helvetica Bold Oblique", + "apple/Helvetica.dfont?4": "Helvetica Light", + "apple/Helvetica.dfont?5": "Helvetica Light Oblique", + "apple/ヒラギノ角ゴシック W0.ttc?0": "Hiragino Sans W0", + "apple/ヒラギノ角ゴシック W0.ttc?1": ".Hiragino Kaku Gothic Interface W0", + + "dejavu/DejaVuSans-ExtraLight.ttf": "DejaVu Sans ExtraLight", + "dejavu/DejaVuSansMono.ttf": "DejaVu Sans Mono", + "dejavu/DejaVuSerif.ttf": "DejaVu Serif", + + "microsoft/Arial.ttf": "Arial", + "microsoft/Arial.ttf?0": "Arial", + "microsoft/Comic_Sans_MS.ttf": "Comic Sans MS", + "microsoft/Times_New_Roman.ttf": "Times New Roman", + "microsoft/Webdings.ttf": "Webdings", + + "noto/NotoColorEmoji.ttf": "Noto Color Emoji", + "noto/NotoSans-Regular.ttf": "Noto Sans", +} + +// proprietaryGlyphIndexTestCases hold a sample of each font's rune to glyph +// index cmap. The numerical values can be verified by running the ttx tool. +var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{ + "adobe/SourceCodePro-Regular.otf": { + '\u0030': 877, // U+0030 DIGIT ZERO + '\u0041': 2, // U+0041 LATIN CAPITAL LETTER A + '\u0061': 28, // U+0061 LATIN SMALL LETTER A + '\u0104': 64, // U+0104 LATIN CAPITAL LETTER A WITH OGONEK + '\u0125': 323, // U+0125 LATIN SMALL LETTER H WITH CIRCUMFLEX + '\u01f4': 111, // U+01F4 LATIN CAPITAL LETTER G WITH ACUTE + '\u03a3': 623, // U+03A3 GREEK CAPITAL LETTER SIGMA + '\u2569': 1500, // U+2569 BOX DRAWINGS DOUBLE UP AND HORIZONTAL + '\U0001f100': 0, // U+0001F100 DIGIT ZERO FULL STOP + }, + "adobe/SourceCodePro-Regular.ttf": { + '\u0030': 877, // U+0030 DIGIT ZERO + '\u0041': 2, // U+0041 LATIN CAPITAL LETTER A + '\u01f4': 111, // U+01F4 LATIN CAPITAL LETTER G WITH ACUTE + }, + "adobe/SourceHanSansSC-Regular.otf": { + '\u0030': 17, // U+0030 DIGIT ZERO + '\u0041': 34, // U+0041 LATIN CAPITAL LETTER A + '\u00d7': 150, // U+00D7 MULTIPLICATION SIGN + '\u1100': 365, // U+1100 HANGUL CHOSEONG KIYEOK + '\u25ca': 1254, // U+25CA LOZENGE + '\u2e9c': 1359, // U+2E9C CJK RADICAL SUN + '\u304b': 1463, // U+304B HIRAGANA LETTER KA + '\u4e2d': 9893, // U+4E2D , 中 + '\ua960': 47537, // U+A960 HANGUL CHOSEONG TIKEUT-MIEUM + '\ufb00': 58919, // U+FB00 LATIN SMALL LIGATURE FF + '\uffee': 59213, // U+FFEE HALFWIDTH WHITE CIRCLE + '\U0001f100': 59214, // U+0001F100 DIGIT ZERO FULL STOP + '\U0001f248': 59449, // U+0001F248 TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 + '\U0002f9f4': 61768, // U+0002F9F4 CJK COMPATIBILITY IDEOGRAPH-2F9F4 + }, + "adobe/SourceSansPro-Regular.otf": { + '\u0041': 2, // U+0041 LATIN CAPITAL LETTER A + '\u03a3': 592, // U+03A3 GREEK CAPITAL LETTER SIGMA + '\u0435': 999, // U+0435 CYRILLIC SMALL LETTER IE + '\u2030': 1728, // U+2030 PER MILLE SIGN + }, + "adobe/SourceSansPro-Regular.ttf": { + '\u0041': 2, // U+0041 LATIN CAPITAL LETTER A + '\u03a3': 592, // U+03A3 GREEK CAPITAL LETTER SIGMA + '\u0435': 999, // U+0435 CYRILLIC SMALL LETTER IE + '\u2030': 1728, // U+2030 PER MILLE SIGN + }, + + "apple/Helvetica.dfont?0": { + '\u0041': 36, // U+0041 LATIN CAPITAL LETTER A + '\u00f1': 120, // U+00F1 LATIN SMALL LETTER N WITH TILDE + '\u0401': 473, // U+0401 CYRILLIC CAPITAL LETTER IO + '\u200d': 611, // U+200D ZERO WIDTH JOINER + '\u20ab': 1743, // U+20AB DONG SIGN + '\u2229': 0, // U+2229 INTERSECTION + '\u04e9': 1208, // U+04E9 CYRILLIC SMALL LETTER BARRED O + '\U0001f100': 0, // U+0001F100 DIGIT ZERO FULL STOP + }, + + "dejavu/DejaVuSerif.ttf": { + '\u0041': 36, // U+0041 LATIN CAPITAL LETTER A + '\u1e00': 1418, // U+1E00 LATIN CAPITAL LETTER A WITH RING BELOW + }, + + "microsoft/Arial.ttf": { + '\u0041': 36, // U+0041 LATIN CAPITAL LETTER A + '\u00f1': 120, // U+00F1 LATIN SMALL LETTER N WITH TILDE + '\u0401': 556, // U+0401 CYRILLIC CAPITAL LETTER IO + '\u200d': 745, // U+200D ZERO WIDTH JOINER + '\u20ab': 1150, // U+20AB DONG SIGN + '\u2229': 320, // U+2229 INTERSECTION + '\u04e9': 1319, // U+04E9 CYRILLIC SMALL LETTER BARRED O + '\U0001f100': 0, // U+0001F100 DIGIT ZERO FULL STOP + }, + "microsoft/Comic_Sans_MS.ttf": { + '\u0041': 36, // U+0041 LATIN CAPITAL LETTER A + '\u03af': 573, // U+03AF GREEK SMALL LETTER IOTA WITH TONOS + }, + "microsoft/Times_New_Roman.ttf": { + '\u0041': 36, // U+0041 LATIN CAPITAL LETTER A + '\u0042': 37, // U+0041 LATIN CAPITAL LETTER B + '\u266a': 392, // U+266A EIGHTH NOTE + '\uf041': 0, // PRIVATE USE AREA + '\uf042': 0, // PRIVATE USE AREA + }, + "microsoft/Webdings.ttf": { + '\u0041': 0, // U+0041 LATIN CAPITAL LETTER A + '\u0042': 0, // U+0041 LATIN CAPITAL LETTER B + '\u266a': 0, // U+266A EIGHTH NOTE + '\uf041': 36, // PRIVATE USE AREA + '\uf042': 37, // PRIVATE USE AREA + }, +} + +// proprietaryGlyphTestCases hold a sample of each font's glyph vectors. The +// numerical values can be verified by running the ttx tool, remembering that: +// - for PostScript glyphs, ttx coordinates are relative. +// - for TrueType glyphs, ttx coordinates are absolute, and consecutive +// off-curve points implies an on-curve point at the midpoint. +var proprietaryGlyphTestCases = map[string]map[rune][]Segment{ + "adobe/SourceHanSansSC-Regular.otf": { + '!': { + // -312 123 callsubr # 123 + bias = 230 + // : # Arg stack is [-312]. + // : -13 140 -119 -21 return + // : # Arg stack is [-312 -13 140 -119 -21]. + // 120 callsubr # 120 + bias = 227 + // : # Arg stack is [-312 -13 140 -119 -21]. + // : hstemhm + // : 95 132 -103 75 return + // : # Arg stack is [95 132 -103 75]. + // hintmask 01010000 + // 8 callsubr # 8 + bias = 115 + // : # Arg stack is []. + // : 130 221 rmoveto + moveTo(130, 221), + // : 63 hlineto + lineTo(193, 221), + // : 12 424 3 -735 callgsubr # -735 + bias = 396 + // : : # Arg stack is [12 424 3]. + // : : 104 rlineto + lineTo(205, 645), + lineTo(208, 749), + // : : -93 hlineto + lineTo(115, 749), + // : : 3 -104 rlineto + lineTo(118, 645), + // : : return + // : : # Arg stack is []. + // : return + // : # Arg stack is []. + // hintmask 01100000 + // 106 callsubr # 106 + bias = 213 + // : # Arg stack is []. + // : 43 -658 rmoveto + lineTo(130, 221), + moveTo(161, -13), + // : 37 29 28 41 return + // : # Arg stack is [37 29 28 41]. + // hvcurveto + cubeTo(198, -13, 227, 15, 227, 56), + // hintmask 10100000 + // 41 -29 30 -37 -36 -30 -30 -41 vhcurveto + cubeTo(227, 97, 198, 127, 161, 127), + cubeTo(125, 127, 95, 97, 95, 56), + // hintmask 01100000 + // 111 callsubr # 111 + bias = 218 + // : # Arg stack is []. + // : -41 30 -28 36 vhcurveto + cubeTo(95, 15, 125, -13, 161, -13), + // : endchar + }, + + '二': { // U+4E8C "two; twice" + // 23 81 510 79 hstem + // 60 881 cntrmask 11000000 + // 144 693 rmoveto + moveTo(144, 693), + // -79 713 79 vlineto + lineTo(144, 614), + lineTo(857, 614), + lineTo(857, 693), + // -797 -589 rmoveto + lineTo(144, 693), + moveTo(60, 104), + // -81 881 81 vlineto + lineTo(60, 23), + lineTo(941, 23), + lineTo(941, 104), + // endchar + lineTo(60, 104), + }, + }, + + "adobe/SourceSansPro-Black.otf": { + '¤': { // U+00A4 CURRENCY SIGN + // -45 147 99 168 98 hstem + // 44 152 148 152 vstem + // 102 76 rmoveto + moveTo(102, 76), + // 71 71 rlineto + lineTo(173, 147), + // 31 -13 33 -6 33 32 34 6 31 hflex1 + cubeTo(204, 134, 237, 128, 270, 128), + cubeTo(302, 128, 336, 134, 367, 147), + // 71 -71 85 85 -61 60 rlineto + lineTo(438, 76), + lineTo(523, 161), + lineTo(462, 221), + // 21 30 13 36 43 vvcurveto + cubeTo(483, 251, 496, 287, 496, 330), + // 42 -12 36 -21 29 vhcurveto + cubeTo(496, 372, 484, 408, 463, 437), + // 60 60 -85 85 -70 -70 rlineto + lineTo(523, 497), + lineTo(438, 582), + lineTo(368, 512), + // -31 13 -34 7 -33 -33 -34 -7 -31 hflex1 + cubeTo(337, 525, 303, 532, 270, 532), + cubeTo(237, 532, 203, 525, 172, 512), + // -70 70 -85 -85 59 -60 rlineto + lineTo(102, 582), + lineTo(17, 497), + lineTo(76, 437), + // -20 -29 -12 -36 -42 vvcurveto + cubeTo(56, 408, 44, 372, 44, 330), + // -43 12 -36 21 -30 vhcurveto + cubeTo(44, 287, 56, 251, 77, 221), + // -60 -60 rlineto + lineTo(17, 161), + // 253 85 rmoveto + lineTo(102, 76), + moveTo(270, 246), + // -42 -32 32 52 52 32 32 42 42 32 -32 -52 -52 -32 -32 -42 hvcurveto + cubeTo(228, 246, 196, 278, 196, 330), + cubeTo(196, 382, 228, 414, 270, 414), + cubeTo(312, 414, 344, 382, 344, 330), + cubeTo(344, 278, 312, 246, 270, 246), + // endchar + }, + }, + + "adobe/SourceSansPro-Regular.otf": { + ',': { + // -309 -1 115 hstem + // 137 61 vstem + // 67 -170 rmoveto + moveTo(67, -170), + // 81 34 50 67 86 vvcurveto + cubeTo(148, -136, 198, -69, 198, 17), + // 60 -26 37 -43 -33 -28 -22 -36 -37 27 -20 32 3 4 0 1 3 vhcurveto + cubeTo(198, 77, 172, 114, 129, 114), + cubeTo(96, 114, 68, 92, 68, 56), + cubeTo(68, 19, 95, -1, 127, -1), + cubeTo(130, -1, 134, -1, 137, 0), + // 1 -53 -34 -44 -57 -25 rrcurveto + cubeTo(138, -53, 104, -97, 47, -122), + // endchar + lineTo(67, -170), + }, + + 'Q': { + // 106 -165 70 87 65 538 73 hstem + // 52 86 388 87 vstem + // 332 57 rmoveto + moveTo(332, 57), + // -117 -77 106 168 163 77 101 117 117 77 -101 -163 -168 -77 -106 -117 hvcurveto + cubeTo(215, 57, 138, 163, 138, 331), + cubeTo(138, 494, 215, 595, 332, 595), + cubeTo(449, 595, 526, 494, 526, 331), + cubeTo(526, 163, 449, 57, 332, 57), + // 201 -222 rmoveto + moveTo(533, -165), + // 39 35 7 8 20 hvcurveto + cubeTo(572, -165, 607, -158, 627, -150), + // -16 64 rlineto + lineTo(611, -86), + // -5 -18 -22 -4 -29 hhcurveto + cubeTo(593, -91, 571, -95, 542, -95), + // -71 -60 29 58 -30 hvcurveto + cubeTo(471, -95, 411, -66, 381, -8), + // 139 24 93 126 189 vvcurveto + cubeTo(520, 16, 613, 142, 613, 331), + // 209 -116 128 -165 -165 -115 -127 -210 -193 96 -127 143 -20 vhcurveto + cubeTo(613, 540, 497, 668, 332, 668), + cubeTo(167, 668, 52, 541, 52, 331), + cubeTo(52, 138, 148, 11, 291, -9), + // -90 38 83 -66 121 hhcurveto + cubeTo(329, -99, 412, -165, 533, -165), + // endchar + }, + + 'ĩ': { // U+0129 LATIN SMALL LETTER I WITH TILDE + // 92 callgsubr # 92 + bias = 199. + // : # Arg stack is []. + // : -312 21 85 callgsubr # 85 + bias = 192. + // : : # Arg stack is [-312 21]. + // : : -21 486 -20 return + // : : # Arg stack is [-312 21 -21 486 -20]. + // : return + // : # Arg stack is [-312 21 -21 486 -20]. + // 111 45 callsubr # 45 + bias = 152 + // : # Arg stack is [-312 21 -21 486 -20 111]. + // : 60 24 60 -9 216 callgsubr # 216 + bias = 323 + // : : # Arg stack is [-312 21 -21 486 -20 111 60 24 60 -9]. + // : : -20 24 -20 hstemhm + // : : return + // : : # Arg stack is []. + // : return + // : # Arg stack is []. + // -50 55 77 82 77 55 hintmask 1101000100000000 + // 134 callsubr # 134 + bias = 241 + // : # Arg stack is []. + // : 82 hmoveto + moveTo(82, 0), + // : 82 127 callsubr # 127 + bias = 234 + // : : # Arg stack is [82]. + // : : 486 -82 hlineto + lineTo(164, 0), + lineTo(164, 486), + lineTo(82, 486), + // : : return + // : : # Arg stack is []. + // : return + // : # Arg stack is []. + // hintmask 1110100110000000 + // 113 91 15 callgsubr # 15 + bias = 122 + // : # Arg stack is [113 91]. + // : rmoveto + lineTo(82, 0), + moveTo(195, 577), + // : 69 29 58 77 3 hvcurveto + cubeTo(264, 577, 293, 635, 296, 712), + // : return + // : # Arg stack is []. + // hintmask 1110010110000000 + // -58 callsubr # -58 + bias = 49 + // : # Arg stack is []. + // : -55 4 rlineto + lineTo(241, 716), + // : -46 -3 -14 -33 -29 -47 -26 84 -71 hhcurveto + cubeTo(238, 670, 224, 637, 195, 637), + cubeTo(148, 637, 122, 721, 51, 721), + // : return + // : # Arg stack is []. + // hintmask 1101001100000000 + // -70 callgsubr # -70 + bias = 37 + // : # Arg stack is []. + // : -69 -29 -58 -78 -3 hvcurveto + cubeTo(-18, 721, -47, 663, -50, 585), + // : 55 -3 rlineto + lineTo(5, 582), + // : 47 3 14 32 30 hhcurveto + cubeTo(8, 629, 22, 661, 52, 661), + // : return + // : # Arg stack is []. + // hintmask 1110100110000000 + // 51 callsubr # 51 + bias = 158 + // : # Arg stack is []. + // : 46 26 -84 71 hhcurveto + cubeTo(98, 661, 124, 577, 195, 577), + // : endchar + }, + + 'ī': { // U+012B LATIN SMALL LETTER I WITH MACRON + // 92 callgsubr # 92 + bias = 199. + // : # Arg stack is []. + // : -312 21 85 callgsubr # 85 + bias = 192. + // : : # Arg stack is [-312 21]. + // : : -21 486 -20 return + // : : # Arg stack is [-312 21 -21 486 -20]. + // : return + // : # Arg stack is [-312 21 -21 486 -20]. + // 135 57 112 callgsubr # 112 + bias = 219 + // : # Arg stack is [-312 21 -21 486 -20 135 57]. + // : hstem + // : 82 82 vstem + // : 134 callsubr # 134 + bias = 241 + // : : # Arg stack is []. + // : : 82 hmoveto + moveTo(82, 0), + // : : 82 127 callsubr # 127 + bias = 234 + // : : : # Arg stack is [82]. + // : : : 486 -82 hlineto + lineTo(164, 0), + lineTo(164, 486), + lineTo(82, 486), + // : : : return + // : : : # Arg stack is []. + // : : return + // : : # Arg stack is []. + // : return + // : # Arg stack is []. + // -92 115 -60 callgsubr # -60 + bias = 47 + // : # Arg stack is [-92 115]. + // : rmoveto + lineTo(82, 0), + moveTo(-10, 601), + // : 266 57 -266 hlineto + lineTo(256, 601), + lineTo(256, 658), + lineTo(-10, 658), + // : endchar + lineTo(-10, 601), + }, + + 'ĭ': { // U+012D LATIN SMALL LETTER I WITH BREVE + // 92 callgsubr # 92 + bias = 199. + // : # Arg stack is []. + // : -312 21 85 callgsubr # 85 + bias = 192. + // : : # Arg stack is [-312 21]. + // : : -21 486 -20 return + // : : # Arg stack is [-312 21 -21 486 -20]. + // : return + // : # Arg stack is [-312 21 -21 486 -20]. + // 105 55 96 -20 hstem + // -32 51 63 82 65 51 vstem + // 134 callsubr # 134 + bias = 241 + // : # Arg stack is []. + // : 82 hmoveto + moveTo(82, 0), + // : 82 127 callsubr # 127 + bias = 234 + // : : # Arg stack is [82]. + // : : 486 -82 hlineto + lineTo(164, 0), + lineTo(164, 486), + lineTo(82, 486), + // : : return + // : : # Arg stack is []. + // : return + // : # Arg stack is []. + // 42 85 143 callsubr # 143 + bias = 250 + // : # Arg stack is [42 85]. + // : rmoveto + lineTo(82, 0), + moveTo(124, 571), + // : -84 callsubr # -84 + bias = 23 + // : : # Arg stack is []. + // : : 107 44 77 74 5 hvcurveto + cubeTo(231, 571, 275, 648, 280, 722), + // : : -51 8 rlineto + lineTo(229, 730), + // : : -51 -8 -32 -53 -65 hhcurveto + cubeTo(221, 679, 189, 626, 124, 626), + // : : -65 -32 53 51 -8 hvcurveto + cubeTo(59, 626, 27, 679, 19, 730), + // : : -51 -22 callsubr # -22 + bias = 85 + // : : : # Arg stack is [-51]. + // : : : -8 rlineto + lineTo(-32, 722), + // : : : -74 5 44 -77 107 hhcurveto + cubeTo(-27, 648, 17, 571, 124, 571), + // : : : return + // : : : # Arg stack is []. + // : : return + // : : # Arg stack is []. + // : return + // : # Arg stack is []. + // endchar + }, + + 'Λ': { // U+039B GREEK CAPITAL LETTER LAMDA + // -43 21 -21 572 84 hstem + // 0 515 vstem + // 0 vmoveto + moveTo(0, 0), + // 85 hlineto + lineTo(85, 0), + // 105 355 23 77 16 63 24 77 rlinecurve + lineTo(190, 355), + cubeTo(213, 432, 229, 495, 253, 572), + // 4 hlineto + lineTo(257, 572), + // 25 -77 16 -63 23 -77 106 -355 rcurveline + cubeTo(282, 495, 298, 432, 321, 355), + lineTo(427, 0), + // 88 hlineto + lineTo(515, 0), + // -210 656 rlineto + lineTo(305, 656), + // -96 hlineto + lineTo(209, 656), + // endchar + lineTo(0, 0), + }, + + 'Ḫ': { // U+1E2A LATIN CAPITAL LETTER H WITH BREVE BELOW + // 94 -231 55 197 157 callgsubr # 157 + bias = 264 + // : # Arg stack is [94 -231 55 197]. + // : -21 309 72 return + // : # Arg stack is [94 -231 55 197 -21 309 72]. + // 275 254 callgsubr # 254 + bias = 361 + // : # Arg stack is [94 -231 55 197 -21 309 72 275]. + // : -20 hstemhm + // : 90 83 return + // : # Arg stack is [90 83]. + // -4 352 callsubr # 352 + bias = 459 + // : # Arg stack is [90 83 -4]. + // : 51 210 51 return + // : # Arg stack is [90 83 -4 51 210 51]. + // -3 84 hintmask 11111001 + // 90 -40 callsubr # -40 + bias = 67 + // : # Arg stack is [90]. + // : -27 callgsubr # -27 + bias = 80 + // : : # Arg stack is [90]. + // : : hmoveto + moveTo(90, 0), + // : : 83 309 305 -309 84 return + // : : # Arg stack is [83 309 305 -309 84]. + // : -41 callgsubr # -41 + bias = 66 + // : : # Arg stack is [83 309 305 -309 84]. + // : : 656 -84 -275 -305 275 -83 return + // : : # Arg stack is [83 309 305 -309 84 656 -84 -275 -305 275 -83]. + // : hlineto + lineTo(173, 0), + lineTo(173, 309), + lineTo(478, 309), + lineTo(478, 0), + lineTo(562, 0), + lineTo(562, 656), + lineTo(478, 656), + lineTo(478, 381), + lineTo(173, 381), + lineTo(173, 656), + lineTo(90, 656), + // : return + // : # Arg stack is []. + // hintmask 11110110 + // 235 -887 143 callsubr # 143 + bias = 250 + // : # Arg stack is [235 -887]. + // : rmoveto + lineTo(90, 0), + moveTo(325, -231), + // : -84 callsubr # -84 + bias = 23 + // : : # Arg stack is []. + // : : 107 44 77 74 5 hvcurveto + cubeTo(432, -231, 476, -154, 481, -80), + // : : -51 8 rlineto + lineTo(430, -72), + // : : -51 -8 -32 -53 -65 hhcurveto + cubeTo(422, -123, 390, -176, 325, -176), + // : : -65 -32 53 51 -8 hvcurveto + cubeTo(260, -176, 228, -123, 220, -72), + // : : -51 -22 callsubr # -22 + bias = 85 + // : : : # Arg stack is [-51]. + // : : : -8 rlineto + lineTo(169, -80), + // : : : -74 5 44 -77 107 hhcurveto + cubeTo(174, -154, 218, -231, 325, -231), + // : : : return + // : : : # Arg stack is []. + // : : return + // : : # Arg stack is []. + // : return + // : # Arg stack is []. + // endchar + }, + }, + + "apple/Helvetica.dfont?0": { + 'i': { + // - contour #0 + moveTo(132, 1066), + lineTo(315, 1066), + lineTo(315, 0), + lineTo(132, 0), + lineTo(132, 1066), + // - contour #1 + moveTo(132, 1469), + lineTo(315, 1469), + lineTo(315, 1265), + lineTo(132, 1265), + lineTo(132, 1469), + }, + }, + + "apple/Helvetica.dfont?1": { + 'i': { + // - contour #0 + moveTo(426, 1220), + lineTo(137, 1220), + lineTo(137, 1483), + lineTo(426, 1483), + lineTo(426, 1220), + // - contour #1 + moveTo(137, 1090), + lineTo(426, 1090), + lineTo(426, 0), + lineTo(137, 0), + lineTo(137, 1090), + }, + }, + + "dejavu/DejaVuSans-ExtraLight.ttf": { + 'i': { + // - contour #0 + moveTo(230, 1120), + lineTo(322, 1120), + lineTo(322, 0), + lineTo(230, 0), + lineTo(230, 1120), + // - contour #1 + moveTo(230, 1556), + lineTo(322, 1556), + lineTo(322, 1430), + lineTo(230, 1430), + lineTo(230, 1556), + }, + }, + + "microsoft/Arial.ttf": { + ',': { + // - contour #0 + moveTo(182, 0), + lineTo(182, 205), + lineTo(387, 205), + lineTo(387, 0), + quadTo(387, -113, 347, -182), + quadTo(307, -252, 220, -290), + lineTo(170, -213), + quadTo(227, -188, 254, -139), + quadTo(281, -91, 284, 0), + lineTo(182, 0), + }, + + 'i': { + // - contour #0 + moveTo(136, 1259), + lineTo(136, 1466), + lineTo(316, 1466), + lineTo(316, 1259), + lineTo(136, 1259), + // - contour #1 + moveTo(136, 0), + lineTo(136, 1062), + lineTo(316, 1062), + lineTo(316, 0), + lineTo(136, 0), + }, + + 'o': { + // - contour #0 + moveTo(68, 531), + quadTo(68, 826, 232, 968), + quadTo(369, 1086, 566, 1086), + quadTo(785, 1086, 924, 942), + quadTo(1063, 799, 1063, 546), + quadTo(1063, 341, 1001, 223), + quadTo(940, 106, 822, 41), + quadTo(705, -24, 566, -24), + quadTo(343, -24, 205, 119), + quadTo(68, 262, 68, 531), + // - contour #1 + moveTo(253, 531), + quadTo(253, 327, 342, 225), + quadTo(431, 124, 566, 124), + quadTo(700, 124, 789, 226), + quadTo(878, 328, 878, 537), + quadTo(878, 734, 788, 835), + quadTo(699, 937, 566, 937), + quadTo(431, 937, 342, 836), + quadTo(253, 735, 253, 531), + }, + + 'í': { // U+00ED LATIN SMALL LETTER I WITH ACUTE + // - contour #0 + translate(0, 0, moveTo(198, 0)), + translate(0, 0, lineTo(198, 1062)), + translate(0, 0, lineTo(378, 1062)), + translate(0, 0, lineTo(378, 0)), + translate(0, 0, lineTo(198, 0)), + // - contour #1 + translate(-33, 0, moveTo(222, 1194)), + translate(-33, 0, lineTo(355, 1474)), + translate(-33, 0, lineTo(591, 1474)), + translate(-33, 0, lineTo(371, 1194)), + translate(-33, 0, lineTo(222, 1194)), + }, + + 'Ī': { // U+012A LATIN CAPITAL LETTER I WITH MACRON + // - contour #0 + translate(0, 0, moveTo(191, 0)), + translate(0, 0, lineTo(191, 1466)), + translate(0, 0, lineTo(385, 1466)), + translate(0, 0, lineTo(385, 0)), + translate(0, 0, lineTo(191, 0)), + // - contour #1 + translate(-57, 336, moveTo(29, 1227)), + translate(-57, 336, lineTo(29, 1375)), + translate(-57, 336, lineTo(653, 1375)), + translate(-57, 336, lineTo(653, 1227)), + translate(-57, 336, lineTo(29, 1227)), + }, + + // Ǻ is a compound glyph whose elements are also compound glyphs. + 'Ǻ': { // U+01FA LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE + // - contour #0 + translate(0, 0, moveTo(-3, 0)), + translate(0, 0, lineTo(560, 1466)), + translate(0, 0, lineTo(769, 1466)), + translate(0, 0, lineTo(1369, 0)), + translate(0, 0, lineTo(1148, 0)), + translate(0, 0, lineTo(977, 444)), + translate(0, 0, lineTo(364, 444)), + translate(0, 0, lineTo(203, 0)), + translate(0, 0, lineTo(-3, 0)), + // - contour #1 + translate(0, 0, moveTo(420, 602)), + translate(0, 0, lineTo(917, 602)), + translate(0, 0, lineTo(764, 1008)), + translate(0, 0, quadTo(694, 1193, 660, 1312)), + translate(0, 0, quadTo(632, 1171, 581, 1032)), + translate(0, 0, lineTo(420, 602)), + // - contour #2 + translate(319, 263, moveTo(162, 1338)), + translate(319, 263, quadTo(162, 1411, 215, 1464)), + translate(319, 263, quadTo(269, 1517, 342, 1517)), + translate(319, 263, quadTo(416, 1517, 469, 1463)), + translate(319, 263, quadTo(522, 1410, 522, 1334)), + translate(319, 263, quadTo(522, 1257, 469, 1204)), + translate(319, 263, quadTo(416, 1151, 343, 1151)), + translate(319, 263, quadTo(268, 1151, 215, 1204)), + translate(319, 263, quadTo(162, 1258, 162, 1338)), + // - contour #3 + translate(319, 263, moveTo(238, 1337)), + translate(319, 263, quadTo(238, 1290, 269, 1258)), + translate(319, 263, quadTo(301, 1226, 344, 1226)), + translate(319, 263, quadTo(387, 1226, 418, 1258)), + translate(319, 263, quadTo(450, 1290, 450, 1335)), + translate(319, 263, quadTo(450, 1380, 419, 1412)), + translate(319, 263, quadTo(388, 1444, 344, 1444)), + translate(319, 263, quadTo(301, 1444, 269, 1412)), + translate(319, 263, quadTo(238, 1381, 238, 1337)), + // - contour #4 + translate(339, 650, moveTo(222, 1194)), + translate(339, 650, lineTo(355, 1474)), + translate(339, 650, lineTo(591, 1474)), + translate(339, 650, lineTo(371, 1194)), + translate(339, 650, lineTo(222, 1194)), + }, + + '﴾': { // U+FD3E ORNATE LEFT PARENTHESIS. + // - contour #0 + moveTo(560, -384), + lineTo(516, -429), + quadTo(412, -304, 361, -226), + quadTo(258, -68, 201, 106), + quadTo(127, 334, 127, 595), + quadTo(127, 845, 201, 1069), + quadTo(259, 1246, 361, 1404), + quadTo(414, 1487, 514, 1608), + lineTo(560, 1566), + quadTo(452, 1328, 396, 1094), + quadTo(336, 845, 336, 603), + quadTo(336, 359, 370, 165), + quadTo(398, 8, 454, -142), + quadTo(482, -217, 560, -384), + }, + + '﴿': { // U+FD3F ORNATE RIGHT PARENTHESIS + // - contour #0 + transform(-1<<14, 0, 0, +1<<14, 653, 0, moveTo(560, -384)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, lineTo(516, -429)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(412, -304, 361, -226)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(258, -68, 201, 106)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(127, 334, 127, 595)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(127, 845, 201, 1069)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(259, 1246, 361, 1404)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(414, 1487, 514, 1608)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, lineTo(560, 1566)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(452, 1328, 396, 1094)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(336, 845, 336, 603)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(336, 359, 370, 165)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(398, 8, 454, -142)), + transform(-1<<14, 0, 0, +1<<14, 653, 0, quadTo(482, -217, 560, -384)), + }, + }, + + "noto/NotoSans-Regular.ttf": { + 'i': { + // - contour #0 + moveTo(354, 0), + lineTo(174, 0), + lineTo(174, 1098), + lineTo(354, 1098), + lineTo(354, 0), + // - contour #1 + moveTo(160, 1395), + quadTo(160, 1455, 190, 1482), + quadTo(221, 1509, 266, 1509), + quadTo(308, 1509, 339, 1482), + quadTo(371, 1455, 371, 1395), + quadTo(371, 1336, 339, 1308), + quadTo(308, 1280, 266, 1280), + quadTo(221, 1280, 190, 1308), + quadTo(160, 1336, 160, 1395), + }, + }, +} + +type kernTestCase struct { + ppem fixed.Int26_6 + hinting font.Hinting + runes [2]rune + want Units +} + +// proprietaryKernTestCases hold a sample of each font's kerning pairs. The +// numerical values can be verified by running the ttx tool. +var proprietaryKernTestCases = map[string][]kernTestCase{ + "dejavu/DejaVuSans-ExtraLight.ttf": { + {2048, font.HintingNone, [2]rune{'A', 'A'}, 57}, + {2048, font.HintingNone, [2]rune{'W', 'A'}, -112}, + // U+00C1 LATIN CAPITAL LETTER A WITH ACUTE + // U+01FA LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE + // U+1E82 LATIN CAPITAL LETTER W WITH ACUTE + {2048, font.HintingNone, [2]rune{'\u00c1', 'A'}, 57}, + // TODO: enable these next two test cases, when we support multiple + // kern subtables. + // {2048, font.HintingNone, [2]rune{'\u01fa', 'A'}, 57}, + // {2048, font.HintingNone, [2]rune{'\u1e82', 'A'}, -112}, + }, + "microsoft/Arial.ttf": { + {2048, font.HintingNone, [2]rune{'A', 'V'}, -152}, + // U+03B8 GREEK SMALL LETTER THETA + // U+03BB GREEK SMALL LETTER LAMDA + {2048, font.HintingNone, [2]rune{'\u03b8', '\u03bb'}, -39}, + {2048, font.HintingNone, [2]rune{'\u03bb', '\u03b8'}, -0}, + }, + "microsoft/Comic_Sans_MS.ttf": { + {2048, font.HintingNone, [2]rune{'A', 'V'}, 0}, + }, + "microsoft/Times_New_Roman.ttf": { + {768, font.HintingNone, [2]rune{'A', 'V'}, -99}, + {768, font.HintingFull, [2]rune{'A', 'V'}, -128}, + {2048, font.HintingNone, [2]rune{'A', 'A'}, 0}, + {2048, font.HintingNone, [2]rune{'A', 'T'}, -227}, + {2048, font.HintingNone, [2]rune{'A', 'V'}, -264}, + {2048, font.HintingNone, [2]rune{'T', 'A'}, -164}, + {2048, font.HintingNone, [2]rune{'T', 'T'}, 0}, + {2048, font.HintingNone, [2]rune{'T', 'V'}, 0}, + {2048, font.HintingNone, [2]rune{'V', 'A'}, -264}, + {2048, font.HintingNone, [2]rune{'V', 'T'}, 0}, + {2048, font.HintingNone, [2]rune{'V', 'V'}, 0}, + // U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS + // U+0393 GREEK CAPITAL LETTER GAMMA + {2048, font.HintingNone, [2]rune{'\u0390', '\u0393'}, 0}, + {2048, font.HintingNone, [2]rune{'\u0393', '\u0390'}, 76}, + }, + "microsoft/Webdings.ttf": { + {2048, font.HintingNone, [2]rune{'\uf041', '\uf042'}, 0}, + }, +} + +// proprietaryFDSelectTestCases hold a sample of each font's Font Dict Select +// (FDSelect) map. The numerical values can be verified by grepping the output +// of the ttx tool: +// +// grep CharString.*fdSelectIndex SourceHanSansSC-Regular.ttx +// +// will print lines like this: +// +// +// +// +// +// +// As for what the values like 3 or 15 actually mean, grepping that ttx file +// for "FontName" gives this list: +// +// 0: +// 1: +// 2: +// 3: +// 4: +// 5: +// 6: +// 7: +// 8: +// 9: +// 10: +// 11: +// 12: +// 13: +// 14: +// 15: +// 16: +// 17: +// 18: +// +// As a sanity check, the cmap table maps U+3127 BOPOMOFO LETTER I to the glyph +// named "cid65353", proprietaryFDSelectTestCases here maps 65353 to Font Dict +// 2, and the list immediately above maps 2 to "Bopomofo". +var proprietaryFDSelectTestCases = map[string]map[GlyphIndex]int{ + "adobe/SourceHanSansSC-Regular.otf": { + 0: 5, + 1: 15, + 2: 15, + 16: 15, + 17: 17, + 26: 17, + 27: 15, + 100: 15, + 101: 15, + 102: 3, + 103: 15, + 777: 4, + 1000: 3, + 2000: 3, + 3000: 13, + 4000: 13, + 20000: 13, + 48000: 12, + 59007: 1, + 59024: 0, + 59087: 8, + 59200: 7, + 59211: 6, + 60000: 13, + 63000: 16, + 63039: 9, + 63060: 11, + 63137: 10, + 65353: 2, + 65486: 14, + 65505: 18, + 65506: 5, + 65533: 5, + 65534: 5, + }, +} 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), + } +} diff --git a/vendor/golang.org/x/image/font/sfnt/sfnt_test.go b/vendor/golang.org/x/image/font/sfnt/sfnt_test.go new file mode 100644 index 0000000..74de278 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/sfnt_test.go @@ -0,0 +1,805 @@ +// 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. + +package sfnt + +import ( + "bytes" + "fmt" + "io/ioutil" + "path/filepath" + "testing" + + "golang.org/x/image/font" + "golang.org/x/image/font/gofont/gobold" + "golang.org/x/image/font/gofont/gomono" + "golang.org/x/image/font/gofont/goregular" + "golang.org/x/image/math/fixed" +) + +func pt(x, y fixed.Int26_6) fixed.Point26_6 { + return fixed.Point26_6{X: x, Y: y} +} + +func moveTo(xa, ya fixed.Int26_6) Segment { + return Segment{ + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{pt(xa, ya)}, + } +} + +func lineTo(xa, ya fixed.Int26_6) Segment { + return Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{pt(xa, ya)}, + } +} + +func quadTo(xa, ya, xb, yb fixed.Int26_6) Segment { + return Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{pt(xa, ya), pt(xb, yb)}, + } +} + +func cubeTo(xa, ya, xb, yb, xc, yc fixed.Int26_6) Segment { + return Segment{ + Op: SegmentOpCubeTo, + Args: [3]fixed.Point26_6{pt(xa, ya), pt(xb, yb), pt(xc, yc)}, + } +} + +func translate(dx, dy fixed.Int26_6, s Segment) Segment { + translateArgs(&s.Args, dx, dy) + return s +} + +func transform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, s Segment) Segment { + transformArgs(&s.Args, txx, txy, tyx, tyy, dx, dy) + return s +} + +func checkSegmentsEqual(got, want []Segment) error { + // Flip got's Y axis. The test cases' coordinates are given with the Y axis + // increasing up, as that is what the ttx tool gives, and is the model for + // the underlying font format. The Go API returns coordinates with the Y + // axis increasing down, the same as the standard graphics libraries. + for i := range got { + for j := range got[i].Args { + got[i].Args[j].Y *= -1 + } + } + + if len(got) != len(want) { + return fmt.Errorf("got %d elements, want %d\noverall:\ngot %v\nwant %v", + len(got), len(want), got, want) + } + for i, g := range got { + if w := want[i]; g != w { + return fmt.Errorf("element %d:\ngot %v\nwant %v\noverall:\ngot %v\nwant %v", + i, g, w, got, want) + } + } + + // Check that every contour is closed. + if len(got) == 0 { + return nil + } + if got[0].Op != SegmentOpMoveTo { + return fmt.Errorf("segments do not start with a moveTo") + } + var ( + first, last fixed.Point26_6 + firstI int + ) + checkClosed := func(lastI int) error { + if first != last { + return fmt.Errorf("segments[%d:%d] not closed:\nfirst %v\nlast %v", firstI, lastI, first, last) + } + return nil + } + for i, g := range got { + switch g.Op { + case SegmentOpMoveTo: + if i != 0 { + if err := checkClosed(i); err != nil { + return err + } + } + firstI, first, last = i, g.Args[0], g.Args[0] + case SegmentOpLineTo: + last = g.Args[0] + case SegmentOpQuadTo: + last = g.Args[1] + case SegmentOpCubeTo: + last = g.Args[2] + } + } + return checkClosed(len(got)) +} + +func TestTrueTypeParse(t *testing.T) { + f, err := Parse(goregular.TTF) + if err != nil { + t.Fatalf("Parse: %v", err) + } + testTrueType(t, f) +} + +func TestTrueTypeParseReaderAt(t *testing.T) { + f, err := ParseReaderAt(bytes.NewReader(goregular.TTF)) + if err != nil { + t.Fatalf("ParseReaderAt: %v", err) + } + testTrueType(t, f) +} + +func testTrueType(t *testing.T, f *Font) { + if got, want := f.UnitsPerEm(), Units(2048); got != want { + t.Errorf("UnitsPerEm: got %d, want %d", got, want) + } + // The exact number of glyphs in goregular.TTF can vary, and future + // versions may add more glyphs, but https://blog.golang.org/go-fonts says + // that "The WGL4 character set... [has] more than 650 characters in all. + if got, want := f.NumGlyphs(), 650; got <= want { + t.Errorf("NumGlyphs: got %d, want > %d", got, want) + } +} + +func fontData(name string) []byte { + switch name { + case "gobold": + return gobold.TTF + case "gomono": + return gomono.TTF + case "goregular": + return goregular.TTF + } + panic("unreachable") +} + +func TestBounds(t *testing.T) { + testCases := map[string]fixed.Rectangle26_6{ + "gobold": { + Min: fixed.Point26_6{ + X: -452, + Y: -2193, + }, + Max: fixed.Point26_6{ + X: 2190, + Y: 432, + }, + }, + "gomono": { + Min: fixed.Point26_6{ + X: 0, + Y: -2227, + }, + Max: fixed.Point26_6{ + X: 1229, + Y: 432, + }, + }, + "goregular": { + Min: fixed.Point26_6{ + X: -440, + Y: -2118, + }, + Max: fixed.Point26_6{ + X: 2160, + Y: 543, + }, + }, + } + + var b Buffer + for name, want := range testCases { + f, err := Parse(fontData(name)) + if err != nil { + t.Errorf("Parse(%q): %v", name, err) + continue + } + ppem := fixed.Int26_6(f.UnitsPerEm()) + + got, err := f.Bounds(&b, ppem, font.HintingNone) + if err != nil { + t.Errorf("name=%q: Bounds: %v", name, err) + continue + } + if got != want { + t.Errorf("name=%q: Bounds: got %v, want %v", name, got, want) + continue + } + } +} + +func TestGlyphAdvance(t *testing.T) { + testCases := map[string][]struct { + r rune + want fixed.Int26_6 + }{ + "gobold": { + {' ', 569}, + {'A', 1479}, + {'Á', 1479}, + {'Æ', 2048}, + {'i', 592}, + {'x', 1139}, + }, + "gomono": { + {' ', 1229}, + {'A', 1229}, + {'Á', 1229}, + {'Æ', 1229}, + {'i', 1229}, + {'x', 1229}, + }, + "goregular": { + {' ', 569}, + {'A', 1366}, + {'Á', 1366}, + {'Æ', 2048}, + {'i', 505}, + {'x', 1024}, + }, + } + + var b Buffer + for name, testCases1 := range testCases { + f, err := Parse(fontData(name)) + if err != nil { + t.Errorf("Parse(%q): %v", name, err) + continue + } + ppem := fixed.Int26_6(f.UnitsPerEm()) + + for _, tc := range testCases1 { + x, err := f.GlyphIndex(&b, tc.r) + if err != nil { + t.Errorf("name=%q, r=%q: GlyphIndex: %v", name, tc.r, err) + continue + } + got, err := f.GlyphAdvance(&b, x, ppem, font.HintingNone) + if err != nil { + t.Errorf("name=%q, r=%q: GlyphAdvance: %v", name, tc.r, err) + continue + } + if got != tc.want { + t.Errorf("name=%q, r=%q: GlyphAdvance: got %d, want %d", name, tc.r, got, tc.want) + continue + } + } + } +} + +func TestGoRegularGlyphIndex(t *testing.T) { + f, err := Parse(goregular.TTF) + if err != nil { + t.Fatalf("Parse: %v", err) + } + + testCases := []struct { + r rune + want GlyphIndex + }{ + // Glyphs that aren't present in Go Regular. + {'\u001f', 0}, // U+001F + {'\u0200', 0}, // U+0200 LATIN CAPITAL LETTER A WITH DOUBLE GRAVE + {'\u2000', 0}, // U+2000 EN QUAD + + // The want values below can be verified by running the ttx tool on + // Go-Regular.ttf. + // + // The actual values are ad hoc, and result from whatever tools the + // Bigelow & Holmes type foundry used and the order in which they + // crafted the glyphs. They may change over time as newer versions of + // the font are released. + + {'\u0020', 3}, // U+0020 SPACE + {'\u0021', 4}, // U+0021 EXCLAMATION MARK + {'\u0022', 5}, // U+0022 QUOTATION MARK + {'\u0023', 6}, // U+0023 NUMBER SIGN + {'\u0024', 7}, // U+0024 DOLLAR SIGN + {'\u0025', 8}, // U+0025 PERCENT SIGN + {'\u0026', 9}, // U+0026 AMPERSAND + {'\u0027', 10}, // U+0027 APOSTROPHE + + {'\u03bd', 396}, // U+03BD GREEK SMALL LETTER NU + {'\u03be', 397}, // U+03BE GREEK SMALL LETTER XI + {'\u03bf', 398}, // U+03BF GREEK SMALL LETTER OMICRON + {'\u03c0', 399}, // U+03C0 GREEK SMALL LETTER PI + {'\u03c1', 400}, // U+03C1 GREEK SMALL LETTER RHO + {'\u03c2', 401}, // U+03C2 GREEK SMALL LETTER FINAL SIGMA + } + + var b Buffer + for _, tc := range testCases { + got, err := f.GlyphIndex(&b, tc.r) + if err != nil { + t.Errorf("r=%q: %v", tc.r, err) + continue + } + if got != tc.want { + t.Errorf("r=%q: got %d, want %d", tc.r, got, tc.want) + continue + } + } +} + +func TestGlyphIndex(t *testing.T) { + data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/cmapTest.ttf")) + if err != nil { + t.Fatal(err) + } + + for _, format := range []int{-1, 0, 4, 12} { + testGlyphIndex(t, data, format) + } +} + +func testGlyphIndex(t *testing.T, data []byte, cmapFormat int) { + if cmapFormat >= 0 { + originalSupportedCmapFormat := supportedCmapFormat + defer func() { + supportedCmapFormat = originalSupportedCmapFormat + }() + supportedCmapFormat = func(format, pid, psid uint16) bool { + return int(format) == cmapFormat && originalSupportedCmapFormat(format, pid, psid) + } + } + + f, err := Parse(data) + if err != nil { + t.Errorf("cmapFormat=%d: %v", cmapFormat, err) + return + } + + testCases := []struct { + r rune + want GlyphIndex + }{ + // Glyphs that aren't present in cmapTest.ttf. + {'?', 0}, + {'\ufffd', 0}, + {'\U0001f4a9', 0}, + + // For a .TTF file, FontForge maps: + // - ".notdef" to glyph index 0. + // - ".null" to glyph index 1. + // - "nonmarkingreturn" to glyph index 2. + + {'/', 0}, + {'0', 3}, + {'1', 4}, + {'2', 5}, + {'3', 0}, + + {'@', 0}, + {'A', 6}, + {'B', 7}, + {'C', 0}, + + {'`', 0}, + {'a', 8}, + {'b', 0}, + + // Of the remaining runes, only U+00FF LATIN SMALL LETTER Y WITH + // DIAERESIS is in both the Mac Roman encoding and the cmapTest.ttf + // font file. + {'\u00fe', 0}, + {'\u00ff', 9}, + {'\u0100', 10}, + {'\u0101', 11}, + {'\u0102', 0}, + + {'\u4e2c', 0}, + {'\u4e2d', 12}, + {'\u4e2e', 0}, + + {'\U0001f0a0', 0}, + {'\U0001f0a1', 13}, + {'\U0001f0a2', 0}, + + {'\U0001f0b0', 0}, + {'\U0001f0b1', 14}, + {'\U0001f0b2', 15}, + {'\U0001f0b3', 0}, + } + + var b Buffer + for _, tc := range testCases { + want := tc.want + switch { + case cmapFormat == 0 && tc.r > '\u007f' && tc.r != '\u00ff': + // cmap format 0, with the Macintosh Roman encoding, can only + // represent a limited set of non-ASCII runes, e.g. U+00FF. + want = 0 + case cmapFormat == 4 && tc.r > '\uffff': + // cmap format 4 only supports the Basic Multilingual Plane (BMP). + want = 0 + } + + got, err := f.GlyphIndex(&b, tc.r) + if err != nil { + t.Errorf("cmapFormat=%d, r=%q: %v", cmapFormat, tc.r, err) + continue + } + if got != want { + t.Errorf("cmapFormat=%d, r=%q: got %d, want %d", cmapFormat, tc.r, got, want) + continue + } + } +} + +func TestPostScriptSegments(t *testing.T) { + // wants' vectors correspond 1-to-1 to what's in the CFFTest.sfd file, + // although OpenType/CFF and FontForge's SFD have reversed orders. + // https://fontforge.github.io/validation.html says that "All paths must be + // drawn in a consistent direction. Clockwise for external paths, + // anti-clockwise for internal paths. (Actually PostScript requires the + // exact opposite, but FontForge reverses PostScript contours when it loads + // them so that everything is consistant internally -- and reverses them + // again when it saves them, of course)." + // + // The .notdef glyph isn't explicitly in the SFD file, but for some unknown + // reason, FontForge generates it in the OpenType/CFF file. + wants := [][]Segment{{ + // .notdef + // - contour #0 + moveTo(50, 0), + lineTo(450, 0), + lineTo(450, 533), + lineTo(50, 533), + lineTo(50, 0), + // - contour #1 + moveTo(100, 50), + lineTo(100, 483), + lineTo(400, 483), + lineTo(400, 50), + lineTo(100, 50), + }, { + // zero + // - contour #0 + moveTo(300, 700), + cubeTo(380, 700, 420, 580, 420, 500), + cubeTo(420, 350, 390, 100, 300, 100), + cubeTo(220, 100, 180, 220, 180, 300), + cubeTo(180, 450, 210, 700, 300, 700), + // - contour #1 + moveTo(300, 800), + cubeTo(200, 800, 100, 580, 100, 400), + cubeTo(100, 220, 200, 0, 300, 0), + cubeTo(400, 0, 500, 220, 500, 400), + cubeTo(500, 580, 400, 800, 300, 800), + }, { + // one + // - contour #0 + moveTo(100, 0), + lineTo(300, 0), + lineTo(300, 800), + lineTo(100, 800), + lineTo(100, 0), + }, { + // Q + // - contour #0 + moveTo(657, 237), + lineTo(289, 387), + lineTo(519, 615), + lineTo(657, 237), + // - contour #1 + moveTo(792, 169), + cubeTo(867, 263, 926, 502, 791, 665), + cubeTo(645, 840, 380, 831, 228, 673), + cubeTo(71, 509, 110, 231, 242, 93), + cubeTo(369, -39, 641, 18, 722, 93), + lineTo(802, 3), + lineTo(864, 83), + lineTo(792, 169), + }, { + // uni4E2D + // - contour #0 + moveTo(141, 520), + lineTo(137, 356), + lineTo(245, 400), + lineTo(331, 26), + lineTo(355, 414), + lineTo(463, 434), + lineTo(453, 620), + lineTo(341, 592), + lineTo(331, 758), + lineTo(243, 752), + lineTo(235, 562), + lineTo(141, 520), + }} + + testSegments(t, "CFFTest.otf", wants) +} + +func TestTrueTypeSegments(t *testing.T) { + // wants' vectors correspond 1-to-1 to what's in the glyfTest.sfd file, + // although FontForge's SFD format stores quadratic Bézier curves as cubics + // with duplicated off-curve points. quadTo(bx, by, cx, cy) is stored as + // "bx by bx by cx cy". + // + // The .notdef, .null and nonmarkingreturn glyphs aren't explicitly in the + // SFD file, but for some unknown reason, FontForge generates them in the + // TrueType file. + wants := [][]Segment{{ + // .notdef + // - contour #0 + moveTo(68, 0), + lineTo(68, 1365), + lineTo(612, 1365), + lineTo(612, 0), + lineTo(68, 0), + // - contour #1 + moveTo(136, 68), + lineTo(544, 68), + lineTo(544, 1297), + lineTo(136, 1297), + lineTo(136, 68), + }, { + // .null + // Empty glyph. + }, { + // nonmarkingreturn + // Empty glyph. + }, { + // zero + // - contour #0 + moveTo(614, 1434), + quadTo(369, 1434, 369, 614), + quadTo(369, 471, 435, 338), + quadTo(502, 205, 614, 205), + quadTo(860, 205, 860, 1024), + quadTo(860, 1167, 793, 1300), + quadTo(727, 1434, 614, 1434), + // - contour #1 + moveTo(614, 1638), + quadTo(1024, 1638, 1024, 819), + quadTo(1024, 0, 614, 0), + quadTo(205, 0, 205, 819), + quadTo(205, 1638, 614, 1638), + }, { + // one + // - contour #0 + moveTo(205, 0), + lineTo(205, 1638), + lineTo(614, 1638), + lineTo(614, 0), + lineTo(205, 0), + }, { + // five + // - contour #0 + moveTo(0, 0), + lineTo(0, 100), + lineTo(400, 100), + lineTo(400, 0), + lineTo(0, 0), + }, { + // six + // - contour #0 + moveTo(0, 0), + lineTo(0, 100), + lineTo(400, 100), + lineTo(400, 0), + lineTo(0, 0), + // - contour #1 + translate(111, 234, moveTo(205, 0)), + translate(111, 234, lineTo(205, 1638)), + translate(111, 234, lineTo(614, 1638)), + translate(111, 234, lineTo(614, 0)), + translate(111, 234, lineTo(205, 0)), + }, { + // seven + // - contour #0 + moveTo(0, 0), + lineTo(0, 100), + lineTo(400, 100), + lineTo(400, 0), + lineTo(0, 0), + // - contour #1 + transform(1<<13, 0, 0, 1<<13, 56, 117, moveTo(205, 0)), + transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 1638)), + transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 1638)), + transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 0)), + transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 0)), + }, { + // eight + // - contour #0 + moveTo(0, 0), + lineTo(0, 100), + lineTo(400, 100), + lineTo(400, 0), + lineTo(0, 0), + // - contour #1 + transform(3<<13, 0, 0, 1<<13, 56, 117, moveTo(205, 0)), + transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 1638)), + transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 1638)), + transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 0)), + transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 0)), + }, { + // nine + // - contour #0 + moveTo(0, 0), + lineTo(0, 100), + lineTo(400, 100), + lineTo(400, 0), + lineTo(0, 0), + // - contour #1 + transform(22381, 8192, 5996, 14188, 237, 258, moveTo(205, 0)), + transform(22381, 8192, 5996, 14188, 237, 258, lineTo(205, 1638)), + transform(22381, 8192, 5996, 14188, 237, 258, lineTo(614, 1638)), + transform(22381, 8192, 5996, 14188, 237, 258, lineTo(614, 0)), + transform(22381, 8192, 5996, 14188, 237, 258, lineTo(205, 0)), + }} + + testSegments(t, "glyfTest.ttf", wants) +} + +func testSegments(t *testing.T, filename string, wants [][]Segment) { + data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/" + filename)) + if err != nil { + t.Fatalf("ReadFile: %v", err) + } + f, err := Parse(data) + if err != nil { + t.Fatalf("Parse: %v", err) + } + ppem := fixed.Int26_6(f.UnitsPerEm()) + + if ng := f.NumGlyphs(); ng != len(wants) { + t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants)) + } + var b Buffer + for i, want := range wants { + got, err := f.LoadGlyph(&b, GlyphIndex(i), ppem, nil) + if err != nil { + t.Errorf("i=%d: LoadGlyph: %v", i, err) + continue + } + if err := checkSegmentsEqual(got, want); err != nil { + t.Errorf("i=%d: %v", i, err) + continue + } + } + if _, err := f.LoadGlyph(nil, 0xffff, ppem, nil); err != ErrNotFound { + t.Errorf("LoadGlyph(..., 0xffff, ...):\ngot %v\nwant %v", err, ErrNotFound) + } + + name, err := f.Name(nil, NameIDFamily) + if err != nil { + t.Errorf("Name: %v", err) + } else if want := filename[:len(filename)-len(".ttf")]; name != want { + t.Errorf("Name:\ngot %q\nwant %q", name, want) + } +} + +func TestPPEM(t *testing.T) { + data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/glyfTest.ttf")) + if err != nil { + t.Fatalf("ReadFile: %v", err) + } + f, err := Parse(data) + if err != nil { + t.Fatalf("Parse: %v", err) + } + var b Buffer + x, err := f.GlyphIndex(&b, '1') + if err != nil { + t.Fatalf("GlyphIndex: %v", err) + } + if x == 0 { + t.Fatalf("GlyphIndex: no glyph index found for the rune '1'") + } + + testCases := []struct { + ppem fixed.Int26_6 + want []Segment + }{{ + ppem: fixed.Int26_6(12 << 6), + want: []Segment{ + moveTo(77, 0), + lineTo(77, 614), + lineTo(230, 614), + lineTo(230, 0), + lineTo(77, 0), + }, + }, { + ppem: fixed.Int26_6(2048), + want: []Segment{ + moveTo(205, 0), + lineTo(205, 1638), + lineTo(614, 1638), + lineTo(614, 0), + lineTo(205, 0), + }, + }} + + for i, tc := range testCases { + got, err := f.LoadGlyph(&b, x, tc.ppem, nil) + if err != nil { + t.Errorf("i=%d: LoadGlyph: %v", i, err) + continue + } + if err := checkSegmentsEqual(got, tc.want); err != nil { + t.Errorf("i=%d: %v", i, err) + continue + } + } +} + +func TestGlyphName(t *testing.T) { + f, err := Parse(goregular.TTF) + if err != nil { + t.Fatalf("Parse: %v", err) + } + + testCases := []struct { + r rune + want string + }{ + {'\x00', "uni0000"}, + {'!', "exclam"}, + {'A', "A"}, + {'{', "braceleft"}, + {'\u00c4', "Adieresis"}, // U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS + {'\u2020', "dagger"}, // U+2020 DAGGER + {'\u2660', "spade"}, // U+2660 BLACK SPADE SUIT + {'\uf800', "gopher"}, // U+F800 + {'\ufffe', ".notdef"}, // Not in the Go Regular font, so GlyphIndex returns (0, nil). + } + + var b Buffer + for _, tc := range testCases { + x, err := f.GlyphIndex(&b, tc.r) + if err != nil { + t.Errorf("r=%q: GlyphIndex: %v", tc.r, err) + continue + } + got, err := f.GlyphName(&b, x) + if err != nil { + t.Errorf("r=%q: GlyphName: %v", tc.r, err) + continue + } + if got != tc.want { + t.Errorf("r=%q: got %q, want %q", tc.r, got, tc.want) + continue + } + } +} + +func TestBuiltInPostNames(t *testing.T) { + testCases := []struct { + x GlyphIndex + want string + }{ + {0, ".notdef"}, + {1, ".null"}, + {2, "nonmarkingreturn"}, + {13, "asterisk"}, + {36, "A"}, + {93, "z"}, + {123, "ocircumflex"}, + {202, "Edieresis"}, + {255, "Ccaron"}, + {256, "ccaron"}, + {257, "dcroat"}, + {258, ""}, + {999, ""}, + {0xffff, ""}, + } + + for _, tc := range testCases { + if tc.x >= numBuiltInPostNames { + continue + } + i := builtInPostNamesOffsets[tc.x+0] + j := builtInPostNamesOffsets[tc.x+1] + got := builtInPostNamesData[i:j] + if got != tc.want { + t.Errorf("x=%d: got %q, want %q", tc.x, got, tc.want) + } + } +} diff --git a/vendor/golang.org/x/image/font/sfnt/truetype.go b/vendor/golang.org/x/image/font/sfnt/truetype.go new file mode 100644 index 0000000..ab27f5b --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/truetype.go @@ -0,0 +1,572 @@ +// Copyright 2017 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. + +package sfnt + +import ( + "golang.org/x/image/math/fixed" +) + +// Flags for simple (non-compound) glyphs. +// +// See https://www.microsoft.com/typography/OTSPEC/glyf.htm +const ( + flagOnCurve = 1 << 0 // 0x0001 + flagXShortVector = 1 << 1 // 0x0002 + flagYShortVector = 1 << 2 // 0x0004 + flagRepeat = 1 << 3 // 0x0008 + + // The same flag bits are overloaded to have two meanings, dependent on the + // value of the flag{X,Y}ShortVector bits. + flagPositiveXShortVector = 1 << 4 // 0x0010 + flagThisXIsSame = 1 << 4 // 0x0010 + flagPositiveYShortVector = 1 << 5 // 0x0020 + flagThisYIsSame = 1 << 5 // 0x0020 +) + +// Flags for compound glyphs. +// +// See https://www.microsoft.com/typography/OTSPEC/glyf.htm +const ( + flagArg1And2AreWords = 1 << 0 // 0x0001 + flagArgsAreXYValues = 1 << 1 // 0x0002 + flagRoundXYToGrid = 1 << 2 // 0x0004 + flagWeHaveAScale = 1 << 3 // 0x0008 + flagReserved4 = 1 << 4 // 0x0010 + flagMoreComponents = 1 << 5 // 0x0020 + flagWeHaveAnXAndYScale = 1 << 6 // 0x0040 + flagWeHaveATwoByTwo = 1 << 7 // 0x0080 + flagWeHaveInstructions = 1 << 8 // 0x0100 + flagUseMyMetrics = 1 << 9 // 0x0200 + flagOverlapCompound = 1 << 10 // 0x0400 + flagScaledComponentOffset = 1 << 11 // 0x0800 + flagUnscaledComponentOffset = 1 << 12 // 0x1000 +) + +func midPoint(p, q fixed.Point26_6) fixed.Point26_6 { + return fixed.Point26_6{ + X: (p.X + q.X) / 2, + Y: (p.Y + q.Y) / 2, + } +} + +func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int32) (locations []uint32, err error) { + if indexToLocFormat { + if loca.length != 4*uint32(numGlyphs+1) { + return nil, errInvalidLocaTable + } + } else { + if loca.length != 2*uint32(numGlyphs+1) { + return nil, errInvalidLocaTable + } + } + + locations = make([]uint32, numGlyphs+1) + buf, err := src.view(nil, int(loca.offset), int(loca.length)) + if err != nil { + return nil, err + } + + if indexToLocFormat { + for i := range locations { + locations[i] = 1*uint32(u32(buf[4*i:])) + glyfOffset + } + } else { + for i := range locations { + locations[i] = 2*uint32(u16(buf[2*i:])) + glyfOffset + } + } + return locations, err +} + +// https://www.microsoft.com/typography/OTSPEC/glyf.htm says that "Each +// glyph begins with the following [10 byte] header". +const glyfHeaderLen = 10 + +func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error { + data, _, _, err := f.viewGlyphData(b, x) + if err != nil { + return err + } + if len(data) == 0 { + return nil + } + if len(data) < glyfHeaderLen { + return errInvalidGlyphData + } + index := glyfHeaderLen + + numContours, numPoints := int16(u16(data)), 0 + switch { + case numContours == -1: + // We have a compound glyph. No-op. + case numContours == 0: + return nil + case numContours > 0: + // We have a simple (non-compound) glyph. + index += 2 * int(numContours) + if index > len(data) { + return errInvalidGlyphData + } + // The +1 for numPoints is because the value in the file format is + // inclusive, but Go's slice[:index] semantics are exclusive. + numPoints = 1 + int(u16(data[index-2:])) + default: + return errInvalidGlyphData + } + + if numContours < 0 { + return loadCompoundGlyf(f, b, data[glyfHeaderLen:], stackBottom, recursionDepth) + } + + // Skip the hinting instructions. + index += 2 + if index > len(data) { + return errInvalidGlyphData + } + hintsLength := int(u16(data[index-2:])) + index += hintsLength + if index > len(data) { + return errInvalidGlyphData + } + + // For simple (non-compound) glyphs, the remainder of the glyf data + // consists of (flags, x, y) points: the Bézier curve segments. These are + // stored in columns (all the flags first, then all the x coordinates, then + // all the y coordinates), not rows, as it compresses better. + // + // Decoding those points in row order involves two passes. The first pass + // determines the indexes (relative to the data slice) of where the flags, + // the x coordinates and the y coordinates each start. + flagIndex := int32(index) + xIndex, yIndex, ok := findXYIndexes(data, index, numPoints) + if !ok { + return errInvalidGlyphData + } + + // The second pass decodes each (flags, x, y) tuple in row order. + g := glyfIter{ + data: data, + flagIndex: flagIndex, + xIndex: xIndex, + yIndex: yIndex, + endIndex: glyfHeaderLen, + // The -1 is because the contour-end index in the file format is + // inclusive, but Go's slice[:index] semantics are exclusive. + prevEnd: -1, + numContours: int32(numContours), + } + for g.nextContour() { + for g.nextSegment() { + b.segments = append(b.segments, g.seg) + } + } + return g.err +} + +func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) { + xDataLen := 0 + yDataLen := 0 + for i := 0; ; { + if i > numPoints { + return 0, 0, false + } + if i == numPoints { + break + } + + repeatCount := 1 + if index >= len(data) { + return 0, 0, false + } + flag := data[index] + index++ + if flag&flagRepeat != 0 { + if index >= len(data) { + return 0, 0, false + } + repeatCount += int(data[index]) + index++ + } + + xSize := 0 + if flag&flagXShortVector != 0 { + xSize = 1 + } else if flag&flagThisXIsSame == 0 { + xSize = 2 + } + xDataLen += xSize * repeatCount + + ySize := 0 + if flag&flagYShortVector != 0 { + ySize = 1 + } else if flag&flagThisYIsSame == 0 { + ySize = 2 + } + yDataLen += ySize * repeatCount + + i += repeatCount + } + if index+xDataLen+yDataLen > len(data) { + return 0, 0, false + } + return int32(index), int32(index + xDataLen), true +} + +func loadCompoundGlyf(f *Font, b *Buffer, data []byte, stackBottom, recursionDepth uint32) error { + if recursionDepth++; recursionDepth == maxCompoundRecursionDepth { + return errUnsupportedCompoundGlyph + } + + // Read and process the compound glyph's components. They are two separate + // for loops, since reading parses the elements of the data slice, and + // processing can overwrite the backing array. + + stackTop := stackBottom + for { + if stackTop >= maxCompoundStackSize { + return errUnsupportedCompoundGlyph + } + elem := &b.compoundStack[stackTop] + stackTop++ + + if len(data) < 4 { + return errInvalidGlyphData + } + flags := u16(data) + elem.glyphIndex = GlyphIndex(u16(data[2:])) + if flags&flagArg1And2AreWords == 0 { + if len(data) < 6 { + return errInvalidGlyphData + } + elem.dx = int16(int8(data[4])) + elem.dy = int16(int8(data[5])) + data = data[6:] + } else { + if len(data) < 8 { + return errInvalidGlyphData + } + elem.dx = int16(u16(data[4:])) + elem.dy = int16(u16(data[6:])) + data = data[8:] + } + + if flags&flagArgsAreXYValues == 0 { + return errUnsupportedCompoundGlyph + } + elem.hasTransform = flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 + if elem.hasTransform { + switch { + case flags&flagWeHaveAScale != 0: + if len(data) < 2 { + return errInvalidGlyphData + } + elem.transformXX = int16(u16(data)) + elem.transformXY = 0 + elem.transformYX = 0 + elem.transformYY = elem.transformXX + data = data[2:] + case flags&flagWeHaveAnXAndYScale != 0: + if len(data) < 4 { + return errInvalidGlyphData + } + elem.transformXX = int16(u16(data[0:])) + elem.transformXY = 0 + elem.transformYX = 0 + elem.transformYY = int16(u16(data[2:])) + data = data[4:] + case flags&flagWeHaveATwoByTwo != 0: + if len(data) < 8 { + return errInvalidGlyphData + } + elem.transformXX = int16(u16(data[0:])) + elem.transformXY = int16(u16(data[2:])) + elem.transformYX = int16(u16(data[4:])) + elem.transformYY = int16(u16(data[6:])) + data = data[8:] + } + } + + if flags&flagMoreComponents == 0 { + break + } + } + + // To support hinting, we'd have to save the remaining bytes in data here + // and interpret them after the for loop below, since that for loop's + // loadGlyf calls can overwrite the backing array. + + for i := stackBottom; i < stackTop; i++ { + elem := &b.compoundStack[i] + base := len(b.segments) + if err := loadGlyf(f, b, elem.glyphIndex, stackTop, recursionDepth); err != nil { + return err + } + dx, dy := fixed.Int26_6(elem.dx), fixed.Int26_6(elem.dy) + segs := b.segments[base:] + if elem.hasTransform { + txx := elem.transformXX + txy := elem.transformXY + tyx := elem.transformYX + tyy := elem.transformYY + for j := range segs { + transformArgs(&segs[j].Args, txx, txy, tyx, tyy, dx, dy) + } + } else { + for j := range segs { + translateArgs(&segs[j].Args, dx, dy) + } + } + } + + return nil +} + +type glyfIter struct { + data []byte + err error + + // Various indices into the data slice. See the "Decoding those points in + // row order" comment above. + flagIndex int32 + xIndex int32 + yIndex int32 + + // endIndex points to the uint16 that is the inclusive point index of the + // current contour's end. prevEnd is the previous contour's end. + endIndex int32 + prevEnd int32 + + // c and p count the current contour and point, up to numContours and + // numPoints. + c, numContours int32 + p, nPoints int32 + + // The next two groups of fields track points and segments. Points are what + // the underlying file format provides. Bézier curve segments are what the + // rasterizer consumes. + // + // Points are either on-curve or off-curve. Two consecutive on-curve points + // define a linear curve segment between them. N off-curve points between + // on-curve points define N quadratic curve segments. The TrueType glyf + // format does not use cubic curves. If N is greater than 1, some of these + // segment end points are implicit, the midpoint of two off-curve points. + // Given the points A, B1, B2, ..., BN, C, where A and C are on-curve and + // all the Bs are off-curve, the segments are: + // + // - A, B1, midpoint(B1, B2) + // - midpoint(B1, B2), B2, midpoint(B2, B3) + // - midpoint(B2, B3), B3, midpoint(B3, B4) + // - ... + // - midpoint(BN-1, BN), BN, C + // + // Note that the sequence of Bs may wrap around from the last point in the + // glyf data to the first. A and C may also be the same point (the only + // explicit on-curve point), or there may be no explicit on-curve points at + // all (but still implicit ones between explicit off-curve points). + + // Points. + x, y int16 + on bool + flag uint8 + repeats uint8 + + // Segments. + closing bool + closed bool + firstOnCurveValid bool + firstOffCurveValid bool + lastOffCurveValid bool + firstOnCurve fixed.Point26_6 + firstOffCurve fixed.Point26_6 + lastOffCurve fixed.Point26_6 + seg Segment +} + +func (g *glyfIter) nextContour() (ok bool) { + if g.c == g.numContours { + return false + } + g.c++ + + end := int32(u16(g.data[g.endIndex:])) + g.endIndex += 2 + if end <= g.prevEnd { + g.err = errInvalidGlyphData + return false + } + g.nPoints = end - g.prevEnd + g.p = 0 + g.prevEnd = end + + g.closing = false + g.closed = false + g.firstOnCurveValid = false + g.firstOffCurveValid = false + g.lastOffCurveValid = false + + return true +} + +func (g *glyfIter) close() { + switch { + case !g.firstOffCurveValid && !g.lastOffCurveValid: + g.closed = true + g.seg = Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{g.firstOnCurve}, + } + case !g.firstOffCurveValid && g.lastOffCurveValid: + g.closed = true + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.lastOffCurve, g.firstOnCurve}, + } + case g.firstOffCurveValid && !g.lastOffCurveValid: + g.closed = true + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.firstOffCurve, g.firstOnCurve}, + } + case g.firstOffCurveValid && g.lastOffCurveValid: + g.lastOffCurveValid = false + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{ + g.lastOffCurve, + midPoint(g.lastOffCurve, g.firstOffCurve), + }, + } + } +} + +func (g *glyfIter) nextSegment() (ok bool) { + for !g.closed { + if g.closing || !g.nextPoint() { + g.closing = true + g.close() + return true + } + + // Convert the tuple (g.x, g.y) to a fixed.Point26_6, since the latter + // is what's held in a Segment. The input (g.x, g.y) is a pair of int16 + // values, measured in font units, since that is what the underlying + // format provides. The output is a pair of fixed.Int26_6 values. A + // fixed.Int26_6 usually represents a 26.6 fixed number of pixels, but + // this here is just a straight numerical conversion, with no scaling + // factor. A later step scales the Segment.Args values by such a factor + // to convert e.g. 1792 font units to 10.5 pixels at 2048 font units + // per em and 12 ppem (pixels per em). + p := fixed.Point26_6{ + X: fixed.Int26_6(g.x), + Y: fixed.Int26_6(g.y), + } + + if !g.firstOnCurveValid { + if g.on { + g.firstOnCurve = p + g.firstOnCurveValid = true + g.seg = Segment{ + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{p}, + } + return true + } else if !g.firstOffCurveValid { + g.firstOffCurve = p + g.firstOffCurveValid = true + continue + } else { + g.firstOnCurve = midPoint(g.firstOffCurve, p) + g.firstOnCurveValid = true + g.lastOffCurve = p + g.lastOffCurveValid = true + g.seg = Segment{ + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{g.firstOnCurve}, + } + return true + } + + } else if !g.lastOffCurveValid { + if !g.on { + g.lastOffCurve = p + g.lastOffCurveValid = true + continue + } else { + g.seg = Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{p}, + } + return true + } + + } else { + if !g.on { + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{ + g.lastOffCurve, + midPoint(g.lastOffCurve, p), + }, + } + g.lastOffCurve = p + g.lastOffCurveValid = true + return true + } else { + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.lastOffCurve, p}, + } + g.lastOffCurveValid = false + return true + } + } + } + return false +} + +func (g *glyfIter) nextPoint() (ok bool) { + if g.p == g.nPoints { + return false + } + g.p++ + + if g.repeats > 0 { + g.repeats-- + } else { + g.flag = g.data[g.flagIndex] + g.flagIndex++ + if g.flag&flagRepeat != 0 { + g.repeats = g.data[g.flagIndex] + g.flagIndex++ + } + } + + if g.flag&flagXShortVector != 0 { + if g.flag&flagPositiveXShortVector != 0 { + g.x += int16(g.data[g.xIndex]) + } else { + g.x -= int16(g.data[g.xIndex]) + } + g.xIndex += 1 + } else if g.flag&flagThisXIsSame == 0 { + g.x += int16(u16(g.data[g.xIndex:])) + g.xIndex += 2 + } + + if g.flag&flagYShortVector != 0 { + if g.flag&flagPositiveYShortVector != 0 { + g.y += int16(g.data[g.yIndex]) + } else { + g.y -= int16(g.data[g.yIndex]) + } + g.yIndex += 1 + } else if g.flag&flagThisYIsSame == 0 { + g.y += int16(u16(g.data[g.yIndex:])) + g.yIndex += 2 + } + + g.on = g.flag&flagOnCurve != 0 + return true +} -- cgit v1.2.3