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"
|
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
@@ -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, ¤tCTM)
|
|
||||||
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, ¤tCTM)
|
refCopy := *ref
|
||||||
|
refCopy.Alpha = mergeAlpha(refCopy.Alpha, cgu.Alpha)
|
||||||
|
r.renderCompositeGraphicUnit(ctx, refCopy, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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, ¤tCTM)
|
r.renderObject(ctx, obj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM)
|
||||||
}
|
}
|
||||||
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, ¤tCTM)
|
r.renderImage(ctx, imgObj, pageH, ¤tCTM)
|
||||||
}
|
}
|
||||||
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, ¤tCTM)
|
r.renderPath(ctx, pathObj, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM)
|
||||||
}
|
}
|
||||||
for _, textObj := range cgu.TextObject {
|
for _, textObj := range cgu.TextObject {
|
||||||
|
textObj.Alpha = mergeAlpha(textObj.Alpha, cgu.Alpha)
|
||||||
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, ¤tCTM)
|
r.renderText(ctx, textObj, pageH, defaultFill, defaultStroke, ¤tCTM)
|
||||||
}
|
}
|
||||||
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, ¤tCTM)
|
r.renderCompositeGraphicUnit(ctx, subCgu, pageH, defaultFill, defaultStroke, defaultLW, ¤tCTM)
|
||||||
}
|
}
|
||||||
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
@@ -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
|
||||||
|
|||||||
@@ -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 颜色对象
|
||||||
|
|||||||
Reference in New Issue
Block a user