freetype/truetype: implement ISECT, SHC, WCVTF, DELTAC and SDPVTL
opcodes. R=bsiegert CC=golang-dev, remyoudompheng https://codereview.appspot.com/29510043
This commit is contained in:
parent
f6106a9f8b
commit
cef9ca89c7
|
@ -280,6 +280,55 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point,
|
||||||
case opSFVTPV:
|
case opSFVTPV:
|
||||||
h.gs.fv = h.gs.pv
|
h.gs.fv = h.gs.pv
|
||||||
|
|
||||||
|
case opISECT:
|
||||||
|
top -= 5
|
||||||
|
p := h.point(2, current, h.stack[top+0])
|
||||||
|
a0 := h.point(1, current, h.stack[top+1])
|
||||||
|
a1 := h.point(1, current, h.stack[top+2])
|
||||||
|
b0 := h.point(0, current, h.stack[top+3])
|
||||||
|
b1 := h.point(0, current, h.stack[top+4])
|
||||||
|
if p == nil || a0 == nil || a1 == nil || b0 == nil || b1 == nil {
|
||||||
|
return errors.New("truetype: hinting: point out of range")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbx := b1.X - b0.X
|
||||||
|
dby := b1.Y - b0.Y
|
||||||
|
dax := a1.X - a0.X
|
||||||
|
day := a1.Y - a0.Y
|
||||||
|
dx := b0.X - a0.X
|
||||||
|
dy := b0.Y - a0.Y
|
||||||
|
discriminant := mulDiv(int64(dax), int64(-dby), 0x40) +
|
||||||
|
mulDiv(int64(day), int64(dbx), 0x40)
|
||||||
|
dotProduct := mulDiv(int64(dax), int64(dbx), 0x40) +
|
||||||
|
mulDiv(int64(day), int64(dby), 0x40)
|
||||||
|
// The discriminant above is actually a cross product of vectors
|
||||||
|
// da and db. Together with the dot product, they can be used as
|
||||||
|
// surrogates for sine and cosine of the angle between the vectors.
|
||||||
|
// Indeed,
|
||||||
|
// dotproduct = |da||db|cos(angle)
|
||||||
|
// discriminant = |da||db|sin(angle)
|
||||||
|
// We use these equations to reject grazing intersections by
|
||||||
|
// thresholding abs(tan(angle)) at 1/19, corresponding to 3 degrees.
|
||||||
|
absDisc, absDotP := discriminant, dotProduct
|
||||||
|
if absDisc < 0 {
|
||||||
|
absDisc = -absDisc
|
||||||
|
}
|
||||||
|
if absDotP < 0 {
|
||||||
|
absDotP = -absDotP
|
||||||
|
}
|
||||||
|
if 19*absDisc > absDotP {
|
||||||
|
val := mulDiv(int64(dx), int64(-dby), 0x40) +
|
||||||
|
mulDiv(int64(dy), int64(dbx), 0x40)
|
||||||
|
rx := mulDiv(val, int64(dax), discriminant)
|
||||||
|
ry := mulDiv(val, int64(day), discriminant)
|
||||||
|
p.X = a0.X + int32(rx)
|
||||||
|
p.Y = a0.Y + int32(ry)
|
||||||
|
} else {
|
||||||
|
p.X = (a0.X + a1.X + b0.X + b1.X) / 4
|
||||||
|
p.Y = (a0.Y + a1.Y + b0.Y + b1.Y) / 4
|
||||||
|
}
|
||||||
|
p.Flags |= flagTouchedX | flagTouchedY
|
||||||
|
|
||||||
case opSRP0, opSRP1, opSRP2:
|
case opSRP0, opSRP1, opSRP2:
|
||||||
top--
|
top--
|
||||||
h.gs.rp[opcode-opSRP0] = h.stack[top]
|
h.gs.rp[opcode-opSRP0] = h.stack[top]
|
||||||
|
@ -498,6 +547,28 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point,
|
||||||
}
|
}
|
||||||
h.gs.loop = 1
|
h.gs.loop = 1
|
||||||
|
|
||||||
|
case opSHC0, opSHC1:
|
||||||
|
top--
|
||||||
|
_, _, d, ok := h.displacement(opcode&1 == 0)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("truetype: hinting: point out of range")
|
||||||
|
}
|
||||||
|
if h.gs.zp[2] == 0 {
|
||||||
|
// TODO: implement this when we have a glyph that does this.
|
||||||
|
return errors.New("hinting: unimplemented SHC instruction")
|
||||||
|
}
|
||||||
|
contour := h.stack[top]
|
||||||
|
if contour < 0 || len(ends) <= int(contour) {
|
||||||
|
return errors.New("truetype: hinting: contour out of range")
|
||||||
|
}
|
||||||
|
j0, j1 := 0, h.ends[contour]
|
||||||
|
if contour > 0 {
|
||||||
|
j0 = h.ends[contour-1]
|
||||||
|
}
|
||||||
|
for j := j0; j < j1; j++ {
|
||||||
|
h.move(h.point(2, current, int32(j)), d, false)
|
||||||
|
}
|
||||||
|
|
||||||
case opSHZ0, opSHZ1:
|
case opSHZ0, opSHZ1:
|
||||||
top--
|
top--
|
||||||
zonePointer, i, d, ok := h.displacement(opcode&1 == 0)
|
zonePointer, i, d, ok := h.displacement(opcode&1 == 0)
|
||||||
|
@ -815,9 +886,16 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point,
|
||||||
// This code does not implement engine compensation, as we don't expect to
|
// This code does not implement engine compensation, as we don't expect to
|
||||||
// be used to output on dot-matrix printers.
|
// be used to output on dot-matrix printers.
|
||||||
|
|
||||||
|
case opWCVTF:
|
||||||
|
top -= 2
|
||||||
|
h.setScaledCVT(h.stack[top], f26dot6(h.font.scale(h.scale*h.stack[top+1])))
|
||||||
|
|
||||||
case opDELTAP2, opDELTAP3:
|
case opDELTAP2, opDELTAP3:
|
||||||
goto deltap
|
goto deltap
|
||||||
|
|
||||||
|
case opDELTAC1, opDELTAC2, opDELTAC3:
|
||||||
|
goto deltac
|
||||||
|
|
||||||
case opSROUND, opS45ROUND:
|
case opSROUND, opS45ROUND:
|
||||||
top--
|
top--
|
||||||
switch (h.stack[top] >> 6) & 0x03 {
|
switch (h.stack[top] >> 6) & 0x03 {
|
||||||
|
@ -878,6 +956,33 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point,
|
||||||
// We do not support dropout control, as we always rasterize grayscale glyphs.
|
// We do not support dropout control, as we always rasterize grayscale glyphs.
|
||||||
top--
|
top--
|
||||||
|
|
||||||
|
case opSDPVTL0, opSDPVTL1:
|
||||||
|
top -= 2
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
pt := unhinted
|
||||||
|
if i != 0 {
|
||||||
|
pt = current
|
||||||
|
}
|
||||||
|
p := h.point(1, pt, h.stack[top])
|
||||||
|
q := h.point(2, pt, h.stack[top+1])
|
||||||
|
if p == nil || q == nil {
|
||||||
|
return errors.New("truetype: hinting: point out of range")
|
||||||
|
}
|
||||||
|
dx := f2dot14(p.X - q.X)
|
||||||
|
dy := f2dot14(p.Y - q.Y)
|
||||||
|
if dx == 0 && dy == 0 {
|
||||||
|
dx = 0x4000
|
||||||
|
} else if opcode&1 != 0 {
|
||||||
|
// Counter-clockwise rotation.
|
||||||
|
dx, dy = -dy, dx
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
h.gs.dv = normalize(dx, dy)
|
||||||
|
} else {
|
||||||
|
h.gs.pv = normalize(dx, dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case opGETINFO:
|
case opGETINFO:
|
||||||
res := int32(0)
|
res := int32(0)
|
||||||
if h.stack[top-1]&(1<<0) != 0 {
|
if h.stack[top-1]&(1<<0) != 0 {
|
||||||
|
@ -1155,39 +1260,83 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: merge the deltap and deltac code, when we have enough test cases
|
||||||
|
// (at least full coverage of Arial) to have confidence in re-factoring.
|
||||||
|
|
||||||
deltap:
|
deltap:
|
||||||
top--
|
{
|
||||||
n := f26dot6(h.stack[top])
|
top--
|
||||||
if top < 2*int(h.gs.loop) {
|
n := f26dot6(h.stack[top])
|
||||||
return errors.New("truetype: hinting: stack underflow")
|
if top < 2*int(h.gs.loop) {
|
||||||
|
return errors.New("truetype: hinting: stack underflow")
|
||||||
|
}
|
||||||
|
for ; n > 0; n-- {
|
||||||
|
top -= 2
|
||||||
|
p := h.point(0, current, h.stack[top+1])
|
||||||
|
if p == nil {
|
||||||
|
return errors.New("truetype: hinting: point out of range")
|
||||||
|
}
|
||||||
|
b := h.stack[top]
|
||||||
|
c := (b & 0xf0) >> 4
|
||||||
|
switch opcode {
|
||||||
|
case opDELTAP2:
|
||||||
|
c += 16
|
||||||
|
case opDELTAP3:
|
||||||
|
c += 32
|
||||||
|
}
|
||||||
|
c += h.gs.deltaBase
|
||||||
|
if ppem := (h.scale + 1<<5) >> 6; ppem != c {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b = (b & 0x0f) - 8
|
||||||
|
if b >= 0 {
|
||||||
|
b++
|
||||||
|
}
|
||||||
|
b = b * 64 / (1 << uint32(h.gs.deltaShift))
|
||||||
|
h.move(p, f26dot6(b), true)
|
||||||
|
}
|
||||||
|
pc++
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
for ; n > 0; n-- {
|
|
||||||
top -= 2
|
deltac:
|
||||||
p := h.point(0, current, h.stack[top+1])
|
{
|
||||||
if p == nil {
|
if !h.scaledCVTInitialized {
|
||||||
return errors.New("truetype: hinting: point out of range")
|
h.initializeScaledCVT()
|
||||||
}
|
}
|
||||||
b := h.stack[top]
|
top--
|
||||||
c := (b & 0xf0) >> 4
|
n := f26dot6(h.stack[top])
|
||||||
switch opcode {
|
if top < 2*int(h.gs.loop) {
|
||||||
case opDELTAP2:
|
return errors.New("truetype: hinting: stack underflow")
|
||||||
c += 16
|
|
||||||
case opDELTAP3:
|
|
||||||
c += 32
|
|
||||||
}
|
}
|
||||||
c += h.gs.deltaBase
|
for ; n > 0; n-- {
|
||||||
if ppem := (h.scale + 1<<5) >> 6; ppem != c {
|
top -= 2
|
||||||
continue
|
b := h.stack[top]
|
||||||
|
c := (b & 0xf0) >> 4
|
||||||
|
switch opcode {
|
||||||
|
case opDELTAC2:
|
||||||
|
c += 16
|
||||||
|
case opDELTAC3:
|
||||||
|
c += 32
|
||||||
|
}
|
||||||
|
c += h.gs.deltaBase
|
||||||
|
if ppem := (h.scale + 1<<5) >> 6; ppem != c {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b = (b & 0x0f) - 8
|
||||||
|
if b >= 0 {
|
||||||
|
b++
|
||||||
|
}
|
||||||
|
b = b * 64 / (1 << uint32(h.gs.deltaShift))
|
||||||
|
a := h.stack[top+1]
|
||||||
|
if a < 0 || len(h.scaledCVT) <= int(a) {
|
||||||
|
return errors.New("truetype: hinting: index out of range")
|
||||||
|
}
|
||||||
|
h.scaledCVT[a] += f26dot6(b)
|
||||||
}
|
}
|
||||||
b = (b & 0x0f) - 8
|
pc++
|
||||||
if b >= 0 {
|
continue
|
||||||
b++
|
|
||||||
}
|
|
||||||
b = b * 64 / (1 << uint32(h.gs.deltaShift))
|
|
||||||
h.move(p, f26dot6(b), true)
|
|
||||||
}
|
}
|
||||||
pc++
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ const (
|
||||||
opGPV = 0x0c // Get Projection Vector
|
opGPV = 0x0c // Get Projection Vector
|
||||||
opGFV = 0x0d // Get Freedom Vector
|
opGFV = 0x0d // Get Freedom Vector
|
||||||
opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector
|
opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector
|
||||||
opISECT = 0x0f
|
opISECT = 0x0f // moves point p to the InterSECTion of two lines
|
||||||
opSRP0 = 0x10 // Set Reference Point 0
|
opSRP0 = 0x10 // Set Reference Point 0
|
||||||
opSRP1 = 0x11 // Set Reference Point 1
|
opSRP1 = 0x11 // Set Reference Point 1
|
||||||
opSRP2 = 0x12 // Set Reference Point 2
|
opSRP2 = 0x12 // Set Reference Point 2
|
||||||
|
@ -62,8 +62,8 @@ const (
|
||||||
opIUP1 = 0x31 // .
|
opIUP1 = 0x31 // .
|
||||||
opSHP0 = 0x32 // SHift Point using reference point
|
opSHP0 = 0x32 // SHift Point using reference point
|
||||||
opSHP1 = 0x33 // .
|
opSHP1 = 0x33 // .
|
||||||
opSHC0 = 0x34
|
opSHC0 = 0x34 // SHift Contour using reference point
|
||||||
opSHC1 = 0x35
|
opSHC1 = 0x35 // .
|
||||||
opSHZ0 = 0x36 // SHift Zone using reference point
|
opSHZ0 = 0x36 // SHift Zone using reference point
|
||||||
opSHZ1 = 0x37 // .
|
opSHZ1 = 0x37 // .
|
||||||
opSHPIX = 0x38 // SHift point by a PIXel amount
|
opSHPIX = 0x38 // SHift point by a PIXel amount
|
||||||
|
@ -122,12 +122,12 @@ const (
|
||||||
opNROUND01 = 0x6d // .
|
opNROUND01 = 0x6d // .
|
||||||
opNROUND10 = 0x6e // .
|
opNROUND10 = 0x6e // .
|
||||||
opNROUND11 = 0x6f // .
|
opNROUND11 = 0x6f // .
|
||||||
opWCVTF = 0x70
|
opWCVTF = 0x70 // Write Control Value Table in Funits
|
||||||
opDELTAP2 = 0x71 // DELTA exception P2
|
opDELTAP2 = 0x71 // DELTA exception P2
|
||||||
opDELTAP3 = 0x72 // DELTA exception P3
|
opDELTAP3 = 0x72 // DELTA exception P3
|
||||||
opDELTAC1 = 0x73
|
opDELTAC1 = 0x73 // DELTA exception C1
|
||||||
opDELTAC2 = 0x74
|
opDELTAC2 = 0x74 // DELTA exception C2
|
||||||
opDELTAC3 = 0x75
|
opDELTAC3 = 0x75 // DELTA exception C3
|
||||||
opSROUND = 0x76 // Super ROUND
|
opSROUND = 0x76 // Super ROUND
|
||||||
opS45ROUND = 0x77 // Super ROUND 45 degrees
|
opS45ROUND = 0x77 // Super ROUND 45 degrees
|
||||||
opJROT = 0x78 // Jump Relative On True
|
opJROT = 0x78 // Jump Relative On True
|
||||||
|
@ -144,8 +144,8 @@ const (
|
||||||
op_0x83 = 0x83
|
op_0x83 = 0x83
|
||||||
op_0x84 = 0x84
|
op_0x84 = 0x84
|
||||||
opSCANCTRL = 0x85 // SCAN conversion ConTRoL
|
opSCANCTRL = 0x85 // SCAN conversion ConTRoL
|
||||||
opSDPVTL0 = 0x86
|
opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line
|
||||||
opSDPVTL1 = 0x87
|
opSDPVTL1 = 0x87 // .
|
||||||
opGETINFO = 0x88 // GET INFOrmation
|
opGETINFO = 0x88 // GET INFOrmation
|
||||||
opIDEF = 0x89 // Instruction DEFinition
|
opIDEF = 0x89 // Instruction DEFinition
|
||||||
opROLL = 0x8a // ROLL the top three stack elements
|
opROLL = 0x8a // ROLL the top three stack elements
|
||||||
|
@ -271,15 +271,15 @@ const (
|
||||||
// popCount is the number of stack elements that each opcode pops.
|
// popCount is the number of stack elements that each opcode pops.
|
||||||
var popCount = [256]uint8{
|
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, 2, 2, 2, 2, 2, 2, 0, 0, 0, q, // 0x00 - 0x0f
|
0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 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, 1, 1, // 0x20 - 0x2f
|
1, 1, 0, 2, 0, 1, 1, q, q, q, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f
|
||||||
0, 0, 0, 0, q, q, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f
|
0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f
|
||||||
0, 0, 2, 1, 2, 1, 1, 1, q, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f
|
0, 0, 2, 1, 2, 1, 1, 1, q, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f
|
||||||
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f
|
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 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
|
||||||
q, 1, 1, q, q, q, 1, 1, 2, 2, 0, q, 0, 0, 1, 1, // 0x70 - 0x7f
|
2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, q, 0, 0, 1, 1, // 0x70 - 0x7f
|
||||||
q, q, q, q, q, 1, q, q, 1, 1, 3, 2, 2, 1, q, q, // 0x80 - 0x8f
|
q, q, q, q, q, 1, 2, 2, 1, 1, 3, 2, 2, 1, q, q, // 0x80 - 0x8f
|
||||||
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
|
||||||
|
|
|
@ -255,7 +255,7 @@ var scalingTestCases = []struct {
|
||||||
hintingBrokenAt int
|
hintingBrokenAt int
|
||||||
}{
|
}{
|
||||||
{"luxisr", 12, -1},
|
{"luxisr", 12, -1},
|
||||||
{"x-arial-bold", 11, 0},
|
{"x-arial-bold", 11, 1},
|
||||||
{"x-deja-vu-sans-oblique", 17, -1},
|
{"x-deja-vu-sans-oblique", 17, -1},
|
||||||
{"x-droid-sans-japanese", 9, 0},
|
{"x-droid-sans-japanese", 9, 0},
|
||||||
{"x-times-new-roman", 13, 0},
|
{"x-times-new-roman", 13, 0},
|
||||||
|
|
Loading…
Reference in New Issue