feat(渲染能力): 优化字体名称识别,修复字体误回退
build.yaml / build (push) Successful in 39s

This commit is contained in:
xiaoqidun committed 2026-09-10 20:37:28 +08:00
1 parent f361c61dcc
commit a3435d5d8d
12 files changed
+339 -58

No files matched your search

+10 -2
View File
@@ -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) {
+1
View File
@@ -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),
+1
View File
@@ -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
View File
@@ -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
-9
View File
@@ -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 字体使用次数
+85 -27
View File
@@ -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"
) )
@@ -33,10 +38,11 @@ const (
// fontSource 字体来源 // fontSource 字体来源
type fontSource struct { type fontSource struct {
kind fontSourceKind kind fontSourceKind
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 { sortFontSources(sources[systemStart:])
for _, name := range fontFSMatchesStyle(fsys, fontFilePatterns(fontDefaultSystemNames()...), bold, italic) { for index := range r.fontFS {
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name}) 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 获取系统字体目录
+36 -2
View File
@@ -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, "*")
stem = strings.TrimSuffix(stem, path.Ext(stem)) if isFontFileName(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]
} }
+160
View File
@@ -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
}
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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