feat(签章渲染): 优化签章渲染

This commit is contained in:
2026-06-26 11:42:53 +08:00
parent 94fa027446
commit 1cb2266a3d
4 changed files with 89 additions and 71 deletions
-14
View File
@@ -16,20 +16,6 @@ package ofdgo
import "encoding/xml" import "encoding/xml"
// Signatures 签名集合
type Signatures struct {
XMLName xml.Name `xml:"Signatures"`
MaxSignID string `xml:"MaxSignId,attr"`
Signature []Signature `xml:"Signature"`
}
// Signature 签名信息
type Signature struct {
ID string `xml:"ID,attr"`
Type string `xml:"Type,attr"`
BaseLoc string `xml:"BaseLoc,attr"`
}
// Annotations 注释集合 // Annotations 注释集合
type Annotations struct { type Annotations struct {
XMLName xml.Name `xml:"Annotations"` XMLName xml.Name `xml:"Annotations"`
+53 -40
View File
@@ -240,7 +240,7 @@ func (r *Renderer) RenderToMultiPagePDF(writer io.Writer) error {
// 入参: ctx 画布上下文, pageID 页面ID, pageH 页面高度 // 入参: ctx 画布上下文, pageID 页面ID, pageH 页面高度
func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH float64) { func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH float64) {
for _, annot := range r.Reader.Annots[pageID] { for _, annot := range r.Reader.Annots[pageID] {
if strings.EqualFold(annot.Type, "Link") || len(annot.Appearance.Objects) == 0 { if len(annot.Appearance.Objects) == 0 {
continue continue
} }
box, _ := ParseBox(annot.Appearance.Boundary) box, _ := ParseBox(annot.Appearance.Boundary)
@@ -322,10 +322,11 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite
if parentCTM != nil { if parentCTM != nil {
currentCTM = parentCTM.Multiply(currentCTM) currentCTM = parentCTM.Multiply(currentCTM)
} }
r.applyClips(ctx, cgu.Clips, pageH, &currentCTM)
if cgu.ResourceID != "" { if cgu.ResourceID != "" {
if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok { if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok {
r.renderCompositeGraphicUnit(ctx, *ref, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM) refCopy := *ref
refCopy.Alpha = mergeAlpha(refCopy.Alpha, cgu.Alpha)
r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM)
} }
} }
if cgu.DrawParam != "" { if cgu.DrawParam != "" {
@@ -343,21 +344,26 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite
} }
if len(cgu.Objects) > 0 { if len(cgu.Objects) > 0 {
for _, obj := range cgu.Objects { for _, obj := range cgu.Objects {
obj = mergeGraphicObjectAlpha(obj, cgu.Alpha)
r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM) r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM)
} }
ctx.Pop() ctx.Pop()
return return
} }
for _, imgObj := range cgu.ImageObject { for _, imgObj := range cgu.ImageObject {
imgObj.Alpha = mergeAlpha(imgObj.Alpha, cgu.Alpha)
r.renderImage(ctx, imgObj, pageH, &currentCTM) r.renderImage(ctx, imgObj, pageH, &currentCTM)
} }
for _, pathObj := range cgu.PathObject { for _, pathObj := range cgu.PathObject {
pathObj.Alpha = mergeAlpha(pathObj.Alpha, cgu.Alpha)
r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM) r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM)
} }
for _, textObj := range cgu.TextObject { for _, textObj := range cgu.TextObject {
textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha)
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, &currentCTM) r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, &currentCTM)
} }
for _, subCgu := range cgu.CompositeGraphicUnit { for _, subCgu := range cgu.CompositeGraphicUnit {
subCgu.Alpha = mergeAlpha(subCgu.Alpha, cgu.Alpha)
r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM) r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM)
} }
ctx.Pop() ctx.Pop()
@@ -378,6 +384,26 @@ func (r *Renderer) renderObject(ctx *canvas.Context, obj GraphicObject, pageH fl
} }
} }
// mergeGraphicObjectAlpha 合并图形对象透明度
// 入参: obj 图形对象, alpha 父级透明度
// 返回: GraphicObject 合并后的图形对象
func mergeGraphicObjectAlpha(obj GraphicObject, alpha *int) GraphicObject {
if alpha == nil {
return obj
}
switch obj.Type {
case "TextObject":
obj.TextObject.Alpha = mergeAlpha(obj.TextObject.Alpha, alpha)
case "PathObject":
obj.PathObject.Alpha = mergeAlpha(obj.PathObject.Alpha, alpha)
case "ImageObject":
obj.ImageObject.Alpha = mergeAlpha(obj.ImageObject.Alpha, alpha)
case "CompositeGraphicUnit", "CompositeObject":
obj.CompositeGraphicUnit.Alpha = mergeAlpha(obj.CompositeGraphicUnit.Alpha, alpha)
}
return obj
}
// getDrawParam 获取绘制参数逻辑 // getDrawParam 获取绘制参数逻辑
// 入参: id 参数ID, visited 访问记录 // 入参: id 参数ID, visited 访问记录
// 返回: *DrawParam 绘制参数 // 返回: *DrawParam 绘制参数
@@ -475,6 +501,7 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
if imgW <= 0 || imgH <= 0 { if imgW <= 0 || imgH <= 0 {
return return
} }
img = imageWithAlpha(img, obj.Alpha)
ctm := NewMatrix(obj.CTM) ctm := NewMatrix(obj.CTM)
if obj.CTM == "" { if obj.CTM == "" {
ctm = Matrix{a: box.W, d: box.H} ctm = Matrix{a: box.W, d: box.H}
@@ -485,13 +512,32 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
tx, ty := ctm.Transform(0, 1) tx, ty := ctm.Transform(0, 1)
canvasX, canvasY := tx+box.X, pageH-(ty+box.Y) canvasX, canvasY := tx+box.X, pageH-(ty+box.Y)
ctx.Push() ctx.Push()
r.applyClips(ctx, obj.Clips, pageH, &ctm)
ctx.Translate(canvasX, canvasY) ctx.Translate(canvasX, canvasY)
ctx.Scale(ctm.a/imgW, ctm.d/imgH) ctx.Scale(ctm.a/imgW, ctm.d/imgH)
ctx.DrawImage(0, 0, img, canvas.DPMM(1.0)) ctx.DrawImage(0, 0, img, canvas.DPMM(1.0))
ctx.Pop() ctx.Pop()
} }
// imageWithAlpha 合并图片透明度
// 入参: img 图片对象, alpha 对象透明度
// 返回: image.Image 合并后的图片对象
func imageWithAlpha(img image.Image, alpha *int) image.Image {
if img == nil || alpha == nil {
return img
}
a := clampColor(*alpha)
bounds := img.Bounds()
out := image.NewNRGBA(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
c.A = uint8(int(c.A) * a / 255)
out.SetNRGBA(x, y, c)
}
}
return out
}
// renderPath 渲染路径 // renderPath 渲染路径
// 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM // 入参: ctx 画布上下文, obj 路径对象, pageH 页面高度, defaultFill 默认填充色, defaultStroke 默认描边色, defaultLW 默认线宽, parentCTM 父级CTM
func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) { func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64, defaultFill, defaultStroke color.Color, defaultLW float64, parentCTM *Matrix) {
@@ -509,8 +555,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
if parentCTM != nil { if parentCTM != nil {
ctm = parentCTM.Multiply(ctm) ctm = parentCTM.Multiply(ctm)
} }
r.applyClips(ctx, obj.Clips, pageH, &ctm) fillColor, strokeColor := colorWithAlpha(defaultFill, obj.Alpha), colorWithAlpha(defaultStroke, obj.Alpha)
fillColor, strokeColor := defaultFill, defaultStroke
var fillPaint any = fillColor var fillPaint any = fillColor
var strokePaint any = strokeColor var strokePaint any = strokeColor
lineWidth := defaultLW lineWidth := defaultLW
@@ -670,7 +715,6 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
if parentCTM != nil { if parentCTM != nil {
ctm = parentCTM.Multiply(ctm) ctm = parentCTM.Multiply(ctm)
} }
r.applyClips(ctx, obj.Clips, pageH, &ctm)
var dp *DrawParam var dp *DrawParam
if obj.DrawParam != "" { if obj.DrawParam != "" {
dp = r.getDrawParam(obj.DrawParam, nil) dp = r.getDrawParam(obj.DrawParam, nil)
@@ -690,9 +734,9 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
sizeMM *= scale sizeMM *= scale
} }
sizePt := sizeMM * 2.83465 sizePt := sizeMM * 2.83465
fillColor := defaultFill fillColor := colorWithAlpha(defaultFill, obj.Alpha)
if fillColor == nil { if fillColor == nil {
fillColor = canvas.Black fillColor = colorWithAlpha(canvas.Black, obj.Alpha)
} }
var fillPaint any = fillColor var fillPaint any = fillColor
var fillColorNode *FillColor var fillColorNode *FillColor
@@ -1159,37 +1203,6 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
return return
} }
} }
ctx.Push()
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()
}
// applyClips 应用裁剪
// 入参: ctx 画布上下文, clips 裁剪对象, pageH 页面高度, parentCTM 父级CTM
func (r *Renderer) applyClips(ctx *canvas.Context, clips *Clips, pageH float64, parentCTM *Matrix) {
if clips == nil {
return
}
for _, clip := range clips.Clip {
for _, area := range clip.Area {
for _, pathObj := range area.Path {
clipCTM := NewMatrix(pathObj.CTM)
if parentCTM != nil {
clipCTM = parentCTM.Multiply(clipCTM)
}
r.buildPath(pathObj, pageH, clipCTM)
}
}
}
} }
// buildPath 解析路径并返回Canvas Path // buildPath 解析路径并返回Canvas Path
+15 -13
View File
@@ -23,24 +23,25 @@ import (
"strings" "strings"
) )
type SignType string
const ( const (
// SignTypeSeal 签章类型: seal SignTypeSeal = "Seal"
SignTypeSeal = "seal" SignTypeSign = "Sign"
// SignTypeSign 签章类型: sign
SignTypeSign = "sign"
) )
// OFDSignatures 签名列表列表 // Signatures 签名列表
type OFDSignatures struct { type Signatures struct {
XMLName xml.Name `xml:"Signatures"` XMLName xml.Name `xml:"Signatures"`
MaxSignId string `xml:"MaxSignId"` MaxSignId string `xml:"MaxSignId"`
List []OFDSignature `xml:"Signature"` List []Signature `xml:"Signature"`
} }
// OFDSignature 签名列表引用 // Signature 签名列表引用
type OFDSignature struct { type Signature struct {
ID string `xml:"ID,attr"` ID string `xml:"ID,attr"`
BaseLoc string `xml:"BaseLoc,attr"` BaseLoc string `xml:"BaseLoc,attr"`
Type SignType `xml:"Type,attr"`
} }
// SignatureFile 签名文件内容描述 // SignatureFile 签名文件内容描述
@@ -66,18 +67,19 @@ func (r *Reader) parseSignatures(doc *Document) error {
if doc.Signatures == "" { if doc.Signatures == "" {
return nil return nil
} }
f, err := r.openFile(doc.Signatures) sigListPath := r.ResPath(doc.Signatures)
f, err := r.openFile(sigListPath)
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer f.Close()
var signatures OFDSignatures var signatures Signatures
if err := xml.NewDecoder(f).Decode(&signatures); err != nil { if err := xml.NewDecoder(f).Decode(&signatures); err != nil {
return err return err
} }
for _, sigRef := range signatures.List { for _, sigRef := range signatures.List {
func(sigRef OFDSignature) { func(sigRef Signature) {
sigPath := resolveResourcePath(doc.Signatures, "", sigRef.BaseLoc) sigPath := resolveResourcePath(sigListPath, "", sigRef.BaseLoc)
sf, err := r.openFile(sigPath) sf, err := r.openFile(sigPath)
if err != nil { if err != nil {
return return
+17
View File
@@ -108,6 +108,23 @@ func withStrokeAlpha(strokeColor *StrokeColor, alpha *int) *StrokeColor {
return &merged return &merged
} }
// colorWithAlpha 合并颜色透明度
// 入参: c 颜色对象, alpha 对象透明度
// 返回: color.Color 合并后的颜色对象
func colorWithAlpha(c color.Color, alpha *int) color.Color {
if c == nil || alpha == nil {
return c
}
a := clampColor(*alpha)
rgba := colorToRGBA(c)
return color.RGBA{
R: uint8(int(rgba.R) * a / 255),
G: uint8(int(rgba.G) * a / 255),
B: uint8(int(rgba.B) * a / 255),
A: uint8(int(rgba.A) * a / 255),
}
}
// parseFillColor 解析填充颜色 // parseFillColor 解析填充颜色
// 入参: fillColor 填充颜色节点 // 入参: fillColor 填充颜色节点
// 返回: color.Color 颜色对象 // 返回: color.Color 颜色对象