feat(字体匹配): 改进字体匹配逻辑

This commit is contained in:
2026-07-02 23:12:49 +08:00
parent 69e5f3252c
commit 3de4d1c85a
4 changed files with 329 additions and 96 deletions
+45 -25
View File
@@ -112,8 +112,8 @@ func (fsys *FontFS) Glob(pattern string) ([]string, error) {
// 入参: names 字体名称列表 // 入参: names 字体名称列表
// 返回: string 匹配字体文件, bool 是否为名称匹配 // 返回: string 匹配字体文件, bool 是否为名称匹配
func (fsys *FontFS) Match(names ...string) (string, bool) { func (fsys *FontFS) Match(names ...string) (string, bool) {
for _, name := range fontCandidateNames(names...) { for _, pattern := range fontFilePatterns(names...) {
if matches := fsys.match(name + "*"); len(matches) > 0 { if matches := fsys.match(pattern); len(matches) > 0 {
return matches[0], true return matches[0], true
} }
} }
@@ -127,12 +127,16 @@ func (fsys *FontFS) Match(names ...string) (string, bool) {
// 入参: pattern 匹配模式 // 入参: pattern 匹配模式
// 返回: []string 字体文件列表 // 返回: []string 字体文件列表
func (fsys *FontFS) match(pattern string) []string { func (fsys *FontFS) match(pattern string) []string {
var matches []string buckets := make([][]string, fontMatchFuzzy+1)
for _, name := range fsys.names { for _, name := range fsys.names {
if matchFontPattern(pattern, name) { if rank := matchFontPatternRank(pattern, name); rank != fontMatchNone {
matches = append(matches, name) buckets[rank] = append(buckets[rank], name)
} }
} }
var matches []string
for rank := fontMatchExact; rank <= fontMatchFuzzy; rank++ {
matches = append(matches, buckets[rank]...)
}
return matches return matches
} }
@@ -140,8 +144,8 @@ func (fsys *FontFS) match(pattern string) []string {
// 返回: []string 字体文件列表 // 返回: []string 字体文件列表
func (fsys *FontFS) fallbackFonts() []string { func (fsys *FontFS) fallbackFonts() []string {
for _, item := range fontFallbackFiles { for _, item := range fontFallbackFiles {
if _, ok := fsys.files[item]; ok { if matches := fsys.match(item); len(matches) > 0 {
return []string{item} return []string{matches[0]}
} }
} }
if len(fsys.names) > 0 { if len(fsys.names) > 0 {
@@ -179,33 +183,49 @@ func cleanFontName(name string) string {
return strings.ToLower(name) return strings.ToLower(name)
} }
// matchFontPattern 匹配字体文件模式 // matchFontPatternRank 获取字体文件匹配等级
// 入参: pattern 匹配模式, name 字体文件名 // 入参: pattern 匹配模式, name 字体文件名
// 返回: bool 是否匹配 // 返回: int 匹配等级
func matchFontPattern(pattern, name string) bool { func matchFontPatternRank(pattern, name string) int {
if ok, _ := path.Match(pattern, name); ok { exactPath, _ := path.Match(pattern, name)
return true lowerPath, _ := path.Match(strings.ToLower(pattern), strings.ToLower(name))
} stem := fontPatternStem(pattern)
if ok, _ := path.Match(strings.ToLower(pattern), strings.ToLower(name)); ok {
return true
}
stem := strings.TrimSuffix(pattern, "*")
stem = strings.TrimSuffix(stem, path.Ext(stem))
stem = fontNormalizeName(stem)
if stem == "" { if stem == "" {
return false if exactPath || lowerPath {
return fontMatchFuzzy
}
return fontMatchNone
} }
name = fontNormalizeName(name) name = fontNormalizeName(name)
if strings.HasPrefix(name, stem) { if name == stem {
return true return fontMatchExact
} }
for _, alias := range fontCandidateNames(stem) { aliases := fontExactCandidateNames(stem)
for _, alias := range aliases {
alias = fontNormalizeName(alias)
if alias != "" && name == alias {
return fontMatchExact
}
}
if strings.HasPrefix(name, stem) {
return fontMatchPartial
}
for _, alias := range aliases {
alias = fontNormalizeName(alias) alias = fontNormalizeName(alias)
if alias != "" && strings.HasPrefix(name, alias) { if alias != "" && strings.HasPrefix(name, alias) {
return true return fontMatchPartial
} }
} }
return false if exactPath || lowerPath || strings.Contains(name, stem) {
return fontMatchFuzzy
}
for _, alias := range aliases {
alias = fontNormalizeName(alias)
if alias != "" && strings.Contains(name, alias) {
return fontMatchFuzzy
}
}
return fontMatchNone
} }
// fontMemFile 内存字体文件 // fontMemFile 内存字体文件
+50 -4
View File
@@ -157,6 +157,13 @@ func (r *Renderer) fontInfo(font Font) FontInfo {
// 入参: names 字体名称列表 // 入参: names 字体名称列表
// 返回: string 匹配字体文件, bool 是否为名称匹配 // 返回: string 匹配字体文件, bool 是否为名称匹配
func (r *Renderer) matchFont(names ...string) (string, bool) { func (r *Renderer) matchFont(names ...string) (string, bool) {
for _, dir := range r.fontDirs {
for _, pattern := range fontFilePatterns(names...) {
if matches := r.globFontFiles(dir, pattern); len(matches) > 0 {
return matches[0], true
}
}
}
for _, fsys := range r.fontFS { for _, fsys := range r.fontFS {
if matcher, ok := fsys.(interface { if matcher, ok := fsys.(interface {
Match(...string) (string, bool) Match(...string) (string, bool)
@@ -170,11 +177,13 @@ func (r *Renderer) matchFont(names ...string) (string, bool) {
return matched, true return matched, true
} }
} }
if !canLoadSystemFonts() {
for _, fsys := range r.fontFS { for _, fsys := range r.fontFS {
if matched := fallbackFontFS(fsys); matched != "" { if matched := fallbackFontFS(fsys); matched != "" {
return matched, false return matched, false
} }
} }
}
return "", false return "", false
} }
@@ -183,8 +192,7 @@ func (r *Renderer) matchFont(names ...string) (string, bool) {
// 返回: string 匹配字体文件 // 返回: string 匹配字体文件
func matchFontFS(fsys fs.FS, names ...string) string { func matchFontFS(fsys fs.FS, names ...string) string {
for _, pattern := range fontFilePatterns(names...) { for _, pattern := range fontFilePatterns(names...) {
matches, err := fs.Glob(fsys, pattern) if matches := fontFSMatches(fsys, pattern); len(matches) > 0 {
if err == nil && len(matches) > 0 {
return matches[0] return matches[0]
} }
} }
@@ -195,13 +203,51 @@ func matchFontFS(fsys fs.FS, names ...string) string {
// 入参: fsys 字体文件系统 // 入参: fsys 字体文件系统
// 返回: string 回退字体文件 // 返回: string 回退字体文件
func fallbackFontFS(fsys fs.FS) string { func fallbackFontFS(fsys fs.FS) string {
matches, err := fs.Glob(fsys, "*") for _, pattern := range fontFallbackFiles {
if err == nil && len(matches) > 0 { if matches := fontFSMatches(fsys, pattern); len(matches) > 0 {
return matches[0] return matches[0]
} }
}
all, _ := fs.Glob(fsys, "*")
for _, name := range all {
if isFontFileName(name) {
return name
}
}
return "" return ""
} }
// fontFSMatches 匹配字体文件系统中的字体文件
// 入参: fsys 字体文件系统, pattern 匹配模式
// 返回: []string 字体文件列表
func fontFSMatches(fsys fs.FS, pattern string) []string {
seen := make(map[string]bool)
buckets := make([][]string, fontMatchFuzzy+1)
add := func(name string, rank int) {
if !isFontFileName(name) {
return
}
if rank == fontMatchNone || seen[name] {
return
}
seen[name] = true
buckets[rank] = append(buckets[rank], name)
}
matches, _ := fs.Glob(fsys, pattern)
for _, name := range matches {
add(name, matchFontPatternRank(pattern, path.Base(name)))
}
all, _ := fs.Glob(fsys, "*")
for _, name := range all {
add(name, matchFontPatternRank(pattern, path.Base(name)))
}
var result []string
for rank := fontMatchExact; rank <= fontMatchFuzzy; rank++ {
result = append(result, buckets[rank]...)
}
return result
}
// fontUsage 统计文档字体使用次数 // fontUsage 统计文档字体使用次数
// 入参: doc 文档结构 // 入参: doc 文档结构
// 返回: map[string]int 字体使用次数 // 返回: map[string]int 字体使用次数
+173 -27
View File
@@ -27,36 +27,43 @@ type fontMatchRule struct {
NoSyntheticBold bool NoSyntheticBold bool
} }
const (
fontMatchNone = iota
fontMatchExact
fontMatchPartial
fontMatchFuzzy
)
var fontMatchRules = []fontMatchRule{ var fontMatchRules = []fontMatchRule{
{ {
Keys: []string{"小标宋", "方正小标宋", "xiaobiaosong", "fzxiaobiaosong", "fzxbs"}, Keys: []string{"小标宋", "方正小标宋", "xiaobiaosong", "fzxiaobiaosong", "fzxbs"},
Names: []string{"小标宋体", "方正小标宋简体", "FZXiaoBiaoSong-B05", "FZXiaoBiaoSong-B05S"}, Names: []string{"小标宋体", "方正小标宋简体", "FZXiaoBiaoSong-B05", "FZXiaoBiaoSong-B05S"},
Files: []string{"FZXBSJW.TTF", "fzxbs*.ttf", "xiaobiaosong*.ttf"}, Files: []string{"FZXBSJW.TTF", "fzxbs*.ttf", "fzxiaobiaosong*.ttf", "xiaobiaosong*.ttf"},
System: "FZXiaoBiaoSong-B05S", System: "FZXiaoBiaoSong-B05S",
NoSyntheticBold: true, NoSyntheticBold: true,
}, },
{ {
Keys: []string{"宋体", "新宋体", "simsun", "nsimsun", "songti", "书宋", "方正书宋"}, Keys: []string{"宋体", "新宋体", "simsun", "nsimsun", "simsunextb", "song", "songti", "stsong", "书宋", "方正书宋"},
Names: []string{"宋体", "新宋体", "SimSun", "NSimSun", "SongTi"}, Names: []string{"宋体", "新宋体", "SimSun", "NSimSun", "SongTi", "STSong"},
Files: []string{"simsun.ttc", "simsun.ttf", "nsimsun.ttf"}, Files: []string{"simsun.ttc", "simsun.ttf", "nsimsun.ttf", "STSONG.TTF"},
System: "SimSun", System: "SimSun",
}, },
{ {
Keys: []string{"黑体", "simhei", "heiti"}, Keys: []string{"黑体", "hei", "simhei", "heiti", "sthei", "方正黑体"},
Names: []string{"黑体", "SimHei", "HeiTi"}, Names: []string{"黑体", "SimHei", "HeiTi", "STHeiti", "方正黑体简体"},
Files: []string{"simhei.ttf"}, Files: []string{"simhei.ttf", "STXIHEI.TTF", "FZHT_GB18030.TTF", "fzht*.ttf"},
System: "SimHei", System: "SimHei",
}, },
{ {
Keys: []string{"楷体", "kaiti", "simkai"}, Keys: []string{"楷体", "kai", "kaiti", "simkai", "stkaiti", "方正楷体"},
Names: []string{"楷体", "KaiTi", "SimKai"}, Names: []string{"楷体", "KaiTi", "SimKai", "STKaiti", "方正楷体简体"},
Files: []string{"simkai.ttf", "kaiti.ttf"}, Files: []string{"simkai.ttf", "kaiti.ttf", "STKAITI.TTF", "FZKTJ.TTF", "fzkai*.ttf", "fzkt*.ttf"},
System: "KaiTi", System: "KaiTi",
}, },
{ {
Keys: []string{"仿宋", "fangsong", "simfang"}, Keys: []string{"仿宋", "fang", "fangsong", "simfang", "stfangsong", "方正仿宋"},
Names: []string{"仿宋", "FangSong", "SimFang"}, Names: []string{"仿宋", "FangSong", "SimFang", "STFangsong", "方正仿宋简体"},
Files: []string{"simfang.ttf", "fangsong.ttf"}, Files: []string{"simfang.ttf", "fangsong.ttf", "STFANGSO.TTF", "FZFSJ.TTF", "fzfs*.ttf"},
System: "FangSong", System: "FangSong",
}, },
{ {
@@ -103,7 +110,40 @@ var fontMatchRules = []fontMatchRule{
}, },
} }
var fontFallbackFiles = []string{"simsun.ttc", "msyh.ttc", "simhei.ttf"} var fontFallbackFiles = []string{
"simsun.ttc", "simsun.ttf", "nsimsun.ttf",
"msyh.ttc", "msyh.ttf", "simhei.ttf",
"NotoSansCJK*.ttc", "NotoSansSC*.otf", "SourceHanSansSC*.otf",
"arial.ttf",
}
// FontNormalizeName 规范化字体名称
// 入参: name 字体名称
// 返回: string 规范化后的字体名称
func FontNormalizeName(name string) string {
return fontNormalizeName(name)
}
// FontCandidateNames 获取字体候选名称
// 入参: names 字体名称列表
// 返回: []string 字体候选名称列表
func FontCandidateNames(names ...string) []string {
return fontCandidateNames(names...)
}
// FontFilePatterns 获取字体文件匹配模式
// 入参: names 字体名称列表
// 返回: []string 字体文件匹配模式
func FontFilePatterns(names ...string) []string {
return fontFilePatterns(names...)
}
// FontSystemNames 获取系统字体名称
// 入参: names 字体名称列表
// 返回: []string 系统字体名称
func FontSystemNames(names ...string) []string {
return fontSystemNames(names...)
}
// fontNormalizeName 规范化字体名称 // fontNormalizeName 规范化字体名称
// 入参: name 字体名称 // 入参: name 字体名称
@@ -119,16 +159,35 @@ func fontNormalizeName(name string) string {
// 入参: names 字体名称列表 // 入参: names 字体名称列表
// 返回: []string 字体候选名称 // 返回: []string 字体候选名称
func fontCandidateNames(names ...string) []string { func fontCandidateNames(names ...string) []string {
return fontCandidateNamesByLevel(fontMatchFuzzy, names...)
}
// fontExactCandidateNames 获取精确字体候选名称
// 入参: names 字体名称列表
// 返回: []string 精确字体候选名称
func fontExactCandidateNames(names ...string) []string {
return fontCandidateNamesByLevel(fontMatchExact, names...)
}
// fontCandidateNamesByLevel 获取指定等级内的字体候选名称
// 入参: maxLevel 最大匹配等级, names 字体名称列表
// 返回: []string 字体候选名称
func fontCandidateNamesByLevel(maxLevel int, names ...string) []string {
var result []string var result []string
seen := make(map[string]bool) seen := make(map[string]bool)
var bases []string
for _, name := range names { for _, name := range names {
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
if name == "" { if name == "" {
continue continue
} }
result = appendFontName(result, seen, name) result = appendFontName(result, seen, name)
bases = append(bases, name)
}
for level := fontMatchExact; level <= maxLevel; level++ {
for _, name := range bases {
for _, rule := range fontMatchRules { for _, rule := range fontMatchRules {
if fontRuleMatch(rule, name) { if fontRuleMatchLevel(rule, name) == level {
result = appendFontName(result, seen, rule.System) result = appendFontName(result, seen, rule.System)
for _, item := range rule.Names { for _, item := range rule.Names {
result = appendFontName(result, seen, item) result = appendFontName(result, seen, item)
@@ -136,27 +195,42 @@ func fontCandidateNames(names ...string) []string {
} }
} }
} }
}
return result return result
} }
// fontPatternStem 获取字体匹配模式主干
// 入参: pattern 字体文件匹配模式
// 返回: string 字体匹配模式主干
func fontPatternStem(pattern string) string {
stem := strings.TrimSuffix(pattern, "*")
stem = strings.TrimSuffix(stem, path.Ext(stem))
if index := strings.IndexAny(stem, "*?["); index >= 0 {
stem = stem[:index]
}
return fontNormalizeName(stem)
}
// fontFilePatterns 获取字体文件匹配模式 // fontFilePatterns 获取字体文件匹配模式
// 入参: names 字体名称列表 // 入参: names 字体名称列表
// 返回: []string 字体文件匹配模式 // 返回: []string 字体文件匹配模式
func fontFilePatterns(names ...string) []string { func fontFilePatterns(names ...string) []string {
var result []string var result []string
seen := make(map[string]bool) seen := make(map[string]bool)
for _, name := range fontCandidateNames(names...) { for level := fontMatchExact; level <= fontMatchFuzzy; level++ {
for _, name := range fontCandidateNamesAtLevel(level, names...) {
result = appendFontPattern(result, seen, name+"*") result = appendFontPattern(result, seen, name+"*")
} }
for _, name := range names { for _, name := range names {
for _, rule := range fontMatchRules { for _, rule := range fontMatchRules {
if fontRuleMatch(rule, name) { if fontRuleMatchLevel(rule, name) == level {
for _, item := range rule.Files { for _, item := range rule.Files {
result = appendFontPattern(result, seen, item) result = appendFontPattern(result, seen, item)
} }
} }
} }
} }
}
return result return result
} }
@@ -167,19 +241,45 @@ func fontSystemNames(names ...string) []string {
var result []string var result []string
seen := make(map[string]bool) seen := make(map[string]bool)
for _, name := range names { for _, name := range names {
for _, item := range fontCandidateNames(name) { for _, item := range fontCandidateNamesByLevel(fontMatchPartial, name) {
result = appendFontName(result, seen, item) result = appendFontName(result, seen, item)
} }
} }
return result return result
} }
// fontCandidateNamesAtLevel 获取指定等级的字体候选名称
// 入参: level 匹配等级, names 字体名称列表
// 返回: []string 字体候选名称
func fontCandidateNamesAtLevel(level int, names ...string) []string {
var result []string
seen := make(map[string]bool)
for _, name := range names {
name = strings.TrimSpace(name)
if name == "" {
continue
}
if level == fontMatchExact {
result = appendFontName(result, seen, name)
}
for _, rule := range fontMatchRules {
if fontRuleMatchLevel(rule, name) == level {
result = appendFontName(result, seen, rule.System)
for _, item := range rule.Names {
result = appendFontName(result, seen, item)
}
}
}
}
return result
}
// fontDefaultSystemNames 获取默认系统字体名称 // fontDefaultSystemNames 获取默认系统字体名称
// 返回: []string 默认系统字体名称 // 返回: []string 默认系统字体名称
func fontDefaultSystemNames() []string { func fontDefaultSystemNames() []string {
return []string{ return []string{
"SimHei", "Microsoft YaHei", "SimSun", "KaiTi", "FangSong", "SimSun", "Microsoft YaHei", "SimHei", "KaiTi", "FangSong",
"Arial", "Segoe UI", "Times New Roman", "Noto Sans CJK SC", "Source Han Sans SC", "Arial", "Segoe UI", "Times New Roman",
} }
} }
@@ -189,7 +289,8 @@ func fontDefaultSystemNames() []string {
func fontNoSyntheticBold(names ...string) bool { func fontNoSyntheticBold(names ...string) bool {
for _, name := range names { for _, name := range names {
for _, rule := range fontMatchRules { for _, rule := range fontMatchRules {
if rule.NoSyntheticBold && fontRuleMatch(rule, name) { level := fontRuleMatchLevel(rule, name)
if rule.NoSyntheticBold && level >= fontMatchExact && level <= fontMatchPartial {
return true return true
} }
} }
@@ -197,21 +298,66 @@ func fontNoSyntheticBold(names ...string) bool {
return false return false
} }
// isFontFileName 判断是否为字体文件名
// 入参: name 文件名
// 返回: bool 是否为字体文件
func isFontFileName(name string) bool {
switch strings.ToLower(path.Ext(name)) {
case ".ttf", ".otf", ".ttc":
return true
default:
return false
}
}
// fontRuleMatch 判断字体规则是否匹配 // fontRuleMatch 判断字体规则是否匹配
// 入参: rule 字体匹配规则, name 字体名称 // 入参: rule 字体匹配规则, name 字体名称
// 返回: bool 是否匹配 // 返回: bool 是否匹配
func fontRuleMatch(rule fontMatchRule, name string) bool { func fontRuleMatch(rule fontMatchRule, name string) bool {
return fontRuleMatchLevel(rule, name) != fontMatchNone
}
// fontRuleMatchLevel 获取字体规则匹配等级
// 入参: rule 字体匹配规则, name 字体名称
// 返回: int 匹配等级
func fontRuleMatchLevel(rule fontMatchRule, name string) int {
name = fontNormalizeName(name) name = fontNormalizeName(name)
if name == "" { if name == "" {
return false return fontMatchNone
} }
for _, key := range rule.Keys { items := fontRuleNames(rule)
key = fontNormalizeName(key) for _, item := range items {
if key != "" && (name == key || strings.Contains(name, key)) { item = fontNormalizeName(item)
return true if item != "" && name == item {
return fontMatchExact
} }
} }
return false for _, item := range items {
item = fontNormalizeName(item)
if item != "" && (strings.HasPrefix(name, item) || strings.HasPrefix(item, name)) {
return fontMatchPartial
}
}
for _, item := range items {
item = fontNormalizeName(item)
if item != "" && (strings.Contains(name, item) || strings.Contains(item, name)) {
return fontMatchFuzzy
}
}
return fontMatchNone
}
// fontRuleNames 获取字体规则名称列表
// 入参: rule 字体匹配规则
// 返回: []string 字体规则名称列表
func fontRuleNames(rule fontMatchRule) []string {
var result []string
result = append(result, rule.Keys...)
result = append(result, rule.Names...)
if rule.System != "" {
result = append(result, rule.System)
}
return result
} }
// appendFontName 追加字体名称 // appendFontName 追加字体名称
+41 -20
View File
@@ -470,6 +470,23 @@ func (r *Renderer) initCommon() {
r.defaultFontLoaded = r.loadDefaultFonts() r.defaultFontLoaded = r.loadDefaultFonts()
} }
// childRenderer 创建继承当前配置的子渲染器
// 入参: reader 子阅读器
// 返回: *Renderer 子渲染器
func (r *Renderer) childRenderer(reader *Reader) *Renderer {
opts := []RendererOption{
WithDPI(r.DPI),
WithAnnotations(r.RenderAnnotations),
}
if len(r.fontDirs) > 0 {
opts = append(opts, WithFontDirs(r.fontDirs...))
}
if len(r.fontFS) > 0 {
opts = append(opts, WithFontFS(r.fontFS...))
}
return NewRenderer(reader, opts...)
}
// renderImage 渲染图片 // renderImage 渲染图片
// 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM // 入参: ctx 画布上下文, obj 图片对象, pageH 页面高度, parentCTM 父级CTM, boundaryInCTM 边界是否参与父级CTM
func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool) { func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float64, parentCTM *Matrix, boundaryInCTM bool) {
@@ -1194,8 +1211,7 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
} }
for _, fsys := range r.fontFS { for _, fsys := range r.fontFS {
for _, pattern := range fontFilePatterns(of.FontName, of.FamilyName) { for _, pattern := range fontFilePatterns(of.FontName, of.FamilyName) {
if matches, err := fs.Glob(fsys, pattern); err == nil { for _, m := range fontFSMatches(fsys, pattern) {
for _, m := range matches {
resData, err := fs.ReadFile(fsys, m) resData, err := fs.ReadFile(fsys, m)
if err == nil { if err == nil {
if err := ff.LoadFont(resData, 0, fontStyle); err == nil { if err := ff.LoadFont(resData, 0, fontStyle); err == nil {
@@ -1206,7 +1222,6 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
} }
} }
} }
}
if !canLoadSystemFonts() { if !canLoadSystemFonts() {
for _, fsys := range r.fontFS { for _, fsys := range r.fontFS {
if matches, err := fs.Glob(fsys, "*"); err == nil { if matches, err := fs.Glob(fsys, "*"); err == nil {
@@ -1262,13 +1277,29 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
// 入参: dir 目录, pattern 模式 // 入参: dir 目录, pattern 模式
// 返回: []string 文件列表 // 返回: []string 文件列表
func (r *Renderer) globFontFiles(dir, pattern string) []string { func (r *Renderer) globFontFiles(dir, pattern string) []string {
matches, _ := filepath.Glob(filepath.Join(dir, pattern)) seen := make(map[string]bool)
var result []string buckets := make([][]string, fontMatchFuzzy+1)
for _, m := range matches { add := func(name string, rank int) {
ext := strings.ToLower(filepath.Ext(m)) if !isFontFileName(name) {
if ext == ".ttf" || ext == ".otf" || ext == ".ttc" { return
result = append(result, m)
} }
if rank == fontMatchNone || seen[name] {
return
}
seen[name] = true
buckets[rank] = append(buckets[rank], name)
}
matches, _ := filepath.Glob(filepath.Join(dir, pattern))
for _, m := range matches {
add(m, matchFontPatternRank(pattern, filepath.Base(m)))
}
matches, _ = filepath.Glob(filepath.Join(dir, "*"))
for _, m := range matches {
add(m, matchFontPatternRank(pattern, filepath.Base(m)))
}
var result []string
for rank := fontMatchExact; rank <= fontMatchFuzzy; rank++ {
result = append(result, buckets[rank]...)
} }
return result return result
} }
@@ -1357,17 +1388,7 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
defer reader.Close() defer reader.Close()
doc, err := reader.Doc() doc, err := reader.Doc()
if err == nil { if err == nil {
opts := []RendererOption{ renderer := r.childRenderer(reader)
WithDPI(r.DPI),
WithAnnotations(r.RenderAnnotations),
}
if len(r.fontDirs) > 0 {
opts = append(opts, WithFontDirs(r.fontDirs...))
}
if len(r.fontFS) > 0 {
opts = append(opts, WithFontFS(r.fontFS...))
}
renderer := NewRenderer(reader, opts...)
for _, pageRef := range doc.Pages.Page { for _, pageRef := range doc.Pages.Page {
content, err := reader.PageContent(pageRef) content, err := reader.PageContent(pageRef)
if err != nil { if err != nil {