mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
This commit is contained in:
1 parent
f361c61dcc
commit
a3435d5d8d
12 files changed
+332
-51
No files matched your search
+10
-2
@@ -861,9 +861,17 @@ function uniqueLocalFonts(fonts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectLocalFonts(fonts, names, limit) {
|
function selectLocalFonts(fonts, names, limit) {
|
||||||
const available = new Map(uniqueLocalFonts(fonts).map((font) => [localFontName(font), font]));
|
const available = new Map();
|
||||||
|
for (const font of uniqueLocalFonts(fonts)) {
|
||||||
|
available.set(localFontName(font), font);
|
||||||
|
for (const name of [font.postscriptName, [font.family, font.style].filter(Boolean).join(" ")]) {
|
||||||
|
if (name) {
|
||||||
|
available.set(`${name}.ttf`, font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const matched = callWASM("ofdgoFontFileMatches", [...available.keys()], names);
|
const matched = callWASM("ofdgoFontFileMatches", [...available.keys()], names);
|
||||||
return matched.slice(0, limit).map((name) => available.get(name));
|
return uniqueLocalFonts(matched.map((name) => available.get(name))).slice(0, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fontData(fonts) {
|
async function fontData(fonts) {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer {
|
|||||||
fontSourceCache: make(map[string][]fontSource),
|
fontSourceCache: make(map[string][]fontSource),
|
||||||
fontSourceUsed: make(map[string]fontSource),
|
fontSourceUsed: make(map[string]fontSource),
|
||||||
fontDirCandidates: make(map[string][]fontFileCandidate),
|
fontDirCandidates: make(map[string][]fontFileCandidate),
|
||||||
|
fontFSCandidates: make(map[int][]fontFileCandidate),
|
||||||
textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue),
|
textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue),
|
||||||
templatePageCache: make(map[string]*PageContent),
|
templatePageCache: make(map[string]*PageContent),
|
||||||
imageCache: make(map[string]image.Image),
|
imageCache: make(map[string]image.Image),
|
||||||
|
|||||||
@@ -787,6 +787,7 @@ func readCFFLocalSubrs(data []byte, fdArrOff int, fdCount int) ([][][]byte, []cf
|
|||||||
return localSubrs, privateDicts
|
return localSubrs, privateDicts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type2Operand Type2操作数
|
||||||
type type2Operand struct {
|
type type2Operand struct {
|
||||||
value int
|
value int
|
||||||
outStart int
|
outStart int
|
||||||
|
|||||||
+42
-18
@@ -56,6 +56,9 @@ func NewFontFS(fonts []FontFile) *FontFS {
|
|||||||
}
|
}
|
||||||
sort.Strings(fsys.names)
|
sort.Strings(fsys.names)
|
||||||
fsys.candidates = fontFileCandidates(fsys.names, path.Base)
|
fsys.candidates = fontFileCandidates(fsys.names, path.Base)
|
||||||
|
for _, file := range fsys.candidates {
|
||||||
|
fsys.candidates = appendFontFileNames(fsys.candidates, file, fontFileNames(bytes.NewReader(fsys.files[file.name])))
|
||||||
|
}
|
||||||
return fsys
|
return fsys
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,8 +129,10 @@ func (fsys *FontFS) Match(names ...string) (string, bool) {
|
|||||||
// 入参: bold 是否粗体, italic 是否斜体, names 字体名称列表
|
// 入参: bold 是否粗体, italic 是否斜体, names 字体名称列表
|
||||||
// 返回: string 匹配字体文件, bool 是否为名称匹配
|
// 返回: string 匹配字体文件, bool 是否为名称匹配
|
||||||
func (fsys *FontFS) MatchStyle(bold, italic bool, names ...string) (string, bool) {
|
func (fsys *FontFS) MatchStyle(bold, italic bool, names ...string) (string, bool) {
|
||||||
if matches := fsys.matchPatternsStyle(fontFilePatterns(names...), bold, italic); len(matches) > 0 {
|
if len(names) > 0 {
|
||||||
return matches[0], true
|
if matches := fsys.matchStyle(names, bold, italic); len(matches) > 0 {
|
||||||
|
return matches[0].name, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if matches := fsys.fallbackFonts(bold, italic); len(matches) > 0 {
|
if matches := fsys.fallbackFonts(bold, italic); len(matches) > 0 {
|
||||||
return matches[0], false
|
return matches[0], false
|
||||||
@@ -139,41 +144,57 @@ func (fsys *FontFS) MatchStyle(bold, italic bool, names ...string) (string, bool
|
|||||||
// 入参: files 可用字体文件名, bold 是否粗体, italic 是否斜体, names 字体名称,留空匹配默认回退字体
|
// 入参: files 可用字体文件名, bold 是否粗体, italic 是否斜体, names 字体名称,留空匹配默认回退字体
|
||||||
// 返回: []string 按优先级排列的匹配文件名
|
// 返回: []string 按优先级排列的匹配文件名
|
||||||
func FontFileMatches(files []string, bold, italic bool, names ...string) []string {
|
func FontFileMatches(files []string, bold, italic bool, names ...string) []string {
|
||||||
if len(names) == 0 {
|
return fontFileMatchNames(fontFileMatches(fontFileCandidates(files, path.Base), names, bold, italic))
|
||||||
names = fontDefaultSystemNames()
|
|
||||||
}
|
|
||||||
return fontFileMatches(fontFileCandidates(files, path.Base), fontFilePatterns(names...), bold, italic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchPatternsStyle 匹配指定样式的字体文件模式
|
// matchStyle 匹配指定样式的字体名称
|
||||||
// 入参: patterns 匹配模式列表, bold 是否粗体, italic 是否斜体
|
// 入参: names 字体名称列表, bold 是否粗体, italic 是否斜体
|
||||||
// 返回: []string 字体文件列表
|
// 返回: []fontFileMatch 字体匹配列表
|
||||||
func (fsys *FontFS) matchPatternsStyle(patterns []string, bold, italic bool) []string {
|
func (fsys *FontFS) matchStyle(names []string, bold, italic bool) []fontFileMatch {
|
||||||
return fontFileMatches(fsys.candidates, patterns, bold, italic)
|
return fontFileMatches(fsys.candidates, names, bold, italic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fontFileMatches 匹配字体文件候选
|
// fontFileMatches 匹配字体文件候选
|
||||||
// 入参: candidates 字体候选, patterns 匹配模式, bold 是否粗体, italic 是否斜体
|
// 入参: candidates 字体候选, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||||
// 返回: []string 按优先级排列的匹配文件名
|
// 返回: []fontFileMatch 按优先级排列的字体匹配
|
||||||
func fontFileMatches(candidates []fontFileCandidate, patterns []string, bold, italic bool) []string {
|
func fontFileMatches(candidates []fontFileCandidate, names []string, bold, italic bool) []fontFileMatch {
|
||||||
matches := make([]fontFileMatch, 0, len(candidates))
|
matches := make([]fontFileMatch, 0, len(candidates))
|
||||||
seen := make(map[string]int, len(candidates))
|
seen := make(map[string]int, len(candidates))
|
||||||
for _, matcher := range newFontPatternMatchers(patterns) {
|
for i, name := range names {
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for j, alias := range append([]string{name}, fontQualifiedNames(name)...) {
|
||||||
|
matcher := fontPatternMatcher{stem: fontNormalizeName(alias), priority: (i-len(names))*2 + min(j, 1)}
|
||||||
|
if matcher.stem == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, file := range candidates {
|
||||||
|
if rank := matcher.rankCandidate(file); rank == fontMatchExact {
|
||||||
|
appendFontFileMatch(&matches, seen, matcher, file, rank, bold, italic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(names) == 0 {
|
||||||
|
names = fontDefaultSystemNames()
|
||||||
|
}
|
||||||
|
for _, matcher := range newFontPatternMatchers(fontFilePatterns(names...)) {
|
||||||
for _, file := range candidates {
|
for _, file := range candidates {
|
||||||
rank := matcher.rankCandidate(file)
|
rank := matcher.rankCandidate(file)
|
||||||
appendFontFileMatch(&matches, seen, matcher, file, rank, bold, italic)
|
appendFontFileMatch(&matches, seen, matcher, file, rank, bold, italic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sortFontFileMatches(matches)
|
sortFontFileMatches(matches)
|
||||||
return fontFileMatchNames(matches)
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallbackFonts 获取回退字体文件
|
// fallbackFonts 获取回退字体文件
|
||||||
// 入参: bold 是否粗体, italic 是否斜体
|
// 入参: bold 是否粗体, italic 是否斜体
|
||||||
// 返回: []string 字体文件列表
|
// 返回: []string 字体文件列表
|
||||||
func (fsys *FontFS) fallbackFonts(bold, italic bool) []string {
|
func (fsys *FontFS) fallbackFonts(bold, italic bool) []string {
|
||||||
if matches := fsys.matchPatternsStyle(fontFilePatterns(fontDefaultSystemNames()...), bold, italic); len(matches) > 0 {
|
if matches := fsys.matchStyle(nil, bold, italic); len(matches) > 0 {
|
||||||
return matches
|
return fontFileMatchNames(matches)
|
||||||
}
|
}
|
||||||
if len(fsys.names) > 0 {
|
if len(fsys.names) > 0 {
|
||||||
return []string{fsys.names[0]}
|
return []string{fsys.names[0]}
|
||||||
@@ -210,6 +231,7 @@ func cleanFontName(name string) string {
|
|||||||
return strings.ToLower(name)
|
return strings.ToLower(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fontFileCandidate 字体文件候选
|
||||||
type fontFileCandidate struct {
|
type fontFileCandidate struct {
|
||||||
name string
|
name string
|
||||||
base string
|
base string
|
||||||
@@ -238,6 +260,7 @@ func fontFileCandidates(names []string, base func(string) string) []fontFileCand
|
|||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fontPatternMatcher 字体文件匹配器
|
||||||
type fontPatternMatcher struct {
|
type fontPatternMatcher struct {
|
||||||
pattern string
|
pattern string
|
||||||
lowerPattern string
|
lowerPattern string
|
||||||
@@ -351,6 +374,7 @@ func (m fontPatternMatcher) styleSuffixNormalized(name string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fontFileMatch 字体文件匹配结果
|
||||||
type fontFileMatch struct {
|
type fontFileMatch struct {
|
||||||
name string
|
name string
|
||||||
priority int
|
priority int
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package ofdgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
@@ -184,14 +183,6 @@ func (r *Renderer) fontInfo(font Font) FontInfo {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
// fontFSMatchesStyle 匹配字体文件系统中的指定样式字体文件
|
|
||||||
// 入参: fsys 字体文件系统, patterns 匹配模式列表, bold 是否粗体, italic 是否斜体
|
|
||||||
// 返回: []string 字体文件列表
|
|
||||||
func fontFSMatchesStyle(fsys fs.FS, patterns []string, bold, italic bool) []string {
|
|
||||||
names, _ := fs.Glob(fsys, "*")
|
|
||||||
return fontFileMatches(fontFileCandidates(names, path.Base), patterns, bold, italic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fontUsage 统计文档字体使用次数
|
// fontUsage 统计文档字体使用次数
|
||||||
// 入参: doc 文档结构, pages 页面内容列表, nil表示读取文档页面
|
// 入参: doc 文档结构, pages 页面内容列表, nil表示读取文档页面
|
||||||
// 返回: map[string]int 字体使用次数
|
// 返回: map[string]int 字体使用次数
|
||||||
|
|||||||
+79
-21
@@ -15,9 +15,14 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/tdewolff/canvas"
|
"github.com/tdewolff/canvas"
|
||||||
)
|
)
|
||||||
@@ -37,6 +42,7 @@ type fontSource struct {
|
|||||||
index int
|
index int
|
||||||
name string
|
name string
|
||||||
exact bool
|
exact bool
|
||||||
|
priority int
|
||||||
}
|
}
|
||||||
|
|
||||||
// fontSourceKey 字体来源去重键
|
// fontSourceKey 字体来源去重键
|
||||||
@@ -139,36 +145,42 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
|
|||||||
}
|
}
|
||||||
bold := style&canvas.FontBold != 0
|
bold := style&canvas.FontBold != 0
|
||||||
italic := style&canvas.FontItalic != 0
|
italic := style&canvas.FontItalic != 0
|
||||||
patterns := fontFilePatterns(font.FontName, font.FamilyName)
|
names := []string{font.FontName, font.FamilyName}
|
||||||
sources := make([]fontSource, 0)
|
sources := make([]fontSource, 0)
|
||||||
seen := make(map[fontSourceKey]bool)
|
seen := make(map[fontSourceKey]bool)
|
||||||
for _, dir := range r.fontDirs {
|
for _, dir := range r.fontDirs {
|
||||||
for _, name := range r.matchFontFiles(dir, patterns, bold, italic) {
|
for _, match := range r.matchFontFiles(dir, names, bold, italic) {
|
||||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: name, exact: true})
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, exact: true, priority: min(match.priority, 0)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index, fsys := range r.fontFS {
|
for index := range r.fontFS {
|
||||||
for _, name := range fontFSMatchesStyle(fsys, patterns, bold, italic) {
|
for _, match := range r.matchFontFS(index, names, bold, italic) {
|
||||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name, exact: true})
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: match.name, exact: true, priority: min(match.priority, 0)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sortFontSources(sources)
|
||||||
|
systemStart := len(sources)
|
||||||
if canLoadSystemFonts() {
|
if canLoadSystemFonts() {
|
||||||
systemDirs := systemFontDirs()
|
for _, dir := range systemFontDirs() {
|
||||||
for _, name := range []string{font.FamilyName, font.FontName} {
|
for _, match := range r.matchFontFiles(dir, names, bold, italic) {
|
||||||
for _, systemName := range fontSystemNames(name) {
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, exact: true, priority: min(match.priority, 0)})
|
||||||
systemPatterns := fontFilePatterns(systemName)
|
|
||||||
for _, dir := range systemDirs {
|
|
||||||
for _, match := range r.matchFontFiles(dir, systemPatterns, bold, italic) {
|
|
||||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match, exact: true})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceSystem, name: systemName, exact: true})
|
for _, name := range fontSystemNames(names...) {
|
||||||
|
source := fontSource{kind: fontSourceSystem, name: name, exact: true}
|
||||||
|
for i, requested := range names {
|
||||||
|
if fontNormalizeName(name) == fontNormalizeName(requested) {
|
||||||
|
source.priority = (i - len(names)) * 2
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sources = appendFontSource(sources, seen, source)
|
||||||
}
|
}
|
||||||
for index, fsys := range r.fontFS {
|
}
|
||||||
for _, name := range fontFSMatchesStyle(fsys, fontFilePatterns(fontDefaultSystemNames()...), bold, italic) {
|
sortFontSources(sources[systemStart:])
|
||||||
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name})
|
for index := range r.fontFS {
|
||||||
|
for _, match := range r.matchFontFS(index, nil, bold, italic) {
|
||||||
|
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: match.name})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index, fsys := range r.fontFS {
|
for index, fsys := range r.fontFS {
|
||||||
@@ -181,6 +193,14 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
|
|||||||
return sources
|
return sources
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortFontSources 将完整名称匹配排在回退候选之前
|
||||||
|
// 入参: sources 字体来源列表
|
||||||
|
func sortFontSources(sources []fontSource) {
|
||||||
|
sort.SliceStable(sources, func(i, j int) bool {
|
||||||
|
return sources[i].priority < sources[j].priority
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// appendFontSource 追加字体来源
|
// appendFontSource 追加字体来源
|
||||||
// 入参: sources 字体来源列表, seen 去重映射, source 字体来源
|
// 入参: sources 字体来源列表, seen 去重映射, source 字体来源
|
||||||
// 返回: []fontSource 字体来源列表
|
// 返回: []fontSource 字体来源列表
|
||||||
@@ -250,16 +270,54 @@ func (r *Renderer) loadFontSource(family *canvas.FontFamily, source fontSource,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// matchFontFiles 查找字体文件
|
// matchFontFiles 查找字体文件
|
||||||
// 入参: dir 目录, patterns 模式列表, bold 是否粗体, italic 是否斜体
|
// 入参: dir 字体目录, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||||
// 返回: []string 文件列表
|
// 返回: []fontFileMatch 字体匹配列表
|
||||||
func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bool) []string {
|
func (r *Renderer) matchFontFiles(dir string, names []string, bold, italic bool) []fontFileMatch {
|
||||||
candidates, ok := r.fontDirCandidates[dir]
|
candidates, ok := r.fontDirCandidates[dir]
|
||||||
if !ok {
|
if !ok {
|
||||||
files, _ := filepath.Glob(filepath.Join(dir, "*"))
|
files, _ := filepath.Glob(filepath.Join(dir, "*"))
|
||||||
candidates = fontFileCandidates(files, filepath.Base)
|
candidates = fontFileCandidates(files, filepath.Base)
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
file, err := os.Open(candidate.name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
candidates = appendFontFileNames(candidates, candidate, fontFileNames(file))
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
r.fontDirCandidates[dir] = candidates
|
r.fontDirCandidates[dir] = candidates
|
||||||
}
|
}
|
||||||
return fontFileMatches(candidates, patterns, bold, italic)
|
return fontFileMatches(candidates, names, bold, italic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchFontFS 匹配字体文件系统中的字体名称
|
||||||
|
// 入参: index 字体文件系统索引, names 字体名称, bold 是否粗体, italic 是否斜体
|
||||||
|
// 返回: []fontFileMatch 字体匹配列表
|
||||||
|
func (r *Renderer) matchFontFS(index int, names []string, bold, italic bool) []fontFileMatch {
|
||||||
|
fsys := r.fontFS[index]
|
||||||
|
if fonts, ok := fsys.(*FontFS); ok {
|
||||||
|
return fonts.matchStyle(names, bold, italic)
|
||||||
|
}
|
||||||
|
candidates, ok := r.fontFSCandidates[index]
|
||||||
|
if !ok {
|
||||||
|
files, _ := fs.Glob(fsys, "*")
|
||||||
|
candidates = fontFileCandidates(files, path.Base)
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
file, err := fsys.Open(candidate.name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
reader, ok := file.(io.ReaderAt)
|
||||||
|
if !ok {
|
||||||
|
data, _ := io.ReadAll(file)
|
||||||
|
reader = bytes.NewReader(data)
|
||||||
|
}
|
||||||
|
candidates = appendFontFileNames(candidates, candidate, fontFileNames(reader))
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
|
r.fontFSCandidates[index] = candidates
|
||||||
|
}
|
||||||
|
return fontFileMatches(candidates, names, bold, italic)
|
||||||
}
|
}
|
||||||
|
|
||||||
// systemFontDirs 获取系统字体目录
|
// systemFontDirs 获取系统字体目录
|
||||||
|
|||||||
+35
-1
@@ -19,6 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// fontMatchRule 字体匹配规则
|
||||||
type fontMatchRule struct {
|
type fontMatchRule struct {
|
||||||
Keys []string
|
Keys []string
|
||||||
Names []string
|
Names []string
|
||||||
@@ -145,7 +146,10 @@ func FontSystemNames(names ...string) []string {
|
|||||||
// 返回: string 规范化后的字体名称
|
// 返回: string 规范化后的字体名称
|
||||||
func fontNormalizeName(name string) string {
|
func fontNormalizeName(name string) string {
|
||||||
name = strings.ReplaceAll(name, "\\", "/")
|
name = strings.ReplaceAll(name, "\\", "/")
|
||||||
name = strings.TrimSuffix(path.Base(name), path.Ext(name))
|
name = path.Base(strings.TrimSpace(name))
|
||||||
|
if isFontFileName(name) {
|
||||||
|
name = strings.TrimSuffix(name, path.Ext(name))
|
||||||
|
}
|
||||||
name = strings.ToLower(strings.TrimSpace(name))
|
name = strings.ToLower(strings.TrimSpace(name))
|
||||||
return fontNameReplacer.Replace(name)
|
return fontNameReplacer.Replace(name)
|
||||||
}
|
}
|
||||||
@@ -164,6 +168,34 @@ func fontExactCandidateNames(names ...string) []string {
|
|||||||
return fontCandidateNamesByLevel(fontMatchExact, names...)
|
return fontCandidateNamesByLevel(fontMatchExact, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fontQualifiedNames 获取保留名称后缀的字体候选
|
||||||
|
// 入参: name 字体名称
|
||||||
|
// 返回: []string 保留后缀的候选名称
|
||||||
|
func fontQualifiedNames(name string) []string {
|
||||||
|
name = fontNormalizeName(name)
|
||||||
|
var names []string
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, rule := range fontMatchRules {
|
||||||
|
if fontRuleMatchLevel(rule, name) != fontMatchPartial {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prefix := ""
|
||||||
|
for _, group := range [][]string{rule.Keys, rule.Names, {rule.System}} {
|
||||||
|
for _, alias := range group {
|
||||||
|
alias = fontNormalizeName(alias)
|
||||||
|
if len(alias) > len(prefix) && strings.HasPrefix(name, alias) {
|
||||||
|
prefix = alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suffix := strings.TrimPrefix(name, prefix)
|
||||||
|
for _, alias := range append([]string{rule.System}, rule.Names...) {
|
||||||
|
names = appendFontName(names, seen, fontNormalizeName(alias)+suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
// fontCandidateNamesByLevel 获取指定等级内的字体候选名称
|
// fontCandidateNamesByLevel 获取指定等级内的字体候选名称
|
||||||
// 入参: maxLevel 最大匹配等级, names 字体名称列表
|
// 入参: maxLevel 最大匹配等级, names 字体名称列表
|
||||||
// 返回: []string 字体候选名称
|
// 返回: []string 字体候选名称
|
||||||
@@ -199,7 +231,9 @@ func fontCandidateNamesByLevel(maxLevel int, names ...string) []string {
|
|||||||
// 返回: string 字体匹配模式主干
|
// 返回: string 字体匹配模式主干
|
||||||
func fontPatternStem(pattern string) string {
|
func fontPatternStem(pattern string) string {
|
||||||
stem := strings.TrimSuffix(pattern, "*")
|
stem := strings.TrimSuffix(pattern, "*")
|
||||||
|
if isFontFileName(stem) {
|
||||||
stem = strings.TrimSuffix(stem, path.Ext(stem))
|
stem = strings.TrimSuffix(stem, path.Ext(stem))
|
||||||
|
}
|
||||||
if index := strings.IndexAny(stem, "*?["); index >= 0 {
|
if index := strings.IndexAny(stem, "*?["); index >= 0 {
|
||||||
stem = stem[:index]
|
stem = stem[:index]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
// 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"
|
||||||
|
"io"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding/charmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fontNameRecord 字体名称表记录
|
||||||
|
type fontNameRecord struct {
|
||||||
|
Platform uint16
|
||||||
|
Encoding uint16
|
||||||
|
Language uint16
|
||||||
|
Name uint16
|
||||||
|
Length uint16
|
||||||
|
Offset uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendFontFileNames 追加字体内部名称对应的候选
|
||||||
|
// 入参: candidates 字体候选, file 字体文件, names 字体内部名称
|
||||||
|
// 返回: []fontFileCandidate 字体候选列表
|
||||||
|
func appendFontFileNames(candidates []fontFileCandidate, file fontFileCandidate, names []string) []fontFileCandidate {
|
||||||
|
base := file.normalized
|
||||||
|
for _, name := range names {
|
||||||
|
file.normalized = fontNormalizeName(name)
|
||||||
|
if file.normalized != base {
|
||||||
|
candidates = append(candidates, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontFileNames 读取字体或集合首项的名称
|
||||||
|
// 入参: file 字体数据
|
||||||
|
// 返回: []string 字体名称及带样式的字体族名称
|
||||||
|
func fontFileNames(file io.ReaderAt) []string {
|
||||||
|
var header [12]byte
|
||||||
|
if _, err := file.ReadAt(header[:], 0); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var offset int64
|
||||||
|
if string(header[:4]) == "ttcf" {
|
||||||
|
if binary.BigEndian.Uint32(header[8:]) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var entry [4]byte
|
||||||
|
if _, err := file.ReadAt(entry[:], 12); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
offset = int64(binary.BigEndian.Uint32(entry[:]))
|
||||||
|
if _, err := file.ReadAt(header[:], offset); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch string(header[:4]) {
|
||||||
|
case "\x00\x01\x00\x00", "OTTO", "true":
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
entries := make([]byte, int(binary.BigEndian.Uint16(header[4:]))*16)
|
||||||
|
if _, err := file.ReadAt(entries, offset+12); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i := 0; i < len(entries); i += 16 {
|
||||||
|
if string(entries[i:i+4]) != "name" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
offset := int64(binary.BigEndian.Uint32(entries[i+8:]))
|
||||||
|
length := int64(binary.BigEndian.Uint32(entries[i+12:]))
|
||||||
|
data, err := io.ReadAll(io.NewSectionReader(file, offset, length))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fontNamesFromTable(data)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fontNamesFromTable 解析字体名称表
|
||||||
|
// 入参: data 名称表数据
|
||||||
|
// 返回: []string 字体名称列表
|
||||||
|
func fontNamesFromTable(data []byte) []string {
|
||||||
|
if len(data) < 6 || binary.BigEndian.Uint16(data) > 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
count := int(binary.BigEndian.Uint16(data[2:]))
|
||||||
|
storage := int(binary.BigEndian.Uint16(data[4:]))
|
||||||
|
if 6+count*12 > storage || storage > len(data) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
records := make([]fontNameRecord, count)
|
||||||
|
if err := binary.Read(bytes.NewReader(data[6:]), binary.BigEndian, records); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
values := make(map[[4]uint16]string)
|
||||||
|
for _, record := range records {
|
||||||
|
switch record.Name {
|
||||||
|
case 1, 2, 4, 6, 16, 17:
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
start := storage + int(record.Offset)
|
||||||
|
end := start + int(record.Length)
|
||||||
|
if end > len(data) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var name string
|
||||||
|
switch {
|
||||||
|
case record.Platform == 0 || record.Platform == 3 && (record.Encoding == 0 || record.Encoding == 1 || record.Encoding == 10):
|
||||||
|
if record.Length%2 != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chars := make([]uint16, int(record.Length)/2)
|
||||||
|
for i := range chars {
|
||||||
|
chars[i] = binary.BigEndian.Uint16(data[start+i*2:])
|
||||||
|
}
|
||||||
|
name = string(utf16.Decode(chars))
|
||||||
|
case record.Platform == 1 && record.Encoding == 0:
|
||||||
|
name, _ = charmap.Macintosh.NewDecoder().String(string(data[start:end]))
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
values[[4]uint16{record.Platform, record.Encoding, record.Language, record.Name}] = name
|
||||||
|
}
|
||||||
|
var names []string
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, record := range records {
|
||||||
|
key := [4]uint16{record.Platform, record.Encoding, record.Language, record.Name}
|
||||||
|
name := values[key]
|
||||||
|
switch record.Name {
|
||||||
|
case 1, 16:
|
||||||
|
key[3]++
|
||||||
|
if style := values[key]; style != "" && name != "" {
|
||||||
|
name += " " + style
|
||||||
|
}
|
||||||
|
case 4, 6:
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
names = appendFontName(names, seen, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
@@ -213,6 +213,7 @@ func (r *Renderer) parseStrokePaint(strokeColor *StrokeColor, x, y, pageH float6
|
|||||||
return r.parseFillPaint((*FillColor)(strokeColor), x, y, pageH)
|
return r.parseFillPaint((*FillColor)(strokeColor), x, y, pageH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// patternPaint 底纹画刷
|
||||||
type patternPaint struct {
|
type patternPaint struct {
|
||||||
*Pattern
|
*Pattern
|
||||||
color FillColor
|
color FillColor
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type Renderer struct {
|
|||||||
fontSourceCache map[string][]fontSource
|
fontSourceCache map[string][]fontSource
|
||||||
fontSourceUsed map[string]fontSource
|
fontSourceUsed map[string]fontSource
|
||||||
fontDirCandidates map[string][]fontFileCandidate
|
fontDirCandidates map[string][]fontFileCandidate
|
||||||
|
fontFSCandidates map[int][]fontFileCandidate
|
||||||
textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue
|
textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue
|
||||||
templatePageCache map[string]*PageContent
|
templatePageCache map[string]*PageContent
|
||||||
imageCache map[string]image.Image
|
imageCache map[string]image.Image
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
const defaultPathLineWidth = 0.353
|
const defaultPathLineWidth = 0.353
|
||||||
const defaultMiterLimit = 3.528
|
const defaultMiterLimit = 3.528
|
||||||
|
|
||||||
|
// pathStyle 路径绘制样式
|
||||||
type pathStyle struct {
|
type pathStyle struct {
|
||||||
fillColor color.Color
|
fillColor color.Color
|
||||||
strokeColor color.Color
|
strokeColor color.Color
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/tdewolff/canvas"
|
"github.com/tdewolff/canvas"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// shdPaint 渐变画刷
|
||||||
type shdPaint struct {
|
type shdPaint struct {
|
||||||
gradient canvas.Gradient
|
gradient canvas.Gradient
|
||||||
view canvas.Matrix
|
view canvas.Matrix
|
||||||
|
|||||||
Reference in new issue
Block a user