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>
This commit is contained in:
Nigel Tao 2017-02-04 13:57:53 +11:00
parent c8ab450c16
commit 28d9a8b4a3
3 changed files with 174 additions and 6 deletions

View File

@ -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")
}
}
}

View File

@ -28,7 +28,11 @@ import (
// These constants are not part of the specifications, but are limitations used // These constants are not part of the specifications, but are limitations used
// by this implementation. // by this implementation.
const ( 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 maxGlyphDataLength = 64 * 1024
maxHintBits = 256 maxHintBits = 256
maxNumTables = 256 maxNumTables = 256

View File

@ -113,6 +113,11 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
return nil, errInvalidGlyphData return nil, errInvalidGlyphData
} }
// TODO: support compound glyphs.
if numContours < 0 {
return nil, errUnsupportedCompoundGlyph
}
// Skip the hinting instructions. // Skip the hinting instructions.
index += 2 index += 2
if index > len(data) { if index > len(data) {
@ -124,11 +129,6 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
return nil, errInvalidGlyphData return nil, errInvalidGlyphData
} }
// TODO: support compound glyphs.
if numContours < 0 {
return nil, errUnsupportedCompoundGlyph
}
// For simple (non-compound) glyphs, the remainder of the glyf data // For simple (non-compound) glyphs, the remainder of the glyf data
// consists of (flags, x, y) points: the Bézier curve segments. These are // 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, // stored in columns (all the flags first, then all the x co-ordinates,