require 'grover/version'
require 'grover/utils'
require 'grover/html_preprocessor'
require 'grover/middleware'
require 'grover/configuration'
require 'schmooze'
#
# Grover interface for converting HTML to PDF
#
class Grover
#
# Processor helper class for calling out to Puppeteer NodeJS library
#
class Processor < Schmooze::Base
dependencies puppeteer: 'puppeteer'
def self.launch_params
ENV['CI'] == 'true' ? "{args: ['--no-sandbox', '--disable-setuid-sandbox']}" : ''
end
method :convert_pdf, Utils.squish(<<-FUNCTION)
async (url, options) => {
let browser;
try {
browser = await puppeteer.launch(#{launch_params});
const page = await browser.newPage();
if (url.match(/^http/i)) {
await page.goto(url, { waitUntil: 'networkidle2' });
} else {
await page.goto(`data:text/html,${url}`, { waitUntil: 'networkidle0' });
}
const emulateMedia = options.emulateMedia; delete options.emulateMedia;
if (emulateMedia) {
await page.emulateMedia(emulateMedia);
}
return await page.pdf(options);
} finally {
if (browser) {
await browser.close();
}
}
}
FUNCTION
end
private_constant :Processor
DISPLAY_URL_PLACEHOLDER = '{{display_url}}'.freeze
DEFAULT_HEADER_TEMPLATE = "
".freeze
DEFAULT_FOOTER_TEMPLATE = Utils.strip_heredoc(<<-HTML).freeze
#{DISPLAY_URL_PLACEHOLDER}
/
HTML
#
# @param [String] url URL of the page to convert
# @param [Hash] options Optional parameters to pass to PDF processor
# see https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions
#
def initialize(url, options = {})
@url = url
@options = Grover.configuration.options.merge options
@root_path = @options.delete :root_path
end
#
# Request URL with provided options and create PDF
#
# @param [String] path Optional path to write the PDF to
# @return [String] The resulting PDF data
#
def to_pdf(path = nil)
result = processor.convert_pdf @url, normalized_options(path)
result['data'].pack('c*')
end
#
# Instance inspection
#
def inspect
format(
'#<%s:0x%p @url="%s">',
class_name: self.class.name,
object_id: object_id,
url: @url
)
end
#
# Configuration for the conversion
#
def self.configuration
@configuration ||= Configuration.new
end
def self.configure
yield(configuration)
end
private
def root_path
@root_path ||= Dir.pwd
end
def processor
Processor.new(root_path)
end
def options_with_template_fix
options = @options.dup
display_url = options.delete :display_url
if display_url
options[:footer_template] ||= DEFAULT_FOOTER_TEMPLATE
%i[header_template footer_template].each do |key|
next unless options[key].is_a? String
options[key] = options[key].gsub(DISPLAY_URL_PLACEHOLDER, display_url)
end
end
options
end
def normalized_options(path)
options = Utils.normalize_object options_with_template_fix
options.merge! meta_options unless url_source?
options['path'] = path if path
options
end
#
# Extract out options from meta tags in the source - based on code from PDFKit project
#
def meta_options
meta_opts = {}
@url.scan(/]*>/) do |meta|
tag_name = meta[/name=["']#{Grover.configuration.meta_tag_prefix}([a-z_-]+)["']/, 1]
next if tag_name.nil?
Utils.deep_assign meta_opts, tag_name.split('-'), meta[/content=["']([^"']+)["']/, 1]
end
meta_opts
end
def url_source?
@url.match(/^http/i)
end
end