diff --git a/sign/appearance.go b/sign/appearance.go index 43010cb..7b3b5d5 100644 --- a/sign/appearance.go +++ b/sign/appearance.go @@ -7,13 +7,6 @@ import ( _ "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 // writeAppearanceHeader writes the header for the appearance stream. @@ -49,7 +42,7 @@ func writeFormTypeAndLength(buffer *bytes.Buffer, streamLength int) { buffer.WriteString(">>\n") } -func writeBufferStream(buffer *bytes.Buffer, stream []byte) { +func writeAppearanceStreamBuffer(buffer *bytes.Buffer, stream []byte) { buffer.WriteString("stream\n") buffer.Write(stream) buffer.WriteString("endstream\n") @@ -129,7 +122,7 @@ func drawImage(buffer *bytes.Buffer, rectWidth, rectHeight float64) { 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] 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) } - text := context.SignData.Signature.Info.Name - - fontSize, textX, textY := computeTextSizeAndPosition(text, rectWidth, rectHeight) - - var appearance_stream_buffer bytes.Buffer - - drawText(&appearance_stream_buffer, text, fontSize, textX, textY) + hasImage := len(context.SignData.Appearance.Image) > 0 + shouldDisplayText := context.SignData.Appearance.ImageAsWatermark || !hasImage // Create the appearance XObject var appearance_buffer bytes.Buffer @@ -151,51 +139,44 @@ func (context *SignContext) createTextAppearance(rect [4]float64) ([]byte, error // Resources dictionary with font 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") - writeFormTypeAndLength(&appearance_buffer, appearance_stream_buffer.Len()) - - 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) - } - + // Create the appearance stream var appearance_stream_buffer bytes.Buffer - drawImage(&appearance_stream_buffer, rectWidth, rectHeight) + if hasImage { + drawImage(&appearance_stream_buffer, rectWidth, rectHeight) + } - // Create the appearance XObject - var appearance_buffer bytes.Buffer - writeAppearanceHeader(&appearance_buffer, rectWidth, rectHeight) - - // Resources dictionary with XObject - appearance_buffer.WriteString(" /Resources <<\n") - createImageResource(&appearance_buffer, imageObjectId) - appearance_buffer.WriteString(" >>\n") + if shouldDisplayText { + text := context.SignData.Signature.Info.Name + fontSize, textX, textY := computeTextSizeAndPosition(text, rectWidth, rectHeight) + drawText(&appearance_stream_buffer, text, fontSize, textX, textY) + } 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 } diff --git a/sign/sign_test.go b/sign/sign_test.go index 37d8313..b05daad 100644 --- a/sign/sign_test.go +++ b/sign/sign_test.go @@ -618,3 +618,55 @@ func TestSignPDFWithTwoImages(t *testing.T) { 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) +} diff --git a/testfiles/pdfsign-signature-watermark.jpg b/testfiles/pdfsign-signature-watermark.jpg new file mode 100644 index 0000000..460ca3a Binary files /dev/null and b/testfiles/pdfsign-signature-watermark.jpg differ diff --git a/testfiles/pdfsign-signature.jpg b/testfiles/pdfsign-signature.jpg index 2e0d6ea..1c033ee 100644 Binary files a/testfiles/pdfsign-signature.jpg and b/testfiles/pdfsign-signature.jpg differ