mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
923 lines
26 KiB
Go
923 lines
26 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 (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
)
|
|
|
|
// type1StandardNames 保存Adobe标准编码的字形名称,用于复合字形引用
|
|
var type1StandardNames = func() [256]string {
|
|
var names [256]string
|
|
copy(names[32:127], cffStandardStrings[1:96])
|
|
for code, name := range map[byte]string{
|
|
161: "exclamdown", 162: "cent", 163: "sterling", 164: "fraction", 165: "yen", 166: "florin", 167: "section", 168: "currency",
|
|
169: "quotesingle", 170: "quotedblleft", 171: "guillemotleft", 172: "guilsinglleft", 173: "guilsinglright", 174: "fi", 175: "fl",
|
|
177: "endash", 178: "dagger", 179: "daggerdbl", 180: "periodcentered", 182: "paragraph", 183: "bullet", 184: "quotesinglbase",
|
|
185: "quotedblbase", 186: "quotedblright", 187: "guillemotright", 188: "ellipsis", 189: "perthousand", 191: "questiondown",
|
|
193: "grave", 194: "acute", 195: "circumflex", 196: "tilde", 197: "macron", 198: "breve", 199: "dotaccent", 200: "dieresis",
|
|
202: "ring", 203: "cedilla", 205: "hungarumlaut", 206: "ogonek", 207: "caron", 208: "emdash", 225: "AE", 227: "ordfeminine",
|
|
232: "Lslash", 233: "Oslash", 234: "OE", 235: "ordmasculine", 241: "ae", 245: "dotlessi", 248: "lslash", 249: "oslash", 250: "oe", 251: "germandbls",
|
|
} {
|
|
names[code] = name
|
|
}
|
|
return names
|
|
}()
|
|
|
|
// type1Program 保存解密后的Type1字形与子程序
|
|
type type1Program struct {
|
|
glyphs []type1Glyph
|
|
subrs [][]byte
|
|
lenIV int
|
|
matrix []float64
|
|
}
|
|
|
|
// type1Glyph 保存字形名称及指令
|
|
type type1Glyph struct {
|
|
name string
|
|
data []byte
|
|
}
|
|
|
|
// type1Scanner 按词项和显式长度读取Type1程序
|
|
type type1Scanner struct {
|
|
data []byte
|
|
pos int
|
|
}
|
|
|
|
// type1Outline 保存字形指令的操作数栈及轮廓位置
|
|
type type1Outline struct {
|
|
program *type1Program
|
|
steps *int
|
|
active map[string]bool
|
|
args []float64
|
|
postscript []float64
|
|
flexPoints [][2]float64
|
|
width float64
|
|
sideX float64
|
|
sideY float64
|
|
originX float64
|
|
originY float64
|
|
x float64
|
|
y float64
|
|
pointX float64
|
|
pointY float64
|
|
open bool
|
|
defined bool
|
|
flex bool
|
|
path bytes.Buffer
|
|
}
|
|
|
|
// parseType1Program 读取Type1字体的字形程序及子程序
|
|
// 入参: data PFA或PFB字体数据
|
|
// 返回: type1Program 字形和子程序, error 错误信息
|
|
func parseType1Program(data []byte) (type1Program, error) {
|
|
var err error
|
|
data, err = flattenType1(data)
|
|
if err != nil {
|
|
return type1Program{}, err
|
|
}
|
|
matrix, start, err := readType1Header(data)
|
|
if err != nil {
|
|
return type1Program{}, err
|
|
}
|
|
for start < len(data) && type1Space(data[start]) {
|
|
start++
|
|
}
|
|
if start == len(data) {
|
|
return type1Program{}, fmt.Errorf("empty Type1 eexec data")
|
|
}
|
|
encrypted := data[start:]
|
|
if type1HexEncoded(encrypted) {
|
|
end := bytes.Index(encrypted, []byte("cleartomark"))
|
|
if end >= 0 {
|
|
encrypted = encrypted[:end]
|
|
}
|
|
digits := make([]byte, 0, len(encrypted))
|
|
for _, c := range encrypted {
|
|
if type1Space(c) {
|
|
continue
|
|
}
|
|
if !type1HexDigit(c) {
|
|
break
|
|
}
|
|
digits = append(digits, c)
|
|
}
|
|
encrypted = make([]byte, hex.DecodedLen(len(digits)))
|
|
if _, err := hex.Decode(encrypted, digits); err != nil {
|
|
return type1Program{}, fmt.Errorf("invalid Type1 eexec encoding: %w", err)
|
|
}
|
|
}
|
|
if len(encrypted) < 4 {
|
|
return type1Program{}, fmt.Errorf("invalid Type1 eexec data")
|
|
}
|
|
decrypted := type1Decrypt(encrypted, 55665)[4:]
|
|
scanner := type1Scanner{data: decrypted}
|
|
program := type1Program{lenIV: 4, matrix: matrix}
|
|
section := ""
|
|
for {
|
|
token := scanner.next()
|
|
if token == "" || token == "closefile" {
|
|
break
|
|
}
|
|
switch token {
|
|
case "/lenIV":
|
|
value, err := strconv.Atoi(scanner.next())
|
|
if err != nil || value < -1 {
|
|
return type1Program{}, fmt.Errorf("invalid Type1 lenIV")
|
|
}
|
|
program.lenIV = value
|
|
case "/Subrs":
|
|
section = "subrs"
|
|
case "/CharStrings":
|
|
section = "glyphs"
|
|
case "def", "ND", "|-":
|
|
if section == "subrs" {
|
|
section = ""
|
|
}
|
|
case "dup":
|
|
if section != "subrs" {
|
|
continue
|
|
}
|
|
index, err := strconv.Atoi(scanner.next())
|
|
if err != nil || index < 0 || index > len(decrypted) {
|
|
return type1Program{}, fmt.Errorf("invalid Type1 subroutine index")
|
|
}
|
|
length, err := strconv.Atoi(scanner.next())
|
|
if err != nil {
|
|
return type1Program{}, fmt.Errorf("invalid Type1 subroutine length")
|
|
}
|
|
if !type1BinaryStart(scanner.next()) {
|
|
return type1Program{}, fmt.Errorf("missing Type1 subroutine data")
|
|
}
|
|
raw, err := scanner.binary(length)
|
|
if err != nil {
|
|
return type1Program{}, err
|
|
}
|
|
for len(program.subrs) <= index {
|
|
program.subrs = append(program.subrs, nil)
|
|
}
|
|
program.subrs[index] = raw
|
|
default:
|
|
if section != "glyphs" || len(token) < 2 || token[0] != '/' {
|
|
continue
|
|
}
|
|
name := token[1:]
|
|
next := scanner.next()
|
|
length, err := strconv.Atoi(next)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if !type1BinaryStart(scanner.next()) {
|
|
return type1Program{}, fmt.Errorf("missing Type1 glyph data")
|
|
}
|
|
raw, err := scanner.binary(length)
|
|
if err != nil {
|
|
return type1Program{}, err
|
|
}
|
|
program.glyphs = append(program.glyphs, type1Glyph{name: name, data: raw})
|
|
}
|
|
}
|
|
if len(program.glyphs) == 0 {
|
|
return type1Program{}, fmt.Errorf("Type1 font has no glyphs")
|
|
}
|
|
for index := range program.subrs {
|
|
program.subrs[index], err = type1CharString(program.subrs[index], program.lenIV)
|
|
if err != nil {
|
|
return type1Program{}, err
|
|
}
|
|
}
|
|
for index := range program.glyphs {
|
|
program.glyphs[index].data, err = type1CharString(program.glyphs[index].data, program.lenIV)
|
|
if err != nil {
|
|
return type1Program{}, fmt.Errorf("Type1 glyph %s: %w", program.glyphs[index].name, err)
|
|
}
|
|
}
|
|
return program, nil
|
|
}
|
|
|
|
// readType1Header 读取明文字体矩阵及加密数据起点
|
|
// 入参: data Type1程序
|
|
// 返回: []float64 字体矩阵, int 加密起点, error 头部错误
|
|
func readType1Header(data []byte) ([]float64, int, error) {
|
|
scanner := type1Scanner{data: data}
|
|
var matrix []float64
|
|
for {
|
|
switch scanner.next() {
|
|
case "":
|
|
return nil, 0, fmt.Errorf("Type1 font lacks eexec data")
|
|
case "eexec":
|
|
return matrix, scanner.pos, nil
|
|
case "/FontMatrix":
|
|
closing := ""
|
|
switch scanner.next() {
|
|
case "[":
|
|
closing = "]"
|
|
case "{":
|
|
closing = "}"
|
|
default:
|
|
return nil, 0, fmt.Errorf("invalid Type1 FontMatrix array")
|
|
}
|
|
matrix = make([]float64, 6)
|
|
for index := range matrix {
|
|
value, err := strconv.ParseFloat(scanner.next(), 64)
|
|
if err != nil || !finite(value) {
|
|
return nil, 0, fmt.Errorf("invalid Type1 FontMatrix value")
|
|
}
|
|
matrix[index] = value
|
|
}
|
|
if scanner.next() != closing {
|
|
return nil, 0, fmt.Errorf("invalid Type1 FontMatrix length")
|
|
}
|
|
if _, err := readCFFMatrix(cffDict{1207: matrix}, [6]float64{}); err != nil {
|
|
return nil, 0, fmt.Errorf("invalid Type1 FontMatrix: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// flattenType1 将PFB分段容器转换为连续的Type1程序
|
|
// 入参: data 字体数据
|
|
// 返回: []byte 连续程序, error 错误信息
|
|
func flattenType1(data []byte) ([]byte, error) {
|
|
if len(data) < 2 || data[0] != 0x80 {
|
|
return data, nil
|
|
}
|
|
var program []byte
|
|
for len(data) >= 2 && data[0] == 0x80 {
|
|
if data[1] == 3 {
|
|
return program, nil
|
|
}
|
|
if len(data) < 6 || data[1] != 1 && data[1] != 2 {
|
|
return nil, fmt.Errorf("invalid PFB segment")
|
|
}
|
|
length := binary.LittleEndian.Uint32(data[2:6])
|
|
if uint64(length) > uint64(len(data)-6) {
|
|
return nil, fmt.Errorf("truncated PFB segment")
|
|
}
|
|
program = append(program, data[6:6+length]...)
|
|
data = data[6+length:]
|
|
}
|
|
return nil, fmt.Errorf("missing PFB end marker")
|
|
}
|
|
|
|
// type1Decrypt 解密Type1的eexec或字形程序
|
|
// 入参: data 密文, seed 初始密钥
|
|
// 返回: []byte 明文
|
|
func type1Decrypt(data []byte, seed uint16) []byte {
|
|
out := make([]byte, len(data))
|
|
for index, value := range data {
|
|
out[index] = value ^ byte(seed>>8)
|
|
seed = uint16((uint32(value)+uint32(seed))*52845 + 22719)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// type1CharString 解密字形程序并移除lenIV随机前缀
|
|
// 入参: data 字形密文, lenIV 前缀长度
|
|
// 返回: []byte 指令数据, error 错误信息
|
|
func type1CharString(data []byte, lenIV int) ([]byte, error) {
|
|
if lenIV < 0 {
|
|
return data, nil
|
|
}
|
|
if len(data) < lenIV {
|
|
return nil, fmt.Errorf("truncated Type1 charstring")
|
|
}
|
|
return type1Decrypt(data, 4330)[lenIV:], nil
|
|
}
|
|
|
|
// type1HexEncoded 区分eexec十六进制与二进制封装
|
|
// 入参: data eexec数据
|
|
// 返回: bool 是否为十六进制
|
|
func type1HexEncoded(data []byte) bool {
|
|
count := 0
|
|
for _, value := range data {
|
|
if type1Space(value) {
|
|
continue
|
|
}
|
|
if !type1HexDigit(value) {
|
|
return false
|
|
}
|
|
count++
|
|
if count == 8 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// type1HexDigit 判断字节是否为十六进制数字
|
|
// 入参: value 待检查的字节
|
|
// 返回: bool 是否为十六进制数字
|
|
func type1HexDigit(value byte) bool {
|
|
return value >= '0' && value <= '9' || value >= 'a' && value <= 'f' || value >= 'A' && value <= 'F'
|
|
}
|
|
|
|
// type1Space 判断PostScript空白字节
|
|
// 入参: value 待检查的字节
|
|
// 返回: bool 是否为空白字节
|
|
func type1Space(value byte) bool {
|
|
return value == ' ' || value == '\t' || value == '\r' || value == '\n' || value == '\f' || value == 0
|
|
}
|
|
|
|
// next 读取下一个PostScript词项,字形二进制块由binary处理
|
|
// 返回: string 词项
|
|
func (s *type1Scanner) next() string {
|
|
for s.pos < len(s.data) {
|
|
if type1Space(s.data[s.pos]) {
|
|
s.pos++
|
|
continue
|
|
}
|
|
if s.data[s.pos] == '%' {
|
|
for s.pos < len(s.data) && s.data[s.pos] != '\n' && s.data[s.pos] != '\r' {
|
|
s.pos++
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
if s.pos == len(s.data) {
|
|
return ""
|
|
}
|
|
start := s.pos
|
|
if s.data[s.pos] == '(' {
|
|
depth := 1
|
|
s.pos++
|
|
for s.pos < len(s.data) && depth > 0 {
|
|
switch s.data[s.pos] {
|
|
case '\\':
|
|
if s.pos+1 < len(s.data) {
|
|
s.pos++
|
|
}
|
|
case '(':
|
|
depth++
|
|
case ')':
|
|
depth--
|
|
}
|
|
s.pos++
|
|
}
|
|
return string(s.data[start:s.pos])
|
|
}
|
|
if s.data[s.pos] == '<' && (s.pos+1 == len(s.data) || s.data[s.pos+1] != '<') {
|
|
for s.pos < len(s.data) && s.data[s.pos] != '>' {
|
|
s.pos++
|
|
}
|
|
if s.pos < len(s.data) {
|
|
s.pos++
|
|
}
|
|
return string(s.data[start:s.pos])
|
|
}
|
|
if bytes.IndexByte([]byte("[]{}()<>"), s.data[s.pos]) >= 0 {
|
|
s.pos++
|
|
return string(s.data[start:s.pos])
|
|
}
|
|
if s.data[s.pos] == '/' {
|
|
s.pos++
|
|
}
|
|
for s.pos < len(s.data) && !type1Space(s.data[s.pos]) && bytes.IndexByte([]byte("[]{}%()/<>"), s.data[s.pos]) < 0 {
|
|
s.pos++
|
|
}
|
|
return string(s.data[start:s.pos])
|
|
}
|
|
|
|
// binary 按PostScript长度读取RD后的加密字节
|
|
// 入参: length 数据长度
|
|
// 返回: []byte 二进制数据, error 错误信息
|
|
func (s *type1Scanner) binary(length int) ([]byte, error) {
|
|
if s.pos >= len(s.data) || !type1Space(s.data[s.pos]) || length < 0 {
|
|
return nil, fmt.Errorf("invalid Type1 binary data")
|
|
}
|
|
if s.data[s.pos] == '\r' && s.pos+1 < len(s.data) && s.data[s.pos+1] == '\n' {
|
|
s.pos += 2
|
|
} else {
|
|
s.pos++
|
|
}
|
|
if length > len(s.data)-s.pos {
|
|
return nil, fmt.Errorf("truncated Type1 binary data")
|
|
}
|
|
data := s.data[s.pos : s.pos+length]
|
|
s.pos += length
|
|
return data, nil
|
|
}
|
|
|
|
// type1BinaryStart 判断Type1二进制数据起始操作符
|
|
// 入参: token PostScript词项
|
|
// 返回: bool 是否为二进制数据起始操作符
|
|
func type1BinaryStart(token string) bool {
|
|
return token == "RD" || token == "-|"
|
|
}
|
|
|
|
// outline 将Type1字形指令转换为保留三次曲线的Type2程序
|
|
// 入参: glyph 字形数据
|
|
// 返回: []byte Type2字形程序, error 错误信息
|
|
func (p *type1Program) outline(glyph type1Glyph) ([]byte, error) {
|
|
state := type1Outline{program: p, active: map[string]bool{glyph.name: true}}
|
|
ended, err := state.run(glyph.data, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Type1 glyph %s: %w", glyph.name, err)
|
|
}
|
|
if !ended || !state.defined {
|
|
return nil, fmt.Errorf("Type1 glyph %s has no complete outline", glyph.name)
|
|
}
|
|
var result bytes.Buffer
|
|
if err := encodeType2Number(&result, state.width); err != nil {
|
|
return nil, err
|
|
}
|
|
result.Write(state.path.Bytes())
|
|
result.WriteByte(14)
|
|
return result.Bytes(), nil
|
|
}
|
|
|
|
// run 执行Type1字形或子程序,遇到endchar返回完成状态
|
|
// 入参: data 指令数据, depth 子程序深度
|
|
// 返回: bool 是否遇到endchar, error 错误信息
|
|
func (s *type1Outline) run(data []byte, depth int) (bool, error) {
|
|
if depth > 32 {
|
|
return false, fmt.Errorf("Type1 subroutine recursion")
|
|
}
|
|
if s.steps == nil {
|
|
s.steps = new(int)
|
|
}
|
|
for index := 0; index < len(data); index++ {
|
|
(*s.steps)++
|
|
if *s.steps > 1<<20 {
|
|
return false, fmt.Errorf("Type1 execution limit exceeded")
|
|
}
|
|
op := int(data[index])
|
|
if op >= 32 {
|
|
var value float64
|
|
switch {
|
|
case op <= 246:
|
|
value = float64(op - 139)
|
|
case op <= 250:
|
|
if index+1 >= len(data) {
|
|
return false, fmt.Errorf("truncated Type1 operand")
|
|
}
|
|
index++
|
|
value = float64((op-247)*256 + int(data[index]) + 108)
|
|
case op <= 254:
|
|
if index+1 >= len(data) {
|
|
return false, fmt.Errorf("truncated Type1 operand")
|
|
}
|
|
index++
|
|
value = float64(-(op-251)*256 - int(data[index]) - 108)
|
|
default:
|
|
if index+4 >= len(data) {
|
|
return false, fmt.Errorf("truncated Type1 operand")
|
|
}
|
|
value = float64(int32(binary.BigEndian.Uint32(data[index+1 : index+5])))
|
|
index += 4
|
|
}
|
|
if len(s.args) >= 24 {
|
|
return false, fmt.Errorf("Type1 operand limit exceeded")
|
|
}
|
|
s.args = append(s.args, value)
|
|
continue
|
|
}
|
|
if op == 12 {
|
|
if index+1 >= len(data) {
|
|
return false, fmt.Errorf("truncated Type1 operator")
|
|
}
|
|
index++
|
|
op = 1200 + int(data[index])
|
|
}
|
|
if op == 10 {
|
|
if len(s.args) == 0 {
|
|
return false, fmt.Errorf("Type1 callsubr has no index")
|
|
}
|
|
value := s.args[len(s.args)-1]
|
|
index := int(value)
|
|
s.args = s.args[:len(s.args)-1]
|
|
if value != float64(index) || index < 0 || index >= len(s.program.subrs) || s.program.subrs[index] == nil {
|
|
return false, fmt.Errorf("invalid Type1 subroutine index %d", index)
|
|
}
|
|
ended, err := s.run(s.program.subrs[index], depth+1)
|
|
if err != nil || ended {
|
|
return ended, err
|
|
}
|
|
continue
|
|
}
|
|
if op == 11 {
|
|
if depth == 0 {
|
|
return false, fmt.Errorf("Type1 return outside subroutine")
|
|
}
|
|
return false, nil
|
|
}
|
|
if op == 14 {
|
|
return true, nil
|
|
}
|
|
if err := s.operator(op); err != nil {
|
|
return false, err
|
|
}
|
|
if op == 1206 {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, fmt.Errorf("unterminated Type1 charstring")
|
|
}
|
|
|
|
// operator 执行单个Type1绘制指令并清空已消费的操作数
|
|
// 入参: op 指令编号
|
|
// 返回: error 错误信息
|
|
func (s *type1Outline) operator(op int) error {
|
|
a := s.args
|
|
s.args = a[:0]
|
|
switch op {
|
|
case 1200:
|
|
s.args = a
|
|
return nil
|
|
case 1, 3, 1201, 1202:
|
|
return nil
|
|
case 13:
|
|
if s.defined || len(a) != 2 {
|
|
return fmt.Errorf("invalid Type1 hsbw")
|
|
}
|
|
s.x, s.width, s.defined = a[0], a[1], true
|
|
s.sideX = s.x
|
|
s.x, s.y = s.x+s.originX, s.originY
|
|
case 1207:
|
|
if s.defined || len(a) != 4 || a[3] != 0 {
|
|
return fmt.Errorf("invalid Type1 sbw")
|
|
}
|
|
s.x, s.y, s.width, s.defined = a[0], a[1], a[2], true
|
|
s.sideX, s.sideY = s.x, s.y
|
|
s.x, s.y = s.x+s.originX, s.y+s.originY
|
|
case 4, 21, 22:
|
|
if !s.defined || op == 4 && len(a) != 1 || op == 21 && len(a) != 2 || op == 22 && len(a) != 1 {
|
|
return fmt.Errorf("invalid Type1 move")
|
|
}
|
|
if s.flex {
|
|
point := [2]float64{}
|
|
if op == 4 || op == 21 {
|
|
point[1] = a[len(a)-1]
|
|
}
|
|
if op == 21 || op == 22 {
|
|
point[0] = a[0]
|
|
}
|
|
s.flexPoints = append(s.flexPoints, point)
|
|
return nil
|
|
}
|
|
if op == 4 || op == 21 {
|
|
s.y += a[len(a)-1]
|
|
}
|
|
if op == 21 || op == 22 {
|
|
s.x += a[0]
|
|
}
|
|
return s.move()
|
|
case 5, 6, 7:
|
|
if !s.defined || op == 5 && (len(a) == 0 || len(a)%2 != 0) || op != 5 && len(a) == 0 {
|
|
return fmt.Errorf("invalid Type1 line")
|
|
}
|
|
if err := s.ensureMove(); err != nil {
|
|
return err
|
|
}
|
|
for n := 0; n < len(a); {
|
|
var dx, dy float64
|
|
switch op {
|
|
case 5:
|
|
dx, dy = a[n], a[n+1]
|
|
n += 2
|
|
case 6:
|
|
dx = a[n]
|
|
n++
|
|
case 7:
|
|
dy = a[n]
|
|
n++
|
|
}
|
|
if err := s.line(dx, dy); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case 8, 30, 31:
|
|
count := 6
|
|
if op != 8 {
|
|
count = 4
|
|
}
|
|
if !s.defined || len(a) == 0 || len(a)%count != 0 {
|
|
return fmt.Errorf("invalid Type1 curve")
|
|
}
|
|
if err := s.ensureMove(); err != nil {
|
|
return err
|
|
}
|
|
for n := 0; n < len(a); n += count {
|
|
var curve [6]float64
|
|
switch op {
|
|
case 8:
|
|
copy(curve[:], a[n:n+6])
|
|
case 30:
|
|
curve = [6]float64{0, a[n], a[n+1], a[n+2], a[n+3], 0}
|
|
case 31:
|
|
curve = [6]float64{a[n], 0, a[n+1], a[n+2], 0, a[n+3]}
|
|
}
|
|
if err := s.curve(curve); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case 9:
|
|
s.open = false
|
|
case 1212:
|
|
if len(a) < 2 || a[len(a)-1] == 0 {
|
|
return fmt.Errorf("invalid Type1 division")
|
|
}
|
|
a[len(a)-2] /= a[len(a)-1]
|
|
s.args = a[:len(a)-1]
|
|
case 1216:
|
|
if len(a) < 2 {
|
|
return fmt.Errorf("invalid Type1 callothersubr")
|
|
}
|
|
count, number := int(a[len(a)-2]), int(a[len(a)-1])
|
|
if float64(count) != a[len(a)-2] || float64(number) != a[len(a)-1] || count < 0 || count > len(a)-2 {
|
|
return fmt.Errorf("invalid Type1 callothersubr arguments")
|
|
}
|
|
s.args = a[:len(a)-count-2]
|
|
a = a[len(a)-count-2 : len(a)-2]
|
|
switch number {
|
|
case 0:
|
|
if !s.flex || count != 3 || len(s.flexPoints) != 7 {
|
|
return fmt.Errorf("invalid Type1 Flex end")
|
|
}
|
|
if err := s.ensureMove(); err != nil {
|
|
return err
|
|
}
|
|
first := s.flexPoints
|
|
if err := s.curve([6]float64{first[0][0] + first[1][0], first[0][1] + first[1][1], first[2][0], first[2][1], first[3][0], first[3][1]}); err != nil {
|
|
return err
|
|
}
|
|
if err := s.curve([6]float64{first[4][0], first[4][1], first[5][0], first[5][1], first[6][0], first[6][1]}); err != nil {
|
|
return err
|
|
}
|
|
if s.x != a[1]+s.originX || s.y != a[2]+s.originY {
|
|
return fmt.Errorf("Type1 Flex endpoint differs from outline")
|
|
}
|
|
s.postscript = []float64{s.x - s.originX, s.y - s.originY}
|
|
s.flexPoints = nil
|
|
s.flex = false
|
|
case 1:
|
|
if count != 0 || s.flex {
|
|
return fmt.Errorf("invalid Type1 Flex start")
|
|
}
|
|
s.flex = true
|
|
s.flexPoints = nil
|
|
case 2:
|
|
if count != 0 || !s.flex {
|
|
return fmt.Errorf("invalid Type1 Flex point")
|
|
}
|
|
case 3:
|
|
if count != 1 {
|
|
return fmt.Errorf("invalid Type1 hint replacement")
|
|
}
|
|
s.postscript = []float64{a[0]}
|
|
default:
|
|
return fmt.Errorf("unsupported Type1 OtherSubr %d", number)
|
|
}
|
|
case 1217:
|
|
if len(s.postscript) == 0 || len(a) >= 24 {
|
|
return fmt.Errorf("invalid Type1 pop")
|
|
}
|
|
s.args = append(a, s.postscript[0])
|
|
s.postscript = s.postscript[1:]
|
|
case 1233:
|
|
if len(a) != 2 {
|
|
return fmt.Errorf("invalid Type1 setcurrentpoint")
|
|
}
|
|
s.x, s.y = a[0]+s.originX, a[1]+s.originY
|
|
case 1206:
|
|
if !s.defined || len(a) != 5 || s.path.Len() != 0 {
|
|
return fmt.Errorf("invalid Type1 seac")
|
|
}
|
|
if err := s.component(a[3], s.originX, s.originY); err != nil {
|
|
return err
|
|
}
|
|
return s.component(a[4], s.originX+s.sideX+a[1]-a[0], s.originY+s.sideY+a[2])
|
|
default:
|
|
return fmt.Errorf("unsupported Type1 operator %d", op)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// component 将标准编码引用的字形展开为当前Type2轮廓,保留复合字形字宽
|
|
// 入参: code 标准编码, x 横向原点, y 纵向原点
|
|
// 返回: error 字形缺失、循环或指令错误
|
|
func (s *type1Outline) component(code, x, y float64) error {
|
|
if len(s.active) >= 32 {
|
|
return fmt.Errorf("Type1 component recursion")
|
|
}
|
|
if code < 0 || code > 255 || code != math.Trunc(code) {
|
|
return fmt.Errorf("invalid Type1 component code")
|
|
}
|
|
name := type1StandardNames[byte(code)]
|
|
if name == "" {
|
|
return fmt.Errorf("undefined Type1 component code %g", code)
|
|
}
|
|
if s.active[name] {
|
|
return fmt.Errorf("cyclic Type1 component %s", name)
|
|
}
|
|
for _, glyph := range s.program.glyphs {
|
|
if glyph.name != name {
|
|
continue
|
|
}
|
|
child := type1Outline{program: s.program, steps: s.steps, active: s.active, originX: x, originY: y, pointX: s.pointX, pointY: s.pointY}
|
|
s.active[name] = true
|
|
ended, err := child.run(glyph.data, 0)
|
|
delete(s.active, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ended || !child.defined {
|
|
return fmt.Errorf("incomplete Type1 component %s", name)
|
|
}
|
|
s.path.Write(child.path.Bytes())
|
|
s.pointX, s.pointY = child.pointX, child.pointY
|
|
return nil
|
|
}
|
|
return fmt.Errorf("missing Type1 component %s", name)
|
|
}
|
|
|
|
// move 在Type2字形程序中建立新轮廓
|
|
// 返回: error 错误信息
|
|
func (s *type1Outline) move() error {
|
|
if err := encodeType2Number(&s.path, s.x-s.pointX); err != nil {
|
|
return err
|
|
}
|
|
if err := encodeType2Number(&s.path, s.y-s.pointY); err != nil {
|
|
return err
|
|
}
|
|
s.path.WriteByte(21)
|
|
s.pointX, s.pointY, s.open = s.x, s.y, true
|
|
return nil
|
|
}
|
|
|
|
// ensureMove 在首次描绘或关闭轮廓后建立当前位置
|
|
// 返回: error 错误信息
|
|
func (s *type1Outline) ensureMove() error {
|
|
if !s.open || s.x != s.pointX || s.y != s.pointY {
|
|
return s.move()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// line 保留Type1线段的实际终点
|
|
// 入参: dx 横向增量, dy 纵向增量
|
|
// 返回: error 错误信息
|
|
func (s *type1Outline) line(dx, dy float64) error {
|
|
if err := encodeType2Number(&s.path, dx); err != nil {
|
|
return err
|
|
}
|
|
if err := encodeType2Number(&s.path, dy); err != nil {
|
|
return err
|
|
}
|
|
s.path.WriteByte(5)
|
|
s.x += dx
|
|
s.y += dy
|
|
s.pointX, s.pointY = s.x, s.y
|
|
return nil
|
|
}
|
|
|
|
// curve 保留Type1三次贝塞尔控制点
|
|
// 入参: values 六个相对坐标
|
|
// 返回: error 错误信息
|
|
func (s *type1Outline) curve(values [6]float64) error {
|
|
for _, value := range values {
|
|
if err := encodeType2Number(&s.path, value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.path.WriteByte(8)
|
|
s.x += values[0] + values[2] + values[4]
|
|
s.y += values[1] + values[3] + values[5]
|
|
s.pointX, s.pointY = s.x, s.y
|
|
return nil
|
|
}
|
|
|
|
// encodeType2Number 编码Type2字形指令的有符号定点操作数
|
|
// 入参: target 目标字节流, value 操作数
|
|
// 返回: error 错误信息
|
|
func encodeType2Number(target *bytes.Buffer, value float64) error {
|
|
if !finite(value) || value < -32768 || value >= 32768 {
|
|
return fmt.Errorf("Type2 operand out of range")
|
|
}
|
|
if value == math.Trunc(value) {
|
|
integer := int(value)
|
|
switch {
|
|
case integer >= -107 && integer <= 107:
|
|
target.WriteByte(byte(integer + 139))
|
|
case integer >= 108 && integer <= 1131:
|
|
integer -= 108
|
|
target.WriteByte(byte(247 + integer/256))
|
|
target.WriteByte(byte(integer % 256))
|
|
case integer <= -108 && integer >= -1131:
|
|
integer = -integer - 108
|
|
target.WriteByte(byte(251 + integer/256))
|
|
target.WriteByte(byte(integer % 256))
|
|
default:
|
|
target.WriteByte(28)
|
|
_ = binary.Write(target, binary.BigEndian, int16(integer))
|
|
}
|
|
return nil
|
|
}
|
|
fixed := math.Round(value * 65536)
|
|
if fixed < math.MinInt32 || fixed > math.MaxInt32 {
|
|
return fmt.Errorf("Type2 fixed operand out of range")
|
|
}
|
|
target.WriteByte(255)
|
|
_ = binary.Write(target, binary.BigEndian, int32(fixed))
|
|
return nil
|
|
}
|
|
|
|
// toCFF 将Type1字形转换为保持三次轮廓的CFF字体
|
|
// 入参: name 字体名称
|
|
// 返回: []byte CFF数据, error 错误信息
|
|
func (p *type1Program) toCFF(name string) ([]byte, error) {
|
|
if len(p.glyphs) > math.MaxUint16 || name == "" {
|
|
return nil, fmt.Errorf("invalid Type1 font identity")
|
|
}
|
|
ordered := make([]type1Glyph, 0, len(p.glyphs))
|
|
for _, glyph := range p.glyphs {
|
|
if glyph.name == ".notdef" {
|
|
ordered = append(ordered, glyph)
|
|
break
|
|
}
|
|
}
|
|
if len(ordered) == 0 {
|
|
return nil, fmt.Errorf("Type1 font lacks .notdef glyph")
|
|
}
|
|
for _, glyph := range p.glyphs {
|
|
if glyph.name != ".notdef" {
|
|
ordered = append(ordered, glyph)
|
|
}
|
|
}
|
|
chars := make([][]byte, len(ordered))
|
|
strings := make([][]byte, 0, len(ordered)-1)
|
|
charset := make([]byte, 1+2*(len(ordered)-1))
|
|
for index, glyph := range ordered {
|
|
charstring, err := p.outline(glyph)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
chars[index] = charstring
|
|
if index == 0 {
|
|
continue
|
|
}
|
|
strings = append(strings, []byte(glyph.name))
|
|
sid := 391 + index - 1
|
|
if sid > math.MaxUint16 {
|
|
return nil, fmt.Errorf("Type1 font has too many glyph names")
|
|
}
|
|
binary.BigEndian.PutUint16(charset[1+(index-1)*2:], uint16(sid))
|
|
}
|
|
nameIndex := encodeCFFIndex([][]byte{[]byte(name)})
|
|
stringIndex := encodeCFFIndex(strings)
|
|
globalSubrs := encodeCFFIndex(nil)
|
|
charIndex := encodeCFFIndex(chars)
|
|
topDict := make([]byte, 12)
|
|
topDict[0], topDict[5], topDict[6], topDict[11] = 29, 15, 29, 17
|
|
if p.matrix != nil {
|
|
topDict = append(topDict, encodeCFFDict(cffDict{1207: p.matrix})...)
|
|
}
|
|
topIndex := encodeCFFIndex([][]byte{topDict})
|
|
charsetOffset := 4 + len(nameIndex) + len(topIndex) + len(stringIndex) + len(globalSubrs)
|
|
charOffset := charsetOffset + len(charset)
|
|
binary.BigEndian.PutUint32(topDict[1:5], uint32(charsetOffset))
|
|
binary.BigEndian.PutUint32(topDict[7:11], uint32(charOffset))
|
|
topIndex = encodeCFFIndex([][]byte{topDict})
|
|
data := make([]byte, 0, charOffset+len(charIndex))
|
|
data = append(data, 1, 0, 4, 4)
|
|
data = append(data, nameIndex...)
|
|
data = append(data, topIndex...)
|
|
data = append(data, stringIndex...)
|
|
data = append(data, globalSubrs...)
|
|
data = append(data, charset...)
|
|
data = append(data, charIndex...)
|
|
return data, nil
|
|
}
|
|
|
|
// glyphIDs 返回Type1名称到转换后字形编号的对应关系
|
|
// 返回: map[string]uint16 字形编号
|
|
func (p *type1Program) glyphIDs() map[string]uint16 {
|
|
ids := map[string]uint16{".notdef": 0}
|
|
next := uint16(1)
|
|
for _, glyph := range p.glyphs {
|
|
if glyph.name != ".notdef" {
|
|
ids[glyph.name] = next
|
|
next++
|
|
}
|
|
}
|
|
return ids
|
|
}
|