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 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)), + }, }, } 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 @@ const ( // 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 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load 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 @@ 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 +} 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 @@ 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