font/sfnt: support .dfont files.

Change-Id: Id7aa18c48c65586c688cee230ce87f4d88dae9b5
Reviewed-on: https://go-review.googlesource.com/40893
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2017-04-17 14:12:48 +10:00
parent a74d51e6d3
commit 3210c0296b
2 changed files with 213 additions and 9 deletions

View File

@ -111,6 +111,30 @@ func TestProprietaryAppleGeezaPro1(t *testing.T) {
testProprietary(t, "apple", "GeezaPro.ttc?1", 1700, -1)
}
func TestProprietaryAppleHelvetica0(t *testing.T) {
testProprietary(t, "apple", "Helvetica.dfont?0", 2100, -1)
}
func TestProprietaryAppleHelvetica1(t *testing.T) {
testProprietary(t, "apple", "Helvetica.dfont?1", 2100, -1)
}
func TestProprietaryAppleHelvetica2(t *testing.T) {
testProprietary(t, "apple", "Helvetica.dfont?2", 2100, -1)
}
func TestProprietaryAppleHelvetica3(t *testing.T) {
testProprietary(t, "apple", "Helvetica.dfont?3", 2100, -1)
}
func TestProprietaryAppleHelvetica4(t *testing.T) {
testProprietary(t, "apple", "Helvetica.dfont?4", 1300, -1)
}
func TestProprietaryAppleHelvetica5(t *testing.T) {
testProprietary(t, "apple", "Helvetica.dfont?5", 1300, -1)
}
func TestProprietaryAppleHiragino0(t *testing.T) {
testProprietary(t, "apple", "ヒラギノ角ゴシック W0.ttc?0", 9000, -1)
}
@ -320,8 +344,8 @@ kernLoop:
// or 1 for a single font. It is not necessarily an exhaustive list of all
// proprietary fonts tested.
var proprietaryNumFonts = map[string]int{
"apple/Helvetica.dfont?0": 6,
"apple/ヒラギノ角ゴシック W0.ttc?0": 2,
"apple/ヒラギノ角ゴシック W0.ttc?1": 2,
"microsoft/Arial.ttf?0": 1,
}
@ -342,6 +366,12 @@ var proprietaryVersions = map[string]string{
"apple/Apple Symbols.ttf": "12.0d3e10",
"apple/GeezaPro.ttc?0": "12.0d1e3",
"apple/GeezaPro.ttc?1": "12.0d1e3",
"apple/Helvetica.dfont?0": "12.0d1e3",
"apple/Helvetica.dfont?1": "12.0d1e3",
"apple/Helvetica.dfont?2": "12.0d1e3",
"apple/Helvetica.dfont?3": "12.0d1e3",
"apple/Helvetica.dfont?4": "12.0d1e3",
"apple/Helvetica.dfont?5": "12.0d1e3",
"apple/ヒラギノ角ゴシック W0.ttc?0": "11.0d7e1",
"apple/ヒラギノ角ゴシック W0.ttc?1": "11.0d7e1",
@ -364,6 +394,12 @@ var proprietaryFullNames = map[string]string{
"apple/Apple Symbols.ttf": "Apple Symbols",
"apple/GeezaPro.ttc?0": "Geeza Pro Regular",
"apple/GeezaPro.ttc?1": "Geeza Pro Bold",
"apple/Helvetica.dfont?0": "Helvetica",
"apple/Helvetica.dfont?1": "Helvetica Bold",
"apple/Helvetica.dfont?2": "Helvetica Oblique",
"apple/Helvetica.dfont?3": "Helvetica Bold Oblique",
"apple/Helvetica.dfont?4": "Helvetica Light",
"apple/Helvetica.dfont?5": "Helvetica Light Oblique",
"apple/ヒラギノ角ゴシック W0.ttc?0": "Hiragino Sans W0",
"apple/ヒラギノ角ゴシック W0.ttc?1": ".Hiragino Kaku Gothic Interface W0",
@ -422,6 +458,17 @@ var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{
'\u2030': 1728, // U+2030 PER MILLE SIGN
},
"apple/Helvetica.dfont?0": {
'\u0041': 36, // U+0041 LATIN CAPITAL LETTER A
'\u00f1': 120, // U+00F1 LATIN SMALL LETTER N WITH TILDE
'\u0401': 473, // U+0401 CYRILLIC CAPITAL LETTER IO
'\u200d': 611, // U+200D ZERO WIDTH JOINER
'\u20ab': 1743, // U+20AB DONG SIGN
'\u2229': 0, // U+2229 INTERSECTION
'\u04e9': 1208, // U+04E9 CYRILLIC SMALL LETTER BARRED O
'\U0001f100': 0, // U+0001F100 DIGIT ZERO FULL STOP
},
"microsoft/Arial.ttf": {
'\u0041': 36, // U+0041 LATIN CAPITAL LETTER A
'\u00f1': 120, // U+00F1 LATIN SMALL LETTER N WITH TILDE
@ -849,6 +896,40 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
},
},
"apple/Helvetica.dfont?0": {
'i': {
// - contour #0
moveTo(132, 1066),
lineTo(315, 1066),
lineTo(315, 0),
lineTo(132, 0),
lineTo(132, 1066),
// - contour #1
moveTo(132, 1469),
lineTo(315, 1469),
lineTo(315, 1265),
lineTo(132, 1265),
lineTo(132, 1469),
},
},
"apple/Helvetica.dfont?1": {
'i': {
// - contour #0
moveTo(426, 1220),
lineTo(137, 1220),
lineTo(137, 1483),
lineTo(426, 1483),
lineTo(426, 1220),
// - contour #1
moveTo(137, 1090),
lineTo(426, 1090),
lineTo(426, 0),
lineTo(137, 0),
lineTo(137, 1090),
},
},
"microsoft/Arial.ttf": {
',': {
// - contour #0

View File

@ -70,6 +70,7 @@ var (
errInvalidBounds = errors.New("sfnt: invalid bounds")
errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
errInvalidDfont = errors.New("sfnt: invalid dfont")
errInvalidFont = errors.New("sfnt: invalid font")
errInvalidFontCollection = errors.New("sfnt: invalid font collection")
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
@ -303,6 +304,7 @@ func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
type Collection struct {
src source
offsets []uint32
isDfont bool
}
// NumFonts returns the number of fonts in the collection.
@ -310,8 +312,13 @@ 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)
// Collections" section describes the TTC header.
//
// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
// describes the dfont header.
//
// 16 is the maximum of sizeof(TTCHeader) and sizeof(DfontHeader).
buf, err := c.src.view(nil, 0, 16)
if err != nil {
return err
}
@ -319,6 +326,8 @@ func (c *Collection) initialize() error {
switch u32(buf) {
default:
return errInvalidFontCollection
case dfontResourceDataOffset:
return c.parseDfont(buf, u32(buf[4:]), u32(buf[12:]))
case 0x00010000, 0x4f54544f:
// Try parsing it as a single font instead of a collection.
c.offsets = []uint32{0}
@ -343,13 +352,116 @@ func (c *Collection) initialize() error {
return nil
}
// dfontResourceDataOffset is the assumed value of a dfont file's resource data
// offset.
//
// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
// says that "A Mac OS resource file... [starts with an] offset from start of
// file to start of resource data section... [usually] 0x0100". In theory,
// 0x00000100 isn't always a magic number for identifying dfont files. In
// practice, it seems to work.
const dfontResourceDataOffset = 0x00000100
// parseDfont parses a dfont resource map, as per
// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
//
// That unofficial wiki page lists all of its fields as *signed* integers,
// which looks unusual. The actual file format might use *unsigned* integers in
// various places, but until we have either an official specification or an
// actual dfont file where this matters, we'll use signed integers and treat
// negative values as invalid.
func (c *Collection) parseDfont(buf []byte, resourceMapOffset, resourceMapLength uint32) error {
if resourceMapOffset > maxTableOffset || resourceMapLength > maxTableLength {
return errUnsupportedTableOffsetLength
}
const headerSize = 28
if resourceMapLength < headerSize {
return errInvalidDfont
}
buf, err := c.src.view(buf, int(resourceMapOffset+24), 2)
if err != nil {
return err
}
typeListOffset := int(int16(u16(buf)))
if typeListOffset < headerSize || resourceMapLength < uint32(typeListOffset)+2 {
return errInvalidDfont
}
buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset, 2)
if err != nil {
return err
}
typeCount := int(int16(u16(buf)))
const tSize = 8
if typeCount < 0 || tSize*uint32(typeCount) > resourceMapLength-uint32(typeListOffset)-2 {
return errInvalidDfont
}
buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset+2, tSize*typeCount)
if err != nil {
return err
}
resourceCount, resourceListOffset := 0, 0
for i := 0; i < typeCount; i++ {
if u32(buf[tSize*i:]) != 0x73666e74 { // "sfnt".
continue
}
resourceCount = int(int16(u16(buf[tSize*i+4:])))
if resourceCount < 0 {
return errInvalidDfont
}
// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
// says that the value in the wire format is "the number of
// resources of this type, minus one."
resourceCount++
resourceListOffset = int(int16(u16(buf[tSize*i+6:])))
if resourceListOffset < 0 {
return errInvalidDfont
}
break
}
if resourceCount == 0 {
return errInvalidDfont
}
if resourceCount > maxNumFonts {
return errUnsupportedNumberOfFonts
}
const rSize = 12
if o, n := uint32(typeListOffset+resourceListOffset), rSize*uint32(resourceCount); o > resourceMapLength || n > resourceMapLength-o {
return errInvalidDfont
} else {
buf, err = c.src.view(buf, int(resourceMapOffset+o), int(n))
if err != nil {
return err
}
}
c.offsets = make([]uint32, resourceCount)
for i := range c.offsets {
o := 0xffffff & u32(buf[rSize*i+4:])
// Offsets are relative to the resource data start, not the file start.
// A particular resource's data also starts with a 4-byte length, which
// we skip.
o += dfontResourceDataOffset + 4
if o > maxTableOffset {
return errUnsupportedTableOffsetLength
}
c.offsets[i] = o
}
c.isDfont = true
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 {
if err := f.initialize(int(c.offsets[i]), c.isDfont); err != nil {
return nil, err
}
return f, nil
@ -359,7 +471,7 @@ func (c *Collection) Font(i int) (*Font, error) {
// source.
func Parse(src []byte) (*Font, error) {
f := &Font{src: source{b: src}}
if err := f.initialize(0); err != nil {
if err := f.initialize(0, false); err != nil {
return nil, err
}
return f, nil
@ -369,7 +481,7 @@ func Parse(src []byte) (*Font, error) {
// io.ReaderAt data source.
func ParseReaderAt(src io.ReaderAt) (*Font, error) {
f := &Font{src: source{r: src}}
if err := f.initialize(0); err != nil {
if err := f.initialize(0, false); err != nil {
return nil, err
}
return f, nil
@ -462,11 +574,11 @@ func (f *Font) NumGlyphs() int { return len(f.cached.glyphData.locations) - 1 }
// UnitsPerEm returns the number of units per em for f.
func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
func (f *Font) initialize(offset int) error {
func (f *Font) initialize(offset int, isDfont bool) error {
if !f.src.valid() {
return errInvalidSourceData
}
buf, isPostScript, err := f.initializeTables(offset)
buf, isPostScript, err := f.initializeTables(offset, isDfont)
if err != nil {
return err
}
@ -526,7 +638,7 @@ func (f *Font) initialize(offset int) error {
return nil
}
func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err error) {
func (f *Font) initializeTables(offset int, isDfont bool) (buf1 []byte, isPostScript bool, err error) {
// https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
// OpenType Font" says that "The OpenType font starts with the Offset
// Table", which is 12 bytes.
@ -539,6 +651,8 @@ func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err
switch u32(buf) {
default:
return nil, false, errInvalidFont
case dfontResourceDataOffset:
return nil, false, errInvalidSingleFont
case 0x00010000:
// No-op.
case 0x4f54544f: // "OTTO".
@ -567,6 +681,15 @@ func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err
prevTag = tag
o, n := u32(b[8:12]), u32(b[12:16])
// For dfont files, the offset is relative to the resource, not the
// file.
if isDfont {
origO := o
o += uint32(offset)
if o < origO {
return nil, false, errUnsupportedTableOffsetLength
}
}
if o > maxTableOffset || n > maxTableLength {
return nil, false, errUnsupportedTableOffsetLength
}