freetype: optimize []byte to uint16/uint32 conversions.
Thanks to Jeff R. Allen <jra@nella.org> for the conversation that led to this change. benchmark old ns/op new ns/op delta BenchmarkDrawString 21168440 20143860 -4.84% The number of mallocs per iteration is unchanged. R=rsc, r CC=golang-dev, jra http://codereview.appspot.com/6304077
This commit is contained in:
parent
e5aa5b6a82
commit
6baa5f0a46
4 changed files with 234 additions and 125 deletions
59
freetype/freetype_test.go
Normal file
59
freetype/freetype_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2012 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package freetype
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkDrawString(b *testing.B) {
|
||||
data, err := ioutil.ReadFile("../licenses/gpl.txt")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
|
||||
data, err = ioutil.ReadFile("../luxi-fonts/luxisr.ttf")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
font, err := ParseFont(data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 800, 600))
|
||||
draw.Draw(dst, dst.Bounds(), image.White, image.ZP, draw.Src)
|
||||
|
||||
c := NewContext()
|
||||
c.SetDst(dst)
|
||||
c.SetClip(dst.Bounds())
|
||||
c.SetSrc(image.Black)
|
||||
c.SetFont(font)
|
||||
|
||||
var ms runtime.MemStats
|
||||
runtime.ReadMemStats(&ms)
|
||||
mallocs := ms.Mallocs
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j, line := range lines {
|
||||
_, err := c.DrawString(line, Pt(0, (j*16)%600))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
runtime.ReadMemStats(&ms)
|
||||
mallocs = ms.Mallocs - mallocs
|
||||
b.Logf("%d iterations, %d mallocs per iteration\n", b.N, int(mallocs)/b.N)
|
||||
}
|
|
@ -47,36 +47,40 @@ const (
|
|||
|
||||
// decodeFlags decodes a glyph's run-length encoded flags,
|
||||
// and returns the remaining data.
|
||||
func (g *GlyphBuf) decodeFlags(d data, np0 int) data {
|
||||
func (g *GlyphBuf) decodeFlags(d []byte, offset int, np0 int) (offset1 int) {
|
||||
for i := np0; i < len(g.Point); {
|
||||
c := d.u8()
|
||||
c := d[offset]
|
||||
offset++
|
||||
g.Point[i].Flags = c
|
||||
i++
|
||||
if c&flagRepeat != 0 {
|
||||
count := d.u8()
|
||||
count := d[offset]
|
||||
offset++
|
||||
for ; count > 0; count-- {
|
||||
g.Point[i].Flags = c
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
return offset
|
||||
}
|
||||
|
||||
// decodeCoords decodes a glyph's delta encoded co-ordinates.
|
||||
func (g *GlyphBuf) decodeCoords(d data, np0 int) {
|
||||
func (g *GlyphBuf) decodeCoords(d []byte, offset int, np0 int) int {
|
||||
var x int16
|
||||
for i := np0; i < len(g.Point); i++ {
|
||||
f := g.Point[i].Flags
|
||||
if f&flagXShortVector != 0 {
|
||||
dx := int16(d.u8())
|
||||
dx := int16(d[offset])
|
||||
offset++
|
||||
if f&flagPositiveXShortVector == 0 {
|
||||
x -= dx
|
||||
} else {
|
||||
x += dx
|
||||
}
|
||||
} else if f&flagThisXIsSame == 0 {
|
||||
x += int16(d.u16())
|
||||
x += int16(u16(d, offset))
|
||||
offset += 2
|
||||
}
|
||||
g.Point[i].X = x
|
||||
}
|
||||
|
@ -84,17 +88,20 @@ func (g *GlyphBuf) decodeCoords(d data, np0 int) {
|
|||
for i := np0; i < len(g.Point); i++ {
|
||||
f := g.Point[i].Flags
|
||||
if f&flagYShortVector != 0 {
|
||||
dy := int16(d.u8())
|
||||
dy := int16(d[offset])
|
||||
offset++
|
||||
if f&flagPositiveYShortVector == 0 {
|
||||
y -= dy
|
||||
} else {
|
||||
y += dy
|
||||
}
|
||||
} else if f&flagThisYIsSame == 0 {
|
||||
y += int16(d.u16())
|
||||
y += int16(u16(d, offset))
|
||||
offset += 2
|
||||
}
|
||||
g.Point[i].Y = y
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
// Load loads a glyph's contours from a Font, overwriting any previously
|
||||
|
@ -102,13 +109,13 @@ func (g *GlyphBuf) decodeCoords(d data, np0 int) {
|
|||
func (g *GlyphBuf) Load(f *Font, i Index) error {
|
||||
// Reset the GlyphBuf.
|
||||
g.B = Bounds{}
|
||||
g.Point = g.Point[0:0]
|
||||
g.End = g.End[0:0]
|
||||
g.Point = g.Point[:0]
|
||||
g.End = g.End[:0]
|
||||
return g.load(f, i, 0)
|
||||
}
|
||||
|
||||
// loadCompound loads a glyph that is composed of other glyphs.
|
||||
func (g *GlyphBuf) loadCompound(f *Font, d data, recursion int) error {
|
||||
func (g *GlyphBuf) loadCompound(f *Font, glyf []byte, offset, recursion int) error {
|
||||
// Flags for decoding a compound glyph. These flags are documented at
|
||||
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html.
|
||||
const (
|
||||
|
@ -125,15 +132,17 @@ func (g *GlyphBuf) loadCompound(f *Font, d data, recursion int) error {
|
|||
flagOverlapCompound
|
||||
)
|
||||
for {
|
||||
flags := d.u16()
|
||||
component := d.u16()
|
||||
flags := u16(glyf, offset)
|
||||
component := u16(glyf, offset+2)
|
||||
var dx, dy int16
|
||||
if flags&flagArg1And2AreWords != 0 {
|
||||
dx = int16(d.u16())
|
||||
dy = int16(d.u16())
|
||||
dx = int16(u16(glyf, offset+4))
|
||||
dy = int16(u16(glyf, offset+6))
|
||||
offset += 8
|
||||
} else {
|
||||
dx = int16(int8(d.u8()))
|
||||
dy = int16(int8(d.u8()))
|
||||
dx = int16(int8(glyf[offset+4]))
|
||||
dy = int16(int8(glyf[offset+5]))
|
||||
offset += 6
|
||||
}
|
||||
if flags&flagArgsAreXYValues == 0 {
|
||||
return UnsupportedError("compound glyph transform vector")
|
||||
|
@ -165,26 +174,25 @@ func (g *GlyphBuf) load(f *Font, i Index, recursion int) error {
|
|||
// Find the relevant slice of f.glyf.
|
||||
var g0, g1 uint32
|
||||
if f.locaOffsetFormat == locaOffsetFormatShort {
|
||||
d := data(f.loca[2*int(i):])
|
||||
g0 = 2 * uint32(d.u16())
|
||||
g1 = 2 * uint32(d.u16())
|
||||
g0 = 2 * uint32(u16(f.loca, 2*int(i)))
|
||||
g1 = 2 * uint32(u16(f.loca, 2*int(i)+2))
|
||||
} else {
|
||||
d := data(f.loca[4*int(i):])
|
||||
g0 = d.u32()
|
||||
g1 = d.u32()
|
||||
g0 = u32(f.loca, 4*int(i))
|
||||
g1 = u32(f.loca, 4*int(i)+4)
|
||||
}
|
||||
if g0 == g1 {
|
||||
return nil
|
||||
}
|
||||
d := data(f.glyf[g0:g1])
|
||||
glyf := f.glyf[g0:g1]
|
||||
// Decode the contour end indices.
|
||||
ne := int(int16(d.u16()))
|
||||
g.B.XMin = int16(d.u16())
|
||||
g.B.YMin = int16(d.u16())
|
||||
g.B.XMax = int16(d.u16())
|
||||
g.B.YMax = int16(d.u16())
|
||||
ne := int(int16(u16(glyf, 0)))
|
||||
g.B.XMin = int16(u16(glyf, 2))
|
||||
g.B.YMin = int16(u16(glyf, 4))
|
||||
g.B.XMax = int16(u16(glyf, 6))
|
||||
g.B.YMax = int16(u16(glyf, 8))
|
||||
offset := 10
|
||||
if ne == -1 {
|
||||
return g.loadCompound(f, d, recursion)
|
||||
return g.loadCompound(f, glyf, offset, recursion)
|
||||
} else if ne < 0 {
|
||||
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that
|
||||
// "the values -2, -3, and so forth, are reserved for future use."
|
||||
|
@ -193,25 +201,26 @@ func (g *GlyphBuf) load(f *Font, i Index, recursion int) error {
|
|||
ne0, np0 := len(g.End), len(g.Point)
|
||||
ne += ne0
|
||||
if ne <= cap(g.End) {
|
||||
g.End = g.End[0:ne]
|
||||
g.End = g.End[:ne]
|
||||
} else {
|
||||
g.End = make([]int, ne, ne*2)
|
||||
}
|
||||
for i := ne0; i < ne; i++ {
|
||||
g.End[i] = 1 + np0 + int(d.u16())
|
||||
g.End[i] = 1 + np0 + int(u16(glyf, offset))
|
||||
offset += 2
|
||||
}
|
||||
// Skip the TrueType hinting instructions.
|
||||
instrLen := int(d.u16())
|
||||
d.skip(instrLen)
|
||||
instrLen := int(u16(glyf, offset))
|
||||
offset += 2 + instrLen
|
||||
// Decode the points.
|
||||
np := int(g.End[ne-1])
|
||||
if np <= cap(g.Point) {
|
||||
g.Point = g.Point[0:np]
|
||||
g.Point = g.Point[:np]
|
||||
} else {
|
||||
g.Point = make([]Point, np, np*2)
|
||||
}
|
||||
d = g.decodeFlags(d, np0)
|
||||
g.decodeCoords(d, np0)
|
||||
offset = g.decodeFlags(glyf, offset, np0)
|
||||
g.decodeCoords(glyf, offset, np0)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -47,43 +47,23 @@ func (e UnsupportedError) Error() string {
|
|||
return "freetype: unsupported TrueType feature: " + string(e)
|
||||
}
|
||||
|
||||
// data interprets a byte slice as a stream of integer values.
|
||||
type data []byte
|
||||
|
||||
// u32 returns the next big-endian uint32.
|
||||
func (d *data) u32() uint32 {
|
||||
x := uint32((*d)[0])<<24 | uint32((*d)[1])<<16 | uint32((*d)[2])<<8 | uint32((*d)[3])
|
||||
*d = (*d)[4:]
|
||||
return x
|
||||
// u32 returns the big-endian uint32 at b[i:].
|
||||
func u32(b []byte, i int) uint32 {
|
||||
return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3])
|
||||
}
|
||||
|
||||
// u16 returns the next big-endian uint16.
|
||||
func (d *data) u16() uint16 {
|
||||
x := uint16((*d)[0])<<8 | uint16((*d)[1])
|
||||
*d = (*d)[2:]
|
||||
return x
|
||||
}
|
||||
|
||||
// u8 returns the next uint8.
|
||||
func (d *data) u8() uint8 {
|
||||
x := (*d)[0]
|
||||
*d = (*d)[1:]
|
||||
return x
|
||||
}
|
||||
|
||||
// skip skips the next n bytes.
|
||||
func (d *data) skip(n int) {
|
||||
*d = (*d)[n:]
|
||||
// u16 returns the big-endian uint16 at b[i:].
|
||||
func u16(b []byte, i int) uint16 {
|
||||
return uint16(b[i])<<8 | uint16(b[i+1])
|
||||
}
|
||||
|
||||
// readTable returns a slice of the TTF data given by a table's directory entry.
|
||||
func readTable(ttf []byte, offsetLength []byte) ([]byte, error) {
|
||||
d := data(offsetLength)
|
||||
offset := int(d.u32())
|
||||
offset := int(u32(offsetLength, 0))
|
||||
if offset < 0 {
|
||||
return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset)))
|
||||
}
|
||||
length := int(d.u32())
|
||||
length := int(u32(offsetLength, 4))
|
||||
if length < 0 {
|
||||
return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length)))
|
||||
}
|
||||
|
@ -134,16 +114,16 @@ func (f *Font) parseCmap() error {
|
|||
if len(f.cmap) < 4 {
|
||||
return FormatError("cmap too short")
|
||||
}
|
||||
d := data(f.cmap[2:])
|
||||
nsubtab := int(d.u16())
|
||||
nsubtab := int(u16(f.cmap, 2))
|
||||
if len(f.cmap) < 8*nsubtab+4 {
|
||||
return FormatError("cmap too short")
|
||||
}
|
||||
offset, found := 0, false
|
||||
offset, found, x := 0, false, 4
|
||||
for i := 0; i < nsubtab; i++ {
|
||||
// We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32.
|
||||
// All values are big-endian.
|
||||
pidPsid, o := d.u32(), d.u32()
|
||||
pidPsid, o := u32(f.cmap, x), u32(f.cmap, x+4)
|
||||
x += 8
|
||||
// We prefer the Unicode cmap encoding. Failing to find that, we fall
|
||||
// back onto the Microsoft cmap encoding.
|
||||
if pidPsid == unicodeEncoding {
|
||||
|
@ -161,37 +141,39 @@ func (f *Font) parseCmap() error {
|
|||
return FormatError("bad cmap offset")
|
||||
}
|
||||
|
||||
d = data(f.cmap[offset:])
|
||||
cmapFormat := d.u16()
|
||||
cmapFormat := u16(f.cmap, offset)
|
||||
if cmapFormat != cmapFormat4 {
|
||||
return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat))
|
||||
}
|
||||
d.skip(2)
|
||||
language := d.u16()
|
||||
language := u16(f.cmap, offset+4)
|
||||
if language != languageIndependent {
|
||||
return UnsupportedError(fmt.Sprintf("language: %d", language))
|
||||
}
|
||||
segCountX2 := int(d.u16())
|
||||
segCountX2 := int(u16(f.cmap, offset+6))
|
||||
if segCountX2%2 == 1 {
|
||||
return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2))
|
||||
}
|
||||
segCount := segCountX2 / 2
|
||||
d.skip(6)
|
||||
offset += 14
|
||||
f.cm = make([]cm, segCount)
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].end = d.u16()
|
||||
f.cm[i].end = u16(f.cmap, offset)
|
||||
offset += 2
|
||||
}
|
||||
d.skip(2)
|
||||
offset += 2
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].start = d.u16()
|
||||
f.cm[i].start = u16(f.cmap, offset)
|
||||
offset += 2
|
||||
}
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].delta = d.u16()
|
||||
f.cm[i].delta = u16(f.cmap, offset)
|
||||
offset += 2
|
||||
}
|
||||
for i := 0; i < segCount; i++ {
|
||||
f.cm[i].offset = d.u16()
|
||||
f.cm[i].offset = u16(f.cmap, offset)
|
||||
offset += 2
|
||||
}
|
||||
f.cmapIndexes = []byte(d)
|
||||
f.cmapIndexes = f.cmap[offset:]
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -199,15 +181,12 @@ func (f *Font) parseHead() error {
|
|||
if len(f.head) != 54 {
|
||||
return FormatError(fmt.Sprintf("bad head length: %d", len(f.head)))
|
||||
}
|
||||
d := data(f.head[18:])
|
||||
f.unitsPerEm = int(d.u16())
|
||||
d.skip(16)
|
||||
f.bounds.XMin = int16(d.u16())
|
||||
f.bounds.YMin = int16(d.u16())
|
||||
f.bounds.XMax = int16(d.u16())
|
||||
f.bounds.YMax = int16(d.u16())
|
||||
d.skip(6)
|
||||
switch i := d.u16(); i {
|
||||
f.unitsPerEm = int(u16(f.head, 18))
|
||||
f.bounds.XMin = int16(u16(f.head, 36))
|
||||
f.bounds.YMin = int16(u16(f.head, 38))
|
||||
f.bounds.XMax = int16(u16(f.head, 40))
|
||||
f.bounds.YMax = int16(u16(f.head, 42))
|
||||
switch i := u16(f.head, 50); i {
|
||||
case 0:
|
||||
f.locaOffsetFormat = locaOffsetFormatShort
|
||||
case 1:
|
||||
|
@ -222,8 +201,7 @@ func (f *Font) parseHhea() error {
|
|||
if len(f.hhea) != 36 {
|
||||
return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea)))
|
||||
}
|
||||
d := data(f.hhea[34:])
|
||||
f.nHMetric = int(d.u16())
|
||||
f.nHMetric = int(u16(f.hhea, 34))
|
||||
if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) {
|
||||
return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx)))
|
||||
}
|
||||
|
@ -249,23 +227,22 @@ func (f *Font) parseKern() error {
|
|||
if len(f.kern) < 18 {
|
||||
return FormatError("kern data too short")
|
||||
}
|
||||
d := data(f.kern[0:])
|
||||
version := d.u16()
|
||||
version, offset := u16(f.kern, 0), 2
|
||||
if version != 0 {
|
||||
return UnsupportedError(fmt.Sprintf("kern version: %d", version))
|
||||
}
|
||||
n := d.u16()
|
||||
n, offset := u16(f.kern, offset), offset+2
|
||||
if n != 1 {
|
||||
return UnsupportedError(fmt.Sprintf("kern nTables: %d", n))
|
||||
}
|
||||
d.skip(2)
|
||||
length := int(d.u16())
|
||||
coverage := d.u16()
|
||||
offset += 2
|
||||
length, offset := int(u16(f.kern, offset)), offset+2
|
||||
coverage, offset := u16(f.kern, offset), offset+2
|
||||
if coverage != 0x0001 {
|
||||
// We only support horizontal kerning.
|
||||
return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage))
|
||||
}
|
||||
f.nKern = int(d.u16())
|
||||
f.nKern, offset = int(u16(f.kern, offset)), offset+2
|
||||
if 6*f.nKern != length-14 {
|
||||
return FormatError("bad kern table length")
|
||||
}
|
||||
|
@ -276,8 +253,7 @@ func (f *Font) parseMaxp() error {
|
|||
if len(f.maxp) != 32 {
|
||||
return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp)))
|
||||
}
|
||||
d := data(f.maxp[4:])
|
||||
f.nGlyph = int(d.u16())
|
||||
f.nGlyph = int(u16(f.maxp, 4))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -301,11 +277,10 @@ func (f *Font) Index(x rune) Index {
|
|||
return Index(c + f.cm[i].delta)
|
||||
}
|
||||
offset := int(f.cm[i].offset) + 2*(i-n+int(c-f.cm[i].start))
|
||||
d := data(f.cmapIndexes[offset:])
|
||||
return Index(d.u16())
|
||||
return Index(u16(f.cmapIndexes, offset))
|
||||
}
|
||||
}
|
||||
return Index(0)
|
||||
return 0
|
||||
}
|
||||
|
||||
// HMetric returns the horizontal metrics for the glyph with the given index.
|
||||
|
@ -315,17 +290,13 @@ func (f *Font) HMetric(i Index) HMetric {
|
|||
return HMetric{}
|
||||
}
|
||||
if j >= f.nHMetric {
|
||||
var hm HMetric
|
||||
p := 4 * (f.nHMetric - 1)
|
||||
d := data(f.hmtx[p:])
|
||||
hm.AdvanceWidth = d.u16()
|
||||
p += 2*(j-f.nHMetric) + 4
|
||||
d = data(f.hmtx[p:])
|
||||
hm.LeftSideBearing = int16(d.u16())
|
||||
return hm
|
||||
return HMetric{
|
||||
u16(f.hmtx, p),
|
||||
int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4)),
|
||||
}
|
||||
}
|
||||
d := data(f.hmtx[4*j:])
|
||||
return HMetric{d.u16(), int16(d.u16())}
|
||||
return HMetric{u16(f.hmtx, 4*j), int16(u16(f.hmtx, 4*j+2))}
|
||||
}
|
||||
|
||||
// Kerning returns the kerning for the given glyph pair.
|
||||
|
@ -337,14 +308,13 @@ func (f *Font) Kerning(i0, i1 Index) int16 {
|
|||
lo, hi := 0, f.nKern
|
||||
for lo < hi {
|
||||
i := (lo + hi) / 2
|
||||
d := data(f.kern[18+6*i:])
|
||||
ig := d.u32()
|
||||
ig := u32(f.kern, 18+6*i)
|
||||
if ig < g {
|
||||
lo = i + 1
|
||||
} else if ig > g {
|
||||
hi = i
|
||||
} else {
|
||||
return int16(d.u16())
|
||||
return int16(u16(f.kern, 22+6*i))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
|
@ -362,33 +332,35 @@ func parse(ttf []byte, offset int) (font *Font, err error) {
|
|||
err = FormatError("TTF data is too short")
|
||||
return
|
||||
}
|
||||
d := data(ttf[offset:])
|
||||
switch d.u32() {
|
||||
originalOffset := offset
|
||||
magic, offset := u32(ttf, offset), offset+4
|
||||
switch magic {
|
||||
case 0x00010000:
|
||||
// No-op.
|
||||
case 0x74746366: // "ttcf" as a big-endian uint32.
|
||||
if offset != 0 {
|
||||
if originalOffset != 0 {
|
||||
err = FormatError("recursive TTC")
|
||||
return
|
||||
}
|
||||
if d.u32() != 0x00010000 {
|
||||
ttcVersion, offset := u32(ttf, offset), offset+4
|
||||
if ttcVersion != 0x00010000 {
|
||||
// TODO: support TTC version 2.0, once I have such a .ttc file to test with.
|
||||
err = FormatError("bad TTC version")
|
||||
return
|
||||
}
|
||||
numFonts := int(d.u32())
|
||||
numFonts, offset := int(u32(ttf, offset)), offset+4
|
||||
if numFonts <= 0 {
|
||||
err = FormatError("bad number of TTC fonts")
|
||||
return
|
||||
}
|
||||
if len(d)/4 < numFonts {
|
||||
if len(ttf[offset:])/4 < numFonts {
|
||||
err = FormatError("TTC offset table is too short")
|
||||
return
|
||||
}
|
||||
// TODO: provide an API to select which font in a TrueType collection to return,
|
||||
// not just the first one. This may require an API to parse a TTC's name tables,
|
||||
// so users of this package can select the font in a TTC by name.
|
||||
offset := int(d.u32())
|
||||
offset = int(u32(ttf, offset))
|
||||
if offset <= 0 || offset > len(ttf) {
|
||||
err = FormatError("bad TTC offset")
|
||||
return
|
||||
|
@ -398,7 +370,7 @@ func parse(ttf []byte, offset int) (font *Font, err error) {
|
|||
err = FormatError("bad TTF version")
|
||||
return
|
||||
}
|
||||
n := int(d.u16())
|
||||
n, offset := int(u16(ttf, offset)), offset+2
|
||||
if len(ttf) < 16*n+12 {
|
||||
err = FormatError("TTF data is too short")
|
||||
return
|
||||
|
|
69
freetype/truetype/truetype_test.go
Normal file
69
freetype/truetype/truetype_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2012 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2 (or
|
||||
// any later version), both of which can be found in the LICENSE file.
|
||||
|
||||
package truetype
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly.
|
||||
// The numerical values can be manually verified by examining luxisr.ttx.
|
||||
func TestParse(t *testing.T) {
|
||||
b, err := ioutil.ReadFile("../../luxi-fonts/luxisr.ttf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
font, err := Parse(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := font.Bounds(), (Bounds{-441, -432, 2024, 2033}); got != want {
|
||||
t.Errorf("Bounds: got %v, want %v", got, want)
|
||||
}
|
||||
if got, want := font.UnitsPerEm(), 2048; got != want {
|
||||
t.Errorf("UnitsPerEm: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
i0 := font.Index('A')
|
||||
i1 := font.Index('V')
|
||||
if i0 != 36 || i1 != 57 {
|
||||
t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1)
|
||||
}
|
||||
if got, want := font.HMetric(i0), (HMetric{1366, 19}); got != want {
|
||||
t.Errorf("HMetric: got %v, want %v", got, want)
|
||||
}
|
||||
if got, want := font.Kerning(i0, i1), int16(-144); got != want {
|
||||
t.Errorf("Kerning: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
g0 := NewGlyphBuf()
|
||||
err = g0.Load(font, i0)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
g1 := &GlyphBuf{
|
||||
B: Bounds{19, 0, 1342, 1480},
|
||||
Point: []Point{
|
||||
{19, 0, 51},
|
||||
{581, 1480, 1},
|
||||
{789, 1480, 51},
|
||||
{1342, 0, 1},
|
||||
{1116, 0, 35},
|
||||
{962, 410, 3},
|
||||
{368, 410, 33},
|
||||
{214, 0, 3},
|
||||
{428, 566, 19},
|
||||
{904, 566, 33},
|
||||
{667, 1200, 3},
|
||||
},
|
||||
End: []int{8, 11},
|
||||
}
|
||||
if got, want := fmt.Sprint(g0), fmt.Sprint(g1); got != want {
|
||||
t.Errorf("GlyphBuf:\ngot %v\nwant %v", got, want)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue