font/sfnt: add a Font.Name method.
Also make all of the Buffer fields non-exported. LoadGlyph now returns the segments instead of setting Buffer.Segments for the caller to use. Change-Id: I3f87070da5e8f014f88dbca70b62a4cd30e3fd66 Reviewed-on: https://go-review.googlesource.com/34532 Reviewed-by: Dave Day <djd@golang.org>
This commit is contained in:
parent
ed90ab82a8
commit
2d771d3c32
|
@ -19,6 +19,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
|
"golang.org/x/text/encoding/charmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// These constants are not part of the specifications, but are limitations used
|
// These constants are not part of the specifications, but are limitations used
|
||||||
|
@ -34,22 +35,26 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errGlyphIndexOutOfRange = errors.New("sfnt: glyph index out of range")
|
// ErrNotFound indicates that the requested value was not found.
|
||||||
|
ErrNotFound = errors.New("sfnt: not found")
|
||||||
|
|
||||||
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")
|
||||||
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
||||||
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
||||||
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
||||||
|
errInvalidNameTable = errors.New("sfnt: invalid name table")
|
||||||
errInvalidSourceData = errors.New("sfnt: invalid source data")
|
errInvalidSourceData = errors.New("sfnt: invalid source data")
|
||||||
errInvalidTableOffset = errors.New("sfnt: invalid table offset")
|
errInvalidTableOffset = errors.New("sfnt: invalid table offset")
|
||||||
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
||||||
|
errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
|
||||||
errInvalidVersion = errors.New("sfnt: invalid version")
|
errInvalidVersion = errors.New("sfnt: invalid version")
|
||||||
|
|
||||||
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
||||||
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
||||||
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
||||||
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
||||||
|
errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
|
||||||
errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
|
errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
|
||||||
errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring")
|
errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring")
|
||||||
)
|
)
|
||||||
|
@ -57,12 +62,57 @@ var (
|
||||||
// GlyphIndex is a glyph index in a Font.
|
// GlyphIndex is a glyph index in a Font.
|
||||||
type GlyphIndex uint16
|
type GlyphIndex uint16
|
||||||
|
|
||||||
|
// NameID identifies a name table entry.
|
||||||
|
//
|
||||||
|
// See the "Name IDs" section of
|
||||||
|
// https://www.microsoft.com/typography/otspec/name.htm
|
||||||
|
type NameID uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
NameIDCopyright NameID = 0
|
||||||
|
NameIDFamily = 1
|
||||||
|
NameIDSubfamily = 2
|
||||||
|
NameIDUniqueIdentifier = 3
|
||||||
|
NameIDFull = 4
|
||||||
|
NameIDVersion = 5
|
||||||
|
NameIDPostScript = 6
|
||||||
|
NameIDTrademark = 7
|
||||||
|
NameIDManufacturer = 8
|
||||||
|
NameIDDesigner = 9
|
||||||
|
NameIDDescription = 10
|
||||||
|
NameIDVendorURL = 11
|
||||||
|
NameIDDesignerURL = 12
|
||||||
|
NameIDLicense = 13
|
||||||
|
NameIDLicenseURL = 14
|
||||||
|
NameIDTypographicFamily = 16
|
||||||
|
NameIDTypographicSubfamily = 17
|
||||||
|
NameIDCompatibleFull = 18
|
||||||
|
NameIDSampleText = 19
|
||||||
|
NameIDPostScriptCID = 20
|
||||||
|
NameIDWWSFamily = 21
|
||||||
|
NameIDWWSSubfamily = 22
|
||||||
|
NameIDLightBackgroundPalette = 23
|
||||||
|
NameIDDarkBackgroundPalette = 24
|
||||||
|
NameIDVariationsPostScriptPrefix = 25
|
||||||
|
)
|
||||||
|
|
||||||
// Units are an integral number of abstract, scalable "font units". The em
|
// Units are an integral number of abstract, scalable "font units". The em
|
||||||
// square is typically 1000 or 2048 "font units". This would map to a certain
|
// square is typically 1000 or 2048 "font units". This would map to a certain
|
||||||
// number (e.g. 30 pixels) of physical pixels, depending on things like the
|
// number (e.g. 30 pixels) of physical pixels, depending on things like the
|
||||||
// display resolution (DPI) and font size (e.g. a 12 point font).
|
// display resolution (DPI) and font size (e.g. a 12 point font).
|
||||||
type Units int32
|
type Units int32
|
||||||
|
|
||||||
|
// Platform IDs and Platform Specific IDs as per
|
||||||
|
// https://www.microsoft.com/typography/otspec/name.htm
|
||||||
|
const (
|
||||||
|
pidMacintosh = 1
|
||||||
|
pidWindows = 3
|
||||||
|
|
||||||
|
psidMacintoshRoman = 0
|
||||||
|
|
||||||
|
psidWindowsUCS2 = 1
|
||||||
|
)
|
||||||
|
|
||||||
func u16(b []byte) uint16 {
|
func u16(b []byte) uint16 {
|
||||||
_ = b[1] // Bounds check hint to compiler.
|
_ = b[1] // Bounds check hint to compiler.
|
||||||
return uint16(b[0])<<8 | uint16(b[1])<<0
|
return uint16(b[0])<<8 | uint16(b[1])<<0
|
||||||
|
@ -178,10 +228,20 @@ func ParseReaderAt(src io.ReaderAt) (*Font, error) {
|
||||||
|
|
||||||
// Font is an SFNT font.
|
// Font is an SFNT font.
|
||||||
//
|
//
|
||||||
// All of its methods are safe to call concurrently, although the method
|
// Many of its methods take a *Buffer argument, as re-using buffers can reduce
|
||||||
// arguments may have further restrictions. For example, it is valid to have
|
// the total memory allocation of repeated Font method calls, such as measuring
|
||||||
// multiple concurrent Font.LoadGlyph calls to the same *Font receiver, as long
|
// and rasterizing every unique glyph in a string of text. If efficiency is not
|
||||||
// as each call has a different Buffer.
|
// a concern, passing a nil *Buffer is valid, and implies using a temporary
|
||||||
|
// buffer for a single call.
|
||||||
|
//
|
||||||
|
// It is valid to re-use a *Buffer with multiple Font method calls, even with
|
||||||
|
// different *Font receivers, as long as they are not concurrent calls.
|
||||||
|
//
|
||||||
|
// All of the Font methods are safe to call concurrently, as long as each call
|
||||||
|
// has a different *Buffer (or nil).
|
||||||
|
//
|
||||||
|
// The Font methods that don't take a *Buffer argument are always safe to call
|
||||||
|
// concurrently.
|
||||||
type Font struct {
|
type Font struct {
|
||||||
src source
|
src source
|
||||||
|
|
||||||
|
@ -369,14 +429,14 @@ func (f *Font) initialize() error {
|
||||||
// TODO: func (f *Font) GlyphIndex(r rune) (x GlyphIndex, ok bool)
|
// TODO: func (f *Font) GlyphIndex(r rune) (x GlyphIndex, ok bool)
|
||||||
// This will require parsing the cmap table.
|
// This will require parsing the cmap table.
|
||||||
|
|
||||||
func (f *Font) viewGlyphData(buf []byte, x GlyphIndex) ([]byte, error) {
|
func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) ([]byte, error) {
|
||||||
xx := int(x)
|
xx := int(x)
|
||||||
if f.NumGlyphs() <= xx {
|
if f.NumGlyphs() <= xx {
|
||||||
return nil, errGlyphIndexOutOfRange
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
i := f.cached.locations[xx+0]
|
i := f.cached.locations[xx+0]
|
||||||
j := f.cached.locations[xx+1]
|
j := f.cached.locations[xx+1]
|
||||||
return f.src.view(buf, int(i), int(j-i))
|
return b.view(&f.src, int(i), int(j-i))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadGlyphOptions are the options to the Font.LoadGlyph method.
|
// LoadGlyphOptions are the options to the Font.LoadGlyph method.
|
||||||
|
@ -384,48 +444,145 @@ type LoadGlyphOptions struct {
|
||||||
// TODO: scale / transform / hinting.
|
// TODO: scale / transform / hinting.
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadGlyph loads the glyphs vectors for the x'th glyph into b.Segments.
|
// LoadGlyph returns the vector segments for the x'th glyph.
|
||||||
func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) error {
|
|
||||||
buf, err := f.viewGlyphData(b.buf, x)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Only update b.buf if it is safe to re-use buf.
|
|
||||||
if f.src.viewBufferWritable() {
|
|
||||||
b.buf = buf
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Segments = b.Segments[:0]
|
|
||||||
if f.cached.isPostScript {
|
|
||||||
b.psi.type2Charstrings.initialize(b.Segments)
|
|
||||||
if err := b.psi.run(psContextType2Charstring, buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.Segments = b.psi.type2Charstrings.segments
|
|
||||||
} else {
|
|
||||||
return errors.New("sfnt: TODO: load glyf data")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: look at opts to scale / transform / hint the Buffer.Segments.
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer holds the result of the Font.LoadGlyph method. It is valid to re-use
|
|
||||||
// a Buffer with multiple Font.LoadGlyph calls, even with different *Font
|
|
||||||
// receivers, as long as they are not concurrent calls.
|
|
||||||
//
|
//
|
||||||
// It is also valid to have multiple concurrent Font.LoadGlyph calls to the
|
// If b is non-nil, the segments become invalid to use once b is re-used.
|
||||||
// same *Font receiver, as long as each call has a different Buffer.
|
//
|
||||||
|
// It returns ErrNotFound if the glyph index is out of range.
|
||||||
|
func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Segment, error) {
|
||||||
|
if b == nil {
|
||||||
|
b = &Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := f.viewGlyphData(b, x)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.segments = b.segments[:0]
|
||||||
|
if f.cached.isPostScript {
|
||||||
|
b.psi.type2Charstrings.initialize(b.segments)
|
||||||
|
if err := b.psi.run(psContextType2Charstring, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.segments = b.psi.type2Charstrings.segments
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("sfnt: TODO: load glyf data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: look at opts to scale / transform / hint the Buffer.segments.
|
||||||
|
|
||||||
|
return b.segments, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name value keyed by the given NameID.
|
||||||
|
//
|
||||||
|
// It returns ErrNotFound if there is no value for that key.
|
||||||
|
func (f *Font) Name(b *Buffer, id NameID) (string, error) {
|
||||||
|
if b == nil {
|
||||||
|
b = &Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerSize, entrySize = 6, 12
|
||||||
|
if f.name.length < headerSize {
|
||||||
|
return "", errInvalidNameTable
|
||||||
|
}
|
||||||
|
buf, err := b.view(&f.src, int(f.name.offset), headerSize)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
nSubtables := u16(buf[2:])
|
||||||
|
if f.name.length < headerSize+entrySize*uint32(nSubtables) {
|
||||||
|
return "", errInvalidNameTable
|
||||||
|
}
|
||||||
|
stringOffset := u16(buf[4:])
|
||||||
|
|
||||||
|
seen := false
|
||||||
|
for i, n := 0, int(nSubtables); i < n; i++ {
|
||||||
|
buf, err := b.view(&f.src, int(f.name.offset)+headerSize+entrySize*i, entrySize)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if u16(buf[6:]) != uint16(id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen = true
|
||||||
|
|
||||||
|
var stringify func([]byte) (string, error)
|
||||||
|
switch u32(buf) {
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
case pidMacintosh<<16 | psidMacintoshRoman:
|
||||||
|
stringify = stringifyMacintosh
|
||||||
|
case pidWindows<<16 | psidWindowsUCS2:
|
||||||
|
stringify = stringifyUCS2
|
||||||
|
}
|
||||||
|
|
||||||
|
nameLength := u16(buf[8:])
|
||||||
|
nameOffset := u16(buf[10:])
|
||||||
|
buf, err = b.view(&f.src, int(f.name.offset)+int(nameOffset)+int(stringOffset), int(nameLength))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return stringify(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if seen {
|
||||||
|
return "", errUnsupportedPlatformEncoding
|
||||||
|
}
|
||||||
|
return "", ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifyMacintosh(b []byte) (string, error) {
|
||||||
|
for _, c := range b {
|
||||||
|
if c >= 0x80 {
|
||||||
|
// b contains some non-ASCII bytes.
|
||||||
|
s, _ := charmap.Macintosh.NewDecoder().Bytes(b)
|
||||||
|
return string(s), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// b contains only ASCII bytes.
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifyUCS2(b []byte) (string, error) {
|
||||||
|
if len(b)&1 != 0 {
|
||||||
|
return "", errInvalidUCS2String
|
||||||
|
}
|
||||||
|
r := make([]rune, len(b)/2)
|
||||||
|
for i := range r {
|
||||||
|
r[i] = rune(u16(b))
|
||||||
|
b = b[2:]
|
||||||
|
}
|
||||||
|
return string(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer holds re-usable buffers that can reduce the total memory allocation
|
||||||
|
// of repeated Font method calls.
|
||||||
|
//
|
||||||
|
// See the Font type's documentation comment for more details.
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
Segments []Segment
|
|
||||||
// buf is a byte buffer for when a Font's source is an io.ReaderAt.
|
// buf is a byte buffer for when a Font's source is an io.ReaderAt.
|
||||||
buf []byte
|
buf []byte
|
||||||
|
// segments holds glyph vector path segments.
|
||||||
|
segments []Segment
|
||||||
// psi is a PostScript interpreter for when the Font is an OpenType/CFF
|
// psi is a PostScript interpreter for when the Font is an OpenType/CFF
|
||||||
// font.
|
// font.
|
||||||
psi psInterpreter
|
psi psInterpreter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) view(src *source, offset, length int) ([]byte, error) {
|
||||||
|
buf, err := src.view(b.buf, offset, length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Only update b.buf if it is safe to re-use buf.
|
||||||
|
if src.viewBufferWritable() {
|
||||||
|
b.buf = buf
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Segment is a segment of a vector path.
|
// Segment is a segment of a vector path.
|
||||||
type Segment struct {
|
type Segment struct {
|
||||||
Op SegmentOp
|
Op SegmentOp
|
||||||
|
|
|
@ -166,11 +166,11 @@ func TestPostScript(t *testing.T) {
|
||||||
var b Buffer
|
var b Buffer
|
||||||
loop:
|
loop:
|
||||||
for i, want := range wants {
|
for i, want := range wants {
|
||||||
if err := f.LoadGlyph(&b, GlyphIndex(i), nil); err != nil {
|
got, err := f.LoadGlyph(&b, GlyphIndex(i), nil)
|
||||||
|
if err != nil {
|
||||||
t.Errorf("i=%d: LoadGlyph: %v", i, err)
|
t.Errorf("i=%d: LoadGlyph: %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
got := b.Segments
|
|
||||||
if len(got) != len(want) {
|
if len(got) != len(want) {
|
||||||
t.Errorf("i=%d: got %d elements, want %d\noverall:\ngot %v\nwant %v",
|
t.Errorf("i=%d: got %d elements, want %d\noverall:\ngot %v\nwant %v",
|
||||||
i, len(got), len(want), got, want)
|
i, len(got), len(want), got, want)
|
||||||
|
@ -184,4 +184,14 @@ loop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, err := f.LoadGlyph(nil, 0xffff, nil); err != ErrNotFound {
|
||||||
|
t.Errorf("LoadGlyph(..., 0xffff, ...):\ngot %v\nwant %v", err, ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := f.Name(nil, NameIDFamily)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Name: %v", err)
|
||||||
|
} else if want := "CFFTest"; name != want {
|
||||||
|
t.Errorf("Name:\ngot %q\nwant %q", name, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user