font/sfnt: support .dfont files.
Change-Id: Id7aa18c48c65586c688cee230ce87f4d88dae9b5
Reviewed-on: https://go-review.googlesource.com/40893
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index f0917d6..2df6f6c 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -111,6 +111,30 @@
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 @@
// 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 @@
"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 @@
"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 @@
'\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 @@
},
},
+ "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
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 3d33e3b..5cb1225 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -70,6 +70,7 @@
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 @@
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) 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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
}