mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
1 parent
f73b67c729
commit
9856dd3bd5
3 files changed
+145
-72
No files matched your search
@@ -16,6 +16,7 @@ package ofdgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -93,7 +94,7 @@ func (c *fontSourceCache) child() *fontSourceCache {
|
|||||||
return cache
|
return cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// readFontSource 读取并拆出独立字体,保留集合中的名称和样式选择
|
// readFontSource 读取独立字体并规范符号映射,保留集合中的名称和样式选择
|
||||||
// 入参: source 字体来源, definition 字体定义
|
// 入参: source 字体来源, definition 字体定义
|
||||||
// 返回: []byte 字体数据, error 读取或集合错误
|
// 返回: []byte 字体数据, error 读取或集合错误
|
||||||
func (r *Renderer) readFontSource(source fontSource, definition *Font) ([]byte, error) {
|
func (r *Renderer) readFontSource(source fontSource, definition *Font) ([]byte, error) {
|
||||||
@@ -127,6 +128,9 @@ func (r *Renderer) readFontSource(source fontSource, definition *Font) ([]byte,
|
|||||||
if err == nil && bytes.HasPrefix(data, []byte("ttcf")) {
|
if err == nil && bytes.HasPrefix(data, []byte("ttcf")) {
|
||||||
data, err = extractCollectionFont(data, key.face)
|
data, err = extractCollectionFont(data, key.face)
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
data, _, err = symbolFontProgram(context.Background(), data, "")
|
||||||
|
}
|
||||||
r.fontSourcesCache.data[key] = fontSourceData{data, err}
|
r.fontSourcesCache.data[key] = fontSourceData{data, err}
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// 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 (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/xiaoqidun/pdfgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// symbolFontProgram 为标准符号字体补齐Unicode映射,保留字形编号和轮廓
|
||||||
|
// 入参: ctx 取消上下文, data 独立字体数据, name 标准字体名,为空时从字体名称表识别
|
||||||
|
// 返回: []byte 字体数据, map[string]uint16 标准字形编号,不适用时为空, error 映射或取消错误
|
||||||
|
func symbolFontProgram(ctx context.Context, data []byte, name string) ([]byte, map[string]uint16, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tables, err := fontFileTables(data, 0)
|
||||||
|
if err != nil {
|
||||||
|
return data, nil, nil
|
||||||
|
}
|
||||||
|
legacy := symbolFontCmap(tables["cmap"])
|
||||||
|
if name == "" && !legacy {
|
||||||
|
return data, nil, nil
|
||||||
|
}
|
||||||
|
actual := symbolFontName(tables["name"])
|
||||||
|
if name == "" {
|
||||||
|
name = actual
|
||||||
|
}
|
||||||
|
if name != "Symbol" && name != "ZapfDingbats" || !legacy && actual != name {
|
||||||
|
return data, nil, nil
|
||||||
|
}
|
||||||
|
standard, err := new(pdfgo.Reader).ReadFontContext(ctx, pdfgo.Dictionary{"Type": pdfgo.Name("Font"), "Subtype": pdfgo.Name("Type1"), "BaseFont": pdfgo.Name(name)})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
codes := make([]byte, 256)
|
||||||
|
for index := range codes {
|
||||||
|
codes[index] = byte(index)
|
||||||
|
}
|
||||||
|
glyphs, err := standard.DecodeContext(ctx, codes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
original := parseCmapMappings(tables["cmap"])
|
||||||
|
mapping := make(map[rune]uint16)
|
||||||
|
names := make(map[string]uint16)
|
||||||
|
for _, glyph := range glyphs {
|
||||||
|
if glyph.Name == ".notdef" || glyph.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
char, size := utf8.DecodeRuneInString(glyph.Text)
|
||||||
|
var id uint16
|
||||||
|
if legacy {
|
||||||
|
id = original[rune(glyph.Code)]
|
||||||
|
for _, base := range [...]rune{0xf000, 0xf100, 0xf200} {
|
||||||
|
if candidate := original[base+rune(glyph.Code)]; candidate != 0 {
|
||||||
|
if id != 0 && id != candidate {
|
||||||
|
return nil, nil, fmt.Errorf("ambiguous symbol font mapping")
|
||||||
|
}
|
||||||
|
id = candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if size != 0 && size == len(glyph.Text) {
|
||||||
|
id = original[char]
|
||||||
|
}
|
||||||
|
if id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
names[glyph.Name] = id
|
||||||
|
if size != 0 && size == len(glyph.Text) {
|
||||||
|
mapping[char] = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !legacy || len(mapping) == 0 {
|
||||||
|
return data, names, nil
|
||||||
|
}
|
||||||
|
tables["cmap"] = buildCmapTable(0, mapping)
|
||||||
|
program, err := serializeOTF(tables)
|
||||||
|
return program, names, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// symbolFontName 从字体名称表识别标准符号字体,不依据文件名推断编码
|
||||||
|
// 入参: data 名称表数据
|
||||||
|
// 返回: string 标准字体名,无法识别时为空
|
||||||
|
func symbolFontName(data []byte) string {
|
||||||
|
for _, name := range fontNamesFromTable(data) {
|
||||||
|
switch fontNormalizeName(name) {
|
||||||
|
case "symbol", "symbolmt", "symbolregular":
|
||||||
|
return "Symbol"
|
||||||
|
case "zapfdingbats", "zapfdingbatsregular", "itczapfdingbats":
|
||||||
|
return "ZapfDingbats"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// symbolFontCmap 识别不含Unicode字符表的Windows符号字体
|
||||||
|
// 入参: data 字符映射表
|
||||||
|
// 返回: bool 是否为旧式符号字体
|
||||||
|
func symbolFontCmap(data []byte) bool {
|
||||||
|
if len(data) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
count := int(binary.BigEndian.Uint16(data[2:]))
|
||||||
|
if count > (len(data)-4)/8 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
symbol := false
|
||||||
|
for i := range count {
|
||||||
|
pos := 4 + i*8
|
||||||
|
platform, encoding := binary.BigEndian.Uint16(data[pos:]), binary.BigEndian.Uint16(data[pos+2:])
|
||||||
|
if platform == 0 || platform == 3 && (encoding == 1 || encoding == 10) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
symbol = symbol || platform == 3 && encoding == 0
|
||||||
|
}
|
||||||
|
return symbol
|
||||||
|
}
|
||||||
+6
-71
@@ -143,7 +143,7 @@ func (p *pdfImporter) importedFont(source *pdfgo.Font) (*pdfImportedFont, error)
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// symbolFont 将标准符号字体的旧式字符表转换为Unicode映射,保留原字形编号
|
// symbolFont 加载标准符号字体,保留原字形编号和源字形名称
|
||||||
// 入参: source 未嵌入的PDF字体
|
// 入参: source 未嵌入的PDF字体
|
||||||
// 返回: *pdfImportedFont 可用的符号字体,无匹配时为空, error 字体或取消错误
|
// 返回: *pdfImportedFont 可用的符号字体,无匹配时为空, error 字体或取消错误
|
||||||
func (p *pdfImporter) symbolFont(source *pdfgo.Font) (*pdfImportedFont, error) {
|
func (p *pdfImporter) symbolFont(source *pdfgo.Font) (*pdfImportedFont, error) {
|
||||||
@@ -167,56 +167,14 @@ func (p *pdfImporter) symbolFont(source *pdfgo.Font) (*pdfImportedFont, error) {
|
|||||||
p.symbolFonts[source] = nil
|
p.symbolFonts[source] = nil
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
tables, err := fontFileTables(resolved.Data, 0)
|
program, names, err := symbolFontProgram(p.ctx, resolved.Data, name)
|
||||||
if err != nil || !pdfSymbolCmap(tables["cmap"]) {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(names) == 0 {
|
||||||
p.symbolFonts[source] = nil
|
p.symbolFonts[source] = nil
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
standard, err := p.reader.ReadFontContext(p.ctx, pdfgo.Dictionary{"Type": pdfgo.Name("Font"), "Subtype": pdfgo.Name("Type1"), "BaseFont": pdfgo.Name(name)})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
codes := make([]byte, 256)
|
|
||||||
for index := range codes {
|
|
||||||
codes[index] = byte(index)
|
|
||||||
}
|
|
||||||
glyphs, err := standard.DecodeContext(p.ctx, codes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
original := parseCmapMappings(tables["cmap"])
|
|
||||||
mapping := make(map[rune]uint16)
|
|
||||||
names := make(map[string]uint16)
|
|
||||||
for _, glyph := range glyphs {
|
|
||||||
if glyph.Name == ".notdef" || glyph.Name == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
id := original[rune(glyph.Code)]
|
|
||||||
for _, base := range [...]rune{0xf000, 0xf100, 0xf200} {
|
|
||||||
if candidate := original[base+rune(glyph.Code)]; candidate != 0 {
|
|
||||||
if id != 0 && id != candidate {
|
|
||||||
return nil, fmt.Errorf("ambiguous PDF symbol font mapping")
|
|
||||||
}
|
|
||||||
id = candidate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if id == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names[glyph.Name] = id
|
|
||||||
if char, size := utf8.DecodeRuneInString(glyph.Text); size != 0 && size == len(glyph.Text) {
|
|
||||||
mapping[char] = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(mapping) == 0 {
|
|
||||||
p.symbolFonts[source] = nil
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
tables["cmap"] = buildCmapTable(0, mapping)
|
|
||||||
program, err := serializeOTF(tables)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resource, err := p.editor.backends.FontResources.OpenFontResource(FontFile{Name: name + ".ttf", Data: program}, 0)
|
resource, err := p.editor.backends.FontResources.OpenFontResource(FontFile{Name: name + ".ttf", Data: program}, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -234,29 +192,6 @@ func (p *pdfImporter) symbolFont(source *pdfgo.Font) (*pdfImportedFont, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfSymbolCmap 识别不含Unicode字符表的Windows符号字体
|
|
||||||
// 入参: data 字符映射表
|
|
||||||
// 返回: bool 是否为旧式符号字体
|
|
||||||
func pdfSymbolCmap(data []byte) bool {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
count := int(binary.BigEndian.Uint16(data[2:]))
|
|
||||||
if count > (len(data)-4)/8 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
symbol := false
|
|
||||||
for i := range count {
|
|
||||||
pos := 4 + i*8
|
|
||||||
platform, encoding := binary.BigEndian.Uint16(data[pos:]), binary.BigEndian.Uint16(data[pos+2:])
|
|
||||||
if platform == 0 || platform == 3 && (encoding == 1 || encoding == 10) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
symbol = symbol || platform == 3 && encoding == 0
|
|
||||||
}
|
|
||||||
return symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdfFontProgram 为PDF子集字体补齐封装表,保留字形轮廓和编号
|
// pdfFontProgram 为PDF子集字体补齐封装表,保留字形轮廓和编号
|
||||||
// 无效字符表按PDF已解码字形重建,不改变字符对应的字形编号
|
// 无效字符表按PDF已解码字形重建,不改变字符对应的字形编号
|
||||||
// 入参: source PDF字体, type1 已解析的Type1程序,nil时按需解析
|
// 入参: source PDF字体, type1 已解析的Type1程序,nil时按需解析
|
||||||
|
|||||||
Reference in new issue
Block a user