mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
1160 lines
33 KiB
Go
1160 lines
33 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"
|
|
"fmt"
|
|
"golang.org/x/text/encoding/simplifiedchinese"
|
|
"math"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// wrapCFFToOTF 将 CFF 裸数据包装为 OpenType 字体格式
|
|
// 入参: cffData CFF字体数据
|
|
// 返回: []byte OTF字体数据, map[rune]uint16 字符映射, error 错误信息
|
|
func wrapCFFToOTF(cffData []byte) ([]byte, map[rune]uint16, error) {
|
|
numGlyphs, err := parseCFFAndCountGlyphs(cffData)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
mapping := getCmapFromCFF(cffData, int(numGlyphs))
|
|
sanitized, err := sanitizeCFF(cffData)
|
|
if err == nil {
|
|
cffData = sanitized
|
|
}
|
|
widths, err := parseCFFWidths(cffData, numGlyphs)
|
|
if err != nil {
|
|
widths = make([]uint16, numGlyphs)
|
|
for i := range widths {
|
|
widths[i] = 500
|
|
}
|
|
}
|
|
var unitsPerEm uint16 = 1000
|
|
if len(cffData) > 4 {
|
|
hdrSize := int(cffData[2])
|
|
off := hdrSize
|
|
_, sz := getCFFIndexCount(cffData, off)
|
|
off += sz
|
|
topDictData, _ := getCFFIndexData(cffData, off)
|
|
if topDictData != nil {
|
|
td := parseCFFDict(topDictData)
|
|
if mat, ok := td[1207]; ok && len(mat) > 0 {
|
|
if mat[0] != 0 {
|
|
val := 1.0 / mat[0]
|
|
if val > 0 {
|
|
unitsPerEm = uint16(math.Round(val))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tables := make(map[string][]byte)
|
|
tables["CFF "] = cffData
|
|
tables["head"] = buildHeadTable(unitsPerEm)
|
|
tables["hhea"] = buildHheaTable(uint16(numGlyphs))
|
|
tables["maxp"] = buildCFFMaxpTable(uint16(numGlyphs))
|
|
tables["OS/2"] = buildOS2Table()
|
|
tables["name"] = buildNameTable()
|
|
tables["post"] = buildPostTable()
|
|
tables["hmtx"] = buildHmtxTable(widths)
|
|
tables["cmap"] = buildCmapTable(uint16(numGlyphs), mapping)
|
|
data, err := serializeOTF(tables)
|
|
return data, mapping, err
|
|
}
|
|
|
|
// cffDict 使用 float64 存储所有数值,以统一处理整数和实数
|
|
type cffDict map[int][]float64
|
|
|
|
// sanitizeCFF 尝试清洗CFF数据,转换CID字体并合并FontMatrix
|
|
// 入参: data 原始CFF数据
|
|
// 返回: []byte 清洗后的CFF数据, error 错误信息
|
|
func sanitizeCFF(data []byte) ([]byte, error) {
|
|
if len(data) < 4 {
|
|
return nil, fmt.Errorf("data too short")
|
|
}
|
|
hdrSize := int(data[2])
|
|
offset := hdrSize
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("truncated")
|
|
}
|
|
nameCount, nameSz := getCFFIndexCount(data, offset)
|
|
if nameCount != 1 {
|
|
return nil, fmt.Errorf("multi-font cff not supported")
|
|
}
|
|
nameIndexData := data[offset : offset+nameSz]
|
|
offset += nameSz
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("truncated")
|
|
}
|
|
topCount, topSz := getCFFIndexCount(data, offset)
|
|
if topCount != 1 {
|
|
return nil, fmt.Errorf("top dict count != 1")
|
|
}
|
|
topDictData, _ := getCFFIndexData(data, offset)
|
|
offset += topSz
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("truncated")
|
|
}
|
|
_, strSz := getCFFIndexCount(data, offset)
|
|
stringIndexData := data[offset : offset+strSz]
|
|
offset += strSz
|
|
if offset >= len(data) {
|
|
return nil, fmt.Errorf("truncated")
|
|
}
|
|
_, glbSz := getCFFIndexCount(data, offset)
|
|
globalSubrIndexData := data[offset : offset+glbSz]
|
|
topDict := parseCFFDict(topDictData)
|
|
if _, isCID := topDict[1230]; !isCID {
|
|
return data, nil
|
|
}
|
|
fdArrOffs, ok := topDict[1236]
|
|
if !ok || len(fdArrOffs) == 0 {
|
|
return nil, fmt.Errorf("cid without fdarray")
|
|
}
|
|
fdArrOff := int(fdArrOffs[0])
|
|
if fdArrOff >= len(data) {
|
|
return nil, fmt.Errorf("fdarray offset oob")
|
|
}
|
|
fdCount, _ := getCFFIndexCount(data, fdArrOff)
|
|
if fdCount != 1 {
|
|
return nil, fmt.Errorf("complex cid (fdarray count=%d) not supported", fdCount)
|
|
}
|
|
fontDictData, _ := getCFFIndexData(data, fdArrOff)
|
|
fontDict := parseCFFDict(fontDictData)
|
|
if fdMat, ok := fontDict[1207]; ok && len(fdMat) == 6 {
|
|
topMat, hasTop := topDict[1207]
|
|
if !hasTop || len(topMat) != 6 {
|
|
topMat = []float64{0.001, 0, 0, 0.001, 0, 0}
|
|
}
|
|
newMat := multiplyAffine(topMat, fdMat)
|
|
topDict[1207] = newMat
|
|
}
|
|
privVals, ok := fontDict[18]
|
|
if !ok || len(privVals) != 2 {
|
|
privVals = []float64{0, 0}
|
|
}
|
|
privSize := int(privVals[0])
|
|
privOff := int(privVals[1])
|
|
var localSubrData []byte
|
|
var privDictData []byte
|
|
if privSize > 0 && privOff < len(data) && privOff+privSize <= len(data) {
|
|
privDictData = data[privOff : privOff+privSize]
|
|
}
|
|
var subrsOffRel int
|
|
if len(privDictData) > 0 {
|
|
pDict := parseCFFDict(privDictData)
|
|
if sVals, ok := pDict[19]; ok && len(sVals) > 0 {
|
|
subrsOffRel = int(sVals[0])
|
|
}
|
|
}
|
|
if subrsOffRel > 0 {
|
|
subrsAbs := privOff + subrsOffRel
|
|
if subrsAbs < len(data) {
|
|
_, subSz := getCFFIndexCount(data, subrsAbs)
|
|
if subrsAbs+subSz <= len(data) {
|
|
localSubrData = data[subrsAbs : subrsAbs+subSz]
|
|
}
|
|
}
|
|
}
|
|
charStringsOffs, ok := topDict[17]
|
|
if !ok || len(charStringsOffs) == 0 {
|
|
return nil, fmt.Errorf("missing charstrings")
|
|
}
|
|
charStringsOff := int(charStringsOffs[0])
|
|
_, charStrSz := getCFFIndexCount(data, charStringsOff)
|
|
charStringsData := data[charStringsOff : charStringsOff+charStrSz]
|
|
delete(topDict, 1230)
|
|
delete(topDict, 1236)
|
|
delete(topDict, 1237)
|
|
delete(topDict, 1234)
|
|
delete(topDict, 15)
|
|
delete(topDict, 16)
|
|
topDict[18] = []float64{float64(privSize), 0}
|
|
var newCFF bytes.Buffer
|
|
newCFF.Write(data[:hdrSize])
|
|
newCFF.Write(nameIndexData)
|
|
dummyDict := make(map[int][]float64)
|
|
for k, v := range topDict {
|
|
dummyDict[k] = v
|
|
}
|
|
dummyDict[17] = []float64{0}
|
|
dummyDict[18] = []float64{float64(privSize), 0}
|
|
dummyTopData := encodeCFFDict(dummyDict)
|
|
topIdxSize := 2 + 1 + 8 + len(dummyTopData)
|
|
dataStart := hdrSize + len(nameIndexData) + topIdxSize + len(stringIndexData) + len(globalSubrIndexData)
|
|
charStringsPos := dataStart
|
|
privatePos := charStringsPos + len(charStringsData)
|
|
privateLen := privSize
|
|
var finalPrivData []byte
|
|
if len(privDictData) > 0 {
|
|
pDict := parseCFFDict(privDictData)
|
|
if _, ok := pDict[19]; ok || len(localSubrData) > 0 {
|
|
pDict[19] = []float64{float64(privateLen)}
|
|
}
|
|
finalPrivData = encodeCFFDict(pDict)
|
|
privateLen = len(finalPrivData)
|
|
}
|
|
localSubrsPos := privatePos + privateLen
|
|
topDict[17] = []float64{float64(charStringsPos)}
|
|
topDict[18] = []float64{float64(privateLen), float64(privatePos)}
|
|
finalTopData := encodeCFFDict(topDict)
|
|
topIndex := encodeCFFIndex([]([]byte){finalTopData})
|
|
newCFF.Reset()
|
|
newCFF.Write(data[:hdrSize])
|
|
newCFF.Write(nameIndexData)
|
|
newCFF.Write(topIndex)
|
|
newCFF.Write(stringIndexData)
|
|
newCFF.Write(globalSubrIndexData)
|
|
if newCFF.Len() != dataStart {
|
|
diff := newCFF.Len() - dataStart
|
|
charStringsPos += diff
|
|
privatePos += diff
|
|
localSubrsPos += diff
|
|
topDict[17] = []float64{float64(charStringsPos)}
|
|
topDict[18] = []float64{float64(privateLen), float64(privatePos)}
|
|
finalTopData = encodeCFFDict(topDict)
|
|
topIndex = encodeCFFIndex([]([]byte){finalTopData})
|
|
newCFF.Reset()
|
|
newCFF.Write(data[:hdrSize])
|
|
newCFF.Write(nameIndexData)
|
|
newCFF.Write(topIndex)
|
|
newCFF.Write(stringIndexData)
|
|
newCFF.Write(globalSubrIndexData)
|
|
}
|
|
newCFF.Write(charStringsData)
|
|
newCFF.Write(finalPrivData)
|
|
if len(localSubrData) > 0 {
|
|
newCFF.Write(localSubrData)
|
|
}
|
|
return newCFF.Bytes(), nil
|
|
}
|
|
|
|
// parseCFFAndCountGlyphs 解析 CFF 头部并统计字形数量
|
|
// 入参: data CFF数据
|
|
// 返回: int 字形数量, error 错误信息
|
|
func parseCFFAndCountGlyphs(data []byte) (int, error) {
|
|
if len(data) < 4 {
|
|
return 0, fmt.Errorf("data too short")
|
|
}
|
|
hdrSize := int(data[2])
|
|
offset := hdrSize
|
|
if offset >= len(data) {
|
|
return 0, fmt.Errorf("truncated")
|
|
}
|
|
count, sz := getCFFIndexCount(data, offset)
|
|
if count != 1 {
|
|
return 0, fmt.Errorf("multi-font cff not supported")
|
|
}
|
|
offset += sz
|
|
if offset >= len(data) {
|
|
return 0, fmt.Errorf("truncated")
|
|
}
|
|
count, _ = getCFFIndexCount(data, offset)
|
|
if count != 1 {
|
|
return 0, fmt.Errorf("top dict count mismatch")
|
|
}
|
|
topDictData, _ := getCFFIndexData(data, offset)
|
|
if topDictData != nil {
|
|
dict := parseCFFDict(topDictData)
|
|
if offsetVals, ok := dict[17]; ok && len(offsetVals) > 0 {
|
|
charStrOff := int(offsetVals[0])
|
|
if charStrOff > 0 && charStrOff < len(data) {
|
|
count, _ := getCFFIndexCount(data, charStrOff)
|
|
return count, nil
|
|
}
|
|
}
|
|
}
|
|
return 0, fmt.Errorf("failed to parse top dict")
|
|
}
|
|
|
|
// multiplyAffine 2x3 仿射矩阵乘法
|
|
// 入参: a 矩阵A, b 矩阵B
|
|
// 返回: []float64 结果矩阵
|
|
func multiplyAffine(a, b []float64) []float64 {
|
|
return []float64{
|
|
a[0]*b[0] + a[2]*b[1],
|
|
a[1]*b[0] + a[3]*b[1],
|
|
a[0]*b[2] + a[2]*b[3],
|
|
a[1]*b[2] + a[3]*b[3],
|
|
a[0]*b[4] + a[2]*b[5] + a[4],
|
|
a[1]*b[4] + a[3]*b[5] + a[5],
|
|
}
|
|
}
|
|
|
|
// parseCFFDict 解析 CFF 字典数据
|
|
// 入参: data 字典数据
|
|
// 返回: cffDict 解析后的字典映射
|
|
func parseCFFDict(data []byte) cffDict {
|
|
dict := make(cffDict)
|
|
var operands []float64
|
|
i := 0
|
|
for i < len(data) {
|
|
b := data[i]
|
|
i++
|
|
if b <= 27 {
|
|
op := int(b)
|
|
if b == 12 {
|
|
if i >= len(data) {
|
|
break
|
|
}
|
|
op = 1200 + int(data[i])
|
|
i++
|
|
}
|
|
dict[op] = operands
|
|
operands = nil
|
|
} else if b == 28 {
|
|
if i+1 < len(data) {
|
|
val := int(int16(binary.BigEndian.Uint16(data[i:])))
|
|
operands = append(operands, float64(val))
|
|
i += 2
|
|
}
|
|
} else if b == 29 {
|
|
if i+3 < len(data) {
|
|
val := int(int32(binary.BigEndian.Uint32(data[i:])))
|
|
operands = append(operands, float64(val))
|
|
i += 4
|
|
}
|
|
} else if b == 30 {
|
|
s, n := parseCFFReal(data[i:])
|
|
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
|
operands = append(operands, f)
|
|
}
|
|
i += n
|
|
} else if b >= 32 && b <= 246 {
|
|
operands = append(operands, float64(int(b)-139))
|
|
} else if b >= 247 && b <= 250 {
|
|
if i < len(data) {
|
|
b1 := int(data[i])
|
|
i++
|
|
operands = append(operands, float64((int(b)-247)*256+b1+108))
|
|
}
|
|
} else if b >= 251 && b <= 254 {
|
|
if i < len(data) {
|
|
b1 := int(data[i])
|
|
i++
|
|
operands = append(operands, float64(-(int(b)-251)*256-b1-108))
|
|
}
|
|
}
|
|
}
|
|
return dict
|
|
}
|
|
|
|
// parseCFFReal 解析 CFF 实数编码
|
|
// 入参: data 数据切片
|
|
// 返回: string 实数字符串, int 消耗字节数
|
|
func parseCFFReal(data []byte) (string, int) {
|
|
var sb strings.Builder
|
|
i := 0
|
|
done := false
|
|
for i < len(data) && !done {
|
|
b := data[i]
|
|
i++
|
|
nibbles := []byte{b >> 4, b & 0x0F}
|
|
for _, n := range nibbles {
|
|
if n == 0xF {
|
|
done = true
|
|
break
|
|
}
|
|
if n <= 9 {
|
|
sb.WriteString(strconv.Itoa(int(n)))
|
|
}
|
|
if n == 0xA {
|
|
sb.WriteString(".")
|
|
}
|
|
if n == 0xB {
|
|
sb.WriteString("E")
|
|
}
|
|
if n == 0xC {
|
|
sb.WriteString("E-")
|
|
}
|
|
if n == 0xE {
|
|
sb.WriteString("-")
|
|
}
|
|
}
|
|
}
|
|
return sb.String(), i
|
|
}
|
|
|
|
// encodeCFFDict 编码 CFF 字典 (仅使用 float64 操作数)
|
|
// 入参: dict CFF字典映射
|
|
// 返回: []byte 编码后的字典数据
|
|
func encodeCFFDict(dict cffDict) []byte {
|
|
buf := new(bytes.Buffer)
|
|
var keys []int
|
|
for k := range dict {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Ints(keys)
|
|
for _, op := range keys {
|
|
vals := dict[op]
|
|
for _, val := range vals {
|
|
encodeNumberCFF(buf, val)
|
|
}
|
|
if op >= 1200 {
|
|
buf.WriteByte(12)
|
|
buf.WriteByte(byte(op - 1200))
|
|
} else {
|
|
buf.WriteByte(byte(op))
|
|
}
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// encodeNumberCFF 编码单个数值到 CFF 格式
|
|
// 入参: buf 缓冲区, val 数值
|
|
func encodeNumberCFF(buf *bytes.Buffer, val float64) {
|
|
if val == math.Trunc(val) {
|
|
iv := int(val)
|
|
if iv >= -107 && iv <= 107 {
|
|
buf.WriteByte(byte(iv + 139))
|
|
} else if iv >= 108 && iv <= 1131 {
|
|
iv -= 108
|
|
buf.WriteByte(byte((iv >> 8) + 247))
|
|
buf.WriteByte(byte(iv & 0xFF))
|
|
} else if iv >= -1131 && iv <= -108 {
|
|
iv = -iv - 108
|
|
buf.WriteByte(byte((iv >> 8) + 251))
|
|
buf.WriteByte(byte(iv & 0xFF))
|
|
} else if iv >= -32768 && iv <= 32767 {
|
|
buf.WriteByte(28)
|
|
binary.Write(buf, binary.BigEndian, int16(iv))
|
|
} else {
|
|
buf.WriteByte(29)
|
|
binary.Write(buf, binary.BigEndian, int32(iv))
|
|
}
|
|
} else {
|
|
s := fmt.Sprintf("%g", val)
|
|
buf.WriteByte(30)
|
|
var nibbles []byte
|
|
for _, c := range s {
|
|
var n byte
|
|
switch c {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
n = byte(c - '0')
|
|
case '.':
|
|
n = 0xA
|
|
case 'E', 'e':
|
|
n = 0xB
|
|
case '-':
|
|
n = 0xE
|
|
}
|
|
nibbles = append(nibbles, n)
|
|
}
|
|
nibbles = append(nibbles, 0xF)
|
|
if len(nibbles)%2 != 0 {
|
|
nibbles = append(nibbles, 0xF)
|
|
}
|
|
for i := 0; i < len(nibbles); i += 2 {
|
|
b := nibbles[i] << 4
|
|
if i+1 < len(nibbles) {
|
|
b |= nibbles[i+1]
|
|
}
|
|
buf.WriteByte(b)
|
|
}
|
|
}
|
|
}
|
|
|
|
// encodeCFFIndex 编码 CFF 索引结构
|
|
// 入参: items 数据项列表
|
|
// 返回: []byte 编码后的索引数据
|
|
func encodeCFFIndex(items []([]byte)) []byte {
|
|
count := len(items)
|
|
buf := new(bytes.Buffer)
|
|
binary.Write(buf, binary.BigEndian, uint16(count))
|
|
if count == 0 {
|
|
return buf.Bytes()
|
|
}
|
|
totalSize := 0
|
|
for _, item := range items {
|
|
totalSize += len(item)
|
|
}
|
|
offSize := 1
|
|
if totalSize+1 > 255 {
|
|
offSize = 2
|
|
}
|
|
if totalSize+1 > 65535 {
|
|
offSize = 3
|
|
}
|
|
if totalSize+1 > 16777215 {
|
|
offSize = 4
|
|
}
|
|
buf.WriteByte(byte(offSize))
|
|
offset := 1
|
|
putOffset(buf, offset, offSize)
|
|
for _, item := range items {
|
|
offset += len(item)
|
|
putOffset(buf, offset, offSize)
|
|
}
|
|
for _, item := range items {
|
|
buf.Write(item)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// putOffset 写入指定大小的偏移量
|
|
// 入参: buf 缓冲区, val 偏移值, size 字节大小
|
|
func putOffset(buf *bytes.Buffer, val int, size int) {
|
|
tmp := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(tmp, uint32(val))
|
|
buf.Write(tmp[4-size:])
|
|
}
|
|
|
|
// getCFFIndexCount 读取 CFF 索引的计数和大小
|
|
// 入参: data CFF数据, offset 偏移量
|
|
// 返回: int 数量, int 索引结构总大小
|
|
func getCFFIndexCount(data []byte, offset int) (int, int) {
|
|
if offset+2 > len(data) {
|
|
return 0, 0
|
|
}
|
|
count := int(binary.BigEndian.Uint16(data[offset:]))
|
|
if count == 0 {
|
|
return 0, 2
|
|
}
|
|
if offset+3 > len(data) {
|
|
return 0, 0
|
|
}
|
|
offSize := int(data[offset+2])
|
|
if offSize < 1 || offSize > 4 {
|
|
return 0, 0
|
|
}
|
|
dataSizeLen := (count + 1) * offSize
|
|
if offset+3+dataSizeLen > len(data) {
|
|
return 0, 0
|
|
}
|
|
endOffsetPos := offset + 3 + count*offSize
|
|
if endOffsetPos+offSize > len(data) {
|
|
return 0, 0
|
|
}
|
|
dataEnd := readCFFOffset(data, endOffsetPos, offSize)
|
|
if dataEnd < 1 {
|
|
return 0, 0
|
|
}
|
|
return count, 3 + (count+1)*offSize + (dataEnd - 1)
|
|
}
|
|
|
|
// getCFFIndexData 读取 CFF 索引的数据块
|
|
// 入参: data CFF数据, offset 偏移量
|
|
// 返回: []byte 索引数据(已去除offsets), int 索引结构总大小
|
|
func getCFFIndexData(data []byte, offset int) ([]byte, int) {
|
|
count, size := getCFFIndexCount(data, offset)
|
|
if count == 0 {
|
|
return nil, size
|
|
}
|
|
if offset+3 > len(data) {
|
|
return nil, size
|
|
}
|
|
offSize := int(data[offset+2])
|
|
if offset+3+offSize > len(data) {
|
|
return nil, size
|
|
}
|
|
off0 := readCFFOffset(data, offset+3, offSize)
|
|
if offset+3+offSize*2 > len(data) {
|
|
return nil, size
|
|
}
|
|
off1 := readCFFOffset(data, offset+3+offSize, offSize)
|
|
dataStartRel := 3 + (count+1)*offSize
|
|
dataStartAbs := offset + dataStartRel
|
|
start := dataStartAbs + (off0 - 1)
|
|
length := off1 - off0
|
|
if start < 0 || length < 0 || start+length > len(data) {
|
|
return nil, size
|
|
}
|
|
return data[start : start+length], size
|
|
}
|
|
|
|
// parseCFFWidths 从 CFF 数据中解析 Glyph 宽度
|
|
// 入参: data CFF数据, numGlyphs 字形数量
|
|
// 返回: []uint16 宽度列表, error 错误信息
|
|
func parseCFFWidths(data []byte, numGlyphs int) ([]uint16, error) {
|
|
if len(data) < 4 {
|
|
return nil, fmt.Errorf("data too short")
|
|
}
|
|
hdrSize := int(data[2])
|
|
offset := hdrSize
|
|
_, sz := getCFFIndexCount(data, offset)
|
|
offset += sz
|
|
topDictData, sz := getCFFIndexData(data, offset)
|
|
offset += sz
|
|
if topDictData == nil {
|
|
return nil, fmt.Errorf("failed to read top dict")
|
|
}
|
|
topDict := parseCFFDict(topDictData)
|
|
_, sz = getCFFIndexCount(data, offset)
|
|
offset += sz
|
|
_, sz = getCFFIndexCount(data, offset)
|
|
offset += sz
|
|
var nominalWidthX float64 = 0
|
|
var defaultWidthX float64 = 0
|
|
if vals, ok := topDict[18]; ok && len(vals) == 2 {
|
|
privSize := int(vals[0])
|
|
privOff := int(vals[1])
|
|
if privSize > 0 && privOff+privSize <= len(data) {
|
|
privData := data[privOff : privOff+privSize]
|
|
privDict := parseCFFDict(privData)
|
|
if v, ok := privDict[20]; ok && len(v) > 0 {
|
|
defaultWidthX = v[0]
|
|
}
|
|
if v, ok := privDict[21]; ok && len(v) > 0 {
|
|
nominalWidthX = v[0]
|
|
}
|
|
}
|
|
}
|
|
if vals, ok := topDict[17]; ok && len(vals) > 0 {
|
|
charStrOff := int(vals[0])
|
|
count, _ := getCFFIndexCount(data, charStrOff)
|
|
limit := count
|
|
if numGlyphs < limit {
|
|
limit = numGlyphs
|
|
}
|
|
widths := make([]uint16, numGlyphs)
|
|
for i := range widths {
|
|
widths[i] = uint16(defaultWidthX)
|
|
}
|
|
for i := 0; i < limit; i++ {
|
|
offSize := int(data[charStrOff+2])
|
|
p1 := charStrOff + 3 + i*offSize
|
|
p2 := p1 + offSize
|
|
off1 := readCFFOffset(data, p1, offSize)
|
|
off2 := readCFFOffset(data, p2, offSize)
|
|
dataStartBase := charStrOff + 3 + (count+1)*offSize
|
|
start := dataStartBase + (off1 - 1)
|
|
length := off2 - off1
|
|
if start < 0 || start+length > len(data) {
|
|
widths[i] = uint16(defaultWidthX)
|
|
continue
|
|
}
|
|
csData := data[start : start+length]
|
|
w := scanCharStringWidth(csData, nominalWidthX, defaultWidthX)
|
|
widths[i] = uint16(w)
|
|
}
|
|
return widths, nil
|
|
}
|
|
return nil, fmt.Errorf("no charstrings")
|
|
}
|
|
|
|
// scanCharStringWidth 扫描 CharString 获取宽度
|
|
// 入参: data CharString数据, nominal, def 默认宽度值
|
|
// 返回: float64 宽度值
|
|
func scanCharStringWidth(data []byte, nominal, def float64) float64 {
|
|
stackDepth := 0
|
|
i := 0
|
|
firstVal := 0.0
|
|
for i < len(data) {
|
|
b := data[i]
|
|
if b <= 31 {
|
|
if b == 28 {
|
|
i += 3
|
|
stackDepth++
|
|
if stackDepth == 1 {
|
|
firstVal = parseShortInt(data, i-3)
|
|
}
|
|
} else if b == 29 {
|
|
i += 5
|
|
stackDepth++
|
|
} else if b == 12 {
|
|
i += 2
|
|
if stackDepth%2 != 0 {
|
|
return nominal + firstVal
|
|
}
|
|
return def
|
|
} else if b == 19 || b == 20 {
|
|
if stackDepth%2 != 0 {
|
|
return nominal + firstVal
|
|
}
|
|
return def
|
|
} else {
|
|
if stackDepth%2 != 0 {
|
|
return nominal + firstVal
|
|
}
|
|
return def
|
|
}
|
|
} else {
|
|
stackDepth++
|
|
if stackDepth == 1 {
|
|
firstVal = parseNumberType2(data, i)
|
|
}
|
|
if b >= 32 && b <= 246 {
|
|
i++
|
|
} else if b >= 247 && b <= 250 {
|
|
i += 2
|
|
} else if b >= 251 && b <= 254 {
|
|
i += 2
|
|
} else if b == 255 {
|
|
i += 5
|
|
}
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
// parseShortInt 解析短整数 (Type 2 CharString)
|
|
// 入参: data 数据, idx 索引
|
|
// 返回: float64 浮点值
|
|
func parseShortInt(data []byte, idx int) float64 {
|
|
return float64(int16(binary.BigEndian.Uint16(data[idx+1:])))
|
|
}
|
|
|
|
// parseNumberType2 解析 Number (Type 2)
|
|
// 入参: data 数据, idx 索引
|
|
// 返回: float64 浮点值
|
|
func parseNumberType2(data []byte, idx int) float64 {
|
|
b := data[idx]
|
|
if b >= 32 && b <= 246 {
|
|
return float64(int(b) - 139)
|
|
}
|
|
if b >= 247 && b <= 250 {
|
|
return float64((int(b)-247)*256 + int(data[idx+1]) + 108)
|
|
}
|
|
if b >= 251 && b <= 254 {
|
|
return float64(-(int(b)-251)*256 - int(data[idx+1]) - 108)
|
|
}
|
|
if b == 28 {
|
|
return float64(int16(binary.BigEndian.Uint16(data[idx+1:])))
|
|
}
|
|
if b == 255 {
|
|
return float64(int16(binary.BigEndian.Uint16(data[idx+1:]))) + float64(binary.BigEndian.Uint16(data[idx+3:]))/65536.0
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// readCFFOffset 读取指定大小的偏移量
|
|
// 入参: data 数据, pos 位置, size 大小
|
|
// 返回: int 偏移量
|
|
func readCFFOffset(data []byte, pos, size int) int {
|
|
var val int
|
|
for i := 0; i < size; i++ {
|
|
if pos+i < len(data) {
|
|
val = (val << 8) | int(data[pos+i])
|
|
}
|
|
}
|
|
return val
|
|
}
|
|
|
|
// getCFFCharsetInfo 读取 CFF 字符集和 ROS 信息
|
|
// 入参: data CFF数据, numGlyphs 字形数量
|
|
// 返回: []int SID或CID列表, string Registry, string Ordering, int 字符串索引偏移, bool 是否成功
|
|
func getCFFCharsetInfo(data []byte, numGlyphs int) ([]int, string, string, int, bool) {
|
|
if len(data) < 4 {
|
|
return nil, "", "", 0, false
|
|
}
|
|
hdrSize := int(data[2])
|
|
offset := hdrSize
|
|
_, sz := getCFFIndexCount(data, offset)
|
|
offset += sz
|
|
_, szTD := getCFFIndexCount(data, offset)
|
|
topDictData, _ := getCFFIndexData(data, offset)
|
|
offset += szTD
|
|
stringIndexOff := offset
|
|
if topDictData == nil {
|
|
return nil, "", "", 0, false
|
|
}
|
|
td := parseCFFDict(topDictData)
|
|
registry, ordering := getCFFROS(data, stringIndexOff, td)
|
|
charsetOff := 0
|
|
if vals, ok := td[15]; ok && len(vals) > 0 {
|
|
charsetOff = int(vals[0])
|
|
}
|
|
sids := make([]int, numGlyphs)
|
|
sids[0] = 0
|
|
if charsetOff > 2 {
|
|
sidsParsed := parseCFFCharset(data, charsetOff, numGlyphs)
|
|
copy(sids[1:], sidsParsed)
|
|
} else if charsetOff == 0 {
|
|
count := 228
|
|
if numGlyphs-1 < count {
|
|
count = numGlyphs - 1
|
|
}
|
|
for i := 1; i <= count; i++ {
|
|
sids[i] = i
|
|
}
|
|
} else {
|
|
return nil, "", "", 0, false
|
|
}
|
|
return sids, registry, ordering, stringIndexOff, true
|
|
}
|
|
|
|
// getCmapFromCFF 从 CFF 数据中恢复 Unicode 映射
|
|
// 入参: data CFF数据, numGlyphs 字形数量
|
|
// 返回: map[rune]uint16 恢复的映射表
|
|
func getCmapFromCFF(data []byte, numGlyphs int) map[rune]uint16 {
|
|
sids, registry, ordering, stringIndexOff, ok := getCFFCharsetInfo(data, numGlyphs)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
mapping := make(map[rune]uint16)
|
|
if registry == "Adobe" && ordering == "GB1" {
|
|
for gid, cid := range sids {
|
|
if gid == 0 {
|
|
continue
|
|
}
|
|
mapping[packedGlyphRune(uint16(gid))] = uint16(gid)
|
|
if r, ok := adobeGB1CIDToUnicode(cid); ok {
|
|
mapping[r] = uint16(gid)
|
|
}
|
|
}
|
|
return mapping
|
|
}
|
|
if registry != "" {
|
|
for gid := range sids {
|
|
if gid == 0 {
|
|
continue
|
|
}
|
|
mapping[packedGlyphRune(uint16(gid))] = uint16(gid)
|
|
}
|
|
return mapping
|
|
}
|
|
for gid, sid := range sids {
|
|
if gid == 0 {
|
|
continue
|
|
}
|
|
var name string
|
|
if sid <= 390 {
|
|
if sid >= 0 && sid < len(cffStandardStrings) {
|
|
name = cffStandardStrings[sid]
|
|
}
|
|
} else {
|
|
idx := sid - 391
|
|
name = readStringIndexItem(data, stringIndexOff, idx)
|
|
}
|
|
r := rune(0)
|
|
if name != "" {
|
|
r = getUnicodeFromName(name)
|
|
}
|
|
if r == 0 {
|
|
r = packedGlyphRune(uint16(gid))
|
|
}
|
|
mapping[packedGlyphRune(uint16(gid))] = uint16(gid)
|
|
mapping[r] = uint16(gid)
|
|
}
|
|
return mapping
|
|
}
|
|
|
|
// getCFFCIDRuneMap 获取 CID 到包装字体字符的映射
|
|
// 入参: data CFF或OpenType字体数据
|
|
// 返回: map[uint16]rune CID映射
|
|
func getCFFCIDRuneMap(data []byte) map[uint16]rune {
|
|
cffData := getCFFData(data)
|
|
if cffData == nil {
|
|
return nil
|
|
}
|
|
numGlyphs, err := parseCFFAndCountGlyphs(cffData)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
sids, registry, _, _, ok := getCFFCharsetInfo(cffData, numGlyphs)
|
|
if !ok || registry == "" {
|
|
return nil
|
|
}
|
|
mapping := getCmapFromCFF(cffData, numGlyphs)
|
|
if len(mapping) == 0 {
|
|
return nil
|
|
}
|
|
gidRunes := make(map[uint16]rune)
|
|
for run, gid := range mapping {
|
|
if run != packedGlyphRune(gid) {
|
|
gidRunes[gid] = run
|
|
}
|
|
}
|
|
for run, gid := range mapping {
|
|
if _, ok := gidRunes[gid]; !ok {
|
|
gidRunes[gid] = run
|
|
}
|
|
}
|
|
result := make(map[uint16]rune)
|
|
for gid, cid := range sids {
|
|
if gid == 0 || cid < 0 || cid > 0xFFFF {
|
|
continue
|
|
}
|
|
if run, ok := gidRunes[uint16(gid)]; ok {
|
|
result[uint16(cid)] = run
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
// getCFFData 获取字体中的 CFF 数据
|
|
// 入参: data 字体数据
|
|
// 返回: []byte CFF数据
|
|
func getCFFData(data []byte) []byte {
|
|
if isBareCFFData(data) {
|
|
return data
|
|
}
|
|
if len(data) < 12 {
|
|
return nil
|
|
}
|
|
tag := string(data[0:4])
|
|
u32Tag := binary.BigEndian.Uint32(data[0:4])
|
|
if tag != "OTTO" && tag != "true" && u32Tag != 0x00010000 {
|
|
return nil
|
|
}
|
|
numTables := int(binary.BigEndian.Uint16(data[4:6]))
|
|
for i := 0; i < numTables; i++ {
|
|
pos := 12 + i*16
|
|
if pos+16 > len(data) {
|
|
return nil
|
|
}
|
|
if string(data[pos:pos+4]) != "CFF " {
|
|
continue
|
|
}
|
|
offset := int(binary.BigEndian.Uint32(data[pos+8 : pos+12]))
|
|
length := int(binary.BigEndian.Uint32(data[pos+12 : pos+16]))
|
|
if offset < 0 || length < 0 || offset+length > len(data) {
|
|
return nil
|
|
}
|
|
return data[offset : offset+length]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parseCFFCharset 解析 CFF 字符集并返回 SID 列表
|
|
// 入参: data CFF数据, offset 偏移量, numGlyphs 字形数量
|
|
// 返回: []int SID列表
|
|
func parseCFFCharset(data []byte, offset int, numGlyphs int) []int {
|
|
if offset >= len(data) {
|
|
return nil
|
|
}
|
|
format := data[offset]
|
|
var sids []int
|
|
count := numGlyphs - 1
|
|
pos := offset + 1
|
|
switch format {
|
|
case 0:
|
|
for i := 0; i < count && pos+2 <= len(data); i++ {
|
|
sid := int(binary.BigEndian.Uint16(data[pos:]))
|
|
sids = append(sids, sid)
|
|
pos += 2
|
|
}
|
|
case 1:
|
|
for len(sids) < count && pos+3 <= len(data) {
|
|
first := int(binary.BigEndian.Uint16(data[pos:]))
|
|
nLeft := int(data[pos+2])
|
|
pos += 3
|
|
for j := 0; j <= nLeft; j++ {
|
|
sids = append(sids, first+j)
|
|
}
|
|
}
|
|
case 2:
|
|
for len(sids) < count && pos+4 <= len(data) {
|
|
first := int(binary.BigEndian.Uint16(data[pos:]))
|
|
nLeft := int(binary.BigEndian.Uint16(data[pos+2:]))
|
|
pos += 4
|
|
for j := 0; j <= nLeft; j++ {
|
|
sids = append(sids, first+j)
|
|
}
|
|
}
|
|
}
|
|
if len(sids) > count {
|
|
sids = sids[:count]
|
|
}
|
|
return sids
|
|
}
|
|
|
|
// readStringIndexItem 读取 CFF 字符串索引项
|
|
// 入参: data CFF数据, offset 索引偏移, idx 索引号
|
|
// 返回: string 读取的字符串
|
|
func readStringIndexItem(data []byte, offset int, idx int) string {
|
|
if offset >= len(data) {
|
|
return ""
|
|
}
|
|
count := int(binary.BigEndian.Uint16(data[offset:]))
|
|
offSize := int(data[offset+2])
|
|
if idx >= count {
|
|
return ""
|
|
}
|
|
offArrayStart := offset + 3
|
|
p1 := offArrayStart + idx*offSize
|
|
p2 := p1 + offSize
|
|
if p2+offSize > len(data) {
|
|
return ""
|
|
}
|
|
loc1 := readCFFOffset(data, p1, offSize)
|
|
loc2 := readCFFOffset(data, p2, offSize)
|
|
dataStart := offArrayStart + (count+1)*offSize
|
|
start := dataStart + loc1 - 1
|
|
length := loc2 - loc1
|
|
if start < 0 || start+length > len(data) {
|
|
return ""
|
|
}
|
|
return string(data[start : start+length])
|
|
}
|
|
|
|
// getCFFROS 读取 CID 字体 ROS 信息
|
|
// 入参: data CFF数据, stringIndexOff 字符串索引偏移, td 顶层字典
|
|
// 返回: string Registry, string Ordering
|
|
func getCFFROS(data []byte, stringIndexOff int, td cffDict) (string, string) {
|
|
vals, ok := td[1230]
|
|
if !ok || len(vals) < 2 {
|
|
return "", ""
|
|
}
|
|
registry := getCFFSIDString(data, stringIndexOff, int(vals[0]))
|
|
ordering := getCFFSIDString(data, stringIndexOff, int(vals[1]))
|
|
return registry, ordering
|
|
}
|
|
|
|
// getCFFSIDString 读取 CFF SID 字符串
|
|
// 入参: data CFF数据, stringIndexOff 字符串索引偏移, sid 字符串ID
|
|
// 返回: string 字符串内容
|
|
func getCFFSIDString(data []byte, stringIndexOff int, sid int) string {
|
|
if sid >= 0 && sid < len(cffStandardStrings) {
|
|
return cffStandardStrings[sid]
|
|
}
|
|
if sid > 390 {
|
|
return readStringIndexItem(data, stringIndexOff, sid-391)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// adobeGB1CIDToUnicode 将 Adobe-GB1 CID 转为 Unicode
|
|
// 入参: cid 字符CID
|
|
// 返回: rune Unicode字符, bool 是否成功
|
|
func adobeGB1CIDToUnicode(cid int) (rune, bool) {
|
|
switch cid {
|
|
case 329:
|
|
return '“', true
|
|
case 330:
|
|
return '”', true
|
|
case 821:
|
|
return '、', true
|
|
case 822:
|
|
return '。', true
|
|
case 829:
|
|
return '《', true
|
|
case 830:
|
|
return '》', true
|
|
}
|
|
n := cid + 471
|
|
if n <= 0 {
|
|
return 0, false
|
|
}
|
|
row := (n-1)/94 + 1
|
|
cell := (n-1)%94 + 1
|
|
if row < 16 || row > 87 || cell < 1 || cell > 94 {
|
|
return 0, false
|
|
}
|
|
gbk := []byte{byte(row + 0xA0), byte(cell + 0xA0)}
|
|
decoded, err := simplifiedchinese.GBK.NewDecoder().Bytes(gbk)
|
|
if err != nil || len(decoded) == 0 {
|
|
return 0, false
|
|
}
|
|
rs := []rune(string(decoded))
|
|
if len(rs) != 1 {
|
|
return 0, false
|
|
}
|
|
return rs[0], true
|
|
}
|
|
|
|
// getUnicodeFromName 根据字形名称获取对应的Unicode字符
|
|
// 入参: name 字形名称
|
|
// 返回: rune Unicode字符
|
|
func getUnicodeFromName(name string) rune {
|
|
if strings.HasPrefix(name, "uni") && len(name) == 7 {
|
|
hexStr := strings.ToUpper(name[3:])
|
|
if val, err := strconv.ParseInt(hexStr, 16, 32); err == nil {
|
|
return rune(val)
|
|
}
|
|
}
|
|
if strings.HasPrefix(name, "u") && len(name) >= 5 && len(name) <= 7 && !strings.HasPrefix(name, "uni") {
|
|
hexStr := strings.ToUpper(name[1:])
|
|
if val, err := strconv.ParseInt(hexStr, 16, 32); err == nil {
|
|
return rune(val)
|
|
}
|
|
}
|
|
switch name {
|
|
case "space":
|
|
return ' '
|
|
case "exclam":
|
|
return '!'
|
|
case "quotedbl":
|
|
return '"'
|
|
case "numbersign":
|
|
return '#'
|
|
case "dollar":
|
|
return '$'
|
|
case "percent":
|
|
return '%'
|
|
case "ampersand":
|
|
return '&'
|
|
case "quotesingle":
|
|
return '\''
|
|
case "parenleft":
|
|
return '('
|
|
case "parenright":
|
|
return ')'
|
|
case "asterisk":
|
|
return '*'
|
|
case "plus":
|
|
return '+'
|
|
case "comma":
|
|
return ','
|
|
case "hyphen":
|
|
return '-'
|
|
case "period":
|
|
return '.'
|
|
case "slash":
|
|
return '/'
|
|
case "colon":
|
|
return ':'
|
|
case "semicolon":
|
|
return ';'
|
|
case "less":
|
|
return '<'
|
|
case "equal":
|
|
return '='
|
|
case "greater":
|
|
return '>'
|
|
case "question":
|
|
return '?'
|
|
case "at":
|
|
return '@'
|
|
case "bracketleft":
|
|
return '['
|
|
case "backslash":
|
|
return '\\'
|
|
case "bracketright":
|
|
return ']'
|
|
case "asciicircum":
|
|
return '^'
|
|
case "underscore":
|
|
return '_'
|
|
case "grave":
|
|
return '`'
|
|
case "braceleft":
|
|
return '{'
|
|
case "bar":
|
|
return '|'
|
|
case "braceright":
|
|
return '}'
|
|
case "asciitilde":
|
|
return '~'
|
|
}
|
|
if len(name) == 1 {
|
|
return rune(name[0])
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// cffStandardStrings CFF 标准字符串表
|
|
var cffStandardStrings = []string{
|
|
".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "quotedblright", "guillemotleft", "guillemotright", "dagger", "daggerdbl", "fi", "fl", "endash", "emdash", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "second", "circumflex", "breve", "dotaccent", "dieresis", "grave", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emspace",
|
|
"AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "2dotlead", "nbspace", "1dotlead", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold",
|
|
}
|