font/sfnt: support parsing legacy fonts with OS2 table version <= 1

Library assumes that OS/2 header size is at least 96 bytes,
which is not the case for fonts with OS/2 table version <= 1.

This CL adds a version test and handles the legacy header.

Fixes golang/go#28339

Change-Id: I79bd8f8bbf262c1caaf4e66888446159b5e4fb43
Reviewed-on: https://go-review.googlesource.com/c/144079
Reviewed-by: Nigel Tao <nigeltao@golang.org>
Reviewed-by: Elias Naur <elias.naur@gmail.com>
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 0b46d0a..eaae675 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -637,7 +637,7 @@
 	if err != nil {
 		return err
 	}
-	buf, xHeight, capHeight, err := f.parseOS2(buf)
+	buf, os2Vers, xHeight, capHeight, err := f.parseOS2(buf)
 	if err != nil {
 		return err
 	}
@@ -664,6 +664,15 @@
 	f.cached.unitsPerEm = unitsPerEm
 	f.cached.xHeight = xHeight
 
+	if os2Vers <= 1 {
+		xh, ch, err := f.initOS2Version1()
+		if err != nil {
+			return err
+		}
+		f.cached.xHeight = xh
+		f.cached.capHeight = ch
+	}
+
 	return nil
 }
 
@@ -1065,22 +1074,80 @@
 	return buf, ret, isColorBitmap, nil
 }
 
-func (f *Font) parseOS2(buf []byte) (buf1 []byte, xHeight, capHeight int32, err error) {
-	// https://docs.microsoft.com/da-dk/typography/opentype/spec/os2
+func (f *Font) glyphTopOS2(b *Buffer, ppem fixed.Int26_6, r rune) (int32, error) {
+	ind, err := f.GlyphIndex(b, r)
+	if err != nil && err != ErrNotFound {
+		return 0, err
+	} else if ind == 0 {
+		return 0, nil
+	}
+	// Y axis points down
+	var min fixed.Int26_6
+	seg, err := f.LoadGlyph(b, ind, ppem, nil)
+	if err != nil {
+		return 0, err
+	}
+	for _, s := range seg {
+		for _, p := range s.Args {
+			if p.Y < min {
+				min = p.Y
+			}
+		}
+	}
+	return int32(min), nil
+}
 
+func (f *Font) initOS2Version1() (xHeight, capHeight int32, err error) {
+	ppem := fixed.Int26_6(f.UnitsPerEm())
+	var b Buffer
+
+	// sxHeight equal to the top of the unscaled and unhinted glyph bounding box
+	// of the glyph encoded at U+0078 (LATIN SMALL LETTER X).
+	xh, err := f.glyphTopOS2(&b, ppem, 'x')
+	if err != nil {
+		return 0, 0, err
+	}
+
+	// sCapHeight may be set equal to the top of the unscaled and unhinted glyph
+	// bounding box of the glyph encoded at U+0048 (LATIN CAPITAL LETTER H).
+	ch, err := f.glyphTopOS2(&b, ppem, 'H')
+	if err != nil {
+		return 0, 0, err
+	}
+
+	return int32(xh), int32(ch), nil
+}
+
+func (f *Font) parseOS2(buf []byte) (buf1 []byte, version uint16, xHeight, capHeight int32, err error) {
+	// https://docs.microsoft.com/da-dk/typography/opentype/spec/os2
+	if f.os2.length < 2 {
+		return nil, 0, 0, 0, errInvalidOS2Table
+	}
+	vers, err := f.src.u16(buf, f.os2, 0)
+	if err != nil {
+		return nil, 0, 0, 0, err
+	}
+	if vers <= 1 {
+		const headerSize = 86
+		if f.os2.length < headerSize {
+			return nil, 0, 0, 0, errInvalidOS2Table
+		}
+		// Will resolve xHeight and capHeight later, see initOS2Version1.
+		return buf, vers, 0, 0, nil
+	}
 	const headerSize = 96
 	if f.os2.length < headerSize {
-		return nil, 0, 0, errInvalidOS2Table
+		return nil, 0, 0, 0, errInvalidOS2Table
 	}
 	xh, err := f.src.u16(buf, f.os2, 86)
 	if err != nil {
-		return nil, 0, 0, err
+		return nil, 0, 0, 0, err
 	}
 	ch, err := f.src.u16(buf, f.os2, 88)
 	if err != nil {
-		return nil, 0, 0, err
+		return nil, 0, 0, 0, err
 	}
-	return buf, int32(int16(xh)), int32(int16(ch)), nil
+	return buf, vers, int32(int16(xh)), int32(int16(ch)), nil
 }
 
 func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVersion uint32, err error) {