From 28d9a8b4a37fa0e5fe8b0cabf09c8b9a0e6ec6f1 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Sat, 4 Feb 2017 13:57:53 +1100 Subject: [PATCH] font/sfnt: add tests for proprietary fonts. Change-Id: I1886e24f726598654d2474f0219a8046ba184a9f Reviewed-on: https://go-review.googlesource.com/36370 Reviewed-by: David Crawshaw --- font/sfnt/proprietary_test.go | 164 ++++++++++++++++++++++++++++++++++ font/sfnt/sfnt.go | 6 +- font/sfnt/truetype.go | 10 +-- 3 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 font/sfnt/proprietary_test.go 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,