mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
1 parent
0be872691d
commit
5cc811c2a1
6 files changed
+159
-213
No files matched your search
@@ -15,9 +15,15 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FontFace 字体文件中的字体名称与零起始索引,不依赖文件名
|
||||||
|
type FontFace = basefont.Face
|
||||||
|
|
||||||
// FixFontDataAggressive 激进修复字体数据
|
// FixFontDataAggressive 激进修复字体数据
|
||||||
// 尝试修复缺失表(OS/2, cmap等)的TrueType字体或包装CFF裸数据
|
// 尝试修复缺失表(OS/2, cmap等)的TrueType字体或包装CFF裸数据
|
||||||
// 对显式字形映射补充私有字符cmap,便于按glyph id渲染
|
// 对显式字形映射补充私有字符cmap,便于按glyph id渲染
|
||||||
@@ -26,3 +32,38 @@ import (
|
|||||||
func FixFontDataAggressive(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uint16, bool, error) {
|
func FixFontDataAggressive(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uint16, bool, error) {
|
||||||
return basefont.FixDataAggressive(data, fixCmap, fixName)
|
return basefont.FixDataAggressive(data, fixCmap, fixName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Faces 读取字体名称表,集合文件按原顺序列出各项,不解析字形轮廓
|
||||||
|
// 返回: []FontFace 字体列表, error 格式或名称表错误
|
||||||
|
func (f FontFile) Faces() ([]FontFace, error) {
|
||||||
|
data, err := decodeFontContainer(f.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return basefont.ReadFaces(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Face 提取指定字体,返回独立的OpenType数据,可用于预览或AddFont,且不修改原文件
|
||||||
|
// 入参: index 零起始字体索引,非集合文件只能为0
|
||||||
|
// 返回: []byte 独立字体数据, error 格式或索引错误
|
||||||
|
func (f FontFile) Face(index int) ([]byte, error) {
|
||||||
|
data, err := decodeFontContainer(f.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return basefont.ExtractFace(data, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MissingGlyphs 检查指定字体的字符覆盖,不注册资源或修改文档,忽略段落换行
|
||||||
|
// 入参: index 零起始字体索引, text 待检查文字
|
||||||
|
// 返回: string 按首次出现顺序去重的缺失字符, error 字体或文字格式错误
|
||||||
|
func (f FontFile) MissingGlyphs(index int, text string) (string, error) {
|
||||||
|
if !utf8.ValidString(text) {
|
||||||
|
return "", fmt.Errorf("text must be UTF-8")
|
||||||
|
}
|
||||||
|
data, err := f.Face(index)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return basefont.MissingGlyphs(data, text)
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// 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 (
|
|
||||||
"fmt"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FontFace 字体文件中的字体名称与零起始索引,不依赖文件名
|
|
||||||
type FontFace = basefont.Face
|
|
||||||
|
|
||||||
// Faces 读取字体名称表,集合文件按原顺序列出各项,不解析字形轮廓
|
|
||||||
// 返回: []FontFace 字体列表, error 格式或名称表错误
|
|
||||||
func (f FontFile) Faces() ([]FontFace, error) {
|
|
||||||
data, err := decodeFontContainer(f.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return basefont.ReadFaces(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Face 提取指定字体,返回独立的OpenType数据,可用于预览或AddFont,且不修改原文件
|
|
||||||
// 入参: index 零起始字体索引,非集合文件只能为0
|
|
||||||
// 返回: []byte 独立字体数据, error 格式或索引错误
|
|
||||||
func (f FontFile) Face(index int) ([]byte, error) {
|
|
||||||
data, err := decodeFontContainer(f.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return basefont.ExtractFace(data, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MissingGlyphs 检查指定字体的字符覆盖,不注册资源或修改文档,忽略段落换行
|
|
||||||
// 入参: index 零起始字体索引, text 待检查文字
|
|
||||||
// 返回: string 按首次出现顺序去重的缺失字符, error 字体或文字格式错误
|
|
||||||
func (f FontFile) MissingGlyphs(index int, text string) (string, error) {
|
|
||||||
if !utf8.ValidString(text) {
|
|
||||||
return "", fmt.Errorf("text must be UTF-8")
|
|
||||||
}
|
|
||||||
data, err := f.Face(index)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return basefont.MissingGlyphs(data, text)
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -389,3 +390,46 @@ func appendFontPattern(patterns []string, seen map[string]bool, pattern string)
|
|||||||
seen[key] = true
|
seen[key] = true
|
||||||
return append(patterns, pattern)
|
return append(patterns, pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendFontFileNames 建立各字体的内部名称与文件别名匹配候选
|
||||||
|
// 入参: candidates 字体候选, index 文件候选索引, faces 各字体内部名称
|
||||||
|
// 返回: []fontFileCandidate 字体候选列表
|
||||||
|
func appendFontFileNames(candidates []fontFileCandidate, index int, faces [][]string) []fontFileCandidate {
|
||||||
|
file := candidates[index]
|
||||||
|
replaced := false
|
||||||
|
for face, names := range faces {
|
||||||
|
file.face = face
|
||||||
|
file.names = make([]string, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
file.names[i] = basefont.NormalizeName(name)
|
||||||
|
}
|
||||||
|
for _, name := range file.names {
|
||||||
|
file.normalized = name
|
||||||
|
if !replaced {
|
||||||
|
candidates[index] = file
|
||||||
|
replaced = true
|
||||||
|
} else {
|
||||||
|
candidates = append(candidates, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontCollectionIndex 按内部名称与样式选择集合项,未匹配时保留首项
|
||||||
|
// 入参: data 集合数据, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||||
|
// 返回: int 零起始字体索引
|
||||||
|
func fontCollectionIndex(data []byte, names []string, bold, italic bool) int {
|
||||||
|
return fontNameIndex(basefont.FileNames(bytes.NewReader(data)), names, bold, italic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontNameIndex 按名称与样式选择字体索引,未匹配时保留首项
|
||||||
|
// 入参: faces 各字体名称, names 目标名称, bold 是否粗体, italic 是否斜体
|
||||||
|
// 返回: int 零起始字体索引
|
||||||
|
func fontNameIndex(faces [][]string, names []string, bold, italic bool) int {
|
||||||
|
candidates := appendFontFileNames([]fontFileCandidate{{}}, 0, faces)
|
||||||
|
if matches := fontFileMatches(candidates, names, bold, italic); len(matches) > 0 {
|
||||||
|
return matches[0].face
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// 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"
|
|
||||||
|
|
||||||
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
|
||||||
)
|
|
||||||
|
|
||||||
// appendFontFileNames 建立各字体的内部名称与文件别名匹配候选
|
|
||||||
// 入参: candidates 字体候选, index 文件候选索引, faces 各字体内部名称
|
|
||||||
// 返回: []fontFileCandidate 字体候选列表
|
|
||||||
func appendFontFileNames(candidates []fontFileCandidate, index int, faces [][]string) []fontFileCandidate {
|
|
||||||
file := candidates[index]
|
|
||||||
replaced := false
|
|
||||||
for face, names := range faces {
|
|
||||||
file.face = face
|
|
||||||
file.names = make([]string, len(names))
|
|
||||||
for i, name := range names {
|
|
||||||
file.names[i] = basefont.NormalizeName(name)
|
|
||||||
}
|
|
||||||
for _, name := range file.names {
|
|
||||||
file.normalized = name
|
|
||||||
if !replaced {
|
|
||||||
candidates[index] = file
|
|
||||||
replaced = true
|
|
||||||
} else {
|
|
||||||
candidates = append(candidates, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return candidates
|
|
||||||
}
|
|
||||||
|
|
||||||
// fontCollectionIndex 按内部名称与样式选择集合项,未匹配时保留首项
|
|
||||||
// 入参: data 集合数据, names 字体名称, bold 是否粗体, italic 是否斜体
|
|
||||||
// 返回: int 零起始字体索引
|
|
||||||
func fontCollectionIndex(data []byte, names []string, bold, italic bool) int {
|
|
||||||
return fontNameIndex(basefont.FileNames(bytes.NewReader(data)), names, bold, italic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fontNameIndex 按名称与样式选择字体索引,未匹配时保留首项
|
|
||||||
// 入参: faces 各字体名称, names 目标名称, bold 是否粗体, italic 是否斜体
|
|
||||||
// 返回: int 零起始字体索引
|
|
||||||
func fontNameIndex(faces [][]string, names []string, bold, italic bool) int {
|
|
||||||
candidates := appendFontFileNames([]fontFileCandidate{{}}, 0, faces)
|
|
||||||
if matches := fontFileMatches(candidates, names, bold, italic); len(matches) > 0 {
|
|
||||||
return matches[0].face
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,13 @@
|
|||||||
|
|
||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
||||||
|
)
|
||||||
|
|
||||||
// FontResource 保存独立字体数据、标准元信息与只读度量,不包含绘图库类型
|
// FontResource 保存独立字体数据、标准元信息与只读度量,不包含绘图库类型
|
||||||
type FontResource struct {
|
type FontResource struct {
|
||||||
FontMetrics
|
FontMetrics
|
||||||
@@ -38,3 +45,70 @@ type FontResourceBackend interface {
|
|||||||
SubsetFont(data []byte, glyphs []uint16, preserve bool) ([]byte, error)
|
SubsetFont(data []byte, glyphs []uint16, preserve bool) ([]byte, error)
|
||||||
SubsetSourceFont(data []byte, usage FontUsage) ([]byte, error)
|
SubsetSourceFont(data []byte, usage FontUsage) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SFNTBackend 提供独立的OpenType字体度量、轮廓和资源处理
|
||||||
|
type SFNTBackend struct{}
|
||||||
|
|
||||||
|
// sfntResourceMetrics 隔离资源解析器的度量实现
|
||||||
|
type sfntResourceMetrics struct{ *basefont.SFNT }
|
||||||
|
|
||||||
|
// Name 返回资源后端标识
|
||||||
|
// 返回: string 后端标识
|
||||||
|
func (SFNTBackend) Name() string { return "sfnt" }
|
||||||
|
|
||||||
|
// OpenFontResource 提取独立字体并读取名称、样式和裁剪能力
|
||||||
|
// CFF2使用默认实例的静态轮廓,不改变原字体文件
|
||||||
|
// 入参: file 字体文件, index 集合索引
|
||||||
|
// 返回: *FontResource 字体资源, error 解析错误
|
||||||
|
func (SFNTBackend) OpenFontResource(file FontFile, index int) (*FontResource, error) {
|
||||||
|
data, err := file.Face(index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err = basefont.DefaultCFF2Font(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sfnt, err := basefont.ParseSFNT(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := basefont.SFNTName(sfnt, 6, 4, 1)
|
||||||
|
if name == "" {
|
||||||
|
return nil, fmt.Errorf("font has no name")
|
||||||
|
}
|
||||||
|
extension := ".ttf"
|
||||||
|
if sfnt.IsCFF {
|
||||||
|
extension = ".otf"
|
||||||
|
}
|
||||||
|
return &FontResource{
|
||||||
|
FontMetrics: sfntResourceMetrics{sfnt}, Data: data, Extension: extension,
|
||||||
|
Font: Font{FontName: name, FamilyName: basefont.SFNTName(sfnt, 16, 1), Charset: "unicode", Bold: binary.BigEndian.Uint16(sfnt.Tables["head"][44:])&1 != 0, Italic: binary.BigEndian.Uint16(sfnt.Tables["head"][44:])&2 != 0, FixedWidth: len(sfnt.Tables["post"]) >= 16 && binary.BigEndian.Uint32(sfnt.Tables["post"][12:]) != 0},
|
||||||
|
CanSubset: basefont.Subsettable(sfnt),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubsetFont 裁剪新建字体并按要求保留显式字形编号
|
||||||
|
// 入参: data 字体数据, glyphs 用字, preserve 是否保留编号
|
||||||
|
// 返回: []byte 子集, error 裁剪错误
|
||||||
|
func (SFNTBackend) SubsetFont(data []byte, glyphs []uint16, preserve bool) ([]byte, error) {
|
||||||
|
return basefont.SubsetSFNT(data, glyphs, preserve)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubsetSourceFont 按全包引用裁剪原字体,不确定时保留原资源
|
||||||
|
// 入参: data 字体数据, usage 用字信息
|
||||||
|
// 返回: []byte 子集, error 裁剪错误
|
||||||
|
func (SFNTBackend) SubsetSourceFont(data []byte, usage FontUsage) ([]byte, error) {
|
||||||
|
u := &basefont.Usage{Characters: make(map[rune]bool), Glyphs: make(map[uint16]bool), Unsafe: usage.Unsafe}
|
||||||
|
for _, char := range usage.Characters {
|
||||||
|
u.Characters[char] = true
|
||||||
|
}
|
||||||
|
for _, glyph := range usage.Glyphs {
|
||||||
|
u.Glyphs[glyph] = true
|
||||||
|
}
|
||||||
|
return basefont.SubsetSourceSFNT(data, u), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write 返回未改动元信息的字体数据
|
||||||
|
// 返回: []byte 字体数据
|
||||||
|
func (f sfntResourceMetrics) Write() []byte { return f.SFNT.Write() }
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// 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"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
basefont "github.com/xiaoqidun/ofdgo/internal/base/font"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SFNTBackend 提供独立的OpenType字体度量、轮廓和资源处理
|
|
||||||
type SFNTBackend struct{}
|
|
||||||
|
|
||||||
// sfntResourceMetrics 隔离资源解析器的度量实现
|
|
||||||
type sfntResourceMetrics struct{ *basefont.SFNT }
|
|
||||||
|
|
||||||
// Name 返回资源后端标识
|
|
||||||
// 返回: string 后端标识
|
|
||||||
func (SFNTBackend) Name() string { return "sfnt" }
|
|
||||||
|
|
||||||
// OpenFontResource 提取独立字体并读取名称、样式和裁剪能力
|
|
||||||
// CFF2使用默认实例的静态轮廓,不改变原字体文件
|
|
||||||
// 入参: file 字体文件, index 集合索引
|
|
||||||
// 返回: *FontResource 字体资源, error 解析错误
|
|
||||||
func (SFNTBackend) OpenFontResource(file FontFile, index int) (*FontResource, error) {
|
|
||||||
data, err := file.Face(index)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err = basefont.DefaultCFF2Font(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sfnt, err := basefont.ParseSFNT(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
name := basefont.SFNTName(sfnt, 6, 4, 1)
|
|
||||||
if name == "" {
|
|
||||||
return nil, fmt.Errorf("font has no name")
|
|
||||||
}
|
|
||||||
extension := ".ttf"
|
|
||||||
if sfnt.IsCFF {
|
|
||||||
extension = ".otf"
|
|
||||||
}
|
|
||||||
return &FontResource{
|
|
||||||
FontMetrics: sfntResourceMetrics{sfnt}, Data: data, Extension: extension,
|
|
||||||
Font: Font{FontName: name, FamilyName: basefont.SFNTName(sfnt, 16, 1), Charset: "unicode", Bold: binary.BigEndian.Uint16(sfnt.Tables["head"][44:])&1 != 0, Italic: binary.BigEndian.Uint16(sfnt.Tables["head"][44:])&2 != 0, FixedWidth: len(sfnt.Tables["post"]) >= 16 && binary.BigEndian.Uint32(sfnt.Tables["post"][12:]) != 0},
|
|
||||||
CanSubset: basefont.Subsettable(sfnt),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubsetFont 裁剪新建字体并按要求保留显式字形编号
|
|
||||||
// 入参: data 字体数据, glyphs 用字, preserve 是否保留编号
|
|
||||||
// 返回: []byte 子集, error 裁剪错误
|
|
||||||
func (SFNTBackend) SubsetFont(data []byte, glyphs []uint16, preserve bool) ([]byte, error) {
|
|
||||||
return basefont.SubsetSFNT(data, glyphs, preserve)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubsetSourceFont 按全包引用裁剪原字体,不确定时保留原资源
|
|
||||||
// 入参: data 字体数据, usage 用字信息
|
|
||||||
// 返回: []byte 子集, error 裁剪错误
|
|
||||||
func (SFNTBackend) SubsetSourceFont(data []byte, usage FontUsage) ([]byte, error) {
|
|
||||||
u := &basefont.Usage{Characters: make(map[rune]bool), Glyphs: make(map[uint16]bool), Unsafe: usage.Unsafe}
|
|
||||||
for _, char := range usage.Characters {
|
|
||||||
u.Characters[char] = true
|
|
||||||
}
|
|
||||||
for _, glyph := range usage.Glyphs {
|
|
||||||
u.Glyphs[glyph] = true
|
|
||||||
}
|
|
||||||
return basefont.SubsetSourceSFNT(data, u), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write 返回未改动元信息的字体数据
|
|
||||||
// 返回: []byte 字体数据
|
|
||||||
func (f sfntResourceMetrics) Write() []byte { return f.SFNT.Write() }
|
|
||||||
Reference in new issue
Block a user