freetype/truetype/hint.go
Nigel Tao a31d0146da Loosen the SLOOP (Set LOOP) opcode precondition.
This goes against spec, but matches the C Freetype implementation and
works around a bug in DejaVuSansMono.ttf's hinted '2' glyph.
2016-05-19 16:15:10 +10:00

1770 lines
43 KiB
Go

// 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"
"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
)
// 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 fixed.Int26_6
// 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 []fixed.Int26_6
}
// 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 fixed.Int26_6
// Delta base / shift.
deltaBase, deltaShift int32
// Minimum distance.
minDist fixed.Int26_6
// Loop count.
loop int32
// Rounding policy.
roundPeriod, roundPhase, roundThreshold fixed.Int26_6
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 a fixed.Int26_6.
deltaBase: 9,
deltaShift: 3,
minDist: 1 << 6, // 1 as a fixed.Int26_6.
loop: 1,
roundPeriod: 1 << 6, // 1 as a fixed.Int26_6.
roundThreshold: 1 << 5, // 1/2 as a fixed.Int26_6.
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
// 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 + 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--
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html#SLOOP
// says that "Setting the loop variable to zero is an error". In
// theory, the inequality on the next line should be "<=" instead
// of "<". In practice, some font files' bytecode, such as the '2'
// glyph in the DejaVuSansMono.ttf that comes with Ubuntu 14.04,
// issue SLOOP with a zero on top of the stack. Just like the C
// Freetype code, we allow the zero.
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:
// 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 := fixed.Int26_6(0)
if opcode == opMDAP1 {
distance = dotProduct(p.X, 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 := 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])
// 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(p.X-ref.X, 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(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
}
// 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], 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)
// Using dv as per C Freetype.
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")
}
// For MPS, point size should be irrelevant; we return the PPEM.
h.stack[top] = int32(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(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:
// 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(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:
// The four flavors of opROUND are equivalent. See the comment below on
// opNROUND for the rationale.
h.stack[top-1] = int32(h.round(fixed.Int26_6(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], 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 {
// 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 * 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:
// 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 := 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)
}
// Single-width cut-in test.
if x := fabs(oldDist - h.gs.singleWidth); 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(p.X-ref.X, 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 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 {
// 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(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
}
// 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]) &&
(fabs(cvtDist-oldDist) > 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 := (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)))
}
}
// getScaledCVT returns the scaled value from the font's Control Value Table.
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]
}
// setScaledCVT overrides the scaled value from the font's Control Value Table.
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
}
// 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)}
}
// fabs returns abs(x) in 26.6 fixed point arithmetic.
func fabs(x fixed.Int26_6) fixed.Int26_6 {
if x < 0 {
return -x
}
return x
}
// fdiv returns x/y in 26.6 fixed point arithmetic.
func fdiv(x, y fixed.Int26_6) fixed.Int26_6 {
return fixed.Int26_6((int64(x) << 6) / int64(y))
}
// fmul returns x*y in 26.6 fixed point arithmetic.
func fmul(x, y fixed.Int26_6) fixed.Int26_6 {
return fixed.Int26_6((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 fixed.Int26_6((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 fixed.Int26_6, q [2]f2dot14) fixed.Int26_6 {
// 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 fixed.Int26_6((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 fixed.Int26_6) fixed.Int26_6 {
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
}