diff --git a/font/sfnt/cmap.go b/font/sfnt/cmap.go index be302d2..3adda03 100644 --- a/font/sfnt/cmap.go +++ b/font/sfnt/cmap.go @@ -80,7 +80,7 @@ var supportedCmapFormat = func(format, pid, psid uint16) bool { case 4: return true case 12: - // TODO: implement. + return true } return false } @@ -92,7 +92,7 @@ func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format ui case 4: return f.makeCachedGlyphIndexFormat4(buf, offset, length) case 12: - // TODO: implement, including a cmapEntry32 type (32, not 16). + return f.makeCachedGlyphIndexFormat12(buf, offset, length) } panic("unreachable") } @@ -196,6 +196,68 @@ func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([ return buf, nil } +func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, error) { + const headerSize = 16 + if offset+headerSize > f.cmap.length { + return nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) + if err != nil { + return nil, err + } + offset += headerSize + + length := u32(buf[4:]) + numGroups := u32(buf[12:]) + if f.cmap.length < offset || length > f.cmap.length-offset { + return nil, errInvalidCmapTable + } + if numGroups > maxCmapSegments { + return nil, errUnsupportedNumberOfCmapSegments + } + + eLength := 12 * numGroups + if headerSize+eLength != length { + return nil, errInvalidCmapTable + } + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) + if err != nil { + return nil, err + } + offset += eLength + + entries := make([]cmapEntry32, numGroups) + for i := range entries { + entries[i] = cmapEntry32{ + start: u32(buf[0+12*i:]), + end: u32(buf[4+12*i:]), + delta: u32(buf[8+12*i:]), + } + } + + f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + c := uint32(r) + for i, j := 0, len(entries); i < j; { + h := i + (j-i)/2 + entry := &entries[h] + if c < entry.start { + j = h + } else if entry.end < c { + i = h + 1 + } else { + return GlyphIndex(c - entry.start + entry.delta), nil + } + } + return 0, nil + } + return buf, nil +} + type cmapEntry16 struct { end, start, delta, offset uint16 } + +type cmapEntry32 struct { + start, end, delta uint32 +} diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go index f1c6ac1..c68d78c 100644 --- a/font/sfnt/sfnt_test.go +++ b/font/sfnt/sfnt_test.go @@ -94,7 +94,7 @@ func TestGlyphIndex(t *testing.T) { t.Fatal(err) } - for _, format := range []int{-1, 0, 4} { + for _, format := range []int{-1, 0, 4, 12} { testGlyphIndex(t, data, format) } } @@ -158,26 +158,26 @@ func testGlyphIndex(t *testing.T, data []byte, cmapFormat int) { {'\u4e2d', 12}, {'\u4e2e', 0}, - /* - TODO: support runes above U+FFFF, i.e. cmap format 12. + {'\U0001f0a0', 0}, + {'\U0001f0a1', 13}, + {'\U0001f0a2', 0}, - {'\U0001f0a0', 0}, - {'\U0001f0a1', 13}, - {'\U0001f0a2', 0}, - - {'\U0001f0b0', 0}, - {'\U0001f0b1', 14}, - {'\U0001f0b2', 15}, - {'\U0001f0b3', 0}, - */ + {'\U0001f0b0', 0}, + {'\U0001f0b1', 14}, + {'\U0001f0b2', 15}, + {'\U0001f0b3', 0}, } var b Buffer for _, tc := range testCases { want := tc.want - // cmap format 0, with the Macintosh Roman encoding, can only represent - // a limited set of non-ASCII runes, e.g. U+00FF. - if cmapFormat == 0 && tc.r > '\u007f' && tc.r != '\u00ff' { + switch { + case cmapFormat == 0 && tc.r > '\u007f' && tc.r != '\u00ff': + // cmap format 0, with the Macintosh Roman encoding, can only + // represent a limited set of non-ASCII runes, e.g. U+00FF. + want = 0 + case cmapFormat == 4 && tc.r > '\uffff': + // cmap format 4 only supports the Basic Multilingual Plane (BMP). want = 0 }