font/sfnt: support font collections (.ttc and .otc files).
Also add tests for Apple proprietary fonts. Change-Id: I5ce8efa2397bb01c5255d956a77c955ba1383105 Reviewed-on: https://go-review.googlesource.com/38272 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
1995ed1a25
commit
6847effb9b
|
@ -19,14 +19,14 @@ 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
|
that such fonts have already been installed. You may need to specify the
|
||||||
directories for these fonts:
|
directories for these fonts:
|
||||||
|
|
||||||
go test golang.org/x/image/font/sfnt -args -proprietary -adobeDir=/foo/bar/aFonts -microsoftDir=/foo/bar/mFonts
|
go test golang.org/x/image/font/sfnt -args -proprietary -adobeDir=$HOME/fonts/adobe -appleDir=$HOME/fonts/apple -microsoftDir=$HOME/fonts/microsoft
|
||||||
|
|
||||||
To only run those tests for the Microsoft fonts:
|
To only run those tests for the Microsoft fonts:
|
||||||
|
|
||||||
go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary
|
go test golang.org/x/image/font/sfnt -test.run=ProprietaryMicrosoft -args -proprietary etc
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: add Apple system fonts? Google fonts (Droid? Noto?)? Emoji fonts?
|
// TODO: add Google fonts (Droid? Noto?)? Emoji fonts?
|
||||||
|
|
||||||
// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
|
// TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
|
@ -60,6 +62,16 @@ var (
|
||||||
"directory name for the Adobe proprietary fonts",
|
"directory name for the Adobe proprietary fonts",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
appleDir = flag.String(
|
||||||
|
"appleDir",
|
||||||
|
// This needs to be set explicitly. These fonts come with macOS, which
|
||||||
|
// is widely available but not freely available.
|
||||||
|
//
|
||||||
|
// On a Mac, set this to "/System/Library/Fonts/".
|
||||||
|
"",
|
||||||
|
"directory name for the Apple proprietary fonts",
|
||||||
|
)
|
||||||
|
|
||||||
microsoftDir = flag.String(
|
microsoftDir = flag.String(
|
||||||
"microsoftDir",
|
"microsoftDir",
|
||||||
"/usr/share/fonts/truetype/msttcorefonts",
|
"/usr/share/fonts/truetype/msttcorefonts",
|
||||||
|
@ -87,10 +99,26 @@ func TestProprietaryAdobeSourceSansProTTF(t *testing.T) {
|
||||||
testProprietary(t, "adobe", "SourceSansPro-Regular.ttf", 1800, 54)
|
testProprietary(t, "adobe", "SourceSansPro-Regular.ttf", 1800, 54)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProprietaryAppleAppleSymbols(t *testing.T) {
|
||||||
|
testProprietary(t, "apple", "Apple Symbols.ttf", 4600, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProprietaryAppleHiragino0(t *testing.T) {
|
||||||
|
testProprietary(t, "apple", "ヒラギノ角ゴシック W0.ttc?0", 9000, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProprietaryAppleHiragino1(t *testing.T) {
|
||||||
|
testProprietary(t, "apple", "ヒラギノ角ゴシック W0.ttc?1", 9000, 6)
|
||||||
|
}
|
||||||
|
|
||||||
func TestProprietaryMicrosoftArial(t *testing.T) {
|
func TestProprietaryMicrosoftArial(t *testing.T) {
|
||||||
testProprietary(t, "microsoft", "Arial.ttf", 1200, -1)
|
testProprietary(t, "microsoft", "Arial.ttf", 1200, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProprietaryMicrosoftArialAsACollection(t *testing.T) {
|
||||||
|
testProprietary(t, "microsoft", "Arial.ttf?0", 1200, -1)
|
||||||
|
}
|
||||||
|
|
||||||
func TestProprietaryMicrosoftComicSansMS(t *testing.T) {
|
func TestProprietaryMicrosoftComicSansMS(t *testing.T) {
|
||||||
testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, -1)
|
testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, -1)
|
||||||
}
|
}
|
||||||
|
@ -117,27 +145,55 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
|
||||||
t.Skip("skipping proprietary font test")
|
t.Skip("skipping proprietary font test")
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := []byte(nil), error(nil)
|
basename, fontIndex, err := filename, -1, error(nil)
|
||||||
|
if i := strings.IndexByte(filename, '?'); i >= 0 {
|
||||||
|
fontIndex, err = strconv.Atoi(filename[i+1:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse collection font index from filename %q", filename)
|
||||||
|
}
|
||||||
|
basename = filename[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := ""
|
||||||
switch proprietor {
|
switch proprietor {
|
||||||
case "adobe":
|
case "adobe":
|
||||||
file, err = ioutil.ReadFile(filepath.Join(*adobeDir, filename))
|
dir = *adobeDir
|
||||||
if err != nil {
|
case "apple":
|
||||||
t.Fatalf("%v\nPerhaps you need to set the -adobeDir=%v flag?", err, *adobeDir)
|
dir = *appleDir
|
||||||
}
|
|
||||||
case "microsoft":
|
case "microsoft":
|
||||||
file, err = ioutil.ReadFile(filepath.Join(*microsoftDir, filename))
|
dir = *microsoftDir
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v\nPerhaps you need to set the -microsoftDir=%v flag?", err, *microsoftDir)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
f, err := Parse(file)
|
file, err := ioutil.ReadFile(filepath.Join(dir, basename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Parse: %v", err)
|
t.Fatalf("%v\nPerhaps you need to set the -%sDir flag?", err, proprietor)
|
||||||
}
|
}
|
||||||
ppem := fixed.Int26_6(f.UnitsPerEm())
|
|
||||||
qualifiedFilename := proprietor + "/" + filename
|
qualifiedFilename := proprietor + "/" + filename
|
||||||
|
|
||||||
|
f := (*Font)(nil)
|
||||||
|
if fontIndex >= 0 {
|
||||||
|
c, err := ParseCollection(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseCollection: %v", err)
|
||||||
|
}
|
||||||
|
if want, ok := proprietaryNumFonts[qualifiedFilename]; ok {
|
||||||
|
if got := c.NumFonts(); got != want {
|
||||||
|
t.Fatalf("NumFonts: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f, err = c.Font(fontIndex)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Font: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f, err = Parse(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ppem := fixed.Int26_6(f.UnitsPerEm())
|
||||||
var buf Buffer
|
var buf Buffer
|
||||||
|
|
||||||
// Some of the tests below, such as which glyph index a particular rune
|
// Some of the tests below, such as which glyph index a particular rune
|
||||||
|
@ -147,7 +203,7 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
|
||||||
// message, but don't automatically fail (i.e. dont' call t.Fatalf).
|
// message, but don't automatically fail (i.e. dont' call t.Fatalf).
|
||||||
gotVersion, err := f.Name(&buf, NameIDVersion)
|
gotVersion, err := f.Name(&buf, NameIDVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Name: %v", err)
|
t.Fatalf("Name(Version): %v", err)
|
||||||
}
|
}
|
||||||
wantVersion := proprietaryVersions[qualifiedFilename]
|
wantVersion := proprietaryVersions[qualifiedFilename]
|
||||||
if gotVersion != wantVersion {
|
if gotVersion != wantVersion {
|
||||||
|
@ -155,6 +211,15 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi
|
||||||
"\ngot %q\nwant %q", gotVersion, wantVersion)
|
"\ngot %q\nwant %q", gotVersion, wantVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gotFull, err := f.Name(&buf, NameIDFull)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Name(Full): %v", err)
|
||||||
|
}
|
||||||
|
wantFull := proprietaryFullNames[qualifiedFilename]
|
||||||
|
if gotFull != wantFull {
|
||||||
|
t.Fatalf("Name(Full):\ngot %q\nwant %q", gotFull, wantFull)
|
||||||
|
}
|
||||||
|
|
||||||
numGlyphs := f.NumGlyphs()
|
numGlyphs := f.NumGlyphs()
|
||||||
if numGlyphs < minNumGlyphs {
|
if numGlyphs < minNumGlyphs {
|
||||||
t.Fatalf("NumGlyphs: got %d, want at least %d", numGlyphs, minNumGlyphs)
|
t.Fatalf("NumGlyphs: got %d, want at least %d", numGlyphs, minNumGlyphs)
|
||||||
|
@ -231,6 +296,15 @@ kernLoop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// proprietaryNumFonts holds the expected number of fonts in each collection,
|
||||||
|
// or 1 for a single font. It is not necessarily an exhaustive list of all
|
||||||
|
// proprietary fonts tested.
|
||||||
|
var proprietaryNumFonts = map[string]int{
|
||||||
|
"apple/ヒラギノ角ゴシック W0.ttc?0": 2,
|
||||||
|
"apple/ヒラギノ角ゴシック W0.ttc?1": 2,
|
||||||
|
"microsoft/Arial.ttf?0": 1,
|
||||||
|
}
|
||||||
|
|
||||||
// proprietaryVersions holds the expected version string of each proprietary
|
// proprietaryVersions holds the expected version string of each proprietary
|
||||||
// font tested. If third parties such as Adobe or Microsoft update their fonts,
|
// font tested. If third parties such as Adobe or Microsoft update their fonts,
|
||||||
// and the tests subsequently fail, these versions should be updated too.
|
// and the tests subsequently fail, these versions should be updated too.
|
||||||
|
@ -245,12 +319,37 @@ var proprietaryVersions = map[string]string{
|
||||||
"adobe/SourceSansPro-Regular.otf": "Version 2.020;PS 2.0;hotconv 1.0.86;makeotf.lib2.5.63406",
|
"adobe/SourceSansPro-Regular.otf": "Version 2.020;PS 2.0;hotconv 1.0.86;makeotf.lib2.5.63406",
|
||||||
"adobe/SourceSansPro-Regular.ttf": "Version 2.020;PS 2.000;hotconv 1.0.86;makeotf.lib2.5.63406",
|
"adobe/SourceSansPro-Regular.ttf": "Version 2.020;PS 2.000;hotconv 1.0.86;makeotf.lib2.5.63406",
|
||||||
|
|
||||||
|
"apple/Apple Symbols.ttf": "12.0d3e10",
|
||||||
|
"apple/ヒラギノ角ゴシック W0.ttc?0": "11.0d7e1",
|
||||||
|
"apple/ヒラギノ角ゴシック W0.ttc?1": "11.0d7e1",
|
||||||
|
|
||||||
"microsoft/Arial.ttf": "Version 2.82",
|
"microsoft/Arial.ttf": "Version 2.82",
|
||||||
|
"microsoft/Arial.ttf?0": "Version 2.82",
|
||||||
"microsoft/Comic_Sans_MS.ttf": "Version 2.10",
|
"microsoft/Comic_Sans_MS.ttf": "Version 2.10",
|
||||||
"microsoft/Times_New_Roman.ttf": "Version 2.82",
|
"microsoft/Times_New_Roman.ttf": "Version 2.82",
|
||||||
"microsoft/Webdings.ttf": "Version 1.03",
|
"microsoft/Webdings.ttf": "Version 1.03",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// proprietaryFullNames holds the expected full name of each proprietary font
|
||||||
|
// tested.
|
||||||
|
var proprietaryFullNames = map[string]string{
|
||||||
|
"adobe/SourceCodePro-Regular.otf": "Source Code Pro",
|
||||||
|
"adobe/SourceCodePro-Regular.ttf": "Source Code Pro",
|
||||||
|
"adobe/SourceHanSansSC-Regular.otf": "Source Han Sans SC Regular",
|
||||||
|
"adobe/SourceSansPro-Regular.otf": "Source Sans Pro",
|
||||||
|
"adobe/SourceSansPro-Regular.ttf": "Source Sans Pro",
|
||||||
|
|
||||||
|
"apple/Apple Symbols.ttf": "Apple Symbols",
|
||||||
|
"apple/ヒラギノ角ゴシック W0.ttc?0": "Hiragino Sans W0",
|
||||||
|
"apple/ヒラギノ角ゴシック W0.ttc?1": ".Hiragino Kaku Gothic Interface W0",
|
||||||
|
|
||||||
|
"microsoft/Arial.ttf": "Arial",
|
||||||
|
"microsoft/Arial.ttf?0": "Arial",
|
||||||
|
"microsoft/Comic_Sans_MS.ttf": "Comic Sans MS",
|
||||||
|
"microsoft/Times_New_Roman.ttf": "Times New Roman",
|
||||||
|
"microsoft/Webdings.ttf": "Webdings",
|
||||||
|
}
|
||||||
|
|
||||||
// proprietaryGlyphIndexTestCases hold a sample of each font's rune to glyph
|
// proprietaryGlyphIndexTestCases hold a sample of each font's rune to glyph
|
||||||
// index cmap. The numerical values can be verified by running the ttx tool.
|
// index cmap. The numerical values can be verified by running the ttx tool.
|
||||||
var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{
|
var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{
|
||||||
|
|
|
@ -49,6 +49,7 @@ const (
|
||||||
maxCompoundStackSize = 64
|
maxCompoundStackSize = 64
|
||||||
maxGlyphDataLength = 64 * 1024
|
maxGlyphDataLength = 64 * 1024
|
||||||
maxHintBits = 256
|
maxHintBits = 256
|
||||||
|
maxNumFonts = 256
|
||||||
maxNumTables = 256
|
maxNumTables = 256
|
||||||
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
||||||
|
|
||||||
|
@ -61,22 +62,24 @@ var (
|
||||||
// ErrNotFound indicates that the requested value was not found.
|
// ErrNotFound indicates that the requested value was not found.
|
||||||
ErrNotFound = errors.New("sfnt: not found")
|
ErrNotFound = errors.New("sfnt: not found")
|
||||||
|
|
||||||
errInvalidBounds = errors.New("sfnt: invalid bounds")
|
errInvalidBounds = errors.New("sfnt: invalid bounds")
|
||||||
errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
|
errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
|
||||||
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
|
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
|
||||||
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
errInvalidFont = errors.New("sfnt: invalid font")
|
||||||
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
errInvalidFontCollection = errors.New("sfnt: invalid font collection")
|
||||||
errInvalidKernTable = errors.New("sfnt: invalid kern table")
|
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
||||||
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
||||||
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
errInvalidKernTable = errors.New("sfnt: invalid kern table")
|
||||||
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
||||||
errInvalidNameTable = errors.New("sfnt: invalid name table")
|
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
||||||
errInvalidPostTable = errors.New("sfnt: invalid post table")
|
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
||||||
errInvalidSourceData = errors.New("sfnt: invalid source data")
|
errInvalidNameTable = errors.New("sfnt: invalid name table")
|
||||||
errInvalidTableOffset = errors.New("sfnt: invalid table offset")
|
errInvalidPostTable = errors.New("sfnt: invalid post table")
|
||||||
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)")
|
||||||
errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
|
errInvalidSourceData = errors.New("sfnt: invalid source data")
|
||||||
errInvalidVersion = errors.New("sfnt: invalid version")
|
errInvalidTableOffset = errors.New("sfnt: invalid table offset")
|
||||||
|
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
||||||
|
errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
|
||||||
|
|
||||||
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
||||||
errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
|
errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
|
||||||
|
@ -85,6 +88,7 @@ var (
|
||||||
errUnsupportedKernTable = errors.New("sfnt: unsupported kern table")
|
errUnsupportedKernTable = errors.New("sfnt: unsupported kern table")
|
||||||
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
||||||
errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
|
errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
|
||||||
|
errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts")
|
||||||
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
||||||
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
||||||
errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
|
errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
|
||||||
|
@ -256,19 +260,105 @@ type table struct {
|
||||||
offset, length uint32
|
offset, length uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses an SFNT font from a []byte data source.
|
// ParseCollection parses an SFNT font collection, such as TTC or OTC data,
|
||||||
func Parse(src []byte) (*Font, error) {
|
// from a []byte data source.
|
||||||
f := &Font{src: source{b: src}}
|
//
|
||||||
if err := f.initialize(); err != nil {
|
// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
|
||||||
|
// will return a collection containing 1 font.
|
||||||
|
func ParseCollection(src []byte) (*Collection, error) {
|
||||||
|
c := &Collection{src: source{b: src}}
|
||||||
|
if err := c.initialize(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
|
||||||
|
// from an io.ReaderAt data source.
|
||||||
|
//
|
||||||
|
// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
|
||||||
|
// will return a collection containing 1 font.
|
||||||
|
func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
|
||||||
|
c := &Collection{src: source{r: src}}
|
||||||
|
if err := c.initialize(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collection is a collection of one or more fonts.
|
||||||
|
//
|
||||||
|
// All of the Collection methods are safe to call concurrently.
|
||||||
|
type Collection struct {
|
||||||
|
src source
|
||||||
|
offsets []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumFonts returns the number of fonts in the collection.
|
||||||
|
func (c *Collection) NumFonts() int { return len(c.offsets) }
|
||||||
|
|
||||||
|
func (c *Collection) initialize() error {
|
||||||
|
// The https://www.microsoft.com/typography/otspec/otff.htm "Font
|
||||||
|
// Collections" section describes the TTC Header.
|
||||||
|
buf, err := c.src.view(nil, 0, 12)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// These cases match the switch statement in Font.initializeTables.
|
||||||
|
switch u32(buf) {
|
||||||
|
default:
|
||||||
|
return errInvalidFontCollection
|
||||||
|
case 0x00010000, 0x4f54544f:
|
||||||
|
// Try parsing it as a single font instead of a collection.
|
||||||
|
c.offsets = []uint32{0}
|
||||||
|
case 0x74746366: // "ttcf".
|
||||||
|
numFonts := u32(buf[8:])
|
||||||
|
if numFonts == 0 || numFonts > maxNumFonts {
|
||||||
|
return errUnsupportedNumberOfFonts
|
||||||
|
}
|
||||||
|
buf, err = c.src.view(nil, 12, int(4*numFonts))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.offsets = make([]uint32, numFonts)
|
||||||
|
for i := range c.offsets {
|
||||||
|
o := u32(buf[4*i:])
|
||||||
|
if o > maxTableOffset {
|
||||||
|
return errUnsupportedTableOffsetLength
|
||||||
|
}
|
||||||
|
c.offsets[i] = o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Font returns the i'th font in the collection.
|
||||||
|
func (c *Collection) Font(i int) (*Font, error) {
|
||||||
|
if i < 0 || len(c.offsets) <= i {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
f := &Font{src: c.src}
|
||||||
|
if err := f.initialize(int(c.offsets[i])); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseReaderAt parses an SFNT font from an io.ReaderAt data source.
|
// Parse parses an SFNT font, such as TTF or OTF data, from a []byte data
|
||||||
|
// source.
|
||||||
|
func Parse(src []byte) (*Font, error) {
|
||||||
|
f := &Font{src: source{b: src}}
|
||||||
|
if err := f.initialize(0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseReaderAt parses an SFNT font, such as TTF or OTF data, from an
|
||||||
|
// io.ReaderAt data source.
|
||||||
func ParseReaderAt(src io.ReaderAt) (*Font, error) {
|
func ParseReaderAt(src io.ReaderAt) (*Font, error) {
|
||||||
f := &Font{src: source{r: src}}
|
f := &Font{src: source{r: src}}
|
||||||
if err := f.initialize(); err != nil {
|
if err := f.initialize(0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
|
@ -362,11 +452,11 @@ func (f *Font) NumGlyphs() int { return len(f.cached.locations) - 1 }
|
||||||
// UnitsPerEm returns the number of units per em for f.
|
// UnitsPerEm returns the number of units per em for f.
|
||||||
func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
|
func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
|
||||||
|
|
||||||
func (f *Font) initialize() error {
|
func (f *Font) initialize(offset int) error {
|
||||||
if !f.src.valid() {
|
if !f.src.valid() {
|
||||||
return errInvalidSourceData
|
return errInvalidSourceData
|
||||||
}
|
}
|
||||||
buf, isPostScript, err := f.initializeTables(nil)
|
buf, isPostScript, err := f.initializeTables(offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -412,21 +502,25 @@ func (f *Font) initialize() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) initializeTables(buf []byte) (buf1 []byte, isPostScript bool, err error) {
|
func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err error) {
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
|
// https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
|
||||||
// OpenType Font" says that "The OpenType font starts with the Offset
|
// OpenType Font" says that "The OpenType font starts with the Offset
|
||||||
// Table", which is 12 bytes.
|
// Table", which is 12 bytes.
|
||||||
buf, err = f.src.view(buf, 0, 12)
|
buf, err := f.src.view(nil, offset, 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
// When updating the cases in this switch statement, also update the
|
||||||
|
// Collection.initialize method.
|
||||||
switch u32(buf) {
|
switch u32(buf) {
|
||||||
default:
|
default:
|
||||||
return nil, false, errInvalidVersion
|
return nil, false, errInvalidFont
|
||||||
case 0x00010000:
|
case 0x00010000:
|
||||||
// No-op.
|
// No-op.
|
||||||
case 0x4f54544f: // "OTTO".
|
case 0x4f54544f: // "OTTO".
|
||||||
isPostScript = true
|
isPostScript = true
|
||||||
|
case 0x74746366: // "ttcf".
|
||||||
|
return nil, false, errInvalidSingleFont
|
||||||
}
|
}
|
||||||
numTables := int(u16(buf[4:]))
|
numTables := int(u16(buf[4:]))
|
||||||
if numTables > maxNumTables {
|
if numTables > maxNumTables {
|
||||||
|
@ -435,7 +529,7 @@ func (f *Font) initializeTables(buf []byte) (buf1 []byte, isPostScript bool, err
|
||||||
|
|
||||||
// "The Offset Table is followed immediately by the Table Record entries...
|
// "The Offset Table is followed immediately by the Table Record entries...
|
||||||
// sorted in ascending order by tag", 16 bytes each.
|
// sorted in ascending order by tag", 16 bytes each.
|
||||||
buf, err = f.src.view(buf, 12, 16*numTables)
|
buf, err = f.src.view(buf, offset+12, 16*numTables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user