font/sfnt: make parseXxx dependencies explicit.
Change-Id: Ib0b76c48cd0b4288700458407077aae4e5911233 Reviewed-on: https://go-review.googlesource.com/37553 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
e6cbe778da
commit
069db1da13
|
@ -88,7 +88,7 @@ var supportedCmapFormat = func(format, pid, psid uint16) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, error) {
|
func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, glyphIndexFunc, error) {
|
||||||
switch format {
|
switch format {
|
||||||
case 0:
|
case 0:
|
||||||
return f.makeCachedGlyphIndexFormat0(buf, offset, length)
|
return f.makeCachedGlyphIndexFormat0(buf, offset, length)
|
||||||
|
@ -100,18 +100,18 @@ func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format ui
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, error) {
|
func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
|
||||||
if length != 6+256 || offset+length > f.cmap.length {
|
if length != 6+256 || offset+length > f.cmap.length {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length))
|
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
var table [256]byte
|
var table [256]byte
|
||||||
copy(table[:], buf[6:])
|
copy(table[:], buf[6:])
|
||||||
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
||||||
// TODO: for this closure to be goroutine-safe, the
|
// TODO: for this closure to be goroutine-safe, the
|
||||||
// golang.org/x/text/encoding/charmap API needs to allocate a new
|
// golang.org/x/text/encoding/charmap API needs to allocate a new
|
||||||
// Encoder and new []byte buffers, for every call to this closure, even
|
// Encoder and new []byte buffers, for every call to this closure, even
|
||||||
|
@ -126,38 +126,37 @@ func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
return GlyphIndex(table[dst[0]]), nil
|
return GlyphIndex(table[dst[0]]), nil
|
||||||
}
|
}, nil
|
||||||
return buf, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, error) {
|
func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) {
|
||||||
const headerSize = 14
|
const headerSize = 14
|
||||||
if offset+headerSize > f.cmap.length {
|
if offset+headerSize > f.cmap.length {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
|
buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
offset += headerSize
|
offset += headerSize
|
||||||
|
|
||||||
segCount := u16(buf[6:])
|
segCount := u16(buf[6:])
|
||||||
if segCount&1 != 0 {
|
if segCount&1 != 0 {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
segCount /= 2
|
segCount /= 2
|
||||||
if segCount > maxCmapSegments {
|
if segCount > maxCmapSegments {
|
||||||
return nil, errUnsupportedNumberOfCmapSegments
|
return nil, nil, errUnsupportedNumberOfCmapSegments
|
||||||
}
|
}
|
||||||
|
|
||||||
eLength := 8*uint32(segCount) + 2
|
eLength := 8*uint32(segCount) + 2
|
||||||
if offset+eLength > f.cmap.length {
|
if offset+eLength > f.cmap.length {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
|
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
offset += eLength
|
offset += eLength
|
||||||
|
|
||||||
|
@ -173,7 +172,7 @@ func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([
|
||||||
indexesBase := f.cmap.offset + offset
|
indexesBase := f.cmap.offset + offset
|
||||||
indexesLength := f.cmap.length - offset
|
indexesLength := f.cmap.length - offset
|
||||||
|
|
||||||
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
||||||
if uint32(r) > 0xffff {
|
if uint32(r) > 0xffff {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
@ -201,38 +200,37 @@ func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}, nil
|
||||||
return buf, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, error) {
|
func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, glyphIndexFunc, error) {
|
||||||
const headerSize = 16
|
const headerSize = 16
|
||||||
if offset+headerSize > f.cmap.length {
|
if offset+headerSize > f.cmap.length {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
|
buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
length := u32(buf[4:])
|
length := u32(buf[4:])
|
||||||
if f.cmap.length < offset || length > f.cmap.length-offset {
|
if f.cmap.length < offset || length > f.cmap.length-offset {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
offset += headerSize
|
offset += headerSize
|
||||||
|
|
||||||
numGroups := u32(buf[12:])
|
numGroups := u32(buf[12:])
|
||||||
if numGroups > maxCmapSegments {
|
if numGroups > maxCmapSegments {
|
||||||
return nil, errUnsupportedNumberOfCmapSegments
|
return nil, nil, errUnsupportedNumberOfCmapSegments
|
||||||
}
|
}
|
||||||
|
|
||||||
eLength := 12 * numGroups
|
eLength := 12 * numGroups
|
||||||
if headerSize+eLength != length {
|
if headerSize+eLength != length {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
|
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
offset += eLength
|
offset += eLength
|
||||||
|
|
||||||
|
@ -245,7 +243,7 @@ func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
||||||
c := uint32(r)
|
c := uint32(r)
|
||||||
for i, j := 0, len(entries); i < j; {
|
for i, j := 0, len(entries); i < j; {
|
||||||
h := i + (j-i)/2
|
h := i + (j-i)/2
|
||||||
|
@ -259,8 +257,7 @@ func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}, nil
|
||||||
return buf, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type cmapEntry16 struct {
|
type cmapEntry16 struct {
|
||||||
|
|
|
@ -340,7 +340,7 @@ type Font struct {
|
||||||
kern table
|
kern table
|
||||||
|
|
||||||
cached struct {
|
cached struct {
|
||||||
glyphIndex func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
|
glyphIndex glyphIndexFunc
|
||||||
indexToLocFormat bool // false means short, true means long.
|
indexToLocFormat bool // false means short, true means long.
|
||||||
isPostScript bool
|
isPostScript bool
|
||||||
kernNumPairs int32
|
kernNumPairs int32
|
||||||
|
@ -364,84 +364,96 @@ func (f *Font) initialize() error {
|
||||||
if !f.src.valid() {
|
if !f.src.valid() {
|
||||||
return errInvalidSourceData
|
return errInvalidSourceData
|
||||||
}
|
}
|
||||||
buf, err := f.initializeTables(nil)
|
buf, isPostScript, err := f.initializeTables(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The order of these parseXxx calls matters. Later calls may depend on
|
// The order of these parseXxx calls matters. Later calls may depend on
|
||||||
// f.cached state set up by earlier calls, such as the number of glyphs in
|
// information parsed by earlier calls, such as the maxp table's numGlyphs.
|
||||||
// the font being parsed by parseMaxp.
|
// To enforce these dependencies, such information is passed and returned
|
||||||
|
// explicitly, and the f.cached fields are only set afterwards.
|
||||||
|
//
|
||||||
|
// When implementing new parseXxx methods, take care not to call methods
|
||||||
|
// such as Font.NumGlyphs that implicitly depend on f.cached fields.
|
||||||
|
|
||||||
// TODO: make state dependencies explicit instead of implicit.
|
buf, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf, numGlyphs, locations, err := f.parseMaxp(buf, indexToLocFormat, isPostScript)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf, glyphIndex, err := f.parseCmap(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf, kernNumPairs, kernOffset, err := f.parseKern(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf, postTableVersion, err := f.parsePost(buf, numGlyphs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.cached.glyphIndex = glyphIndex
|
||||||
|
f.cached.indexToLocFormat = indexToLocFormat
|
||||||
|
f.cached.isPostScript = isPostScript
|
||||||
|
f.cached.kernNumPairs = kernNumPairs
|
||||||
|
f.cached.kernOffset = kernOffset
|
||||||
|
f.cached.postTableVersion = postTableVersion
|
||||||
|
f.cached.unitsPerEm = unitsPerEm
|
||||||
|
f.cached.locations = locations
|
||||||
|
|
||||||
buf, err = f.parseHead(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf, err = f.parseMaxp(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf, err = f.parseCmap(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf, err = f.parseKern(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf, err = f.parsePost(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) initializeTables(buf []byte) ([]byte, error) {
|
func (f *Font) initializeTables(buf []byte) (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.
|
||||||
buf, err := f.src.view(buf, 0, 12)
|
buf, err = f.src.view(buf, 0, 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
switch u32(buf) {
|
switch u32(buf) {
|
||||||
default:
|
default:
|
||||||
return nil, errInvalidVersion
|
return nil, false, errInvalidVersion
|
||||||
case 0x00010000:
|
case 0x00010000:
|
||||||
// No-op.
|
// No-op.
|
||||||
case 0x4f54544f: // "OTTO".
|
case 0x4f54544f: // "OTTO".
|
||||||
f.cached.isPostScript = true
|
isPostScript = true
|
||||||
}
|
}
|
||||||
numTables := int(u16(buf[4:]))
|
numTables := int(u16(buf[4:]))
|
||||||
if numTables > maxNumTables {
|
if numTables > maxNumTables {
|
||||||
return nil, errUnsupportedNumberOfTables
|
return nil, false, errUnsupportedNumberOfTables
|
||||||
}
|
}
|
||||||
|
|
||||||
// "The Offset Table is followed immediately by the Table Record entries...
|
// "The Offset Table is followed immediately by the Table Record entries...
|
||||||
// sorted in ascending order by tag", 16 bytes each.
|
// sorted in ascending order by tag", 16 bytes each.
|
||||||
buf, err = f.src.view(buf, 12, 16*numTables)
|
buf, err = f.src.view(buf, 12, 16*numTables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] {
|
for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] {
|
||||||
tag := u32(b)
|
tag := u32(b)
|
||||||
if first {
|
if first {
|
||||||
first = false
|
first = false
|
||||||
} else if tag <= prevTag {
|
} else if tag <= prevTag {
|
||||||
return nil, errInvalidTableTagOrder
|
return nil, false, errInvalidTableTagOrder
|
||||||
}
|
}
|
||||||
prevTag = tag
|
prevTag = tag
|
||||||
|
|
||||||
o, n := u32(b[8:12]), u32(b[12:16])
|
o, n := u32(b[8:12]), u32(b[12:16])
|
||||||
if o > maxTableOffset || n > maxTableLength {
|
if o > maxTableOffset || n > maxTableLength {
|
||||||
return nil, errUnsupportedTableOffsetLength
|
return nil, false, errUnsupportedTableOffsetLength
|
||||||
}
|
}
|
||||||
// We ignore the checksums, but "all tables must begin on four byte
|
// We ignore the checksums, but "all tables must begin on four byte
|
||||||
// boundries [sic]".
|
// boundries [sic]".
|
||||||
if o&3 != 0 {
|
if o&3 != 0 {
|
||||||
return nil, errInvalidTableOffset
|
return nil, false, errInvalidTableOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
|
// Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
|
||||||
|
@ -472,23 +484,23 @@ func (f *Font) initializeTables(buf []byte) ([]byte, error) {
|
||||||
f.post = table{o, n}
|
f.post = table{o, n}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buf, nil
|
return buf, isPostScript, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseCmap(buf []byte) ([]byte, error) {
|
func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, err error) {
|
||||||
// https://www.microsoft.com/typography/OTSPEC/cmap.htm
|
// https://www.microsoft.com/typography/OTSPEC/cmap.htm
|
||||||
|
|
||||||
const headerSize, entrySize = 4, 8
|
const headerSize, entrySize = 4, 8
|
||||||
if f.cmap.length < headerSize {
|
if f.cmap.length < headerSize {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
u, err := f.src.u16(buf, f.cmap, 2)
|
u, err := f.src.u16(buf, f.cmap, 2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
numSubtables := int(u)
|
numSubtables := int(u)
|
||||||
if f.cmap.length < headerSize+entrySize*uint32(numSubtables) {
|
if f.cmap.length < headerSize+entrySize*uint32(numSubtables) {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -503,7 +515,7 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) {
|
||||||
for i := 0; i < numSubtables; i++ {
|
for i := 0; i < numSubtables; i++ {
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize)
|
buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
pid := u16(buf)
|
pid := u16(buf)
|
||||||
psid := u16(buf[2:])
|
psid := u16(buf[2:])
|
||||||
|
@ -514,11 +526,11 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) {
|
||||||
offset := u32(buf[4:])
|
offset := u32(buf[4:])
|
||||||
|
|
||||||
if offset > f.cmap.length-4 {
|
if offset > f.cmap.length-4 {
|
||||||
return nil, errInvalidCmapTable
|
return nil, nil, errInvalidCmapTable
|
||||||
}
|
}
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4)
|
buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
format := u16(buf)
|
format := u16(buf)
|
||||||
if !supportedCmapFormat(format, pid, psid) {
|
if !supportedCmapFormat(format, pid, psid) {
|
||||||
|
@ -533,46 +545,46 @@ func (f *Font) parseCmap(buf []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if bestWidth == 0 {
|
if bestWidth == 0 {
|
||||||
return nil, errUnsupportedCmapEncodings
|
return nil, nil, errUnsupportedCmapEncodings
|
||||||
}
|
}
|
||||||
return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
|
return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseHead(buf []byte) ([]byte, error) {
|
func (f *Font) parseHead(buf []byte) (buf1 []byte, indexToLocFormat bool, unitsPerEm Units, err error) {
|
||||||
// https://www.microsoft.com/typography/otspec/head.htm
|
// https://www.microsoft.com/typography/otspec/head.htm
|
||||||
|
|
||||||
if f.head.length != 54 {
|
if f.head.length != 54 {
|
||||||
return nil, errInvalidHeadTable
|
return nil, false, 0, errInvalidHeadTable
|
||||||
}
|
}
|
||||||
u, err := f.src.u16(buf, f.head, 18)
|
u, err := f.src.u16(buf, f.head, 18)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, 0, err
|
||||||
}
|
}
|
||||||
if u == 0 {
|
if u == 0 {
|
||||||
return nil, errInvalidHeadTable
|
return nil, false, 0, errInvalidHeadTable
|
||||||
}
|
}
|
||||||
f.cached.unitsPerEm = Units(u)
|
unitsPerEm = Units(u)
|
||||||
u, err = f.src.u16(buf, f.head, 50)
|
u, err = f.src.u16(buf, f.head, 50)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, false, 0, err
|
||||||
}
|
}
|
||||||
f.cached.indexToLocFormat = u != 0
|
indexToLocFormat = u != 0
|
||||||
return buf, nil
|
return buf, indexToLocFormat, unitsPerEm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseKern(buf []byte) ([]byte, error) {
|
func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
|
||||||
// https://www.microsoft.com/typography/otspec/kern.htm
|
// https://www.microsoft.com/typography/otspec/kern.htm
|
||||||
|
|
||||||
if f.kern.length == 0 {
|
if f.kern.length == 0 {
|
||||||
return buf, nil
|
return buf, 0, 0, nil
|
||||||
}
|
}
|
||||||
const headerSize = 4
|
const headerSize = 4
|
||||||
if f.kern.length < headerSize {
|
if f.kern.length < headerSize {
|
||||||
return nil, errInvalidKernTable
|
return nil, 0, 0, errInvalidKernTable
|
||||||
}
|
}
|
||||||
buf, err := f.src.view(buf, int(f.kern.offset), headerSize)
|
buf, err = f.src.view(buf, int(f.kern.offset), headerSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
offset := int(f.kern.offset) + headerSize
|
offset := int(f.kern.offset) + headerSize
|
||||||
length := int(f.kern.length) - headerSize
|
length := int(f.kern.length) - headerSize
|
||||||
|
@ -581,7 +593,7 @@ func (f *Font) parseKern(buf []byte) ([]byte, error) {
|
||||||
case 0:
|
case 0:
|
||||||
// TODO: support numTables != 1. Testing that requires finding such a font.
|
// TODO: support numTables != 1. Testing that requires finding such a font.
|
||||||
if numTables := int(u16(buf[2:])); numTables != 1 {
|
if numTables := int(u16(buf[2:])); numTables != 1 {
|
||||||
return nil, errUnsupportedKernTable
|
return nil, 0, 0, errUnsupportedKernTable
|
||||||
}
|
}
|
||||||
return f.parseKernVersion0(buf, offset, length)
|
return f.parseKernVersion0(buf, offset, length)
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -590,28 +602,28 @@ func (f *Font) parseKern(buf []byte) ([]byte, error) {
|
||||||
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
|
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
|
||||||
// say that such fonts work on Mac OS but not on Windows.
|
// say that such fonts work on Mac OS but not on Windows.
|
||||||
}
|
}
|
||||||
return nil, errUnsupportedKernTable
|
return nil, 0, 0, errUnsupportedKernTable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseKernVersion0(buf []byte, offset, length int) ([]byte, error) {
|
func (f *Font) parseKernVersion0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
|
||||||
const headerSize = 6
|
const headerSize = 6
|
||||||
if length < headerSize {
|
if length < headerSize {
|
||||||
return nil, errInvalidKernTable
|
return nil, 0, 0, errInvalidKernTable
|
||||||
}
|
}
|
||||||
buf, err := f.src.view(buf, offset, headerSize)
|
buf, err = f.src.view(buf, offset, headerSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
if version := u16(buf); version != 0 {
|
if version := u16(buf); version != 0 {
|
||||||
return nil, errUnsupportedKernTable
|
return nil, 0, 0, errUnsupportedKernTable
|
||||||
}
|
}
|
||||||
subtableLength := int(u16(buf[2:]))
|
subtableLength := int(u16(buf[2:]))
|
||||||
if subtableLength < headerSize || length < subtableLength {
|
if subtableLength < headerSize || length < subtableLength {
|
||||||
return nil, errInvalidKernTable
|
return nil, 0, 0, errInvalidKernTable
|
||||||
}
|
}
|
||||||
if coverageBits := buf[5]; coverageBits != 0x01 {
|
if coverageBits := buf[5]; coverageBits != 0x01 {
|
||||||
// We only support horizontal kerning.
|
// We only support horizontal kerning.
|
||||||
return nil, errUnsupportedKernTable
|
return nil, 0, 0, errUnsupportedKernTable
|
||||||
}
|
}
|
||||||
offset += headerSize
|
offset += headerSize
|
||||||
length -= headerSize
|
length -= headerSize
|
||||||
|
@ -623,99 +635,97 @@ func (f *Font) parseKernVersion0(buf []byte, offset, length int) ([]byte, error)
|
||||||
case 2:
|
case 2:
|
||||||
// TODO: find such a (proprietary?) font, and support it.
|
// TODO: find such a (proprietary?) font, and support it.
|
||||||
}
|
}
|
||||||
return nil, errUnsupportedKernTable
|
return nil, 0, 0, errUnsupportedKernTable
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseKernFormat0(buf []byte, offset, length int) ([]byte, error) {
|
func (f *Font) parseKernFormat0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
|
||||||
const headerSize, entrySize = 8, 6
|
const headerSize, entrySize = 8, 6
|
||||||
if length < headerSize {
|
if length < headerSize {
|
||||||
return nil, errInvalidKernTable
|
return nil, 0, 0, errInvalidKernTable
|
||||||
}
|
}
|
||||||
buf, err := f.src.view(buf, offset, headerSize)
|
buf, err = f.src.view(buf, offset, headerSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
numPairs := u16(buf)
|
kernNumPairs = int32(u16(buf))
|
||||||
if length != headerSize+entrySize*int(numPairs) {
|
if length != headerSize+entrySize*int(kernNumPairs) {
|
||||||
return nil, errInvalidKernTable
|
return nil, 0, 0, errInvalidKernTable
|
||||||
}
|
}
|
||||||
f.cached.kernNumPairs = int32(numPairs)
|
return buf, kernNumPairs, int32(offset) + headerSize, nil
|
||||||
f.cached.kernOffset = int32(offset) + headerSize
|
|
||||||
return buf, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parseMaxp(buf []byte) ([]byte, error) {
|
func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1 []byte, numGlyphs int, locations []uint32, err error) {
|
||||||
// https://www.microsoft.com/typography/otspec/maxp.htm
|
// https://www.microsoft.com/typography/otspec/maxp.htm
|
||||||
|
|
||||||
if f.cached.isPostScript {
|
if isPostScript {
|
||||||
if f.maxp.length != 6 {
|
if f.maxp.length != 6 {
|
||||||
return nil, errInvalidMaxpTable
|
return nil, 0, nil, errInvalidMaxpTable
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if f.maxp.length != 32 {
|
if f.maxp.length != 32 {
|
||||||
return nil, errInvalidMaxpTable
|
return nil, 0, nil, errInvalidMaxpTable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u, err := f.src.u16(buf, f.maxp, 4)
|
u, err := f.src.u16(buf, f.maxp, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
numGlyphs := int(u)
|
numGlyphs = int(u)
|
||||||
|
|
||||||
if f.cached.isPostScript {
|
if isPostScript {
|
||||||
p := cffParser{
|
p := cffParser{
|
||||||
src: &f.src,
|
src: &f.src,
|
||||||
base: int(f.cff.offset),
|
base: int(f.cff.offset),
|
||||||
offset: int(f.cff.offset),
|
offset: int(f.cff.offset),
|
||||||
end: int(f.cff.offset + f.cff.length),
|
end: int(f.cff.offset + f.cff.length),
|
||||||
}
|
}
|
||||||
f.cached.locations, err = p.parse()
|
locations, err = p.parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.cached.locations, err = parseLoca(
|
locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
|
||||||
&f.src, f.loca, f.glyf.offset, f.cached.indexToLocFormat, numGlyphs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(f.cached.locations) != numGlyphs+1 {
|
if len(locations) != numGlyphs+1 {
|
||||||
return nil, errInvalidLocationData
|
return nil, 0, nil, errInvalidLocationData
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf, nil
|
return buf, numGlyphs, locations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Font) parsePost(buf []byte) ([]byte, error) {
|
func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersion uint32, 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, errInvalidPostTable
|
return nil, 0, 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, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
switch u {
|
switch u {
|
||||||
case 0x20000:
|
case 0x20000:
|
||||||
if f.post.length < headerSize+2+2*uint32(f.NumGlyphs()) {
|
if f.post.length < headerSize+2+2*uint32(numGlyphs) {
|
||||||
return nil, errInvalidPostTable
|
return nil, 0, errInvalidPostTable
|
||||||
}
|
}
|
||||||
case 0x30000:
|
case 0x30000:
|
||||||
// No-op.
|
// No-op.
|
||||||
default:
|
default:
|
||||||
return nil, errUnsupportedPostTable
|
return nil, 0, errUnsupportedPostTable
|
||||||
}
|
}
|
||||||
f.cached.postTableVersion = u
|
return buf, u, nil
|
||||||
return buf, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: API for looking up glyph variants?? For example, some fonts may
|
// TODO: API for looking up glyph variants?? For example, some fonts may
|
||||||
// provide both slashed and dotted zero glyphs ('0'), or regular and 'old
|
// provide both slashed and dotted zero glyphs ('0'), or regular and 'old
|
||||||
// style' numerals, and users can direct software to choose a variant.
|
// style' numerals, and users can direct software to choose a variant.
|
||||||
|
|
||||||
|
type glyphIndexFunc func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
|
||||||
|
|
||||||
// GlyphIndex returns the glyph index for the given rune.
|
// GlyphIndex returns the glyph index for the given rune.
|
||||||
//
|
//
|
||||||
// It returns (0, nil) if there is no glyph for r.
|
// It returns (0, nil) if there is no glyph for r.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user