golang-image/font/sfnt/sfnt_test.go
Denys Smirnov d9c2484c48 font/sfnt: parse and expose PostScript information
Currently the library only parses the version in PostScript table.
However use cases such as PDF document processing requires this
information to be exposed.

CL parses a minimal set of the fields from the PostScript table
and exposes it via new PostTable method.

Change-Id: Ia86eecea9f5aaf557c7e4737f2474966aa30cff2
Reviewed-on: https://go-review.googlesource.com/c/145797
Reviewed-by: Nigel Tao <nigeltao@golang.org>
2018-11-15 06:11:51 +00:00

867 lines
20 KiB
Go

// 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"
"image"
"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 TestMetrics(t *testing.T) {
cmapFont, err := ioutil.ReadFile(filepath.FromSlash("../testdata/cmapTest.ttf"))
if err != nil {
t.Fatal(err)
}
testCases := map[string]struct {
font []byte
want font.Metrics
}{
"goregular": {goregular.TTF, font.Metrics{Height: 2367, Ascent: 1935, Descent: 432, XHeight: 1086, CapHeight: 1480,
CaretSlope: image.Point{X: 0, Y: 1}}},
// cmapTest.ttf has a non-zero lineGap.
"cmapTest": {cmapFont, font.Metrics{Height: 1549, Ascent: 1365, Descent: 0, XHeight: 800, CapHeight: 800,
CaretSlope: image.Point{X: 20, Y: 100}}},
}
var b Buffer
for name, tc := range testCases {
f, err := Parse(tc.font)
if err != nil {
t.Errorf("name=%q: Parse: %v", name, err)
continue
}
ppem := fixed.Int26_6(f.UnitsPerEm())
got, err := f.Metrics(&b, ppem, font.HintingNone)
if err != nil {
t.Errorf("name=%q: Metrics: %v", name, err)
continue
}
if got != tc.want {
t.Errorf("name=%q: Metrics: got %v, want %v", name, got, tc.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 <control>
{'\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 TestPostInfo(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)
}
post := f.PostTable()
if post.ItalicAngle != -11.25 {
t.Error("ItalicAngle:", post.ItalicAngle)
}
if post.UnderlinePosition != -255 {
t.Error("UnderlinePosition:", post.UnderlinePosition)
}
if post.UnderlineThickness != 102 {
t.Error("UnderlineThickness:", post.UnderlineThickness)
}
if post.IsFixedPitch {
t.Error("IsFixedPitch:", post.IsFixedPitch)
}
}
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 <Private Use>
{'\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)
}
}
}