font/sfnt: support TrueType compound glyphs.

Change-Id: I129e3f7894ad0edccc9e8ca4a21fc9e60e23105b
Reviewed-on: https://go-review.googlesource.com/38111
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2017-03-13 11:07:50 +11:00
parent c1a19c11c3
commit 792d36e11d
3 changed files with 196 additions and 32 deletions

View File

@ -88,15 +88,15 @@ func TestProprietaryAdobeSourceSansProTTF(t *testing.T) {
} }
func TestProprietaryMicrosoftArial(t *testing.T) { func TestProprietaryMicrosoftArial(t *testing.T) {
testProprietary(t, "microsoft", "Arial.ttf", 1200, 98) testProprietary(t, "microsoft", "Arial.ttf", 1200, 599)
} }
func TestProprietaryMicrosoftComicSansMS(t *testing.T) { func TestProprietaryMicrosoftComicSansMS(t *testing.T) {
testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, 98) testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, -1)
} }
func TestProprietaryMicrosoftTimesNewRoman(t *testing.T) { func TestProprietaryMicrosoftTimesNewRoman(t *testing.T) {
testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 98) testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 423)
} }
func TestProprietaryMicrosoftWebdings(t *testing.T) { func TestProprietaryMicrosoftWebdings(t *testing.T) {
@ -351,6 +351,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// 1 -53 -34 -44 -57 -25 rrcurveto // 1 -53 -34 -44 -57 -25 rrcurveto
cubeTo(138, -53, 104, -97, 47, -122), cubeTo(138, -53, 104, -97, 47, -122),
}, },
'Q': { 'Q': {
// - contour #0 // - contour #0
// 332 57 rmoveto // 332 57 rmoveto
@ -380,6 +381,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
// -90 38 83 -66 121 hhcurveto // -90 38 83 -66 121 hhcurveto
cubeTo(329, -99, 412, -165, 533, -165), cubeTo(329, -99, 412, -165, 533, -165),
}, },
'Λ': { // U+039B GREEK CAPITAL LETTER LAMDA 'Λ': { // U+039B GREEK CAPITAL LETTER LAMDA
// 0 vmoveto // 0 vmoveto
moveTo(0, 0), moveTo(0, 0),
@ -416,6 +418,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
quadTo(281, -91, 284, 0), quadTo(281, -91, 284, 0),
lineTo(182, 0), lineTo(182, 0),
}, },
'i': { 'i': {
// - contour #0 // - contour #0
moveTo(136, 1259), moveTo(136, 1259),
@ -430,6 +433,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
lineTo(316, 0), lineTo(316, 0),
lineTo(136, 0), lineTo(136, 0),
}, },
'o': { 'o': {
// - contour #0 // - contour #0
moveTo(68, 531), moveTo(68, 531),
@ -453,6 +457,83 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
quadTo(431, 937, 342, 836), quadTo(431, 937, 342, 836),
quadTo(253, 735, 253, 531), quadTo(253, 735, 253, 531),
}, },
'í': { // U+00ED LATIN SMALL LETTER I WITH ACUTE
// - contour #0
translate(0, 0, moveTo(198, 0)),
translate(0, 0, lineTo(198, 1062)),
translate(0, 0, lineTo(378, 1062)),
translate(0, 0, lineTo(378, 0)),
translate(0, 0, lineTo(198, 0)),
// - contour #1
translate(-33, 0, moveTo(222, 1194)),
translate(-33, 0, lineTo(355, 1474)),
translate(-33, 0, lineTo(591, 1474)),
translate(-33, 0, lineTo(371, 1194)),
translate(-33, 0, lineTo(222, 1194)),
},
'Ī': { // U+012A LATIN CAPITAL LETTER I WITH MACRON
// - contour #0
translate(0, 0, moveTo(191, 0)),
translate(0, 0, lineTo(191, 1466)),
translate(0, 0, lineTo(385, 1466)),
translate(0, 0, lineTo(385, 0)),
translate(0, 0, lineTo(191, 0)),
// - contour #1
translate(-57, 336, moveTo(29, 1227)),
translate(-57, 336, lineTo(29, 1375)),
translate(-57, 336, lineTo(653, 1375)),
translate(-57, 336, lineTo(653, 1227)),
translate(-57, 336, lineTo(29, 1227)),
},
// Ǻ is a compound glyph whose elements are also compound glyphs.
'Ǻ': { // U+01FA LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
// - contour #0
translate(0, 0, moveTo(-3, 0)),
translate(0, 0, lineTo(560, 1466)),
translate(0, 0, lineTo(769, 1466)),
translate(0, 0, lineTo(1369, 0)),
translate(0, 0, lineTo(1148, 0)),
translate(0, 0, lineTo(977, 444)),
translate(0, 0, lineTo(364, 444)),
translate(0, 0, lineTo(203, 0)),
translate(0, 0, lineTo(-3, 0)),
// - contour #1
translate(0, 0, moveTo(420, 602)),
translate(0, 0, lineTo(917, 602)),
translate(0, 0, lineTo(764, 1008)),
translate(0, 0, quadTo(694, 1193, 660, 1312)),
translate(0, 0, quadTo(632, 1171, 581, 1032)),
translate(0, 0, lineTo(420, 602)),
// - contour #2
translate(319, 263, moveTo(162, 1338)),
translate(319, 263, quadTo(162, 1411, 215, 1464)),
translate(319, 263, quadTo(269, 1517, 342, 1517)),
translate(319, 263, quadTo(416, 1517, 469, 1463)),
translate(319, 263, quadTo(522, 1410, 522, 1334)),
translate(319, 263, quadTo(522, 1257, 469, 1204)),
translate(319, 263, quadTo(416, 1151, 343, 1151)),
translate(319, 263, quadTo(268, 1151, 215, 1204)),
translate(319, 263, quadTo(162, 1258, 162, 1338)),
// - contour #3
translate(319, 263, moveTo(238, 1337)),
translate(319, 263, quadTo(238, 1290, 269, 1258)),
translate(319, 263, quadTo(301, 1226, 344, 1226)),
translate(319, 263, quadTo(387, 1226, 418, 1258)),
translate(319, 263, quadTo(450, 1290, 450, 1335)),
translate(319, 263, quadTo(450, 1380, 419, 1412)),
translate(319, 263, quadTo(388, 1444, 344, 1444)),
translate(319, 263, quadTo(301, 1444, 269, 1412)),
translate(319, 263, quadTo(238, 1381, 238, 1337)),
// - contour #4
translate(339, 650, moveTo(222, 1194)),
translate(339, 650, lineTo(355, 1474)),
translate(339, 650, lineTo(591, 1474)),
translate(339, 650, lineTo(371, 1194)),
translate(339, 650, lineTo(222, 1194)),
},
}, },
} }

View File

@ -45,6 +45,8 @@ const (
// safe to call concurrently, as long as each call has a different *Buffer. // safe to call concurrently, as long as each call has a different *Buffer.
maxCmapSegments = 20000 maxCmapSegments = 20000
maxCompoundRecursionDepth = 8
maxCompoundStackSize = 64
maxGlyphDataLength = 64 * 1024 maxGlyphDataLength = 64 * 1024
maxHintBits = 256 maxHintBits = 256
maxNumTables = 256 maxNumTables = 256
@ -766,24 +768,21 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load
b = &Buffer{} b = &Buffer{}
} }
b.segments = b.segments[:0]
if f.cached.isPostScript {
buf, err := f.viewGlyphData(b, x) buf, err := f.viewGlyphData(b, x)
if err != nil { if err != nil {
return nil, err return nil, err
} }
b.segments = b.segments[:0]
if f.cached.isPostScript {
b.psi.type2Charstrings.initialize(b.segments) b.psi.type2Charstrings.initialize(b.segments)
if err := b.psi.run(psContextType2Charstring, buf); err != nil { if err := b.psi.run(psContextType2Charstring, buf); err != nil {
return nil, err return nil, err
} }
b.segments = b.psi.type2Charstrings.segments b.segments = b.psi.type2Charstrings.segments
} else { } else {
segments, err := appendGlyfSegments(b.segments, buf) if err := loadGlyf(f, b, x, 0, 0); err != nil {
if err != nil {
return nil, err return nil, err
} }
b.segments = segments
} }
// Scale the segments. If we want to support hinting, we'll have to push // Scale the segments. If we want to support hinting, we'll have to push
@ -1024,6 +1023,11 @@ type Buffer struct {
buf []byte buf []byte
// segments holds glyph vector path segments. // segments holds glyph vector path segments.
segments []Segment segments []Segment
// compoundStack holds the components of a TrueType compound glyph.
compoundStack [maxCompoundStackSize]struct {
glyphIndex GlyphIndex
dx, dy int16
}
// 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
@ -1056,3 +1060,13 @@ const (
SegmentOpQuadTo SegmentOpQuadTo
SegmentOpCubeTo SegmentOpCubeTo
) )
func translate(dx, dy fixed.Int26_6, s Segment) Segment {
s.Args[0] += dx
s.Args[1] += dy
s.Args[2] += dx
s.Args[3] += dy
s.Args[4] += dx
s.Args[5] += dy
return s
}

View File

@ -84,13 +84,16 @@ func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool
// glyph begins with the following [10 byte] header". // glyph begins with the following [10 byte] header".
const glyfHeaderLen = 10 const glyfHeaderLen = 10
// appendGlyfSegments appends to dst the segments encoded in the glyf data. func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error {
func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) { data, err := f.viewGlyphData(b, x)
if err != nil {
return err
}
if len(data) == 0 { if len(data) == 0 {
return dst, nil return nil
} }
if len(data) < glyfHeaderLen { if len(data) < glyfHeaderLen {
return nil, errInvalidGlyphData return errInvalidGlyphData
} }
index := glyfHeaderLen index := glyfHeaderLen
@ -99,34 +102,33 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
case numContours == -1: case numContours == -1:
// We have a compound glyph. No-op. // We have a compound glyph. No-op.
case numContours == 0: case numContours == 0:
return dst, nil return nil
case numContours > 0: case numContours > 0:
// We have a simple (non-compound) glyph. // We have a simple (non-compound) glyph.
index += 2 * int(numContours) index += 2 * int(numContours)
if index > len(data) { if index > len(data) {
return nil, errInvalidGlyphData return errInvalidGlyphData
} }
// The +1 for numPoints is because the value in the file format is // The +1 for numPoints is because the value in the file format is
// inclusive, but Go's slice[:index] semantics are exclusive. // inclusive, but Go's slice[:index] semantics are exclusive.
numPoints = 1 + int(u16(data[index-2:])) numPoints = 1 + int(u16(data[index-2:]))
default: default:
return nil, errInvalidGlyphData return errInvalidGlyphData
} }
// TODO: support compound glyphs.
if numContours < 0 { if numContours < 0 {
return nil, errUnsupportedCompoundGlyph return loadCompoundGlyf(f, b, data[glyfHeaderLen:], stackBottom, recursionDepth)
} }
// Skip the hinting instructions. // Skip the hinting instructions.
index += 2 index += 2
if index > len(data) { if index > len(data) {
return nil, errInvalidGlyphData return errInvalidGlyphData
} }
hintsLength := int(u16(data[index-2:])) hintsLength := int(u16(data[index-2:]))
index += hintsLength index += hintsLength
if index > len(data) { if index > len(data) {
return nil, errInvalidGlyphData return errInvalidGlyphData
} }
// For simple (non-compound) glyphs, the remainder of the glyf data // For simple (non-compound) glyphs, the remainder of the glyf data
@ -140,7 +142,7 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
flagIndex := int32(index) flagIndex := int32(index)
xIndex, yIndex, ok := findXYIndexes(data, index, numPoints) xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
if !ok { if !ok {
return nil, errInvalidGlyphData return errInvalidGlyphData
} }
// The second pass decodes each (flags, x, y) tuple in row order. // The second pass decodes each (flags, x, y) tuple in row order.
@ -157,13 +159,10 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
} }
for g.nextContour() { for g.nextContour() {
for g.nextSegment() { for g.nextSegment() {
dst = append(dst, g.seg) b.segments = append(b.segments, g.seg)
} }
} }
if g.err != nil { return g.err
return nil, g.err
}
return dst, nil
} }
func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) { func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) {
@ -215,6 +214,76 @@ func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok
return int32(index), int32(index + xDataLen), true return int32(index), int32(index + xDataLen), true
} }
func loadCompoundGlyf(f *Font, b *Buffer, data []byte, stackBottom, recursionDepth uint32) error {
if recursionDepth++; recursionDepth == maxCompoundRecursionDepth {
return errUnsupportedCompoundGlyph
}
// Read and process the compound glyph's components. They are two separate
// for loops, since reading parses the elements of the data slice, and
// processing can overwrite the backing array.
stackTop := stackBottom
for {
if stackTop >= maxCompoundStackSize {
return errUnsupportedCompoundGlyph
}
elem := &b.compoundStack[stackTop]
stackTop++
if len(data) < 4 {
return errInvalidGlyphData
}
flags := u16(data)
elem.glyphIndex = GlyphIndex(u16(data[2:]))
if flags&flagArg1And2AreWords == 0 {
if len(data) < 6 {
return errInvalidGlyphData
}
elem.dx = int16(int8(data[4]))
elem.dy = int16(int8(data[5]))
data = data[6:]
} else {
if len(data) < 8 {
return errInvalidGlyphData
}
elem.dx = int16(u16(data[4:]))
elem.dy = int16(u16(data[6:]))
data = data[8:]
}
if flags&flagArgsAreXYValues == 0 {
return errUnsupportedCompoundGlyph
}
// TODO: read the other elem.transform elements.
if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 {
return errUnsupportedCompoundGlyph
}
if flags&flagMoreComponents == 0 {
break
}
}
// To support hinting, we'd have to save the remaining bytes in data here
// and interpret them after the for loop below, since that for loop's
// loadGlyf calls can overwrite the backing array.
for i := stackBottom; i < stackTop; i++ {
elem := &b.compoundStack[i]
base := len(b.segments)
if err := loadGlyf(f, b, elem.glyphIndex, stackTop, recursionDepth); err != nil {
return err
}
dx, dy := fixed.Int26_6(elem.dx), fixed.Int26_6(elem.dy)
segs := b.segments[base:]
for j := range segs {
segs[j] = translate(dx, dy, segs[j])
}
}
return nil
}
type glyfIter struct { type glyfIter struct {
data []byte data []byte
err error err error