Implement image watermark
This commit is contained in:
@@ -7,13 +7,6 @@ import (
|
|||||||
_ "image/jpeg" // register JPEG format
|
_ "image/jpeg" // register JPEG format
|
||||||
)
|
)
|
||||||
|
|
||||||
func (context *SignContext) createAppearance(rect [4]float64) ([]byte, error) {
|
|
||||||
if len(context.SignData.Appearance.Image) > 0 {
|
|
||||||
return context.createImageAppearance(rect)
|
|
||||||
}
|
|
||||||
return context.createTextAppearance(rect)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions for PDF resource components
|
// Helper functions for PDF resource components
|
||||||
|
|
||||||
// writeAppearanceHeader writes the header for the appearance stream.
|
// writeAppearanceHeader writes the header for the appearance stream.
|
||||||
@@ -49,7 +42,7 @@ func writeFormTypeAndLength(buffer *bytes.Buffer, streamLength int) {
|
|||||||
buffer.WriteString(">>\n")
|
buffer.WriteString(">>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeBufferStream(buffer *bytes.Buffer, stream []byte) {
|
func writeAppearanceStreamBuffer(buffer *bytes.Buffer, stream []byte) {
|
||||||
buffer.WriteString("stream\n")
|
buffer.WriteString("stream\n")
|
||||||
buffer.Write(stream)
|
buffer.Write(stream)
|
||||||
buffer.WriteString("endstream\n")
|
buffer.WriteString("endstream\n")
|
||||||
@@ -129,7 +122,7 @@ func drawImage(buffer *bytes.Buffer, rectWidth, rectHeight float64) {
|
|||||||
buffer.WriteString("Q\n") // Restore graphics state
|
buffer.WriteString("Q\n") // Restore graphics state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (context *SignContext) createTextAppearance(rect [4]float64) ([]byte, error) {
|
func (context *SignContext) createAppearance(rect [4]float64) ([]byte, error) {
|
||||||
rectWidth := rect[2] - rect[0]
|
rectWidth := rect[2] - rect[0]
|
||||||
rectHeight := rect[3] - rect[1]
|
rectHeight := rect[3] - rect[1]
|
||||||
|
|
||||||
@@ -137,13 +130,8 @@ func (context *SignContext) createTextAppearance(rect [4]float64) ([]byte, error
|
|||||||
return nil, fmt.Errorf("invalid rectangle dimensions: width %.2f and height %.2f must be greater than 0", rectWidth, rectHeight)
|
return nil, fmt.Errorf("invalid rectangle dimensions: width %.2f and height %.2f must be greater than 0", rectWidth, rectHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := context.SignData.Signature.Info.Name
|
hasImage := len(context.SignData.Appearance.Image) > 0
|
||||||
|
shouldDisplayText := context.SignData.Appearance.ImageAsWatermark || !hasImage
|
||||||
fontSize, textX, textY := computeTextSizeAndPosition(text, rectWidth, rectHeight)
|
|
||||||
|
|
||||||
var appearance_stream_buffer bytes.Buffer
|
|
||||||
|
|
||||||
drawText(&appearance_stream_buffer, text, fontSize, textX, textY)
|
|
||||||
|
|
||||||
// Create the appearance XObject
|
// Create the appearance XObject
|
||||||
var appearance_buffer bytes.Buffer
|
var appearance_buffer bytes.Buffer
|
||||||
@@ -151,51 +139,44 @@ func (context *SignContext) createTextAppearance(rect [4]float64) ([]byte, error
|
|||||||
|
|
||||||
// Resources dictionary with font
|
// Resources dictionary with font
|
||||||
appearance_buffer.WriteString(" /Resources <<\n")
|
appearance_buffer.WriteString(" /Resources <<\n")
|
||||||
createFontResource(&appearance_buffer)
|
|
||||||
|
if hasImage {
|
||||||
|
// Create and add the image XObject
|
||||||
|
imageStream, err := context.createImageXObject()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create image XObject: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageObjectId, err := context.addObject(imageStream)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add image object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createImageResource(&appearance_buffer, imageObjectId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldDisplayText {
|
||||||
|
createFontResource(&appearance_buffer)
|
||||||
|
}
|
||||||
|
|
||||||
appearance_buffer.WriteString(" >>\n")
|
appearance_buffer.WriteString(" >>\n")
|
||||||
|
|
||||||
writeFormTypeAndLength(&appearance_buffer, appearance_stream_buffer.Len())
|
// Create the appearance stream
|
||||||
|
|
||||||
writeBufferStream(&appearance_buffer, appearance_stream_buffer.Bytes())
|
|
||||||
|
|
||||||
return appearance_buffer.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context *SignContext) createImageAppearance(rect [4]float64) ([]byte, error) {
|
|
||||||
rectWidth := rect[2] - rect[0]
|
|
||||||
rectHeight := rect[3] - rect[1]
|
|
||||||
|
|
||||||
if rectWidth < 1 || rectHeight < 1 {
|
|
||||||
return nil, fmt.Errorf("invalid rectangle dimensions: width %.2f and height %.2f must be greater than 0", rectWidth, rectHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and add the image XObject
|
|
||||||
imageStream, err := context.createImageXObject()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create image XObject: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
imageObjectId, err := context.addObject(imageStream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to add image object: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var appearance_stream_buffer bytes.Buffer
|
var appearance_stream_buffer bytes.Buffer
|
||||||
|
|
||||||
drawImage(&appearance_stream_buffer, rectWidth, rectHeight)
|
if hasImage {
|
||||||
|
drawImage(&appearance_stream_buffer, rectWidth, rectHeight)
|
||||||
|
}
|
||||||
|
|
||||||
// Create the appearance XObject
|
if shouldDisplayText {
|
||||||
var appearance_buffer bytes.Buffer
|
text := context.SignData.Signature.Info.Name
|
||||||
writeAppearanceHeader(&appearance_buffer, rectWidth, rectHeight)
|
fontSize, textX, textY := computeTextSizeAndPosition(text, rectWidth, rectHeight)
|
||||||
|
drawText(&appearance_stream_buffer, text, fontSize, textX, textY)
|
||||||
// Resources dictionary with XObject
|
}
|
||||||
appearance_buffer.WriteString(" /Resources <<\n")
|
|
||||||
createImageResource(&appearance_buffer, imageObjectId)
|
|
||||||
appearance_buffer.WriteString(" >>\n")
|
|
||||||
|
|
||||||
writeFormTypeAndLength(&appearance_buffer, appearance_stream_buffer.Len())
|
writeFormTypeAndLength(&appearance_buffer, appearance_stream_buffer.Len())
|
||||||
|
|
||||||
writeBufferStream(&appearance_buffer, appearance_stream_buffer.Bytes())
|
writeAppearanceStreamBuffer(&appearance_buffer, appearance_stream_buffer.Bytes())
|
||||||
|
|
||||||
return appearance_buffer.Bytes(), nil
|
return appearance_buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
@@ -618,3 +618,55 @@ func TestSignPDFWithTwoImages(t *testing.T) {
|
|||||||
|
|
||||||
verifySignedFile(t, secondSignature, filepath.Base(tbsFile))
|
verifySignedFile(t, secondSignature, filepath.Base(tbsFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSignPDFWithWatermarkImage tests signing a PDF with an image and text above
|
||||||
|
func TestSignPDFWithWatermarkImage(t *testing.T) {
|
||||||
|
cert, pkey := loadCertificateAndKey(t)
|
||||||
|
inputFilePath := "../testfiles/testfile12.pdf"
|
||||||
|
originalFileName := filepath.Base(inputFilePath)
|
||||||
|
|
||||||
|
// Read the signature image file
|
||||||
|
signatureImage, err := os.ReadFile("../testfiles/pdfsign-signature-watermark.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read signature image: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpfile, err := os.CreateTemp("", t.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s", err.Error())
|
||||||
|
}
|
||||||
|
if !testing.Verbose() {
|
||||||
|
defer os.Remove(tmpfile.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SignFile(inputFilePath, tmpfile.Name(), SignData{
|
||||||
|
Signature: SignDataSignature{
|
||||||
|
Info: SignDataSignatureInfo{
|
||||||
|
Name: "James SuperSmith",
|
||||||
|
Location: "Somewhere",
|
||||||
|
Reason: "Test with visible signature and watermark image",
|
||||||
|
ContactInfo: "None",
|
||||||
|
Date: time.Now().Local(),
|
||||||
|
},
|
||||||
|
CertType: ApprovalSignature,
|
||||||
|
DocMDPPerm: AllowFillingExistingFormFieldsAndSignaturesPerms,
|
||||||
|
},
|
||||||
|
Appearance: Appearance{
|
||||||
|
Visible: true,
|
||||||
|
LowerLeftX: 400,
|
||||||
|
LowerLeftY: 50,
|
||||||
|
UpperRightX: 600,
|
||||||
|
UpperRightY: 125,
|
||||||
|
Image: signatureImage, // Use the signature image
|
||||||
|
ImageAsWatermark: true, // Set the image as a watermark
|
||||||
|
},
|
||||||
|
DigestAlgorithm: crypto.SHA512,
|
||||||
|
Signer: pkey,
|
||||||
|
Certificate: cert,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: %s", originalFileName, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
verifySignedFile(t, tmpfile, originalFileName)
|
||||||
|
}
|
||||||
|
BIN
testfiles/pdfsign-signature-watermark.jpg
Normal file
BIN
testfiles/pdfsign-signature-watermark.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 53 KiB |
Reference in New Issue
Block a user