mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
feat(印章渲染): 强化印章渲染
This commit is contained in:
@@ -127,6 +127,9 @@ func (r *Reader) Doc() (*Document, error) {
|
|||||||
if err := xml.Unmarshal(data, &doc); err != nil {
|
if err := xml.Unmarshal(data, &doc); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal document.xml: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal document.xml: %w", err)
|
||||||
}
|
}
|
||||||
|
if doc.Signatures == "" {
|
||||||
|
doc.Signatures = docAttr.Signatures
|
||||||
|
}
|
||||||
r.OFD.DocBody[0].DocInfo.DocID = "loaded"
|
r.OFD.DocBody[0].DocInfo.DocID = "loaded"
|
||||||
if doc.CommonData.DocumentRes != "" {
|
if doc.CommonData.DocumentRes != "" {
|
||||||
r.loadRes(doc.CommonData.DocumentRes)
|
r.loadRes(doc.CommonData.DocumentRes)
|
||||||
|
|||||||
+40
-27
@@ -859,37 +859,11 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
names := []string{of.FamilyName, of.FontName}
|
names := []string{of.FamilyName, of.FontName}
|
||||||
aliases := map[string]string{
|
|
||||||
"simhei": "SimHei",
|
|
||||||
"黑体": "SimHei",
|
|
||||||
"microsoft yahei": "Microsoft YaHei",
|
|
||||||
"微软雅黑": "Microsoft YaHei",
|
|
||||||
"simsun": "SimSun",
|
|
||||||
"宋体": "SimSun",
|
|
||||||
"kaiti": "KaiTi",
|
|
||||||
"楷体": "KaiTi",
|
|
||||||
"fangsong": "FangSong",
|
|
||||||
"仿宋": "FangSong",
|
|
||||||
"arial": "Arial",
|
|
||||||
"segoe ui": "Segoe UI",
|
|
||||||
"times new roman": "Times New Roman",
|
|
||||||
}
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
targetName := name
|
targetName := fontAliasName(name)
|
||||||
lower := strings.ToLower(name)
|
|
||||||
if mapped, ok := aliases[lower]; ok {
|
|
||||||
targetName = mapped
|
|
||||||
} else {
|
|
||||||
for k, v := range aliases {
|
|
||||||
if strings.Contains(lower, k) {
|
|
||||||
targetName = v
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := ff.LoadSystemFont(targetName, fontStyle); err == nil {
|
if err := ff.LoadSystemFont(targetName, fontStyle); err == nil {
|
||||||
r.FontMap[fontID] = ff
|
r.FontMap[fontID] = ff
|
||||||
return ff
|
return ff
|
||||||
@@ -933,6 +907,45 @@ func (r *Renderer) loadFont(fontID string) *canvas.FontFamily {
|
|||||||
return defaultFont
|
return defaultFont
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fontAliasName 获取系统字体替代名称
|
||||||
|
// 入参: name OFD字体名称
|
||||||
|
// 返回: string 系统字体名称
|
||||||
|
func fontAliasName(name string) string {
|
||||||
|
lower := strings.ToLower(strings.TrimSpace(name))
|
||||||
|
aliases := map[string]string{
|
||||||
|
"simhei": "SimHei",
|
||||||
|
"microsoft yahei": "Microsoft YaHei",
|
||||||
|
"simsun": "SimSun",
|
||||||
|
"kaiti": "KaiTi",
|
||||||
|
"fangsong": "FangSong",
|
||||||
|
"arial": "Arial",
|
||||||
|
"segoe ui": "Segoe UI",
|
||||||
|
"times new roman": "Times New Roman",
|
||||||
|
}
|
||||||
|
if mapped, ok := aliases[lower]; ok {
|
||||||
|
return mapped
|
||||||
|
}
|
||||||
|
contains := []struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}{
|
||||||
|
{"黑体", "SimHei"},
|
||||||
|
{"微软雅黑", "Microsoft YaHei"},
|
||||||
|
{"仿宋", "FangSong"},
|
||||||
|
{"楷体", "KaiTi"},
|
||||||
|
{"方正书宋", "SimSun"},
|
||||||
|
{"书宋", "SimSun"},
|
||||||
|
{"宋体", "SimSun"},
|
||||||
|
{"宋", "SimSun"},
|
||||||
|
}
|
||||||
|
for _, item := range contains {
|
||||||
|
if strings.Contains(lower, item.Key) {
|
||||||
|
return item.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
// globFontFiles 查找字体文件
|
// globFontFiles 查找字体文件
|
||||||
// 入参: dir 目录, pattern 模式
|
// 入参: dir 目录, pattern 模式
|
||||||
// 返回: []string 文件列表
|
// 返回: []string 文件列表
|
||||||
|
|||||||
+111
-21
@@ -15,7 +15,9 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -46,6 +48,9 @@ type SignatureFile struct {
|
|||||||
XMLName xml.Name `xml:"Signature"`
|
XMLName xml.Name `xml:"Signature"`
|
||||||
SignedValue string `xml:"SignedValue"`
|
SignedValue string `xml:"SignedValue"`
|
||||||
SignedInfo struct {
|
SignedInfo struct {
|
||||||
|
Seal struct {
|
||||||
|
BaseLoc string `xml:"BaseLoc"`
|
||||||
|
} `xml:"Seal"`
|
||||||
StampAnnot []struct {
|
StampAnnot []struct {
|
||||||
ID string `xml:"ID,attr"`
|
ID string `xml:"ID,attr"`
|
||||||
PageRef string `xml:"PageRef,attr"`
|
PageRef string `xml:"PageRef,attr"`
|
||||||
@@ -82,12 +87,20 @@ func (r *Reader) parseSignatures(doc *Document) error {
|
|||||||
if err := xml.NewDecoder(sf).Decode(&sigFile); err != nil {
|
if err := xml.NewDecoder(sf).Decode(&sigFile); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
signedValuePath := path.Join(path.Dir(sigPath), sigFile.SignedValue)
|
var sealType string
|
||||||
svData, err := r.ResData(signedValuePath)
|
var sealData []byte
|
||||||
if err != nil {
|
if sigFile.SignedInfo.Seal.BaseLoc != "" {
|
||||||
return
|
sealPath := path.Join(path.Dir(sigPath), sigFile.SignedInfo.Seal.BaseLoc)
|
||||||
|
if data, err := r.ResData(sealPath); err == nil {
|
||||||
|
sealType, sealData = extractSeal(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(sealData) == 0 && sigFile.SignedValue != "" {
|
||||||
|
signedValuePath := path.Join(path.Dir(sigPath), sigFile.SignedValue)
|
||||||
|
if data, err := r.ResData(signedValuePath); err == nil {
|
||||||
|
sealType, sealData = extractSeal(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sealType, sealData := extractSeal(svData)
|
|
||||||
for _, annot := range sigFile.SignedInfo.StampAnnot {
|
for _, annot := range sigFile.SignedInfo.StampAnnot {
|
||||||
pageID := annot.PageRef
|
pageID := annot.PageRef
|
||||||
bbox, _ := ParseBox(annot.Boundary)
|
bbox, _ := ParseBox(annot.Boundary)
|
||||||
@@ -102,6 +115,9 @@ func (r *Reader) parseSignatures(doc *Document) error {
|
|||||||
// 入参: data 签名值数据
|
// 入参: data 签名值数据
|
||||||
// 返回: string 印章类型, []byte 印章数据
|
// 返回: string 印章类型, []byte 印章数据
|
||||||
func extractSeal(data []byte) (string, []byte) {
|
func extractSeal(data []byte) (string, []byte) {
|
||||||
|
if sealType, sealData := extractImageData(data); len(sealData) > 0 {
|
||||||
|
return sealType, sealData
|
||||||
|
}
|
||||||
var raw asn1.RawValue
|
var raw asn1.RawValue
|
||||||
_, err := asn1.Unmarshal(data, &raw)
|
_, err := asn1.Unmarshal(data, &raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,24 +127,24 @@ func extractSeal(data []byte) (string, []byte) {
|
|||||||
var foundData []byte
|
var foundData []byte
|
||||||
var search func(node asn1.RawValue) bool
|
var search func(node asn1.RawValue) bool
|
||||||
search = func(node asn1.RawValue) bool {
|
search = func(node asn1.RawValue) bool {
|
||||||
|
if node.Tag == 4 {
|
||||||
|
if sealType, sealData := extractImageData(node.Bytes); len(sealData) > 0 {
|
||||||
|
foundType, foundData = sealType, sealData
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
if node.IsCompound {
|
if node.IsCompound {
|
||||||
var elements []asn1.RawValue
|
if elements, ok := asn1Children(node.Bytes); ok {
|
||||||
rest, err := asn1.Unmarshal(node.Bytes, &elements)
|
if len(elements) >= 2 && elements[1].Tag == 4 {
|
||||||
if err == nil && len(rest) == 0 {
|
sealType := sealString(elements[0])
|
||||||
if len(elements) == 4 {
|
if sealType == "es" {
|
||||||
e0, e1, e2, e3 := elements[0], elements[1], elements[2], elements[3]
|
sealType = "png"
|
||||||
if e1.Tag == 4 && e2.Tag == 2 && e3.Tag == 2 {
|
}
|
||||||
foundData = e1.Bytes
|
if imgType, sealData := extractImageData(elements[1].Bytes); len(sealData) > 0 {
|
||||||
var s string
|
if sealType == "" {
|
||||||
if _, err := asn1.Unmarshal(e0.FullBytes, &s); err == nil {
|
sealType = imgType
|
||||||
foundType = s
|
|
||||||
} else {
|
|
||||||
foundType = string(e0.Bytes)
|
|
||||||
}
|
|
||||||
foundType = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(foundType, "\x00", "")))
|
|
||||||
if foundType == "es" {
|
|
||||||
foundType = "png"
|
|
||||||
}
|
}
|
||||||
|
foundType, foundData = sealType, sealData
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +163,80 @@ func extractSeal(data []byte) (string, []byte) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// asn1Children 解析ASN.1复合节点子元素
|
||||||
|
// 入参: data 复合节点内容
|
||||||
|
// 返回: []asn1.RawValue 子元素列表, bool 是否成功
|
||||||
|
func asn1Children(data []byte) ([]asn1.RawValue, bool) {
|
||||||
|
var elements []asn1.RawValue
|
||||||
|
for len(data) > 0 {
|
||||||
|
var child asn1.RawValue
|
||||||
|
rest, err := asn1.Unmarshal(data, &child)
|
||||||
|
if err != nil || len(rest) == len(data) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
elements = append(elements, child)
|
||||||
|
data = rest
|
||||||
|
}
|
||||||
|
return elements, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// sealString 解析印章图片类型
|
||||||
|
// 入参: raw ASN.1原始值
|
||||||
|
// 返回: string 图片类型
|
||||||
|
func sealString(raw asn1.RawValue) string {
|
||||||
|
var s string
|
||||||
|
if _, err := asn1.Unmarshal(raw.FullBytes, &s); err != nil {
|
||||||
|
s = string(raw.Bytes)
|
||||||
|
}
|
||||||
|
s = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(s, "\x00", "")))
|
||||||
|
if s == "jpg" {
|
||||||
|
s = "jpeg"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractImageData 提取图片数据
|
||||||
|
// 入参: data 原始数据
|
||||||
|
// 返回: string 图片类型, []byte 图片数据
|
||||||
|
func extractImageData(data []byte) (string, []byte) {
|
||||||
|
if idx := bytes.Index(data, []byte("\x89PNG\r\n\x1a\n")); idx >= 0 {
|
||||||
|
if end := pngDataEnd(data[idx:]); end > 0 {
|
||||||
|
return "png", data[idx : idx+end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx := bytes.Index(data, []byte{0xff, 0xd8, 0xff}); idx >= 0 {
|
||||||
|
if end := bytes.Index(data[idx+2:], []byte{0xff, 0xd9}); end >= 0 {
|
||||||
|
return "jpeg", data[idx : idx+2+end+2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx := bytes.Index(data, []byte("PK\x03\x04")); idx >= 0 {
|
||||||
|
return "ofd", data[idx:]
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pngDataEnd 获取PNG数据结束位置
|
||||||
|
// 入参: data PNG数据
|
||||||
|
// 返回: int 结束位置
|
||||||
|
func pngDataEnd(data []byte) int {
|
||||||
|
if len(data) < 8 || !bytes.Equal(data[:8], []byte("\x89PNG\r\n\x1a\n")) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
pos := 8
|
||||||
|
for pos+12 <= len(data) {
|
||||||
|
size := int(binary.BigEndian.Uint32(data[pos : pos+4]))
|
||||||
|
next := pos + 8 + size + 4
|
||||||
|
if size < 0 || next > len(data) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if string(data[pos+4:pos+8]) == "IEND" {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
pos = next
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// Stamp 印章信息结构
|
// Stamp 印章信息结构
|
||||||
type Stamp struct {
|
type Stamp struct {
|
||||||
Box Box
|
Box Box
|
||||||
|
|||||||
Reference in New Issue
Block a user