Skip to content

Instantly share code, notes, and snippets.

@swistak
Last active January 3, 2016 06:38
Show Gist options
  • Save swistak/8423518 to your computer and use it in GitHub Desktop.
Save swistak/8423518 to your computer and use it in GitHub Desktop.
require 'fileutils'
require 'logger'
require 'erb'
# Pdf generation service based on wkhtmltopdf.
class PdfGenerator
class WkhtmltopdfError < RuntimeError; end
DPI = 96 # Pixels in inch
DPC = (DPI / 2.54) # = 37.795 Pixels in centimeter
# See http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html
#
# Options without argument just take true, rest should have string as value.
# To disable option set it to false.
PDF_OPTIONS = {
'quiet' => true,
# Header is disabled.
# 'header-html' => 'header.html',
# 'footer-html' => 'footer.html',
'print-media-type' => true,
# 'disable-smart-shrinking' => true,
'page-size' => 'A4',
'margin-top' => '0.5cm',
'margin-right' => '0.5cm',
'margin-bottom' => '1cm',
'margin-left' => '0.5cm',
'encoding' => "UTF-8",
}
PDF_DIR = 'pdfs'
FileUtils.mkdir_p(PDF_DIR)
attr_accessor :error_message
attr_accessor :note
def logger
defined?(Rails) ? Rails.logger : (@logger ||= Logger.new($stdout))
end
def pdf_options
PDF_OPTIONS
end
def note_url
'index.html'
end
def pdf_path
File.join(PDF_DIR, "ramki.pdf")
end
def wkhtmltopdf
@wkhtmltopdf ||= begin
pdf_command = [
`ls -1 bin/wkhtmltopdf* 2>/dev/null`.split("\n").last,
`which wkhtmltopdf`
].detect{|c| !c.nil? }
raise("Cannot locate wkhtmltopdf in #{Dir.pwd} or $PATH") if pdf_command.nil?
pdf_command.strip
end
end
def xvfb_wrapper
"" #'xvfb-run -s "-screen 0, 1600x1200x24"'
end
# wkhtmltopdf [OPTIONS]... <input file> [More input files] <output file>
def pdf_command
normalized_options = pdf_options.map{|k, v|
v ? "--#{k} #{v == true ? "" : v}" : nil
}.compact.join(" ")
"#{xvfb_wrapper} #{wkhtmltopdf} #{normalized_options} #{note_url} #{pdf_path}"
end
def render
File.open('index.html', 'w') do |f|
f.write ERB.new(File.read('index.html.erb')).result
end
end
# Generates a pdf.
def generate
render
perform
end
# Performs a pdf generation.
def perform
logger.debug "Running: " + pdf_command
# See http://mentalized.net/journal/2010/03/08/5_ways_to_run_commands_from_ruby/
redirection = " 2>&1"
@out = IO.popen(pdf_command + redirection, "wb+") do |pdf|
pdf.close_write
pdf.gets(nil)
end
unless $? == 0
self.error_message = "Failed to generate pdf via: #{wkhtmltopdf}. Exit code: #{$?}."
raise(WkhtmltopdfError, error_message, caller[1..-1])
end
end
end
if __FILE__ == $0
PdfGenerator.new.generate
end
/*
* Printing styles based on HTML5 Boilerplate
*
* What follows is the result of much research on cross-browser styling.
* Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
* Kroc Camen, and the H5BP dev community and team.
*
* Additional modifications and printing classes by Marcin Raczkowski
*/
/* ==========================================================================
Base styles: opinionated defaults + Print styles.
Inlined to avoid required HTTP connection: h5bp.com/r
========================================================================== */
html,
body,
button,
input,
select,
textarea {
color: #000;/* Black prints faster: h5bp.com/s */
background-color: #fff;
}
html, body {
font-size: 9pt;
line-height: 1.4;
font-family: Georgia, serif;
}
@media screen {
html, body {
font-size: 10px;
}
}
body {
/* Print related */
box-shadow: none !important;
text-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
/*
* Don't show links for images, or javascript/internal links
*/
.ir a:after,
a[href^="javascript:"]:after,
a[href^="#"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
thead {
display: table-header-group; /* h5bp.com/t */
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
/*
* A better looking default horizontal rule
*/
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
/*
* Remove the gap between images and the bottom of their containers: h5bp.com/i/440
*/
img {
vertical-align: middle;
}
/*
* Remove default fieldset styles.
*/
fieldset {
border: 0;
margin: 0;
padding: 0;
}
/*
* Allow only vertical resizing of textareas.
*/
textarea {
resize: vertical;
}
/* ==========================================================================
Additional print styles. Usefull for previews & document authoring.
========================================================================== */
body {
margin: 0 auto;
position: relative;
}
body.vertical {
width: 21cm; /* A4 sized page with 0.5 cm margin/padding */
padding: 0.5cm 0.5cm 1cm; /* USe these values if smart shrinking is disabled,they must mach margins */
}
body.horizontal {
width: 29.7cm; /* A4(210x297) sized page with 0.5 cm margin top & 1cm margin bottom */
padding: 0.5cm 0.5cm 1cm;
}
body.vertical .page {
width: 20cm; /* A4 sized page with 0.5 cm margin */
height: 28.1cm; /* 29.7 height - 0.5 top margin - 1cm bottom margin - 0.1 for rounding errors. */
}
body.horizontal .page {
width: 29.7cm; /* A4 sized page with 0.5 cm margin */
height: 19.4cm; /* 21 (A4 H) - 0.5 (top margin) - 1 (bottom margin) - 0.1(rounding errors) */
}
/* gives us roughly 200x287 or 287x200 cm working area */
body .page {
position: relative;
margin: 0;
page-break-inside: avoid;
}
@media screen {
body .page {
overflow: hidden;
padding: 2px;
outline: black 1px solid;
outline-offset: 0.5cm;
margin-top: 1cm;
margin-bottom: 1.5cm;
}
}
/* Compact definition list */
dl.compact {
margin: 0;
font-size: 0.9em;
width: 5cm;
}
dl.compact dt {
font-weight: bold;
clear: left; float: left;
width: 2.25cm;
white-space: nowrap;
overflow: hidden;
}
dl.compact dd {
float: left;
margin: 0 0 0 20px;
white-space: nowrap;
overflow: hidden;
}
/* Fixed width tables + some default table styles */
table.fixed {
table-layout: fixed;
}
table.fixed td,
table.fixed th {
white-space: nowrap;
overflow: visible;
word-break: break-all;
}
table td,
table th {
padding: 0.3em 0.6em;
border: 1px solid black;
white-space: nowrap;
}
table th {
padding: 0.3em 0;
text-align: center;
}
table.zebra {
width: 100%;
border: 2px solid black;
}
table.zebra th { background-color: #aaa !important; }
table.zebra td { background-color: #fff !important; }
table.zebra td:nth-child(2n) { background-color: #ccc !important; }
/* ==========================================================================
Helper classes
========================================================================== */
.hidden {
display: none !important;
visibility: hidden;
}
/*
* Hide visually and from screenreaders, but maintain layout
*/
.invisible {
visibility: hidden;
}
/*
* Clearfix: contain floats
*
* For modern browsers
* 1. The space content is one way to avoid an Opera bug when the
* `contenteditable` attribute is included anywhere else in the document.
* Otherwise it causes space to appear at the top and bottom of elements
* that receive the `clearfix` class.
* 2. The use of `table` rather than `block` is only necessary if using
* `:before` to contain the top-margins of child elements.
*/
.clearfix:before,
.clearfix:after {
content: " "; /* 1 */
display: table; /* 2 */
}
.clearfix:after {
clear: both;
}
/*
* For IE 6/7 only
* Include this rule to trigger hasLayout and contain floats.
*/
.clearfix {
*zoom: 1;
}
.ruler {
border-left: 1px solid black;
border-bottom: 1px solid black;
border-right: 1px solid black;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment