From cef9ca89c7799e2b6bec46abfedbe96a54494be1 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Thu, 21 Nov 2013 08:51:09 +1100 Subject: [PATCH] freetype/truetype: implement ISECT, SHC, WCVTF, DELTAC and SDPVTL opcodes. R=bsiegert CC=golang-dev, remyoudompheng https://codereview.appspot.com/29510043 --- freetype/truetype/hint.go | 203 +++++++++++++++++++++++++---- freetype/truetype/opcodes.go | 26 ++-- freetype/truetype/truetype_test.go | 2 +- 3 files changed, 190 insertions(+), 41 deletions(-) diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go index 28de384..3a8b677 100644 --- a/freetype/truetype/hint.go +++ b/freetype/truetype/hint.go @@ -280,6 +280,55 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point, case opSFVTPV: 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: 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 + 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: top-- 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 // 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: goto deltap + case opDELTAC1, opDELTAC2, opDELTAC3: + goto deltac + case opSROUND, opS45ROUND: top-- 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. 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: res := int32(0) if h.stack[top-1]&(1<<0) != 0 { @@ -1155,39 +1260,83 @@ func (h *Hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point, 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: - top-- - n := f26dot6(h.stack[top]) - if top < 2*int(h.gs.loop) { - return errors.New("truetype: hinting: stack underflow") + { + top-- + n := f26dot6(h.stack[top]) + 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 - p := h.point(0, current, h.stack[top+1]) - if p == nil { - return errors.New("truetype: hinting: point out of range") + + deltac: + { + if !h.scaledCVTInitialized { + h.initializeScaledCVT() } - b := h.stack[top] - c := (b & 0xf0) >> 4 - switch opcode { - case opDELTAP2: - c += 16 - case opDELTAP3: - c += 32 + top-- + n := f26dot6(h.stack[top]) + if top < 2*int(h.gs.loop) { + return errors.New("truetype: hinting: stack underflow") } - c += h.gs.deltaBase - if ppem := (h.scale + 1<<5) >> 6; ppem != c { - continue + for ; n > 0; n-- { + top -= 2 + 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 - if b >= 0 { - b++ - } - b = b * 64 / (1 << uint32(h.gs.deltaShift)) - h.move(p, f26dot6(b), true) + pc++ + continue } - pc++ - continue } return nil } diff --git a/freetype/truetype/opcodes.go b/freetype/truetype/opcodes.go index bb07b7e..cfe3b32 100644 --- a/freetype/truetype/opcodes.go +++ b/freetype/truetype/opcodes.go @@ -25,7 +25,7 @@ const ( opGPV = 0x0c // Get Projection Vector opGFV = 0x0d // Get Freedom 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 opSRP1 = 0x11 // Set Reference Point 1 opSRP2 = 0x12 // Set Reference Point 2 @@ -62,8 +62,8 @@ const ( opIUP1 = 0x31 // . opSHP0 = 0x32 // SHift Point using reference point opSHP1 = 0x33 // . - opSHC0 = 0x34 - opSHC1 = 0x35 + opSHC0 = 0x34 // SHift Contour using reference point + opSHC1 = 0x35 // . opSHZ0 = 0x36 // SHift Zone using reference point opSHZ1 = 0x37 // . opSHPIX = 0x38 // SHift point by a PIXel amount @@ -122,12 +122,12 @@ const ( opNROUND01 = 0x6d // . opNROUND10 = 0x6e // . opNROUND11 = 0x6f // . - opWCVTF = 0x70 + opWCVTF = 0x70 // Write Control Value Table in Funits opDELTAP2 = 0x71 // DELTA exception P2 opDELTAP3 = 0x72 // DELTA exception P3 - opDELTAC1 = 0x73 - opDELTAC2 = 0x74 - opDELTAC3 = 0x75 + opDELTAC1 = 0x73 // DELTA exception C1 + opDELTAC2 = 0x74 // DELTA exception C2 + opDELTAC3 = 0x75 // DELTA exception C3 opSROUND = 0x76 // Super ROUND opS45ROUND = 0x77 // Super ROUND 45 degrees opJROT = 0x78 // Jump Relative On True @@ -144,8 +144,8 @@ const ( op_0x83 = 0x83 op_0x84 = 0x84 opSCANCTRL = 0x85 // SCAN conversion ConTRoL - opSDPVTL0 = 0x86 - opSDPVTL1 = 0x87 + opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line + opSDPVTL1 = 0x87 // . opGETINFO = 0x88 // GET INFOrmation opIDEF = 0x89 // Instruction DEFinition opROLL = 0x8a // ROLL the top three stack elements @@ -271,15 +271,15 @@ const ( // popCount is the number of stack elements that each opcode pops. 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, 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, 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 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 - q, 1, 1, q, q, q, 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 + 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, q, 0, 0, 1, 1, // 0x70 - 0x7f + 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, // 0xa0 - 0xaf 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf diff --git a/freetype/truetype/truetype_test.go b/freetype/truetype/truetype_test.go index 0a75c43..fd5d0d8 100644 --- a/freetype/truetype/truetype_test.go +++ b/freetype/truetype/truetype_test.go @@ -255,7 +255,7 @@ var scalingTestCases = []struct { hintingBrokenAt int }{ {"luxisr", 12, -1}, - {"x-arial-bold", 11, 0}, + {"x-arial-bold", 11, 1}, {"x-deja-vu-sans-oblique", 17, -1}, {"x-droid-sans-japanese", 9, 0}, {"x-times-new-roman", 13, 0},