gettalong / hexapdf

Versatile PDF creation and manipulation for Ruby
https://hexapdf.gettalong.org
Other
1.21k stars 69 forks source link

rotate image signature in acro_form #282

Closed WSPDV closed 6 months ago

WSPDV commented 7 months ago

hi, mas @gettalong I am stuck on the rotation image using the signature field. There is no error, but the signature image does not appear in the result.

        field = @doc.acro_form(create: true) .create_signature_field(SecureRandom.uuid)

        widget = field.create_widget(@doc.pages[0], defaults: false, Rect: [0, 0, 364, 146], rotation: 90)
        image = widget.create_appearance
                      .canvas
                      .xobject(@image_path, at: [0, 0], width: 364, height: 146)
        image[:Resources] = {}
gettalong commented 7 months ago

Please have a look at https://hexapdf.gettalong.org/documentation/api/HexaPDF/Type/AcroForm/Field/index.html#method-i-create_widget - the :rotation key is not a valid dictionary entry for a widget annotation.

If you want to rotate the image, you should use the rotated image size in the Rect value and use the canvas.rotate command before placing the image.

WSPDV commented 7 months ago
 widget = field.create_widget(@page, defaults: false, Rect: [0, 0, 146, 364])
        image = widget.create_appearance
                      .canvas.rotate(90)
                      .xobject(@image_path, at: [0, 0], width: 146, height: 364)
        image[:Resources] = {}
        field

rotate like this @gettalong ?

the Rect is correct but the image is still not appear

image
gettalong commented 7 months ago

Can you please provide a complete sample script that shows the problem?

FYI: The rect you specify is in the lower-left corner of the page, is that correct?

WSPDV commented 7 months ago

this is a picture of signature

image

and this is a full code

class Trial4
      require 'hexapdf'
      require HexaPDF.data_dir + '/cert/demo_cert.rb'

      def initialize(pdf_path)
        @signed_pdf_path = "tmp/img_signed_#{Time.now.to_i}.pdf"
        @pdf_path = pdf_path
        @doc = HexaPDF::Document.open(pdf_path)
        @image_path = 'spec/fixtures/files/signature.png'
      end

      def call
        @doc.sign(@signed_pdf_path, reason: 'Some reason',
                                    location: 'Hatimu',
                                    contact_info: 'wspdv@tampan.coeg',
                                    signature: field_signature,
                                    certificate: HexaPDF.demo_cert.cert,
                                    key: HexaPDF.demo_cert.key,
                                    certificate_chain: [HexaPDF.demo_cert.sub_ca,
                                                        HexaPDF.demo_cert.root_ca])
      end

      def image
        @image ||= Magick::Image.read(@image_path).first
      end

      def field_signature
        field = @doc.acro_form(create: true)
                    .create_signature_field('AQ')

        widget = field.create_widget(@doc.pages[0], defaults: false,
                                                    Rect: [0, 0, image.columns, image.rows])
        img_sig = widget.create_appearance
                        .canvas
                        .xobject(@image_path, at: [0, 0], width: image.columns, height: image.rows)
        img_sig[:Resources] = {}
        field
      end
    end

and the result like below (this coordinate is expected because I set the coordinate in 0, 0 in the document that has 180 rotate). see the picture of the signature (the picture is rotated, different from the original picture).

image

i want to set the rotation image (the position image kept like an original picture). then i wrote code like this (override the method field_signature)

      def field_signature
        field = @doc.acro_form(create: true)
                    .create_signature_field('AQ')

        widget = field.create_widget(@doc.pages[0], defaults: false,
                                                    Rect: [0, 0, image.columns, image.rows])
        img_sig = widget.create_appearance
                        .canvas.rotate(180)
                        .xobject(@image_path, at: [0, 0], width: image.columns, height: image.rows)
        img_sig[:Resources] = {}
        field
      end

then the result is image does not appear

image
gettalong commented 7 months ago

So the problem here is that once the canvas is rotated, everything is upside down, including the coordinate system. So the positive x direction will now go to the left and the positive y direction will now go down. And note that the origin did not change its location, it is still at the bottom-left corner of the rectangle. To remedy that you need to move the origin to the upper-right corner.

Here is an example:

require 'hexapdf'

doc = HexaPDF::Document.new
canvas = doc.pages.add([0, 0, 200, 200]).canvas
canvas.save_graphics_state do
  canvas.stroke_color("grey").line_width(0.5).
    line(100, 0, -100, 0).line(0, -100, 0, 100).stroke
end
canvas.rotate(180) do
  canvas.rectangle(0, 0, 80, 40).stroke
end
canvas.rotate(180) do
  canvas.translate(-200, -200) # move origin to the upper-right corner
  canvas.stroke_color("hp-blue").
    rectangle(0, 0, 80, 40).stroke
end
doc.write('rotation.pdf')

Here is the result: image

You can only see the second rectangle, the blue one, in the image because the other one, the first one, is out of view below the bottom-left corner.

In your case you would need to use .rotate(180).translate(image.columns, image.rows).

WSPDV commented 7 months ago

if using canvas means can break the PDF content, I already have a code using canvas, and it works. so I need it for the signature field only.

i already added the code that you suggested, but the image still not showing

def field_signature
        field = @doc.acro_form(create: true)
                    .create_signature_field('AQ')

        widget = field.create_widget(@doc.pages[0], defaults: false,
                                                    Rect: [0, 0, image.rows, image.columns])
        img_sig = widget.create_appearance
                        .canvas
                        .rotate(270).translate(image.rows, image.columns)
                        .xobject(@image_path, at: [0, 0], width: image.rows, height: image.columns)
        img_sig[:Resources] = {}
        field
      end

if without .rotate(270).translate(image.rows, image.columns)

image showing, but the rotation for the image is not true.

image

I need the image still like the original

image

like this

image
gettalong commented 7 months ago

If you rotate by 270 degrees, you have to adjust the translation arguments as well as the width/height arguments for the #xobject call.

You might also need to use the :no_rotate flag, i.e. widget.flag(:no_rotate) to avoid the viewer rotating the annotation if the page has the /Rotate key set.

WSPDV commented 7 months ago

translation argument height and width are correct when I click on the signature panel, it will show the box where the signature is placed (only the dots in frame) like the picture below (in the bottom right). I assumed this is already correct.

image

for this is code -> widget.flag(:no_rotate) where is the right placed ? I already added widget.flag(:no_rotate) after create_widget:

        field = @doc.acro_form(create: true)
                    .create_signature_field('AQ')

        widget = field.create_widget(@doc.pages[0], defaults: false, Rect: [0, 0, 512, 908], flag: :no_rotate)
        widget.flag(:no_rotate)

        image = widget.create_appearance
                      .canvas
                      .xobject(image_signature, at: [0, 0])

        widget.flag(:no_rotate)
        field

but the image is still now shown. I also tried to larger the width and height of the signature field (to be full of pages), and try rearrange this section -> .xobject(image_signature, at: [100, 100]) but still not appear

gettalong commented 7 months ago

Here is a self-contained example based on your code:

require 'hexapdf'
require HexaPDF.data_dir + '/cert/demo_cert.rb'

doc = HexaPDF::Document.new
doc.pages.add.canvas.
  font("Helvetica", size: 10).
  text("Top-left", at: [20, 800])
doc.pages[0].rotate(180)

image = doc.add({Type: :XObject, Subtype: :Form, BBox: [0, 0, 200, 100]})
image.canvas.font("Helvetica", size: 10).text('Signature', at: [20, 20])

field = doc.acro_form(create: true).create_signature_field('AQ')
widget = field.create_widget(doc.pages[0], defaults: false,
                                            Rect: [0, 0, image.width, image.height])
img_sig = widget.create_appearance.
                canvas.
                save_graphics_state.fill_color("ffddff").rectangle(0, 0, image.width, image.height).fill.restore_graphic_state.
                xobject(image, at: [0, 0], width: image.width, height: image.height)
img_sig[:Resources] = {}

doc.sign('signed.pdf', reason: 'Some reason',
                            location: 'Hatimu',
                            contact_info: 'wspdv@tampan.coeg',
                            signature: field,
                            certificate: HexaPDF.demo_cert.cert,
                            key: HexaPDF.demo_cert.key,
                            certificate_chain: [HexaPDF.demo_cert.sub_ca,
                                                HexaPDF.demo_cert.root_ca])

resulting in this:

image

WSPDV commented 7 months ago

Thanks, mas @gettalong

the simple choice is using page.rotate(0, flatten: true) but if the document already has a certificate, it will break the certificate before, because it will modify the content/page.

i already tried that and I also tried use rotate(180).translate(-image.width, -image.height). the second is work (not breaking the certificate before if the document has a certificate). Could you let me know the rules/formula for the value from .translate().?

gettalong commented 7 months ago

The rules are just basic geometry. You can visualize it:

require 'hexapdf'

doc = HexaPDF::Document.new
canvas = doc.pages.add.canvas
canvas.font('Helvetica', size: 8)
iw, ih = 100, 60
[0, 90, 180, 270].each_with_index do |rotation, index|
  canvas.save_graphics_state do
    # Simulate page rotation
    canvas.translate(220, 700 - index * 150)
    canvas.rotate(rotation)
    canvas.rectangle(0, 0, iw, ih).stroke
    canvas.text("Text", at: [5, 5])
    canvas.fill_color("hp-blue").circle(0, 0, 2).fill

    case rotation
    when 0 then #nothing
    when 90 then canvas.rotate(-90).translate(-ih, 0)
    when 180 then canvas.rotate(-180).translate(-iw, -ih)
    when 270 then canvas.rotate(-270).translate(0, -iw)
    end
    canvas.fill_color("hp-orange").circle(0, 0, 2).fill
    canvas.stroke_color("hp-teal").rectangle(0, 0, iw, ih).stroke
    canvas.text("Text", at: [5, 5])
  end
end
doc.write('visualize.pdf')

The result is this: visualize.pdf

WSPDV commented 6 months ago

Your answer is very helpful. I will try in my local. Hopefully, it can work. I'll close this discussion. Thank you mas @gettalong 🙏🏿