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 {
// The glyph's bounding box.
B Bounds
// Point contains all Points from all contours of the glyph.
Point []Point
// Point contains all Points from all contours of the glyph. If a
// 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
// contour consists of points Point[End[i-1]:End[i]], where End[-1]
// is interpreted to mean zero.
@ -36,6 +41,10 @@ const (
flagRepeat
flagPositiveXShortVector
flagPositiveYShortVector
// The remaining flags are for internal use.
flagTouchedX
flagTouchedY
)
// 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.
g.B = Bounds{}
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]
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
}
g.B.XMin = f.scale(scale * g.B.XMin)
g.B.YMin = f.scale(scale * g.B.YMin)
g.B.XMax = f.scale(scale * g.B.XMax)
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
}
// 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 {
// 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 {
flags := u16(glyf, offset)
component := u16(glyf, offset+2)
component := Index(u16(glyf, offset+2))
dx1, dy1 := dx, dy
if flags&flagArg1And2AreWords != 0 {
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")
}
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 {
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.
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 {
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)))
offset := 10
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 {
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that
// "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))
offset += 2
}
// Skip the TrueType hinting instructions.
// Note the TrueType hinting instructions.
instrLen := int(u16(glyf, offset))
offset += 2 + instrLen
offset += 2
program := glyf[offset : offset+instrLen]
offset += instrLen
// Decode the points.
np := int(g.End[ne-1])
if np <= cap(g.Point) {
g.Point = g.Point[:np]
} else {
p := g.Point
g.Point = make([]Point, np, np*2)
copy(g.Point, p)
}
offset = g.decodeFlags(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 {
dx = (f.scale(scale*dx) + 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))
}
}
if h != nil {
g.Unhinted = append(g.Unhinted, g.Point[np0:np]...)
if err := h.run(program); err != nil {
return err
}
}
return nil
}
func (g *GlyphBuf) points(zonePointer int32) []Point {
if zonePointer == 0 {
return g.Twilight
}
return g.Point
}
// NewGlyphBuf returns a newly allocated GlyphBuf.
func NewGlyphBuf() *GlyphBuf {
g := new(GlyphBuf)

View file

@ -28,9 +28,11 @@ type Hinter struct {
// functions is a map from function number to bytecode.
functions map[int32][]byte
// font and scale are the font and scale last used for this Hinter.
// Changing the font will require running the new font's fpgm bytecode.
// Changing either will require running the font's prep bytecode.
// g, font and scale are the glyph buffer, font and scale last used for
// this Hinter. Changing the font will require running the new font's
// fpgm bytecode. Changing either will require running the font's prep
// bytecode.
g *GlyphBuf
font *Font
scale int32
@ -75,7 +77,9 @@ var globalDefaultGS = graphicsState{
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
if h.font != f {
h.font, rescale = f, true
@ -158,20 +162,20 @@ func (h *Hinter) run(program []byte) error {
case opSVTCA0:
h.gs.pv = [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:
h.gs.pv = [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:
h.gs.pv = [2]f2dot14{0, 0x4000}
// TODO: h.gs.dv = h.gs.pv ??
h.gs.dv = [2]f2dot14{0, 0x4000}
case opSPVTCA1:
h.gs.pv = [2]f2dot14{0x4000, 0}
// TODO: h.gs.dv = h.gs.pv ??
h.gs.dv = [2]f2dot14{0x4000, 0}
case opSFVTCA0:
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
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:
h.gs.roundPeriod = 1 << 5
h.gs.roundPhase = 0
@ -617,6 +660,80 @@ func (h *Hinter) run(program []byte) error {
opcode += 0x80
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:
return errors.New("truetype: hinting: unrecognized instruction")
}
@ -698,6 +815,27 @@ func (h *Hinter) run(program []byte) error {
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
// variable length PUSHB or PUSHW instruction.
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.
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.
func (x f26dot6) div(y f26dot6) f26dot6 {
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)
}
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
// https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
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 {
h := &Hinter{}
h.init(&Font{
h.init(&g, &Font{
maxStorage: 32,
maxStackElements: 100,
}, 768)

View file

@ -56,8 +56,8 @@ const (
opCALL = 0x2b // CALL function
opFDEF = 0x2c // Function DEFinition
opENDF = 0x2d // END Function definition
opMDAP0 = 0x2e
opMDAP1 = 0x2f
opMDAP0 = 0x2e // Move Direct Absolute Point
opMDAP1 = 0x2f // .
opIUP0 = 0x30
opIUP1 = 0x31
opSHP0 = 0x32
@ -70,7 +70,7 @@ const (
opIP = 0x39
opMSIRP0 = 0x3a
opMSIRP1 = 0x3b
opALIGNRP = 0x3c
opALIGNRP = 0x3c // ALIGN to Reference Point
opRTDG = 0x3d // Round To Double Grid
opMIAP0 = 0x3e
opMIAP1 = 0x3f
@ -202,38 +202,38 @@ const (
opPUSHW101 = 0xbd // .
opPUSHW110 = 0xbe // .
opPUSHW111 = 0xbf // .
opMDRP00000 = 0xc0
opMDRP00001 = 0xc1
opMDRP00010 = 0xc2
opMDRP00011 = 0xc3
opMDRP00100 = 0xc4
opMDRP00101 = 0xc5
opMDRP00110 = 0xc6
opMDRP00111 = 0xc7
opMDRP01000 = 0xc8
opMDRP01001 = 0xc9
opMDRP01010 = 0xca
opMDRP01011 = 0xcb
opMDRP01100 = 0xcc
opMDRP01101 = 0xcd
opMDRP01110 = 0xce
opMDRP01111 = 0xcf
opMDRP10000 = 0xd0
opMDRP10001 = 0xd1
opMDRP10010 = 0xd2
opMDRP10011 = 0xd3
opMDRP10100 = 0xd4
opMDRP10101 = 0xd5
opMDRP10110 = 0xd6
opMDRP10111 = 0xd7
opMDRP11000 = 0xd8
opMDRP11001 = 0xd9
opMDRP11010 = 0xda
opMDRP11011 = 0xdb
opMDRP11100 = 0xdd
opMDRP11101 = 0xdc
opMDRP11110 = 0xde
opMDRP11111 = 0xdf
opMDRP00000 = 0xc0 // Move Direct Relative Point
opMDRP00001 = 0xc1 // .
opMDRP00010 = 0xc2 // .
opMDRP00011 = 0xc3 // .
opMDRP00100 = 0xc4 // .
opMDRP00101 = 0xc5 // .
opMDRP00110 = 0xc6 // .
opMDRP00111 = 0xc7 // .
opMDRP01000 = 0xc8 // .
opMDRP01001 = 0xc9 // .
opMDRP01010 = 0xca // .
opMDRP01011 = 0xcb // .
opMDRP01100 = 0xcc // .
opMDRP01101 = 0xcd // .
opMDRP01110 = 0xce // .
opMDRP01111 = 0xcf // .
opMDRP10000 = 0xd0 // .
opMDRP10001 = 0xd1 // .
opMDRP10010 = 0xd2 // .
opMDRP10011 = 0xd3 // .
opMDRP10100 = 0xd4 // .
opMDRP10101 = 0xd5 // .
opMDRP10110 = 0xd6 // .
opMDRP10111 = 0xd7 // .
opMDRP11000 = 0xd8 // .
opMDRP11001 = 0xd9 // .
opMDRP11010 = 0xda // .
opMDRP11011 = 0xdb // .
opMDRP11100 = 0xdc // .
opMDRP11101 = 0xdd // .
opMDRP11110 = 0xde // .
opMDRP11111 = 0xdf // .
opMIRP00000 = 0xe0
opMIRP00001 = 0xe1
opMIRP00010 = 0xe2
@ -262,8 +262,8 @@ const (
opMIRP11001 = 0xf9
opMIRP11010 = 0xfa
opMIRP11011 = 0xfb
opMIRP11100 = 0xfd
opMIRP11101 = 0xfc
opMIRP11100 = 0xfc
opMIRP11101 = 0xfd
opMIRP11110 = 0xfe
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
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, 0, 2, 0, 1, 1, q, q, q, 2, 1, 1, 0, q, q, // 0x20 - 0x2f
q, q, q, q, q, q, q, q, q, q, q, q, q, 0, q, q, // 0x30 - 0x3f
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, 0, 0, q, q, // 0x30 - 0x3f
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, 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, // 0xa0 - 0xaf
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
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, // 0xc0 - 0xcf
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, // 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")
if err != nil {
t.Fatalf("ReadFile: %v", err)
@ -114,7 +114,14 @@ func testScaling(t *testing.T, filename string) {
const fontSize = 12
glyphBuf := NewGlyphBuf()
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)
}
got := glyphBuf.Point
@ -128,9 +135,9 @@ func testScaling(t *testing.T, filename string) {
}
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) {
// TODO.
testScaling(t, "luxisr-12pt-with-hinting.txt", &Hinter{})
}