2012-06-16 04:19:07 +02:00
|
|
|
|
// Copyright 2012 The Freetype-Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by your choice of either the
|
|
|
|
|
// FreeType License or the GNU General Public License version 2 (or
|
|
|
|
|
// any later version), both of which can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package truetype
|
|
|
|
|
|
|
|
|
|
import (
|
2013-08-13 13:08:54 +02:00
|
|
|
|
"bufio"
|
2012-06-16 04:19:07 +02:00
|
|
|
|
"fmt"
|
2013-08-13 13:08:54 +02:00
|
|
|
|
"io"
|
2012-06-16 04:19:07 +02:00
|
|
|
|
"io/ioutil"
|
2013-08-13 13:08:54 +02:00
|
|
|
|
"os"
|
2013-10-13 06:43:34 +02:00
|
|
|
|
"strconv"
|
2013-08-13 13:08:54 +02:00
|
|
|
|
"strings"
|
2012-06-16 04:19:07 +02:00
|
|
|
|
"testing"
|
2015-08-18 08:30:37 +02:00
|
|
|
|
|
2023-09-21 21:41:56 +02:00
|
|
|
|
"git.fireandbrimst.one/aw/golang-image/font"
|
|
|
|
|
"git.fireandbrimst.one/aw/golang-image/math/fixed"
|
2012-06-16 04:19:07 +02:00
|
|
|
|
)
|
|
|
|
|
|
2015-08-20 08:07:51 +02:00
|
|
|
|
func parseTestdataFont(name string) (f *Font, testdataIsOptional bool, err error) {
|
2015-08-12 06:32:40 +02:00
|
|
|
|
b, err := ioutil.ReadFile(fmt.Sprintf("../testdata/%s.ttf", name))
|
2013-10-10 22:29:40 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
// The "x-foo" fonts are optional tests, as they are not checked
|
|
|
|
|
// in for copyright or file size reasons.
|
|
|
|
|
return nil, strings.HasPrefix(name, "x-"), fmt.Errorf("%s: ReadFile: %v", name, err)
|
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
f, err = Parse(b)
|
2013-10-10 22:29:40 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, true, fmt.Errorf("%s: Parse: %v", name, err)
|
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
return f, false, nil
|
2013-10-10 22:29:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 14:06:37 +02:00
|
|
|
|
func mkBounds(minX, minY, maxX, maxY fixed.Int26_6) fixed.Rectangle26_6 {
|
|
|
|
|
return fixed.Rectangle26_6{
|
|
|
|
|
Min: fixed.Point26_6{
|
|
|
|
|
X: minX,
|
|
|
|
|
Y: minY,
|
|
|
|
|
},
|
|
|
|
|
Max: fixed.Point26_6{
|
|
|
|
|
X: maxX,
|
|
|
|
|
Y: maxY,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-16 04:19:07 +02:00
|
|
|
|
// TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly.
|
|
|
|
|
// The numerical values can be manually verified by examining luxisr.ttx.
|
|
|
|
|
func TestParse(t *testing.T) {
|
2015-08-20 08:07:51 +02:00
|
|
|
|
f, _, err := parseTestdataFont("luxisr")
|
2012-06-16 04:19:07 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
if got, want := f.FUnitsPerEm(), int32(2048); got != want {
|
2012-07-25 14:10:25 +02:00
|
|
|
|
t.Errorf("FUnitsPerEm: got %v, want %v", got, want)
|
2012-06-16 04:19:07 +02:00
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
fupe := fixed.Int26_6(f.FUnitsPerEm())
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 14:06:37 +02:00
|
|
|
|
if got, want := f.Bounds(fupe), mkBounds(-441, -432, 2024, 2033); got != want {
|
2012-07-25 14:10:25 +02:00
|
|
|
|
t.Errorf("Bounds: got %v, want %v", got, want)
|
2012-06-16 04:19:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-20 08:07:51 +02:00
|
|
|
|
i0 := f.Index('A')
|
|
|
|
|
i1 := f.Index('V')
|
2012-06-16 04:19:07 +02:00
|
|
|
|
if i0 != 36 || i1 != 57 {
|
|
|
|
|
t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1)
|
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
if got, want := f.HMetric(fupe, i0), (HMetric{1366, 19}); got != want {
|
2012-06-16 04:19:07 +02:00
|
|
|
|
t.Errorf("HMetric: got %v, want %v", got, want)
|
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
if got, want := f.VMetric(fupe, i0), (VMetric{2465, 553}); got != want {
|
2013-11-04 23:58:40 +01:00
|
|
|
|
t.Errorf("VMetric: got %v, want %v", got, want)
|
|
|
|
|
}
|
2015-08-24 08:17:16 +02:00
|
|
|
|
if got, want := f.Kern(fupe, i0, i1), fixed.Int26_6(-144); got != want {
|
|
|
|
|
t.Errorf("Kern: got %v, want %v", got, want)
|
2012-06-16 04:19:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-08-30 13:35:58 +02:00
|
|
|
|
g := &GlyphBuf{}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
err = g.Load(f, fupe, i0, font.HintingNone)
|
2012-06-16 04:19:07 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Load: %v", err)
|
|
|
|
|
}
|
2013-10-16 09:54:54 +02:00
|
|
|
|
g0 := &GlyphBuf{
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 14:06:37 +02:00
|
|
|
|
Bounds: g.Bounds,
|
2015-08-30 14:27:18 +02:00
|
|
|
|
Points: g.Points,
|
|
|
|
|
Ends: g.Ends,
|
2013-10-16 09:54:54 +02:00
|
|
|
|
}
|
2012-06-16 04:19:07 +02:00
|
|
|
|
g1 := &GlyphBuf{
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 14:06:37 +02:00
|
|
|
|
Bounds: mkBounds(19, 0, 1342, 1480),
|
2015-08-30 14:27:18 +02:00
|
|
|
|
Points: []Point{
|
2012-06-16 04:19:07 +02:00
|
|
|
|
{19, 0, 51},
|
|
|
|
|
{581, 1480, 1},
|
|
|
|
|
{789, 1480, 51},
|
|
|
|
|
{1342, 0, 1},
|
|
|
|
|
{1116, 0, 35},
|
|
|
|
|
{962, 410, 3},
|
|
|
|
|
{368, 410, 33},
|
|
|
|
|
{214, 0, 3},
|
|
|
|
|
{428, 566, 19},
|
|
|
|
|
{904, 566, 33},
|
|
|
|
|
{667, 1200, 3},
|
|
|
|
|
},
|
2015-08-30 14:27:18 +02:00
|
|
|
|
Ends: []int{8, 11},
|
2012-06-16 04:19:07 +02:00
|
|
|
|
}
|
|
|
|
|
if got, want := fmt.Sprint(g0), fmt.Sprint(g1); got != want {
|
|
|
|
|
t.Errorf("GlyphBuf:\ngot %v\nwant %v", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-08-13 13:08:54 +02:00
|
|
|
|
|
2013-10-10 22:29:40 +02:00
|
|
|
|
func TestIndex(t *testing.T) {
|
|
|
|
|
testCases := map[string]map[rune]Index{
|
|
|
|
|
"luxisr": {
|
|
|
|
|
' ': 3,
|
|
|
|
|
'!': 4,
|
|
|
|
|
'A': 36,
|
|
|
|
|
'V': 57,
|
|
|
|
|
'É': 101,
|
|
|
|
|
'fl': 193,
|
|
|
|
|
'\u22c5': 385,
|
|
|
|
|
'中': 0,
|
|
|
|
|
},
|
2014-11-06 23:38:15 +01:00
|
|
|
|
|
|
|
|
|
// The x-etc test cases use those versions of the .ttf files provided
|
|
|
|
|
// by Ubuntu 14.04. See testdata/make-other-hinting-txts.sh for details.
|
|
|
|
|
|
2013-10-10 22:29:40 +02:00
|
|
|
|
"x-arial-bold": {
|
|
|
|
|
' ': 3,
|
|
|
|
|
'+': 14,
|
|
|
|
|
'0': 19,
|
|
|
|
|
'_': 66,
|
|
|
|
|
'w': 90,
|
|
|
|
|
'~': 97,
|
|
|
|
|
'Ä': 98,
|
|
|
|
|
'fl': 192,
|
|
|
|
|
'½': 242,
|
|
|
|
|
'σ': 305,
|
|
|
|
|
'λ': 540,
|
|
|
|
|
'ỹ': 1275,
|
|
|
|
|
'\u04e9': 1319,
|
|
|
|
|
'中': 0,
|
|
|
|
|
},
|
|
|
|
|
"x-deja-vu-sans-oblique": {
|
|
|
|
|
' ': 3,
|
|
|
|
|
'*': 13,
|
|
|
|
|
'Œ': 276,
|
|
|
|
|
'ω': 861,
|
|
|
|
|
'‡': 2571,
|
2014-11-06 23:38:15 +01:00
|
|
|
|
'⊕': 3110,
|
|
|
|
|
'fl': 4728,
|
|
|
|
|
'\ufb03': 4729,
|
|
|
|
|
'\ufffd': 4813,
|
2013-10-10 22:29:40 +02:00
|
|
|
|
// TODO: '\U0001f640': ???,
|
|
|
|
|
'中': 0,
|
|
|
|
|
},
|
|
|
|
|
"x-droid-sans-japanese": {
|
|
|
|
|
' ': 0,
|
|
|
|
|
'\u3000': 3,
|
|
|
|
|
'\u3041': 25,
|
|
|
|
|
'\u30fe': 201,
|
|
|
|
|
'\uff61': 202,
|
|
|
|
|
'\uff67': 208,
|
|
|
|
|
'\uff9e': 263,
|
|
|
|
|
'\uff9f': 264,
|
|
|
|
|
'\u4e00': 265,
|
|
|
|
|
'\u557e': 1000,
|
|
|
|
|
'\u61b6': 2024,
|
|
|
|
|
'\u6ede': 3177,
|
|
|
|
|
'\u7505': 3555,
|
|
|
|
|
'\u81e3': 4602,
|
|
|
|
|
'\u81e5': 4603,
|
|
|
|
|
'\u81e7': 4604,
|
|
|
|
|
'\u81e8': 4605,
|
|
|
|
|
'\u81ea': 4606,
|
|
|
|
|
'\u81ed': 4607,
|
|
|
|
|
'\u81f3': 4608,
|
|
|
|
|
'\u81f4': 4609,
|
|
|
|
|
'\u91c7': 5796,
|
|
|
|
|
'\u9fa0': 6620,
|
|
|
|
|
'\u203e': 12584,
|
|
|
|
|
},
|
|
|
|
|
"x-times-new-roman": {
|
|
|
|
|
' ': 3,
|
|
|
|
|
':': 29,
|
|
|
|
|
'fl': 192,
|
|
|
|
|
'Ŀ': 273,
|
|
|
|
|
'♠': 388,
|
|
|
|
|
'Ŗ': 451,
|
|
|
|
|
'Σ': 520,
|
|
|
|
|
'\u200D': 745,
|
|
|
|
|
'Ẽ': 1216,
|
|
|
|
|
'\u04e9': 1319,
|
|
|
|
|
'中': 0,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for name, wants := range testCases {
|
2015-08-20 08:07:51 +02:00
|
|
|
|
f, testdataIsOptional, err := parseTestdataFont(name)
|
2013-10-10 22:29:40 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
if testdataIsOptional {
|
|
|
|
|
t.Log(err)
|
|
|
|
|
} else {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for r, want := range wants {
|
2015-08-20 08:07:51 +02:00
|
|
|
|
if got := f.Index(r); got != want {
|
2013-10-12 00:29:59 +02:00
|
|
|
|
t.Errorf("%s: Index of %q, aka %U: got %d, want %d", name, r, r, got, want)
|
2013-10-10 22:29:40 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-21 08:46:35 +02:00
|
|
|
|
func TestName(t *testing.T) {
|
|
|
|
|
testCases := map[string]string{
|
|
|
|
|
"luximr": "Luxi Mono",
|
|
|
|
|
"luxirr": "Luxi Serif",
|
|
|
|
|
"luxisr": "Luxi Sans",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, want := range testCases {
|
|
|
|
|
f, testdataIsOptional, err := parseTestdataFont(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if testdataIsOptional {
|
|
|
|
|
t.Log(err)
|
|
|
|
|
} else {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if got := f.Name(NameIDFontFamily); got != want {
|
|
|
|
|
t.Errorf("%s: got %q, want %q", name, got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-16 03:47:21 +01:00
|
|
|
|
type scalingTestData struct {
|
2015-08-18 08:30:37 +02:00
|
|
|
|
advanceWidth fixed.Int26_6
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 14:06:37 +02:00
|
|
|
|
bounds fixed.Rectangle26_6
|
2013-12-16 03:47:21 +01:00
|
|
|
|
points []Point
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-13 06:43:34 +02:00
|
|
|
|
// scalingTestParse parses a line of points like
|
2013-12-16 03:47:21 +01:00
|
|
|
|
// 213 -22 -111 236 555;-22 -111 1, 178 555 1, 236 555 1, 36 -111 1
|
2013-10-13 06:43:34 +02:00
|
|
|
|
// The line will not have a trailing "\n".
|
2013-12-16 03:47:21 +01:00
|
|
|
|
func scalingTestParse(line string) (ret scalingTestData) {
|
2015-08-18 08:30:37 +02:00
|
|
|
|
next := func(s string) (string, fixed.Int26_6) {
|
2013-12-16 03:47:21 +01:00
|
|
|
|
t, i := "", strings.Index(s, " ")
|
|
|
|
|
if i != -1 {
|
|
|
|
|
s, t = s[:i], s[i+1:]
|
|
|
|
|
}
|
|
|
|
|
x, _ := strconv.Atoi(s)
|
2015-08-18 08:30:37 +02:00
|
|
|
|
return t, fixed.Int26_6(x)
|
2013-10-13 06:43:34 +02:00
|
|
|
|
}
|
2013-12-16 03:47:21 +01:00
|
|
|
|
|
|
|
|
|
i := strings.Index(line, ";")
|
|
|
|
|
prefix, line := line[:i], line[i+1:]
|
|
|
|
|
|
|
|
|
|
prefix, ret.advanceWidth = next(prefix)
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 14:06:37 +02:00
|
|
|
|
prefix, ret.bounds.Min.X = next(prefix)
|
|
|
|
|
prefix, ret.bounds.Min.Y = next(prefix)
|
|
|
|
|
prefix, ret.bounds.Max.X = next(prefix)
|
|
|
|
|
prefix, ret.bounds.Max.Y = next(prefix)
|
2013-12-16 03:47:21 +01:00
|
|
|
|
|
|
|
|
|
ret.points = make([]Point, 0, 1+strings.Count(line, ","))
|
2013-10-13 06:43:34 +02:00
|
|
|
|
for len(line) > 0 {
|
|
|
|
|
s := line
|
|
|
|
|
if i := strings.Index(line, ","); i != -1 {
|
|
|
|
|
s, line = line[:i], line[i+1:]
|
|
|
|
|
for len(line) > 0 && line[0] == ' ' {
|
|
|
|
|
line = line[1:]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
line = ""
|
|
|
|
|
}
|
2013-12-16 03:47:21 +01:00
|
|
|
|
s, x := next(s)
|
|
|
|
|
s, y := next(s)
|
|
|
|
|
s, f := next(s)
|
|
|
|
|
ret.points = append(ret.points, Point{X: x, Y: y, Flags: uint32(f)})
|
2013-10-13 06:43:34 +02:00
|
|
|
|
}
|
2013-12-16 03:47:21 +01:00
|
|
|
|
return ret
|
2013-10-13 06:43:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// scalingTestEquals is equivalent to, but faster than, calling
|
2017-06-09 02:35:04 +02:00
|
|
|
|
// reflect.DeepEqual(a, b), and also returns the index of the first non-equal
|
2013-12-03 00:57:02 +01:00
|
|
|
|
// element. It also treats a nil []Point and an empty non-nil []Point as equal.
|
|
|
|
|
// a and b must have equal length.
|
|
|
|
|
func scalingTestEquals(a, b []Point) (index int, equals bool) {
|
2013-10-13 06:43:34 +02:00
|
|
|
|
for i, p := range a {
|
|
|
|
|
if p != b[i] {
|
2013-12-03 00:57:02 +01:00
|
|
|
|
return i, false
|
2013-10-13 06:43:34 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-12-03 00:57:02 +01:00
|
|
|
|
return 0, true
|
2013-10-13 06:43:34 +02:00
|
|
|
|
}
|
|
|
|
|
|
2013-10-09 11:07:32 +02:00
|
|
|
|
var scalingTestCases = []struct {
|
|
|
|
|
name string
|
2015-08-18 08:30:37 +02:00
|
|
|
|
size int
|
2013-10-09 11:07:32 +02:00
|
|
|
|
}{
|
2014-01-13 23:58:07 +01:00
|
|
|
|
{"luxisr", 12},
|
|
|
|
|
{"x-arial-bold", 11},
|
|
|
|
|
{"x-deja-vu-sans-oblique", 17},
|
|
|
|
|
{"x-droid-sans-japanese", 9},
|
|
|
|
|
{"x-times-new-roman", 13},
|
2013-10-09 11:07:32 +02:00
|
|
|
|
}
|
2013-08-13 13:08:54 +02:00
|
|
|
|
|
2015-08-20 08:07:51 +02:00
|
|
|
|
func testScaling(t *testing.T, h font.Hinting) {
|
2013-10-09 11:07:32 +02:00
|
|
|
|
for _, tc := range scalingTestCases {
|
2015-08-20 08:07:51 +02:00
|
|
|
|
f, testdataIsOptional, err := parseTestdataFont(tc.name)
|
2013-10-09 11:07:32 +02:00
|
|
|
|
if err != nil {
|
2013-10-10 22:29:40 +02:00
|
|
|
|
if testdataIsOptional {
|
|
|
|
|
t.Log(err)
|
2013-10-09 11:07:32 +02:00
|
|
|
|
} else {
|
2013-10-10 22:29:40 +02:00
|
|
|
|
t.Error(err)
|
2013-08-13 13:08:54 +02:00
|
|
|
|
}
|
2013-10-16 12:39:06 +02:00
|
|
|
|
continue
|
2013-08-13 13:08:54 +02:00
|
|
|
|
}
|
2014-02-01 04:12:48 +01:00
|
|
|
|
hintingStr := "sans"
|
2015-08-20 08:07:51 +02:00
|
|
|
|
if h != font.HintingNone {
|
2014-02-01 04:12:48 +01:00
|
|
|
|
hintingStr = "with"
|
2013-10-09 11:07:32 +02:00
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
testFile, err := os.Open(fmt.Sprintf(
|
2015-08-12 06:32:40 +02:00
|
|
|
|
"../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hintingStr))
|
2013-10-09 11:07:32 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("%s: Open: %v", tc.name, err)
|
2013-10-16 12:39:06 +02:00
|
|
|
|
continue
|
2013-10-09 11:07:32 +02:00
|
|
|
|
}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
defer testFile.Close()
|
2013-08-13 13:08:54 +02:00
|
|
|
|
|
2013-12-16 03:47:21 +01:00
|
|
|
|
wants := []scalingTestData{}
|
2015-08-20 08:07:51 +02:00
|
|
|
|
scanner := bufio.NewScanner(testFile)
|
2013-11-28 04:02:28 +01:00
|
|
|
|
if scanner.Scan() {
|
|
|
|
|
major, minor, patch := 0, 0, 0
|
|
|
|
|
_, err := fmt.Sscanf(scanner.Text(), "freetype version %d.%d.%d", &major, &minor, &patch)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("%s: version information: %v", tc.name, err)
|
|
|
|
|
}
|
|
|
|
|
if (major < 2) || (major == 2 && minor < 5) || (major == 2 && minor == 5 && patch < 1) {
|
|
|
|
|
t.Errorf("%s: need freetype version >= 2.5.1.\n"+
|
|
|
|
|
"Try setting LD_LIBRARY_PATH=/path/to/freetype_built_from_src/objs/.libs/\n"+
|
|
|
|
|
"and re-running testdata/make-other-hinting-txts.sh",
|
|
|
|
|
tc.name)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
t.Errorf("%s: no version information", tc.name)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2013-10-09 11:07:32 +02:00
|
|
|
|
for scanner.Scan() {
|
2013-10-13 06:43:34 +02:00
|
|
|
|
wants = append(wants, scalingTestParse(scanner.Text()))
|
2013-08-13 13:08:54 +02:00
|
|
|
|
}
|
2013-10-09 11:07:32 +02:00
|
|
|
|
if err := scanner.Err(); err != nil && err != io.EOF {
|
|
|
|
|
t.Errorf("%s: Scanner: %v", tc.name, err)
|
2013-10-16 12:39:06 +02:00
|
|
|
|
continue
|
2013-08-13 13:08:54 +02:00
|
|
|
|
}
|
2013-10-09 11:07:32 +02:00
|
|
|
|
|
2015-08-30 13:35:58 +02:00
|
|
|
|
glyphBuf := &GlyphBuf{}
|
2013-10-09 11:07:32 +02:00
|
|
|
|
for i, want := range wants {
|
2015-08-20 08:07:51 +02:00
|
|
|
|
if err = glyphBuf.Load(f, fixed.I(tc.size), Index(i), h); err != nil {
|
2013-10-09 11:07:32 +02:00
|
|
|
|
t.Errorf("%s: glyph #%d: Load: %v", tc.name, i, err)
|
2013-10-16 12:39:06 +02:00
|
|
|
|
continue
|
2013-10-09 11:07:32 +02:00
|
|
|
|
}
|
2013-12-16 03:47:21 +01:00
|
|
|
|
got := scalingTestData{
|
2014-01-13 23:58:07 +01:00
|
|
|
|
advanceWidth: glyphBuf.AdvanceWidth,
|
Use fixed.Rectangle26_6 instead of truetype.Bounds.
The previous "the endpoints are inclusive" comment seems confusing. It's true
that the bounding box's max X equals the right-most coordinate, which suggests
<= instead of <, but that node's coordinate is itself exclusive. Consider the
solid 1-pixel square: (0, 0), (64, 0), (64, 64), (0, 64) in fixed.Point26_6
coordinates. The right-most coordinate is 64, and the bounding box's max X
equals 64, but rasterizing that square only affects sub-pixels up to but not
including 64.
Instead, it seems accurate to follow the fixed.Rectangle26_6 description, in
that the max values are exclusive.
2015-08-30 14:06:37 +02:00
|
|
|
|
bounds: glyphBuf.Bounds,
|
2015-08-30 14:27:18 +02:00
|
|
|
|
points: glyphBuf.Points,
|
2013-12-16 03:47:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.advanceWidth != want.advanceWidth {
|
|
|
|
|
t.Errorf("%s: glyph #%d advance width:\ngot %v\nwant %v",
|
|
|
|
|
tc.name, i, got.advanceWidth, want.advanceWidth)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got.bounds != want.bounds {
|
|
|
|
|
t.Errorf("%s: glyph #%d bounds:\ngot %v\nwant %v",
|
|
|
|
|
tc.name, i, got.bounds, want.bounds)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := range got.points {
|
|
|
|
|
got.points[i].Flags &= 0x01
|
2013-10-09 11:07:32 +02:00
|
|
|
|
}
|
2013-12-16 03:47:21 +01:00
|
|
|
|
if len(got.points) != len(want.points) {
|
2013-12-03 00:57:02 +01:00
|
|
|
|
t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\ndifferent slice lengths: %d versus %d",
|
2013-12-16 03:47:21 +01:00
|
|
|
|
tc.name, i, got.points, want.points, len(got.points), len(want.points))
|
2013-12-03 00:57:02 +01:00
|
|
|
|
continue
|
|
|
|
|
}
|
2013-12-16 03:47:21 +01:00
|
|
|
|
if j, equals := scalingTestEquals(got.points, want.points); !equals {
|
2013-12-03 00:57:02 +01:00
|
|
|
|
t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\nat index %d: %v versus %v",
|
2013-12-16 03:47:21 +01:00
|
|
|
|
tc.name, i, got.points, want.points, j, got.points[j], want.points[j])
|
2013-12-03 00:57:02 +01:00
|
|
|
|
continue
|
2013-10-09 11:07:32 +02:00
|
|
|
|
}
|
2013-08-13 13:08:54 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-20 08:07:51 +02:00
|
|
|
|
func TestScalingHintingNone(t *testing.T) { testScaling(t, font.HintingNone) }
|
|
|
|
|
func TestScalingHintingFull(t *testing.T) { testScaling(t, font.HintingFull) }
|