From 1cb2266a3dae02c8bfb28fd7868bfbffcf4a0e6b Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Fri, 26 Jun 2026 11:42:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=AD=BE=E7=AB=A0=E6=B8=B2=E6=9F=93):=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=AD=BE=E7=AB=A0=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_annot.go | 14 ------- ofdgo_renderer.go | 93 +++++++++++++++++++++++++++-------------------- ofdgo_sign.go | 36 +++++++++--------- ofdgo_text.go | 17 +++++++++ 4 files changed, 89 insertions(+), 71 deletions(-) diff --git a/ofdgo_annot.go b/ofdgo_annot.go index 63e4116..e4cf8e8 100644 --- a/ofdgo_annot.go +++ b/ofdgo_annot.go @@ -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"` diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index 6f73454..8c845a5 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -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, ¤tCTM) if cgu.ResourceID != "" { if ref, ok := r.CompositeGraphicUnits[cgu.ResourceID]; ok { - r.renderCompositeGraphicUnit(ctx, *ref, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM) + refCopy := *ref + refCopy.Alpha = mergeAlpha(refCopy.Alpha, cgu.Alpha) + r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM) } } 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, ¤tCTM) } ctx.Pop() return } for _, imgObj := range cgu.ImageObject { + imgObj.Alpha = mergeAlpha(imgObj.Alpha, cgu.Alpha) r.renderImage(ctx, imgObj, pageH, ¤tCTM) } for _, pathObj := range cgu.PathObject { + pathObj.Alpha = mergeAlpha(pathObj.Alpha, cgu.Alpha) r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM) } for _, textObj := range cgu.TextObject { + textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha) r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, ¤tCTM) } for _, subCgu := range cgu.CompositeGraphicUnit { + subCgu.Alpha = mergeAlpha(subCgu.Alpha, cgu.Alpha) r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM) } 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 diff --git a/ofdgo_sign.go b/ofdgo_sign.go index 0cdaba5..8eff071 100644 --- a/ofdgo_sign.go +++ b/ofdgo_sign.go @@ -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 { - XMLName xml.Name `xml:"Signatures"` - MaxSignId string `xml:"MaxSignId"` - List []OFDSignature `xml:"Signature"` +// Signatures 签名列表 +type Signatures struct { + XMLName xml.Name `xml:"Signatures"` + MaxSignId string `xml:"MaxSignId"` + List []Signature `xml:"Signature"` } -// OFDSignature 签名列表引用 -type OFDSignature struct { - ID string `xml:"ID,attr"` - BaseLoc string `xml:"BaseLoc,attr"` +// 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 diff --git a/ofdgo_text.go b/ofdgo_text.go index 2447f87..d1e178a 100644 --- a/ofdgo_text.go +++ b/ofdgo_text.go @@ -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 颜色对象