logo
Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Options
Go to last post Go to first unread
cbrit  
#1 Posted : Monday, January 4, 2021 1:51:06 PM(UTC)
cbrit

Rank: Member

Groups: Registered
Joined: 1/4/2021(UTC)
Posts: 10

Thanks: 1 times
I am a customer developing a WinForms application using Pdfium.NET SDK, and I am relatively new to low level PDF concepts.

I need to be able to add and remove signature images from a PDF page using the Viewer when the user clicks a signature field. I have accomplished the first part of this. I can make an image appear by inserting it into a PdfPage.PageObjects list as a PdfImageObject. I learned this method from the support forum:

Code:
imageObject = PdfImageObject.Create(document);
imageObject.SetBitmap(pdfBitmap);
imageObject.Matrix = new FS_MATRIX(signatureSize.Width, 0, 0, signatureSize.Height, x, y - (signatureSize.Height - baselineOffset));

page = document.Pages[pageIndex];

//// By placing the image at the beginning of the PageObjects collection, the signature appears
//// behind other elements to make it look more like a written signature.
//// Index is -1 because for some reason the InsertObject method is placing objects at the
//// index + 1 position.
page.PageObjects.InsertObject(imageObject, -1);
page.GenerateContent();


I also know that some of the methods I am using may be obsolete but this is the only way I could get the image to appear how I wanted.

Now my problem is removing the image when the signature field is clicked again. Because the PdfImageObject is an unrelated object to the signature field, I do not know how to determine which PdfImageObject in page.PageObjects to delete when there are multiple signatures on the same page. I have been able to remove the image by deleting the object when I know its index, but I need to be able to unsign a PDF that was just opened and has multiple signatures on the page which means I do not know the index ahead of time.

Is the PageObject approach the correct way to do this? If so, how do I determine which PdfImageObject is associated with a given Field? If not, what is the best way to associate a signature image with a field?

Edited by user Monday, January 4, 2021 1:52:47 PM(UTC)  | Reason: Not specified

Paul Rayman  
#2 Posted : Tuesday, January 5, 2021 1:07:12 AM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 961

Thanks: 3 times
Was thanked: 115 time(s) in 112 post(s)
Hi,
I see you have created ticket #053527, which was answered more than 15 days ago. Here's a quote:

The signature field is actually the widget annotation.
Instead of embedding the image in the content of the page, try embedding it in this annotation. Below is an example of how to do this.
However, it is more correct to embed not a picture, but page objects. For example, sign the file in Adobe Acrobat and examine the NormalApparance property in the below example. It will contain two PdfForm objects, which in turn contain PageObjects collection. It is not necessary to repeat exactly the same structure. You can insert your objects directly into the NormalAppearance.


Code:
var ctrl = signatureField.Controls[0];
var widget = new PdfWidgetAnnotation(page, ctrl.Dictionary);
var bmp = PdfBitmap.FromFile(@"e:\0\stamp.jpg");
var img = PdfImageObject.Create(pdfViewer1.Document, bmp, ctrl.BoundRect.left, ctrl.BoundRect.bottom);
widget.NormalAppearance.Add(img); //instead of adding an image add page objects like PdfTextObject and PdfPathObject
widget.GenerateAppearance(AppearanceStreamModes.Normal);
page.Dispose();
pdfViewer1.Invalidate();

Edited by user Tuesday, January 5, 2021 1:07:43 AM(UTC)  | Reason: Not specified

thanks 1 user thanked Paul Rayman for this useful post.
cbrit on 1/5/2021(UTC)
cbrit  
#3 Posted : Tuesday, January 5, 2021 12:13:23 PM(UTC)
cbrit

Rank: Member

Groups: Registered
Joined: 1/4/2021(UTC)
Posts: 10

Thanks: 1 times
Thank you, Paul, I'll give that a go.

As to the ticket, you must be referring to the email I sent to support. I never received a reply in my inbox so I'm not sure what happened.
cbrit  
#4 Posted : Tuesday, January 5, 2021 12:51:51 PM(UTC)
cbrit

Rank: Member

Groups: Registered
Joined: 1/4/2021(UTC)
Posts: 10

Thanks: 1 times
I'm pretty new to a lot of the concepts in PDF so maybe you can help me understand what's going on here.

When I use your method, the signature appears but it seems to shift the field down and appear above it. My program paints (WinForms) the field yellow so you can see how it is appearing below.

Unsigned (before click):
signatureunsigned.PNG (19kb) downloaded 0 time(s).

Signed (using above approach):
signaturebug.PNG (37kb) downloaded 1 time(s).

It also seems to be changing the size of the field in addition to shifting it down. The resizing may just be due to me not scaling the image down enough, but I'm not sure why the signature appears above the field instead of over it. I had to add this line to your code because the new widget's NormalAppearance property was null:

Code:

if (widget.NormalAppearance is null)
    widget.CreateEmptyAppearance(AppearanceStreamModes.Normal);


Any idea why that could be happening?

Edited by user Tuesday, January 5, 2021 1:07:23 PM(UTC)  | Reason: Not specified

Paul Rayman  
#5 Posted : Tuesday, January 5, 2021 6:33:35 PM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 961

Thanks: 3 times
Was thanked: 115 time(s) in 112 post(s)
On a PDF page the origin is in the left-bottom corner, and the y-axis direction is from bottom to top. Please refer this link for details
https://pdfium.patagames...tm#sec-coordinate-system

It looks like you have specified the upper-left corner of the field as the insertion coordinates, but you need to specify the bottom-left instead. In addition, the unit of measurement in PDF is a point (and resolution is 1/72 inches). And the size of your picture is set in pixels, so you do not fall into the border of the field. After inserting the image, you also need to change its size by applying a transformation matrix.

Code:

var img = PdfImageObject.Create(pdfViewer1.Document, bmp, 0, 0);
img.Matrix = new FS_MATRIX(ctrl.BoundRect.Width, 0, 0, ctrl.BoundRect.Height, ctrl.BoundRect.left, ctrl.BoundRect.bottom);


You have to consider the aspect ratio of your image of course.

Edited by user Wednesday, January 6, 2021 1:09:40 AM(UTC)  | Reason: Not specified

cbrit  
#6 Posted : Wednesday, January 6, 2021 11:04:54 AM(UTC)
cbrit

Rank: Member

Groups: Registered
Joined: 1/4/2021(UTC)
Posts: 10

Thanks: 1 times
The coordinates do not seem to be the issue, as I've tested your code in conjunction with my signature-placement code that use the PageObjectCollection and it still shifts the field around. Regardless if the Matrix or any coordinates given, the yellow box (representing the PdfField for signatures) gets shifted under the new NormalAppearance stream. What I need is for the NormalAppearance to be on top of that yellow box. As proof, here is the signature with correct placement, but the original PdfField field still gets shifted down a distance equal to the signature height:

signature2.png (34kb) downloaded 0 time(s).

I have confirmed that clicking the same spot on the viewer does not trigger the PlaceSignature method again, but clicking the yellow box in its new position does. So the actual field in the PDF is getting shifted down on the Y axis rather than being overlayed by the signature.

Here is my method for placing a signature with some unrelated code removed that sets the coordinates:

Code:

private void PlaceSignature(PdfField field, float x, float y, int pageIndex = 0)
{
    PdfDocument document = Viewer.Document;

    // Occasionally the pageIndex gets passed in as -1 because it was
    // decremented by the caller.
    if (pageIndex == -1)
        pageIndex = 0;

    Bitmap bitmap = new Bitmap(Signature);
    SignatureProcessor proc = new SignatureProcessor(bitmap);
    int baselineOffset = proc.GetBaseline();
    Size signatureSize = GetPageSignatureSize();

    //Create .Net bitmap from file and lock bits for copy into pdf image
    BitmapData data = bitmap.LockBits(new Rectangle(0, 0, Signature.Width, Signature.Height), ImageLockMode.ReadOnly,
        PixelFormat.Format32bppArgb);

    //Create PdfBitmap object from .Net bitmap
    PdfBitmap pdfBitmap = new PdfBitmap(
        data.Width,
        data.Height,
        BitmapFormats.FXDIB_Argb,
        data.Scan0,
        data.Stride);

    //Create pdf image object and then set PdfBitmap object into it.
    PdfImageObject imageObject;
    PdfPage page;

    baselineOffset = (int)(((float)signatureSize.Height / (float)bitmap.Height) * (float)baselineOffset);
    imageObject = PdfImageObject.Create(document);
    imageObject.SetBitmap(pdfBitmap);
    imageObject.Matrix = new FS_MATRIX(signatureSize.Width, 0, 0, signatureSize.Height, x, y - (signatureSize.Height - baselineOffset));
    IntPtr streamHandle = Pdfium.FPDFImageObj_GenerateStream(imageObject.Handle, document.Pages[0].Handle);
    page = document.Pages[pageIndex];

    var ctrl = field.Controls[0];
    var widget = new PdfWidgetAnnotation(page, ctrl.Dictionary);

    if (widget.NormalAppearance is null)
        widget.CreateEmptyAppearance(AppearanceStreamModes.Normal);

    widget.NormalAppearance.Add(imageObject); //instead of adding an image add page objects like PdfTextObject and PdfPathObject
    widget.GenerateAppearance(AppearanceStreamModes.Normal);

    page.GenerateContent();

    ...
}
Paul Rayman  
#7 Posted : Wednesday, January 6, 2021 9:40:53 PM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 961

Thanks: 3 times
Was thanked: 115 time(s) in 112 post(s)
Most likely the appearance stream contains objects that affect the calculation of your annotation's bounding box. You need to clear the NormalAppearance collection before adding your own objects.
Code:
widget.NormalAppearance.Clear();


Please try the following code with your document as is, without major modifications. It works well on all of the documents I've tested on my part.
Code:

var signatureField = ...
var page = ...
var doc = pdfViewer1.Document;

//Convert field to widget annotation
var ctrl = signatureField.Controls[0];
var widget = new PdfWidgetAnnotation(page, ctrl.Dictionary);
//Load bitmap
var bmp = PdfBitmap.FromFile(@"e:\0\stamp.jpg");
//Fit bitmap
double height = ctrl.BoundRect.Height;
double width = (double)bmp.Width * height / bmp.Height;
if (width > ctrl.BoundRect.Width)
{
	width = ctrl.BoundRect.Width;
	height = (double)bmp.Height * width / bmp.Width;
}
//align bitmap
double xPos = ctrl.BoundRect.left + (ctrl.BoundRect.Width - width) / 2;
double yPos = ctrl.BoundRect.bottom + (ctrl.BoundRect.Height - height) / 2;

//Create an image object
var img = PdfImageObject.Create(doc, bmp, 0, 0);
//move and reasize the image object
img.Matrix = new FS_MATRIX(width, 0, 0, height, xPos, yPos);
//Create border around field;
var border = PdfPathObject.Create(FillModes.None, true);
border.StrokeColor = FS_COLOR.Black;
border.FillColor = FS_COLOR.Transparent;
border.LineWidth = 2.0f;
border.Path.AppendRect(ctrl.BoundRect);

//Prepare appearance stream
if (widget.NormalAppearance == null)
	widget.CreateEmptyAppearance(AppearanceStreamModes.Normal);
else
	widget.NormalAppearance.Clear(); //Clear all ald objects from appearance stream
			
//Insert newly create page objects into appearance stream;
widget.NormalAppearance.Add(img); 
widget.NormalAppearance.Add(border);
//Generate Appearance stream
widget.GenerateAppearance(AppearanceStreamModes.Normal);
			
//Update Viewer
page.Dispose();
pdfViewer1.Invalidate();


The result should be like this. Purple is the highlight of the field.
scr1.png (54kb) downloaded 2 time(s).

If the code does not work properly with your documet, please provide it for analisys.
Thank you.

Edited by user Wednesday, January 6, 2021 11:43:04 PM(UTC)  | Reason: Not specified

cbrit  
#8 Posted : Thursday, January 14, 2021 2:53:34 PM(UTC)
cbrit

Rank: Member

Groups: Registered
Joined: 1/4/2021(UTC)
Posts: 10

Thanks: 1 times
Okay, trying your code as is resulted in the signature image upside down, the border around the correct spot, and the field (painted yellow) shifted down.

Capture.PNG (46kb) downloaded 0 time(s).
Paul Rayman  
#9 Posted : Thursday, January 14, 2021 4:03:09 PM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 961

Thanks: 3 times
Was thanked: 115 time(s) in 112 post(s)
It seems the height in the matrix bellow is negative. Please check it.
Code:
img.Matrix = new FS_MATRIX(width, 0, 0, height, xPos, yPos);


Please contact support@patagames.com with the document and the image

Edited by user Thursday, January 14, 2021 4:08:10 PM(UTC)  | Reason: Not specified

cbrit  
#10 Posted : Thursday, January 14, 2021 5:58:32 PM(UTC)
cbrit

Rank: Member

Groups: Registered
Joined: 1/4/2021(UTC)
Posts: 10

Thanks: 1 times
Yes it is negative. I actually noticed that a few days ago and forgot. Putting the height in Math.Abs() flips the signature back to normal.

I've emailed the PDF and signature image.

Edited by user Thursday, January 14, 2021 5:59:51 PM(UTC)  | Reason: Not specified

Paul Rayman  
#11 Posted : Thursday, January 14, 2021 7:21:36 PM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 961

Thanks: 3 times
Was thanked: 115 time(s) in 112 post(s)
The code I provided earlier assumes that the X and Y coordinates of the bottom-left corner must be less than the top-right corner. Both the height and width must not be negative. In your document, this principle is violated along the Y axis. Therefore, the calculation algorithm needs to be modified.
Just normalize the field's bounding box to match the page coordinate system. In the below example, I did it for the height. This works well for your document, signature image and code I provided earlier.

Code:

//Fit bitmap
double height = Math.Abs(ctrl.BoundRect.Height);
double width = (double)bmp.Width * height / bmp.Height;
if (width > ctrl.BoundRect.Width)
{
	width = ctrl.BoundRect.Width;
	height = (double)bmp.Height * width / bmp.Width;
}
//align bitmap
double xPos = ctrl.BoundRect.left + (ctrl.BoundRect.Width - width) / 2;
double yPos = Math.Min(ctrl.BoundRect.bottom, ctrl.BoundRect.top)+ (Math.Abs(ctrl.BoundRect.Height) - height) / 2;

Just in case, you can do exactly the same with width.

You can know more about PDF's coordinate systems here
https://pdfium.patagames...tm#sec-coordinate-system
and for the viewer here
https://pdfium.patagames...er_CoordinateSystems.htm

Edited by user Thursday, January 14, 2021 7:23:20 PM(UTC)  | Reason: Not specified

cbrit  
#12 Posted : Tuesday, January 19, 2021 12:57:06 PM(UTC)
cbrit

Rank: Member

Groups: Registered
Joined: 1/4/2021(UTC)
Posts: 10

Thanks: 1 times
Okay things are working now. Thank you for your solution.
Users browsing this topic
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.