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:
parent
c1a19c11c3
commit
792d36e11d
|
@ -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)),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,10 +45,12 @@ 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
|
||||||
|
|
||||||
maxGlyphDataLength = 64 * 1024
|
maxCompoundRecursionDepth = 8
|
||||||
maxHintBits = 256
|
maxCompoundStackSize = 64
|
||||||
maxNumTables = 256
|
maxGlyphDataLength = 64 * 1024
|
||||||
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
maxHintBits = 256
|
||||||
|
maxNumTables = 256
|
||||||
|
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
||||||
|
|
||||||
// (maxTableOffset + maxTableLength) will not overflow an int32.
|
// (maxTableOffset + maxTableLength) will not overflow an int32.
|
||||||
maxTableLength = 1 << 29
|
maxTableLength = 1 << 29
|
||||||
|
@ -766,24 +768,21 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load
|
||||||
b = &Buffer{}
|
b = &Buffer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := f.viewGlyphData(b, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.segments = b.segments[:0]
|
b.segments = b.segments[:0]
|
||||||
if f.cached.isPostScript {
|
if f.cached.isPostScript {
|
||||||
|
buf, err := f.viewGlyphData(b, x)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user