font/sfnt: parse and expose PostScript information

Currently the library only parses the version in PostScript table.
However use cases such as PDF document processing requires this
information to be exposed.

CL parses a minimal set of the fields from the PostScript table
and exposes it via new PostTable method.

Change-Id: Ia86eecea9f5aaf557c7e4737f2474966aa30cff2
Reviewed-on: https://go-review.googlesource.com/c/145797
Reviewed-by: Nigel Tao <nigeltao@golang.org>
This commit is contained in:
Denys Smirnov 2018-10-30 03:32:54 +02:00 committed by Nigel Tao
parent 46e4eb730a
commit d9c2484c48
2 changed files with 87 additions and 10 deletions

View File

@ -579,7 +579,7 @@ type Font struct {
kernOffset int32 kernOffset int32
lineGap int32 lineGap int32
numHMetrics int32 numHMetrics int32
postTableVersion uint32 post *PostTable
slope [2]int32 slope [2]int32
unitsPerEm Units unitsPerEm Units
xHeight int32 xHeight int32
@ -641,7 +641,7 @@ func (f *Font) initialize(offset int, isDfont bool) error {
if err != nil { if err != nil {
return err return err
} }
buf, postTableVersion, err := f.parsePost(buf, numGlyphs) buf, post, err := f.parsePost(buf, numGlyphs)
if err != nil { if err != nil {
return err return err
} }
@ -659,7 +659,7 @@ func (f *Font) initialize(offset int, isDfont bool) error {
f.cached.kernOffset = kernOffset f.cached.kernOffset = kernOffset
f.cached.lineGap = lineGap f.cached.lineGap = lineGap
f.cached.numHMetrics = numHMetrics f.cached.numHMetrics = numHMetrics
f.cached.postTableVersion = postTableVersion f.cached.post = post
f.cached.slope = [2]int32{run, rise} f.cached.slope = [2]int32{run, rise}
f.cached.unitsPerEm = unitsPerEm f.cached.unitsPerEm = unitsPerEm
f.cached.xHeight = xHeight f.cached.xHeight = xHeight
@ -1153,30 +1153,80 @@ func (f *Font) parseOS2(buf []byte) (buf1 []byte, version uint16, xHeight, capHe
return buf, vers, 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) { // PostTable represents an information stored in the PostScript font section.
type PostTable struct {
// Version of the version tag of the "post" table.
Version uint32
// ItalicAngle in counter-clockwise degrees from the vertical. Zero for
// upright text, negative for text that leans to the right (forward).
ItalicAngle float32
// UnderlinePosition is the suggested distance of the top of the
// underline from the baseline (negative values indicate below baseline).
UnderlinePosition int16
// Suggested values for the underline thickness.
UnderlineThickness int16
// IsFixedPitch indicates that the font is not proportionally spaced
// (i.e. monospaced).
IsFixedPitch bool
}
// PostTable returns the information from the font's "post" table. It can
// return nil, if the font doesn't have such a table.
//
// See https://docs.microsoft.com/en-us/typography/opentype/spec/post
func (f *Font) PostTable() *PostTable {
return f.cached.post
}
func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, post *PostTable, err error) {
// https://www.microsoft.com/typography/otspec/post.htm // https://www.microsoft.com/typography/otspec/post.htm
const headerSize = 32 const headerSize = 32
if f.post.length < headerSize { if f.post.length < headerSize {
return nil, 0, errInvalidPostTable return nil, nil, errInvalidPostTable
} }
u, err := f.src.u32(buf, f.post, 0) u, err := f.src.u32(buf, f.post, 0)
if err != nil { if err != nil {
return nil, 0, err return nil, nil, err
} }
switch u { switch u {
case 0x10000: case 0x10000:
// No-op. // No-op.
case 0x20000: case 0x20000:
if f.post.length < headerSize+2+2*uint32(numGlyphs) { if f.post.length < headerSize+2+2*uint32(numGlyphs) {
return nil, 0, errInvalidPostTable return nil, nil, errInvalidPostTable
} }
case 0x30000: case 0x30000:
// No-op. // No-op.
default: default:
return nil, 0, errUnsupportedPostTable return nil, nil, errUnsupportedPostTable
} }
return buf, u, nil
ang, err := f.src.u32(buf, f.post, 4)
if err != nil {
return nil, nil, err
}
up, err := f.src.u16(buf, f.post, 8)
if err != nil {
return nil, nil, err
}
ut, err := f.src.u16(buf, f.post, 10)
if err != nil {
return nil, nil, err
}
fp, err := f.src.u32(buf, f.post, 12)
if err != nil {
return nil, nil, err
}
post = &PostTable{
Version: u,
ItalicAngle: float32(int16(ang>>16)) + float32(ang&0xffff)/0x10000,
UnderlinePosition: int16(up),
UnderlineThickness: int16(ut),
IsFixedPitch: fp != 0,
}
return buf, post, nil
} }
// Bounds returns the union of a Font's glyphs' bounds. // Bounds returns the union of a Font's glyphs' bounds.
@ -1380,7 +1430,10 @@ func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
if int(x) >= f.NumGlyphs() { if int(x) >= f.NumGlyphs() {
return "", ErrNotFound return "", ErrNotFound
} }
switch f.cached.postTableVersion { if f.cached.post == nil {
return "", nil
}
switch f.cached.post.Version {
case 0x10000: case 0x10000:
return f.glyphNameFormat10(x) return f.glyphNameFormat10(x)
case 0x20000: case 0x20000:

View File

@ -767,6 +767,30 @@ func TestPPEM(t *testing.T) {
} }
} }
func TestPostInfo(t *testing.T) {
data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/glyfTest.ttf"))
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
f, err := Parse(data)
if err != nil {
t.Fatalf("Parse: %v", err)
}
post := f.PostTable()
if post.ItalicAngle != -11.25 {
t.Error("ItalicAngle:", post.ItalicAngle)
}
if post.UnderlinePosition != -255 {
t.Error("UnderlinePosition:", post.UnderlinePosition)
}
if post.UnderlineThickness != 102 {
t.Error("UnderlineThickness:", post.UnderlineThickness)
}
if post.IsFixedPitch {
t.Error("IsFixedPitch:", post.IsFixedPitch)
}
}
func TestGlyphName(t *testing.T) { func TestGlyphName(t *testing.T) {
f, err := Parse(goregular.TTF) f, err := Parse(goregular.TTF)
if err != nil { if err != nil {