mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
feat(字体匹配): 改进字体匹配逻辑
This commit is contained in:
+45
-25
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user