A couple of weeks ago, I was working on a feature in Camaloon, which I ended up not using. However, I thought it would still be fun to share.
So, let's get to the issue without the boilerplate as it's late at night and I have to rise early :).
Problem:
I was trying to implement a third-party API and hopefully get a Base64 encoded PDF file of different documents I was trying to fetch. Unfortunately, the third party API didn't provide the PDF files but instead, images.
So, I thought alright I will just use Prawn and shove all images inside the Prawn pdf document and be done with it. Buttttt, images were in GIF format which I had to convert them to Prawn supported formats such as PNG/JPEG.
So, seeing these issues, Base64ImageToPdfConverter
was born. I wanted to keep the class generic in the case in the future we need to reuse it.
Solution: Accept an array of Base64 encoded images, check if they need a conversion to a PNG(Prawn supported format).Convert them to PNG in case they need conversion and include it in the prawn document, if not, include them directly.
Let's create our class.
require 'prawn'
require 'tempfile'
require 'base64'
class Base64ImageToPdfConverter
PRAWN_SUPPORTED_MIME_TYPES= ['image/png', 'image/jpeg'].freeze
def initialize(base64_images:)
@base64_images = base64_images
end
def run!
return if base64_images.empty?
open_tempfile(['combined-pdf', '.pdf']) do |pdf_file|
base64_images.each do |base64_image|
with_image_path(base64_image) do |image_file|
# Process and Insert image to Prawn document
end
end
# Render content to Prawn Document
# Encode PDF in base64
end
end
private
attr_reader :base64_images
def open_tempfile(*args)
f = Tempfile.new(*args)
yield(f)
ensure
f.close!
end
def with_image_path(base64_image)
open_tempfile('image') do |f|
f.write Base64.decode64(base64_image)
f.flush
yield f
end
end
end
So what the hell was that? Here, we are creating one temp file for final PDF that we will combine all images in and a temp file for each image, decoding Base64 and writing to it as we will need it to work with ImageMagick conversion soon.
Let's add our conversion logic.
require 'prawn'
require 'tempfile'
require 'base64'
class Base64ImageToPdfConverter
PRAWN_SUPPORTED_MIME_TYPES= ['image/png', 'image/jpeg'].freeze
def initialize(base64_images:)
@base64_images = base64_images
end
def base64_pdf
return if base64_images.empty?
open_tempfile(['combined-pdf', '.pdf']) do |pdf_file|
base64_images.each do |base64_image|
with_image_path(base64_image) do |image_file|
insert_image(image(image_file.path))
end
end
# render and return the Base64 encoded PDF.
prawn_document.render_file pdf_file.path
base64_encoded(pdf_file.path)
end
end
private
attr_reader :base64_images
attr_accessor :prawn_document
def insert_image(string_io)
prawn_document.start_new_page
prawn_document.image string_io, position: :center
end
def image(file_path)
if need_conversion?(file_path)
# We are using imagemagick convert command to convert to png
system "convert #{file_path} #{file_path}.png"
# As we do not need a Tempfile but an object that responds to
# read and write to use in Prawn document. We can use StringIO and
# unlink tempfile.
string_io= StringIO.new(File.read("#{file_path}.png"))
File.unlink("#{file_path}.png")
else
string_io= StringIO.new(File.read(file_path))
end
string_io
end
def need_conversion?(file_path)
!PRAWN_SUPPORTED_MIME_TYPES.include?(mime_type(file_path))
end
def mime_type(file_path)
`file #{file_path} --mime-type -b`.strip
end
def prawn_document
@prawn_document ||= Prawn::Document.new margin: 0,
skip_page_creation: true
end
def base64_encoded(file_path)
Base64.encode64(File.read(file_path))
end
def open_tempfile(*args)
f = Tempfile.new(*args)
yield(f)
ensure
f.close!
end
def with_image_path(base64_image)
open_tempfile('image') do |f|
f.write Base64.decode64(base64_image)
f.flush
yield f
end
end
end
base64_gifs = ['R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=']
converter = Base64ImageToPdfConverter.new(base64_images: base64_gifs)
puts converter.base64_pdf
So here we go, we check for each image's Mime Type, convert them using ImageMagick's convert
command to PNG
and include it in Prawn document. We return the Base64 encoded PDF.
You can write the result to a file if you would like to create one:
base64_gifs = ['R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=']
File.open "your_pdf_document.pdf", "wb" do |file|
file.write Base64.decode64(Base64ImageToPdfConverter.new(base64_images: base64_gifs)
.base64_pdf)
file.close
end
Credits to Roger for the review of the PR and shaping the class to be a bit better than its first iteration :D
Top comments (0)