Skip to content

Instantly share code, notes, and snippets.

@jch
Created May 2, 2013 01:00
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jch/5499505 to your computer and use it in GitHub Desktop.
Save jch/5499505 to your computer and use it in GitHub Desktop.
require 'base64'
require 'nokogiri'
module Rack
# Rack middleware to inline css and images from underlying application so it
# can be proccessed by PDFKit without additional network requests.
#
# For example, this middleware will transform this response:
#
# <html>
# <head>
# <link href="/stylesheet.css" media="all" rel="stylesheet" type="text/css">
# </head>
# <body>
# <img src="/bananas.png" />
# </body>
# </html>
#
# into this response, with all the assets inlined:
#
# <html>
# <head>
# <style>/* contents of /stylesheet.css */</style>
# </head>
# <body>
# <!-- base64 encoded version of /bananas.png -->
# <img src="data:image/png;base64,ZmFrZSBpbWFnZQ==%0A" />
# </body>
# </html>
#
# Usage:
#
# Rack::Builder.app do
# use Rack::PDF::PDFPreflight
# run YourApp
# end
class PDFPreflight
def initialize(app, options={})
@app = app
end
def call(env)
@env = env
response = Rack::Response.new(@app.call(env))
bodies = response.body.map do |body|
doc = Nokogiri::HTML.parse(body)
inline_css(doc)
inline_images(doc)
doc.to_html
end
response.body = bodies
puts response.body
response.finish
end
# Internal: In place replaces `link` elements in `doc` with `style`
# element containing the asset's styles.
#
# Returns nothing.
def inline_css(doc)
doc.css('link').each do |node|
href = node.attr('href')
if href =~ /css$/i
app_response = get(href)
if app_response.ok?
styles = doc.create_element "style"
styles.content = app_response.body.join("\n")
node.replace(styles)
end
end
end
end
# Internal: In place replace `img` elements in `doc` with base64 encoded
# images.
#
# Returns nothing.
def inline_images(doc)
doc.css('img').each do |node|
if src = node.attr('src')
app_response = get(src)
content_type = app_response.headers['Content-Type']
asset = app_response.body.join("")
node['src'] = "data:#{content_type};base64,#{Base64.encode64(asset)}"
end
end
end
# Fetch a resource from the underlying Rack application
#
# Returns a rack response
def get(path)
e = @env.dup
e['PATH'] = path
@app.call(e)
end
end
end
require 'test/unit'
require 'rack/test'
class Rack::PDFPreflightTest < Test::Unit::TestCase
include Rack::Test::Methods
class TestApp
HTML = <<-HTML
<html>
<head>
<link href="/stylesheet.css" media="all" rel="stylesheet" type="text/css">
</head>
<body>
<img src="/foo.png" />
</body>
</html>
HTML
def self.call(env)
case env['PATH']
when /css$/
Rack::Response.new(["/* fake css */"], 200, "Content-Type" => "text/css")
when /png$/
Rack::Response.new(["fake image"], 200, "Content-Type" => "image/png")
else
Rack::Response.new([HTML.clone], 200, "Content-Type" => "text/html")
end
end
end
def app
Rack::Builder.app do
use Rack::PDFPreflight
run TestApp
end
end
def setup
get '/'
@doc = Nokogiri::HTML.parse(last_response.body)
end
def test_inline_css
assert_equal 0, @doc.css('link').size
assert_equal 1, @doc.css('style').size
assert_match @doc.css('style').text, /fake css/
end
def test_inline_images
assert_equal 1, @doc.css('img').size
assert_equal "data:image/png;base64,ZmFrZSBpbWFnZQ==%0A", @doc.css('img').attr('src').to_s
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment