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) {
|
||||
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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
|||
// 1 -53 -34 -44 -57 -25 rrcurveto
|
||||
cubeTo(138, -53, 104, -97, 47, -122),
|
||||
},
|
||||
|
||||
'Q': {
|
||||
// - contour #0
|
||||
// 332 57 rmoveto
|
||||
|
@ -380,6 +381,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
|||
// -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 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
|||
quadTo(281, -91, 284, 0),
|
||||
lineTo(182, 0),
|
||||
},
|
||||
|
||||
'i': {
|
||||
// - contour #0
|
||||
moveTo(136, 1259),
|
||||
|
@ -430,6 +433,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
|||
lineTo(316, 0),
|
||||
lineTo(136, 0),
|
||||
},
|
||||
|
||||
'o': {
|
||||
// - contour #0
|
||||
moveTo(68, 531),
|
||||
|
@ -453,6 +457,83 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
|||
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)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ const (
|
|||
// safe to call concurrently, as long as each call has a different *Buffer.
|
||||
maxCmapSegments = 20000
|
||||
|
||||
maxCompoundRecursionDepth = 8
|
||||
maxCompoundStackSize = 64
|
||||
maxGlyphDataLength = 64 * 1024
|
||||
maxHintBits = 256
|
||||
maxNumTables = 256
|
||||
|
@ -766,24 +768,21 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load
|
|||
b = &Buffer{}
|
||||
}
|
||||
|
||||
b.segments = b.segments[:0]
|
||||
if f.cached.isPostScript {
|
||||
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 {
|
||||
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 @@ type Buffer struct {
|
|||
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 @@ const (
|
|||
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
|
||||
}
|
||||
|
|
|
@ -84,13 +84,16 @@ func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool
|
|||
// 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 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
|
|||
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 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
|
|||
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 @@ func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
|
|||
}
|
||||
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 @@ func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user