Class: PdfTools

Inherits:
Object
  • Object
show all
Defined in:
lib/pdf_tools.rb

Overview

High-level PDF helpers built on top of PdfCombinator. Accepts a
heterogeneous list of inputs (URIs, file paths, Upload records,
raw PDF blobs, or anything that responds to #path), normalises
them onto disk, then merges and optionally re-orients the result.

Class Method Summary collapse

Class Method Details

.combine(files = [], output_mode: :file, raw_pdf_data: false, output_file_path: nil, remove_originals: false, fix_rotation: nil, orientation: nil) ⇒ Object

files can be a an array of

  • URI
  • File Path
  • Upload
  • Anything that responds to .path
    alternatives, if you specify raw_pdf_data true then files is interpreted as an array of pdf blobs


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/pdf_tools.rb', line 13

def self.combine(files = [], output_mode: :file, raw_pdf_data: false, output_file_path: nil, remove_originals: false, fix_rotation: nil, orientation: nil)
  raise 'Invalid orientation specified' if orientation && !orientation.in?(%i[portrait landscape])

  output_file_path ||= Upload.temp_location("combined_#{Time.current.to_i}.pdf")
  pdfs = []
  files.compact.each do |f|
    if raw_pdf_data
      # Store blob in a temp location
      temp_file = Tempfile.new(%w[combined pdf], binmode: true)
      temp_file.write(f)
      temp_file.flush
      temp_file.fsync
      pdfs << temp_file.path
    elsif f.is_a? String
      # Url or Filepath?
      if f&.match?(URI::DEFAULT_PARSER.make_regexp) # URI remote file
        tmp_f = Down::Http.download(f) { |client| client.timeout(read: 120) }
        pdfs << tmp_f.path
      else # FilePath
        pdfs << f
      end
    elsif f.is_a? Upload
      # Use our new convenient method
      pdfs << f.to_file
    elsif f.respond_to? :path
      pdfs << f.path
    end
  end

  # Only combine file that exist
  pdfs = pdfs.compact.select { |f| File.exist?(f) }.map(&:to_s)
  Rails.logger.debug { "PdfTools.combine: pdfs: #{pdfs.inspect}, after pdfs.select{|f| f and File.exist?f }" }
  new_pdf = PdfCombinator.new
  pdfs.select { |f| f && File.exist?(f) }.each do |pdf_file_path|
    new_pdf << PdfCombinator.load(pdf_file_path)
  rescue HexaPDF::Error => e
    # Carrier sometimes returns a corrupt label PDF (no trailer, truncated, etc.).
    # Skip the bad file and keep batching so one bad shipment doesn't kill the
    # entire batched combo PDF (AppSignal #5196).
    ErrorReporting.warning(e, pdf_file_path: pdf_file_path, message: 'PdfTools.combine skipping malformed PDF')
  end

  # If you specify the orientation and the orientation mode does not match, fix it
  new_pdf.fix_orientation(orientation) if orientation || fix_rotation

  return_result = nil
  if output_mode == :file
    new_pdf.save output_file_path
    return_result = output_file_path
  else # Return a blob
    return_result = new_pdf.to_pdf
  end

  if remove_originals && File.exist?(output_file_path)
    # Remove all originals
    pdfs.each { |f| FileUtils.rm(f) }
  end

  return_result
end