blob: c68d78c91ea2dc335af5d450019ebfb4c44ad719 [file] [log] [blame]
// 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"
"io/ioutil"
"path/filepath"
"testing"
"golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/math/fixed"
)
func moveTo(xa, ya int) Segment {
return Segment{
Op: SegmentOpMoveTo,
Args: [6]fixed.Int26_6{
0: fixed.I(xa),
1: fixed.I(ya),
},
}
}
func lineTo(xa, ya int) Segment {
return Segment{
Op: SegmentOpLineTo,
Args: [6]fixed.Int26_6{
0: fixed.I(xa),
1: fixed.I(ya),
},
}
}
func quadTo(xa, ya, xb, yb int) Segment {
return Segment{
Op: SegmentOpQuadTo,
Args: [6]fixed.Int26_6{
0: fixed.I(xa),
1: fixed.I(ya),
2: fixed.I(xb),
3: fixed.I(yb),
},
}
}
func cubeTo(xa, ya, xb, yb, xc, yc int) Segment {
return Segment{
Op: SegmentOpCubeTo,
Args: [6]fixed.Int26_6{
0: fixed.I(xa),
1: fixed.I(ya),
2: fixed.I(xb),
3: fixed.I(yb),
4: fixed.I(xc),
5: fixed.I(yc),
},
}
}
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 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),
// - contour #1
moveTo(100, 50),
lineTo(100, 483),
lineTo(400, 483),
lineTo(400, 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),
}, {
// Q
// - contour #0
moveTo(657, 237),
lineTo(289, 387),
lineTo(519, 615),
// - 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),
}, {
// 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),
// TODO: explicitly (not implicitly) close these contours?
}}
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),
}}
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.Fatal(err)
}
f, err := Parse(data)
if err != nil {
t.Fatal(err)
}
if ng := f.NumGlyphs(); ng != len(wants) {
t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
}
var b Buffer
loop:
for i, want := range wants {
got, err := f.LoadGlyph(&b, GlyphIndex(i), nil)
if err != nil {
t.Errorf("i=%d: LoadGlyph: %v", i, err)
continue
}
if len(got) != len(want) {
t.Errorf("i=%d: got %d elements, want %d\noverall:\ngot %v\nwant %v",
i, len(got), len(want), got, want)
continue
}
for j, g := range got {
if w := want[j]; g != w {
t.Errorf("i=%d: element %d:\ngot %v\nwant %v\noverall:\ngot %v\nwant %v",
i, j, g, w, got, want)
continue loop
}
}
}
if _, err := f.LoadGlyph(nil, 0xffff, 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)
}
}