Rails: prawn PDF to rmagick output - ruby-on-rails

What's wrong with this method? I'm totally stumped. I keep getting this error on the img.write(res) line:
ArgumentImage no images in this image list
Here's the controller method:
def convert_to_image
#document = Document.find(params[:document_id])
img = Magick::ImageList.new
pdf = Prawn::Document.generate("tmp.pdf", :margin => 0, :page_size => [398, 398]) do |p|
res = #document.post_pdf(p)
img.write(res)
send_data img
end
end
The post_pdf method mentioned above looks like this:
def post_pdf(p)
size = 398
p.image self.user.logo.path, :width => size
p.bounding_box([30, 490], :width => size) do
p.fill_color self.user.colour1
p.pad_bottom(10) { p.text self.title, :size => 28, :style => :bold }
p.fill_color '#444444'
end
self.components.each do |component|
p.image component.image_newsletter.path, :width => size
p.fill_color self.user.colour1
p.fill_rectangle([0, 20], size, 20)
end
end

SOLVED. It needed:
1.) Ghostscript.
2.) A rewritten method:
def create_pdf_image
#document = Document.find(params[:document_id])
pdf = Prawn::Document.new
temp = "#{#document.user.name.downcase.parameterize.underscore}-#{#document.id}"
#document.post_pdf(pdf)
pdf.render_file("#{temp}.pdf")
image = Magick::ImageList.new("#{temp}.pdf")
image.write("#{temp}.png") { self.quality = 100 }
send_file "#{temp}.png"
end
Might help someone. Apparently rmagick needs ghostscript. My method just wasn't playing either, although the post_pdf method remains unchanged.

Related

Prawn PDF and muliplte records

I'm looking to extract all records to a single pdf using Prawn PDF and rails 4.2.
Currently I have them generating by id for individual pdf's and works well.
Show
def show
#agreement = Agreement.find(params[:id])
respond_to do |format|
format.html
format.pdf do
pdf = AgreementPdf.new(#agreement)
send_data pdf.render, filename: "Agreement - #{#agreement.entity}", type: "application/pdf", disposition: "inline"
end
end
end
Index in table
<% #agreements.each do |agreement| %>
<tr>
<td><%= agreement.entity %></td>
<td><%= link_to 'Download', agreement_path(agreement.id, format: 'pdf'), :target => "_blank" %></td>
</tr>
<% end %>
First, you have to add a route with the path of the new method:
get '/agreements/all_records' => 'agreements#all_records', :as => :agreement_all_records
Then call the methods with in the view:
<td><%= link_to 'Download All Records', agreement_all_records_path(), :target => "_blank" %></td>
It will look something like this in the controllers:
def all_records
#agreements = Agreement.all
pdf = AgreementsPdf.new(#agreements)
send_data pdf.render,filename:'agreements.pdf',type:'application/pdf', disposition: 'inline'
end
And the Report may look like this ( assuming that agreement model has id,name fields ):
require 'prawn/table'
class AgreementsPdf < PdfReport
TABLE_WIDTHS = [100, 100]
PAGE_MARGIN = [40, 40, 40, 40]
def initialize(agreements=[])
super(:page_size => "LEGAL", margin: PAGE_MARGIN, :page_layout => :landscape)
#agreements = agreements
display_table
end
private
def display_table
if table_data.empty?
text "None data"
else
table(table_data,column_widths: TABLE_WIDTHS, :cell_style => { size: 10 } )
end
end
def table_data
#table_data ||= #agreements.map { |e| [e.id, e.name] }
end
end
Hope this will give you an idea

Ruby Prawn PDF Prevent Header Space From Populating Next Page

I am using Prawn to generate a pdf receipt. Ideally I would like the following format:
Header
Customer Info
Items for order
Total
However when it goes to the second page there is a huge gap on the top from the Header spacing. How can I prevent it from adding the extra spacing on top?
Here is the code generating the receipt below (Sorry there is a lot of code):
class OrderPdf < Prawn::Document
def initialize(order)
super()
#order = order
bounding_box([10, 700], :width => 500) do
logo
end
message
date_shipped
order_number
bounding_box([0, -70], :width => 2000) do
bounding_box([65, 560], :width => 200) do
shipping_address
end
bounding_box([320, 560], :width => 200) do
payment_method
end
bounding_box([25, 450], :width => 500, :position => :center) do
line_items
end
end
end
def logo
image "#{Rails.root}/app/assets/images/letterhead-header.jpg" , :scale => 0.25, :position => :center
end
def message
move_down 20
text "Thanks for shopping with Dwellers. If you have any questions, please email support#dwellers.com"
end
def date_shipped
move_down 10
data = [["Shipment on #{#order.date_to_ship.strftime("%B %d, %Y")}",
"Items: #{#order.order_items.count}",
"Total: #{helpers.humanized_money(Money.new((#order.total_cents), "USD"), {:no_cents_if_whole => false, :symbol => true})}"]]
table data do
cells.style(:borders => [], :background_color => "DDDDDD", :valign => :center)
column(0).font_style = :bold
column(0).width = 400
column(0).size = 18
end
end
def order_number
move_down 10
# order_id = Digest::MD5.hexdigest "#{#order.id}#{#order.date_to_ship}"
text "Order\# #{#order.number}", size: 14, style: :bold
end
def shipping_address
move_down 10
text "Shipping address:", size: 14, style: :bold
indent(20) do
text "#{#order.user.name} #{#order.user.name_last}"
address_fields = #order.user.address.split(",")
text "#{address_fields[0]}"
if !address_fields[1].empty?
text "#{address_fields[1]}"
end
text "#{address_fields[2]}, #{address_fields[3]} #{address_fields[4]}"
end
end
def payment_method
move_down 10
#card = #order.user.card
text "Payment Information:", size: 14, style: :bold
if #card != nil
indent(20) do
text "#{#card[:card_type].capitalize} **** **** **** #{#card[:last_four]}"
billing_address
end
end
end
def billing_address
move_down 10
#billing_address = #order.user.billing_address.split
address_fields = #order.user.billing_address.split(",")
text "#{#card[:name]}"
text "#{address_fields[0]}"
if !address_fields[1].empty?
text "#{address_fields[1]}"
end
text "#{address_fields[2]}, #{address_fields[3]} #{address_fields[4]}"
end
def line_items
move_down 10
text "In this shipment:", size: 14, style: :bold
table line_item_rows + summary do
row(0).font_style = :bold
columns(1..3).align = :left
self.header = true
cells.style(:borders => [])
column(0).width = 380
row(row_heights().length-5).borders = [:bottom]
row(row_heights().length-2..row_heights().length-1).column(1).font_style = :bold
row(row_heights().length-1).column(1..2).size = 14
end
end
def line_item_rows
[["Item names", "Qty", "Price"]] +
#order.order_items.map do |order_item|
product = order_item.item.name + " - " + order_item.item.description
quantity = " x " +order_item.quantity.to_s
[product, quantity, helpers.humanized_money(Money.new((order_item.price_paid_cents), "USD"), {:no_cents_if_whole => false, :symbol => true})]
end
end
def summary
# move_down 15
data = [["", "", ""],
["Subtotal:", "Shipping:", "Total:"],
[helpers.humanized_money(Money.new((#order.subtotal_cents), "USD"), {:no_cents_if_whole => false, :symbol => true}),
helpers.humanized_money(Money.new((#order.shipping_fee_cents), "USD"), {:no_cents_if_whole => false, :symbol => true}),
helpers.humanized_money(Money.new(#order.total_cents, "USD"), {:no_cents_if_whole => false, :symbol => true})]].transpose
end
# Need this to get access to View Helpers, such as number_to_currency
def helpers
ActionController::Base.helpers
end
end
Below is how the receipt looks like.
For your flowing text that is beneath the header, you can use a span rather than a bounding_box, and it will start at the top of the page. There are some examples in the manual, just search for span in there:
http://prawn.majesticseacreature.com/manual.pdf

Rails: passing objects between model and controller

Is there a best practice for managing objects between model and controller in Rails? I've got code that clumsily hands files from controller to model and back like so:
In document_controller.rb:
tmp_file = Tempfile.new(Digest::MD5.hexdigest(rand(12).to_s))
pdf = Prawn::Document.generate(tmp_file.path(), :page_size => "A4", :skip_page_creation => true) do |posting|
#document.process(posting)
send_data posting.render, :filename => "#{#document.id}", :type => "application/pdf"
end
And in document.rb:
def process(pdf)
pdf.start_new_page
pdf.image self.user.logo.path, :width => pdf.bounds.width
pdf.fill_color '#444444'
pdf.image self.component.image_snapshot.path, :width => pdf.bounds.width
pdf.move_down 20
pdf.indent 20 do
pdf.text self.component.name, :size => 24
end
box_start = pdf.cursor
pdf.reflow_column_box([20, box_start-15], :width => pdf.bounds.width-20) do
format_for_prawn(pdf, self.component.body, self.user.colour1)
end
end
How can I rewrite this code so I don't have to clumsily pass the pdf/posting object around? It feels very un-Rails-like.
Thanks for your input.
EDIT: worse example.
Again in the controller:
tempfile = Magick::Image.read(#document.component.image_newsletter.path).first
overlay = Magick::Image.read(#document.user.logo.path).first.resize_to_fit(tempfile.columns)
rec = Magick::Draw.new
rec.stroke = "##{#document.user.colour1}"
rec.fill = "##{#document.user.colour1}"
rec.rectangle 0, 0, tempfile.columns, 5
lank = tempfile.extent(tempfile.columns, tempfile.rows+overlay.rows, 0 ,0)
final = lank.composite(overlay, Magick::SouthGravity, 0, 0, Magick::OverCompositeOp)
rec.draw(final)
send_data final.to_blob, :filename => "#{#document.user.name}-#{#document.id}.jpg", :type => "image/jpg"
This is in the controller because otherwise send_data can't access final. Any help?
Thanks again.
EDIT
You can create extension methods for inbuilt Class by
#lib/ext/prawn/document.rb
class Prawn::Document
def process(pdf)
pdf = self if pdf.blank?
pdf.start_new_page
pdf.image self.user.logo.path, :width => pdf.bounds.width
pdf.fill_color '#444444'
pdf.image self.component.image_snapshot.path, :width => pdf.bounds.width
pdf.move_down 20
pdf.indent 20 do
pdf.text self.component.name, :size => 24
end
box_start = pdf.cursor
pdf.reflow_column_box([20, box_start-15], :width => pdf.bounds.width-20) do
format_for_prawn(pdf, self.component.body, self.user.colour1)
end
end
end
And in your controller
tmp_file = Tempfile.new(Digest::MD5.hexdigest(rand(12).to_s))
pdf = Prawn::Document.generate(tmp_file.path(), :page_size => "A4", :skip_page_creation => true) do |posting|
posting.process
send_data posting.render, :filename => "#{#document.id}", :type => "application/pdf"
end
To load this class, you will need to require it in your config/application.rb file or in an initializer.
require 'ext/prawn/document'
To get that messy controller code into the model I've done this:
def process_media
temp = "#{::Rails.root}/public/temporary/#{temp_file_name}"
tempfile = Magick::Image.read(self.component.image_newsletter.path).first
overlay = Magick::Image.read(self.user.logo.path).first.resize_to_fit(tempfile.columns)
rec = Magick::Draw.new
rec.stroke = "##{self.user.colour1}"
rec.fill = "##{self.user.colour1}"
rec.rectangle 0, 0, tempfile.columns, 5
lank = tempfile.extent(tempfile.columns, tempfile.rows+overlay.rows, 0 ,0)
final = lank.composite(overlay, Magick::SouthGravity, 0, 0, Magick::OverCompositeOp)
rec.draw(final)
final.to_blob
final.write("#{temp}.jpg")
end
And I'm sending the actual file with send_file from the controller. Hope that helps someone.

generating email address images or something which can't be easily scraped

I'm looking for a better Text-to-image solution for my Rails project to replace my current method which is generating a png using ImageMagick every time a new record is created or updated.
I want to prevent a mass scraping of data and abuse of email addresses provided. I'm wondering if there is an API to generate the text or use of javascript, or SVG, etc. Anything short of Flash.
I'm looking for a better solution than my current method:
def create_new_email_image
if email_changed?
path_to_images = '/images/emails'
puts "Connecting to AWS..."
config = YAML.load(File.open("#{RAILS_ROOT}/config/s3_credentials.yml"))[RAILS_ENV]
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
puts "Finding S3 bucket..."
begin
bucket = AWS::S3::Bucket.find config['bucket_name']
rescue AWS::S3::NoSuchBucket
AWS::S3::Bucket.create config['bucket_name']
bucket = AWS::S3::Bucket.find config['bucket_name']
end
images_path = "#{RAILS_ROOT}/tmp/"
file_name = "#{id}.png"
#file_name = "5056.png"
file_path = images_path + "/"+ file_name
File.delete file_path if File.exists? file_path
img = Magick::Image.new(400, 22) { self.background_color = 'transparent' }
img.format = 'PNG'
text = Magick::Draw.new
text.annotate(img, 0, 0, 1, 0, "#{email}") {
self.gravity = Magick::WestGravity
self.pointsize = 18
self.fill = '#000000'
self.kerning = -1
self.font_weight = Magick::BoldWeight
}
img.write file_path
if AWS::S3::S3Object.exists? file_name, bucket.name + path_to_images
puts "file exists (deleting)"
AWS::S3::S3Object.delete file_name, bucket.name + path_to_images, :force => true
end
AWS::S3::S3Object.store file_name,
File.open(file_path),
bucket.name + path_to_images,
:content_type => 'image/png',
:access => :public_read,
:reload => true
`rm #{file_path}`
end
end
Rails provides a mail_to helper.
mail_to "me#domain.com"
# => me#domain.com
mail_to "me#domain.com", "My email", :encode => "javascript"
# => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
mail_to "me#domain.com", "My email", :encode => "hex"
# => My email
mail_to "me#domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email"
# => me_at_domain_dot_com
mail_to "me#domain.com", "My email", :cc => "ccaddress#domain.com",
:subject => "This is an example email"
# => My email
The :encode => "hex" or :encode => "javascript" options are what you are looking for.
I've had the same problem. Here is my solution:
def text_to_image(text,options={})
return if text.blank?
filename=get_text_file_path(text)
unless File.exists?(filename)
gc = Magick::Draw.new
gc.fill = options[:color] unless options[:color].blank?
gc.pointsize options[:size] unless options[:size].blank?
gc.font=options[:font] unless options[:font].blank?
gc.gravity = Magick::CenterGravity
gc.text(0,0, text)
metrics=gc.get_type_metrics(text)
image = Magick::Image.new(metrics.width, metrics.height){
self.background_color = 'transparent'
}
gc.draw(image)
image.write(filename)
end
end
I use after_save callback to update graphic cache of email attribute.

Prawn - how to generalize the common things in the pdf document into a separate module for code re-usage?

I am using the Rails Prawn to generate pdf files. Now that i am able to generate pdf with all necessary things that i need(eg. table, header, footer, logo, borders etc...). Now I need to use the common things(header, foooter, borders) in a method inside separate module and call this method from my original program?
My original program:
travel_report.rb
module TravelReport
include Header
def self.generate(start_ts, end_ts, format_type, obu_ids, interval)
Prawn::Document.generate("public/test.pdf") do
Borders
page_count.times do |i|
go_to_page(i+1)
Header.border
end
Header and Boundary Lines
page_count.times do |i|
go_to_page(i+1)
ask = "public/ashok.jpg"
image ask, :at => [15, 750], :width => 120
alert = "public/alert.jpg"
image alert, :at => [410, 740], :width => 120
end
footer
page_count.times do |i|
go_to_page(i+1)
lazy_bounding_box([bounds.left+30, bounds.bottom + 20], :width => 100) {
text "Bypass Report"
}.draw
end
end
Separate Module for Borders
module Header
#class Cheader < Prawn::Document::BoundingBox
#include Prawn
def self.border
pdf = Prawn::Document.new
pdf.bounding_box([5, 705], :width => 540, :height => 680) do
pdf.stroke_bounds
end
end
#end
end
This code doesnt creates any border... Any idea how to create separate module for this????
#create a separate module
#program
include HeaderFooter
Prawn::Document.generate("public/test.pdf") do |pdf|
pdf.page_count.times do |i|
pdf.go_to_page(i+1)
HeaderFooter.border(pdf)
#render :partial => 'header', :locals => {:ppdf => pdf}
end
#Header and Boundary Lines
pdf.page_count.times do |i|
pdf.go_to_page(i+1)
HeaderFooter.image(pdf)
end
#footer
pdf.page_count.times do |i|
pdf.go_to_page(i+1)
HeaderFooter.footer(pdf)
end
end
create a module to define the methods(header_footer.rb)
module HeaderFooter
#Method for border creation in pdf
def self.border(ppdf)
ppdf.bounding_box([5, 705], :width => 540, :height => 680) do
ppdf.stroke_bounds
end
end
#method to create the logos in the pdf
def self.image(ppdf)
ask = "public/ashok.jpg"
ppdf.image ask, :at => [15, 750], :width => 120
alert = "public/alert.jpg"
ppdf.image alert, :at => [410, 740], :width => 120
end
#method to print footer text in the pdf
def self.footer(ppdf)
ppdf.lazy_bounding_box([ppdf.bounds.left+30, ppdf.bounds.bottom + 20], :width => 100) {
ppdf.text "Bypass Report"
}.draw
end
end
This works fine...

Resources