mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
feat(签章渲染): 优化签章渲染
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
+19
-17
@@ -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
|
||||
|
||||
@@ -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 颜色对象
|
||||
|
||||
Reference in New Issue
Block a user