diff --git a/ofdgo.go b/ofdgo.go index dcb34f7..ce90a32 100644 --- a/ofdgo.go +++ b/ofdgo.go @@ -73,7 +73,9 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer { FontMap: make(map[string]*canvas.FontFamily), FontGIDMap: make(map[string]map[uint16]rune), FontCIDMap: make(map[string]map[uint16]rune), - fontFSCache: make(map[fontFSKey]*canvas.FontFamily), + fontCache: make(map[fontCacheKey]*canvas.FontFamily), + fontSourceCache: make(map[string][]fontSource), + fontSourceUsed: make(map[string]fontSource), textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue), templatePageCache: make(map[string]*PageContent), } diff --git a/ofdgo_font_info.go b/ofdgo_font_info.go index 6d404d8..0507762 100644 --- a/ofdgo_font_info.go +++ b/ofdgo_font_info.go @@ -168,9 +168,9 @@ func (r *Renderer) fontInfo(font Font) FontInfo { } return info } - if matched, exact := r.matchFont(font.FontName, font.FamilyName); matched != "" { - info.Matched = matched - if exact { + if source, ok := r.fontSourceMatch(font.ID, &font); ok { + info.Matched = source.name + if source.exact { info.Status = FontStatusMatched info.Detail = "使用外部字体文件" } else { @@ -184,74 +184,6 @@ func (r *Renderer) fontInfo(font Font) FontInfo { return info } -// matchFont 匹配外部字体 -// 入参: names 字体名称列表 -// 返回: string 匹配字体文件, bool 是否为名称匹配 -func (r *Renderer) matchFont(names ...string) (string, bool) { - patterns := fontFilePatterns(names...) - for _, dir := range r.fontDirs { - if matches := r.matchFontFiles(dir, patterns, false, false); len(matches) > 0 { - return matches[0], true - } - } - for _, fsys := range r.fontFS { - if matcher, ok := fsys.(interface { - Match(...string) (string, bool) - }); ok { - if matched, exact := matcher.Match(names...); matched != "" { - return matched, exact - } - continue - } - if matched := matchFontFS(fsys, names...); matched != "" { - return matched, true - } - } - if !canLoadSystemFonts() { - for _, fsys := range r.fontFS { - if matched := fallbackFontFS(fsys); matched != "" { - return matched, false - } - } - } - return "", false -} - -// matchFontFS 从字体文件系统匹配字体 -// 入参: fsys 字体文件系统, names 字体名称列表 -// 返回: string 匹配字体文件 -func matchFontFS(fsys fs.FS, names ...string) string { - if matches := fontFSMatches(fsys, fontFilePatterns(names...)); len(matches) > 0 { - return matches[0] - } - return "" -} - -// fallbackFontFS 从字体文件系统获取回退字体 -// 入参: fsys 字体文件系统 -// 返回: string 回退字体文件 -func fallbackFontFS(fsys fs.FS) string { - for _, pattern := range fontFallbackFiles { - if matches := fontFSMatches(fsys, []string{pattern}); len(matches) > 0 { - return matches[0] - } - } - all, _ := fs.Glob(fsys, "*") - for _, name := range all { - if isFontFileName(name) { - return name - } - } - return "" -} - -// fontFSMatches 匹配字体文件系统中的字体文件 -// 入参: fsys 字体文件系统, patterns 匹配模式列表 -// 返回: []string 字体文件列表 -func fontFSMatches(fsys fs.FS, patterns []string) []string { - return fontFSMatchesStyle(fsys, patterns, false, false) -} - // fontFSMatchesStyle 匹配字体文件系统中的指定样式字体文件 // 入参: fsys 字体文件系统, patterns 匹配模式列表, bold 是否粗体, italic 是否斜体 // 返回: []string 字体文件列表 diff --git a/ofdgo_font_loader.go b/ofdgo_font_loader.go index ce56d65..ecfbd26 100644 --- a/ofdgo_font_loader.go +++ b/ofdgo_font_loader.go @@ -22,10 +22,33 @@ import ( "github.com/tdewolff/canvas" ) -// fontFSKey 字体文件系统缓存键 -type fontFSKey struct { +// fontSourceKind 字体来源类型 +type fontSourceKind uint8 + +const ( + fontSourceFile fontSourceKind = iota + fontSourceFS + fontSourceSystem +) + +// fontSource 字体来源 +type fontSource struct { + kind fontSourceKind index int name string + exact bool +} + +// fontSourceKey 字体来源去重键 +type fontSourceKey struct { + kind fontSourceKind + index int + name string +} + +// fontCacheKey 字体加载缓存键 +type fontCacheKey struct { + fontSourceKey style canvas.FontStyle } @@ -44,6 +67,7 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily { if !ok { return defaultFont } + fontStyle := canvasFontStyle(of) ff := canvas.NewFontFamily(of.FontName) if of.FontFile != "" { if fontData, err := r.Reader.ResData(of.FontFile); err == nil { @@ -73,13 +97,6 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily { r.FontGIDMap[fontID] = inv } } - var fontStyle canvas.FontStyle - if of.Bold { - fontStyle |= canvas.FontBold - } - if of.Italic { - fontStyle |= canvas.FontItalic - } if err := ff.LoadFont(fontData, 0, fontStyle); err == nil { r.FontMap[fontID] = ff return ff @@ -88,87 +105,143 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily { } return nil } - var fontStyle canvas.FontStyle - if of.Bold { - fontStyle |= canvas.FontBold - } - if of.Italic { - fontStyle |= canvas.FontItalic - } - boldStyle := fontStyle&canvas.FontBold != 0 - italicStyle := fontStyle&canvas.FontItalic != 0 - patterns := fontFilePatterns(of.FontName, of.FamilyName) - for _, dir := range r.fontDirs { - for _, m := range r.matchFontFiles(dir, patterns, boldStyle, italicStyle) { - if err := ff.LoadFontFile(m, fontStyle); err == nil { - r.FontMap[fontID] = ff - return ff - } - } - } - for index, fsys := range r.fontFS { - for _, m := range fontFSMatchesStyle(fsys, patterns, boldStyle, italicStyle) { - if loaded := r.loadFontFromFS(fontID, ff, index, fsys, m, fontStyle); loaded != nil { - return loaded - } - } - } - if !canLoadSystemFonts() { - for index, fsys := range r.fontFS { - if matches, err := fs.Glob(fsys, "*"); err == nil { - for _, m := range matches { - if loaded := r.loadFontFromFS(fontID, ff, index, fsys, m, fontStyle); loaded != nil { - return loaded - } - } - } - } - r.FontMap[fontID] = defaultFont - return defaultFont - } - names := []string{of.FamilyName, of.FontName} - for _, name := range names { - if name == "" { - continue - } - for _, targetName := range fontSystemNames(name) { - for _, m := range r.matchFontFiles(systemFontDir(), fontFilePatterns(targetName), boldStyle, italicStyle) { - if err := ff.LoadFontFile(m, fontStyle); err == nil { - r.FontMap[fontID] = ff - return ff - } - } - if err := ff.LoadSystemFont(targetName, fontStyle); err == nil { - r.FontMap[fontID] = ff - return ff - } - } - for _, m := range r.matchFontFiles(systemFontDir(), fontFilePatterns(name), boldStyle, italicStyle) { - if err := ff.LoadFontFile(m, fontStyle); err == nil { - r.FontMap[fontID] = ff - return ff - } + for _, source := range r.fontSources(fontID, of, fontStyle) { + if loaded := r.loadFontSource(ff, source, fontStyle); loaded != nil { + r.FontMap[fontID] = loaded + r.fontSourceUsed[fontID] = source + return loaded } } r.FontMap[fontID] = defaultFont return defaultFont } -// loadFontFromFS 从字体文件系统加载字体 -// 入参: fontID 字体ID, family 字体族, index 文件系统索引, fsys 字体文件系统, name 字体文件名, style 字体样式 +// canvasFontStyle 获取Canvas字体样式 +// 入参: font OFD字体定义 +// 返回: canvas.FontStyle Canvas字体样式 +func canvasFontStyle(font *Font) canvas.FontStyle { + var style canvas.FontStyle + if font.Bold { + style |= canvas.FontBold + } + if font.Italic { + style |= canvas.FontItalic + } + return style +} + +// fontSources 获取字体来源列表 +// 入参: fontID 字体ID, font OFD字体定义, style Canvas字体样式 +// 返回: []fontSource 字体来源列表 +func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle) []fontSource { + if sources, ok := r.fontSourceCache[fontID]; ok { + return sources + } + bold := style&canvas.FontBold != 0 + italic := style&canvas.FontItalic != 0 + patterns := fontFilePatterns(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 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}) + } + } + if !canLoadSystemFonts() { + for index, fsys := range r.fontFS { + names, _ := fs.Glob(fsys, "*") + for _, name := range names { + sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFS, index: index, name: name}) + } + } + r.fontSourceCache[fontID] = sources + return sources + } + for _, name := range []string{font.FamilyName, font.FontName} { + for _, systemName := range fontSystemNames(name) { + for _, match := range r.matchFontFiles(systemFontDir(), fontFilePatterns(systemName), bold, italic) { + sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match, exact: true}) + } + sources = appendFontSource(sources, seen, fontSource{kind: fontSourceSystem, name: systemName, exact: true}) + } + } + r.fontSourceCache[fontID] = sources + return sources +} + +// appendFontSource 追加字体来源 +// 入参: sources 字体来源列表, seen 去重映射, source 字体来源 +// 返回: []fontSource 字体来源列表 +func appendFontSource(sources []fontSource, seen map[fontSourceKey]bool, source fontSource) []fontSource { + if source.name == "" { + return sources + } + key := fontSourceKey{kind: source.kind, index: source.index, name: source.name} + if seen[key] { + return sources + } + seen[key] = true + return append(sources, source) +} + +// fontSourceMatch 获取匹配的字体来源 +// 入参: fontID 字体ID, font OFD字体定义 +// 返回: fontSource 字体来源, bool 是否匹配 +func (r *Renderer) fontSourceMatch(fontID string, font *Font) (fontSource, bool) { + if source, ok := r.fontSourceUsed[fontID]; ok { + return source, true + } + style := canvasFontStyle(font) + for _, source := range r.fontSources(fontID, font, style) { + key := fontCacheKey{fontSourceKey: fontSourceKey{kind: source.kind, index: source.index, name: source.name}, style: style} + if cached, ok := r.fontCache[key]; ok { + if cached != nil { + return source, true + } + continue + } + if source.kind != fontSourceSystem { + return source, true + } + family := canvas.NewFontFamily(font.FontName) + if r.loadFontSource(family, source, style) != nil { + return source, true + } + } + return fontSource{}, false +} + +// loadFontSource 加载字体来源 +// 入参: family 字体族, source 字体来源, style Canvas字体样式 // 返回: *canvas.FontFamily 字体族 -func (r *Renderer) loadFontFromFS(fontID string, family *canvas.FontFamily, index int, fsys fs.FS, name string, style canvas.FontStyle) *canvas.FontFamily { - key := fontFSKey{index: index, name: name, style: style} - if cached := r.fontFSCache[key]; cached != nil { - r.FontMap[fontID] = cached +func (r *Renderer) loadFontSource(family *canvas.FontFamily, source fontSource, style canvas.FontStyle) *canvas.FontFamily { + key := fontCacheKey{fontSourceKey: fontSourceKey{kind: source.kind, index: source.index, name: source.name}, style: style} + if cached, ok := r.fontCache[key]; ok { return cached } - data, err := readFontData(fsys, name) - if err != nil || family.LoadFont(data, 0, style) != nil { + var err error + switch source.kind { + case fontSourceFile: + err = family.LoadFontFile(source.name, style) + case fontSourceFS: + var data []byte + data, err = readFontData(r.fontFS[source.index], source.name) + if err == nil { + err = family.LoadFont(data, 0, style) + } + case fontSourceSystem: + err = family.LoadSystemFont(source.name, style) + } + if err != nil { + r.fontCache[key] = nil return nil } - r.fontFSCache[key] = family - r.FontMap[fontID] = family + r.fontCache[key] = family return family } diff --git a/ofdgo_geom.go b/ofdgo_geom.go index c1e7acd..d61bb7f 100644 --- a/ofdgo_geom.go +++ b/ofdgo_geom.go @@ -15,6 +15,7 @@ package ofdgo import ( + "fmt" "math" "strconv" "strings" @@ -30,14 +31,18 @@ type Box struct { // 返回: Box 矩形对象, error 错误信息 func ParseBox(s string) (Box, error) { parts := strings.Fields(s) - if len(parts) < 4 { - return Box{}, nil + if len(parts) != 4 { + return Box{}, fmt.Errorf("invalid box: %s", s) } - x, _ := strconv.ParseFloat(parts[0], 64) - y, _ := strconv.ParseFloat(parts[1], 64) - w, _ := strconv.ParseFloat(parts[2], 64) - h, _ := strconv.ParseFloat(parts[3], 64) - return Box{X: x, Y: y, W: w, H: h}, nil + var values [4]float64 + for i, part := range parts { + value, err := strconv.ParseFloat(part, 64) + if err != nil { + return Box{}, fmt.Errorf("invalid box: %s", s) + } + values[i] = value + } + return Box{X: values[0], Y: values[1], W: values[2], H: values[3]}, nil } // Matrix 2D仿射变换矩阵 diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index 6081d97..5446273 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -33,7 +33,9 @@ type Renderer struct { FontMap map[string]*canvas.FontFamily FontGIDMap map[string]map[uint16]rune FontCIDMap map[string]map[uint16]rune - fontFSCache map[fontFSKey]*canvas.FontFamily + fontCache map[fontCacheKey]*canvas.FontFamily + fontSourceCache map[string][]fontSource + fontSourceUsed map[string]fontSource textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue templatePageCache map[string]*PageContent fontDirs []string diff --git a/ofdgo_sign.go b/ofdgo_sign.go index 24e2d1a..3bbff4f 100644 --- a/ofdgo_sign.go +++ b/ofdgo_sign.go @@ -20,7 +20,6 @@ import ( "encoding/binary" "encoding/xml" "fmt" - "strconv" "strings" ) @@ -28,7 +27,9 @@ import ( type SignType string const ( + // SignTypeSeal 印章签名 SignTypeSeal SignType = "Seal" + // SignTypeSign 数字签名 SignTypeSign SignType = "Sign" ) @@ -150,27 +151,11 @@ func (r *Reader) SignatureStampPositions(stamps []SignatureStamp) ([]SignatureSt // 入参: s 区域字符串 // 返回: Box 矩形对象, error 错误信息 func parseSignatureStampBox(s string) (Box, error) { - parts := strings.Fields(s) - if len(parts) != 4 { - return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) - } - x, err := strconv.ParseFloat(parts[0], 64) + box, err := ParseBox(s) if err != nil { return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) } - y, err := strconv.ParseFloat(parts[1], 64) - if err != nil { - return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) - } - w, err := strconv.ParseFloat(parts[2], 64) - if err != nil { - return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) - } - h, err := strconv.ParseFloat(parts[3], 64) - if err != nil { - return Box{}, fmt.Errorf("invalid signature stamp box: %s", s) - } - return Box{X: x, Y: y, W: w, H: h}, nil + return box, nil } // parseSignatures 解析签名文件