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)