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:
parent
a8a5cfeb78
commit
a3c53fdc3f
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue