DollarChills DollarChills - 5 months ago 17
JSON Question

Render JSON to image in Prawn PDF

I'm using prawn pdf in conjunction with signature-pad gem in my rails 3.2 app and i'm having troubles converting the JSON data to an image to render in the pdf.

I have the signature-pad on completion throw the JSON data into the table and it looks like this.

JSON

[{"lx":29,"ly":18,"mx":29,"my":17},{"lx":29,"ly":19,"mx":29,"my":18},{"lx":29,"ly":24,"mx":29,"my":19},{"lx":29,"ly":27,"mx":29,"my":24},{"lx":29,"ly":30,"mx":29,"my":27},{"lx":29,"ly":32,"mx":29,"my":30},{"lx":32,"ly":32,"mx":29,"my":32},{"lx":33,"ly":32,"mx":32,"my":32},{"lx":35,"ly":31,"mx":33,"my":32},{"lx":39,"ly":24,"mx":35,"my":31},{"lx":42,"ly":16,"mx":39,"my":24},{"lx":48,"ly":7,"mx":42,"my":16},{"lx":51,"ly":2,"mx":48,"my":7},{"lx":54,"ly":-3,"mx":51,"my":2},{"lx":58,"ly":2,"mx":58,"my":1},{"lx":59,"ly":9,"mx":58,"my":2},{"lx":60,"ly":18,"mx":59,"my":9},{"lx":60,"ly":27,"mx":60,"my":18},{"lx":60,"ly":38,"mx":60,"my":27},{"lx":55,"ly":45,"mx":60,"my":38},{"lx":49,"ly":51,"mx":55,"my":45},{"lx":45,"ly":54,"mx":49,"my":51},{"lx":39,"ly":57,"mx":45,"my":54},{"lx":35,"ly":51,"mx":35,"my":50},{"lx":43,"ly":45,"mx":35,"my":51},{"lx":54,"ly":39,"mx":43,"my":45},{"lx":70,"ly":32,"mx":54,"my":39},{"lx":81,"ly":28,"mx":70,"my":32},{"lx":96,"ly":25,"mx":81,"my":28},{"lx":111,"ly":23,"mx":96,"my":25},{"lx":119,"ly":23,"mx":111,"my":23},{"lx":126,"ly":23,"mx":119,"my":23},{"lx":129,"ly":23,"mx":126,"my":23},{"lx":130,"ly":23,"mx":129,"my":23},{"lx":128,"ly":24,"mx":130,"my":23},{"lx":117,"ly":25,"mx":128,"my":24},{"lx":105,"ly":27,"mx":117,"my":25},{"lx":96,"ly":29,"mx":105,"my":27},{"lx":89,"ly":30,"mx":96,"my":29},{"lx":85,"ly":30,"mx":89,"my":30},{"lx":84,"ly":31,"mx":85,"my":30},{"lx":87,"ly":32,"mx":84,"my":31},{"lx":101,"ly":36,"mx":87,"my":32},{"lx":118,"ly":39,"mx":101,"my":36},{"lx":136,"ly":42,"mx":118,"my":39},{"lx":151,"ly":43,"mx":136,"my":42},{"lx":165,"ly":43,"mx":151,"my":43},{"lx":171,"ly":40,"mx":165,"my":43},{"lx":175,"ly":37,"mx":171,"my":40},{"lx":177,"ly":34,"mx":175,"my":37},{"lx":178,"ly":32,"mx":177,"my":34},{"lx":178,"ly":31,"mx":178,"my":32}]


I have seen this, but i'm not sure how best to implement it?

Controller

def show
@form = Form.find(params[:id])

respond_to do |format|
format.html
format.pdf do
pdf = FormPdf.new(@form)
send_data pdf.render, filename: "form - #{@form.title}", type: "application/pdf", disposition: "inline"
end
end
end


Prawn PDF

# encoding: utf-8
class FormPdf < Prawn::Document

def initialize(form)
super()
@form = form
all
end

def all
text "Form text here"
move_down 20
signature_data = [[@form.signature, "Signature of person"]]
table(signature_data, position: :center) do
cells.style(:border_width => 0)
end
end

Answer

Please see: https://github.com/nqngo/rails-signature-pad-prawns-demo

The signature in question image:

SignaturePad Signature

Luckily I did something similar at my workplace, so I will walk you through the whole thought process. Assume we store the data in @sig and setup a signature box dimension :

signature = '[{"lx":29,"ly":18,"mx":29,"my":17},{"lx":29,"ly":19,"mx":29,"my":18},{"lx":29,"ly":24,"mx":29,"my":19},{"lx":29,"ly":27,"mx":29,"my":24},{"lx":29,"ly":30,"mx":29,"my":27},{"lx":29,"ly":32,"mx":29,"my":30},{"lx":32,"ly":32,"mx":29,"my":32},{"lx":33,"ly":32,"mx":32,"my":32},{"lx":35,"ly":31,"mx":33,"my":32},{"lx":39,"ly":24,"mx":35,"my":31},{"lx":42,"ly":16,"mx":39,"my":24},{"lx":48,"ly":7,"mx":42,"my":16},{"lx":51,"ly":2,"mx":48,"my":7},{"lx":54,"ly":-3,"mx":51,"my":2},{"lx":58,"ly":2,"mx":58,"my":1},{"lx":59,"ly":9,"mx":58,"my":2},{"lx":60,"ly":18,"mx":59,"my":9},{"lx":60,"ly":27,"mx":60,"my":18},{"lx":60,"ly":38,"mx":60,"my":27},{"lx":55,"ly":45,"mx":60,"my":38},{"lx":49,"ly":51,"mx":55,"my":45},{"lx":45,"ly":54,"mx":49,"my":51},{"lx":39,"ly":57,"mx":45,"my":54},{"lx":35,"ly":51,"mx":35,"my":50},{"lx":43,"ly":45,"mx":35,"my":51},{"lx":54,"ly":39,"mx":43,"my":45},{"lx":70,"ly":32,"mx":54,"my":39},{"lx":81,"ly":28,"mx":70,"my":32},{"lx":96,"ly":25,"mx":81,"my":28},{"lx":111,"ly":23,"mx":96,"my":25},{"lx":119,"ly":23,"mx":111,"my":23},{"lx":126,"ly":23,"mx":119,"my":23},{"lx":129,"ly":23,"mx":126,"my":23},{"lx":130,"ly":23,"mx":129,"my":23},{"lx":128,"ly":24,"mx":130,"my":23},{"lx":117,"ly":25,"mx":128,"my":24},{"lx":105,"ly":27,"mx":117,"my":25},{"lx":96,"ly":29,"mx":105,"my":27},{"lx":89,"ly":30,"mx":96,"my":29},{"lx":85,"ly":30,"mx":89,"my":30},{"lx":84,"ly":31,"mx":85,"my":30},{"lx":87,"ly":32,"mx":84,"my":31},{"lx":101,"ly":36,"mx":87,"my":32},{"lx":118,"ly":39,"mx":101,"my":36},{"lx":136,"ly":42,"mx":118,"my":39},{"lx":151,"ly":43,"mx":136,"my":42},{"lx":165,"ly":43,"mx":151,"my":43},{"lx":171,"ly":40,"mx":165,"my":43},{"lx":175,"ly":37,"mx":171,"my":40},{"lx":177,"ly":34,"mx":175,"my":37},{"lx":178,"ly":32,"mx":177,"my":34},{"lx":178,"ly":31,"mx":178,"my":32}]'
@sig = JSON.parse signature
sigpad_height = 55
sigpad_width = 198

You then create a bounding_box at the cursor point and draw the line from the JSON data. The reason why we have to use a bounding_box is to set the coordinate of the line origin. Otherwise, the line function will use the bottom left of the page as the origin point:

bounding_box([0, cursor], width: sigpad_width, height: sigpad_height) do
  stroke_bounds
  @sig.each do |e|
    stroke { line [e["lx"], e["ly"]],
                  [e["mx"], e["my"]] }
  end
end

The resulting PDF will be:

PDF 1

Notice how the image is upside down, this is due to the different point of axis-direction between PDF and canvas. In PDF the origin point is bottom-left, where in canvas, the origin point is top-left. What we need to do is convert the coordinate from canvas style to PDF style. A basic transformation is to flip it over the x-axis, and translate it back by sigpad_height. The line code is now:

    stroke { line [e["lx"], sigpad_height - e["ly"]],
                  [e["mx"], sigpad_height - e["my"]] }

The end result will be:

PDF 2

If you do not want the border around the bounding_box removes the stroke_bounds. A couple of gotchas you need to be careful about:

  • SignaturePad captures data coordinates outside the HTML signature pad dimension, hence why you see the rendered PDF signature have overdrawn lines outside its bounding_box.
  • The above transformation assumes the signature height of the bounding box and the HTML pad is the same. If different, you will need to add some offset to translate the signature back into the correct position due to the flipping over the x-axis.
  • Depends on how you store your JSON in the database. You might be able to access the coordinate as a :hash. Hence e["lx"] will yield nil, you must use e[:lx] instead.
Comments