| // Copyright 2012 The Freetype-Go Authors. All rights reserved. |
| // Use of this source code is governed by your choice of either the |
| // FreeType License or the GNU General Public License version 2 (or |
| // any later version), both of which can be found in the LICENSE file. |
| |
| package truetype |
| |
| // This file implements a Truetype bytecode interpreter. |
| // The opcodes are described at https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html |
| |
| import ( |
| "errors" |
| "math" |
| ) |
| |
| const ( |
| twilightZone = 0 |
| glyphZone = 1 |
| numZone = 2 |
| ) |
| |
| type pointType uint32 |
| |
| const ( |
| current pointType = 0 |
| unhinted pointType = 1 |
| inFontUnits pointType = 2 |
| numPointType = 3 |
| ) |
| |
| // callStackEntry is a bytecode call stack entry. |
| type callStackEntry struct { |
| program []byte |
| pc int |
| loopCount int32 |
| } |
| |
| // hinter implements bytecode hinting. A hinter can be re-used to hint a series |
| // of glyphs from a Font. |
| type hinter struct { |
| stack, store []int32 |
| |
| // 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. |
| font *Font |
| scale int32 |
| |
| // gs and defaultGS are the current and default graphics state. The |
| // default graphics state is the global default graphics state after |
| // the font's fpgm and prep programs have been run. |
| gs, defaultGS graphicsState |
| |
| // points and ends are the twilight zone's points, glyph's points |
| // and glyph's contour boundaries. |
| points [numZone][numPointType][]Point |
| ends []int |
| |
| // scaledCVT is the lazily initialized scaled Control Value Table. |
| scaledCVTInitialized bool |
| scaledCVT []f26dot6 |
| } |
| |
| // graphicsState is described at https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html |
| type graphicsState struct { |
| // Projection vector, freedom vector and dual projection vector. |
| pv, fv, dv [2]f2dot14 |
| // Reference points and zone pointers. |
| rp, zp [3]int32 |
| // Control Value / Single Width Cut-In. |
| controlValueCutIn, singleWidthCutIn, singleWidth f26dot6 |
| // Delta base / shift. |
| deltaBase, deltaShift int32 |
| // Minimum distance. |
| minDist f26dot6 |
| // Loop count. |
| loop int32 |
| // Rounding policy. |
| roundPeriod, roundPhase, roundThreshold f26dot6 |
| roundSuper45 bool |
| // Auto-flip. |
| autoFlip bool |
| } |
| |
| var globalDefaultGS = graphicsState{ |
| pv: [2]f2dot14{0x4000, 0}, // Unit vector along the X axis. |
| fv: [2]f2dot14{0x4000, 0}, |
| dv: [2]f2dot14{0x4000, 0}, |
| zp: [3]int32{1, 1, 1}, |
| controlValueCutIn: (17 << 6) / 16, // 17/16 as an f26dot6. |
| deltaBase: 9, |
| deltaShift: 3, |
| minDist: 1 << 6, // 1 as an f26dot6. |
| loop: 1, |
| roundPeriod: 1 << 6, // 1 as an f26dot6. |
| roundThreshold: 1 << 5, // 1/2 as an f26dot6. |
| roundSuper45: false, |
| autoFlip: true, |
| } |
| |
| func resetTwilightPoints(f *Font, p []Point) []Point { |
| if n := int(f.maxTwilightPoints) + 4; n <= cap(p) { |
| p = p[:n] |
| for i := range p { |
| p[i] = Point{} |
| } |
| } else { |
| p = make([]Point, n) |
| } |
| return p |
| } |
| |
| func (h *hinter) init(f *Font, scale int32) error { |
| h.points[twilightZone][0] = resetTwilightPoints(f, h.points[twilightZone][0]) |
| h.points[twilightZone][1] = resetTwilightPoints(f, h.points[twilightZone][1]) |
| h.points[twilightZone][2] = resetTwilightPoints(f, h.points[twilightZone][2]) |
| |
| rescale := h.scale != scale |
| if h.font != f { |
| h.font, rescale = f, true |
| if h.functions == nil { |
| h.functions = make(map[int32][]byte) |
| } else { |
| for k := range h.functions { |
| delete(h.functions, k) |
| } |
| } |
| |
| if x := int(f.maxStackElements); x > len(h.stack) { |
| x += 255 |
| x &^= 255 |
| h.stack = make([]int32, x) |
| } |
| if x := int(f.maxStorage); x > len(h.store) { |
| x += 15 |
| x &^= 15 |
| h.store = make([]int32, x) |
| } |
| if len(f.fpgm) != 0 { |
| if err := h.run(f.fpgm, nil, nil, nil, nil); err != nil { |
| return err |
| } |
| } |
| } |
| |
| if rescale { |
| h.scale = scale |
| h.scaledCVTInitialized = false |
| |
| h.defaultGS = globalDefaultGS |
| |
| if len(f.prep) != 0 { |
| if err := h.run(f.prep, nil, nil, nil, nil); err != nil { |
| return err |
| } |
| h.defaultGS = h.gs |
| // The MS rasterizer doesn't allow the following graphics state |
| // variables to be modified by the CVT program. |
| h.defaultGS.pv = globalDefaultGS.pv |
| h.defaultGS.fv = globalDefaultGS.fv |
| h.defaultGS.dv = globalDefaultGS.dv |
| h.defaultGS.rp = globalDefaultGS.rp |
| h.defaultGS.zp = globalDefaultGS.zp |
| h.defaultGS.loop = globalDefaultGS.loop |
| } |
| } |
| return nil |
| } |
| |
| func (h *hinter) run(program []byte, pCurrent, pUnhinted, pInFontUnits []Point, ends []int) error { |
| h.gs = h.defaultGS |
| h.points[glyphZone][current] = pCurrent |
| h.points[glyphZone][unhinted] = pUnhinted |
| h.points[glyphZone][inFontUnits] = pInFontUnits |
| h.ends = ends |
| |
| if len(program) > 50000 { |
| return errors.New("truetype: hinting: too many instructions") |
| } |
| var ( |
| steps, pc, top int |
| opcode uint8 |
| |
| callStack [32]callStackEntry |
| callStackTop int |
| ) |
| |
| for 0 <= pc && pc < len(program) { |
| steps++ |
| if steps == 100000 { |
| return errors.New("truetype: hinting: too many steps") |
| } |
| opcode = program[pc] |
| if top < int(popCount[opcode]) { |
| return errors.New("truetype: hinting: stack underflow") |
| } |
| switch opcode { |
| |
| case opSVTCA0: |
| h.gs.pv = [2]f2dot14{0, 0x4000} |
| h.gs.fv = [2]f2dot14{0, 0x4000} |
| h.gs.dv = [2]f2dot14{0, 0x4000} |
| |
| case opSVTCA1: |
| h.gs.pv = [2]f2dot14{0x4000, 0} |
| h.gs.fv = [2]f2dot14{0x4000, 0} |
| h.gs.dv = [2]f2dot14{0x4000, 0} |
| |
| case opSPVTCA0: |
| h.gs.pv = [2]f2dot14{0, 0x4000} |
| h.gs.dv = [2]f2dot14{0, 0x4000} |
| |
| case opSPVTCA1: |
| h.gs.pv = [2]f2dot14{0x4000, 0} |
| h.gs.dv = [2]f2dot14{0x4000, 0} |
| |
| case opSFVTCA0: |
| h.gs.fv = [2]f2dot14{0, 0x4000} |
| |
| case opSFVTCA1: |
| h.gs.fv = [2]f2dot14{0x4000, 0} |
| |
| case opSPVTL0, opSPVTL1, opSFVTL0, opSFVTL1: |
| top -= 2 |
| p1 := h.point(0, current, h.stack[top+0]) |
| p2 := h.point(0, current, h.stack[top+1]) |
| if p1 == nil || p2 == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| dx := f2dot14(p1.X - p2.X) |
| dy := f2dot14(p1.Y - p2.Y) |
| if dx == 0 && dy == 0 { |
| dx = 0x4000 |
| } else if opcode&1 != 0 { |
| // Counter-clockwise rotation. |
| dx, dy = -dy, dx |
| } |
| v := normalize(dx, dy) |
| if opcode < opSFVTL0 { |
| h.gs.pv = v |
| h.gs.dv = v |
| } else { |
| h.gs.fv = v |
| } |
| |
| case opSPVFS: |
| top -= 2 |
| h.gs.pv = normalize(f2dot14(h.stack[top]), f2dot14(h.stack[top+1])) |
| h.gs.dv = h.gs.pv |
| |
| case opSFVFS: |
| top -= 2 |
| h.gs.fv = normalize(f2dot14(h.stack[top]), f2dot14(h.stack[top+1])) |
| |
| case opGPV: |
| if top+1 >= len(h.stack) { |
| return errors.New("truetype: hinting: stack overflow") |
| } |
| h.stack[top+0] = int32(h.gs.pv[0]) |
| h.stack[top+1] = int32(h.gs.pv[1]) |
| top += 2 |
| |
| case opGFV: |
| if top+1 >= len(h.stack) { |
| return errors.New("truetype: hinting: stack overflow") |
| } |
| h.stack[top+0] = int32(h.gs.fv[0]) |
| h.stack[top+1] = int32(h.gs.fv[1]) |
| top += 2 |
| |
| 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] |
| |
| case opSZP0, opSZP1, opSZP2: |
| top-- |
| h.gs.zp[opcode-opSZP0] = h.stack[top] |
| |
| case opSZPS: |
| top-- |
| h.gs.zp[0] = h.stack[top] |
| h.gs.zp[1] = h.stack[top] |
| h.gs.zp[2] = h.stack[top] |
| |
| case opSLOOP: |
| top-- |
| if h.stack[top] <= 0 { |
| return errors.New("truetype: hinting: invalid data") |
| } |
| h.gs.loop = h.stack[top] |
| |
| case opRTG: |
| h.gs.roundPeriod = 1 << 6 |
| h.gs.roundPhase = 0 |
| h.gs.roundThreshold = 1 << 5 |
| h.gs.roundSuper45 = false |
| |
| case opRTHG: |
| h.gs.roundPeriod = 1 << 6 |
| h.gs.roundPhase = 1 << 5 |
| h.gs.roundThreshold = 1 << 5 |
| h.gs.roundSuper45 = false |
| |
| case opSMD: |
| top-- |
| h.gs.minDist = f26dot6(h.stack[top]) |
| |
| case opELSE: |
| opcode = 1 |
| goto ifelse |
| |
| case opJMPR: |
| top-- |
| pc += int(h.stack[top]) |
| continue |
| |
| case opSCVTCI: |
| top-- |
| h.gs.controlValueCutIn = f26dot6(h.stack[top]) |
| |
| case opSSWCI: |
| top-- |
| h.gs.singleWidthCutIn = f26dot6(h.stack[top]) |
| |
| case opSSW: |
| top-- |
| h.gs.singleWidth = f26dot6(h.font.scale(h.scale * h.stack[top])) |
| |
| case opDUP: |
| if top >= len(h.stack) { |
| return errors.New("truetype: hinting: stack overflow") |
| } |
| h.stack[top] = h.stack[top-1] |
| top++ |
| |
| case opPOP: |
| top-- |
| |
| case opCLEAR: |
| top = 0 |
| |
| case opSWAP: |
| h.stack[top-1], h.stack[top-2] = h.stack[top-2], h.stack[top-1] |
| |
| case opDEPTH: |
| if top >= len(h.stack) { |
| return errors.New("truetype: hinting: stack overflow") |
| } |
| h.stack[top] = int32(top) |
| top++ |
| |
| case opCINDEX, opMINDEX: |
| x := int(h.stack[top-1]) |
| if x <= 0 || x >= top { |
| return errors.New("truetype: hinting: invalid data") |
| } |
| h.stack[top-1] = h.stack[top-1-x] |
| if opcode == opMINDEX { |
| copy(h.stack[top-1-x:top-1], h.stack[top-x:top]) |
| top-- |
| } |
| |
| case opALIGNPTS: |
| top -= 2 |
| p := h.point(1, current, h.stack[top]) |
| q := h.point(0, current, h.stack[top+1]) |
| if p == nil || q == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| d := dotProduct(f26dot6(q.X-p.X), f26dot6(q.Y-p.Y), h.gs.pv) / 2 |
| h.move(p, +d, true) |
| h.move(q, -d, true) |
| |
| case opUTP: |
| top-- |
| p := h.point(0, current, h.stack[top]) |
| if p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| p.Flags &^= flagTouchedX | flagTouchedY |
| |
| case opLOOPCALL, opCALL: |
| if callStackTop >= len(callStack) { |
| return errors.New("truetype: hinting: call stack overflow") |
| } |
| top-- |
| f, ok := h.functions[h.stack[top]] |
| if !ok { |
| return errors.New("truetype: hinting: undefined function") |
| } |
| callStack[callStackTop] = callStackEntry{program, pc, 1} |
| if opcode == opLOOPCALL { |
| top-- |
| if h.stack[top] == 0 { |
| break |
| } |
| callStack[callStackTop].loopCount = h.stack[top] |
| } |
| callStackTop++ |
| program, pc = f, 0 |
| continue |
| |
| case opFDEF: |
| // Save all bytecode up until the next ENDF. |
| startPC := pc + 1 |
| fdefloop: |
| for { |
| pc++ |
| if pc >= len(program) { |
| return errors.New("truetype: hinting: unbalanced FDEF") |
| } |
| switch program[pc] { |
| case opFDEF: |
| return errors.New("truetype: hinting: nested FDEF") |
| case opENDF: |
| top-- |
| h.functions[h.stack[top]] = program[startPC : pc+1] |
| break fdefloop |
| default: |
| var ok bool |
| pc, ok = skipInstructionPayload(program, pc) |
| if !ok { |
| return errors.New("truetype: hinting: unbalanced FDEF") |
| } |
| } |
| } |
| |
| case opENDF: |
| if callStackTop == 0 { |
| return errors.New("truetype: hinting: call stack underflow") |
| } |
| callStackTop-- |
| callStack[callStackTop].loopCount-- |
| if callStack[callStackTop].loopCount != 0 { |
| callStackTop++ |
| pc = 0 |
| continue |
| } |
| program, pc = callStack[callStackTop].program, callStack[callStackTop].pc |
| |
| case opMDAP0, opMDAP1: |
| top-- |
| i := h.stack[top] |
| p := h.point(0, current, i) |
| if p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| 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, true) |
| h.gs.rp[0] = i |
| h.gs.rp[1] = i |
| |
| case opIUP0, opIUP1: |
| iupY, mask := opcode == opIUP0, uint32(flagTouchedX) |
| if iupY { |
| mask = flagTouchedY |
| } |
| prevEnd := 0 |
| for _, end := range h.ends { |
| for i := prevEnd; i < end; i++ { |
| for i < end && h.points[glyphZone][current][i].Flags&mask == 0 { |
| i++ |
| } |
| if i == end { |
| break |
| } |
| firstTouched, curTouched := i, i |
| i++ |
| for ; i < end; i++ { |
| if h.points[glyphZone][current][i].Flags&mask != 0 { |
| h.iupInterp(iupY, curTouched+1, i-1, curTouched, i) |
| curTouched = i |
| } |
| } |
| if curTouched == firstTouched { |
| h.iupShift(iupY, prevEnd, end, curTouched) |
| } else { |
| h.iupInterp(iupY, curTouched+1, end-1, curTouched, firstTouched) |
| if firstTouched > 0 { |
| h.iupInterp(iupY, prevEnd, firstTouched-1, curTouched, firstTouched) |
| } |
| } |
| } |
| prevEnd = end |
| } |
| |
| case opSHP0, opSHP1: |
| if top < int(h.gs.loop) { |
| return errors.New("truetype: hinting: stack underflow") |
| } |
| _, _, d, ok := h.displacement(opcode&1 == 0) |
| if !ok { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| for ; h.gs.loop != 0; h.gs.loop-- { |
| top-- |
| p := h.point(2, current, h.stack[top]) |
| if p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| h.move(p, d, true) |
| } |
| h.gs.loop = 1 |
| |
| case opSHC0, opSHC1: |
| top-- |
| zonePointer, i, 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 := int32(0), int32(h.ends[contour]) |
| if contour > 0 { |
| j0 = int32(h.ends[contour-1]) |
| } |
| move := h.gs.zp[zonePointer] != h.gs.zp[2] |
| for j := j0; j < j1; j++ { |
| if move || j != i { |
| h.move(h.point(2, current, j), d, true) |
| } |
| } |
| |
| case opSHZ0, opSHZ1: |
| top-- |
| zonePointer, i, d, ok := h.displacement(opcode&1 == 0) |
| if !ok { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| |
| // As per C Freetype, SHZ doesn't move the phantom points, or mark |
| // the points as touched. |
| limit := int32(len(h.points[h.gs.zp[2]][current])) |
| if h.gs.zp[2] == glyphZone { |
| limit -= 4 |
| } |
| for j := int32(0); j < limit; j++ { |
| if i != j || h.gs.zp[zonePointer] != h.gs.zp[2] { |
| h.move(h.point(2, current, j), d, false) |
| } |
| } |
| |
| case opSHPIX: |
| top-- |
| d := f26dot6(h.stack[top]) |
| if top < int(h.gs.loop) { |
| return errors.New("truetype: hinting: stack underflow") |
| } |
| for ; h.gs.loop != 0; h.gs.loop-- { |
| top-- |
| p := h.point(2, current, h.stack[top]) |
| if p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| h.move(p, d, true) |
| } |
| h.gs.loop = 1 |
| |
| case opIP: |
| if top < int(h.gs.loop) { |
| return errors.New("truetype: hinting: stack underflow") |
| } |
| pointType := inFontUnits |
| twilight := h.gs.zp[0] == 0 || h.gs.zp[1] == 0 || h.gs.zp[2] == 0 |
| if twilight { |
| pointType = unhinted |
| } |
| p := h.point(1, pointType, h.gs.rp[2]) |
| oldP := h.point(0, pointType, h.gs.rp[1]) |
| oldRange := dotProduct(f26dot6(p.X-oldP.X), f26dot6(p.Y-oldP.Y), h.gs.dv) |
| |
| p = h.point(1, current, h.gs.rp[2]) |
| curP := h.point(0, current, h.gs.rp[1]) |
| curRange := dotProduct(f26dot6(p.X-curP.X), f26dot6(p.Y-curP.Y), h.gs.pv) |
| for ; h.gs.loop != 0; h.gs.loop-- { |
| top-- |
| i := h.stack[top] |
| p = h.point(2, pointType, i) |
| oldDist := dotProduct(f26dot6(p.X-oldP.X), f26dot6(p.Y-oldP.Y), h.gs.dv) |
| p = h.point(2, current, i) |
| curDist := dotProduct(f26dot6(p.X-curP.X), f26dot6(p.Y-curP.Y), h.gs.pv) |
| newDist := f26dot6(0) |
| if oldDist != 0 { |
| if oldRange != 0 { |
| newDist = f26dot6(mulDiv(int64(oldDist), int64(curRange), int64(oldRange))) |
| } else { |
| newDist = -oldDist |
| } |
| } |
| h.move(p, newDist-curDist, true) |
| } |
| h.gs.loop = 1 |
| |
| case opMSIRP0, opMSIRP1: |
| top -= 2 |
| i := h.stack[top] |
| distance := f26dot6(h.stack[top+1]) |
| |
| // TODO: special case h.gs.zp[1] == 0 in C Freetype. |
| ref := h.point(0, current, h.gs.rp[0]) |
| p := h.point(1, current, i) |
| if ref == nil || p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| curDist := dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv) |
| |
| // Set-RP0 bit. |
| if opcode == opMSIRP1 { |
| h.gs.rp[0] = i |
| } |
| h.gs.rp[1] = h.gs.rp[0] |
| h.gs.rp[2] = i |
| |
| // Move the point. |
| h.move(p, distance-curDist, true) |
| |
| case opALIGNRP: |
| if top < int(h.gs.loop) { |
| return errors.New("truetype: hinting: stack underflow") |
| } |
| ref := h.point(0, current, h.gs.rp[0]) |
| if ref == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| for ; h.gs.loop != 0; h.gs.loop-- { |
| top-- |
| p := h.point(1, current, h.stack[top]) |
| if p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| h.move(p, -dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv), true) |
| } |
| h.gs.loop = 1 |
| |
| case opRTDG: |
| h.gs.roundPeriod = 1 << 5 |
| h.gs.roundPhase = 0 |
| h.gs.roundThreshold = 1 << 4 |
| h.gs.roundSuper45 = false |
| |
| case opMIAP0, opMIAP1: |
| top -= 2 |
| i := h.stack[top] |
| distance := h.getScaledCVT(h.stack[top+1]) |
| if h.gs.zp[0] == 0 { |
| p := h.point(0, unhinted, i) |
| q := h.point(0, current, i) |
| p.X = int32((int64(distance) * int64(h.gs.fv[0])) >> 14) |
| p.Y = int32((int64(distance) * int64(h.gs.fv[1])) >> 14) |
| *q = *p |
| } |
| p := h.point(0, current, i) |
| oldDist := dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv) |
| if opcode == opMIAP1 { |
| if (distance - oldDist).abs() > h.gs.controlValueCutIn { |
| distance = oldDist |
| } |
| // TODO: metrics compensation. |
| distance = h.round(distance) |
| } |
| h.move(p, distance-oldDist, true) |
| h.gs.rp[0] = i |
| h.gs.rp[1] = i |
| |
| case opNPUSHB: |
| opcode = 0 |
| goto push |
| |
| case opNPUSHW: |
| opcode = 0x80 |
| goto push |
| |
| case opWS: |
| top -= 2 |
| i := int(h.stack[top]) |
| if i < 0 || len(h.store) <= i { |
| return errors.New("truetype: hinting: invalid data") |
| } |
| h.store[i] = h.stack[top+1] |
| |
| case opRS: |
| i := int(h.stack[top-1]) |
| if i < 0 || len(h.store) <= i { |
| return errors.New("truetype: hinting: invalid data") |
| } |
| h.stack[top-1] = h.store[i] |
| |
| case opWCVTP: |
| top -= 2 |
| h.setScaledCVT(h.stack[top], f26dot6(h.stack[top+1])) |
| |
| case opRCVT: |
| h.stack[top-1] = int32(h.getScaledCVT(h.stack[top-1])) |
| |
| case opGC0, opGC1: |
| i := h.stack[top-1] |
| if opcode == opGC0 { |
| p := h.point(2, current, i) |
| h.stack[top-1] = int32(dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv)) |
| } else { |
| p := h.point(2, unhinted, i) |
| // Using dv as per C Freetype. |
| h.stack[top-1] = int32(dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.dv)) |
| } |
| |
| case opSCFS: |
| top -= 2 |
| i := h.stack[top] |
| p := h.point(2, current, i) |
| if p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| c := dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv) |
| h.move(p, f26dot6(h.stack[top+1])-c, true) |
| if h.gs.zp[2] != 0 { |
| break |
| } |
| q := h.point(2, unhinted, i) |
| if q == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| q.X = p.X |
| q.Y = p.Y |
| |
| case opMD0, opMD1: |
| top-- |
| pt, v, scale := pointType(0), [2]f2dot14{}, false |
| if opcode == opMD0 { |
| pt = current |
| v = h.gs.pv |
| } else if h.gs.zp[0] == 0 || h.gs.zp[1] == 0 { |
| pt = unhinted |
| v = h.gs.dv |
| } else { |
| pt = inFontUnits |
| v = h.gs.dv |
| scale = true |
| } |
| p := h.point(0, pt, h.stack[top-1]) |
| q := h.point(1, pt, h.stack[top]) |
| if p == nil || q == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| d := int32(dotProduct(f26dot6(p.X-q.X), f26dot6(p.Y-q.Y), v)) |
| if scale { |
| d = int32(int64(d*h.scale) / int64(h.font.fUnitsPerEm)) |
| } |
| h.stack[top-1] = d |
| |
| case opMPPEM, opMPS: |
| if top >= len(h.stack) { |
| return errors.New("truetype: hinting: stack overflow") |
| } |
| // For MPS, point size should be irrelevant; we return the PPEM. |
| h.stack[top] = h.scale >> 6 |
| top++ |
| |
| case opFLIPON, opFLIPOFF: |
| h.gs.autoFlip = opcode == opFLIPON |
| |
| case opDEBUG: |
| // No-op. |
| |
| case opLT: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1] < h.stack[top]) |
| |
| case opLTEQ: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1] <= h.stack[top]) |
| |
| case opGT: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1] > h.stack[top]) |
| |
| case opGTEQ: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1] >= h.stack[top]) |
| |
| case opEQ: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1] == h.stack[top]) |
| |
| case opNEQ: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1] != h.stack[top]) |
| |
| case opODD, opEVEN: |
| i := h.round(f26dot6(h.stack[top-1])) >> 6 |
| h.stack[top-1] = int32(i&1) ^ int32(opcode-opODD) |
| |
| case opIF: |
| top-- |
| if h.stack[top] == 0 { |
| opcode = 0 |
| goto ifelse |
| } |
| |
| case opEIF: |
| // No-op. |
| |
| case opAND: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1] != 0 && h.stack[top] != 0) |
| |
| case opOR: |
| top-- |
| h.stack[top-1] = bool2int32(h.stack[top-1]|h.stack[top] != 0) |
| |
| case opNOT: |
| h.stack[top-1] = bool2int32(h.stack[top-1] == 0) |
| |
| case opDELTAP1: |
| goto delta |
| |
| case opSDB: |
| top-- |
| h.gs.deltaBase = h.stack[top] |
| |
| case opSDS: |
| top-- |
| h.gs.deltaShift = h.stack[top] |
| |
| case opADD: |
| top-- |
| h.stack[top-1] += h.stack[top] |
| |
| case opSUB: |
| top-- |
| h.stack[top-1] -= h.stack[top] |
| |
| case opDIV: |
| top-- |
| if h.stack[top] == 0 { |
| return errors.New("truetype: hinting: division by zero") |
| } |
| h.stack[top-1] = int32(f26dot6(h.stack[top-1]).div(f26dot6(h.stack[top]))) |
| |
| case opMUL: |
| top-- |
| h.stack[top-1] = int32(f26dot6(h.stack[top-1]).mul(f26dot6(h.stack[top]))) |
| |
| case opABS: |
| if h.stack[top-1] < 0 { |
| h.stack[top-1] = -h.stack[top-1] |
| } |
| |
| case opNEG: |
| h.stack[top-1] = -h.stack[top-1] |
| |
| case opFLOOR: |
| h.stack[top-1] &^= 63 |
| |
| case opCEILING: |
| h.stack[top-1] += 63 |
| h.stack[top-1] &^= 63 |
| |
| case opROUND00, opROUND01, opROUND10, opROUND11: |
| // The four flavors of opROUND are equivalent. See the comment below on |
| // opNROUND for the rationale. |
| h.stack[top-1] = int32(h.round(f26dot6(h.stack[top-1]))) |
| |
| case opNROUND00, opNROUND01, opNROUND10, opNROUND11: |
| // No-op. The spec says to add one of four "compensations for the engine |
| // characteristics", to cater for things like "different dot-size printers". |
| // https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#engine_compensation |
| // 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, opDELTAC1, opDELTAC2, opDELTAC3: |
| goto delta |
| |
| case opSROUND, opS45ROUND: |
| top-- |
| switch (h.stack[top] >> 6) & 0x03 { |
| case 0: |
| h.gs.roundPeriod = 1 << 5 |
| case 1, 3: |
| h.gs.roundPeriod = 1 << 6 |
| case 2: |
| h.gs.roundPeriod = 1 << 7 |
| } |
| h.gs.roundSuper45 = opcode == opS45ROUND |
| if h.gs.roundSuper45 { |
| // The spec says to multiply by √2, but the C Freetype code says 1/√2. |
| // We go with 1/√2. |
| h.gs.roundPeriod *= 46341 |
| h.gs.roundPeriod /= 65536 |
| } |
| h.gs.roundPhase = h.gs.roundPeriod * f26dot6((h.stack[top]>>4)&0x03) / 4 |
| if x := h.stack[top] & 0x0f; x != 0 { |
| h.gs.roundThreshold = h.gs.roundPeriod * f26dot6(x-4) / 8 |
| } else { |
| h.gs.roundThreshold = h.gs.roundPeriod - 1 |
| } |
| |
| case opJROT: |
| top -= 2 |
| if h.stack[top+1] != 0 { |
| pc += int(h.stack[top]) |
| continue |
| } |
| |
| case opJROF: |
| top -= 2 |
| if h.stack[top+1] == 0 { |
| pc += int(h.stack[top]) |
| continue |
| } |
| |
| case opROFF: |
| h.gs.roundPeriod = 0 |
| h.gs.roundPhase = 0 |
| h.gs.roundThreshold = 0 |
| h.gs.roundSuper45 = false |
| |
| case opRUTG: |
| h.gs.roundPeriod = 1 << 6 |
| h.gs.roundPhase = 0 |
| h.gs.roundThreshold = 1<<6 - 1 |
| h.gs.roundSuper45 = false |
| |
| case opRDTG: |
| h.gs.roundPeriod = 1 << 6 |
| h.gs.roundPhase = 0 |
| h.gs.roundThreshold = 0 |
| h.gs.roundSuper45 = false |
| |
| case opSANGW, opAA: |
| // These ops are "anachronistic" and no longer used. |
| top-- |
| |
| case opFLIPPT: |
| if top < int(h.gs.loop) { |
| return errors.New("truetype: hinting: stack underflow") |
| } |
| points := h.points[glyphZone][current] |
| for ; h.gs.loop != 0; h.gs.loop-- { |
| top-- |
| i := h.stack[top] |
| if i < 0 || len(points) <= int(i) { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| points[i].Flags ^= flagOnCurve |
| } |
| h.gs.loop = 1 |
| |
| case opFLIPRGON, opFLIPRGOFF: |
| top -= 2 |
| i, j, points := h.stack[top], h.stack[top+1], h.points[glyphZone][current] |
| if i < 0 || len(points) <= int(i) || j < 0 || len(points) <= int(j) { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| for ; i <= j; i++ { |
| if opcode == opFLIPRGON { |
| points[i].Flags |= flagOnCurve |
| } else { |
| points[i].Flags &^= flagOnCurve |
| } |
| } |
| |
| case opSCANCTRL: |
| // 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 { |
| // Set the engine version. We hard-code this to 35, the same as |
| // the C freetype code, which says that "Version~35 corresponds |
| // to MS rasterizer v.1.7 as used e.g. in Windows~98". |
| res |= 35 |
| } |
| if h.stack[top-1]&(1<<5) != 0 { |
| // Set that we support grayscale. |
| res |= 1 << 12 |
| } |
| // We set no other bits, as we do not support rotated or stretched glyphs. |
| h.stack[top-1] = res |
| |
| case opIDEF: |
| // IDEF is for ancient versions of the bytecode interpreter, and is no longer used. |
| return errors.New("truetype: hinting: unsupported IDEF instruction") |
| |
| case opROLL: |
| h.stack[top-1], h.stack[top-3], h.stack[top-2] = |
| h.stack[top-3], h.stack[top-2], h.stack[top-1] |
| |
| case opMAX: |
| top-- |
| if h.stack[top-1] < h.stack[top] { |
| h.stack[top-1] = h.stack[top] |
| } |
| |
| case opMIN: |
| top-- |
| if h.stack[top-1] > h.stack[top] { |
| h.stack[top-1] = h.stack[top] |
| } |
| |
| case opSCANTYPE: |
| // We do not support dropout control, as we always rasterize grayscale glyphs. |
| top-- |
| |
| case opINSTCTRL: |
| // TODO: support instruction execution control? It seems rare, and even when |
| // nominally used (e.g. Source Sans Pro), it seems conditional on extreme or |
| // unusual rasterization conditions. For example, the code snippet at |
| // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html#INSTCTRL |
| // uses INSTCTRL when grid-fitting a rotated or stretched glyph, but |
| // freetype-go does not support rotated or stretched glyphs. |
| top -= 2 |
| |
| default: |
| if opcode < opPUSHB000 { |
| return errors.New("truetype: hinting: unrecognized instruction") |
| } |
| |
| if opcode < opMDRP00000 { |
| // PUSHxxxx opcode. |
| |
| if opcode < opPUSHW000 { |
| opcode -= opPUSHB000 - 1 |
| } else { |
| opcode -= opPUSHW000 - 1 - 0x80 |
| } |
| goto push |
| } |
| |
| if opcode < opMIRP00000 { |
| // MDRPxxxxx opcode. |
| |
| top-- |
| i := h.stack[top] |
| ref := h.point(0, current, h.gs.rp[0]) |
| p := h.point(1, current, i) |
| if ref == nil || p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| |
| oldDist := f26dot6(0) |
| if h.gs.zp[0] == 0 || h.gs.zp[1] == 0 { |
| p0 := h.point(1, unhinted, i) |
| p1 := h.point(0, unhinted, h.gs.rp[0]) |
| oldDist = dotProduct(f26dot6(p0.X-p1.X), f26dot6(p0.Y-p1.Y), h.gs.dv) |
| } else { |
| p0 := h.point(1, inFontUnits, i) |
| p1 := h.point(0, inFontUnits, h.gs.rp[0]) |
| oldDist = dotProduct(f26dot6(p0.X-p1.X), f26dot6(p0.Y-p1.Y), h.gs.dv) |
| oldDist = f26dot6(h.font.scale(h.scale * int32(oldDist))) |
| } |
| |
| // Single-width cut-in test. |
| if x := (oldDist - h.gs.singleWidth).abs(); x < h.gs.singleWidthCutIn { |
| if oldDist >= 0 { |
| oldDist = +h.gs.singleWidth |
| } else { |
| oldDist = -h.gs.singleWidth |
| } |
| } |
| |
| // Rounding bit. |
| // TODO: metrics compensation. |
| distance := oldDist |
| if opcode&0x04 != 0 { |
| distance = h.round(oldDist) |
| } |
| |
| // Minimum distance bit. |
| if opcode&0x08 != 0 { |
| if oldDist >= 0 { |
| if distance < h.gs.minDist { |
| distance = h.gs.minDist |
| } |
| } else { |
| if distance > -h.gs.minDist { |
| distance = -h.gs.minDist |
| } |
| } |
| } |
| |
| // Set-RP0 bit. |
| h.gs.rp[1] = h.gs.rp[0] |
| h.gs.rp[2] = i |
| if opcode&0x10 != 0 { |
| h.gs.rp[0] = i |
| } |
| |
| // Move the point. |
| oldDist = dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv) |
| h.move(p, distance-oldDist, true) |
| |
| } else { |
| // MIRPxxxxx opcode. |
| |
| top -= 2 |
| i := h.stack[top] |
| cvtDist := h.getScaledCVT(h.stack[top+1]) |
| if (cvtDist - h.gs.singleWidth).abs() < h.gs.singleWidthCutIn { |
| if cvtDist >= 0 { |
| cvtDist = +h.gs.singleWidth |
| } else { |
| cvtDist = -h.gs.singleWidth |
| } |
| } |
| |
| if h.gs.zp[1] == 0 { |
| // TODO: implement once we have a .ttf file that triggers |
| // this, so that we can step through C's freetype. |
| return errors.New("truetype: hinting: unimplemented twilight point adjustment") |
| } |
| |
| ref := h.point(0, unhinted, h.gs.rp[0]) |
| p := h.point(1, unhinted, i) |
| if ref == nil || p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| oldDist := dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.dv) |
| |
| ref = h.point(0, current, h.gs.rp[0]) |
| p = h.point(1, current, i) |
| if ref == nil || p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| curDist := dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv) |
| |
| if h.gs.autoFlip && oldDist^cvtDist < 0 { |
| cvtDist = -cvtDist |
| } |
| |
| // Rounding bit. |
| // TODO: metrics compensation. |
| distance := cvtDist |
| if opcode&0x04 != 0 { |
| // The CVT value is only used if close enough to oldDist. |
| if (h.gs.zp[0] == h.gs.zp[1]) && |
| ((cvtDist - oldDist).abs() > h.gs.controlValueCutIn) { |
| |
| distance = oldDist |
| } |
| distance = h.round(distance) |
| } |
| |
| // Minimum distance bit. |
| if opcode&0x08 != 0 { |
| if oldDist >= 0 { |
| if distance < h.gs.minDist { |
| distance = h.gs.minDist |
| } |
| } else { |
| if distance > -h.gs.minDist { |
| distance = -h.gs.minDist |
| } |
| } |
| } |
| |
| // Set-RP0 bit. |
| h.gs.rp[1] = h.gs.rp[0] |
| h.gs.rp[2] = i |
| if opcode&0x10 != 0 { |
| h.gs.rp[0] = i |
| } |
| |
| // Move the point. |
| h.move(p, distance-curDist, true) |
| } |
| } |
| pc++ |
| continue |
| |
| ifelse: |
| // Skip past bytecode until the next ELSE (if opcode == 0) or the |
| // next EIF (for all opcodes). Opcode == 0 means that we have come |
| // from an IF. Opcode == 1 means that we have come from an ELSE. |
| { |
| ifelseloop: |
| for depth := 0; ; { |
| pc++ |
| if pc >= len(program) { |
| return errors.New("truetype: hinting: unbalanced IF or ELSE") |
| } |
| switch program[pc] { |
| case opIF: |
| depth++ |
| case opELSE: |
| if depth == 0 && opcode == 0 { |
| break ifelseloop |
| } |
| case opEIF: |
| depth-- |
| if depth < 0 { |
| break ifelseloop |
| } |
| default: |
| var ok bool |
| pc, ok = skipInstructionPayload(program, pc) |
| if !ok { |
| return errors.New("truetype: hinting: unbalanced IF or ELSE") |
| } |
| } |
| } |
| pc++ |
| continue |
| } |
| |
| push: |
| // Push n elements from the program to the stack, where n is the low 7 bits of |
| // opcode. If the low 7 bits are zero, then n is the next byte from the program. |
| // The high bit being 0 means that the elements are zero-extended bytes. |
| // The high bit being 1 means that the elements are sign-extended words. |
| { |
| width := 1 |
| if opcode&0x80 != 0 { |
| opcode &^= 0x80 |
| width = 2 |
| } |
| if opcode == 0 { |
| pc++ |
| if pc >= len(program) { |
| return errors.New("truetype: hinting: insufficient data") |
| } |
| opcode = program[pc] |
| } |
| pc++ |
| if top+int(opcode) > len(h.stack) { |
| return errors.New("truetype: hinting: stack overflow") |
| } |
| if pc+width*int(opcode) > len(program) { |
| return errors.New("truetype: hinting: insufficient data") |
| } |
| for ; opcode > 0; opcode-- { |
| if width == 1 { |
| h.stack[top] = int32(program[pc]) |
| } else { |
| h.stack[top] = int32(int8(program[pc]))<<8 | int32(program[pc+1]) |
| } |
| top++ |
| pc += width |
| } |
| continue |
| } |
| |
| delta: |
| { |
| if opcode >= opDELTAC1 && !h.scaledCVTInitialized { |
| h.initializeScaledCVT() |
| } |
| top-- |
| n := h.stack[top] |
| if int32(top) < 2*n { |
| return errors.New("truetype: hinting: stack underflow") |
| } |
| for ; n > 0; n-- { |
| top -= 2 |
| b := h.stack[top] |
| c := (b & 0xf0) >> 4 |
| switch opcode { |
| case opDELTAP2, opDELTAC2: |
| c += 16 |
| case opDELTAP3, 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)) |
| if opcode >= opDELTAC1 { |
| 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) |
| } else { |
| p := h.point(0, current, h.stack[top+1]) |
| if p == nil { |
| return errors.New("truetype: hinting: point out of range") |
| } |
| h.move(p, f26dot6(b), true) |
| } |
| } |
| pc++ |
| continue |
| } |
| } |
| return nil |
| } |
| |
| func (h *hinter) initializeScaledCVT() { |
| h.scaledCVTInitialized = true |
| if n := len(h.font.cvt) / 2; n <= cap(h.scaledCVT) { |
| h.scaledCVT = h.scaledCVT[:n] |
| } else { |
| if n < 32 { |
| n = 32 |
| } |
| h.scaledCVT = make([]f26dot6, len(h.font.cvt)/2, n) |
| } |
| for i := range h.scaledCVT { |
| unscaled := uint16(h.font.cvt[2*i])<<8 | uint16(h.font.cvt[2*i+1]) |
| h.scaledCVT[i] = f26dot6(h.font.scale(h.scale * int32(int16(unscaled)))) |
| } |
| } |
| |
| // getScaledCVT returns the scaled value from the font's Control Value Table. |
| func (h *hinter) getScaledCVT(i int32) f26dot6 { |
| if !h.scaledCVTInitialized { |
| h.initializeScaledCVT() |
| } |
| if i < 0 || len(h.scaledCVT) <= int(i) { |
| return 0 |
| } |
| return h.scaledCVT[i] |
| } |
| |
| // setScaledCVT overrides the scaled value from the font's Control Value Table. |
| func (h *hinter) setScaledCVT(i int32, v f26dot6) { |
| if !h.scaledCVTInitialized { |
| h.initializeScaledCVT() |
| } |
| if i < 0 || len(h.scaledCVT) <= int(i) { |
| return |
| } |
| h.scaledCVT[i] = v |
| } |
| |
| func (h *hinter) point(zonePointer uint32, pt pointType, i int32) *Point { |
| points := h.points[h.gs.zp[zonePointer]][pt] |
| if i < 0 || len(points) <= int(i) { |
| return nil |
| } |
| return &points[i] |
| } |
| |
| func (h *hinter) move(p *Point, distance f26dot6, touch bool) { |
| fvx := int64(h.gs.fv[0]) |
| pvx := int64(h.gs.pv[0]) |
| if fvx == 0x4000 && pvx == 0x4000 { |
| p.X += int32(distance) |
| if touch { |
| p.Flags |= flagTouchedX |
| } |
| return |
| } |
| |
| fvy := int64(h.gs.fv[1]) |
| pvy := int64(h.gs.pv[1]) |
| if fvy == 0x4000 && pvy == 0x4000 { |
| p.Y += int32(distance) |
| if touch { |
| p.Flags |= flagTouchedY |
| } |
| return |
| } |
| |
| fvDotPv := (fvx*pvx + fvy*pvy) >> 14 |
| |
| if fvx != 0 { |
| p.X += int32(mulDiv(fvx, int64(distance), fvDotPv)) |
| if touch { |
| p.Flags |= flagTouchedX |
| } |
| } |
| |
| if fvy != 0 { |
| p.Y += int32(mulDiv(fvy, int64(distance), fvDotPv)) |
| if touch { |
| p.Flags |= flagTouchedY |
| } |
| } |
| } |
| |
| func (h *hinter) iupInterp(interpY bool, p1, p2, ref1, ref2 int) { |
| if p1 > p2 { |
| return |
| } |
| if ref1 >= len(h.points[glyphZone][current]) || |
| ref2 >= len(h.points[glyphZone][current]) { |
| return |
| } |
| |
| var ifu1, ifu2 int32 |
| if interpY { |
| ifu1 = h.points[glyphZone][inFontUnits][ref1].Y |
| ifu2 = h.points[glyphZone][inFontUnits][ref2].Y |
| } else { |
| ifu1 = h.points[glyphZone][inFontUnits][ref1].X |
| ifu2 = h.points[glyphZone][inFontUnits][ref2].X |
| } |
| if ifu1 > ifu2 { |
| ifu1, ifu2 = ifu2, ifu1 |
| ref1, ref2 = ref2, ref1 |
| } |
| |
| var unh1, unh2, delta1, delta2 int32 |
| if interpY { |
| unh1 = h.points[glyphZone][unhinted][ref1].Y |
| unh2 = h.points[glyphZone][unhinted][ref2].Y |
| delta1 = h.points[glyphZone][current][ref1].Y - unh1 |
| delta2 = h.points[glyphZone][current][ref2].Y - unh2 |
| } else { |
| unh1 = h.points[glyphZone][unhinted][ref1].X |
| unh2 = h.points[glyphZone][unhinted][ref2].X |
| delta1 = h.points[glyphZone][current][ref1].X - unh1 |
| delta2 = h.points[glyphZone][current][ref2].X - unh2 |
| } |
| |
| var xy, ifuXY int32 |
| if ifu1 == ifu2 { |
| for i := p1; i <= p2; i++ { |
| if interpY { |
| xy = h.points[glyphZone][unhinted][i].Y |
| } else { |
| xy = h.points[glyphZone][unhinted][i].X |
| } |
| |
| if xy <= unh1 { |
| xy += delta1 |
| } else { |
| xy += delta2 |
| } |
| |
| if interpY { |
| h.points[glyphZone][current][i].Y = xy |
| } else { |
| h.points[glyphZone][current][i].X = xy |
| } |
| } |
| return |
| } |
| |
| scale, scaleOK := int64(0), false |
| for i := p1; i <= p2; i++ { |
| if interpY { |
| xy = h.points[glyphZone][unhinted][i].Y |
| ifuXY = h.points[glyphZone][inFontUnits][i].Y |
| } else { |
| xy = h.points[glyphZone][unhinted][i].X |
| ifuXY = h.points[glyphZone][inFontUnits][i].X |
| } |
| |
| if xy <= unh1 { |
| xy += delta1 |
| } else if xy >= unh2 { |
| xy += delta2 |
| } else { |
| if !scaleOK { |
| scaleOK = true |
| scale = mulDiv(int64(unh2+delta2-unh1-delta1), 0x10000, int64(ifu2-ifu1)) |
| } |
| numer := int64(ifuXY-ifu1) * scale |
| if numer >= 0 { |
| numer += 0x8000 |
| } else { |
| numer -= 0x8000 |
| } |
| xy = unh1 + delta1 + int32(numer/0x10000) |
| } |
| |
| if interpY { |
| h.points[glyphZone][current][i].Y = xy |
| } else { |
| h.points[glyphZone][current][i].X = xy |
| } |
| } |
| } |
| |
| func (h *hinter) iupShift(interpY bool, p1, p2, p int) { |
| var delta int32 |
| if interpY { |
| delta = h.points[glyphZone][current][p].Y - h.points[glyphZone][unhinted][p].Y |
| } else { |
| delta = h.points[glyphZone][current][p].X - h.points[glyphZone][unhinted][p].X |
| } |
| if delta == 0 { |
| return |
| } |
| for i := p1; i < p2; i++ { |
| if i == p { |
| continue |
| } |
| if interpY { |
| h.points[glyphZone][current][i].Y += delta |
| } else { |
| h.points[glyphZone][current][i].X += delta |
| } |
| } |
| } |
| |
| func (h *hinter) displacement(useZP1 bool) (zonePointer uint32, i int32, d f26dot6, ok bool) { |
| zonePointer, i = uint32(0), h.gs.rp[1] |
| if useZP1 { |
| zonePointer, i = 1, h.gs.rp[2] |
| } |
| p := h.point(zonePointer, current, i) |
| q := h.point(zonePointer, unhinted, i) |
| if p == nil || q == nil { |
| return 0, 0, 0, false |
| } |
| d = dotProduct(f26dot6(p.X-q.X), f26dot6(p.Y-q.Y), h.gs.pv) |
| return zonePointer, i, d, true |
| } |
| |
| // 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) { |
| switch program[pc] { |
| case opNPUSHB: |
| pc++ |
| if pc >= len(program) { |
| return 0, false |
| } |
| pc += int(program[pc]) |
| case opNPUSHW: |
| pc++ |
| if pc >= len(program) { |
| return 0, false |
| } |
| pc += 2 * int(program[pc]) |
| case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, |
| opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111: |
| pc += int(program[pc] - (opPUSHB000 - 1)) |
| case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, |
| opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111: |
| pc += 2 * int(program[pc]-(opPUSHW000-1)) |
| } |
| return pc, true |
| } |
| |
| // f2dot14 is a 2.14 fixed point number. |
| type f2dot14 int16 |
| |
| func normalize(x, y f2dot14) [2]f2dot14 { |
| fx, fy := float64(x), float64(y) |
| l := 0x4000 / math.Hypot(fx, fy) |
| fx *= l |
| if fx >= 0 { |
| fx += 0.5 |
| } else { |
| fx -= 0.5 |
| } |
| fy *= l |
| if fy >= 0 { |
| fy += 0.5 |
| } else { |
| fy -= 0.5 |
| } |
| return [2]f2dot14{f2dot14(fx), f2dot14(fy)} |
| } |
| |
| // 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)) |
| } |
| |
| // mul returns x*y in 26.6 fixed point arithmetic. |
| func (x f26dot6) mul(y f26dot6) f26dot6 { |
| return f26dot6((int64(x)*int64(y) + 1<<5) >> 6) |
| } |
| |
| // dotProduct returns the dot product of [x, y] and q. It is almost the same as |
| // px := int64(x) |
| // py := int64(y) |
| // qx := int64(q[0]) |
| // qy := int64(q[1]) |
| // return f26dot6((px*qx + py*qy + 1<<13) >> 14) |
| // except that the computation is done with 32-bit integers to produce exactly |
| // the same rounding behavior as C Freetype. |
| func dotProduct(x, y f26dot6, q [2]f2dot14) f26dot6 { |
| // Compute x*q[0] as 64-bit value. |
| l := uint32((int32(x) & 0xFFFF) * int32(q[0])) |
| m := (int32(x) >> 16) * int32(q[0]) |
| |
| lo1 := l + (uint32(m) << 16) |
| hi1 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo1 < l) |
| |
| // Compute y*q[1] as 64-bit value. |
| l = uint32((int32(y) & 0xFFFF) * int32(q[1])) |
| m = (int32(y) >> 16) * int32(q[1]) |
| |
| lo2 := l + (uint32(m) << 16) |
| hi2 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo2 < l) |
| |
| // Add them. |
| lo := lo1 + lo2 |
| hi := hi1 + hi2 + bool2int32(lo < lo1) |
| |
| // Divide the result by 2^14 with rounding. |
| s := hi >> 31 |
| l = lo + uint32(s) |
| hi += s + bool2int32(l < lo) |
| lo = l |
| |
| l = lo + 0x2000 |
| hi += bool2int32(l < lo) |
| |
| return f26dot6((uint32(hi) << 18) | (l >> 14)) |
| } |
| |
| // mulDiv returns x*y/z, rounded to the nearest integer. |
| func mulDiv(x, y, z int64) int64 { |
| xy := x * y |
| if z < 0 { |
| xy, z = -xy, -z |
| } |
| if xy >= 0 { |
| xy += z / 2 |
| } else { |
| xy -= z / 2 |
| } |
| return xy / z |
| } |
| |
| // 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 { |
| if h.gs.roundPeriod == 0 { |
| // Rounding is off. |
| return x |
| } |
| if x >= 0 { |
| ret := x - h.gs.roundPhase + h.gs.roundThreshold |
| if h.gs.roundSuper45 { |
| ret /= h.gs.roundPeriod |
| ret *= h.gs.roundPeriod |
| } else { |
| ret &= -h.gs.roundPeriod |
| } |
| if x != 0 && ret < 0 { |
| ret = 0 |
| } |
| return ret + h.gs.roundPhase |
| } |
| ret := -x - h.gs.roundPhase + h.gs.roundThreshold |
| if h.gs.roundSuper45 { |
| ret /= h.gs.roundPeriod |
| ret *= h.gs.roundPeriod |
| } else { |
| ret &= -h.gs.roundPeriod |
| } |
| if ret < 0 { |
| ret = 0 |
| } |
| return -ret - h.gs.roundPhase |
| } |
| |
| func bool2int32(b bool) int32 { |
| if b { |
| return 1 |
| } |
| return 0 |
| } |