feat(优化兼容): 添加ofdgo_sign.go文件

This commit is contained in:
2025-12-30 10:08:50 +08:00
parent a1dbc9dfe5
commit d989bd9c07
5 changed files with 241 additions and 1 deletions
+1
View File
@@ -24,6 +24,7 @@ type Document struct {
Outlines Outlines `xml:"Outlines"` Outlines Outlines `xml:"Outlines"`
Permissions Permissions `xml:"Permissions"` Permissions Permissions `xml:"Permissions"`
Annotations string `xml:"Annotations"` Annotations string `xml:"Annotations"`
Signatures string `xml:"Signatures"`
Attachments Attachments `xml:"Attachments"` Attachments Attachments `xml:"Attachments"`
Extensions Extensions `xml:"Extensions"` Extensions Extensions `xml:"Extensions"`
} }
+1
View File
@@ -19,6 +19,7 @@ import "encoding/xml"
// PageContent 页面内容 // PageContent 页面内容
type PageContent struct { type PageContent struct {
XMLName xml.Name `xml:"Page"` XMLName xml.Name `xml:"Page"`
ID string `xml:"-"`
Area PageArea `xml:"Area"` Area PageArea `xml:"Area"`
Template []Template `xml:"Template"` Template []Template `xml:"Template"`
Content Content `xml:"Content"` Content Content `xml:"Content"`
+17 -1
View File
@@ -34,6 +34,7 @@ type Reader struct {
fontCache map[string]*Font fontCache map[string]*Font
drawParamCache map[string]*DrawParam drawParamCache map[string]*DrawParam
doc *Document doc *Document
Stamps map[string][]Stamp
} }
// Close 关闭阅读器 // Close 关闭阅读器
@@ -73,6 +74,19 @@ func (r *Reader) readFile(name string) ([]byte, error) {
return nil, fmt.Errorf("file not found: %s", name) return nil, fmt.Errorf("file not found: %s", name)
} }
// openFile 打开压缩包内的文件流
// 入参: name 文件名
// 返回: io.ReadCloser 文件流, error 错误信息
func (r *Reader) openFile(name string) (io.ReadCloser, error) {
name = strings.ReplaceAll(name, "\\", "/")
for _, f := range r.Zip.File {
if f.Name == name {
return f.Open()
}
}
return nil, fmt.Errorf("file not found: %s", name)
}
// readZipFile 读取zip文件内容 // readZipFile 读取zip文件内容
// 入参: f zip文件对象 // 入参: f zip文件对象
// 返回: []byte 文件内容, error 错误信息 // 返回: []byte 文件内容, error 错误信息
@@ -116,7 +130,8 @@ func (r *Reader) Doc() (*Document, error) {
r.loadRes(doc.CommonData.PublicRes) r.loadRes(doc.CommonData.PublicRes)
} }
r.doc = &doc r.doc = &doc
return &doc, nil _ = r.parseSignatures(&doc)
return r.doc, nil
} }
// loadRes 加载资源文件 // loadRes 加载资源文件
@@ -183,6 +198,7 @@ func (r *Reader) PageContent(page Page) (*PageContent, error) {
if err := xml.Unmarshal(data, &content); err != nil { if err := xml.Unmarshal(data, &content); err != nil {
return nil, fmt.Errorf("failed to unmarshal page content: %w", err) return nil, fmt.Errorf("failed to unmarshal page content: %w", err)
} }
content.ID = page.ID
return &content, nil return &content, nil
} }
+55
View File
@@ -81,6 +81,11 @@ func (r *Renderer) RenderPage(page *PageContent) (*canvas.Canvas, error) {
r.renderLayer(ctx, layer, pageH, nil, nil, 0) r.renderLayer(ctx, layer, pageH, nil, nil, 0)
} }
} }
if stamps, ok := r.Reader.Stamps[page.ID]; ok {
for _, s := range stamps {
r.renderStamp(ctx, s, pageH)
}
}
return c, nil return c, nil
} }
@@ -570,6 +575,56 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
return ff return ff
} }
} }
winFontDir := `C:\Windows\Fonts`
matches, _ := filepath.Glob(filepath.Join(winFontDir, "*"+targetName+"*"))
if len(matches) == 0 {
if targetName == "SimSun" {
matches, _ = filepath.Glob(filepath.Join(winFontDir, "simsun.ttc"))
} else if targetName == "KaiTi" {
matches, _ = filepath.Glob(filepath.Join(winFontDir, "simkai.ttf"))
} else if targetName == "SimHei" {
matches, _ = filepath.Glob(filepath.Join(winFontDir, "simhei.ttf"))
} else if targetName == "FangSong" {
matches, _ = filepath.Glob(filepath.Join(winFontDir, "simfang.ttf"))
}
}
for _, m := range matches {
if err := ff.LoadFontFile(m, canvas.FontRegular); err == nil {
r.FontMap[fontID] = ff
return ff
}
}
} }
return defaultFont return defaultFont
} }
// renderStamp 渲染印章
// 入参: ctx 画布上下文, s 印章对象, pageH 页面高度
func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
x, y, w, h := s.Box.X, s.Box.Y, s.Box.W, s.Box.H
if len(s.Data) > 0 {
img, _, err := image.Decode(bytes.NewReader(s.Data))
if err == nil {
ctx.Push()
screenY := pageH - (y + h)
ctx.Translate(x, screenY)
ctx.Scale(w/float64(img.Bounds().Dx()), h/float64(img.Bounds().Dy()))
ctx.DrawImage(0, 0, img, canvas.DPMM(1.0))
ctx.Pop()
return
}
}
ctx.Push()
screenY := pageH - (y + h)
ctx.SetStrokeColor(canvas.Red)
ctx.SetStrokeWidth(0.5)
ctx.SetFillColor(canvas.Transparent)
ctx.DrawPath(x, screenY, canvas.Rectangle(w, h))
ctx.SetFillColor(canvas.Red)
fontSize := 3.0
if r.fontFamily != nil {
font := r.fontFamily.Face(fontSize*2.83465, canvas.Red, canvas.FontRegular, canvas.FontNormal)
ctx.DrawText(x+w/2-font.TextWidth("Signature")/2, screenY+h/2-fontSize/2, canvas.NewTextLine(font, "Signature", canvas.Left))
}
ctx.Pop()
}
+167
View File
@@ -0,0 +1,167 @@
// Copyright 2025 肖其顿 (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 (
"encoding/asn1"
"encoding/xml"
"path"
"strings"
)
const (
// SignTypeSeal 签章类型: seal
SignTypeSeal = "seal"
// SignTypeSign 签章类型: sign
SignTypeSign = "sign"
)
// OFDSignatures 签名列表列表
type OFDSignatures struct {
XMLName xml.Name `xml:"Signatures"`
MaxSignId string `xml:"MaxSignId"`
List []OFDSignature `xml:"Signature"`
}
// OFDSignature 签名列表引用
type OFDSignature struct {
ID string `xml:"ID,attr"`
BaseLoc string `xml:"BaseLoc,attr"`
}
// SignatureFile 签名文件内容描述
type SignatureFile struct {
XMLName xml.Name `xml:"Signature"`
SignedValue string `xml:"SignedValue"`
SignedInfo struct {
StampAnnot []struct {
ID string `xml:"ID,attr"`
PageRef string `xml:"PageRef,attr"`
Boundary string `xml:"Boundary,attr"`
} `xml:"StampAnnot"`
} `xml:"SignedInfo"`
}
// parseSignatures 解析签名文件
// 入参: doc 文档结构
// 返回: error 错误信息
func (r *Reader) parseSignatures(doc *Document) error {
if doc.Signatures == "" {
return nil
}
f, err := r.openFile(doc.Signatures)
if err != nil {
return err
}
defer f.Close()
var signatures OFDSignatures
if err := xml.NewDecoder(f).Decode(&signatures); err != nil {
return err
}
for _, sigRef := range signatures.List {
sigPath := path.Join(path.Dir(doc.Signatures), sigRef.BaseLoc)
sf, err := r.openFile(sigPath)
if err != nil {
continue
}
var sigFile SignatureFile
if err := xml.NewDecoder(sf).Decode(&sigFile); err != nil {
sf.Close()
continue
}
sf.Close()
signedValuePath := path.Join(path.Dir(sigPath), sigFile.SignedValue)
svData, err := r.ResData(signedValuePath)
if err != nil {
continue
}
sealType, sealData := extractSeal(svData)
for _, annot := range sigFile.SignedInfo.StampAnnot {
pageID := annot.PageRef
bbox, _ := ParseBox(annot.Boundary)
r.addStamp(pageID, bbox, sealType, sealData)
}
}
return nil
}
// extractSeal 尝试提取印章数据
// 入参: data 签名值数据
// 返回: string 印章类型, []byte 印章数据
func extractSeal(data []byte) (string, []byte) {
var raw asn1.RawValue
_, err := asn1.Unmarshal(data, &raw)
if err != nil {
return "", nil
}
var foundType string
var foundData []byte
var search func(node asn1.RawValue) bool
search = func(node asn1.RawValue) bool {
if node.IsCompound {
var elements []asn1.RawValue
rest, err := asn1.Unmarshal(node.Bytes, &elements)
if err == nil && len(rest) == 0 {
if len(elements) == 4 {
e0, e1, e2, e3 := elements[0], elements[1], elements[2], elements[3]
if e1.Tag == 4 && e2.Tag == 2 && e3.Tag == 2 {
foundData = e1.Bytes
var s string
if _, err := asn1.Unmarshal(e0.FullBytes, &s); err == nil {
foundType = s
} else {
foundType = string(e0.Bytes)
}
foundType = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(foundType, "\x00", "")))
if foundType == "es" {
foundType = "png"
}
return true
}
}
for _, child := range elements {
if search(child) {
return true
}
}
}
}
return false
}
if search(raw) {
return foundType, foundData
}
return "", nil
}
// Stamp 印章信息结构
type Stamp struct {
Box Box
Type string
Data []byte
}
// addStamp 添加印章到页面
// 入参: pageID 页面ID, box 印章区域, sType 印章类型, data 印章数据
func (r *Reader) addStamp(pageID string, box Box, sType string, data []byte) {
if r.Stamps == nil {
r.Stamps = make(map[string][]Stamp)
}
r.Stamps[pageID] = append(r.Stamps[pageID], Stamp{
Box: box,
Type: sType,
Data: data,
})
}