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"
// 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 注释集合
type Annotations struct {
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 页面高度
func (r *Renderer) renderAnnotations(ctx *canvas.Context, pageID string, pageH float64) {
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
}
box, _ := ParseBox(annot.Appearance.Boundary)
@@ -322,10 +322,11 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite
if parentCTM != nil {
currentCTM = parentCTM.Multiply(currentCTM)
}
r.applyClips(ctx, cgu.Clips, pageH, &currentCTM)
if cgu.ResourceID != "" {
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 != "" {
@@ -343,21 +344,26 @@ func (r *Renderer) renderCompositeGraphicUnit(ctx *canvas.Context, cgu Composite
}
if len(cgu.Objects) > 0 {
for _, obj := range cgu.Objects {
obj = mergeGraphicObjectAlpha(obj, cgu.Alpha)
r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM)
}
ctx.Pop()
return
}
for _, imgObj := range cgu.ImageObject {
imgObj.Alpha = mergeAlpha(imgObj.Alpha, cgu.Alpha)
r.renderImage(ctx, imgObj, pageH, &currentCTM)
}
for _, pathObj := range cgu.PathObject {
pathObj.Alpha = mergeAlpha(pathObj.Alpha, cgu.Alpha)
r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM)
}
for _, textObj := range cgu.TextObject {
textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha)
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, &currentCTM)
}
for _, subCgu := range cgu.CompositeGraphicUnit {
subCgu.Alpha = mergeAlpha(subCgu.Alpha, cgu.Alpha)
r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, &currentCTM)
}
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 获取绘制参数逻辑
// 入参: id 参数ID, visited 访问记录
// 返回: *DrawParam 绘制参数
@@ -475,6 +501,7 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
if imgW <= 0 || imgH <= 0 {
return
}
img = imageWithAlpha(img, obj.Alpha)
ctm := NewMatrix(obj.CTM)
if obj.CTM == "" {
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)
canvasX, canvasY := tx+box.X, pageH-(ty+box.Y)
ctx.Push()
r.applyClips(ctx, obj.Clips, pageH, &ctm)
ctx.Translate(canvasX, canvasY)
ctx.Scale(ctm.a/imgW, ctm.d/imgH)
ctx.DrawImage(0, 0, img, canvas.DPMM(1.0))
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 渲染路径
// 入参: 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) {
@@ -509,8 +555,7 @@ func (r *Renderer) renderPath(ctx *canvas.Context, obj PathObject, pageH float64
if parentCTM != nil {
ctm = parentCTM.Multiply(ctm)
}
r.applyClips(ctx, obj.Clips, pageH, &ctm)
fillColor, strokeColor := defaultFill, defaultStroke
fillColor, strokeColor := colorWithAlpha(defaultFill, obj.Alpha), colorWithAlpha(defaultStroke, obj.Alpha)
var fillPaint any = fillColor
var strokePaint any = strokeColor
lineWidth := defaultLW
@@ -670,7 +715,6 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
if parentCTM != nil {
ctm = parentCTM.Multiply(ctm)
}
r.applyClips(ctx, obj.Clips, pageH, &ctm)
var dp *DrawParam
if obj.DrawParam != "" {
dp = r.getDrawParam(obj.DrawParam, nil)
@@ -690,9 +734,9 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
sizeMM *= scale
}
sizePt := sizeMM * 2.83465
fillColor := defaultFill
fillColor := colorWithAlpha(defaultFill, obj.Alpha)
if fillColor == nil {
fillColor = canvas.Black
fillColor = colorWithAlpha(canvas.Black, obj.Alpha)
}
var fillPaint any = fillColor
var fillColorNode *FillColor
@@ -1159,37 +1203,6 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
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
+15 -13
View File
@@ -23,24 +23,25 @@ import (
"strings"
)
type SignType string
const (
// SignTypeSeal 签章类型: seal
SignTypeSeal = "seal"
// SignTypeSign 签章类型: sign
SignTypeSign = "sign"
SignTypeSeal = "Seal"
SignTypeSign = "Sign"
)
// OFDSignatures 签名列表列表
type OFDSignatures struct {
// Signatures 签名列表
type Signatures struct {
XMLName xml.Name `xml:"Signatures"`
MaxSignId string `xml:"MaxSignId"`
List []OFDSignature `xml:"Signature"`
List []Signature `xml:"Signature"`
}
// OFDSignature 签名列表引用
type OFDSignature struct {
// Signature 签名列表引用
type Signature struct {
ID string `xml:"ID,attr"`
BaseLoc string `xml:"BaseLoc,attr"`
Type SignType `xml:"Type,attr"`
}
// SignatureFile 签名文件内容描述
@@ -66,18 +67,19 @@ func (r *Reader) parseSignatures(doc *Document) error {
if doc.Signatures == "" {
return nil
}
f, err := r.openFile(doc.Signatures)
sigListPath := r.ResPath(doc.Signatures)
f, err := r.openFile(sigListPath)
if err != nil {
return err
}
defer f.Close()
var signatures OFDSignatures
var signatures Signatures
if err := xml.NewDecoder(f).Decode(&signatures); err != nil {
return err
}
for _, sigRef := range signatures.List {
func(sigRef OFDSignature) {
sigPath := resolveResourcePath(doc.Signatures, "", sigRef.BaseLoc)
func(sigRef Signature) {
sigPath := resolveResourcePath(sigListPath, "", sigRef.BaseLoc)
sf, err := r.openFile(sigPath)
if err != nil {
return
+17
View File
@@ -108,6 +108,23 @@ func withStrokeAlpha(strokeColor *StrokeColor, alpha *int) *StrokeColor {
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 解析填充颜色
// 入参: fillColor 填充颜色节点
// 返回: color.Color 颜色对象