package truetype
import (
"errors"
"math"
"golang.org/x/image/math/fixed"
)
const (
twilightZone = 0
glyphZone = 1
numZone = 2
)
type pointType uint32
const (
current pointType = 0
unhinted pointType = 1
inFontUnits pointType = 2
numPointType = 3
)
type callStackEntry struct {
program []byte
pc int
loopCount int32
}
type hinter struct {
stack, store []int32
functions map [int32 ][]byte
font *Font
scale fixed .Int26_6
gs, defaultGS graphicsState
points [numZone ][numPointType ][]Point
ends []int
scaledCVTInitialized bool
scaledCVT []fixed .Int26_6
}
type graphicsState struct {
pv, fv, dv [2 ]f2dot14
rp, zp [3 ]int32
controlValueCutIn, singleWidthCutIn, singleWidth fixed .Int26_6
deltaBase, deltaShift int32
minDist fixed .Int26_6
loop int32
roundPeriod, roundPhase, roundThreshold fixed .Int26_6
roundSuper45 bool
autoFlip bool
}
var globalDefaultGS = graphicsState {
pv : [2 ]f2dot14 {0x4000 , 0 },
fv : [2 ]f2dot14 {0x4000 , 0 },
dv : [2 ]f2dot14 {0x4000 , 0 },
zp : [3 ]int32 {1 , 1 , 1 },
controlValueCutIn : (17 << 6 ) / 16 ,
deltaBase : 9 ,
deltaShift : 3 ,
minDist : 1 << 6 ,
loop : 1 ,
roundPeriod : 1 << 6 ,
roundThreshold : 1 << 5 ,
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 fixed .Int26_6 ) 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
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 {
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 )
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 + fixed .Int26_6 (rx )
p .Y = a0 .Y + fixed .Int26_6 (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 = fixed .Int26_6 (h .stack [top ])
case opELSE :
opcode = 1
goto ifelse
case opJMPR :
top --
pc += int (h .stack [top ])
continue
case opSCVTCI :
top --
h .gs .controlValueCutIn = fixed .Int26_6 (h .stack [top ])
case opSSWCI :
top --
h .gs .singleWidthCutIn = fixed .Int26_6 (h .stack [top ])
case opSSW :
top --
h .gs .singleWidth = h .font .scale (h .scale * fixed .Int26_6 (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 (fixed .Int26_6 (q .X -p .X ), fixed .Int26_6 (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 :
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 := fixed .Int26_6 (0 )
if opcode == opMDAP1 {
distance = dotProduct (p .X , p .Y , h .gs .pv )
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 {
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" )
}
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 := fixed .Int26_6 (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 (p .X -oldP .X , 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 (p .X -curP .X , 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 (p .X -oldP .X , p .Y -oldP .Y , h .gs .dv )
p = h .point (2 , current , i )
curDist := dotProduct (p .X -curP .X , p .Y -curP .Y , h .gs .pv )
newDist := fixed .Int26_6 (0 )
if oldDist != 0 {
if oldRange != 0 {
newDist = fixed .Int26_6 (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 := fixed .Int26_6 (h .stack [top +1 ])
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 (p .X -ref .X , p .Y -ref .Y , h .gs .pv )
if opcode == opMSIRP1 {
h .gs .rp [0 ] = i
}
h .gs .rp [1 ] = h .gs .rp [0 ]
h .gs .rp [2 ] = i
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 (p .X -ref .X , 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 = fixed .Int26_6 ((int64 (distance ) * int64 (h .gs .fv [0 ])) >> 14 )
p .Y = fixed .Int26_6 ((int64 (distance ) * int64 (h .gs .fv [1 ])) >> 14 )
*q = *p
}
p := h .point (0 , current , i )
oldDist := dotProduct (p .X , p .Y , h .gs .pv )
if opcode == opMIAP1 {
if fabs (distance -oldDist ) > h .gs .controlValueCutIn {
distance = oldDist
}
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 ], fixed .Int26_6 (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 (p .X , p .Y , h .gs .pv ))
} else {
p := h .point (2 , unhinted , i )
h .stack [top -1 ] = int32 (dotProduct (p .X , 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 (p .X , p .Y , h .gs .pv )
h .move (p , fixed .Int26_6 (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 (p .X -q .X , p .Y -q .Y , v ))
if scale {
d = int32 (int64 (d *int32 (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" )
}
h .stack [top ] = int32 (h .scale ) >> 6
top ++
case opFLIPON , opFLIPOFF :
h .gs .autoFlip = opcode == opFLIPON
case opDEBUG :
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 (fixed .Int26_6 (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 :
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 (fdiv (fixed .Int26_6 (h .stack [top -1 ]), fixed .Int26_6 (h .stack [top ])))
case opMUL :
top --
h .stack [top -1 ] = int32 (fmul (fixed .Int26_6 (h .stack [top -1 ]), fixed .Int26_6 (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 :
h .stack [top -1 ] = int32 (h .round (fixed .Int26_6 (h .stack [top -1 ])))
case opNROUND00 , opNROUND01 , opNROUND10 , opNROUND11 :
case opWCVTF :
top -= 2
h .setScaledCVT (h .stack [top ], h .font .scale (h .scale *fixed .Int26_6 (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 {
h .gs .roundPeriod *= 46341
h .gs .roundPeriod /= 65536
}
h .gs .roundPhase = h .gs .roundPeriod * fixed .Int26_6 ((h .stack [top ]>>4 )&0x03 ) / 4
if x := h .stack [top ] & 0x0f ; x != 0 {
h .gs .roundThreshold = h .gs .roundPeriod * fixed .Int26_6 (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 :
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 :
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 {
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 {
res |= 35
}
if h .stack [top -1 ]&(1 <<5 ) != 0 {
res |= 1 << 12
}
h .stack [top -1 ] = res
case opIDEF :
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 :
top --
case opINSTCTRL :
top -= 2
default :
if opcode < opPUSHB000 {
return errors .New ("truetype: hinting: unrecognized instruction" )
}
if opcode < opMDRP00000 {
if opcode < opPUSHW000 {
opcode -= opPUSHB000 - 1
} else {
opcode -= opPUSHW000 - 1 - 0x80
}
goto push
}
if opcode < opMIRP00000 {
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 := fixed .Int26_6 (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 (p0 .X -p1 .X , 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 (p0 .X -p1 .X , p0 .Y -p1 .Y , h .gs .dv )
oldDist = h .font .scale (h .scale * oldDist )
}
if x := fabs (oldDist - h .gs .singleWidth ); x < h .gs .singleWidthCutIn {
if oldDist >= 0 {
oldDist = +h .gs .singleWidth
} else {
oldDist = -h .gs .singleWidth
}
}
distance := oldDist
if opcode &0x04 != 0 {
distance = h .round (oldDist )
}
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
}
}
}
h .gs .rp [1 ] = h .gs .rp [0 ]
h .gs .rp [2 ] = i
if opcode &0x10 != 0 {
h .gs .rp [0 ] = i
}
oldDist = dotProduct (p .X -ref .X , p .Y -ref .Y , h .gs .pv )
h .move (p , distance -oldDist , true )
} else {
top -= 2
i := h .stack [top ]
cvtDist := h .getScaledCVT (h .stack [top +1 ])
if fabs (cvtDist -h .gs .singleWidth ) < h .gs .singleWidthCutIn {
if cvtDist >= 0 {
cvtDist = +h .gs .singleWidth
} else {
cvtDist = -h .gs .singleWidth
}
}
if h .gs .zp [1 ] == 0 {
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 (p .X -ref .X , 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 (p .X -ref .X , p .Y -ref .Y , h .gs .pv )
if h .gs .autoFlip && oldDist ^cvtDist < 0 {
cvtDist = -cvtDist
}
distance := cvtDist
if opcode &0x04 != 0 {
if (h .gs .zp [0 ] == h .gs .zp [1 ]) &&
(fabs (cvtDist -oldDist ) > h .gs .controlValueCutIn ) {
distance = oldDist
}
distance = h .round (distance )
}
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
}
}
}
h .gs .rp [1 ] = h .gs .rp [0 ]
h .gs .rp [2 ] = i
if opcode &0x10 != 0 {
h .gs .rp [0 ] = i
}
h .move (p , distance -curDist , true )
}
}
pc ++
continue
ifelse :
{
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 :
{
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 := (int32 (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 ] += fixed .Int26_6 (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 , fixed .Int26_6 (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 ([]fixed .Int26_6 , 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 ] = h .font .scale (h .scale * fixed .Int26_6 (int16 (unscaled )))
}
}
func (h *hinter ) getScaledCVT (i int32 ) fixed .Int26_6 {
if !h .scaledCVTInitialized {
h .initializeScaledCVT ()
}
if i < 0 || len (h .scaledCVT ) <= int (i ) {
return 0
}
return h .scaledCVT [i ]
}
func (h *hinter ) setScaledCVT (i int32 , v fixed .Int26_6 ) {
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 fixed .Int26_6 , touch bool ) {
fvx := int64 (h .gs .fv [0 ])
pvx := int64 (h .gs .pv [0 ])
if fvx == 0x4000 && pvx == 0x4000 {
p .X += fixed .Int26_6 (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 += fixed .Int26_6 (distance )
if touch {
p .Flags |= flagTouchedY
}
return
}
fvDotPv := (fvx *pvx + fvy *pvy ) >> 14
if fvx != 0 {
p .X += fixed .Int26_6 (mulDiv (fvx , int64 (distance ), fvDotPv ))
if touch {
p .Flags |= flagTouchedX
}
}
if fvy != 0 {
p .Y += fixed .Int26_6 (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 fixed .Int26_6
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 fixed .Int26_6
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 fixed .Int26_6
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 + fixed .Int26_6 (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 fixed .Int26_6
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 fixed .Int26_6 , 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 (p .X -q .X , p .Y -q .Y , h .gs .pv )
return zonePointer , i , d , true
}
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
}
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 )}
}
func fabs(x fixed .Int26_6 ) fixed .Int26_6 {
if x < 0 {
return -x
}
return x
}
func fdiv(x , y fixed .Int26_6 ) fixed .Int26_6 {
return fixed .Int26_6 ((int64 (x ) << 6 ) / int64 (y ))
}
func fmul(x , y fixed .Int26_6 ) fixed .Int26_6 {
return fixed .Int26_6 ((int64 (x )*int64 (y ) + 1 <<5 ) >> 6 )
}
func dotProduct(x , y fixed .Int26_6 , q [2 ]f2dot14 ) fixed .Int26_6 {
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 )
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 )
lo := lo1 + lo2
hi := hi1 + hi2 + bool2int32 (lo < lo1 )
s := hi >> 31
l = lo + uint32 (s )
hi += s + bool2int32 (l < lo )
lo = l
l = lo + 0x2000
hi += bool2int32 (l < lo )
return fixed .Int26_6 ((uint32 (hi ) << 18 ) | (l >> 14 ))
}
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
}
func (h *hinter ) round (x fixed .Int26_6 ) fixed .Int26_6 {
if h .gs .roundPeriod == 0 {
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
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .