# frozen_string_literal: true require_dependency "renalware/letters" require "attr_extras" module Renalware module Letters module Printing # Build PDF for printing. We are targetting an envelope stuffer so there will be an address # cover sheet in front of each letter. # So for a letter where the patient is the main recipient, and we CC to the GP (no practice # email address so snail mailing) and 1 extra CC (a contact) the output will look like # this: # # 1. Patient address cover sheet # 2. Letter # 3. GP address cover sheet # 2. Letter # 5. Contact address cover sheet # 2. Letter # # Note however the we always pad each item (address sheet, letter) with blank pages to make # sure duplex printing does not cause the next page recipient's content to be rendered on the # back of the ast page of the previous letter for example/ # # Compiles print content for all input letters and outputs the merged PDF to output_file. # Allows batch printing if you want to print say all 2 page letters together. # The caller may choose for example to send the output_file could be sent back to the browser # for manual printing using (eg using send_file) or copy the merged PDF to a folder for # automated printing. class CreatePdfByInterleavingAddressSheetAndLetterForEachRecipient pattr_initialize [:letters!, :output_file!] def call # There is a choice of two methods of building the merged PDF. See pros and cons. # The other is UsingSeparatePdfForEachLetterSection. UsingOnePdfForAllLetterSections.new( letters: letters, output_file: output_file ).call end # For each letter, uses whkhtmltopdf to build the entire print content, ie all address # sheets and required instance of the letter are created as one fresh pdf (no cache will be # used). # # Pros: # - Quicker: For each letter only one PDF is generated, and although the generation is slow # as it does not use the previously cached letter PDF, and it has to render all the letter # sections, including the letter itself, for every recipient, its stil faster then # rendering separate PDFs fo each section. # Cons: # - wastes some resources because it does not use the cache and renders same letter # multiple times in the views # - cannot be 100% sure the number of pages in the final PDF will be extactly what we # predict, so requires a check to load the pdf into a reader and validate the page_count class UsingOnePdfForAllLetterSections include PdfCombining pattr_initialize [:letters!, :output_file!] def call in_a_temporary_folder do |dir| Array(letters).each do |letter| if letter.page_count.to_i < 1 raise(ArgumentError, "letter.page_count not set on letter.id=#{letter.id}") end letter_filename = create_letter_pdf_in(dir, letter) files << letter_filename end combine_multiple_pdfs_into_one(dir, output_file) # For debuging: # FileUtils.cp output_file, "/Users/tim/Desktop/x.pdf" # `open /Users/tim/Desktop/x.pdf` end end private def create_letter_pdf_in(dir, letter) filename = "letter_#{letter.id}.pdf" path = dir.join(filename) File.open(path, "wb") { |file| file.write(DuplexInterleavedPdfRenderer.call(letter)) } filename end end # This approach generates a PDF for each address cover sheet and each # letter. It shells to ghostscript to merge the PDFs together in # the right order. # # Pros: # - uses the letter PDF already in the cache from when we rendered it to deduce the page # count after the letter was archived # - uses very little memory as PDF concatenation done on disk # - concatentation itslef reasonable fast # # Cons: # - slow because it shells to wkhtmltopdf for each cover sheet, and these are unklikely to # be found in tha cache. So for main recip and 2 CCs it wil call wkhtmltopdf 3 times to # render the address cover sheets, and the letter PDF should be pulled from cache so that # is quick at least. # class UsingSeparatePdfForEachLetterSection include PdfCombining pattr_initialize [:letters!, :output_file!] def call in_a_temporary_folder do |dir| Array(letters).each do |letter| letter_filename = create_letter_pdf_in(dir, letter) PrintableRecipients.for(letter).each do |recipient| files << create_cover_sheet_for(recipient, letter, dir) files << letter_filename end end combine_multiple_pdfs_into_one(dir, output_file) end end private def create_letter_pdf_in(dir, letter) filename = "letter_#{letter.id}.pdf" path = dir.join(filename) File.open(path, "wb") { |file| file.write(PdfRenderer.call(letter)) } filename end def create_cover_sheet_for(recipient, _letter, dir) filename = "recipient_#{recipient.id}.pdf" path = dir.join(filename) File.open(path, "wb") { |file| file.write(RecipientAddressPdfRenderer.call(recipient)) } filename end end end end end end