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:
parent
a74d51e6d3
commit
3210c0296b
|
@ -111,6 +111,30 @@ func TestProprietaryAppleGeezaPro1(t *testing.T) {
|
||||||
testProprietary(t, "apple", "GeezaPro.ttc?1", 1700, -1)
|
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) {
|
func TestProprietaryAppleHiragino0(t *testing.T) {
|
||||||
testProprietary(t, "apple", "ヒラギノ角ゴシック W0.ttc?0", 9000, -1)
|
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
|
// or 1 for a single font. It is not necessarily an exhaustive list of all
|
||||||
// proprietary fonts tested.
|
// proprietary fonts tested.
|
||||||
var proprietaryNumFonts = map[string]int{
|
var proprietaryNumFonts = map[string]int{
|
||||||
|
"apple/Helvetica.dfont?0": 6,
|
||||||
"apple/ヒラギノ角ゴシック W0.ttc?0": 2,
|
"apple/ヒラギノ角ゴシック W0.ttc?0": 2,
|
||||||
"apple/ヒラギノ角ゴシック W0.ttc?1": 2,
|
|
||||||
"microsoft/Arial.ttf?0": 1,
|
"microsoft/Arial.ttf?0": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,6 +366,12 @@ var proprietaryVersions = map[string]string{
|
||||||
"apple/Apple Symbols.ttf": "12.0d3e10",
|
"apple/Apple Symbols.ttf": "12.0d3e10",
|
||||||
"apple/GeezaPro.ttc?0": "12.0d1e3",
|
"apple/GeezaPro.ttc?0": "12.0d1e3",
|
||||||
"apple/GeezaPro.ttc?1": "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?0": "11.0d7e1",
|
||||||
"apple/ヒラギノ角ゴシック W0.ttc?1": "11.0d7e1",
|
"apple/ヒラギノ角ゴシック W0.ttc?1": "11.0d7e1",
|
||||||
|
|
||||||
|
@ -364,6 +394,12 @@ var proprietaryFullNames = map[string]string{
|
||||||
"apple/Apple Symbols.ttf": "Apple Symbols",
|
"apple/Apple Symbols.ttf": "Apple Symbols",
|
||||||
"apple/GeezaPro.ttc?0": "Geeza Pro Regular",
|
"apple/GeezaPro.ttc?0": "Geeza Pro Regular",
|
||||||
"apple/GeezaPro.ttc?1": "Geeza Pro Bold",
|
"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?0": "Hiragino Sans W0",
|
||||||
"apple/ヒラギノ角ゴシック W0.ttc?1": ".Hiragino Kaku Gothic Interface 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
|
'\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": {
|
"microsoft/Arial.ttf": {
|
||||||
'\u0041': 36, // U+0041 LATIN CAPITAL LETTER A
|
'\u0041': 36, // U+0041 LATIN CAPITAL LETTER A
|
||||||
'\u00f1': 120, // U+00F1 LATIN SMALL LETTER N WITH TILDE
|
'\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": {
|
"microsoft/Arial.ttf": {
|
||||||
',': {
|
',': {
|
||||||
// - contour #0
|
// - contour #0
|
||||||
|
|
|
@ -70,6 +70,7 @@ var (
|
||||||
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")
|
||||||
|
errInvalidDfont = errors.New("sfnt: invalid dfont")
|
||||||
errInvalidFont = errors.New("sfnt: invalid font")
|
errInvalidFont = errors.New("sfnt: invalid font")
|
||||||
errInvalidFontCollection = errors.New("sfnt: invalid font collection")
|
errInvalidFontCollection = errors.New("sfnt: invalid font collection")
|
||||||
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
||||||
|
@ -303,6 +304,7 @@ func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
src source
|
src source
|
||||||
offsets []uint32
|
offsets []uint32
|
||||||
|
isDfont bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumFonts returns the number of fonts in the collection.
|
// 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 {
|
func (c *Collection) initialize() error {
|
||||||
// The https://www.microsoft.com/typography/otspec/otff.htm "Font
|
// The https://www.microsoft.com/typography/otspec/otff.htm "Font
|
||||||
// Collections" section describes the TTC Header.
|
// Collections" section describes the TTC header.
|
||||||
buf, err := c.src.view(nil, 0, 12)
|
//
|
||||||
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -319,6 +326,8 @@ func (c *Collection) initialize() error {
|
||||||
switch u32(buf) {
|
switch u32(buf) {
|
||||||
default:
|
default:
|
||||||
return errInvalidFontCollection
|
return errInvalidFontCollection
|
||||||
|
case dfontResourceDataOffset:
|
||||||
|
return c.parseDfont(buf, u32(buf[4:]), u32(buf[12:]))
|
||||||
case 0x00010000, 0x4f54544f:
|
case 0x00010000, 0x4f54544f:
|
||||||
// Try parsing it as a single font instead of a collection.
|
// Try parsing it as a single font instead of a collection.
|
||||||
c.offsets = []uint32{0}
|
c.offsets = []uint32{0}
|
||||||
|
@ -343,13 +352,116 @@ func (c *Collection) initialize() error {
|
||||||
return nil
|
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.
|
// Font returns the i'th font in the collection.
|
||||||
func (c *Collection) Font(i int) (*Font, error) {
|
func (c *Collection) Font(i int) (*Font, error) {
|
||||||
if i < 0 || len(c.offsets) <= i {
|
if i < 0 || len(c.offsets) <= i {
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
f := &Font{src: c.src}
|
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 nil, err
|
||||||
}
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
|
@ -359,7 +471,7 @@ func (c *Collection) Font(i int) (*Font, error) {
|
||||||
// source.
|
// source.
|
||||||
func Parse(src []byte) (*Font, error) {
|
func Parse(src []byte) (*Font, error) {
|
||||||
f := &Font{src: source{b: src}}
|
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 nil, err
|
||||||
}
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
|
@ -369,7 +481,7 @@ func Parse(src []byte) (*Font, error) {
|
||||||
// io.ReaderAt data source.
|
// 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(0); err != nil {
|
if err := f.initialize(0, false); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return f, nil
|
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.
|
// 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(offset int) error {
|
func (f *Font) initialize(offset int, isDfont bool) error {
|
||||||
if !f.src.valid() {
|
if !f.src.valid() {
|
||||||
return errInvalidSourceData
|
return errInvalidSourceData
|
||||||
}
|
}
|
||||||
buf, isPostScript, err := f.initializeTables(offset)
|
buf, isPostScript, err := f.initializeTables(offset, isDfont)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -526,7 +638,7 @@ func (f *Font) initialize(offset int) error {
|
||||||
return nil
|
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
|
// 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.
|
||||||
|
@ -539,6 +651,8 @@ func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err
|
||||||
switch u32(buf) {
|
switch u32(buf) {
|
||||||
default:
|
default:
|
||||||
return nil, false, errInvalidFont
|
return nil, false, errInvalidFont
|
||||||
|
case dfontResourceDataOffset:
|
||||||
|
return nil, false, errInvalidSingleFont
|
||||||
case 0x00010000:
|
case 0x00010000:
|
||||||
// No-op.
|
// No-op.
|
||||||
case 0x4f54544f: // "OTTO".
|
case 0x4f54544f: // "OTTO".
|
||||||
|
@ -567,6 +681,15 @@ func (f *Font) initializeTables(offset int) (buf1 []byte, isPostScript bool, err
|
||||||
prevTag = tag
|
prevTag = tag
|
||||||
|
|
||||||
o, n := u32(b[8:12]), u32(b[12:16])
|
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 {
|
if o > maxTableOffset || n > maxTableLength {
|
||||||
return nil, false, errUnsupportedTableOffsetLength
|
return nil, false, errUnsupportedTableOffsetLength
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user