font/sfnt: support TrueType compound glyphs.
Change-Id: I129e3f7894ad0edccc9e8ca4a21fc9e60e23105b
Reviewed-on: https://go-review.googlesource.com/38111
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index 2e127b6..4c6a633 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -88,15 +88,15 @@
}
func TestProprietaryMicrosoftArial(t *testing.T) {
- testProprietary(t, "microsoft", "Arial.ttf", 1200, 98)
+ testProprietary(t, "microsoft", "Arial.ttf", 1200, 599)
}
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) {
- testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 98)
+ testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 423)
}
func TestProprietaryMicrosoftWebdings(t *testing.T) {
@@ -351,6 +351,7 @@
// 1 -53 -34 -44 -57 -25 rrcurveto
cubeTo(138, -53, 104, -97, 47, -122),
},
+
'Q': {
// - contour #0
// 332 57 rmoveto
@@ -380,6 +381,7 @@
// -90 38 83 -66 121 hhcurveto
cubeTo(329, -99, 412, -165, 533, -165),
},
+
'Λ': { // U+039B GREEK CAPITAL LETTER LAMDA
// 0 vmoveto
moveTo(0, 0),
@@ -416,6 +418,7 @@
quadTo(281, -91, 284, 0),
lineTo(182, 0),
},
+
'i': {
// - contour #0
moveTo(136, 1259),
@@ -430,6 +433,7 @@
lineTo(316, 0),
lineTo(136, 0),
},
+
'o': {
// - contour #0
moveTo(68, 531),
@@ -453,6 +457,83 @@
quadTo(431, 937, 342, 836),
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)),
+ },
},
}
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 02efd5f..71aae52 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -45,10 +45,12 @@
// safe to call concurrently, as long as each call has a different *Buffer.
maxCmapSegments = 20000
- maxGlyphDataLength = 64 * 1024
- maxHintBits = 256
- maxNumTables = 256
- maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
+ maxCompoundRecursionDepth = 8
+ maxCompoundStackSize = 64
+ maxGlyphDataLength = 64 * 1024
+ maxHintBits = 256
+ maxNumTables = 256
+ maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
// (maxTableOffset + maxTableLength) will not overflow an int32.
maxTableLength = 1 << 29
@@ -766,24 +768,21 @@
b = &Buffer{}
}
- buf, err := f.viewGlyphData(b, x)
- if err != nil {
- return nil, err
- }
-
b.segments = b.segments[:0]
if f.cached.isPostScript {
+ buf, err := f.viewGlyphData(b, x)
+ if err != nil {
+ return nil, err
+ }
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 {
- segments, err := appendGlyfSegments(b.segments, buf)
- if err != nil {
+ if err := loadGlyf(f, b, x, 0, 0); err != nil {
return nil, err
}
- b.segments = segments
}
// Scale the segments. If we want to support hinting, we'll have to push
@@ -1024,6 +1023,11 @@
buf []byte
// segments holds glyph vector path segments.
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
// font.
psi psInterpreter
@@ -1056,3 +1060,13 @@
SegmentOpQuadTo
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
+}
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
index c0eefba..3c0f880 100644
--- a/font/sfnt/truetype.go
+++ b/font/sfnt/truetype.go
@@ -84,13 +84,16 @@
// glyph begins with the following [10 byte] header".
const glyfHeaderLen = 10
-// appendGlyfSegments appends to dst the segments encoded in the glyf data.
-func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
+func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error {
+ data, err := f.viewGlyphData(b, x)
+ if err != nil {
+ return err
+ }
if len(data) == 0 {
- return dst, nil
+ return nil
}
if len(data) < glyfHeaderLen {
- return nil, errInvalidGlyphData
+ return errInvalidGlyphData
}
index := glyfHeaderLen
@@ -99,34 +102,33 @@
case numContours == -1:
// We have a compound glyph. No-op.
case numContours == 0:
- return dst, nil
+ return nil
case numContours > 0:
// We have a simple (non-compound) glyph.
index += 2 * int(numContours)
if index > len(data) {
- return nil, errInvalidGlyphData
+ return errInvalidGlyphData
}
// The +1 for numPoints is because the value in the file format is
// inclusive, but Go's slice[:index] semantics are exclusive.
numPoints = 1 + int(u16(data[index-2:]))
default:
- return nil, errInvalidGlyphData
+ return errInvalidGlyphData
}
- // TODO: support compound glyphs.
if numContours < 0 {
- return nil, errUnsupportedCompoundGlyph
+ return loadCompoundGlyf(f, b, data[glyfHeaderLen:], stackBottom, recursionDepth)
}
// Skip the hinting instructions.
index += 2
if index > len(data) {
- return nil, errInvalidGlyphData
+ return errInvalidGlyphData
}
hintsLength := int(u16(data[index-2:]))
index += hintsLength
if index > len(data) {
- return nil, errInvalidGlyphData
+ return errInvalidGlyphData
}
// For simple (non-compound) glyphs, the remainder of the glyf data
@@ -140,7 +142,7 @@
flagIndex := int32(index)
xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
if !ok {
- return nil, errInvalidGlyphData
+ return errInvalidGlyphData
}
// The second pass decodes each (flags, x, y) tuple in row order.
@@ -157,13 +159,10 @@
}
for g.nextContour() {
for g.nextSegment() {
- dst = append(dst, g.seg)
+ b.segments = append(b.segments, g.seg)
}
}
- if g.err != nil {
- return nil, g.err
- }
- return dst, nil
+ return g.err
}
func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) {
@@ -215,6 +214,76 @@
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 {
data []byte
err error