Created
May 2, 2013 01:00
-
-
Save jch/5499505 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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