class PDFKit

Constants

REPEATABLE_OPTIONS

Pulled from: github.com/wkhtmltopdf/wkhtmltopdf/blob/ebf9b6cfc4c58a31349fb94c568b254fac37b3d3/README_WKHTMLTOIMAGE#L27

SPECIAL_OPTIONS
VERSION

Attributes

configuration[RW]
options[R]
source[RW]
stylesheets[RW]

Public Class Methods

configure() { |configuration| ... } click to toggle source
# File lib/pdfkit/configuration.rb, line 51
def self.configure
  yield(configuration)
end
new(url_file_or_html, options = {}) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 22
def initialize(url_file_or_html, options = {})
  @source = Source.new(url_file_or_html)

  @stylesheets = []

  @options = PDFKit.configuration.default_options.merge(options)
  @options.delete(:quiet) if PDFKit.configuration.verbose?
  @options.merge! find_options_in_meta(url_file_or_html) unless source.url?
  @options = normalize_options(@options)

  raise NoExecutableError.new unless File.exists?(PDFKit.configuration.wkhtmltopdf)
end

Public Instance Methods

command(path = nil) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 35
def command(path = nil)
  args = @options.to_a.flatten.compact
  shell_escaped_command = [executable, shell_escape_for_os(args)].join ' '

  # In order to allow for URL parameters (e.g. https://www.google.com/search?q=pdfkit) we do
  # not escape the source. The user is responsible for ensuring that no vulnerabilities exist
  # in the source. Please see https://github.com/pdfkit/pdfkit/issues/164.
  input_for_command = @source.to_input_for_command
  output_for_command = path ? Shellwords.shellescape(path) : '-'

  "#{shell_escaped_command} #{input_for_command} #{output_for_command}"
end
executable() click to toggle source
# File lib/pdfkit/pdfkit.rb, line 48
def executable
  default = PDFKit.configuration.wkhtmltopdf
  return default if default !~ /^\// # its not a path, so nothing we can do
  if File.exist?(default)
    default
  else
    default.split('/').last
  end
end
to_file(path) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 75
def to_file(path)
  self.to_pdf(path)
  File.new(path)
end
to_pdf(path=nil) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 58
def to_pdf(path=nil)
  append_stylesheets

  invoke = command(path)

  result = IO.popen(invoke, "wb+") do |pdf|
    pdf.puts(@source.to_s) if @source.html?
    pdf.close_write
    pdf.gets(nil) if path.nil?
  end

  # $? is thread safe per
  # http://stackoverflow.com/questions/2164887/thread-safe-external-process-in-ruby-plus-checking-exitstatus
  raise "command failed (exitstatus=#{$?.exitstatus}): #{invoke}" if empty_result?(path, result) or !successful?($?)
  return result
end

Protected Instance Methods

append_stylesheets() click to toggle source
# File lib/pdfkit/pdfkit.rb, line 114
def append_stylesheets
  raise ImproperSourceError.new('Stylesheets may only be added to an HTML source') if stylesheets.any? && !@source.html?

  stylesheets.each do |stylesheet|
    if @source.to_s.match(/<\/head>/)
      @source = Source.new(@source.to_s.gsub(/(<\/head>)/) {|s| style_tag_for(stylesheet) + s })
    else
      @source.to_s.insert(0, style_tag_for(stylesheet))
    end
  end
end
empty_result?(path, result) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 189
def empty_result?(path, result)
  (path && File.size(path) == 0) || (path.nil? && result.to_s.strip.empty?)
end
error_handling?() click to toggle source
# File lib/pdfkit/pdfkit.rb, line 193
def error_handling?
  @options.key?('--ignore-load-errors') ||
    # wkhtmltopdf v0.10.0 beta4 replaces ignore-load-errors with load-error-handling
    # https://code.google.com/p/wkhtmltopdf/issues/detail?id=55
    %w(skip ignore).include?(@options['--load-error-handling'])
end
find_options_in_meta(content) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 87
def find_options_in_meta(content)
  # Read file if content is a File
  content = content.read if content.is_a?(File)

  found = {}
  content.scan(/<meta [^>]*>/) do |meta|
    if meta.match(/name=["']#{PDFKit.configuration.meta_tag_prefix}/)
      name = meta.scan(/name=["']#{PDFKit.configuration.meta_tag_prefix}([^"']*)/)[0][0].split
      found[name] = meta.scan(/content=["']([^"'\]+)["']/)[0][0]
    end
  end

  tuple_keys = found.keys.select { |k| k.is_a? Array }
  tuple_keys.each do |key|
    value = found.delete key
    new_key = key.shift
    found[new_key] ||= {}
    found[new_key][key] = value
  end

  found
end
host_is_windows?() click to toggle source
# File lib/pdfkit/pdfkit.rb, line 200
def host_is_windows?
  @host_is_windows ||= !(RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince/).nil?
end
normalize_arg(arg) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 149
def normalize_arg(arg)
  arg.to_s.downcase.gsub(/[^a-z0-9]/,'-')
end
normalize_options(options) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 126
def normalize_options(options)
  normalized_options = {}

  options.each do |key, value|
    next if !value

    # The actual option for wkhtmltopdf
    normalized_key = normalize_arg key
    normalized_key = "--#{normalized_key}" unless SPECIAL_OPTIONS.include?(normalized_key)

    # If the option is repeatable, attempt to normalize all values
    if REPEATABLE_OPTIONS.include? normalized_key
      normalize_repeatable_value(normalized_key, value) do |normalized_unique_key, normalized_value|
        normalized_options[normalized_unique_key] = normalized_value
      end
    else # Otherwise, just normalize it like usual
      normalized_options[normalized_key] = normalize_value(value)
    end
  end

  normalized_options
end
normalize_repeatable_value(option_name, value) { |[option_name, normalize_value(key)], normalize_value(val)| ... } click to toggle source
# File lib/pdfkit/pdfkit.rb, line 168
def normalize_repeatable_value(option_name, value)
  case value
  when Hash, Array
    value.each do |(key, val)|
      yield [[option_name, normalize_value(key)], normalize_value(val)]
    end
  else
    yield [[option_name, normalize_value(value)], nil]
  end
end
normalize_value(value) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 153
def normalize_value(value)
  case value
  when nil
    nil
  when TrueClass, 'true' #ie, ==true, see http://www.ruby-doc.org/core-1.9.3/TrueClass.html
    nil
  when Hash
    value.to_a.flatten.collect{|x| normalize_value(x)}.compact
  when Array
    value.flatten.collect{|x| x.to_s}
  else
    (host_is_windows? && value.to_s.index(' ')) ? "'#{ value.to_s }'" : value.to_s
  end
end
shell_escape_for_os(args) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 204
def shell_escape_for_os(args)
  if (host_is_windows?)
    # Windows reserved shell characters are: & | ( ) < > ^
    # See http://technet.microsoft.com/en-us/library/cc723564.aspx#XSLTsection123121120120
    args.map { |arg| arg.gsub(/([&|()<>^])/,'^\1') }.join(" ")
  else
    args.shelljoin
  end
end
style_tag_for(stylesheet) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 110
def style_tag_for(stylesheet)
  "<style>#{File.read(stylesheet)}</style>"
end
successful?(status) click to toggle source
# File lib/pdfkit/pdfkit.rb, line 179
def successful?(status)
  return true if status.success?

  # Some of the codes: https://code.google.com/p/wkhtmltopdf/issues/detail?id=1088
  # returned when assets are missing (404): https://code.google.com/p/wkhtmltopdf/issues/detail?id=548
  return true if status.exitstatus == 2 && error_handling?

  false
end