git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

commit28d9a8b4a37fa0e5fe8b0cabf09c8b9a0e6ec6f1
parentc8ab450c16
authorNigel Tao <nigeltao@golang.org>
date2017-02-04 13:57
font/sfnt: add tests for proprietary fonts.

Change-Id: I1886e24f726598654d2474f0219a8046ba184a9f
Reviewed-on: https://go-review.googlesource.com/36370
Reviewed-by: David Crawshaw <crawshaw@golang.org>

 font/sfnt/proprietary_test.go | 164 ++++++++++++++++++++++++++++++++++++++++++
 font/sfnt/sfnt.go             |   6 +-
 font/sfnt/truetype.go         |  10 +--
 3 files changed, 174 insertions(+), 6 deletions(-)

diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
new file mode 100644
index 0000000..dc0d725
--- /dev/null
+++ b/font/sfnt/proprietary_test.go
@@ -0,0 +1,164 @@
+// 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=/foo/bar/aFonts -microsoftDir=/foo/bar/mFonts
+
+To only run those tests for the Microsoft fonts:
+
+go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary
+*/
+
+// TODO: add Apple system fonts? Google fonts (Droid? Noto?)? Emoji fonts?
+
+// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
+
+import (
+	"flag"
+	"io/ioutil"
+	"path/filepath"
+	"testing"
+)
+
+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",
+	)
+
+	microsoftDir = flag.String(
+		"microsoftDir",
+		"/usr/share/fonts/truetype/msttcorefonts",
+		"directory name for the Microsoft proprietary fonts",
+	)
+)
+
+type proprietor int
+
+const (
+	adobe proprietor = iota
+	microsoft
+)
+
+func TestProprietaryAdobeSourceCodeProOTF(t *testing.T) {
+	testProprietary(t, adobe, "SourceCodePro-Regular.otf", 1500, 2)
+}
+
+func TestProprietaryAdobeSourceCodeProTTF(t *testing.T) {
+	testProprietary(t, adobe, "SourceCodePro-Regular.ttf", 1500, 36)
+}
+
+func TestProprietaryAdobeSourceHanSansSC(t *testing.T) {
+	testProprietary(t, adobe, "SourceHanSansSC-Regular.otf", 65535, 2)
+}
+
+func TestProprietaryAdobeSourceSansProOTF(t *testing.T) {
+	testProprietary(t, adobe, "SourceSansPro-Regular.otf", 1800, 2)
+}
+
+func TestProprietaryAdobeSourceSansProTTF(t *testing.T) {
+	// The 1000 here is smaller than the 1800 above. For some reason, the TTF
+	// version of the file has fewer glyphs than the (presumably canonical) OTF
+	// version. The number of glyphs in the .otf and .ttf files can be verified
+	// with the ttx tool.
+	testProprietary(t, adobe, "SourceSansPro-Regular.ttf", 1000, 56)
+}
+
+func TestProprietaryMicrosoftArial(t *testing.T) {
+	testProprietary(t, microsoft, "Arial.ttf", 1200, 98)
+}
+
+func TestProprietaryMicrosoftComicSansMS(t *testing.T) {
+	testProprietary(t, microsoft, "Comic_Sans_MS.ttf", 550, 98)
+}
+
+func TestProprietaryMicrosoftTimesNewRoman(t *testing.T) {
+	testProprietary(t, microsoft, "Times_New_Roman.ttf", 1200, 98)
+}
+
+func TestProprietaryMicrosoftWebdings(t *testing.T) {
+	testProprietary(t, microsoft, "Webdings.ttf", 200, -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, p proprietor, filename string, minNumGlyphs, firstUnsupportedGlyph int) {
+	if !*proprietary {
+		t.Skip("skipping proprietary font test")
+	}
+
+	file, err := []byte(nil), error(nil)
+	switch p {
+	case adobe:
+		file, err = ioutil.ReadFile(filepath.Join(*adobeDir, filename))
+		if err != nil {
+			t.Fatalf("%v\nPerhaps you need to set the -adobeDir=%v flag?", err, *adobeDir)
+		}
+	case microsoft:
+		file, err = ioutil.ReadFile(filepath.Join(*microsoftDir, filename))
+		if err != nil {
+			t.Fatalf("%v\nPerhaps you need to set the -microsoftDir=%v flag?", err, *microsoftDir)
+		}
+	}
+	f, err := Parse(file)
+	if err != nil {
+		t.Fatalf("Parse: %v", err)
+	}
+
+	numGlyphs := f.NumGlyphs()
+	if numGlyphs < minNumGlyphs {
+		t.Fatalf("NumGlyphs: got %d, want at least %d", numGlyphs, minNumGlyphs)
+	}
+
+	var buf Buffer
+	iMax := numGlyphs
+	if firstUnsupportedGlyph >= 0 {
+		iMax = firstUnsupportedGlyph
+	}
+	for i, numErrors := 0, 0; i < iMax; i++ {
+		if _, err := f.LoadGlyph(&buf, GlyphIndex(i), nil); err != nil {
+			t.Errorf("LoadGlyph(%d): %v", i, err)
+			numErrors++
+		}
+		if numErrors == 10 {
+			t.Fatal("LoadGlyph: too many errors")
+		}
+	}
+}
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 83065b1..8757d7a 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -28,7 +28,11 @@ import (
 // These constants are not part of the specifications, but are limitations used
 // by this implementation.
 const (
-	maxCmapSegments     = 1024
+	// 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 1581 cmap segments.
+	maxCmapSegments = 4096
+
 	maxGlyphDataLength  = 64 * 1024
 	maxHintBits         = 256
 	maxNumTables        = 256
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
index 851904d..be90154 100644
--- a/font/sfnt/truetype.go
+++ b/font/sfnt/truetype.go
@@ -113,6 +113,11 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
 		return nil, errInvalidGlyphData
 	}
 
+	// TODO: support compound glyphs.
+	if numContours < 0 {
+		return nil, errUnsupportedCompoundGlyph
+	}
+
 	// Skip the hinting instructions.
 	index += 2
 	if index > len(data) {
@@ -124,11 +129,6 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
 		return nil, errInvalidGlyphData
 	}
 
-	// TODO: support compound glyphs.
-	if numContours < 0 {
-		return nil, errUnsupportedCompoundGlyph
-	}
-
 	// 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 co-ordinates,