From a3435d5d8d625cab8cc50fdfdae9c1622bea9a8d Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Thu, 10 Sep 2026 20:37:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B8=B2=E6=9F=93=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AD=97=E4=BD=93=E5=90=8D=E7=A7=B0=E8=AF=86?= =?UTF-8?q?=E5=88=AB=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=AD=97=E4=BD=93=E8=AF=AF?= =?UTF-8?q?=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo.js | 12 +++- ofdgo.go | 1 + ofdgo_font_cff.go | 1 + ofdgo_font_fs.go | 60 +++++++++++----- ofdgo_font_info.go | 9 --- ofdgo_font_loader.go | 112 ++++++++++++++++++++++------- ofdgo_font_match.go | 38 +++++++++- ofdgo_font_name.go | 160 +++++++++++++++++++++++++++++++++++++++++ ofdgo_paint.go | 1 + ofdgo_renderer.go | 1 + ofdgo_renderer_path.go | 1 + ofdgo_shading.go | 1 + 12 files changed, 339 insertions(+), 58 deletions(-) create mode 100644 ofdgo_font_name.go diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 5f56ae4..b17c3bf 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -861,9 +861,17 @@ function uniqueLocalFonts(fonts) { } 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); - 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) { diff --git a/ofdgo.go b/ofdgo.go index cad6546..74d2712 100644 --- a/ofdgo.go +++ b/ofdgo.go @@ -78,6 +78,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer { fontSourceCache: make(map[string][]fontSource), fontSourceUsed: make(map[string]fontSource), fontDirCandidates: make(map[string][]fontFileCandidate), + fontFSCandidates: make(map[int][]fontFileCandidate), textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue), templatePageCache: make(map[string]*PageContent), imageCache: make(map[string]image.Image), diff --git a/ofdgo_font_cff.go b/ofdgo_font_cff.go index b11dc70..ee0ddce 100644 --- a/ofdgo_font_cff.go +++ b/ofdgo_font_cff.go @@ -787,6 +787,7 @@ func readCFFLocalSubrs(data []byte, fdArrOff int, fdCount int) ([][][]byte, []cf return localSubrs, privateDicts } +// type2Operand Type2操作数 type type2Operand struct { value int outStart int diff --git a/ofdgo_font_fs.go b/ofdgo_font_fs.go index 8cf7c69..f5a6803 100644 --- a/ofdgo_font_fs.go +++ b/ofdgo_font_fs.go @@ -56,6 +56,9 @@ func NewFontFS(fonts []FontFile) *FontFS { } sort.Strings(fsys.names) 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 } @@ -126,8 +129,10 @@ func (fsys *FontFS) Match(names ...string) (string, bool) { // 入参: bold 是否粗体, italic 是否斜体, names 字体名称列表 // 返回: string 匹配字体文件, bool 是否为名称匹配 func (fsys *FontFS) MatchStyle(bold, italic bool, names ...string) (string, bool) { - if matches := fsys.matchPatternsStyle(fontFilePatterns(names...), bold, italic); len(matches) > 0 { - return matches[0], true + if len(names) > 0 { + if matches := fsys.matchStyle(names, bold, italic); len(matches) > 0 { + return matches[0].name, true + } } if matches := fsys.fallbackFonts(bold, italic); len(matches) > 0 { return matches[0], false @@ -139,41 +144,57 @@ func (fsys *FontFS) MatchStyle(bold, italic bool, names ...string) (string, bool // 入参: files 可用字体文件名, bold 是否粗体, italic 是否斜体, names 字体名称,留空匹配默认回退字体 // 返回: []string 按优先级排列的匹配文件名 func FontFileMatches(files []string, bold, italic bool, names ...string) []string { - if len(names) == 0 { - names = fontDefaultSystemNames() - } - return fontFileMatches(fontFileCandidates(files, path.Base), fontFilePatterns(names...), bold, italic) + return fontFileMatchNames(fontFileMatches(fontFileCandidates(files, path.Base), names, bold, italic)) } -// matchPatternsStyle 匹配指定样式的字体文件模式 -// 入参: patterns 匹配模式列表, bold 是否粗体, italic 是否斜体 -// 返回: []string 字体文件列表 -func (fsys *FontFS) matchPatternsStyle(patterns []string, bold, italic bool) []string { - return fontFileMatches(fsys.candidates, patterns, bold, italic) +// matchStyle 匹配指定样式的字体名称 +// 入参: names 字体名称列表, bold 是否粗体, italic 是否斜体 +// 返回: []fontFileMatch 字体匹配列表 +func (fsys *FontFS) matchStyle(names []string, bold, italic bool) []fontFileMatch { + return fontFileMatches(fsys.candidates, names, bold, italic) } // fontFileMatches 匹配字体文件候选 -// 入参: candidates 字体候选, patterns 匹配模式, bold 是否粗体, italic 是否斜体 -// 返回: []string 按优先级排列的匹配文件名 -func fontFileMatches(candidates []fontFileCandidate, patterns []string, bold, italic bool) []string { +// 入参: candidates 字体候选, names 字体名称, bold 是否粗体, italic 是否斜体 +// 返回: []fontFileMatch 按优先级排列的字体匹配 +func fontFileMatches(candidates []fontFileCandidate, names []string, bold, italic bool) []fontFileMatch { matches := make([]fontFileMatch, 0, 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 { rank := matcher.rankCandidate(file) appendFontFileMatch(&matches, seen, matcher, file, rank, bold, italic) } } sortFontFileMatches(matches) - return fontFileMatchNames(matches) + return matches } // fallbackFonts 获取回退字体文件 // 入参: bold 是否粗体, italic 是否斜体 // 返回: []string 字体文件列表 func (fsys *FontFS) fallbackFonts(bold, italic bool) []string { - if matches := fsys.matchPatternsStyle(fontFilePatterns(fontDefaultSystemNames()...), bold, italic); len(matches) > 0 { - return matches + if matches := fsys.matchStyle(nil, bold, italic); len(matches) > 0 { + return fontFileMatchNames(matches) } if len(fsys.names) > 0 { return []string{fsys.names[0]} @@ -210,6 +231,7 @@ func cleanFontName(name string) string { return strings.ToLower(name) } +// fontFileCandidate 字体文件候选 type fontFileCandidate struct { name string base string @@ -238,6 +260,7 @@ func fontFileCandidates(names []string, base func(string) string) []fontFileCand return files } +// fontPatternMatcher 字体文件匹配器 type fontPatternMatcher struct { pattern string lowerPattern string @@ -351,6 +374,7 @@ func (m fontPatternMatcher) styleSuffixNormalized(name string) string { return "" } +// fontFileMatch 字体文件匹配结果 type fontFileMatch struct { name string priority int diff --git a/ofdgo_font_info.go b/ofdgo_font_info.go index 15af323..145e7d6 100644 --- a/ofdgo_font_info.go +++ b/ofdgo_font_info.go @@ -16,7 +16,6 @@ package ofdgo import ( "fmt" - "io/fs" "path" "sort" ) @@ -184,14 +183,6 @@ func (r *Renderer) fontInfo(font Font) FontInfo { 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 统计文档字体使用次数 // 入参: doc 文档结构, pages 页面内容列表, nil表示读取文档页面 // 返回: map[string]int 字体使用次数 diff --git a/ofdgo_font_loader.go b/ofdgo_font_loader.go index 6b347f3..82057c8 100644 --- a/ofdgo_font_loader.go +++ b/ofdgo_font_loader.go @@ -15,9 +15,14 @@ package ofdgo import ( + "bytes" + "io" "io/fs" + "os" + "path" "path/filepath" "runtime" + "sort" "github.com/tdewolff/canvas" ) @@ -33,10 +38,11 @@ const ( // fontSource 字体来源 type fontSource struct { - kind fontSourceKind - index int - name string - exact bool + kind fontSourceKind + index int + name string + exact bool + priority int } // fontSourceKey 字体来源去重键 @@ -139,36 +145,42 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle } bold := style&canvas.FontBold != 0 italic := style&canvas.FontItalic != 0 - patterns := fontFilePatterns(font.FontName, font.FamilyName) + names := []string{font.FontName, font.FamilyName} sources := make([]fontSource, 0) seen := make(map[fontSourceKey]bool) for _, dir := range r.fontDirs { - for _, name := range r.matchFontFiles(dir, patterns, bold, italic) { - sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: name, exact: true}) + for _, match := range r.matchFontFiles(dir, names, bold, italic) { + sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, exact: true, priority: min(match.priority, 0)}) } } - for index, fsys := range r.fontFS { - for _, name := range fontFSMatchesStyle(fsys, patterns, bold, italic) { - sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name, exact: true}) + for index := range r.fontFS { + for _, match := range r.matchFontFS(index, names, bold, italic) { + 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() { - systemDirs := systemFontDirs() - for _, name := range []string{font.FamilyName, font.FontName} { - for _, systemName := range fontSystemNames(name) { - 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 _, dir := range systemFontDirs() { + for _, match := range r.matchFontFiles(dir, names, bold, italic) { + sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match.name, exact: true, priority: min(match.priority, 0)}) } } + 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) { - sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name}) + sortFontSources(sources[systemStart:]) + 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 { @@ -181,6 +193,14 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle return sources } +// sortFontSources 将完整名称匹配排在回退候选之前 +// 入参: sources 字体来源列表 +func sortFontSources(sources []fontSource) { + sort.SliceStable(sources, func(i, j int) bool { + return sources[i].priority < sources[j].priority + }) +} + // appendFontSource 追加字体来源 // 入参: sources 字体来源列表, seen 去重映射, source 字体来源 // 返回: []fontSource 字体来源列表 @@ -250,16 +270,54 @@ func (r *Renderer) loadFontSource(family *canvas.FontFamily, source fontSource, } // matchFontFiles 查找字体文件 -// 入参: dir 目录, patterns 模式列表, bold 是否粗体, italic 是否斜体 -// 返回: []string 文件列表 -func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bool) []string { +// 入参: dir 字体目录, names 字体名称, bold 是否粗体, italic 是否斜体 +// 返回: []fontFileMatch 字体匹配列表 +func (r *Renderer) matchFontFiles(dir string, names []string, bold, italic bool) []fontFileMatch { candidates, ok := r.fontDirCandidates[dir] if !ok { files, _ := filepath.Glob(filepath.Join(dir, "*")) 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 } - 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 获取系统字体目录 diff --git a/ofdgo_font_match.go b/ofdgo_font_match.go index a9990b2..284f568 100644 --- a/ofdgo_font_match.go +++ b/ofdgo_font_match.go @@ -19,6 +19,7 @@ import ( "strings" ) +// fontMatchRule 字体匹配规则 type fontMatchRule struct { Keys []string Names []string @@ -145,7 +146,10 @@ func FontSystemNames(names ...string) []string { // 返回: string 规范化后的字体名称 func fontNormalizeName(name string) string { 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)) return fontNameReplacer.Replace(name) } @@ -164,6 +168,34 @@ func fontExactCandidateNames(names ...string) []string { 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 获取指定等级内的字体候选名称 // 入参: maxLevel 最大匹配等级, names 字体名称列表 // 返回: []string 字体候选名称 @@ -199,7 +231,9 @@ func fontCandidateNamesByLevel(maxLevel int, names ...string) []string { // 返回: string 字体匹配模式主干 func fontPatternStem(pattern string) string { 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 { stem = stem[:index] } diff --git a/ofdgo_font_name.go b/ofdgo_font_name.go new file mode 100644 index 0000000..b8c95c7 --- /dev/null +++ b/ofdgo_font_name.go @@ -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 +} diff --git a/ofdgo_paint.go b/ofdgo_paint.go index ced31db..c8113ef 100644 --- a/ofdgo_paint.go +++ b/ofdgo_paint.go @@ -213,6 +213,7 @@ func (r *Renderer) parseStrokePaint(strokeColor *StrokeColor, x, y, pageH float6 return r.parseFillPaint((*FillColor)(strokeColor), x, y, pageH) } +// patternPaint 底纹画刷 type patternPaint struct { *Pattern color FillColor diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index c7b012a..7d5c1c5 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -38,6 +38,7 @@ type Renderer struct { fontSourceCache map[string][]fontSource fontSourceUsed map[string]fontSource fontDirCandidates map[string][]fontFileCandidate + fontFSCandidates map[int][]fontFileCandidate textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue templatePageCache map[string]*PageContent imageCache map[string]image.Image diff --git a/ofdgo_renderer_path.go b/ofdgo_renderer_path.go index fa98dfd..2e16370 100644 --- a/ofdgo_renderer_path.go +++ b/ofdgo_renderer_path.go @@ -26,6 +26,7 @@ import ( const defaultPathLineWidth = 0.353 const defaultMiterLimit = 3.528 +// pathStyle 路径绘制样式 type pathStyle struct { fillColor color.Color strokeColor color.Color diff --git a/ofdgo_shading.go b/ofdgo_shading.go index 40173ad..9f59785 100644 --- a/ofdgo_shading.go +++ b/ofdgo_shading.go @@ -22,6 +22,7 @@ import ( "github.com/tdewolff/canvas" ) +// shdPaint 渐变画刷 type shdPaint struct { gradient canvas.Gradient view canvas.Matrix