freetype/truetype: implement ALIGNRP, MDAP and MDRP opcodes.

We can now hint the .notdef glyph from luxisr.ttf. Yay.

R=bsiegert
CC=golang-dev
https://codereview.appspot.com/12829048
This commit is contained in:
Nigel Tao 2013-08-31 16:08:40 +10:00
parent a8a5cfeb78
commit a3c53fdc3f
5 changed files with 272 additions and 70 deletions

View File

@ -19,8 +19,13 @@ type Point struct {
type GlyphBuf struct { type GlyphBuf struct {
// The glyph's bounding box. // The glyph's bounding box.
B Bounds B Bounds
// Point contains all Points from all contours of the glyph. // Point contains all Points from all contours of the glyph. If a
Point []Point // Hinter was used to load a glyph then Unhinted contains those
// Points before they were hinted, and InFontUnits contains those
// Points before they were hinted and scaled. Twilight is those
// Points created in the 'twilight zone' by the truetype hinting
// process.
Point, Unhinted, InFontUnits, Twilight []Point
// The length of End is the number of contours in the glyph. The i'th // The length of End is the number of contours in the glyph. The i'th
// contour consists of points Point[End[i-1]:End[i]], where End[-1] // contour consists of points Point[End[i-1]:End[i]], where End[-1]
// is interpreted to mean zero. // is interpreted to mean zero.
@ -36,6 +41,10 @@ const (
flagRepeat flagRepeat
flagPositiveXShortVector flagPositiveXShortVector
flagPositiveYShortVector flagPositiveYShortVector
// The remaining flags are for internal use.
flagTouchedX
flagTouchedY
) )
// The same flag bits (0x10 and 0x20) are overloaded to have two meanings, // The same flag bits (0x10 and 0x20) are overloaded to have two meanings,
@ -112,25 +121,27 @@ func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error {
// Reset the GlyphBuf. // Reset the GlyphBuf.
g.B = Bounds{} g.B = Bounds{}
g.Point = g.Point[:0] g.Point = g.Point[:0]
g.Unhinted = g.Unhinted[:0]
g.InFontUnits = g.InFontUnits[:0]
g.Twilight = g.Twilight[:0]
g.End = g.End[:0] g.End = g.End[:0]
if err := g.load(f, scale, i, 0, 0, false, 0); err != nil { if h != nil {
if err := h.init(g, f, scale); err != nil {
return err
}
}
if err := g.load(f, scale, i, h, 0, 0, false, 0); err != nil {
return err return err
} }
g.B.XMin = f.scale(scale * g.B.XMin) g.B.XMin = f.scale(scale * g.B.XMin)
g.B.YMin = f.scale(scale * g.B.YMin) g.B.YMin = f.scale(scale * g.B.YMin)
g.B.XMax = f.scale(scale * g.B.XMax) g.B.XMax = f.scale(scale * g.B.XMax)
g.B.YMax = f.scale(scale * g.B.YMax) g.B.YMax = f.scale(scale * g.B.YMax)
if h != nil {
if err := h.init(f, scale); err != nil {
return err
}
// TODO: invoke h.
}
return nil return nil
} }
// loadCompound loads a glyph that is composed of other glyphs. // loadCompound loads a glyph that is composed of other glyphs.
func (g *GlyphBuf) loadCompound(f *Font, scale int32, glyf []byte, offset int, func (g *GlyphBuf) loadCompound(f *Font, scale int32, h *Hinter, glyf []byte, offset int,
dx, dy int32, recursion int) error { dx, dy int32, recursion int) error {
// Flags for decoding a compound glyph. These flags are documented at // Flags for decoding a compound glyph. These flags are documented at
@ -150,7 +161,7 @@ func (g *GlyphBuf) loadCompound(f *Font, scale int32, glyf []byte, offset int,
) )
for { for {
flags := u16(glyf, offset) flags := u16(glyf, offset)
component := u16(glyf, offset+2) component := Index(u16(glyf, offset+2))
dx1, dy1 := dx, dy dx1, dy1 := dx, dy
if flags&flagArg1And2AreWords != 0 { if flags&flagArg1And2AreWords != 0 {
dx1 += int32(int16(u16(glyf, offset+4))) dx1 += int32(int16(u16(glyf, offset+4)))
@ -168,7 +179,7 @@ func (g *GlyphBuf) loadCompound(f *Font, scale int32, glyf []byte, offset int,
return UnsupportedError("compound glyph scale/transform") return UnsupportedError("compound glyph scale/transform")
} }
b0 := g.B b0 := g.B
g.load(f, scale, Index(component), dx1, dy1, flags&flagRoundXYToGrid != 0, recursion+1) g.load(f, scale, component, h, dx1, dy1, flags&flagRoundXYToGrid != 0, recursion+1)
if flags&flagUseMyMetrics == 0 { if flags&flagUseMyMetrics == 0 {
g.B = b0 g.B = b0
} }
@ -180,7 +191,7 @@ func (g *GlyphBuf) loadCompound(f *Font, scale int32, glyf []byte, offset int,
} }
// load appends a glyph's contours to this GlyphBuf. // load appends a glyph's contours to this GlyphBuf.
func (g *GlyphBuf) load(f *Font, scale int32, i Index, func (g *GlyphBuf) load(f *Font, scale int32, i Index, h *Hinter,
dx, dy int32, roundDxDy bool, recursion int) error { dx, dy int32, roundDxDy bool, recursion int) error {
if recursion >= 4 { if recursion >= 4 {
@ -207,7 +218,7 @@ func (g *GlyphBuf) load(f *Font, scale int32, i Index,
g.B.YMax = int32(int16(u16(glyf, 8))) g.B.YMax = int32(int16(u16(glyf, 8)))
offset := 10 offset := 10
if ne == -1 { if ne == -1 {
return g.loadCompound(f, scale, glyf, offset, dx, dy, recursion) return g.loadCompound(f, scale, h, glyf, offset, dx, dy, recursion)
} else if ne < 0 { } else if ne < 0 {
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that // http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that
// "the values -2, -3, and so forth, are reserved for future use." // "the values -2, -3, and so forth, are reserved for future use."
@ -224,18 +235,33 @@ func (g *GlyphBuf) load(f *Font, scale int32, i Index,
g.End[i] = 1 + np0 + int(u16(glyf, offset)) g.End[i] = 1 + np0 + int(u16(glyf, offset))
offset += 2 offset += 2
} }
// Skip the TrueType hinting instructions.
// Note the TrueType hinting instructions.
instrLen := int(u16(glyf, offset)) instrLen := int(u16(glyf, offset))
offset += 2 + instrLen offset += 2
program := glyf[offset : offset+instrLen]
offset += instrLen
// Decode the points. // Decode the points.
np := int(g.End[ne-1]) np := int(g.End[ne-1])
if np <= cap(g.Point) { if np <= cap(g.Point) {
g.Point = g.Point[:np] g.Point = g.Point[:np]
} else { } else {
p := g.Point
g.Point = make([]Point, np, np*2) g.Point = make([]Point, np, np*2)
copy(g.Point, p)
} }
offset = g.decodeFlags(glyf, offset, np0) offset = g.decodeFlags(glyf, offset, np0)
g.decodeCoords(glyf, offset, np0) g.decodeCoords(glyf, offset, np0)
// Delta-adjust, scale and hint.
if h != nil {
g.InFontUnits = append(g.InFontUnits, g.Point[np0:np]...)
for i := np0; i < np; i++ {
g.InFontUnits[i].X += dx
g.InFontUnits[i].Y += dy
}
}
if roundDxDy { if roundDxDy {
dx = (f.scale(scale*dx) + 32) &^ 63 dx = (f.scale(scale*dx) + 32) &^ 63
dy = (f.scale(scale*dy) + 32) &^ 63 dy = (f.scale(scale*dy) + 32) &^ 63
@ -249,9 +275,23 @@ func (g *GlyphBuf) load(f *Font, scale int32, i Index,
g.Point[i].Y = f.scale(scale * (g.Point[i].Y + dy)) g.Point[i].Y = f.scale(scale * (g.Point[i].Y + dy))
} }
} }
if h != nil {
g.Unhinted = append(g.Unhinted, g.Point[np0:np]...)
if err := h.run(program); err != nil {
return err
}
}
return nil return nil
} }
func (g *GlyphBuf) points(zonePointer int32) []Point {
if zonePointer == 0 {
return g.Twilight
}
return g.Point
}
// NewGlyphBuf returns a newly allocated GlyphBuf. // NewGlyphBuf returns a newly allocated GlyphBuf.
func NewGlyphBuf() *GlyphBuf { func NewGlyphBuf() *GlyphBuf {
g := new(GlyphBuf) g := new(GlyphBuf)

View File

@ -28,9 +28,11 @@ type Hinter struct {
// functions is a map from function number to bytecode. // functions is a map from function number to bytecode.
functions map[int32][]byte functions map[int32][]byte
// font and scale are the font and scale last used for this Hinter. // g, font and scale are the glyph buffer, font and scale last used for
// Changing the font will require running the new font's fpgm bytecode. // this Hinter. Changing the font will require running the new font's
// Changing either will require running the font's prep bytecode. // fpgm bytecode. Changing either will require running the font's prep
// bytecode.
g *GlyphBuf
font *Font font *Font
scale int32 scale int32
@ -75,7 +77,9 @@ var globalDefaultGS = graphicsState{
autoFlip: true, autoFlip: true,
} }
func (h *Hinter) init(f *Font, scale int32) error { func (h *Hinter) init(g *GlyphBuf, f *Font, scale int32) error {
h.g = g
rescale := h.scale != scale rescale := h.scale != scale
if h.font != f { if h.font != f {
h.font, rescale = f, true h.font, rescale = f, true
@ -158,20 +162,20 @@ func (h *Hinter) run(program []byte) error {
case opSVTCA0: case opSVTCA0:
h.gs.pv = [2]f2dot14{0, 0x4000} h.gs.pv = [2]f2dot14{0, 0x4000}
h.gs.fv = [2]f2dot14{0, 0x4000} h.gs.fv = [2]f2dot14{0, 0x4000}
// TODO: h.gs.dv = h.gs.pv ?? h.gs.dv = [2]f2dot14{0, 0x4000}
case opSVTCA1: case opSVTCA1:
h.gs.pv = [2]f2dot14{0x4000, 0} h.gs.pv = [2]f2dot14{0x4000, 0}
h.gs.fv = [2]f2dot14{0x4000, 0} h.gs.fv = [2]f2dot14{0x4000, 0}
// TODO: h.gs.dv = h.gs.pv ?? h.gs.dv = [2]f2dot14{0x4000, 0}
case opSPVTCA0: case opSPVTCA0:
h.gs.pv = [2]f2dot14{0, 0x4000} h.gs.pv = [2]f2dot14{0, 0x4000}
// TODO: h.gs.dv = h.gs.pv ?? h.gs.dv = [2]f2dot14{0, 0x4000}
case opSPVTCA1: case opSPVTCA1:
h.gs.pv = [2]f2dot14{0x4000, 0} h.gs.pv = [2]f2dot14{0x4000, 0}
// TODO: h.gs.dv = h.gs.pv ?? h.gs.dv = [2]f2dot14{0x4000, 0}
case opSFVTCA0: case opSFVTCA0:
h.gs.fv = [2]f2dot14{0, 0x4000} h.gs.fv = [2]f2dot14{0, 0x4000}
@ -360,6 +364,45 @@ func (h *Hinter) run(program []byte) error {
} }
program, pc = callStack[callStackTop].program, callStack[callStackTop].pc program, pc = callStack[callStackTop].program, callStack[callStackTop].pc
case opMDAP0, opMDAP1:
points := h.g.points(h.gs.zp[0])
top--
i := int(h.stack[top])
if i < 0 || len(points) <= i {
return errors.New("truetype: hinting: point out of range")
}
p := &points[i]
distance := f26dot6(0)
if opcode == opMDAP1 {
distance = dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv)
// TODO: metrics compensation.
distance = h.round(distance) - distance
}
h.move(p, distance)
h.gs.rp[0] = int32(i)
h.gs.rp[1] = int32(i)
case opALIGNRP:
if top < int(h.gs.loop) {
return errors.New("truetype: hinting: stack underflow")
}
i, points := int(h.gs.rp[0]), h.g.points(h.gs.zp[0])
if i < 0 || len(points) <= i {
return errors.New("truetype: hinting: point out of range")
}
ref := &points[i]
points = h.g.points(h.gs.zp[1])
for ; h.gs.loop != 0; h.gs.loop-- {
top--
i = int(h.stack[top])
if i < 0 || len(points) <= i {
return errors.New("truetype: hinting: point out of range")
}
p := &points[i]
h.move(p, -dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv))
}
h.gs.loop = 1
case opRTDG: case opRTDG:
h.gs.roundPeriod = 1 << 5 h.gs.roundPeriod = 1 << 5
h.gs.roundPhase = 0 h.gs.roundPhase = 0
@ -617,6 +660,80 @@ func (h *Hinter) run(program []byte) error {
opcode += 0x80 opcode += 0x80
goto push goto push
case opMDRP00000, opMDRP00001, opMDRP00010, opMDRP00011,
opMDRP00100, opMDRP00101, opMDRP00110, opMDRP00111,
opMDRP01000, opMDRP01001, opMDRP01010, opMDRP01011,
opMDRP01100, opMDRP01101, opMDRP01110, opMDRP01111,
opMDRP10000, opMDRP10001, opMDRP10010, opMDRP10011,
opMDRP10100, opMDRP10101, opMDRP10110, opMDRP10111,
opMDRP11000, opMDRP11001, opMDRP11010, opMDRP11011,
opMDRP11100, opMDRP11101, opMDRP11110, opMDRP11111:
i, points := int(h.gs.rp[0]), h.g.points(h.gs.zp[0])
if i < 0 || len(points) <= i {
return errors.New("truetype: hinting: point out of range")
}
ref := &points[i]
top--
i = int(h.stack[top])
points = h.g.points(h.gs.zp[1])
if i < 0 || len(points) <= i {
return errors.New("truetype: hinting: point out of range")
}
p := &points[i]
origDist := f26dot6(0)
if h.gs.zp[0] == 0 && h.gs.zp[1] == 0 {
p0 := &h.g.Unhinted[i]
p1 := &h.g.Unhinted[h.gs.rp[0]]
origDist = dotProduct(f26dot6(p0.X-p1.X), f26dot6(p0.Y-p1.Y), h.gs.dv)
} else {
p0 := &h.g.InFontUnits[i]
p1 := &h.g.InFontUnits[h.gs.rp[0]]
origDist = dotProduct(f26dot6(p0.X-p1.X), f26dot6(p0.Y-p1.Y), h.gs.dv)
origDist = f26dot6(h.font.scale(h.scale * int32(origDist)))
}
// Single-width cut-in test.
if x := (origDist - h.gs.singleWidth).abs(); x < h.gs.singleWidthCutIn {
if origDist >= 0 {
origDist = h.gs.singleWidthCutIn
} else {
origDist = -h.gs.singleWidthCutIn
}
}
// Rounding bit.
// TODO: metrics compensation.
distance := origDist
if opcode&0x04 != 0 {
distance = h.round(origDist)
}
// Minimum distance bit.
if opcode&0x08 != 0 {
if origDist >= 0 {
if distance < h.gs.minDist {
distance = h.gs.minDist
}
} else {
if distance > -h.gs.minDist {
distance = -h.gs.minDist
}
}
}
// Set-RP0 bit.
if opcode&0x10 != 0 {
h.gs.rp[0] = int32(i)
}
h.gs.rp[1] = h.gs.rp[0]
h.gs.rp[2] = int32(i)
// Move the point.
origDist = dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv)
h.move(p, distance-origDist)
default: default:
return errors.New("truetype: hinting: unrecognized instruction") return errors.New("truetype: hinting: unrecognized instruction")
} }
@ -698,6 +815,27 @@ func (h *Hinter) run(program []byte) error {
return nil return nil
} }
func (h *Hinter) move(p *Point, distance f26dot6) {
if h.gs.fv[0] == 0 {
p.Y += int32(distance)
p.Flags |= flagTouchedY
return
}
if h.gs.fv[1] == 0 {
p.X += int32(distance)
p.Flags |= flagTouchedX
return
}
fvx := int64(h.gs.fv[0])
fvy := int64(h.gs.fv[1])
pvx := int64(h.gs.pv[0])
pvy := int64(h.gs.pv[1])
fvDotPv := (fvx*pvx + fvy*pvy) >> 14
p.X += int32(int64(distance) * fvx / fvDotPv)
p.Y += int32(int64(distance) * fvy / fvDotPv)
p.Flags |= flagTouchedX | flagTouchedY
}
// skipInstructionPayload increments pc by the extra data that follows a // skipInstructionPayload increments pc by the extra data that follows a
// variable length PUSHB or PUSHW instruction. // variable length PUSHB or PUSHW instruction.
func skipInstructionPayload(program []byte, pc int) (newPC int, ok bool) { func skipInstructionPayload(program []byte, pc int) (newPC int, ok bool) {
@ -730,6 +868,14 @@ type f2dot14 int16
// f26dot6 is a 26.6 fixed point number. // f26dot6 is a 26.6 fixed point number.
type f26dot6 int32 type f26dot6 int32
// abs returns abs(x) in 26.6 fixed point arithmetic.
func (x f26dot6) abs() f26dot6 {
if x < 0 {
return -x
}
return x
}
// div returns x/y in 26.6 fixed point arithmetic. // div returns x/y in 26.6 fixed point arithmetic.
func (x f26dot6) div(y f26dot6) f26dot6 { func (x f26dot6) div(y f26dot6) f26dot6 {
return f26dot6((int64(x) << 6) / int64(y)) return f26dot6((int64(x) << 6) / int64(y))
@ -740,6 +886,14 @@ func (x f26dot6) mul(y f26dot6) f26dot6 {
return f26dot6(int64(x) * int64(y) >> 6) return f26dot6(int64(x) * int64(y) >> 6)
} }
func dotProduct(x, y f26dot6, q [2]f2dot14) f26dot6 {
px := int64(x)
py := int64(y)
qx := int64(q[0])
qy := int64(q[1])
return f26dot6((px*qx + py*qy) >> 14)
}
// round rounds the given number. The rounding algorithm is described at // round rounds the given number. The rounding algorithm is described at
// https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding // https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
func (h *Hinter) round(x f26dot6) f26dot6 { func (h *Hinter) round(x f26dot6) f26dot6 {

View File

@ -553,9 +553,10 @@ func TestBytecode(t *testing.T) {
}, },
} }
var g GlyphBuf
for _, tc := range testCases { for _, tc := range testCases {
h := &Hinter{} h := &Hinter{}
h.init(&Font{ h.init(&g, &Font{
maxStorage: 32, maxStorage: 32,
maxStackElements: 100, maxStackElements: 100,
}, 768) }, 768)

View File

@ -56,8 +56,8 @@ const (
opCALL = 0x2b // CALL function opCALL = 0x2b // CALL function
opFDEF = 0x2c // Function DEFinition opFDEF = 0x2c // Function DEFinition
opENDF = 0x2d // END Function definition opENDF = 0x2d // END Function definition
opMDAP0 = 0x2e opMDAP0 = 0x2e // Move Direct Absolute Point
opMDAP1 = 0x2f opMDAP1 = 0x2f // .
opIUP0 = 0x30 opIUP0 = 0x30
opIUP1 = 0x31 opIUP1 = 0x31
opSHP0 = 0x32 opSHP0 = 0x32
@ -70,7 +70,7 @@ const (
opIP = 0x39 opIP = 0x39
opMSIRP0 = 0x3a opMSIRP0 = 0x3a
opMSIRP1 = 0x3b opMSIRP1 = 0x3b
opALIGNRP = 0x3c opALIGNRP = 0x3c // ALIGN to Reference Point
opRTDG = 0x3d // Round To Double Grid opRTDG = 0x3d // Round To Double Grid
opMIAP0 = 0x3e opMIAP0 = 0x3e
opMIAP1 = 0x3f opMIAP1 = 0x3f
@ -202,38 +202,38 @@ const (
opPUSHW101 = 0xbd // . opPUSHW101 = 0xbd // .
opPUSHW110 = 0xbe // . opPUSHW110 = 0xbe // .
opPUSHW111 = 0xbf // . opPUSHW111 = 0xbf // .
opMDRP00000 = 0xc0 opMDRP00000 = 0xc0 // Move Direct Relative Point
opMDRP00001 = 0xc1 opMDRP00001 = 0xc1 // .
opMDRP00010 = 0xc2 opMDRP00010 = 0xc2 // .
opMDRP00011 = 0xc3 opMDRP00011 = 0xc3 // .
opMDRP00100 = 0xc4 opMDRP00100 = 0xc4 // .
opMDRP00101 = 0xc5 opMDRP00101 = 0xc5 // .
opMDRP00110 = 0xc6 opMDRP00110 = 0xc6 // .
opMDRP00111 = 0xc7 opMDRP00111 = 0xc7 // .
opMDRP01000 = 0xc8 opMDRP01000 = 0xc8 // .
opMDRP01001 = 0xc9 opMDRP01001 = 0xc9 // .
opMDRP01010 = 0xca opMDRP01010 = 0xca // .
opMDRP01011 = 0xcb opMDRP01011 = 0xcb // .
opMDRP01100 = 0xcc opMDRP01100 = 0xcc // .
opMDRP01101 = 0xcd opMDRP01101 = 0xcd // .
opMDRP01110 = 0xce opMDRP01110 = 0xce // .
opMDRP01111 = 0xcf opMDRP01111 = 0xcf // .
opMDRP10000 = 0xd0 opMDRP10000 = 0xd0 // .
opMDRP10001 = 0xd1 opMDRP10001 = 0xd1 // .
opMDRP10010 = 0xd2 opMDRP10010 = 0xd2 // .
opMDRP10011 = 0xd3 opMDRP10011 = 0xd3 // .
opMDRP10100 = 0xd4 opMDRP10100 = 0xd4 // .
opMDRP10101 = 0xd5 opMDRP10101 = 0xd5 // .
opMDRP10110 = 0xd6 opMDRP10110 = 0xd6 // .
opMDRP10111 = 0xd7 opMDRP10111 = 0xd7 // .
opMDRP11000 = 0xd8 opMDRP11000 = 0xd8 // .
opMDRP11001 = 0xd9 opMDRP11001 = 0xd9 // .
opMDRP11010 = 0xda opMDRP11010 = 0xda // .
opMDRP11011 = 0xdb opMDRP11011 = 0xdb // .
opMDRP11100 = 0xdd opMDRP11100 = 0xdc // .
opMDRP11101 = 0xdc opMDRP11101 = 0xdd // .
opMDRP11110 = 0xde opMDRP11110 = 0xde // .
opMDRP11111 = 0xdf opMDRP11111 = 0xdf // .
opMIRP00000 = 0xe0 opMIRP00000 = 0xe0
opMIRP00001 = 0xe1 opMIRP00001 = 0xe1
opMIRP00010 = 0xe2 opMIRP00010 = 0xe2
@ -262,8 +262,8 @@ const (
opMIRP11001 = 0xf9 opMIRP11001 = 0xf9
opMIRP11010 = 0xfa opMIRP11010 = 0xfa
opMIRP11011 = 0xfb opMIRP11011 = 0xfb
opMIRP11100 = 0xfd opMIRP11100 = 0xfc
opMIRP11101 = 0xfc opMIRP11101 = 0xfd
opMIRP11110 = 0xfe opMIRP11110 = 0xfe
opMIRP11111 = 0xff opMIRP11111 = 0xff
) )
@ -273,8 +273,8 @@ var popCount = [256]uint8{
// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f // 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
0, 0, 0, 0, 0, 0, q, q, q, q, 2, 2, 0, 0, 0, q, // 0x00 - 0x0f 0, 0, 0, 0, 0, 0, q, q, q, q, 2, 2, 0, 0, 0, q, // 0x00 - 0x0f
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f
1, 1, 0, 2, 0, 1, 1, q, q, q, 2, 1, 1, 0, q, q, // 0x20 - 0x2f 1, 1, 0, 2, 0, 1, 1, q, q, q, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f
q, q, q, q, q, q, q, q, q, q, q, q, q, 0, q, q, // 0x30 - 0x3f q, q, q, q, q, q, q, q, q, q, q, q, 0, 0, q, q, // 0x30 - 0x3f
0, 0, 2, 1, q, q, q, q, q, q, q, 0, 0, 0, 0, 0, // 0x40 - 0x4f 0, 0, 2, 1, q, q, q, q, q, q, q, 0, 0, 0, 0, 0, // 0x40 - 0x4f
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, q, 1, 1, // 0x50 - 0x5f 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, q, 1, 1, // 0x50 - 0x5f
2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f
@ -283,8 +283,8 @@ var popCount = [256]uint8{
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x90 - 0x9f q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x90 - 0x9f
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xa0 - 0xaf q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xa0 - 0xaf
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xc0 - 0xcf 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xd0 - 0xdf 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xe0 - 0xef q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xe0 - 0xef
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xf0 - 0xff q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xf0 - 0xff
} }

View File

@ -74,7 +74,7 @@ func TestParse(t *testing.T) {
} }
} }
func testScaling(t *testing.T, filename string) { func testScaling(t *testing.T, filename string, hinter *Hinter) {
b, err := ioutil.ReadFile("../../luxi-fonts/luxisr.ttf") b, err := ioutil.ReadFile("../../luxi-fonts/luxisr.ttf")
if err != nil { if err != nil {
t.Fatalf("ReadFile: %v", err) t.Fatalf("ReadFile: %v", err)
@ -114,7 +114,14 @@ func testScaling(t *testing.T, filename string) {
const fontSize = 12 const fontSize = 12
glyphBuf := NewGlyphBuf() glyphBuf := NewGlyphBuf()
for i, want := range wants { for i, want := range wants {
if err = glyphBuf.Load(font, fontSize*64, Index(i), nil); err != nil { // TODO: completely implement hinting. For now, only the first N glyphs
// of luxisr.ttf are correctly hinted.
const N = 1
if hinter != nil && i == N {
break
}
if err = glyphBuf.Load(font, fontSize*64, Index(i), hinter); err != nil {
t.Fatalf("Load: %v", err) t.Fatalf("Load: %v", err)
} }
got := glyphBuf.Point got := glyphBuf.Point
@ -128,9 +135,9 @@ func testScaling(t *testing.T, filename string) {
} }
func TestScalingSansHinting(t *testing.T) { func TestScalingSansHinting(t *testing.T) {
testScaling(t, "luxisr-12pt-sans-hinting.txt") testScaling(t, "luxisr-12pt-sans-hinting.txt", nil)
} }
func TestScalingWithHinting(t *testing.T) { func TestScalingWithHinting(t *testing.T) {
// TODO. testScaling(t, "luxisr-12pt-with-hinting.txt", &Hinter{})
} }