Files
ofdgo/ofdgo_font_hint.go
T
2026-10-08 01:06:43 +08:00

1020 lines
28 KiB
Go

// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"encoding/binary"
"errors"
"fmt"
"maps"
"math"
)
// errTTStackUnderflow 表示字形程序缺少必需的栈参数
var errTTStackUnderflow = errors.New("TrueType instruction stack underflow")
// errTTInvalidReference 表示字形指令引用不存在的点或函数
var errTTInvalidReference = errors.New("invalid TrueType instruction reference")
// errTTInvalidControlFlow 表示条件分支未闭合或跳转超出字节码范围
var errTTInvalidControlFlow = errors.New("invalid TrueType control flow")
// ttPoint 保存TrueType当前坐标、原始坐标及点状态,坐标使用26.6定点数
type ttPoint struct {
x, y, ox, oy int32
on, tx, ty bool
}
// ttGraphics 保存TrueType图形状态
type ttGraphics struct {
projection, freedom, dual [2]float64
zone [3]int32
reference [3]int32
loop, minimum, cutIn, singleCutIn, singleWidth int32
deltaBase, deltaShift int32
period, phase, threshold int32
autoFlip bool
inhibit, reset bool
}
// ttInterpreter 在设计单位精度执行TrueType字形程序,保留由指令构造的轮廓
type ttInterpreter struct {
graphics ttGraphics
stack, storage, cvt []int32
zones [2][]ttPoint
ends []uint16
functions map[int32][]byte
instructions map[byte][]byte
units int32
stackLimit int
steps int
err error
}
// newTTInterpreter 解析字体程序使用的存储容量和控制值
// 入参: tables 字体表, units 每字面设计单位数
// 返回: *ttInterpreter 指令执行器, error 字体表错误
func newTTInterpreter(tables map[string][]byte, units uint16) (*ttInterpreter, error) {
maxp := tables["maxp"]
if len(maxp) < 32 || units == 0 || len(tables["cvt "])%2 != 0 {
return nil, fmt.Errorf("invalid TrueType instruction tables")
}
v := &ttInterpreter{
units: int32(units), stackLimit: int(binary.BigEndian.Uint16(maxp[24:])),
storage: make([]int32, binary.BigEndian.Uint16(maxp[18:])),
functions: map[int32][]byte{}, instructions: map[byte][]byte{},
}
v.zones[0] = make([]ttPoint, int(binary.BigEndian.Uint16(maxp[16:]))+4)
for i := 0; i < len(tables["cvt "]); i += 2 {
v.cvt = append(v.cvt, int32(int16(binary.BigEndian.Uint16(tables["cvt "][i:])))*64)
}
v.graphics = defaultTTGraphics()
if err := v.run(tables["fpgm"]); err != nil {
return nil, err
}
v.stack = nil
v.graphics = defaultTTGraphics()
if err := v.run(tables["prep"]); err != nil {
return nil, err
}
v.stack = nil
return v, nil
}
// defaultTTGraphics 返回规范规定的初始图形状态
// 返回: ttGraphics 图形状态
func defaultTTGraphics() ttGraphics {
return ttGraphics{projection: [2]float64{1, 0}, freedom: [2]float64{1, 0}, dual: [2]float64{1, 0}, zone: [3]int32{1, 1, 1}, loop: 1, minimum: 64, cutIn: 68, deltaBase: 9, deltaShift: 3, period: 64, threshold: 32, autoFlip: true}
}
// clone 为单个字形创建独立存储,避免字形顺序和并发改变输出
// 返回: *ttInterpreter 独立执行器
func (v *ttInterpreter) clone() *ttInterpreter {
c := *v
c.stack = nil
c.storage = append([]int32(nil), v.storage...)
c.cvt = append([]int32(nil), v.cvt...)
c.functions = maps.Clone(v.functions)
c.instructions = maps.Clone(v.instructions)
c.zones[0] = append([]ttPoint(nil), v.zones[0]...)
c.steps = 0
if c.graphics.reset {
c.graphics = defaultTTGraphics()
}
c.graphics.projection, c.graphics.freedom, c.graphics.dual = [2]float64{1, 0}, [2]float64{1, 0}, [2]float64{1, 0}
c.graphics.reference, c.graphics.zone, c.graphics.loop = [3]int32{}, [3]int32{1, 1, 1}, 1
return &c
}
// pop 取出栈顶操作数并记录栈下溢
// 返回: int32 操作数
func (v *ttInterpreter) pop() int32 {
if len(v.stack) == 0 {
v.err = errTTStackUnderflow
return 0
}
x := v.stack[len(v.stack)-1]
v.stack = v.stack[:len(v.stack)-1]
return x
}
// push 压入操作数并检查字体声明的栈容量
// 入参: values 操作数
func (v *ttInterpreter) push(values ...int32) {
if len(v.stack)+len(values) > v.stackLimit {
v.err = fmt.Errorf("TrueType instruction stack overflow")
return
}
v.stack = append(v.stack, values...)
}
// point 获取指定区的点并检查引用范围
// 入参: zone 区号, index 点号
// 返回: *ttPoint 点
func (v *ttInterpreter) point(zone, index int32) *ttPoint {
if zone < 0 || zone > 1 || index < 0 || int64(index) >= int64(len(v.zones[zone])) {
v.err = fmt.Errorf("%w: point %d in zone %d", errTTInvalidReference, index, zone)
return &ttPoint{}
}
return &v.zones[zone][index]
}
// read 读取控制值或存储区
// 入参: values 存储区, index 位置
// 返回: int32 存储值
func (v *ttInterpreter) read(values []int32, index int32) int32 {
if index < 0 || int64(index) >= int64(len(values)) {
v.err = fmt.Errorf("invalid TrueType storage reference")
return 0
}
return values[index]
}
// write 写入控制值或存储区
// 入参: values 存储区, index 位置, value 存储值
func (v *ttInterpreter) write(values []int32, index, value int32) {
if index < 0 || int64(index) >= int64(len(values)) {
v.err = fmt.Errorf("invalid TrueType storage reference")
return
}
values[index] = value
}
// ttProject 将定点坐标投影到单位向量
// 入参: x 横坐标, y 纵坐标, vector 单位向量
// 返回: int32 投影值
func ttProject(x, y int32, vector [2]float64) int32 {
return int32(math.Round(float64(x)*vector[0] + float64(y)*vector[1]))
}
// move 沿自由向量移动点,使其投影改变指定距离
// 入参: point 点, distance 投影距离, touch 是否标记触及状态
func (v *ttInterpreter) move(point *ttPoint, distance int32, touch bool) {
g := &v.graphics
if distance != 0 {
dot := g.freedom[0]*g.projection[0] + g.freedom[1]*g.projection[1]
if math.Abs(dot) < 1.0/16384 {
v.err = fmt.Errorf("perpendicular TrueType movement vectors")
return
}
point.x += int32(math.Round(float64(distance) * g.freedom[0] / dot))
point.y += int32(math.Round(float64(distance) * g.freedom[1] / dot))
}
if touch {
point.tx = point.tx || g.freedom[0] != 0
point.ty = point.ty || g.freedom[1] != 0
}
}
// round 按当前舍入状态调整有符号距离
// 入参: value 距离
// 返回: int32 舍入距离
func (v *ttInterpreter) round(value int32) int32 {
g := v.graphics
if g.period == 0 {
return value
}
sign := int32(1)
if value < 0 {
sign = -1
value = -value
}
result := (value-g.phase+g.threshold)/g.period*g.period + g.phase
if result < 0 {
result = g.phase
}
return sign * result
}
// ttInstructionEnd 跳过一条指令及其立即数
// 入参: code 字节码, position 指令位置
// 返回: int 结束位置, error 字节码错误
func ttInstructionEnd(code []byte, position int) (int, error) {
if position < 0 || position >= len(code) {
return 0, fmt.Errorf("invalid TrueType instruction offset")
}
op := code[position]
position++
n := 0
if op == 0x40 || op == 0x41 {
if position >= len(code) {
return 0, fmt.Errorf("truncated TrueType push instruction")
}
n = int(code[position])
position++
if op == 0x41 {
n *= 2
}
} else if op >= 0xb0 && op <= 0xbf {
n = int(op&7) + 1
if op >= 0xb8 {
n *= 2
}
}
if n > len(code)-position {
return 0, fmt.Errorf("truncated TrueType push instruction")
}
return position + n, nil
}
// skipTTConditional 定位当前条件分支的替代分支或结束位置
// 入参: code 字节码, position 起始位置, alternate 是否停在ELSE
// 返回: int 后续指令位置, error 分支错误
func skipTTConditional(code []byte, position int, alternate bool) (int, error) {
start := position
depth := 0
for position < len(code) {
op := code[position]
if op == 0x58 {
depth++
}
if op == 0x59 {
if depth == 0 {
return position + 1, nil
}
depth--
}
if op == 0x1b && depth == 0 && alternate {
return position + 1, nil
}
var err error
position, err = ttInstructionEnd(code, position)
if err != nil {
return 0, err
}
}
return 0, fmt.Errorf("%w: unterminated conditional at %d in %d bytes", errTTInvalidControlFlow, start, len(code))
}
// run 执行字体、预处理或字形程序
// 入参: code 字节码
// 返回: error 指令错误
func (v *ttInterpreter) run(code []byte) error {
return v.execute(code, 0)
}
// execute 执行字节码并限制递归与无限循环
// 入参: code 字节码, depth 调用深度
// 返回: error 指令错误
func (v *ttInterpreter) execute(code []byte, depth int) error {
if depth > 128 {
return fmt.Errorf("TrueType instruction call depth exceeded")
}
g := &v.graphics
for pc := 0; pc < len(code); {
v.steps++
if v.steps > 1000000 {
return fmt.Errorf("TrueType instruction execution limit exceeded")
}
start, op := pc, code[pc]
end, err := ttInstructionEnd(code, pc)
if err != nil {
return err
}
pc++
if op == 0x40 || op == 0x41 || op >= 0xb0 && op <= 0xbf {
word := op == 0x41 || op >= 0xb8
if op == 0x40 || op == 0x41 {
pc++
}
for pc < end {
if word {
v.push(int32(int16(binary.BigEndian.Uint16(code[pc:]))))
pc += 2
} else {
v.push(int32(code[pc]))
pc++
}
}
} else {
switch {
case op <= 5:
axis := [2]float64{0, 1}
if op&1 != 0 {
axis = [2]float64{1, 0}
}
if op < 4 {
g.projection, g.dual = axis, axis
}
if op < 2 || op >= 4 {
g.freedom = axis
}
case op >= 6 && op <= 9 || op == 0x86 || op == 0x87:
p1, p2 := v.point(g.zone[2], v.pop()), v.point(g.zone[1], v.pop())
direction := func(x, y int32) [2]float64 {
a, b := float64(x), float64(y)
if op&1 != 0 {
a, b = -b, a
}
n := math.Hypot(a, b)
if n == 0 {
return [2]float64{1, 0}
}
return [2]float64{a / n, b / n}
}
axis := direction(p2.x-p1.x, p2.y-p1.y)
if op == 8 || op == 9 {
g.freedom = axis
} else {
g.projection, g.dual = axis, axis
if op >= 0x86 {
g.dual = direction(p2.ox-p1.ox, p2.oy-p1.oy)
}
}
case op == 0x0a || op == 0x0b:
y, x := float64(int16(v.pop())), float64(int16(v.pop()))
length := math.Hypot(x, y)
if length == 0 {
return fmt.Errorf("zero TrueType vector")
}
axis := [2]float64{x / length, y / length}
if op == 0x0a {
g.projection, g.dual = axis, axis
} else {
g.freedom = axis
}
case op == 0x0c || op == 0x0d:
axis := g.projection
if op == 0x0d {
axis = g.freedom
}
v.push(int32(math.Round(axis[0]*16384)), int32(math.Round(axis[1]*16384)))
case op == 0x0e:
g.freedom = g.projection
case op == 0x0f:
v.intersect()
case op >= 0x10 && op <= 0x12:
g.reference[op-0x10] = v.pop()
case op >= 0x13 && op <= 0x16:
z := v.pop()
if z < 0 || z > 1 {
return fmt.Errorf("invalid TrueType zone")
}
if op == 0x16 {
g.zone = [3]int32{z, z, z}
} else {
g.zone[op-0x13] = z
}
case op == 0x17:
g.loop = v.pop()
if g.loop < 0 || int64(g.loop) > int64(v.stackLimit) {
return fmt.Errorf("invalid TrueType loop count")
}
case op == 0x18:
g.period, g.phase, g.threshold = 64, 0, 32
case op == 0x19:
g.period, g.phase, g.threshold = 64, 32, 32
case op == 0x1a:
g.minimum = v.pop()
case op == 0x1b:
pc, err = skipTTConditional(code, pc, false)
case op == 0x1c:
pc = start + int(v.pop())
case op == 0x1d:
g.cutIn = v.pop()
case op == 0x1e:
g.singleCutIn = v.pop()
case op == 0x1f:
g.singleWidth = v.pop() * 64
case op == 0x20:
a := v.pop()
v.push(a, a)
case op == 0x21:
v.pop()
case op == 0x22:
v.stack = v.stack[:0]
case op == 0x23:
a, b := v.pop(), v.pop()
v.push(a, b)
case op == 0x24:
v.push(int32(len(v.stack)))
case op == 0x25 || op == 0x26:
n := v.pop()
if n <= 0 || int64(n) > int64(len(v.stack)) {
return fmt.Errorf("invalid TrueType stack index")
}
i := len(v.stack) - int(n)
a := v.stack[i]
if op == 0x26 {
v.stack = append(v.stack[:i], v.stack[i+1:]...)
}
v.push(a)
case op == 0x27:
p2, p1 := v.point(g.zone[1], v.pop()), v.point(g.zone[0], v.pop())
d := ttProject(p2.x-p1.x, p2.y-p1.y, g.projection) / 2
v.move(p1, d, true)
v.move(p2, -d, true)
case op == 0x29:
p := v.point(g.zone[0], v.pop())
if g.freedom[0] != 0 {
p.tx = false
}
if g.freedom[1] != 0 {
p.ty = false
}
case op == 0x2a || op == 0x2b:
id := v.pop()
count := int32(1)
if op == 0x2a {
count = v.pop()
}
if count < 0 || count > 1000000 {
return fmt.Errorf("invalid TrueType function call %d", id)
}
body, ok := v.functions[id]
if !ok {
return fmt.Errorf("%w: function %d", errTTInvalidReference, id)
}
for n := int32(0); n < count; n++ {
if err = v.execute(body, depth+1); err != nil {
return fmt.Errorf("TrueType function %d: %w", id, err)
}
}
case op == 0x2c || op == 0x89:
id := v.pop()
bodyStart := pc
for pc < len(code) && code[pc] != 0x2d {
pc, err = ttInstructionEnd(code, pc)
if err != nil {
return err
}
}
if pc == len(code) || id < 0 || op == 0x89 && id > 255 {
return fmt.Errorf("invalid TrueType function definition")
}
if op == 0x2c {
v.functions[id] = code[bodyStart:pc]
} else {
v.instructions[byte(id)] = code[bodyStart:pc]
}
pc++
case op == 0x2d:
return fmt.Errorf("unexpected TrueType function terminator")
case op == 0x2e || op == 0x2f:
i := v.pop()
p := v.point(g.zone[0], i)
pos := ttProject(p.x, p.y, g.projection)
target := pos
if op&1 != 0 {
target = v.round(pos)
}
v.move(p, target-pos, true)
g.reference[0], g.reference[1] = i, i
case op == 0x30 || op == 0x31:
v.interpolateUntouched(op&1 != 0)
case op >= 0x32 && op <= 0x37:
v.shift(op)
case op == 0x38:
d := v.pop()
for n := int32(0); n < g.loop; n++ {
p := v.point(g.zone[2], v.pop())
p.x += int32(math.Round(float64(d) * g.freedom[0]))
p.y += int32(math.Round(float64(d) * g.freedom[1]))
p.tx = p.tx || g.freedom[0] != 0
p.ty = p.ty || g.freedom[1] != 0
}
g.loop = 1
case op == 0x39:
v.interpolatePoints()
case op == 0x3a || op == 0x3b:
d, i := v.pop(), v.pop()
p, r := v.point(g.zone[1], i), v.point(g.zone[0], g.reference[0])
if g.zone[1] == 0 {
p.ox = r.ox + int32(math.Round(float64(d)*g.freedom[0]))
p.oy = r.oy + int32(math.Round(float64(d)*g.freedom[1]))
p.x, p.y = p.ox, p.oy
}
v.move(p, d-ttProject(p.x-r.x, p.y-r.y, g.projection), true)
g.reference[1], g.reference[2] = g.reference[0], i
if op&1 != 0 {
g.reference[0] = i
}
case op == 0x3c:
r := v.point(g.zone[0], g.reference[0])
for n := int32(0); n < g.loop; n++ {
p := v.point(g.zone[1], v.pop())
v.move(p, ttProject(r.x-p.x, r.y-p.y, g.projection), true)
}
g.loop = 1
case op == 0x3d:
g.period, g.phase, g.threshold = 32, 0, 16
case op == 0x3e || op == 0x3f:
cv, i := v.pop(), v.pop()
p := v.point(g.zone[0], i)
target := v.read(v.cvt, cv)
if g.zone[0] == 0 {
p.x = int32(math.Round(float64(target) * g.freedom[0]))
p.y = int32(math.Round(float64(target) * g.freedom[1]))
p.ox, p.oy = p.x, p.y
}
pos := ttProject(p.x, p.y, g.projection)
if op&1 != 0 {
if ttAbs(target-pos) > g.cutIn {
target = pos
}
target = v.round(target)
}
v.move(p, target-pos, true)
g.reference[0], g.reference[1] = i, i
case op == 0x42 || op == 0x44 || op == 0x70:
value, index := v.pop(), v.pop()
target := v.cvt
if op == 0x42 {
target = v.storage
}
if op == 0x70 {
value *= 64
}
v.write(target, index, value)
case op == 0x43:
v.push(v.read(v.storage, v.pop()))
case op == 0x45:
v.push(v.read(v.cvt, v.pop()))
case op == 0x46 || op == 0x47:
p := v.point(g.zone[2], v.pop())
if op&1 != 0 {
v.push(ttProject(p.ox, p.oy, g.dual))
} else {
v.push(ttProject(p.x, p.y, g.projection))
}
case op == 0x48:
value := v.pop()
p := v.point(g.zone[2], v.pop())
v.move(p, value-ttProject(p.x, p.y, g.projection), true)
if g.zone[2] == 0 {
p.ox, p.oy = p.x, p.y
}
case op == 0x49 || op == 0x4a:
p1, p2 := v.point(g.zone[1], v.pop()), v.point(g.zone[0], v.pop())
if op&1 == 0 {
v.push(ttProject(p2.ox-p1.ox, p2.oy-p1.oy, g.dual))
} else {
v.push(ttProject(p2.x-p1.x, p2.y-p1.y, g.projection))
}
case op == 0x4b:
v.push(v.units)
case op == 0x4c:
v.push(v.units * 64)
case op == 0x4d:
g.autoFlip = true
case op == 0x4e:
g.autoFlip = false
case op == 0x4f:
v.pop()
case op >= 0x50 && op <= 0x55:
b, a := v.pop(), v.pop()
result := false
switch op {
case 0x50:
result = a < b
case 0x51:
result = a <= b
case 0x52:
result = a > b
case 0x53:
result = a >= b
case 0x54:
result = a == b
case 0x55:
result = a != b
}
v.push(ttBool(result))
case op == 0x56 || op == 0x57:
r := v.round(v.pop()) / 64 & 1
v.push(ttBool(r == int32(0x57-op)))
case op == 0x58:
if v.pop() == 0 {
pc, err = skipTTConditional(code, pc, true)
}
case op == 0x59:
case op == 0x5a || op == 0x5b:
b, a := v.pop() != 0, v.pop() != 0
if op == 0x5a {
v.push(ttBool(a && b))
} else {
v.push(ttBool(a || b))
}
case op == 0x5c:
v.push(ttBool(v.pop() == 0))
case op == 0x5d || op >= 0x71 && op <= 0x75:
v.delta(op)
case op == 0x5e:
g.deltaBase = v.pop()
case op == 0x5f:
g.deltaShift = v.pop()
if g.deltaShift < 0 || g.deltaShift > 6 {
return fmt.Errorf("invalid TrueType delta shift")
}
case op >= 0x60 && op <= 0x63:
b, a := v.pop(), v.pop()
switch op {
case 0x60:
v.push(a + b)
case 0x61:
v.push(a - b)
case 0x62:
if b == 0 {
return fmt.Errorf("TrueType division by zero")
}
v.push(int32(int64(a) * 64 / int64(b)))
case 0x63:
v.push(int32(int64(a) * int64(b) / 64))
}
case op == 0x64:
v.push(ttAbs(v.pop()))
case op == 0x65:
v.push(-v.pop())
case op == 0x66:
v.push(v.pop() &^ 63)
case op == 0x67:
v.push((v.pop() + 63) &^ 63)
case op >= 0x68 && op <= 0x6b:
v.push(v.round(v.pop()))
case op >= 0x6c && op <= 0x6f:
v.push(v.pop())
case op == 0x76 || op == 0x77:
s := v.pop()
period := 64.0
if op == 0x77 {
period = math.Sqrt(0.5) * 64
}
switch (s >> 6) & 3 {
case 0:
period /= 2
case 2:
period *= 2
case 3:
return fmt.Errorf("invalid TrueType rounding period")
}
g.period = int32(math.Round(period))
g.phase = int32(math.Round(float64((s>>4)&3) * period / 4))
if s&15 == 0 {
g.threshold = g.period - 1
} else {
g.threshold = int32(math.Round(float64((s&15)-4) * period / 8))
}
case op == 0x78 || op == 0x79:
condition, offset := v.pop(), v.pop()
if (condition != 0) == (op == 0x78) {
pc = start + int(offset)
}
case op == 0x7a:
g.period, g.phase, g.threshold = 0, 0, 0
case op == 0x7c:
g.period, g.phase, g.threshold = 64, 0, 63
case op == 0x7d:
g.period, g.phase, g.threshold = 64, 0, 0
case op == 0x7e || op == 0x7f:
v.pop()
case op == 0x80:
for n := int32(0); n < g.loop; n++ {
p := v.point(g.zone[0], v.pop())
p.on = !p.on
}
g.loop = 1
case op == 0x81 || op == 0x82:
hi, lo := v.pop(), v.pop()
if lo < 0 || hi < lo || int64(hi) >= int64(len(v.zones[g.zone[0]])) {
return fmt.Errorf("invalid TrueType point range")
}
for i := lo; i <= hi; i++ {
v.point(g.zone[0], i).on = op == 0x81
}
case op == 0x85 || op == 0x8d:
v.pop()
case op == 0x88:
selector := v.pop()
result := int32(0)
if selector&1 != 0 {
result = 35
}
if selector&32 != 0 {
result |= 1 << 12
}
v.push(result)
case op == 0x8a:
c, b, a := v.pop(), v.pop(), v.pop()
v.push(b, c, a)
case op == 0x8b:
b, a := v.pop(), v.pop()
v.push(max(a, b))
case op == 0x8c:
b, a := v.pop(), v.pop()
v.push(min(a, b))
case op == 0x8e:
selector, value := v.pop(), v.pop()
if selector == 1 {
g.inhibit = value != 0
} else if selector == 2 {
g.reset = value != 0
} else if selector != 3 {
return fmt.Errorf("invalid TrueType instruction control")
}
case op >= 0xc0:
v.relativeMove(op)
default:
body, ok := v.instructions[op]
if !ok {
return fmt.Errorf("unsupported TrueType instruction 0x%02x", op)
}
err = v.execute(body, depth+1)
}
}
if err != nil {
return err
}
if v.err != nil {
return fmt.Errorf("TrueType instruction 0x%02x at %d: %w", op, start, v.err)
}
if pc < 0 || pc > len(code) {
return fmt.Errorf("%w: jump target %d at %d in %d bytes", errTTInvalidControlFlow, pc, start, len(code))
}
}
return nil
}
// ttAbs 返回定点数绝对值
// 入参: value 定点数
// 返回: int32 绝对值
func ttAbs(value int32) int32 {
if value < 0 {
return -value
}
return value
}
// ttBool 将逻辑值转换为TrueType栈值
// 入参: value 逻辑值
// 返回: int32 栈值
func ttBool(value bool) int32 {
if value {
return 1
}
return 0
}
// relativeMove 执行直接或控制值驱动的相对移动
// 入参: op 移动指令
func (v *ttInterpreter) relativeMove(op byte) {
g := &v.graphics
var target int32
if op >= 0xe0 {
target = v.read(v.cvt, v.pop())
}
index := v.pop()
p, r := v.point(g.zone[1], index), v.point(g.zone[0], g.reference[0])
if op >= 0xe0 && g.zone[1] == 0 {
p.ox = r.ox + int32(math.Round(float64(target)*g.freedom[0]))
p.oy = r.oy + int32(math.Round(float64(target)*g.freedom[1]))
p.x, p.y = p.ox, p.oy
}
original := ttProject(p.ox-r.ox, p.oy-r.oy, g.dual)
if op < 0xe0 {
target = original
} else if g.autoFlip && (original < 0) != (target < 0) {
target = -target
}
if ttAbs(ttAbs(target)-g.singleWidth) < g.singleCutIn {
target = g.singleWidth
if original < 0 {
target = -target
}
}
if op&4 != 0 {
if op >= 0xe0 && g.zone[0] == g.zone[1] && ttAbs(target-original) > g.cutIn {
target = original
}
target = v.round(target)
}
if op&8 != 0 {
if original >= 0 {
target = max(target, g.minimum)
} else {
target = min(target, -g.minimum)
}
}
v.move(p, target-ttProject(p.x-r.x, p.y-r.y, g.projection), true)
g.reference[1], g.reference[2] = g.reference[0], index
if op&16 != 0 {
g.reference[0] = index
}
}
// shift 根据参考点位移平移指定点、轮廓或整个区
// 入参: op 平移指令
func (v *ttInterpreter) shift(op byte) {
g := &v.graphics
zone, index := g.zone[1], g.reference[2]
if op&1 != 0 {
zone, index = g.zone[0], g.reference[1]
}
r := v.point(zone, index)
distance := ttProject(r.x-r.ox, r.y-r.oy, g.projection)
if op <= 0x33 {
for i := int32(0); i < g.loop; i++ {
v.move(v.point(g.zone[2], v.pop()), distance, true)
}
g.loop = 1
return
}
from, to := 0, 0
target := g.zone[2]
if op <= 0x35 {
contour := v.pop()
if contour < 0 || int64(contour) >= int64(len(v.ends)) {
v.err = fmt.Errorf("invalid TrueType contour reference")
return
}
if contour > 0 {
from = int(v.ends[contour-1]) + 1
}
to = int(v.ends[contour]) + 1
} else {
target = v.pop()
if target < 0 || target > 1 {
v.err = fmt.Errorf("invalid TrueType zone")
return
}
to = len(v.zones[target])
if target == 1 {
to -= 4
}
}
for i := from; i < to; i++ {
if target == zone && i == int(index) {
continue
}
v.move(v.point(target, int32(i)), distance, op <= 0x35)
}
}
// interpolatePoints 保持点在两个参考点之间的原始相对比例
func (v *ttInterpreter) interpolatePoints() {
g := &v.graphics
r1, r2 := v.point(g.zone[0], g.reference[1]), v.point(g.zone[1], g.reference[2])
original := ttProject(r2.ox-r1.ox, r2.oy-r1.oy, g.dual)
current := ttProject(r2.x-r1.x, r2.y-r1.y, g.projection)
for i := int32(0); i < g.loop; i++ {
p := v.point(g.zone[2], v.pop())
d := ttProject(p.ox-r1.ox, p.oy-r1.oy, g.dual)
if original != 0 {
d = int32(math.Round(float64(d) * float64(current) / float64(original)))
}
v.move(p, d-ttProject(p.x-r1.x, p.y-r1.y, g.projection), true)
}
g.loop = 1
}
// interpolateUntouched 按轮廓循环插值未触及点,不改变独立轮廓
// 入参: horizontal 是否沿横轴插值
func (v *ttInterpreter) interpolateUntouched(horizontal bool) {
if v.graphics.zone[2] != 1 {
v.err = fmt.Errorf("invalid TrueType interpolation zone")
return
}
points := v.zones[1]
original := func(p ttPoint) int32 {
if horizontal {
return p.ox
}
return p.oy
}
current := func(p ttPoint) int32 {
if horizontal {
return p.x
}
return p.y
}
touched := func(p ttPoint) bool {
if horizontal {
return p.tx
}
return p.ty
}
set := func(p *ttPoint, value int32) {
if horizontal {
p.x = value
} else {
p.y = value
}
}
first := 0
for _, endpoint := range v.ends {
last := int(endpoint)
if last < first || last >= len(points) {
v.err = fmt.Errorf("invalid TrueType contour endpoints")
return
}
anchors := []int{}
for i := first; i <= last; i++ {
if touched(points[i]) {
anchors = append(anchors, i)
}
}
for n, left := range anchors {
right := anchors[(n+1)%len(anchors)]
a, b := points[left], points[right]
if original(a) > original(b) {
a, b = b, a
}
for i := (left-first+1)%(last-first+1) + first; i != right; i = (i-first+1)%(last-first+1) + first {
x := original(points[i])
var value int32
if len(anchors) == 1 || x <= original(a) {
value = x + current(a) - original(a)
} else if x >= original(b) {
value = x + current(b) - original(b)
} else {
value = current(a) + int32(math.Round(float64(x-original(a))*float64(current(b)-current(a))/float64(original(b)-original(a))))
}
set(&points[i], value)
}
}
first = last + 1
}
}
// delta 对指定像素尺寸应用局部点位或控制值修正
// 入参: op 修正指令
func (v *ttInterpreter) delta(op byte) {
n := v.pop()
if n < 0 || int64(n)*2 > int64(len(v.stack)) {
v.err = fmt.Errorf("invalid TrueType delta count")
return
}
base := v.graphics.deltaBase
if op == 0x71 || op == 0x74 {
base += 16
}
if op == 0x72 || op == 0x75 {
base += 32
}
for i := int32(0); i < n; i++ {
index, arg := v.pop(), v.pop()
if base+((arg>>4)&15) != v.units {
continue
}
step := (arg & 15) - 8
if step >= 0 {
step++
}
distance := step * (64 >> v.graphics.deltaShift)
if op >= 0x73 {
v.write(v.cvt, index, v.read(v.cvt, index)+distance)
} else {
v.move(v.point(v.graphics.zone[0], index), distance, true)
}
}
}
// intersect 将目标点移到两条直线的交点,平行线使用端点平均值
func (v *ttInterpreter) intersect() {
g := &v.graphics
b1, b0 := v.point(g.zone[0], v.pop()), v.point(g.zone[0], v.pop())
a1, a0 := v.point(g.zone[1], v.pop()), v.point(g.zone[1], v.pop())
p := v.point(g.zone[2], v.pop())
ax, ay, bx, by := float64(a1.x-a0.x), float64(a1.y-a0.y), float64(b1.x-b0.x), float64(b1.y-b0.y)
det := ax*by - ay*bx
if math.Abs(det) < 1 {
p.x = int32((int64(a0.x) + int64(a1.x) + int64(b0.x) + int64(b1.x)) / 4)
p.y = int32((int64(a0.y) + int64(a1.y) + int64(b0.y) + int64(b1.y)) / 4)
} else {
t := (float64(b0.x-a0.x)*by - float64(b0.y-a0.y)*bx) / det
p.x = a0.x + int32(math.Round(t*ax))
p.y = a0.y + int32(math.Round(t*ay))
}
p.tx, p.ty = true, true
}